keyvault.go 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308
  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. esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
  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 esv1beta1.GenericStore
  58. provider *esv1beta1.AzureKVProvider
  59. baseClient SecretClient
  60. namespace string
  61. }
  62. func init() {
  63. schema.Register(&Azure{}, &esv1beta1.SecretStoreProvider{
  64. AzureKV: &esv1beta1.AzureKVProvider{},
  65. })
  66. }
  67. // NewClient constructs a new secrets client based on the provided store.
  68. func (a *Azure) NewClient(ctx context.Context, store esv1beta1.GenericStore, kube client.Client, namespace string) (provider.SecretsClient, error) {
  69. return newClient(ctx, store, kube, namespace)
  70. }
  71. func newClient(ctx context.Context, store esv1beta1.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 esv1beta1.GenericStore) (*esv1beta1.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. // Empty GetAllSecrets.
  100. func (a *Azure) GetAllSecrets(ctx context.Context, ref esv1beta1.ExternalSecretFind) (map[string][]byte, error) {
  101. // TO be implemented
  102. return nil, fmt.Errorf("GetAllSecrets not implemented")
  103. }
  104. // Implements store.Client.GetSecret Interface.
  105. // Retrieves a secret/Key/Certificate with the secret name defined in ref.Name
  106. // The Object Type is defined as a prefix in the ref.Name , if no prefix is defined , we assume a secret is required.
  107. func (a *Azure) GetSecret(ctx context.Context, ref esv1beta1.ExternalSecretDataRemoteRef) ([]byte, error) {
  108. version := ""
  109. objectType, secretName := getObjType(ref)
  110. if ref.Version != "" {
  111. version = ref.Version
  112. }
  113. switch objectType {
  114. case defaultObjType:
  115. // returns a SecretBundle with the secret value
  116. // https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/services/keyvault/v7.0/keyvault#SecretBundle
  117. secretResp, err := a.baseClient.GetSecret(context.Background(), *a.provider.VaultURL, secretName, version)
  118. if err != nil {
  119. return nil, err
  120. }
  121. if ref.Property == "" {
  122. return []byte(*secretResp.Value), nil
  123. }
  124. res := gjson.Get(*secretResp.Value, ref.Property)
  125. if !res.Exists() {
  126. return nil, fmt.Errorf(errPropNotExist, ref.Property, ref.Key)
  127. }
  128. return []byte(res.String()), err
  129. case objectTypeCert:
  130. // returns a CertBundle. We return CER contents of x509 certificate
  131. // see: https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/services/keyvault/v7.0/keyvault#CertificateBundle
  132. secretResp, err := a.baseClient.GetCertificate(context.Background(), *a.provider.VaultURL, secretName, version)
  133. if err != nil {
  134. return nil, err
  135. }
  136. return *secretResp.Cer, nil
  137. case objectTypeKey:
  138. // returns a KeyBundle that contains a jwk
  139. // azure kv returns only public keys
  140. // see: https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/services/keyvault/v7.0/keyvault#KeyBundle
  141. keyResp, err := a.baseClient.GetKey(context.Background(), *a.provider.VaultURL, secretName, version)
  142. if err != nil {
  143. return nil, err
  144. }
  145. return json.Marshal(keyResp.Key)
  146. }
  147. return nil, fmt.Errorf(errUnknownObjectType, secretName)
  148. }
  149. // Implements store.Client.GetSecretMap Interface.
  150. // New version of GetSecretMap.
  151. func (a *Azure) GetSecretMap(ctx context.Context, ref esv1beta1.ExternalSecretDataRemoteRef) (map[string][]byte, error) {
  152. objectType, secretName := getObjType(ref)
  153. switch objectType {
  154. case defaultObjType:
  155. data, err := a.GetSecret(ctx, ref)
  156. if err != nil {
  157. return nil, err
  158. }
  159. kv := make(map[string]string)
  160. err = json.Unmarshal(data, &kv)
  161. if err != nil {
  162. return nil, fmt.Errorf(errUnmarshalJSONData, err)
  163. }
  164. secretData := make(map[string][]byte)
  165. for k, v := range kv {
  166. secretData[k] = []byte(v)
  167. }
  168. return secretData, nil
  169. case objectTypeCert:
  170. return nil, fmt.Errorf(errDataFromCert)
  171. case objectTypeKey:
  172. return nil, fmt.Errorf(errDataFromKey)
  173. }
  174. return nil, fmt.Errorf(errUnknownObjectType, secretName)
  175. }
  176. func (a *Azure) setAzureClientWithManagedIdentity() (bool, error) {
  177. if *a.provider.AuthType != esv1beta1.ManagedIdentity {
  178. return false, nil
  179. }
  180. msiConfig := kvauth.NewMSIConfig()
  181. msiConfig.Resource = vaultResource
  182. if a.provider.IdentityID != nil {
  183. msiConfig.ClientID = *a.provider.IdentityID
  184. }
  185. authorizer, err := msiConfig.Authorizer()
  186. if err != nil {
  187. return true, err
  188. }
  189. cl := keyvault.New()
  190. cl.Authorizer = authorizer
  191. a.baseClient = &cl
  192. return true, nil
  193. }
  194. func (a *Azure) setAzureClientWithServicePrincipal(ctx context.Context) (bool, error) {
  195. if *a.provider.AuthType != esv1beta1.ServicePrincipal {
  196. return false, nil
  197. }
  198. if a.provider.TenantID == nil {
  199. return true, fmt.Errorf(errMissingTenant)
  200. }
  201. if a.provider.AuthSecretRef == nil {
  202. return true, fmt.Errorf(errMissingSecretRef)
  203. }
  204. if a.provider.AuthSecretRef.ClientID == nil || a.provider.AuthSecretRef.ClientSecret == nil {
  205. return true, fmt.Errorf(errMissingClientIDSecret)
  206. }
  207. clusterScoped := false
  208. if a.store.GetObjectKind().GroupVersionKind().Kind == esv1beta1.ClusterSecretStoreKind {
  209. clusterScoped = true
  210. }
  211. cid, err := a.secretKeyRef(ctx, a.store.GetNamespace(), *a.provider.AuthSecretRef.ClientID, clusterScoped)
  212. if err != nil {
  213. return true, err
  214. }
  215. csec, err := a.secretKeyRef(ctx, a.store.GetNamespace(), *a.provider.AuthSecretRef.ClientSecret, clusterScoped)
  216. if err != nil {
  217. return true, err
  218. }
  219. clientCredentialsConfig := kvauth.NewClientCredentialsConfig(cid, csec, *a.provider.TenantID)
  220. clientCredentialsConfig.Resource = vaultResource
  221. authorizer, err := clientCredentialsConfig.Authorizer()
  222. if err != nil {
  223. return true, err
  224. }
  225. cl := keyvault.New()
  226. cl.Authorizer = authorizer
  227. a.baseClient = &cl
  228. return true, nil
  229. }
  230. func (a *Azure) secretKeyRef(ctx context.Context, namespace string, secretRef smmeta.SecretKeySelector, clusterScoped bool) (string, error) {
  231. var secret corev1.Secret
  232. ref := types.NamespacedName{
  233. Namespace: namespace,
  234. Name: secretRef.Name,
  235. }
  236. if clusterScoped && secretRef.Namespace != nil {
  237. ref.Namespace = *secretRef.Namespace
  238. }
  239. err := a.kube.Get(ctx, ref, &secret)
  240. if err != nil {
  241. return "", fmt.Errorf(errFindSecret, ref.Namespace, ref.Name, err)
  242. }
  243. keyBytes, ok := secret.Data[secretRef.Key]
  244. if !ok {
  245. return "", fmt.Errorf(errFindDataKey, secretRef.Key, secretRef.Name, namespace)
  246. }
  247. value := strings.TrimSpace(string(keyBytes))
  248. return value, nil
  249. }
  250. func (a *Azure) Close(ctx context.Context) error {
  251. return nil
  252. }
  253. func (a *Azure) Validate() error {
  254. return nil
  255. }
  256. func getObjType(ref esv1beta1.ExternalSecretDataRemoteRef) (string, string) {
  257. objectType := defaultObjType
  258. secretName := ref.Key
  259. nameSplitted := strings.Split(secretName, "/")
  260. if len(nameSplitted) > 1 {
  261. objectType = nameSplitted[0]
  262. secretName = nameSplitted[1]
  263. // TODO: later tokens can be used to read the secret tags
  264. }
  265. return objectType, secretName
  266. }