keyvault.go 15 KB

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