keyvault.go 9.0 KB

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