keyvault.go 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283
  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. vaultResource = "https://vault.azure.net"
  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. clientSet, err := anAzure.setAzureClientWithManagedIdentity()
  66. if clientSet {
  67. return anAzure, err
  68. }
  69. clientSet, err = anAzure.setAzureClientWithServicePrincipal(ctx)
  70. if clientSet {
  71. return anAzure, err
  72. }
  73. return nil, fmt.Errorf("cannot initialize Azure Client: no valid authType was specified")
  74. }
  75. // Implements store.Client.GetSecret Interface.
  76. // Retrieves a secret/Key/Certificate with the secret name defined in ref.Name
  77. // The Object Type is defined as a prefix in the ref.Name , if no prefix is defined , we assume a secret is required.
  78. func (a *Azure) GetSecret(ctx context.Context, ref esv1alpha1.ExternalSecretDataRemoteRef) ([]byte, error) {
  79. version := ""
  80. basicClient := a.baseClient
  81. objectType, secretName := getObjType(ref)
  82. if ref.Version != "" {
  83. version = ref.Version
  84. }
  85. switch objectType {
  86. case defaultObjType:
  87. // returns a SecretBundle with the secret value
  88. // https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/services/keyvault/v7.0/keyvault#SecretBundle
  89. secretResp, err := basicClient.GetSecret(context.Background(), a.vaultURL, secretName, version)
  90. if err != nil {
  91. return nil, err
  92. }
  93. if ref.Property == "" {
  94. return []byte(*secretResp.Value), nil
  95. }
  96. res := gjson.Get(*secretResp.Value, ref.Property)
  97. if !res.Exists() {
  98. return nil, fmt.Errorf("property %s does not exist in key %s", ref.Property, ref.Key)
  99. }
  100. return []byte(res.String()), err
  101. case "cert":
  102. // returns a CertBundle. We return CER contents of x509 certificate
  103. // see: https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/services/keyvault/v7.0/keyvault#CertificateBundle
  104. secretResp, err := basicClient.GetCertificate(context.Background(), a.vaultURL, secretName, version)
  105. if err != nil {
  106. return nil, err
  107. }
  108. return *secretResp.Cer, nil
  109. case "key":
  110. // returns a KeyBundla that contains a jwk
  111. // azure kv returns only public keys
  112. // see: https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/services/keyvault/v7.0/keyvault#KeyBundle
  113. keyResp, err := basicClient.GetKey(context.Background(), a.vaultURL, secretName, version)
  114. if err != nil {
  115. return nil, err
  116. }
  117. return json.Marshal(keyResp.Key)
  118. }
  119. return nil, fmt.Errorf("unknown Azure Keyvault object Type for %s", secretName)
  120. }
  121. // Implements store.Client.GetSecretMap Interface.
  122. // New version of GetSecretMap.
  123. func (a *Azure) GetSecretMap(ctx context.Context, ref esv1alpha1.ExternalSecretDataRemoteRef) (map[string][]byte, error) {
  124. objectType, secretName := getObjType(ref)
  125. switch objectType {
  126. case defaultObjType:
  127. data, err := a.GetSecret(ctx, ref)
  128. if err != nil {
  129. return nil, err
  130. }
  131. kv := make(map[string]string)
  132. err = json.Unmarshal(data, &kv)
  133. if err != nil {
  134. return nil, fmt.Errorf("error unmarshalling json data: %w", err)
  135. }
  136. secretData := make(map[string][]byte)
  137. for k, v := range kv {
  138. secretData[k] = []byte(v)
  139. }
  140. return secretData, nil
  141. case "cert":
  142. return nil, fmt.Errorf("cannot get use dataFrom to get certificate secret")
  143. case "key":
  144. return nil, fmt.Errorf("cannot get use dataFrom to get key secret")
  145. }
  146. return nil, fmt.Errorf("unknown Azure Keyvault object Type for %s", secretName)
  147. }
  148. func (a *Azure) setAzureClientWithManagedIdentity() (bool, error) {
  149. spec := *a.store.GetSpec().Provider.AzureKV
  150. if *spec.AuthType != esv1alpha1.ManagedIdentity {
  151. return false, nil
  152. }
  153. msiConfig := kvauth.NewMSIConfig()
  154. msiConfig.Resource = vaultResource
  155. if spec.IdentityID != nil {
  156. msiConfig.ClientID = *spec.IdentityID
  157. }
  158. authorizer, err := msiConfig.Authorizer()
  159. if err != nil {
  160. return true, err
  161. }
  162. basicClient := keyvault.New()
  163. basicClient.Authorizer = authorizer
  164. a.baseClient = basicClient
  165. a.vaultURL = *spec.VaultURL
  166. return true, nil
  167. }
  168. func (a *Azure) setAzureClientWithServicePrincipal(ctx context.Context) (bool, error) {
  169. spec := *a.store.GetSpec().Provider.AzureKV
  170. if *spec.AuthType != esv1alpha1.ServicePrincipal {
  171. return false, nil
  172. }
  173. if spec.TenantID == nil {
  174. return true, fmt.Errorf("missing tenantID in store config")
  175. }
  176. if spec.AuthSecretRef == nil {
  177. return true, fmt.Errorf("missing clientID/clientSecret in store config")
  178. }
  179. if spec.AuthSecretRef.ClientID == nil || spec.AuthSecretRef.ClientSecret == nil {
  180. return true, fmt.Errorf("missing accessKeyID/secretAccessKey in store config")
  181. }
  182. clusterScoped := false
  183. if a.store.GetObjectKind().GroupVersionKind().Kind == esv1alpha1.ClusterSecretStoreKind {
  184. clusterScoped = true
  185. }
  186. cid, err := a.secretKeyRef(ctx, a.store.GetNamespace(), *spec.AuthSecretRef.ClientID, clusterScoped)
  187. if err != nil {
  188. return true, err
  189. }
  190. csec, err := a.secretKeyRef(ctx, a.store.GetNamespace(), *spec.AuthSecretRef.ClientSecret, clusterScoped)
  191. if err != nil {
  192. return true, err
  193. }
  194. clientCredentialsConfig := kvauth.NewClientCredentialsConfig(cid, csec, *spec.TenantID)
  195. clientCredentialsConfig.Resource = vaultResource
  196. authorizer, err := clientCredentialsConfig.Authorizer()
  197. if err != nil {
  198. return true, err
  199. }
  200. basicClient := keyvault.New()
  201. basicClient.Authorizer = authorizer
  202. a.baseClient = &basicClient
  203. a.vaultURL = *spec.VaultURL
  204. return true, nil
  205. }
  206. func (a *Azure) secretKeyRef(ctx context.Context, namespace string, secretRef smmeta.SecretKeySelector, clusterScoped bool) (string, error) {
  207. var secret corev1.Secret
  208. ref := types.NamespacedName{
  209. Namespace: namespace,
  210. Name: secretRef.Name,
  211. }
  212. if clusterScoped && secretRef.Namespace != nil {
  213. ref.Namespace = *secretRef.Namespace
  214. }
  215. err := a.kube.Get(ctx, ref, &secret)
  216. if err != nil {
  217. return "", fmt.Errorf("could not find secret %s/%s: %w", ref.Namespace, ref.Name, err)
  218. }
  219. keyBytes, ok := secret.Data[secretRef.Key]
  220. if !ok {
  221. return "", fmt.Errorf("no data for %q in secret '%s/%s'", secretRef.Key, secretRef.Name, namespace)
  222. }
  223. value := strings.TrimSpace(string(keyBytes))
  224. return value, nil
  225. }
  226. func (a *Azure) Close(ctx context.Context) error {
  227. return nil
  228. }
  229. func getObjType(ref esv1alpha1.ExternalSecretDataRemoteRef) (string, string) {
  230. objectType := defaultObjType
  231. secretName := ref.Key
  232. nameSplitted := strings.Split(secretName, "/")
  233. if len(nameSplitted) > 1 {
  234. objectType = nameSplitted[0]
  235. secretName = nameSplitted[1]
  236. // TODO: later tokens can be used to read the secret tags
  237. }
  238. return objectType, secretName
  239. }