diff --git a/models/secret/secret.go b/models/secret/secret.go index 41e860d7f6640..b326e0332b5b8 100644 --- a/models/secret/secret.go +++ b/models/secret/secret.go @@ -64,8 +64,8 @@ func init() { } func (s *Secret) Validate() error { - if s.OwnerID == 0 && s.RepoID == 0 { - return errors.New("the secret is not bound to any scope") + if s.OwnerID != 0 && s.RepoID != 0 { + return errors.New("a secret should not be bound to an owner and a repository at the same time") } return nil } @@ -80,12 +80,8 @@ type FindSecretsOptions struct { func (opts FindSecretsOptions) ToConds() builder.Cond { cond := builder.NewCond() - if opts.OwnerID > 0 { - cond = cond.And(builder.Eq{"owner_id": opts.OwnerID}) - } - if opts.RepoID > 0 { - cond = cond.And(builder.Eq{"repo_id": opts.RepoID}) - } + cond = cond.And(builder.Eq{"owner_id": opts.OwnerID}) + cond = cond.And(builder.Eq{"repo_id": opts.RepoID}) if opts.SecretID != 0 { cond = cond.And(builder.Eq{"id": opts.SecretID}) } diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 9af4d70171c50..0b3f1a18103af 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -3495,6 +3495,7 @@ deletion.description = Removing a secret is permanent and cannot be undone. Cont deletion.success = The secret has been removed. deletion.failed = Failed to remove secret. management = Secrets Management +instance_desc = Although secrets will be masked if users try to print them in Actions workflows, this is not absolutely secure. Users can still obtain the contents of secrets by writing malicious workflows, so please ensure that global secrets are not used by people you do not trust. Otherwise, please use organization/user-level or repository-level secrets to limit their scope of use. Alternatively, if it's acceptable to expose their contents, please use global variables. [actions] actions = Actions diff --git a/routers/api/actions/runner/utils.go b/routers/api/actions/runner/utils.go index a7cb31288cab1..4cf71cf7f4477 100644 --- a/routers/api/actions/runner/utils.go +++ b/routers/api/actions/runner/utils.go @@ -68,18 +68,24 @@ func getSecretsOfTask(ctx context.Context, task *actions_model.ActionTask) map[s return secrets } - ownerSecrets, err := db.Find[secret_model.Secret](ctx, secret_model.FindSecretsOptions{OwnerID: task.Job.Run.Repo.OwnerID}) + globalSecrets, err := db.Find[secret_model.Secret](ctx, secret_model.FindSecretsOptions{OwnerID: 0, RepoID: 0}) + if err != nil { + log.Error("find global secrets: %v", err) + // go on + } + ownerSecrets, err := db.Find[secret_model.Secret](ctx, secret_model.FindSecretsOptions{OwnerID: task.Job.Run.Repo.OwnerID, RepoID: 0}) if err != nil { log.Error("find secrets of owner %v: %v", task.Job.Run.Repo.OwnerID, err) // go on } - repoSecrets, err := db.Find[secret_model.Secret](ctx, secret_model.FindSecretsOptions{RepoID: task.Job.Run.RepoID}) + repoSecrets, err := db.Find[secret_model.Secret](ctx, secret_model.FindSecretsOptions{OwnerID: 0, RepoID: task.Job.Run.RepoID}) if err != nil { log.Error("find secrets of repo %v: %v", task.Job.Run.RepoID, err) // go on } - for _, secret := range append(ownerSecrets, repoSecrets...) { + // Level precedence: Repo > Org / User > Global + for _, secret := range append(globalSecrets, append(ownerSecrets, repoSecrets...)...) { if v, err := secret_module.DecryptSecret(setting.SecretKey, secret.Data); err != nil { log.Error("decrypt secret %v %q: %v", secret.ID, secret.Name, err) // go on diff --git a/routers/api/v1/admin/action.go b/routers/api/v1/admin/action.go new file mode 100644 index 0000000000000..edb18ec28dc36 --- /dev/null +++ b/routers/api/v1/admin/action.go @@ -0,0 +1,103 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package admin + +import ( + "errors" + "net/http" + + "code.gitea.io/gitea/modules/context" + api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/modules/util" + "code.gitea.io/gitea/modules/web" + secret_service "code.gitea.io/gitea/services/secrets" +) + +// CreateOrUpdateSecret create or update one secret in instance scope +func CreateOrUpdateSecret(ctx *context.APIContext) { + // swagger:operation PUT /admin/actions/secrets/{secretname} admin updateAdminSecret + // --- + // summary: Create or Update a secret value in instance scope + // consumes: + // - application/json + // produces: + // - application/json + // parameters: + // - name: secretname + // in: path + // description: name of the secret + // type: string + // required: true + // - name: body + // in: body + // schema: + // "$ref": "#/definitions/CreateOrUpdateSecretOption" + // responses: + // "201": + // description: secret created + // "204": + // description: secret updated + // "400": + // "$ref": "#/responses/error" + // "404": + // "$ref": "#/responses/notFound" + + opt := web.GetForm(ctx).(*api.CreateOrUpdateSecretOption) + + _, created, err := secret_service.CreateOrUpdateSecret(ctx, 0, 0, ctx.Params("secretname"), opt.Data) + if err != nil { + if errors.Is(err, util.ErrInvalidArgument) { + ctx.Error(http.StatusBadRequest, "CreateOrUpdateSecret", err) + } else if errors.Is(err, util.ErrNotExist) { + ctx.Error(http.StatusNotFound, "CreateOrUpdateSecret", err) + } else { + ctx.Error(http.StatusInternalServerError, "CreateOrUpdateSecret", err) + } + return + } + + if created { + ctx.Status(http.StatusCreated) + } else { + ctx.Status(http.StatusNoContent) + } +} + +// DeleteSecret delete one secret in instance scope +func DeleteSecret(ctx *context.APIContext) { + // swagger:operation DELETE /admin/actions/secrets/{secretname} admin deleteAdminSecret + // --- + // summary: Delete a secret in instance scope + // consumes: + // - application/json + // produces: + // - application/json + // parameters: + // - name: secretname + // in: path + // description: name of the secret + // type: string + // required: true + // responses: + // "204": + // description: secret deleted + // "400": + // "$ref": "#/responses/error" + // "404": + // "$ref": "#/responses/notFound" + + err := secret_service.DeleteSecretByName(ctx, 0, 0, ctx.Params("secretname")) + if err != nil { + if errors.Is(err, util.ErrInvalidArgument) { + ctx.Error(http.StatusBadRequest, "DeleteSecret", err) + } else if errors.Is(err, util.ErrNotExist) { + ctx.Error(http.StatusNotFound, "DeleteSecret", err) + } else { + ctx.Error(http.StatusInternalServerError, "DeleteSecret", err) + } + return + } + + ctx.Status(http.StatusNoContent) +} diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go index cb1803f7c652f..b3791971aed3b 100644 --- a/routers/api/v1/api.go +++ b/routers/api/v1/api.go @@ -948,7 +948,6 @@ func Routes() *web.Route { Post(bind(api.CreateEmailOption{}), user.AddEmail). Delete(bind(api.DeleteEmailOption{}), user.DeleteEmail) - // manage user-level actions features m.Group("/actions", func() { m.Group("/secrets", func() { m.Combo("/{secretname}"). @@ -1499,6 +1498,11 @@ func Routes() *web.Route { }, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryOrganization), orgAssignment(false, true), reqToken(), reqTeamMembership()) m.Group("/admin", func() { + m.Group("/actions/secrets", func() { + m.Combo("/{secretname}"). + Put(bind(api.CreateOrUpdateSecretOption{}), admin.CreateOrUpdateSecret). + Delete(admin.DeleteSecret) + }) m.Group("/cron", func() { m.Get("", admin.ListCronTasks) m.Post("/{task}", admin.PostCronTask) diff --git a/routers/api/v1/org/secrets.go b/routers/api/v1/org/secrets.go index ddc74d865b183..d58ecaa9490f3 100644 --- a/routers/api/v1/org/secrets.go +++ b/routers/api/v1/org/secrets.go @@ -67,7 +67,7 @@ func ListActionsSecrets(ctx *context.APIContext) { ctx.JSON(http.StatusOK, apiSecrets) } -// create or update one secret of the organization +// CreateOrUpdateSecret create or update one secret in an organization func CreateOrUpdateSecret(ctx *context.APIContext) { // swagger:operation PUT /orgs/{org}/actions/secrets/{secretname} organization updateOrgSecret // --- @@ -93,9 +93,9 @@ func CreateOrUpdateSecret(ctx *context.APIContext) { // "$ref": "#/definitions/CreateOrUpdateSecretOption" // responses: // "201": - // description: response when creating a secret + // description: secret created // "204": - // description: response when updating a secret + // description: secret updated // "400": // "$ref": "#/responses/error" // "404": @@ -122,7 +122,7 @@ func CreateOrUpdateSecret(ctx *context.APIContext) { } } -// DeleteSecret delete one secret of the organization +// DeleteSecret delete one secret in an organization func DeleteSecret(ctx *context.APIContext) { // swagger:operation DELETE /orgs/{org}/actions/secrets/{secretname} organization deleteOrgSecret // --- @@ -144,7 +144,7 @@ func DeleteSecret(ctx *context.APIContext) { // required: true // responses: // "204": - // description: delete one secret of the organization + // description: secret deleted // "400": // "$ref": "#/responses/error" // "404": diff --git a/routers/api/v1/repo/action.go b/routers/api/v1/repo/action.go index 039cdadac9c14..46fa8fd05f755 100644 --- a/routers/api/v1/repo/action.go +++ b/routers/api/v1/repo/action.go @@ -14,7 +14,7 @@ import ( secret_service "code.gitea.io/gitea/services/secrets" ) -// create or update one secret of the repository +// CreateOrUpdateSecret create or update one secret in a repository func CreateOrUpdateSecret(ctx *context.APIContext) { // swagger:operation PUT /repos/{owner}/{repo}/actions/secrets/{secretname} repository updateRepoSecret // --- @@ -45,20 +45,19 @@ func CreateOrUpdateSecret(ctx *context.APIContext) { // "$ref": "#/definitions/CreateOrUpdateSecretOption" // responses: // "201": - // description: response when creating a secret + // description: secret created // "204": - // description: response when updating a secret + // description: secret updated // "400": // "$ref": "#/responses/error" // "404": // "$ref": "#/responses/notFound" - owner := ctx.Repo.Owner repo := ctx.Repo.Repository opt := web.GetForm(ctx).(*api.CreateOrUpdateSecretOption) - _, created, err := secret_service.CreateOrUpdateSecret(ctx, owner.ID, repo.ID, ctx.Params("secretname"), opt.Data) + _, created, err := secret_service.CreateOrUpdateSecret(ctx, 0, repo.ID, ctx.Params("secretname"), opt.Data) if err != nil { if errors.Is(err, util.ErrInvalidArgument) { ctx.Error(http.StatusBadRequest, "CreateOrUpdateSecret", err) @@ -77,7 +76,7 @@ func CreateOrUpdateSecret(ctx *context.APIContext) { } } -// DeleteSecret delete one secret of the repository +// DeleteSecret delete one secret in a repository func DeleteSecret(ctx *context.APIContext) { // swagger:operation DELETE /repos/{owner}/{repo}/actions/secrets/{secretname} repository deleteRepoSecret // --- @@ -104,16 +103,15 @@ func DeleteSecret(ctx *context.APIContext) { // required: true // responses: // "204": - // description: delete one secret of the organization + // description: secret deleted // "400": // "$ref": "#/responses/error" // "404": // "$ref": "#/responses/notFound" - owner := ctx.Repo.Owner repo := ctx.Repo.Repository - err := secret_service.DeleteSecretByName(ctx, owner.ID, repo.ID, ctx.Params("secretname")) + err := secret_service.DeleteSecretByName(ctx, 0, repo.ID, ctx.Params("secretname")) if err != nil { if errors.Is(err, util.ErrInvalidArgument) { ctx.Error(http.StatusBadRequest, "DeleteSecret", err) diff --git a/routers/api/v1/user/action.go b/routers/api/v1/user/action.go index cbe332a7798f7..7a7c0e7c8347f 100644 --- a/routers/api/v1/user/action.go +++ b/routers/api/v1/user/action.go @@ -14,7 +14,7 @@ import ( secret_service "code.gitea.io/gitea/services/secrets" ) -// create or update one secret of the user scope +// CreateOrUpdateSecret create or update one secret in a user scope func CreateOrUpdateSecret(ctx *context.APIContext) { // swagger:operation PUT /user/actions/secrets/{secretname} user updateUserSecret // --- @@ -35,9 +35,9 @@ func CreateOrUpdateSecret(ctx *context.APIContext) { // "$ref": "#/definitions/CreateOrUpdateSecretOption" // responses: // "201": - // description: response when creating a secret + // description: secret created // "204": - // description: response when updating a secret + // description: secret updated // "400": // "$ref": "#/responses/error" // "404": @@ -64,7 +64,7 @@ func CreateOrUpdateSecret(ctx *context.APIContext) { } } -// DeleteSecret delete one secret of the user scope +// DeleteSecret delete one secret in a user scope func DeleteSecret(ctx *context.APIContext) { // swagger:operation DELETE /user/actions/secrets/{secretname} user deleteUserSecret // --- @@ -81,7 +81,7 @@ func DeleteSecret(ctx *context.APIContext) { // required: true // responses: // "204": - // description: delete one secret of the user + // description: secret deleted // "400": // "$ref": "#/responses/error" // "404": diff --git a/routers/web/repo/setting/secrets.go b/routers/web/repo/setting/secrets.go index cf427b2c44bd7..eb77c4c9e3932 100644 --- a/routers/web/repo/setting/secrets.go +++ b/routers/web/repo/setting/secrets.go @@ -16,9 +16,10 @@ import ( const ( // TODO: Separate secrets from runners when layout is ready - tplRepoSecrets base.TplName = "repo/settings/actions" - tplOrgSecrets base.TplName = "org/settings/actions" - tplUserSecrets base.TplName = "user/settings/actions" + tplRepoSecrets base.TplName = "repo/settings/actions" + tplOrgSecrets base.TplName = "org/settings/actions" + tplUserSecrets base.TplName = "user/settings/actions" + tplAdminSecrets base.TplName = "admin/actions" ) type secretsCtx struct { @@ -27,10 +28,12 @@ type secretsCtx struct { IsRepo bool IsOrg bool IsUser bool + IsGlobal bool SecretsTemplate base.TplName RedirectLink string } +//nolint:dupl func getSecretsCtx(ctx *context.Context) (*secretsCtx, error) { if ctx.Data["PageIsRepoSettings"] == true { return &secretsCtx{ @@ -67,6 +70,16 @@ func getSecretsCtx(ctx *context.Context) (*secretsCtx, error) { }, nil } + if ctx.Data["PageIsAdmin"] == true { + return &secretsCtx{ + OwnerID: 0, + RepoID: 0, + IsGlobal: true, + SecretsTemplate: tplAdminSecrets, + RedirectLink: setting.AppSubURL + "/admin/actions/secrets", + }, nil + } + return nil, errors.New("unable to set Secrets context") } diff --git a/routers/web/repo/setting/variables.go b/routers/web/repo/setting/variables.go index 428aa0bd5c4fe..24270c4dcf995 100644 --- a/routers/web/repo/setting/variables.go +++ b/routers/web/repo/setting/variables.go @@ -32,6 +32,7 @@ type variablesCtx struct { RedirectLink string } +//nolint:dupl func getVariablesCtx(ctx *context.Context) (*variablesCtx, error) { if ctx.Data["PageIsRepoSettings"] == true { return &variablesCtx{ diff --git a/routers/web/web.go b/routers/web/web.go index 92cf5132b45b6..93f2d208113b4 100644 --- a/routers/web/web.go +++ b/routers/web/web.go @@ -777,6 +777,7 @@ func registerRoutes(m *web.Route) { m.Group("/actions", func() { m.Get("", admin.RedirectToDefaultSetting) addSettingsRunnersRoutes() + addSettingsSecretsRoutes() addSettingsVariablesRoutes() }) }, adminReq, ctxDataSet("EnableOAuth2", setting.OAuth2.Enabled, "EnablePackages", setting.Packages.Enabled)) diff --git a/templates/admin/actions.tmpl b/templates/admin/actions.tmpl index 597863d73b15e..e71330c67415d 100644 --- a/templates/admin/actions.tmpl +++ b/templates/admin/actions.tmpl @@ -3,6 +3,9 @@ {{if eq .PageType "runners"}} {{template "shared/actions/runner_list" .}} {{end}} + {{if eq .PageType "secrets"}} + {{template "shared/secrets/add_list" (dict "ctxData" . "desc" "secrets.instance_desc")}} + {{end}} {{if eq .PageType "variables"}} {{template "shared/variables/variable_list" .}} {{end}} diff --git a/templates/admin/navbar.tmpl b/templates/admin/navbar.tmpl index fa79f0f7598a8..55d01adff4535 100644 --- a/templates/admin/navbar.tmpl +++ b/templates/admin/navbar.tmpl @@ -63,12 +63,15 @@ {{end}} {{end}} {{if .EnableActions}} -
+
{{ctx.Locale.Tr "actions.actions"}}
- {{if .Secrets}}
- {{range .Secrets}} -
-
- {{svg "octicon-key" 32}} + {{if .desc}} +
+ {{ctx.Locale.Tr .desc}}
-
-
- {{.Name}} -
-
- ****** + {{end}} + {{if .ctxData.Secrets}} + {{range .ctxData.Secrets}} +
+
+ {{svg "octicon-key" 32}} +
+
+
+ {{.Name}} +
+
+ ****** +
+
+
+ + {{ctx.Locale.Tr "settings.added_on" (DateTime "short" .CreatedUnix) | Safe}} + + +
+ {{end}} + {{else}} +
+ {{ctx.Locale.Tr "secrets.none"}}
-
- - {{ctx.Locale.Tr "settings.added_on" (DateTime "short" .CreatedUnix) | Safe}} - - -
-
{{end}}
- {{else}} - {{ctx.Locale.Tr "secrets.none"}} - {{end}}
{{/* Add secret dialog */}} diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl index 403f241d72787..84badc0d2ba6d 100644 --- a/templates/swagger/v1_json.tmpl +++ b/templates/swagger/v1_json.tmpl @@ -75,6 +75,84 @@ } } }, + "/admin/actions/secrets/{secretname}": { + "put": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "admin" + ], + "summary": "Create or Update a secret value in instance scope", + "operationId": "updateAdminSecret", + "parameters": [ + { + "type": "string", + "description": "name of the secret", + "name": "secretname", + "in": "path", + "required": true + }, + { + "name": "body", + "in": "body", + "schema": { + "$ref": "#/definitions/CreateOrUpdateSecretOption" + } + } + ], + "responses": { + "201": { + "description": "secret created" + }, + "204": { + "description": "secret updated" + }, + "400": { + "$ref": "#/responses/error" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + }, + "delete": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "admin" + ], + "summary": "Delete a secret in instance scope", + "operationId": "deleteAdminSecret", + "parameters": [ + { + "type": "string", + "description": "name of the secret", + "name": "secretname", + "in": "path", + "required": true + } + ], + "responses": { + "204": { + "description": "secret deleted" + }, + "400": { + "$ref": "#/responses/error" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, "/admin/cron": { "get": { "produces": [ @@ -1687,10 +1765,10 @@ ], "responses": { "201": { - "description": "response when creating a secret" + "description": "secret created" }, "204": { - "description": "response when updating a secret" + "description": "secret updated" }, "400": { "$ref": "#/responses/error" @@ -1730,7 +1808,7 @@ ], "responses": { "204": { - "description": "delete one secret of the organization" + "description": "secret deleted" }, "400": { "$ref": "#/responses/error" @@ -3414,10 +3492,10 @@ ], "responses": { "201": { - "description": "response when creating a secret" + "description": "secret created" }, "204": { - "description": "response when updating a secret" + "description": "secret updated" }, "400": { "$ref": "#/responses/error" @@ -3464,7 +3542,7 @@ ], "responses": { "204": { - "description": "delete one secret of the organization" + "description": "secret deleted" }, "400": { "$ref": "#/responses/error" @@ -14644,10 +14722,10 @@ ], "responses": { "201": { - "description": "response when creating a secret" + "description": "secret created" }, "204": { - "description": "response when updating a secret" + "description": "secret updated" }, "400": { "$ref": "#/responses/error" @@ -14680,7 +14758,7 @@ ], "responses": { "204": { - "description": "delete one secret of the user" + "description": "secret deleted" }, "400": { "$ref": "#/responses/error" diff --git a/templates/user/settings/actions.tmpl b/templates/user/settings/actions.tmpl index abc544338355b..37f211eeff092 100644 --- a/templates/user/settings/actions.tmpl +++ b/templates/user/settings/actions.tmpl @@ -1,7 +1,7 @@ {{template "user/settings/layout_head" (dict "ctxData" . "pageClass" "user settings actions")}}
{{if eq .PageType "secrets"}} - {{template "shared/secrets/add_list" .}} + {{template "shared/secrets/add_list" (dict "ctxData" .)}} {{else if eq .PageType "runners"}} {{template "shared/actions/runner_list" .}} {{else if eq .PageType "variables"}} diff --git a/tests/integration/api_admin_secrets_test.go b/tests/integration/api_admin_secrets_test.go new file mode 100644 index 0000000000000..694f274d8645f --- /dev/null +++ b/tests/integration/api_admin_secrets_test.go @@ -0,0 +1,98 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package integration + +import ( + "fmt" + "net/http" + "testing" + + auth_model "code.gitea.io/gitea/models/auth" + api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/tests" +) + +func TestAPIAdminSecrets(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + session := loginUser(t, "user1") + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteAdmin) + + t.Run("Create", func(t *testing.T) { + cases := []struct { + Name string + ExpectedStatus int + }{ + { + Name: "", + ExpectedStatus: http.StatusNotFound, + }, + { + Name: "-", + ExpectedStatus: http.StatusBadRequest, + }, + { + Name: "_", + ExpectedStatus: http.StatusCreated, + }, + { + Name: "secret", + ExpectedStatus: http.StatusCreated, + }, + { + Name: "2secret", + ExpectedStatus: http.StatusBadRequest, + }, + { + Name: "GITEA_secret", + ExpectedStatus: http.StatusBadRequest, + }, + { + Name: "GITHUB_secret", + ExpectedStatus: http.StatusBadRequest, + }, + } + + for _, c := range cases { + req := NewRequestWithJSON(t, "PUT", fmt.Sprintf("/api/v1/admin/actions/secrets/%s", c.Name), api.CreateOrUpdateSecretOption{ + Data: "data", + }).AddTokenAuth(token) + MakeRequest(t, req, c.ExpectedStatus) + } + }) + + t.Run("Update", func(t *testing.T) { + name := "update_secret" + url := fmt.Sprintf("/api/v1/admin/actions/secrets/%s", name) + + req := NewRequestWithJSON(t, "PUT", url, api.CreateOrUpdateSecretOption{ + Data: "initial", + }).AddTokenAuth(token) + MakeRequest(t, req, http.StatusCreated) + + req = NewRequestWithJSON(t, "PUT", url, api.CreateOrUpdateSecretOption{ + Data: "changed", + }).AddTokenAuth(token) + MakeRequest(t, req, http.StatusNoContent) + }) + + t.Run("Delete", func(t *testing.T) { + name := "delete_secret" + url := fmt.Sprintf("/api/v1/admin/actions/secrets/%s", name) + + req := NewRequestWithJSON(t, "PUT", url, api.CreateOrUpdateSecretOption{ + Data: "initial", + }).AddTokenAuth(token) + MakeRequest(t, req, http.StatusCreated) + + req = NewRequest(t, "DELETE", url).AddTokenAuth(token) + MakeRequest(t, req, http.StatusNoContent) + + req = NewRequest(t, "DELETE", url).AddTokenAuth(token) + MakeRequest(t, req, http.StatusNotFound) + + req = NewRequest(t, "DELETE", "/api/v1/admin/actions/secrets/000").AddTokenAuth(token) + MakeRequest(t, req, http.StatusBadRequest) + }) +}