Skip to content

Commit

Permalink
Add instance-level secrets
Browse files Browse the repository at this point in the history
  • Loading branch information
jbgomond committed Jan 4, 2024
1 parent 92711b0 commit b56057a
Show file tree
Hide file tree
Showing 14 changed files with 348 additions and 44 deletions.
12 changes: 4 additions & 8 deletions models/secret/secret.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand All @@ -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})
}
Expand Down
12 changes: 9 additions & 3 deletions routers/api/actions/runner/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
103 changes: 103 additions & 0 deletions routers/api/v1/admin/action.go
Original file line number Diff line number Diff line change
@@ -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)
}
6 changes: 5 additions & 1 deletion routers/api/v1/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -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}").
Expand Down Expand Up @@ -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)
Expand Down
10 changes: 5 additions & 5 deletions routers/api/v1/org/secrets.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
// ---
Expand All @@ -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":
Expand All @@ -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
// ---
Expand All @@ -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":
Expand Down
16 changes: 7 additions & 9 deletions routers/api/v1/repo/action.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
// ---
Expand Down Expand Up @@ -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)
Expand All @@ -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
// ---
Expand All @@ -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)
Expand Down
10 changes: 5 additions & 5 deletions routers/api/v1/user/action.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
// ---
Expand All @@ -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":
Expand All @@ -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
// ---
Expand All @@ -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":
Expand Down
19 changes: 16 additions & 3 deletions routers/web/repo/setting/secrets.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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{
Expand Down Expand Up @@ -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")
}

Expand Down
1 change: 1 addition & 0 deletions routers/web/repo/setting/variables.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{
Expand Down
1 change: 1 addition & 0 deletions routers/web/web.go
Original file line number Diff line number Diff line change
Expand Up @@ -775,6 +775,7 @@ func registerRoutes(m *web.Route) {
m.Group("/actions", func() {
m.Get("", admin.RedirectToDefaultSetting)
addSettingsRunnersRoutes()
addSettingsSecretsRoutes()
addSettingsVariablesRoutes()
})
}, adminReq, ctxDataSet("EnableOAuth2", setting.OAuth2.Enable, "EnablePackages", setting.Packages.Enabled))
Expand Down
3 changes: 3 additions & 0 deletions templates/admin/actions.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
{{if eq .PageType "runners"}}
{{template "shared/actions/runner_list" .}}
{{end}}
{{if eq .PageType "secrets"}}
{{template "shared/secrets/add_list" .}}
{{end}}
{{if eq .PageType "variables"}}
{{template "shared/variables/variable_list" .}}
{{end}}
Expand Down
Loading

0 comments on commit b56057a

Please sign in to comment.