provider.go 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  1. /*
  2. Copyright © The ESO Authors
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. https://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. // Package github implements a provider for GitHub secrets, allowing
  14. // External Secrets to write secrets to GitHub Actions or Dependabot.
  15. package github
  16. import (
  17. "context"
  18. "errors"
  19. "fmt"
  20. github "github.com/google/go-github/v56/github"
  21. "sigs.k8s.io/controller-runtime/pkg/client"
  22. "sigs.k8s.io/controller-runtime/pkg/webhook/admission"
  23. esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
  24. )
  25. const (
  26. errUnexpectedStoreSpec = "unexpected store spec"
  27. errInvalidStoreSpec = "invalid store spec"
  28. errInvalidStoreProv = "invalid store provider"
  29. errInvalidGithubProv = "invalid github provider"
  30. errInvalidStore = "invalid store"
  31. )
  32. // Provider implements the GitHub provider for managing secrets through GitHub Actions or Dependabot.
  33. type Provider struct {
  34. }
  35. var _ esv1.Provider = &Provider{}
  36. // Capabilities return the provider supported capabilities (ReadOnly, WriteOnly, ReadWrite).
  37. func (p *Provider) Capabilities() esv1.SecretStoreCapabilities {
  38. return esv1.SecretStoreWriteOnly
  39. }
  40. // NewClient constructs a new secrets client based on the provided store.
  41. func (p *Provider) NewClient(ctx context.Context, store esv1.GenericStore, kube client.Client, namespace string) (esv1.SecretsClient, error) {
  42. return newClient(ctx, store, kube, namespace)
  43. }
  44. func newClient(ctx context.Context, store esv1.GenericStore, kube client.Client, namespace string) (esv1.SecretsClient, error) {
  45. provider, err := getProvider(store)
  46. if err != nil {
  47. return nil, err
  48. }
  49. secretType, err := validateGithubProvider(provider)
  50. if err != nil {
  51. return nil, err
  52. }
  53. g := &Client{
  54. crClient: kube,
  55. store: store,
  56. namespace: namespace,
  57. provider: provider,
  58. storeKind: store.GetObjectKind().GroupVersionKind().Kind,
  59. }
  60. ghClient, err := g.AuthWithPrivateKey(ctx)
  61. if err != nil {
  62. return nil, fmt.Errorf("could not get private key: %w", err)
  63. }
  64. if err := g.configureSecretClient(ctx, ghClient, secretType); err != nil {
  65. return nil, err
  66. }
  67. return g, nil
  68. }
  69. func (g *Client) configureSecretClient(ctx context.Context, ghClient *github.Client, secretType esv1.GithubSecretType) error {
  70. if secretType == esv1.GithubSecretTypeDependabot {
  71. g.dependabotClient = *ghClient.Dependabot
  72. g.getSecretFn = g.dependabotOrgGetSecretFn
  73. g.getPublicKeyFn = g.dependabotOrgGetPublicKeyFn
  74. g.createOrUpdateFn = g.dependabotOrgCreateOrUpdateSecret
  75. g.listSecretsFn = g.dependabotOrgListSecretsFn
  76. g.deleteSecretFn = g.dependabotOrgDeleteSecretFn
  77. g.listSelectedReposFn = g.dependabotOrgListSelectedRepoIDs
  78. if g.provider.Repository != "" {
  79. g.getSecretFn = g.dependabotRepoGetSecretFn
  80. g.getPublicKeyFn = g.dependabotRepoGetPublicKeyFn
  81. g.createOrUpdateFn = g.dependabotRepoCreateOrUpdateSecret
  82. g.listSecretsFn = g.dependabotRepoListSecretsFn
  83. g.deleteSecretFn = g.dependabotRepoDeleteSecretFn
  84. g.listSelectedReposFn = nil
  85. }
  86. return nil
  87. }
  88. g.baseClient = *ghClient.Actions
  89. g.getSecretFn = g.orgGetSecretFn
  90. g.getPublicKeyFn = g.orgGetPublicKeyFn
  91. g.createOrUpdateFn = g.orgCreateOrUpdateSecret
  92. g.listSecretsFn = g.orgListSecretsFn
  93. g.deleteSecretFn = g.orgDeleteSecretsFn
  94. g.listSelectedReposFn = g.orgListSelectedRepoIDs
  95. if g.provider.Repository != "" {
  96. g.getSecretFn = g.repoGetSecretFn
  97. g.getPublicKeyFn = g.repoGetPublicKeyFn
  98. g.createOrUpdateFn = g.repoCreateOrUpdateSecret
  99. g.listSecretsFn = g.repoListSecretsFn
  100. g.deleteSecretFn = g.repoDeleteSecretsFn
  101. // Repo and env secrets have no "selected repositories" concept.
  102. g.listSelectedReposFn = nil
  103. if g.provider.Environment != "" {
  104. // For environment to work, we need the repository ID instead of its name.
  105. repo, _, err := ghClient.Repositories.Get(ctx, g.provider.Organization, g.provider.Repository)
  106. if err != nil {
  107. return fmt.Errorf("error fetching repository: %w", err)
  108. }
  109. g.repoID = repo.GetID()
  110. g.getSecretFn = g.envGetSecretFn
  111. g.getPublicKeyFn = g.envGetPublicKeyFn
  112. g.createOrUpdateFn = g.envCreateOrUpdateSecret
  113. g.listSecretsFn = g.envListSecretsFn
  114. g.deleteSecretFn = g.envDeleteSecretsFn
  115. }
  116. }
  117. return nil
  118. }
  119. func getProvider(store esv1.GenericStore) (*esv1.GithubProvider, error) {
  120. spc := store.GetSpec()
  121. if spc == nil || spc.Provider.Github == nil {
  122. return nil, errors.New(errUnexpectedStoreSpec)
  123. }
  124. return spc.Provider.Github, nil
  125. }
  126. // ValidateStore validates the configuration of a GitHub secret store.
  127. func (p *Provider) ValidateStore(store esv1.GenericStore) (admission.Warnings, error) {
  128. if store == nil {
  129. return nil, errors.New(errInvalidStore)
  130. }
  131. spc := store.GetSpec()
  132. if spc == nil {
  133. return nil, errors.New(errInvalidStoreSpec)
  134. }
  135. if spc.Provider == nil {
  136. return nil, errors.New(errInvalidStoreProv)
  137. }
  138. prov := spc.Provider.Github
  139. if prov == nil {
  140. return nil, errors.New(errInvalidGithubProv)
  141. }
  142. if _, err := validateGithubProvider(prov); err != nil {
  143. return nil, err
  144. }
  145. return nil, nil
  146. }
  147. func validateGithubProvider(provider *esv1.GithubProvider) (esv1.GithubSecretType, error) {
  148. secretType := provider.SecretType
  149. if secretType == "" {
  150. secretType = esv1.GithubSecretTypeActions
  151. }
  152. if secretType != esv1.GithubSecretTypeActions && secretType != esv1.GithubSecretTypeDependabot {
  153. return "", fmt.Errorf("unsupported GitHub secret type %q", secretType)
  154. }
  155. if secretType == esv1.GithubSecretTypeDependabot && provider.Environment != "" {
  156. return "", errors.New("Dependabot secrets do not support environments")
  157. }
  158. return secretType, nil
  159. }
  160. // NewProvider creates a new Provider instance.
  161. func NewProvider() esv1.Provider {
  162. return &Provider{}
  163. }
  164. // ProviderSpec returns the provider specification for registration.
  165. func ProviderSpec() *esv1.SecretStoreProvider {
  166. return &esv1.SecretStoreProvider{
  167. Github: &esv1.GithubProvider{},
  168. }
  169. }
  170. // MaintenanceStatus returns the maintenance status of the provider.
  171. func MaintenanceStatus() esv1.MaintenanceStatus {
  172. return esv1.MaintenanceStatusMaintained
  173. }