keyvault.go 9.5 KB

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