keyvault.go 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239
  1. /*
  2. Licensed under the Apache License, Version 2.0 (the "License");
  3. you may not use this file except in compliance with the License.
  4. You may obtain a copy of the License at
  5. http://www.apache.org/licenses/LICENSE-2.0
  6. Unless required by applicable law or agreed to in writing, software
  7. distributed under the License is distributed on an "AS IS" BASIS,
  8. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  9. See the License for the specific language governing permissions and
  10. limitations under the License.
  11. */
  12. package keyvault
  13. import (
  14. "context"
  15. "encoding/json"
  16. "fmt"
  17. "path"
  18. "strings"
  19. "github.com/Azure/azure-sdk-for-go/profiles/latest/keyvault/keyvault"
  20. kvauth "github.com/Azure/go-autorest/autorest/azure/auth"
  21. "github.com/tidwall/gjson"
  22. corev1 "k8s.io/api/core/v1"
  23. "k8s.io/apimachinery/pkg/types"
  24. "sigs.k8s.io/controller-runtime/pkg/client"
  25. esv1alpha1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1alpha1"
  26. smmeta "github.com/external-secrets/external-secrets/apis/meta/v1"
  27. "github.com/external-secrets/external-secrets/pkg/provider"
  28. "github.com/external-secrets/external-secrets/pkg/provider/schema"
  29. )
  30. // Provider satisfies the provider interface.
  31. type Provider struct{}
  32. // interface to keyvault.BaseClient.
  33. type SecretClient interface {
  34. GetKey(ctx context.Context, vaultBaseURL string, keyName string, keyVersion string) (result keyvault.KeyBundle, err error)
  35. GetSecret(ctx context.Context, vaultBaseURL string, secretName string, secretVersion string) (result keyvault.SecretBundle, err error)
  36. GetSecretsComplete(ctx context.Context, vaultBaseURL string, maxresults *int32) (result keyvault.SecretListResultIterator, err error)
  37. GetCertificate(ctx context.Context, vaultBaseURL string, certificateName string, certificateVersion string) (result keyvault.CertificateBundle, err error)
  38. }
  39. // Azure satisfies the provider.SecretsClient interface.
  40. type Azure struct {
  41. kube client.Client
  42. store esv1alpha1.GenericStore
  43. baseClient SecretClient
  44. vaultURL string
  45. namespace string
  46. }
  47. func init() {
  48. schema.Register(&Provider{}, &esv1alpha1.SecretStoreProvider{
  49. AzureKV: &esv1alpha1.AzureKVProvider{},
  50. })
  51. }
  52. // NewClient constructs a new secrets client based on the provided store.
  53. func (p *Provider) NewClient(ctx context.Context, store esv1alpha1.GenericStore, kube client.Client, namespace string) (provider.SecretsClient, error) {
  54. return newClient(ctx, store, kube, namespace)
  55. }
  56. func newClient(ctx context.Context, store esv1alpha1.GenericStore, kube client.Client, namespace string) (provider.SecretsClient, error) {
  57. anAzure := &Azure{
  58. kube: kube,
  59. store: store,
  60. namespace: namespace,
  61. }
  62. azClient, vaultURL, err := anAzure.newAzureClient(ctx)
  63. if err != nil {
  64. return nil, err
  65. }
  66. anAzure.baseClient = azClient
  67. anAzure.vaultURL = vaultURL
  68. return anAzure, nil
  69. }
  70. // Implements store.Client.GetSecret Interface.
  71. // Retrieves a secret/Key/Certificate with the secret name defined in ref.Name
  72. // The Object Type is defined as a prefix in the ref.Name , if no prefix is defined , we assume a secret is required.
  73. func (a *Azure) GetSecret(ctx context.Context, ref esv1alpha1.ExternalSecretDataRemoteRef) ([]byte, error) {
  74. version := ""
  75. objectType := "secret"
  76. basicClient := a.baseClient
  77. if ref.Version != "" {
  78. version = ref.Version
  79. }
  80. secretName := ref.Key
  81. nameSplitted := strings.Split(secretName, "/")
  82. if len(nameSplitted) > 1 {
  83. objectType = nameSplitted[0]
  84. secretName = nameSplitted[1]
  85. // TODO: later tokens can be used to read the secret tags
  86. }
  87. switch objectType {
  88. case "secret":
  89. // returns a SecretBundle with the secret value
  90. // https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/services/keyvault/v7.0/keyvault#SecretBundle
  91. secretResp, err := basicClient.GetSecret(context.Background(), a.vaultURL, secretName, version)
  92. if err != nil {
  93. return nil, err
  94. }
  95. if ref.Property == "" {
  96. return []byte(*secretResp.Value), nil
  97. }
  98. res := gjson.Get(*secretResp.Value, ref.Property)
  99. if !res.Exists() {
  100. return nil, fmt.Errorf("property %s does not exist in key %s", ref.Property, ref.Key)
  101. }
  102. return []byte(res.String()), err
  103. case "cert":
  104. // returns a CertBundle. We return CER contents of x509 certificate
  105. // see: https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/services/keyvault/v7.0/keyvault#CertificateBundle
  106. secretResp, err := basicClient.GetCertificate(context.Background(), a.vaultURL, secretName, version)
  107. if err != nil {
  108. return nil, err
  109. }
  110. return *secretResp.Cer, nil
  111. case "key":
  112. // returns a KeyBundla that contains a jwk
  113. // azure kv returns only public keys
  114. // see: https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/services/keyvault/v7.0/keyvault#KeyBundle
  115. keyResp, err := basicClient.GetKey(context.Background(), a.vaultURL, secretName, version)
  116. if err != nil {
  117. return nil, err
  118. }
  119. return json.Marshal(keyResp.Key)
  120. }
  121. return nil, fmt.Errorf("unknown Azure Keyvault object Type for %s", secretName)
  122. }
  123. // Implements store.Client.GetSecretMap Interface.
  124. // retrieve ALL secrets in a specific keyvault.
  125. // ExternalSecretDataRemoteRef Key is mandatory, but with current model we do not use its content.
  126. func (a *Azure) GetSecretMap(ctx context.Context, _ esv1alpha1.ExternalSecretDataRemoteRef) (map[string][]byte, error) {
  127. basicClient := a.baseClient
  128. secretsMap := make(map[string][]byte)
  129. secretListIter, err := basicClient.GetSecretsComplete(context.Background(), a.vaultURL, nil)
  130. if err != nil {
  131. return nil, err
  132. }
  133. for secretListIter.NotDone() {
  134. secretList := secretListIter.Response().Value
  135. for _, secret := range *secretList {
  136. if !*secret.Attributes.Enabled {
  137. continue
  138. }
  139. secretName := path.Base(*secret.ID)
  140. secretResp, err := basicClient.GetSecret(context.Background(), a.vaultURL, secretName, "")
  141. secretValue := *secretResp.Value
  142. if err != nil {
  143. return nil, err
  144. }
  145. secretsMap[secretName] = []byte(secretValue)
  146. }
  147. err = secretListIter.Next()
  148. if err != nil {
  149. return nil, err
  150. }
  151. }
  152. return secretsMap, nil
  153. }
  154. func (a *Azure) newAzureClient(ctx context.Context) (*keyvault.BaseClient, string, error) {
  155. spec := *a.store.GetSpec().Provider.AzureKV
  156. tenantID := *spec.TenantID
  157. vaultURL := *spec.VaultURL
  158. if spec.AuthSecretRef == nil {
  159. return nil, "", fmt.Errorf("missing clientID/clientSecret in store config")
  160. }
  161. clusterScoped := false
  162. if a.store.GetObjectMeta().String() == esv1alpha1.ClusterSecretStoreKind {
  163. clusterScoped = true
  164. }
  165. if spec.AuthSecretRef.ClientID == nil || spec.AuthSecretRef.ClientSecret == nil {
  166. return nil, "", fmt.Errorf("missing accessKeyID/secretAccessKey in store config")
  167. }
  168. cid, err := a.secretKeyRef(ctx, a.store.GetNamespace(), *spec.AuthSecretRef.ClientID, clusterScoped)
  169. if err != nil {
  170. return nil, "", err
  171. }
  172. csec, err := a.secretKeyRef(ctx, a.store.GetNamespace(), *spec.AuthSecretRef.ClientSecret, clusterScoped)
  173. if err != nil {
  174. return nil, "", err
  175. }
  176. clientCredentialsConfig := kvauth.NewClientCredentialsConfig(cid, csec, tenantID)
  177. // the default resource api is the management URL and not the vault URL which we need for keyvault operations
  178. clientCredentialsConfig.Resource = "https://vault.azure.net"
  179. authorizer, err := clientCredentialsConfig.Authorizer()
  180. if err != nil {
  181. return nil, "", err
  182. }
  183. basicClient := keyvault.New()
  184. basicClient.Authorizer = authorizer
  185. return &basicClient, vaultURL, nil
  186. }
  187. func (a *Azure) secretKeyRef(ctx context.Context, namespace string, secretRef smmeta.SecretKeySelector, clusterScoped bool) (string, error) {
  188. var secret corev1.Secret
  189. ref := types.NamespacedName{
  190. Namespace: namespace,
  191. Name: secretRef.Name,
  192. }
  193. if clusterScoped && secretRef.Namespace != nil {
  194. ref.Namespace = *secretRef.Namespace
  195. }
  196. err := a.kube.Get(ctx, ref, &secret)
  197. if err != nil {
  198. return "", fmt.Errorf("could not find secret %s/%s: %w", ref.Namespace, ref.Name, err)
  199. }
  200. keyBytes, ok := secret.Data[secretRef.Key]
  201. if !ok {
  202. return "", fmt.Errorf("no data for %q in secret '%s/%s'", secretRef.Key, secretRef.Name, namespace)
  203. }
  204. value := strings.TrimSpace(string(keyBytes))
  205. return value, nil
  206. }
  207. func (a *Azure) Close() error {
  208. return nil
  209. }