keyvault.go 11 KB

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