provider.go 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185
  1. /*
  2. Copyright © 2025 ESO Maintainer Team
  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 gitlab
  14. import (
  15. "context"
  16. "crypto/tls"
  17. "crypto/x509"
  18. "errors"
  19. "fmt"
  20. "net/http"
  21. gitlab "gitlab.com/gitlab-org/api/client-go"
  22. kclient "sigs.k8s.io/controller-runtime/pkg/client"
  23. "sigs.k8s.io/controller-runtime/pkg/webhook/admission"
  24. esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
  25. "github.com/external-secrets/external-secrets/pkg/constants"
  26. "github.com/external-secrets/external-secrets/pkg/metrics"
  27. "github.com/external-secrets/external-secrets/pkg/utils"
  28. )
  29. // Provider satisfies the provider interface.
  30. type Provider struct{}
  31. // gitlabBase satisfies the provider.SecretsClient interface.
  32. type gitlabBase struct {
  33. kube kclient.Client
  34. store *esv1.GitlabProvider
  35. storeKind string
  36. namespace string
  37. projectsClient ProjectsClient
  38. projectVariablesClient ProjectVariablesClient
  39. groupVariablesClient GroupVariablesClient
  40. }
  41. // Capabilities return the provider supported capabilities (ReadOnly, WriteOnly, ReadWrite).
  42. func (g *Provider) Capabilities() esv1.SecretStoreCapabilities {
  43. return esv1.SecretStoreReadOnly
  44. }
  45. // Method on GitLab Provider to set up projectVariablesClient with credentials, populate projectID and environment.
  46. func (g *Provider) NewClient(ctx context.Context, store esv1.GenericStore, kube kclient.Client, namespace string) (esv1.SecretsClient, error) {
  47. storeSpec := store.GetSpec()
  48. if storeSpec == nil || storeSpec.Provider == nil || storeSpec.Provider.Gitlab == nil {
  49. return nil, errors.New("no store type or wrong store type")
  50. }
  51. storeSpecGitlab := storeSpec.Provider.Gitlab
  52. gl := &gitlabBase{
  53. kube: kube,
  54. store: storeSpecGitlab,
  55. namespace: namespace,
  56. storeKind: store.GetObjectKind().GroupVersionKind().Kind,
  57. }
  58. client, err := gl.getClient(ctx, storeSpecGitlab)
  59. if err != nil {
  60. return nil, err
  61. }
  62. gl.projectsClient = client.Projects
  63. gl.projectVariablesClient = client.ProjectVariables
  64. gl.groupVariablesClient = client.GroupVariables
  65. return gl, nil
  66. }
  67. func (g *gitlabBase) getClient(ctx context.Context, provider *esv1.GitlabProvider) (*gitlab.Client, error) {
  68. credentials, err := g.getAuth(ctx)
  69. if err != nil {
  70. return nil, err
  71. }
  72. // Create projectVariablesClient options
  73. var opts []gitlab.ClientOptionFunc
  74. if provider.URL != "" {
  75. opts = append(opts, gitlab.WithBaseURL(provider.URL))
  76. }
  77. if len(provider.CABundle) > 0 || provider.CAProvider != nil {
  78. caCertPool := x509.NewCertPool()
  79. ca, err := utils.FetchCACertFromSource(ctx, utils.CreateCertOpts{
  80. CABundle: provider.CABundle,
  81. CAProvider: provider.CAProvider,
  82. StoreKind: g.storeKind,
  83. Namespace: g.namespace,
  84. Client: g.kube,
  85. })
  86. if err != nil {
  87. return nil, fmt.Errorf("failed to read ca bundle: %w", err)
  88. }
  89. if ok := caCertPool.AppendCertsFromPEM(ca); !ok {
  90. return nil, errors.New("failed to append ca bundle")
  91. }
  92. transport := &http.Transport{
  93. TLSClientConfig: &tls.Config{
  94. RootCAs: caCertPool,
  95. MinVersion: tls.VersionTLS12,
  96. },
  97. }
  98. httpClient := &http.Client{Transport: transport}
  99. opts = append(opts, gitlab.WithHTTPClient(httpClient))
  100. }
  101. // ClientOptionFunc from the gitlab package can be mapped with the CRD
  102. // in a similar way to extend functionality of the provider
  103. // Create a new GitLab Client using credentials and options
  104. client, err := gitlab.NewClient(credentials, opts...)
  105. if err != nil {
  106. return nil, err
  107. }
  108. return client, nil
  109. }
  110. func (g *gitlabBase) getVariables(ref esv1.ExternalSecretDataRemoteRef, vopts *gitlab.GetProjectVariableOptions) (*gitlab.ProjectVariable, *gitlab.Response, error) {
  111. data, resp, err := g.projectVariablesClient.GetVariable(g.store.ProjectID, ref.Key, vopts)
  112. metrics.ObserveAPICall(constants.ProviderGitLab, constants.CallGitLabProjectVariableGet, err)
  113. if err != nil {
  114. if resp != nil && resp.StatusCode == http.StatusNotFound && !isEmptyOrWildcard(g.store.Environment) {
  115. vopts.Filter.EnvironmentScope = "*"
  116. data, resp, err = g.projectVariablesClient.GetVariable(g.store.ProjectID, ref.Key, vopts)
  117. metrics.ObserveAPICall(constants.ProviderGitLab, constants.CallGitLabProjectVariableGet, err)
  118. if err != nil || resp == nil {
  119. return nil, resp, fmt.Errorf("error getting variable %s from GitLab: %w", ref.Key, err)
  120. }
  121. } else {
  122. return nil, resp, err
  123. }
  124. }
  125. return data, resp, nil
  126. }
  127. func (g *Provider) ValidateStore(store esv1.GenericStore) (admission.Warnings, error) {
  128. storeSpec := store.GetSpec()
  129. gitlabSpec := storeSpec.Provider.Gitlab
  130. accessToken := gitlabSpec.Auth.SecretRef.AccessToken
  131. err := utils.ValidateSecretSelector(store, accessToken)
  132. if err != nil {
  133. return nil, err
  134. }
  135. if gitlabSpec.ProjectID == "" && len(gitlabSpec.GroupIDs) == 0 {
  136. return nil, errors.New("projectID and groupIDs must not both be empty")
  137. }
  138. if gitlabSpec.InheritFromGroups && len(gitlabSpec.GroupIDs) > 0 {
  139. return nil, errors.New("defining groupIDs and inheritFromGroups = true is not allowed")
  140. }
  141. if accessToken.Key == "" {
  142. return nil, errors.New("accessToken.key cannot be empty")
  143. }
  144. if accessToken.Name == "" {
  145. return nil, errors.New("accessToken.name cannot be empty")
  146. }
  147. return nil, nil
  148. }
  149. func init() {
  150. esv1.Register(&Provider{}, &esv1.SecretStoreProvider{
  151. Gitlab: &esv1.GitlabProvider{},
  152. }, esv1.MaintenanceStatusMaintained)
  153. }