diff options
author | Jason Song <i@wolfogre.com> | 2022-12-20 17:07:13 +0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-12-20 17:07:13 +0800 |
commit | 659055138b6d32492b20c9f4d1d5a3cdaa47188d (patch) | |
tree | e2e7741be2b7b349e04f6901bff92b75b9b7c9ac /models/secret/secret.go | |
parent | 40ba750c4bf1f3f5f8dff5af57b2db4b600f237f (diff) | |
download | gitea-659055138b6d32492b20c9f4d1d5a3cdaa47188d.tar.gz gitea-659055138b6d32492b20c9f4d1d5a3cdaa47188d.zip |
Secrets storage with SecretKey encrypted (#22142)
Fork of #14483, but [gave up
MasterKey](https://github.com/go-gitea/gitea/pull/14483#issuecomment-1350728557),
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>
Diffstat (limited to 'models/secret/secret.go')
-rw-r--r-- | models/secret/secret.go | 124 |
1 files changed, 124 insertions, 0 deletions
diff --git a/models/secret/secret.go b/models/secret/secret.go new file mode 100644 index 0000000000..f970d5319e --- /dev/null +++ b/models/secret/secret.go @@ -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) +} |