gitea源码

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203
  1. // Copyright 2022 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package secret
  4. import (
  5. "context"
  6. "fmt"
  7. "strings"
  8. actions_model "code.gitea.io/gitea/models/actions"
  9. "code.gitea.io/gitea/models/db"
  10. actions_module "code.gitea.io/gitea/modules/actions"
  11. "code.gitea.io/gitea/modules/log"
  12. secret_module "code.gitea.io/gitea/modules/secret"
  13. "code.gitea.io/gitea/modules/setting"
  14. "code.gitea.io/gitea/modules/timeutil"
  15. "code.gitea.io/gitea/modules/util"
  16. "xorm.io/builder"
  17. )
  18. // Secret represents a secret
  19. //
  20. // It can be:
  21. // 1. org/user level secret, OwnerID is org/user ID and RepoID is 0
  22. // 2. repo level secret, OwnerID is 0 and RepoID is repo ID
  23. //
  24. // Please note that it's not acceptable to have both OwnerID and RepoID to be non-zero,
  25. // or it will be complicated to find secrets belonging to a specific owner.
  26. // For example, conditions like `OwnerID = 1` will also return secret {OwnerID: 1, RepoID: 1},
  27. // but it's a repo level secret, not an org/user level secret.
  28. // To avoid this, make it clear with {OwnerID: 0, RepoID: 1} for repo level secrets.
  29. //
  30. // Please note that it's not acceptable to have both OwnerID and RepoID to zero, global secrets are not supported.
  31. // It's for security reasons, admin may be not aware of that the secrets could be stolen by any user when setting them as global.
  32. type Secret struct {
  33. ID int64
  34. OwnerID int64 `xorm:"INDEX UNIQUE(owner_repo_name) NOT NULL"`
  35. RepoID int64 `xorm:"INDEX UNIQUE(owner_repo_name) NOT NULL DEFAULT 0"`
  36. Name string `xorm:"UNIQUE(owner_repo_name) NOT NULL"`
  37. Data string `xorm:"LONGTEXT"` // encrypted data
  38. Description string `xorm:"TEXT"`
  39. CreatedUnix timeutil.TimeStamp `xorm:"created NOT NULL"`
  40. }
  41. const (
  42. SecretDataMaxLength = 65536
  43. SecretDescriptionMaxLength = 4096
  44. )
  45. // ErrSecretNotFound represents a "secret not found" error.
  46. type ErrSecretNotFound struct {
  47. Name string
  48. }
  49. func (err ErrSecretNotFound) Error() string {
  50. return fmt.Sprintf("secret was not found [name: %s]", err.Name)
  51. }
  52. func (err ErrSecretNotFound) Unwrap() error {
  53. return util.ErrNotExist
  54. }
  55. // InsertEncryptedSecret Creates, encrypts, and validates a new secret with yet unencrypted data and insert into database
  56. func InsertEncryptedSecret(ctx context.Context, ownerID, repoID int64, name, data, description string) (*Secret, error) {
  57. if ownerID != 0 && repoID != 0 {
  58. // It's trying to create a secret that belongs to a repository, but OwnerID has been set accidentally.
  59. // Remove OwnerID to avoid confusion; it's not worth returning an error here.
  60. ownerID = 0
  61. }
  62. if ownerID == 0 && repoID == 0 {
  63. return nil, fmt.Errorf("%w: ownerID and repoID cannot be both zero, global secrets are not supported", util.ErrInvalidArgument)
  64. }
  65. if len(data) > SecretDataMaxLength {
  66. return nil, util.NewInvalidArgumentErrorf("data too long")
  67. }
  68. description = util.TruncateRunes(description, SecretDescriptionMaxLength)
  69. encrypted, err := secret_module.EncryptSecret(setting.SecretKey, data)
  70. if err != nil {
  71. return nil, err
  72. }
  73. secret := &Secret{
  74. OwnerID: ownerID,
  75. RepoID: repoID,
  76. Name: strings.ToUpper(name),
  77. Data: encrypted,
  78. Description: description,
  79. }
  80. return secret, db.Insert(ctx, secret)
  81. }
  82. func init() {
  83. db.RegisterModel(new(Secret))
  84. }
  85. type FindSecretsOptions struct {
  86. db.ListOptions
  87. RepoID int64
  88. OwnerID int64 // it will be ignored if RepoID is set
  89. SecretID int64
  90. Name string
  91. }
  92. func (opts FindSecretsOptions) ToConds() builder.Cond {
  93. cond := builder.NewCond()
  94. cond = cond.And(builder.Eq{"repo_id": opts.RepoID})
  95. if opts.RepoID != 0 { // if RepoID is set
  96. // ignore OwnerID and treat it as 0
  97. cond = cond.And(builder.Eq{"owner_id": 0})
  98. } else {
  99. cond = cond.And(builder.Eq{"owner_id": opts.OwnerID})
  100. }
  101. if opts.SecretID != 0 {
  102. cond = cond.And(builder.Eq{"id": opts.SecretID})
  103. }
  104. if opts.Name != "" {
  105. cond = cond.And(builder.Eq{"name": strings.ToUpper(opts.Name)})
  106. }
  107. return cond
  108. }
  109. // UpdateSecret changes org or user reop secret.
  110. func UpdateSecret(ctx context.Context, secretID int64, data, description string) error {
  111. if len(data) > SecretDataMaxLength {
  112. return util.NewInvalidArgumentErrorf("data too long")
  113. }
  114. description = util.TruncateRunes(description, SecretDescriptionMaxLength)
  115. encrypted, err := secret_module.EncryptSecret(setting.SecretKey, data)
  116. if err != nil {
  117. return err
  118. }
  119. s := &Secret{
  120. Data: encrypted,
  121. Description: description,
  122. }
  123. affected, err := db.GetEngine(ctx).ID(secretID).Cols("data", "description").Update(s)
  124. if affected != 1 {
  125. return ErrSecretNotFound{}
  126. }
  127. return err
  128. }
  129. func GetSecretsOfTask(ctx context.Context, task *actions_model.ActionTask) (map[string]string, error) {
  130. secrets := map[string]string{}
  131. secrets["GITHUB_TOKEN"] = task.Token
  132. secrets["GITEA_TOKEN"] = task.Token
  133. if task.Job.Run.IsForkPullRequest && task.Job.Run.TriggerEvent != actions_module.GithubEventPullRequestTarget {
  134. // ignore secrets for fork pull request, except GITHUB_TOKEN and GITEA_TOKEN which are automatically generated.
  135. // for the tasks triggered by pull_request_target event, they could access the secrets because they will run in the context of the base branch
  136. // see the documentation: https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request_target
  137. return secrets, nil
  138. }
  139. ownerSecrets, err := db.Find[Secret](ctx, FindSecretsOptions{OwnerID: task.Job.Run.Repo.OwnerID})
  140. if err != nil {
  141. log.Error("find secrets of owner %v: %v", task.Job.Run.Repo.OwnerID, err)
  142. return nil, err
  143. }
  144. repoSecrets, err := db.Find[Secret](ctx, FindSecretsOptions{RepoID: task.Job.Run.RepoID})
  145. if err != nil {
  146. log.Error("find secrets of repo %v: %v", task.Job.Run.RepoID, err)
  147. return nil, err
  148. }
  149. for _, secret := range append(ownerSecrets, repoSecrets...) {
  150. v, err := secret_module.DecryptSecret(setting.SecretKey, secret.Data)
  151. if err != nil {
  152. log.Error("decrypt secret %v %q: %v", secret.ID, secret.Name, err)
  153. return nil, err
  154. }
  155. secrets[secret.Name] = v
  156. }
  157. return secrets, nil
  158. }
  159. func CountWrongRepoLevelSecrets(ctx context.Context) (int64, error) {
  160. var result int64
  161. _, err := db.GetEngine(ctx).SQL("SELECT count(`id`) FROM `secret` WHERE `repo_id` > 0 AND `owner_id` > 0").Get(&result)
  162. return result, err
  163. }
  164. func UpdateWrongRepoLevelSecrets(ctx context.Context) (int64, error) {
  165. result, err := db.GetEngine(ctx).Exec("UPDATE `secret` SET `owner_id` = 0 WHERE `repo_id` > 0 AND `owner_id` > 0")
  166. if err != nil {
  167. return 0, err
  168. }
  169. return result.RowsAffected()
  170. }