keyvault.go 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532
  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. "os"
  19. "path"
  20. "regexp"
  21. "strings"
  22. "github.com/Azure/azure-sdk-for-go/profiles/latest/keyvault/keyvault"
  23. "github.com/Azure/go-autorest/autorest"
  24. "github.com/Azure/go-autorest/autorest/adal"
  25. kvauth "github.com/Azure/go-autorest/autorest/azure/auth"
  26. "github.com/AzureAD/microsoft-authentication-library-for-go/apps/confidential"
  27. "github.com/tidwall/gjson"
  28. authv1 "k8s.io/api/authentication/v1"
  29. corev1 "k8s.io/api/core/v1"
  30. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  31. "k8s.io/apimachinery/pkg/types"
  32. "k8s.io/client-go/kubernetes"
  33. kcorev1 "k8s.io/client-go/kubernetes/typed/core/v1"
  34. "sigs.k8s.io/controller-runtime/pkg/client"
  35. ctrlcfg "sigs.k8s.io/controller-runtime/pkg/client/config"
  36. esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
  37. smmeta "github.com/external-secrets/external-secrets/apis/meta/v1"
  38. "github.com/external-secrets/external-secrets/pkg/utils"
  39. )
  40. const (
  41. defaultObjType = "secret"
  42. objectTypeCert = "cert"
  43. objectTypeKey = "key"
  44. vaultResource = "https://vault.azure.net"
  45. azureDefaultAudience = "api://AzureADTokenExchange"
  46. annotationClientID = "azure.workload.identity/client-id"
  47. annotationTenantID = "azure.workload.identity/tenant-id"
  48. errUnexpectedStoreSpec = "unexpected store spec"
  49. errMissingAuthType = "cannot initialize Azure Client: no valid authType was specified"
  50. errPropNotExist = "property %s does not exist in key %s"
  51. errUnknownObjectType = "unknown Azure Keyvault object Type for %s"
  52. errUnmarshalJSONData = "error unmarshalling json data: %w"
  53. errDataFromCert = "cannot get use dataFrom to get certificate secret"
  54. errDataFromKey = "cannot get use dataFrom to get key secret"
  55. errMissingTenant = "missing tenantID in store config"
  56. errMissingSecretRef = "missing secretRef in provider config"
  57. errMissingClientIDSecret = "missing accessKeyID/secretAccessKey in store config"
  58. errFindSecret = "could not find secret %s/%s: %w"
  59. errFindDataKey = "no data for %q in secret '%s/%s'"
  60. errInvalidStore = "invalid store"
  61. errInvalidStoreSpec = "invalid store spec"
  62. errInvalidStoreProv = "invalid store provider"
  63. errInvalidAzureProv = "invalid azure keyvault provider"
  64. errInvalidSecRefClientID = "invalid AuthSecretRef.ClientID: %w"
  65. errInvalidSecRefClientSecret = "invalid AuthSecretRef.ClientSecret: %w"
  66. errInvalidSARef = "invalid ServiceAccountRef: %w"
  67. errMissingWorkloadEnvVars = "missing environment variables. AZURE_CLIENT_ID, AZURE_TENANT_ID and AZURE_FEDERATED_TOKEN_FILE must be set"
  68. errReadTokenFile = "unable to read token file %s: %w"
  69. errMissingSAAnnotation = "missing service account annotation: %s"
  70. )
  71. // https://github.com/external-secrets/external-secrets/issues/644
  72. var _ esv1beta1.SecretsClient = &Azure{}
  73. var _ esv1beta1.Provider = &Azure{}
  74. // interface to keyvault.BaseClient.
  75. type SecretClient interface {
  76. GetKey(ctx context.Context, vaultBaseURL string, keyName string, keyVersion string) (result keyvault.KeyBundle, err error)
  77. GetSecret(ctx context.Context, vaultBaseURL string, secretName string, secretVersion string) (result keyvault.SecretBundle, err error)
  78. GetSecretsComplete(ctx context.Context, vaultBaseURL string, maxresults *int32) (result keyvault.SecretListResultIterator, err error)
  79. GetCertificate(ctx context.Context, vaultBaseURL string, certificateName string, certificateVersion string) (result keyvault.CertificateBundle, err error)
  80. }
  81. type Azure struct {
  82. crClient client.Client
  83. kubeClient kcorev1.CoreV1Interface
  84. store esv1beta1.GenericStore
  85. provider *esv1beta1.AzureKVProvider
  86. baseClient SecretClient
  87. namespace string
  88. }
  89. func init() {
  90. esv1beta1.Register(&Azure{}, &esv1beta1.SecretStoreProvider{
  91. AzureKV: &esv1beta1.AzureKVProvider{},
  92. })
  93. }
  94. // NewClient constructs a new secrets client based on the provided store.
  95. func (a *Azure) NewClient(ctx context.Context, store esv1beta1.GenericStore, kube client.Client, namespace string) (esv1beta1.SecretsClient, error) {
  96. return newClient(ctx, store, kube, namespace)
  97. }
  98. func newClient(ctx context.Context, store esv1beta1.GenericStore, kube client.Client, namespace string) (esv1beta1.SecretsClient, error) {
  99. provider, err := getProvider(store)
  100. if err != nil {
  101. return nil, err
  102. }
  103. cfg, err := ctrlcfg.GetConfig()
  104. if err != nil {
  105. return nil, err
  106. }
  107. kubeClient, err := kubernetes.NewForConfig(cfg)
  108. if err != nil {
  109. return nil, err
  110. }
  111. az := &Azure{
  112. crClient: kube,
  113. kubeClient: kubeClient.CoreV1(),
  114. store: store,
  115. namespace: namespace,
  116. provider: provider,
  117. }
  118. var authorizer autorest.Authorizer
  119. switch *provider.AuthType {
  120. case esv1beta1.AzureManagedIdentity:
  121. authorizer, err = az.authorizerForManagedIdentity()
  122. case esv1beta1.AzureServicePrincipal:
  123. authorizer, err = az.authorizerForServicePrincipal(ctx)
  124. case esv1beta1.AzureWorkloadIdentity:
  125. authorizer, err = az.authorizerForWorkloadIdentity(ctx, newTokenProvider)
  126. default:
  127. err = fmt.Errorf(errMissingAuthType)
  128. }
  129. cl := keyvault.New()
  130. cl.Authorizer = authorizer
  131. az.baseClient = &cl
  132. return az, err
  133. }
  134. func getProvider(store esv1beta1.GenericStore) (*esv1beta1.AzureKVProvider, error) {
  135. spc := store.GetSpec()
  136. if spc == nil || spc.Provider.AzureKV == nil {
  137. return nil, errors.New(errUnexpectedStoreSpec)
  138. }
  139. return spc.Provider.AzureKV, nil
  140. }
  141. func (a *Azure) ValidateStore(store esv1beta1.GenericStore) error {
  142. if store == nil {
  143. return fmt.Errorf(errInvalidStore)
  144. }
  145. spc := store.GetSpec()
  146. if spc == nil {
  147. return fmt.Errorf(errInvalidStoreSpec)
  148. }
  149. if spc.Provider == nil {
  150. return fmt.Errorf(errInvalidStoreProv)
  151. }
  152. p := spc.Provider.AzureKV
  153. if p == nil {
  154. return fmt.Errorf(errInvalidAzureProv)
  155. }
  156. if p.AuthSecretRef != nil {
  157. if p.AuthSecretRef.ClientID != nil {
  158. if err := utils.ValidateSecretSelector(store, *p.AuthSecretRef.ClientID); err != nil {
  159. return fmt.Errorf(errInvalidSecRefClientID, err)
  160. }
  161. }
  162. if p.AuthSecretRef.ClientSecret != nil {
  163. if err := utils.ValidateSecretSelector(store, *p.AuthSecretRef.ClientSecret); err != nil {
  164. return fmt.Errorf(errInvalidSecRefClientSecret, err)
  165. }
  166. }
  167. }
  168. if p.ServiceAccountRef != nil {
  169. if err := utils.ValidateServiceAccountSelector(store, *p.ServiceAccountRef); err != nil {
  170. return fmt.Errorf(errInvalidSARef, err)
  171. }
  172. }
  173. return nil
  174. }
  175. // Implements store.Client.GetAllSecrets Interface.
  176. // Retrieves a map[string][]byte with the secret names as key and the secret itself as the calue.
  177. func (a *Azure) GetAllSecrets(ctx context.Context, ref esv1beta1.ExternalSecretFind) (map[string][]byte, error) {
  178. basicClient := a.baseClient
  179. secretsMap := make(map[string][]byte)
  180. checkTags := len(ref.Tags) > 0
  181. checkName := ref.Name != nil && len(ref.Name.RegExp) > 0
  182. secretListIter, err := basicClient.GetSecretsComplete(context.Background(), *a.provider.VaultURL, nil)
  183. if err != nil {
  184. return nil, err
  185. }
  186. for secretListIter.NotDone() {
  187. secretList := secretListIter.Response().Value
  188. for _, secret := range *secretList {
  189. ok, secretName := isValidSecret(checkTags, checkName, ref, secret)
  190. if !ok {
  191. continue
  192. }
  193. secretResp, err := basicClient.GetSecret(context.Background(), *a.provider.VaultURL, secretName, "")
  194. if err != nil {
  195. return nil, err
  196. }
  197. secretValue := *secretResp.Value
  198. secretsMap[secretName] = []byte(secretValue)
  199. }
  200. err = secretListIter.Next()
  201. if err != nil {
  202. return nil, err
  203. }
  204. }
  205. return secretsMap, nil
  206. }
  207. // Implements store.Client.GetSecret Interface.
  208. // Retrieves a secret/Key/Certificate with the secret name defined in ref.Name
  209. // The Object Type is defined as a prefix in the ref.Name , if no prefix is defined , we assume a secret is required.
  210. func (a *Azure) GetSecret(ctx context.Context, ref esv1beta1.ExternalSecretDataRemoteRef) ([]byte, error) {
  211. version := ""
  212. objectType, secretName := getObjType(ref)
  213. if ref.Version != "" {
  214. version = ref.Version
  215. }
  216. switch objectType {
  217. case defaultObjType:
  218. // returns a SecretBundle with the secret value
  219. // https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/services/keyvault/v7.0/keyvault#SecretBundle
  220. secretResp, err := a.baseClient.GetSecret(context.Background(), *a.provider.VaultURL, secretName, version)
  221. if err != nil {
  222. return nil, err
  223. }
  224. if ref.Property == "" {
  225. return []byte(*secretResp.Value), nil
  226. }
  227. res := gjson.Get(*secretResp.Value, ref.Property)
  228. if !res.Exists() {
  229. return nil, fmt.Errorf(errPropNotExist, ref.Property, ref.Key)
  230. }
  231. return []byte(res.String()), err
  232. case objectTypeCert:
  233. // returns a CertBundle. We return CER contents of x509 certificate
  234. // see: https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/services/keyvault/v7.0/keyvault#CertificateBundle
  235. secretResp, err := a.baseClient.GetCertificate(context.Background(), *a.provider.VaultURL, secretName, version)
  236. if err != nil {
  237. return nil, err
  238. }
  239. return *secretResp.Cer, nil
  240. case objectTypeKey:
  241. // returns a KeyBundle that contains a jwk
  242. // azure kv returns only public keys
  243. // see: https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/services/keyvault/v7.0/keyvault#KeyBundle
  244. keyResp, err := a.baseClient.GetKey(context.Background(), *a.provider.VaultURL, secretName, version)
  245. if err != nil {
  246. return nil, err
  247. }
  248. return json.Marshal(keyResp.Key)
  249. }
  250. return nil, fmt.Errorf(errUnknownObjectType, secretName)
  251. }
  252. // Implements store.Client.GetSecretMap Interface.
  253. // New version of GetSecretMap.
  254. func (a *Azure) GetSecretMap(ctx context.Context, ref esv1beta1.ExternalSecretDataRemoteRef) (map[string][]byte, error) {
  255. objectType, secretName := getObjType(ref)
  256. switch objectType {
  257. case defaultObjType:
  258. data, err := a.GetSecret(ctx, ref)
  259. if err != nil {
  260. return nil, err
  261. }
  262. kv := make(map[string]string)
  263. err = json.Unmarshal(data, &kv)
  264. if err != nil {
  265. return nil, fmt.Errorf(errUnmarshalJSONData, err)
  266. }
  267. secretData := make(map[string][]byte)
  268. for k, v := range kv {
  269. secretData[k] = []byte(v)
  270. }
  271. return secretData, nil
  272. case objectTypeCert:
  273. return nil, fmt.Errorf(errDataFromCert)
  274. case objectTypeKey:
  275. return nil, fmt.Errorf(errDataFromKey)
  276. }
  277. return nil, fmt.Errorf(errUnknownObjectType, secretName)
  278. }
  279. func (a *Azure) authorizerForWorkloadIdentity(ctx context.Context, tokenProvider tokenProviderFunc) (autorest.Authorizer, error) {
  280. // if no serviceAccountRef was provided
  281. // we expect certain env vars to be present.
  282. // They are set by the azure workload identity webhook.
  283. if a.provider.ServiceAccountRef == nil {
  284. clientID := os.Getenv("AZURE_CLIENT_ID")
  285. tenantID := os.Getenv("AZURE_TENANT_ID")
  286. tokenFilePath := os.Getenv("AZURE_FEDERATED_TOKEN_FILE")
  287. if clientID == "" || tenantID == "" || tokenFilePath == "" {
  288. return nil, errors.New(errMissingWorkloadEnvVars)
  289. }
  290. token, err := os.ReadFile(tokenFilePath)
  291. if err != nil {
  292. return nil, fmt.Errorf(errReadTokenFile, tokenFilePath, err)
  293. }
  294. tp, err := tokenProvider(ctx, string(token), clientID, tenantID)
  295. if err != nil {
  296. return nil, err
  297. }
  298. return autorest.NewBearerAuthorizer(tp), nil
  299. }
  300. ns := a.namespace
  301. if a.store.GetObjectKind().GroupVersionKind().Kind == esv1beta1.ClusterSecretStoreKind {
  302. ns = *a.provider.ServiceAccountRef.Namespace
  303. }
  304. var sa corev1.ServiceAccount
  305. err := a.crClient.Get(ctx, types.NamespacedName{
  306. Name: a.provider.ServiceAccountRef.Name,
  307. Namespace: ns,
  308. }, &sa)
  309. if err != nil {
  310. return nil, err
  311. }
  312. clientID, ok := sa.ObjectMeta.Annotations[annotationClientID]
  313. if !ok {
  314. return nil, fmt.Errorf(errMissingSAAnnotation, annotationClientID)
  315. }
  316. tenantID, ok := sa.ObjectMeta.Annotations[annotationTenantID]
  317. if !ok {
  318. return nil, fmt.Errorf(errMissingSAAnnotation, annotationTenantID)
  319. }
  320. token, err := fetchSAToken(ctx, ns, a.provider.ServiceAccountRef.Name, a.kubeClient)
  321. if err != nil {
  322. return nil, err
  323. }
  324. tp, err := tokenProvider(ctx, token, clientID, tenantID)
  325. if err != nil {
  326. return nil, err
  327. }
  328. return autorest.NewBearerAuthorizer(tp), nil
  329. }
  330. func fetchSAToken(ctx context.Context, ns, name string, kubeClient kcorev1.CoreV1Interface) (string, error) {
  331. token, err := kubeClient.ServiceAccounts(ns).CreateToken(ctx, name, &authv1.TokenRequest{
  332. Spec: authv1.TokenRequestSpec{
  333. Audiences: []string{azureDefaultAudience},
  334. },
  335. }, metav1.CreateOptions{})
  336. if err != nil {
  337. return "", err
  338. }
  339. return token.Status.Token, nil
  340. }
  341. // tokenProvider satisfies the adal.OAuthTokenProvider interface.
  342. type tokenProvider struct {
  343. accessToken string
  344. }
  345. type tokenProviderFunc func(ctx context.Context, token, clientID, tenantID string) (adal.OAuthTokenProvider, error)
  346. func newTokenProvider(ctx context.Context, token, clientID, tenantID string) (adal.OAuthTokenProvider, error) {
  347. // exchange token with Azure AccessToken
  348. cred, err := confidential.NewCredFromAssertion(token)
  349. if err != nil {
  350. return nil, err
  351. }
  352. // AZURE_AUTHORITY_HOST
  353. cClient, err := confidential.New(clientID, cred, confidential.WithAuthority(
  354. fmt.Sprintf("https://login.microsoftonline.com/%s/oauth2/token", tenantID),
  355. ))
  356. if err != nil {
  357. return nil, err
  358. }
  359. authRes, err := cClient.AcquireTokenByCredential(ctx, []string{
  360. "https://vault.azure.net/.default",
  361. })
  362. if err != nil {
  363. return nil, err
  364. }
  365. return &tokenProvider{
  366. accessToken: authRes.AccessToken,
  367. }, nil
  368. }
  369. func (t *tokenProvider) OAuthToken() string {
  370. return t.accessToken
  371. }
  372. func (a *Azure) authorizerForManagedIdentity() (autorest.Authorizer, error) {
  373. msiConfig := kvauth.NewMSIConfig()
  374. msiConfig.Resource = vaultResource
  375. if a.provider.IdentityID != nil {
  376. msiConfig.ClientID = *a.provider.IdentityID
  377. }
  378. return msiConfig.Authorizer()
  379. }
  380. func (a *Azure) authorizerForServicePrincipal(ctx context.Context) (autorest.Authorizer, error) {
  381. if a.provider.TenantID == nil {
  382. return nil, fmt.Errorf(errMissingTenant)
  383. }
  384. if a.provider.AuthSecretRef == nil {
  385. return nil, fmt.Errorf(errMissingSecretRef)
  386. }
  387. if a.provider.AuthSecretRef.ClientID == nil || a.provider.AuthSecretRef.ClientSecret == nil {
  388. return nil, fmt.Errorf(errMissingClientIDSecret)
  389. }
  390. clusterScoped := false
  391. if a.store.GetObjectKind().GroupVersionKind().Kind == esv1beta1.ClusterSecretStoreKind {
  392. clusterScoped = true
  393. }
  394. cid, err := a.secretKeyRef(ctx, a.store.GetNamespace(), *a.provider.AuthSecretRef.ClientID, clusterScoped)
  395. if err != nil {
  396. return nil, err
  397. }
  398. csec, err := a.secretKeyRef(ctx, a.store.GetNamespace(), *a.provider.AuthSecretRef.ClientSecret, clusterScoped)
  399. if err != nil {
  400. return nil, err
  401. }
  402. clientCredentialsConfig := kvauth.NewClientCredentialsConfig(cid, csec, *a.provider.TenantID)
  403. clientCredentialsConfig.Resource = vaultResource
  404. return clientCredentialsConfig.Authorizer()
  405. }
  406. // secretKeyRef fetch a secret key.
  407. func (a *Azure) secretKeyRef(ctx context.Context, namespace string, secretRef smmeta.SecretKeySelector, clusterScoped bool) (string, error) {
  408. var secret corev1.Secret
  409. ref := types.NamespacedName{
  410. Namespace: namespace,
  411. Name: secretRef.Name,
  412. }
  413. if clusterScoped && secretRef.Namespace != nil {
  414. ref.Namespace = *secretRef.Namespace
  415. }
  416. err := a.crClient.Get(ctx, ref, &secret)
  417. if err != nil {
  418. return "", fmt.Errorf(errFindSecret, ref.Namespace, ref.Name, err)
  419. }
  420. keyBytes, ok := secret.Data[secretRef.Key]
  421. if !ok {
  422. return "", fmt.Errorf(errFindDataKey, secretRef.Key, secretRef.Name, namespace)
  423. }
  424. value := strings.TrimSpace(string(keyBytes))
  425. return value, nil
  426. }
  427. func (a *Azure) Close(ctx context.Context) error {
  428. return nil
  429. }
  430. func (a *Azure) Validate() (esv1beta1.ValidationResult, error) {
  431. return esv1beta1.ValidationResultReady, nil
  432. }
  433. func getObjType(ref esv1beta1.ExternalSecretDataRemoteRef) (string, string) {
  434. objectType := defaultObjType
  435. secretName := ref.Key
  436. nameSplitted := strings.Split(secretName, "/")
  437. if len(nameSplitted) > 1 {
  438. objectType = nameSplitted[0]
  439. secretName = nameSplitted[1]
  440. // TODO: later tokens can be used to read the secret tags
  441. }
  442. return objectType, secretName
  443. }
  444. func isValidSecret(checkTags, checkName bool, ref esv1beta1.ExternalSecretFind, secret keyvault.SecretItem) (bool, string) {
  445. if secret.ID == nil || !*secret.Attributes.Enabled {
  446. return false, ""
  447. }
  448. if checkTags && !okByTags(ref, secret) {
  449. return false, ""
  450. }
  451. secretName := path.Base(*secret.ID)
  452. if checkName && !okByName(ref, secretName) {
  453. return false, ""
  454. }
  455. return true, secretName
  456. }
  457. func okByName(ref esv1beta1.ExternalSecretFind, secretName string) bool {
  458. matches, _ := regexp.MatchString(ref.Name.RegExp, secretName)
  459. return matches
  460. }
  461. func okByTags(ref esv1beta1.ExternalSecretFind, secret keyvault.SecretItem) bool {
  462. tagsFound := true
  463. for k, v := range ref.Tags {
  464. if val, ok := secret.Tags[k]; !ok || *val != v {
  465. tagsFound = false
  466. break
  467. }
  468. }
  469. return tagsFound
  470. }