Skip to content

Commit 158bb9a

Browse files
committed
Add instance-level secrets
1 parent 5f5b5ba commit 158bb9a

File tree

20 files changed

+386
-79
lines changed

20 files changed

+386
-79
lines changed

models/secret/secret.go

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,8 @@ func init() {
6464
}
6565

6666
func (s *Secret) Validate() error {
67-
if s.OwnerID == 0 && s.RepoID == 0 {
68-
return errors.New("the secret is not bound to any scope")
67+
if s.OwnerID != 0 && s.RepoID != 0 {
68+
return errors.New("a secret should not be bound to an owner and a repository at the same time")
6969
}
7070
return nil
7171
}
@@ -80,12 +80,8 @@ type FindSecretsOptions struct {
8080

8181
func (opts FindSecretsOptions) ToConds() builder.Cond {
8282
cond := builder.NewCond()
83-
if opts.OwnerID > 0 {
84-
cond = cond.And(builder.Eq{"owner_id": opts.OwnerID})
85-
}
86-
if opts.RepoID > 0 {
87-
cond = cond.And(builder.Eq{"repo_id": opts.RepoID})
88-
}
83+
cond = cond.And(builder.Eq{"owner_id": opts.OwnerID})
84+
cond = cond.And(builder.Eq{"repo_id": opts.RepoID})
8985
if opts.SecretID != 0 {
9086
cond = cond.And(builder.Eq{"id": opts.SecretID})
9187
}

options/locale/locale_en-US.ini

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3495,6 +3495,7 @@ deletion.description = Removing a secret is permanent and cannot be undone. Cont
34953495
deletion.success = The secret has been removed.
34963496
deletion.failed = Failed to remove secret.
34973497
management = Secrets Management
3498+
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.
34983499
34993500
[actions]
35003501
actions = Actions

routers/api/actions/runner/utils.go

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -68,18 +68,24 @@ func getSecretsOfTask(ctx context.Context, task *actions_model.ActionTask) map[s
6868
return secrets
6969
}
7070

71-
ownerSecrets, err := db.Find[secret_model.Secret](ctx, secret_model.FindSecretsOptions{OwnerID: task.Job.Run.Repo.OwnerID})
71+
globalSecrets, err := db.Find[secret_model.Secret](ctx, secret_model.FindSecretsOptions{OwnerID: 0, RepoID: 0})
72+
if err != nil {
73+
log.Error("find global secrets: %v", err)
74+
// go on
75+
}
76+
ownerSecrets, err := db.Find[secret_model.Secret](ctx, secret_model.FindSecretsOptions{OwnerID: task.Job.Run.Repo.OwnerID, RepoID: 0})
7277
if err != nil {
7378
log.Error("find secrets of owner %v: %v", task.Job.Run.Repo.OwnerID, err)
7479
// go on
7580
}
76-
repoSecrets, err := db.Find[secret_model.Secret](ctx, secret_model.FindSecretsOptions{RepoID: task.Job.Run.RepoID})
81+
repoSecrets, err := db.Find[secret_model.Secret](ctx, secret_model.FindSecretsOptions{OwnerID: 0, RepoID: task.Job.Run.RepoID})
7782
if err != nil {
7883
log.Error("find secrets of repo %v: %v", task.Job.Run.RepoID, err)
7984
// go on
8085
}
8186

82-
for _, secret := range append(ownerSecrets, repoSecrets...) {
87+
// Level precedence: Repo > Org / User > Global
88+
for _, secret := range append(globalSecrets, append(ownerSecrets, repoSecrets...)...) {
8389
if v, err := secret_module.DecryptSecret(setting.SecretKey, secret.Data); err != nil {
8490
log.Error("decrypt secret %v %q: %v", secret.ID, secret.Name, err)
8591
// go on

routers/api/v1/admin/action.go

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
// Copyright 2023 The Gitea Authors. All rights reserved.
2+
// SPDX-License-Identifier: MIT
3+
4+
package admin
5+
6+
import (
7+
"errors"
8+
"net/http"
9+
10+
"code.gitea.io/gitea/modules/context"
11+
api "code.gitea.io/gitea/modules/structs"
12+
"code.gitea.io/gitea/modules/util"
13+
"code.gitea.io/gitea/modules/web"
14+
secret_service "code.gitea.io/gitea/services/secrets"
15+
)
16+
17+
// CreateOrUpdateSecret create or update one secret in instance scope
18+
func CreateOrUpdateSecret(ctx *context.APIContext) {
19+
// swagger:operation PUT /admin/actions/secrets/{secretname} admin updateAdminSecret
20+
// ---
21+
// summary: Create or Update a secret value in instance scope
22+
// consumes:
23+
// - application/json
24+
// produces:
25+
// - application/json
26+
// parameters:
27+
// - name: secretname
28+
// in: path
29+
// description: name of the secret
30+
// type: string
31+
// required: true
32+
// - name: body
33+
// in: body
34+
// schema:
35+
// "$ref": "#/definitions/CreateOrUpdateSecretOption"
36+
// responses:
37+
// "201":
38+
// description: secret created
39+
// "204":
40+
// description: secret updated
41+
// "400":
42+
// "$ref": "#/responses/error"
43+
// "404":
44+
// "$ref": "#/responses/notFound"
45+
46+
opt := web.GetForm(ctx).(*api.CreateOrUpdateSecretOption)
47+
48+
_, created, err := secret_service.CreateOrUpdateSecret(ctx, 0, 0, ctx.Params("secretname"), opt.Data)
49+
if err != nil {
50+
if errors.Is(err, util.ErrInvalidArgument) {
51+
ctx.Error(http.StatusBadRequest, "CreateOrUpdateSecret", err)
52+
} else if errors.Is(err, util.ErrNotExist) {
53+
ctx.Error(http.StatusNotFound, "CreateOrUpdateSecret", err)
54+
} else {
55+
ctx.Error(http.StatusInternalServerError, "CreateOrUpdateSecret", err)
56+
}
57+
return
58+
}
59+
60+
if created {
61+
ctx.Status(http.StatusCreated)
62+
} else {
63+
ctx.Status(http.StatusNoContent)
64+
}
65+
}
66+
67+
// DeleteSecret delete one secret in instance scope
68+
func DeleteSecret(ctx *context.APIContext) {
69+
// swagger:operation DELETE /admin/actions/secrets/{secretname} admin deleteAdminSecret
70+
// ---
71+
// summary: Delete a secret in instance scope
72+
// consumes:
73+
// - application/json
74+
// produces:
75+
// - application/json
76+
// parameters:
77+
// - name: secretname
78+
// in: path
79+
// description: name of the secret
80+
// type: string
81+
// required: true
82+
// responses:
83+
// "204":
84+
// description: secret deleted
85+
// "400":
86+
// "$ref": "#/responses/error"
87+
// "404":
88+
// "$ref": "#/responses/notFound"
89+
90+
err := secret_service.DeleteSecretByName(ctx, 0, 0, ctx.Params("secretname"))
91+
if err != nil {
92+
if errors.Is(err, util.ErrInvalidArgument) {
93+
ctx.Error(http.StatusBadRequest, "DeleteSecret", err)
94+
} else if errors.Is(err, util.ErrNotExist) {
95+
ctx.Error(http.StatusNotFound, "DeleteSecret", err)
96+
} else {
97+
ctx.Error(http.StatusInternalServerError, "DeleteSecret", err)
98+
}
99+
return
100+
}
101+
102+
ctx.Status(http.StatusNoContent)
103+
}

routers/api/v1/api.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -948,7 +948,6 @@ func Routes() *web.Route {
948948
Post(bind(api.CreateEmailOption{}), user.AddEmail).
949949
Delete(bind(api.DeleteEmailOption{}), user.DeleteEmail)
950950

951-
// manage user-level actions features
952951
m.Group("/actions", func() {
953952
m.Group("/secrets", func() {
954953
m.Combo("/{secretname}").
@@ -1499,6 +1498,11 @@ func Routes() *web.Route {
14991498
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryOrganization), orgAssignment(false, true), reqToken(), reqTeamMembership())
15001499

15011500
m.Group("/admin", func() {
1501+
m.Group("/actions/secrets", func() {
1502+
m.Combo("/{secretname}").
1503+
Put(bind(api.CreateOrUpdateSecretOption{}), admin.CreateOrUpdateSecret).
1504+
Delete(admin.DeleteSecret)
1505+
})
15021506
m.Group("/cron", func() {
15031507
m.Get("", admin.ListCronTasks)
15041508
m.Post("/{task}", admin.PostCronTask)

routers/api/v1/org/secrets.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ func ListActionsSecrets(ctx *context.APIContext) {
6767
ctx.JSON(http.StatusOK, apiSecrets)
6868
}
6969

70-
// create or update one secret of the organization
70+
// CreateOrUpdateSecret create or update one secret in an organization
7171
func CreateOrUpdateSecret(ctx *context.APIContext) {
7272
// swagger:operation PUT /orgs/{org}/actions/secrets/{secretname} organization updateOrgSecret
7373
// ---
@@ -93,9 +93,9 @@ func CreateOrUpdateSecret(ctx *context.APIContext) {
9393
// "$ref": "#/definitions/CreateOrUpdateSecretOption"
9494
// responses:
9595
// "201":
96-
// description: response when creating a secret
96+
// description: secret created
9797
// "204":
98-
// description: response when updating a secret
98+
// description: secret updated
9999
// "400":
100100
// "$ref": "#/responses/error"
101101
// "404":
@@ -122,7 +122,7 @@ func CreateOrUpdateSecret(ctx *context.APIContext) {
122122
}
123123
}
124124

125-
// DeleteSecret delete one secret of the organization
125+
// DeleteSecret delete one secret in an organization
126126
func DeleteSecret(ctx *context.APIContext) {
127127
// swagger:operation DELETE /orgs/{org}/actions/secrets/{secretname} organization deleteOrgSecret
128128
// ---
@@ -144,7 +144,7 @@ func DeleteSecret(ctx *context.APIContext) {
144144
// required: true
145145
// responses:
146146
// "204":
147-
// description: delete one secret of the organization
147+
// description: secret deleted
148148
// "400":
149149
// "$ref": "#/responses/error"
150150
// "404":

routers/api/v1/repo/action.go

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import (
1414
secret_service "code.gitea.io/gitea/services/secrets"
1515
)
1616

17-
// create or update one secret of the repository
17+
// CreateOrUpdateSecret create or update one secret in a repository
1818
func CreateOrUpdateSecret(ctx *context.APIContext) {
1919
// swagger:operation PUT /repos/{owner}/{repo}/actions/secrets/{secretname} repository updateRepoSecret
2020
// ---
@@ -45,20 +45,19 @@ func CreateOrUpdateSecret(ctx *context.APIContext) {
4545
// "$ref": "#/definitions/CreateOrUpdateSecretOption"
4646
// responses:
4747
// "201":
48-
// description: response when creating a secret
48+
// description: secret created
4949
// "204":
50-
// description: response when updating a secret
50+
// description: secret updated
5151
// "400":
5252
// "$ref": "#/responses/error"
5353
// "404":
5454
// "$ref": "#/responses/notFound"
5555

56-
owner := ctx.Repo.Owner
5756
repo := ctx.Repo.Repository
5857

5958
opt := web.GetForm(ctx).(*api.CreateOrUpdateSecretOption)
6059

61-
_, created, err := secret_service.CreateOrUpdateSecret(ctx, owner.ID, repo.ID, ctx.Params("secretname"), opt.Data)
60+
_, created, err := secret_service.CreateOrUpdateSecret(ctx, 0, repo.ID, ctx.Params("secretname"), opt.Data)
6261
if err != nil {
6362
if errors.Is(err, util.ErrInvalidArgument) {
6463
ctx.Error(http.StatusBadRequest, "CreateOrUpdateSecret", err)
@@ -77,7 +76,7 @@ func CreateOrUpdateSecret(ctx *context.APIContext) {
7776
}
7877
}
7978

80-
// DeleteSecret delete one secret of the repository
79+
// DeleteSecret delete one secret in a repository
8180
func DeleteSecret(ctx *context.APIContext) {
8281
// swagger:operation DELETE /repos/{owner}/{repo}/actions/secrets/{secretname} repository deleteRepoSecret
8382
// ---
@@ -104,16 +103,15 @@ func DeleteSecret(ctx *context.APIContext) {
104103
// required: true
105104
// responses:
106105
// "204":
107-
// description: delete one secret of the organization
106+
// description: secret deleted
108107
// "400":
109108
// "$ref": "#/responses/error"
110109
// "404":
111110
// "$ref": "#/responses/notFound"
112111

113-
owner := ctx.Repo.Owner
114112
repo := ctx.Repo.Repository
115113

116-
err := secret_service.DeleteSecretByName(ctx, owner.ID, repo.ID, ctx.Params("secretname"))
114+
err := secret_service.DeleteSecretByName(ctx, 0, repo.ID, ctx.Params("secretname"))
117115
if err != nil {
118116
if errors.Is(err, util.ErrInvalidArgument) {
119117
ctx.Error(http.StatusBadRequest, "DeleteSecret", err)

routers/api/v1/user/action.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import (
1414
secret_service "code.gitea.io/gitea/services/secrets"
1515
)
1616

17-
// create or update one secret of the user scope
17+
// CreateOrUpdateSecret create or update one secret in a user scope
1818
func CreateOrUpdateSecret(ctx *context.APIContext) {
1919
// swagger:operation PUT /user/actions/secrets/{secretname} user updateUserSecret
2020
// ---
@@ -35,9 +35,9 @@ func CreateOrUpdateSecret(ctx *context.APIContext) {
3535
// "$ref": "#/definitions/CreateOrUpdateSecretOption"
3636
// responses:
3737
// "201":
38-
// description: response when creating a secret
38+
// description: secret created
3939
// "204":
40-
// description: response when updating a secret
40+
// description: secret updated
4141
// "400":
4242
// "$ref": "#/responses/error"
4343
// "404":
@@ -64,7 +64,7 @@ func CreateOrUpdateSecret(ctx *context.APIContext) {
6464
}
6565
}
6666

67-
// DeleteSecret delete one secret of the user scope
67+
// DeleteSecret delete one secret in a user scope
6868
func DeleteSecret(ctx *context.APIContext) {
6969
// swagger:operation DELETE /user/actions/secrets/{secretname} user deleteUserSecret
7070
// ---
@@ -81,7 +81,7 @@ func DeleteSecret(ctx *context.APIContext) {
8181
// required: true
8282
// responses:
8383
// "204":
84-
// description: delete one secret of the user
84+
// description: secret deleted
8585
// "400":
8686
// "$ref": "#/responses/error"
8787
// "404":

routers/web/repo/setting/secrets.go

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,10 @@ import (
1616

1717
const (
1818
// TODO: Separate secrets from runners when layout is ready
19-
tplRepoSecrets base.TplName = "repo/settings/actions"
20-
tplOrgSecrets base.TplName = "org/settings/actions"
21-
tplUserSecrets base.TplName = "user/settings/actions"
19+
tplRepoSecrets base.TplName = "repo/settings/actions"
20+
tplOrgSecrets base.TplName = "org/settings/actions"
21+
tplUserSecrets base.TplName = "user/settings/actions"
22+
tplAdminSecrets base.TplName = "admin/actions"
2223
)
2324

2425
type secretsCtx struct {
@@ -27,10 +28,12 @@ type secretsCtx struct {
2728
IsRepo bool
2829
IsOrg bool
2930
IsUser bool
31+
IsGlobal bool
3032
SecretsTemplate base.TplName
3133
RedirectLink string
3234
}
3335

36+
//nolint:dupl
3437
func getSecretsCtx(ctx *context.Context) (*secretsCtx, error) {
3538
if ctx.Data["PageIsRepoSettings"] == true {
3639
return &secretsCtx{
@@ -67,6 +70,16 @@ func getSecretsCtx(ctx *context.Context) (*secretsCtx, error) {
6770
}, nil
6871
}
6972

73+
if ctx.Data["PageIsAdmin"] == true {
74+
return &secretsCtx{
75+
OwnerID: 0,
76+
RepoID: 0,
77+
IsGlobal: true,
78+
SecretsTemplate: tplAdminSecrets,
79+
RedirectLink: setting.AppSubURL + "/admin/actions/secrets",
80+
}, nil
81+
}
82+
7083
return nil, errors.New("unable to set Secrets context")
7184
}
7285

routers/web/repo/setting/variables.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ type variablesCtx struct {
3232
RedirectLink string
3333
}
3434

35+
//nolint:dupl
3536
func getVariablesCtx(ctx *context.Context) (*variablesCtx, error) {
3637
if ctx.Data["PageIsRepoSettings"] == true {
3738
return &variablesCtx{

routers/web/web.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -777,6 +777,7 @@ func registerRoutes(m *web.Route) {
777777
m.Group("/actions", func() {
778778
m.Get("", admin.RedirectToDefaultSetting)
779779
addSettingsRunnersRoutes()
780+
addSettingsSecretsRoutes()
780781
addSettingsVariablesRoutes()
781782
})
782783
}, adminReq, ctxDataSet("EnableOAuth2", setting.OAuth2.Enabled, "EnablePackages", setting.Packages.Enabled))

templates/admin/actions.tmpl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
{{if eq .PageType "runners"}}
44
{{template "shared/actions/runner_list" .}}
55
{{end}}
6+
{{if eq .PageType "secrets"}}
7+
{{template "shared/secrets/add_list" (dict "ctxData" . "desc" "secrets.instance_desc")}}
8+
{{end}}
69
{{if eq .PageType "variables"}}
710
{{template "shared/variables/variable_list" .}}
811
{{end}}

0 commit comments

Comments
 (0)