provider.go 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215
  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 delinea
  14. import (
  15. "context"
  16. "errors"
  17. "github.com/DelineaXPM/dsv-sdk-go/v2/vault"
  18. kubeClient "sigs.k8s.io/controller-runtime/pkg/client"
  19. "sigs.k8s.io/controller-runtime/pkg/webhook/admission"
  20. esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
  21. esmeta "github.com/external-secrets/external-secrets/apis/meta/v1"
  22. "github.com/external-secrets/external-secrets/runtime/esutils"
  23. "github.com/external-secrets/external-secrets/runtime/esutils/resolvers"
  24. )
  25. var (
  26. errEmptyTenant = errors.New("tenant must not be empty")
  27. errEmptyClientID = errors.New("clientID must be set")
  28. errEmptyClientSecret = errors.New("clientSecret must be set")
  29. errMissingStore = errors.New("missing store specification")
  30. errInvalidSpec = errors.New("invalid specification for delinea provider")
  31. errMissingSecretName = errors.New("must specify a secret name")
  32. errMissingSecretKey = errors.New("must specify a secret key")
  33. errClusterStoreRequiresNamespace = errors.New("when using a ClusterSecretStore, namespaces must be explicitly set")
  34. )
  35. // Provider implements the External Secrets provider for Delinea DevOps
  36. // Secrets Vault.
  37. type Provider struct{}
  38. var _ esv1.Provider = &Provider{}
  39. // Capabilities return the provider supported capabilities (ReadOnly, WriteOnly, ReadWrite).
  40. func (p *Provider) Capabilities() esv1.SecretStoreCapabilities {
  41. return esv1.SecretStoreReadOnly
  42. }
  43. // NewClient creates a new Delinea DevOps Secrets Vault client.
  44. func (p *Provider) NewClient(ctx context.Context, store esv1.GenericStore, kube kubeClient.Client, namespace string) (esv1.SecretsClient, error) {
  45. cfg, err := getConfig(store)
  46. if err != nil {
  47. return nil, err
  48. }
  49. if store.GetKind() == esv1.ClusterSecretStoreKind && doesConfigDependOnNamespace(cfg) {
  50. // we are not attached to a specific namespace, but some config values are dependent on it
  51. return nil, errClusterStoreRequiresNamespace
  52. }
  53. clientID, err := loadConfigSecret(ctx, store.GetKind(), cfg.ClientID, kube, namespace)
  54. if err != nil {
  55. return nil, err
  56. }
  57. clientSecret, err := loadConfigSecret(ctx, store.GetKind(), cfg.ClientSecret, kube, namespace)
  58. if err != nil {
  59. return nil, err
  60. }
  61. dsvClient, err := vault.New(vault.Configuration{
  62. Credentials: vault.ClientCredential{
  63. ClientID: clientID,
  64. ClientSecret: clientSecret,
  65. },
  66. Tenant: cfg.Tenant,
  67. TLD: cfg.TLD,
  68. URLTemplate: cfg.URLTemplate,
  69. })
  70. if err != nil {
  71. return nil, err
  72. }
  73. return &client{
  74. api: dsvClient,
  75. }, nil
  76. }
  77. // loadConfigSecret resolves a Delinea credential from either an inline value or a secret reference.
  78. func loadConfigSecret(
  79. ctx context.Context,
  80. storeKind string,
  81. ref *esv1.DelineaProviderSecretRef,
  82. kube kubeClient.Client,
  83. namespace string) (string, error) {
  84. if ref.SecretRef == nil {
  85. return ref.Value, nil
  86. }
  87. if err := validateSecretRef(ref); err != nil {
  88. return "", err
  89. }
  90. return resolvers.SecretKeyRef(ctx, kube, storeKind, namespace, ref.SecretRef)
  91. }
  92. // delineaCredentialRefPolicy returns the validation policy for Delinea credential fields.
  93. func delineaCredentialRefPolicy(store esv1.GenericStore) esutils.ValueOrRefPolicy[esmeta.SecretKeySelector] {
  94. return esutils.ValueOrRefPolicy[esmeta.SecretKeySelector]{
  95. Presence: esutils.RequireValueOrRef,
  96. ValidateRef: validateDelineaCredentialSecretRef(store),
  97. }
  98. }
  99. // validateDelineaCredentialSecretRef validates a Delinea credential secret reference against the store scope.
  100. func validateDelineaCredentialSecretRef(store esv1.GenericStore) func(esmeta.SecretKeySelector) error {
  101. return func(ref esmeta.SecretKeySelector) error {
  102. if err := esutils.ValidateReferentSecretSelector(store, ref); err != nil {
  103. return err
  104. }
  105. return validateDelineaCredentialSecretRefNameAndKey(ref)
  106. }
  107. }
  108. // validateSecretRef validates a Delinea credential reference independently of a store.
  109. func validateSecretRef(ref *esv1.DelineaProviderSecretRef) error {
  110. return esutils.ValidateValueOrRef(ref.Value, ref.SecretRef, esutils.ValueOrRefPolicy[esmeta.SecretKeySelector]{
  111. Presence: esutils.RequireValueOrRef,
  112. ValidateRef: validateDelineaCredentialSecretRefNameAndKey,
  113. })
  114. }
  115. // validateDelineaCredentialSecretRefNameAndKey ensures a Delinea credential secret reference has both name and key.
  116. func validateDelineaCredentialSecretRefNameAndKey(ref esmeta.SecretKeySelector) error {
  117. if ref.Name == "" {
  118. return errMissingSecretName
  119. }
  120. if ref.Key == "" {
  121. return errMissingSecretKey
  122. }
  123. return nil
  124. }
  125. func doesConfigDependOnNamespace(cfg *esv1.DelineaProvider) bool {
  126. if cfg.ClientID.SecretRef != nil && cfg.ClientID.SecretRef.Namespace == nil {
  127. return true
  128. }
  129. if cfg.ClientSecret.SecretRef != nil && cfg.ClientSecret.SecretRef.Namespace == nil {
  130. return true
  131. }
  132. return false
  133. }
  134. func getConfig(store esv1.GenericStore) (*esv1.DelineaProvider, error) {
  135. if store == nil {
  136. return nil, errMissingStore
  137. }
  138. storeSpec := store.GetSpec()
  139. if storeSpec == nil || storeSpec.Provider == nil || storeSpec.Provider.Delinea == nil {
  140. return nil, errInvalidSpec
  141. }
  142. cfg := storeSpec.Provider.Delinea
  143. if cfg.Tenant == "" {
  144. return nil, errEmptyTenant
  145. }
  146. if cfg.ClientID == nil {
  147. return nil, errEmptyClientID
  148. }
  149. if cfg.ClientSecret == nil {
  150. return nil, errEmptyClientSecret
  151. }
  152. if err := esutils.ValidateValueOrRef(cfg.ClientID.Value, cfg.ClientID.SecretRef, delineaCredentialRefPolicy(store)); err != nil {
  153. return nil, err
  154. }
  155. if err := esutils.ValidateValueOrRef(cfg.ClientSecret.Value, cfg.ClientSecret.SecretRef, delineaCredentialRefPolicy(store)); err != nil {
  156. return nil, err
  157. }
  158. return cfg, nil
  159. }
  160. // ValidateStore validates the Delinea SecretStore configuration.
  161. func (p *Provider) ValidateStore(store esv1.GenericStore) (admission.Warnings, error) {
  162. _, err := getConfig(store)
  163. return nil, err
  164. }
  165. // NewProvider creates a new Provider instance.
  166. func NewProvider() esv1.Provider {
  167. return &Provider{}
  168. }
  169. // ProviderSpec returns the provider specification for registration.
  170. func ProviderSpec() *esv1.SecretStoreProvider {
  171. return &esv1.SecretStoreProvider{
  172. Delinea: &esv1.DelineaProvider{},
  173. }
  174. }
  175. // MaintenanceStatus returns the maintenance status of the provider.
  176. func MaintenanceStatus() esv1.MaintenanceStatus {
  177. return esv1.MaintenanceStatusMaintained
  178. }