keyvault.go 8.1 KB

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