-
-
Notifications
You must be signed in to change notification settings - Fork 5.5k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Secrets storage with SecretKey encrypted (#22142)
Fork of #14483, but [gave up MasterKey](#14483 (comment)), and fixed some problems. Close #12065. Needed by #13539. Featrues: - Secrets for repo and org, not user yet. - Use SecretKey to encrypte/encrypt secrets. - Trim spaces of secret value. - Add a new locale ini block, to make it easy to support secrets for user. Snapshots: Repo level secrets: ![image](https://user-images.githubusercontent.com/9418365/207823319-b8a4903f-38ca-4af7-9d05-336a5af906f3.png) Rrg level secrets ![image](https://user-images.githubusercontent.com/9418365/207823371-8bd02e93-1928-40d1-8c76-f48b255ace36.png) Co-authored-by: Lauris BH <lauris@nix.lv> Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com> Co-authored-by: wxiaoguang <wxiaoguang@gmail.com> Co-authored-by: delvh <dev.lh@web.de> Co-authored-by: KN4CK3R <admin@oldschoolhack.me>
- Loading branch information
1 parent
40ba750
commit 6590551
Showing
17 changed files
with
468 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
--- | ||
date: "2022-12-19T21:26:00+08:00" | ||
title: "Encrypted secrets" | ||
slug: "secrets/overview" | ||
draft: false | ||
toc: false | ||
menu: | ||
sidebar: | ||
parent: "secrets" | ||
name: "Overview" | ||
weight: 1 | ||
identifier: "overview" | ||
--- | ||
|
||
# Encrypted secrets | ||
|
||
Encrypted secrets allow you to store sensitive information in your organization or repository. | ||
Secrets are available on Gitea 1.19+. | ||
|
||
# Naming your secrets | ||
|
||
The following rules apply to secret names: | ||
|
||
Secret names can only contain alphanumeric characters (`[a-z]`, `[A-Z]`, `[0-9]`) or underscores (`_`). Spaces are not allowed. | ||
|
||
Secret names must not start with the `GITHUB_` and `GITEA_` prefix. | ||
|
||
Secret names must not start with a number. | ||
|
||
Secret names are not case-sensitive. | ||
|
||
Secret names must be unique at the level they are created at. | ||
|
||
For example, a secret created at the repository level must have a unique name in that repository, and a secret created at the organization level must have a unique name at that level. | ||
|
||
If a secret with the same name exists at multiple levels, the secret at the lowest level takes precedence. For example, if an organization-level secret has the same name as a repository-level secret, then the repository-level secret takes precedence. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
// Copyright 2022 The Gitea Authors. All rights reserved. | ||
// SPDX-License-Identifier: MIT | ||
|
||
package v1_19 //nolint | ||
|
||
import ( | ||
"code.gitea.io/gitea/modules/timeutil" | ||
|
||
"xorm.io/xorm" | ||
) | ||
|
||
func CreateSecretsTable(x *xorm.Engine) error { | ||
type Secret struct { | ||
ID int64 | ||
OwnerID int64 `xorm:"INDEX UNIQUE(owner_repo_name) NOT NULL"` | ||
RepoID int64 `xorm:"INDEX UNIQUE(owner_repo_name) NOT NULL DEFAULT 0"` | ||
Name string `xorm:"UNIQUE(owner_repo_name) NOT NULL"` | ||
Data string `xorm:"LONGTEXT"` | ||
CreatedUnix timeutil.TimeStamp `xorm:"created NOT NULL"` | ||
} | ||
|
||
return x.Sync(new(Secret)) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
// Copyright 2022 The Gitea Authors. All rights reserved. | ||
// SPDX-License-Identifier: MIT | ||
|
||
package secret | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"regexp" | ||
"strings" | ||
|
||
"code.gitea.io/gitea/models/db" | ||
secret_module "code.gitea.io/gitea/modules/secret" | ||
"code.gitea.io/gitea/modules/setting" | ||
"code.gitea.io/gitea/modules/timeutil" | ||
"code.gitea.io/gitea/modules/util" | ||
|
||
"xorm.io/builder" | ||
) | ||
|
||
type ErrSecretInvalidValue struct { | ||
Name *string | ||
Data *string | ||
} | ||
|
||
func (err ErrSecretInvalidValue) Error() string { | ||
if err.Name != nil { | ||
return fmt.Sprintf("secret name %q is invalid", *err.Name) | ||
} | ||
if err.Data != nil { | ||
return fmt.Sprintf("secret data %q is invalid", *err.Data) | ||
} | ||
return util.ErrInvalidArgument.Error() | ||
} | ||
|
||
func (err ErrSecretInvalidValue) Unwrap() error { | ||
return util.ErrInvalidArgument | ||
} | ||
|
||
// Secret represents a secret | ||
type Secret struct { | ||
ID int64 | ||
OwnerID int64 `xorm:"INDEX UNIQUE(owner_repo_name) NOT NULL"` | ||
RepoID int64 `xorm:"INDEX UNIQUE(owner_repo_name) NOT NULL DEFAULT 0"` | ||
Name string `xorm:"UNIQUE(owner_repo_name) NOT NULL"` | ||
Data string `xorm:"LONGTEXT"` // encrypted data | ||
CreatedUnix timeutil.TimeStamp `xorm:"created NOT NULL"` | ||
} | ||
|
||
// newSecret Creates a new already encrypted secret | ||
func newSecret(ownerID, repoID int64, name, data string) *Secret { | ||
return &Secret{ | ||
OwnerID: ownerID, | ||
RepoID: repoID, | ||
Name: strings.ToUpper(name), | ||
Data: data, | ||
} | ||
} | ||
|
||
// InsertEncryptedSecret Creates, encrypts, and validates a new secret with yet unencrypted data and insert into database | ||
func InsertEncryptedSecret(ctx context.Context, ownerID, repoID int64, name, data string) (*Secret, error) { | ||
encrypted, err := secret_module.EncryptSecret(setting.SecretKey, strings.TrimSpace(data)) | ||
if err != nil { | ||
return nil, err | ||
} | ||
secret := newSecret(ownerID, repoID, name, encrypted) | ||
if err := secret.Validate(); err != nil { | ||
return secret, err | ||
} | ||
return secret, db.Insert(ctx, secret) | ||
} | ||
|
||
func init() { | ||
db.RegisterModel(new(Secret)) | ||
} | ||
|
||
var ( | ||
secretNameReg = regexp.MustCompile("^[A-Z_][A-Z0-9_]*$") | ||
forbiddenSecretPrefixReg = regexp.MustCompile("^GIT(EA|HUB)_") | ||
) | ||
|
||
// Validate validates the required fields and formats. | ||
func (s *Secret) Validate() error { | ||
switch { | ||
case len(s.Name) == 0 || len(s.Name) > 50: | ||
return ErrSecretInvalidValue{Name: &s.Name} | ||
case len(s.Data) == 0: | ||
return ErrSecretInvalidValue{Data: &s.Data} | ||
case !secretNameReg.MatchString(s.Name) || | ||
forbiddenSecretPrefixReg.MatchString(s.Name): | ||
return ErrSecretInvalidValue{Name: &s.Name} | ||
default: | ||
return nil | ||
} | ||
} | ||
|
||
type FindSecretsOptions struct { | ||
db.ListOptions | ||
OwnerID int64 | ||
RepoID int64 | ||
} | ||
|
||
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}) | ||
} | ||
|
||
return cond | ||
} | ||
|
||
func FindSecrets(ctx context.Context, opts FindSecretsOptions) ([]*Secret, error) { | ||
var secrets []*Secret | ||
sess := db.GetEngine(ctx) | ||
if opts.PageSize != 0 { | ||
sess = db.SetSessionPagination(sess, &opts.ListOptions) | ||
} | ||
return secrets, sess. | ||
Where(opts.toConds()). | ||
Find(&secrets) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.