keyvault_new_sdk.go 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758
  1. /*
  2. Copyright © The ESO Authors
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. https://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package keyvault
  14. import (
  15. "context"
  16. "crypto/sha3"
  17. b64 "encoding/base64"
  18. "encoding/json"
  19. "errors"
  20. "fmt"
  21. "maps"
  22. "regexp"
  23. "time"
  24. "github.com/Azure/azure-sdk-for-go/sdk/azcore"
  25. "github.com/Azure/azure-sdk-for-go/sdk/azcore/cloud"
  26. "github.com/Azure/azure-sdk-for-go/sdk/azidentity"
  27. "github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azcertificates"
  28. "github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys"
  29. "github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets"
  30. "github.com/aws/smithy-go/ptr"
  31. "github.com/lestrrat-go/jwx/v2/jwk"
  32. corev1 "k8s.io/api/core/v1"
  33. "k8s.io/apimachinery/pkg/types"
  34. esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
  35. "github.com/external-secrets/external-secrets/runtime/constants"
  36. "github.com/external-secrets/external-secrets/runtime/esutils/resolvers"
  37. "github.com/external-secrets/external-secrets/runtime/metrics"
  38. )
  39. // New SDK implementations for setter methods.
  40. func (a *Azure) setKeyVaultSecretWithNewSDK(ctx context.Context, secretName string, value []byte, _ *time.Time, tags map[string]string) error {
  41. // Check if secret exists and if we can create/update it
  42. existingSecret, err := a.secretsClient.GetSecret(ctx, secretName, "", nil)
  43. metrics.ObserveAPICall(constants.ProviderAzureKV, constants.CallAzureKVGetSecret, err)
  44. if err != nil {
  45. var respErr *azcore.ResponseError
  46. if !errors.As(err, &respErr) || respErr.StatusCode != 404 {
  47. return fmt.Errorf("cannot get secret %v: %w", secretName, parseNewSDKError(err))
  48. }
  49. } else {
  50. // Check if managed by external-secrets using new SDK tags
  51. if existingSecret.Tags != nil {
  52. if managedByTag, exists := existingSecret.Tags[managedBy]; !exists || managedByTag == nil || *managedByTag != managerLabel {
  53. return fmt.Errorf("secret %v not managed by external-secrets", secretName)
  54. }
  55. }
  56. // Check if secret content is the same
  57. val := string(value)
  58. if existingSecret.Value != nil && val == *existingSecret.Value {
  59. // Note: We're not checking expiration here since the new SDK doesn't support setting it
  60. // This means the new SDK implementation will always update the secret if the content is the same
  61. // but different expiration is requested
  62. return nil
  63. }
  64. }
  65. // Prepare tags for new SDK
  66. secretTags := map[string]*string{
  67. managedBy: new(managerLabel),
  68. }
  69. for k, v := range tags {
  70. secretTags[k] = &v
  71. }
  72. // Set the secret
  73. val := string(value)
  74. params := azsecrets.SetSecretParameters{
  75. Value: &val,
  76. Tags: secretTags,
  77. }
  78. // Note: The new SDK doesn't support setting expiration in SetSecretParameters
  79. // This is a limitation compared to the legacy SDK - expiration would need to be handled differently
  80. _, err = a.secretsClient.SetSecret(ctx, secretName, params, nil)
  81. metrics.ObserveAPICall(constants.ProviderAzureKV, constants.CallAzureKVSetSecret, err)
  82. if err != nil {
  83. return fmt.Errorf("could not set secret %v: %w", secretName, parseNewSDKError(err))
  84. }
  85. return nil
  86. }
  87. func (a *Azure) setKeyVaultCertificateWithNewSDK(ctx context.Context, secretName string, value []byte, tags map[string]string) error {
  88. val := b64.StdEncoding.EncodeToString(value)
  89. localCert, err := getCertificateFromValue(value)
  90. if err != nil {
  91. return fmt.Errorf("value from secret is not a valid certificate: %w", err)
  92. }
  93. // Check if certificate exists
  94. cert, err := a.certsClient.GetCertificate(ctx, secretName, "", nil)
  95. metrics.ObserveAPICall(constants.ProviderAzureKV, constants.CallAzureKVGetCertificate, err)
  96. if err != nil {
  97. var respErr *azcore.ResponseError
  98. if !errors.As(err, &respErr) || respErr.StatusCode != 404 {
  99. return fmt.Errorf("cannot get certificate %v: %w", secretName, parseNewSDKError(err))
  100. }
  101. } else {
  102. // Check if managed by external-secrets
  103. if cert.Tags != nil {
  104. if managedByTag, exists := cert.Tags[managedBy]; !exists || managedByTag == nil || *managedByTag != managerLabel {
  105. return fmt.Errorf("certificate %v not managed by external-secrets", secretName)
  106. }
  107. }
  108. // Check if certificate content is the same
  109. b512 := sha3.Sum512(localCert.Raw)
  110. if cert.CER != nil && b512 == sha3.Sum512(cert.CER) {
  111. return nil
  112. }
  113. }
  114. // Prepare tags for new SDK
  115. certTags := map[string]*string{
  116. managedBy: new(managerLabel),
  117. }
  118. for k, v := range tags {
  119. certTags[k] = &v
  120. }
  121. params := azcertificates.ImportCertificateParameters{
  122. Base64EncodedCertificate: &val,
  123. Tags: certTags,
  124. }
  125. _, err = a.certsClient.ImportCertificate(ctx, secretName, params, nil)
  126. metrics.ObserveAPICall(constants.ProviderAzureKV, constants.CallAzureKVImportCertificate, err)
  127. if err != nil {
  128. return fmt.Errorf("could not import certificate %v: %w", secretName, parseNewSDKError(err))
  129. }
  130. return nil
  131. }
  132. func (a *Azure) setKeyVaultKeyWithNewSDK(ctx context.Context, secretName string, value []byte, tags map[string]string) error {
  133. key, err := getKeyFromValue(value)
  134. if err != nil {
  135. return fmt.Errorf("could not load private key %v: %w", secretName, err)
  136. }
  137. jwKey, err := jwk.FromRaw(key)
  138. if err != nil {
  139. return fmt.Errorf("failed to generate a JWK from secret %v content: %w", secretName, err)
  140. }
  141. buf, err := json.Marshal(jwKey)
  142. if err != nil {
  143. return fmt.Errorf("error parsing key: %w", err)
  144. }
  145. var azkey azkeys.JSONWebKey
  146. err = json.Unmarshal(buf, &azkey)
  147. if err != nil {
  148. return fmt.Errorf("error unmarshalling key: %w", err)
  149. }
  150. // Check if key exists
  151. keyFromVault, err := a.keysClient.GetKey(ctx, secretName, "", nil)
  152. metrics.ObserveAPICall(constants.ProviderAzureKV, constants.CallAzureKVGetKey, err)
  153. if err != nil {
  154. var respErr *azcore.ResponseError
  155. if !errors.As(err, &respErr) || respErr.StatusCode != 404 {
  156. return fmt.Errorf("cannot get key %v: %w", secretName, parseNewSDKError(err))
  157. }
  158. } else if keyFromVault.Tags != nil {
  159. // Check if managed by external-secrets
  160. if managedByTag, exists := keyFromVault.Tags[managedBy]; !exists || managedByTag == nil || *managedByTag != managerLabel {
  161. return fmt.Errorf("key %v not managed by external-secrets", secretName)
  162. }
  163. }
  164. // For key comparison, we'll do a simple check - if we get here and the key exists, we'll update it
  165. // A more sophisticated comparison could be added later if needed
  166. // Prepare tags for new SDK
  167. keyTags := map[string]*string{
  168. managedBy: new(managerLabel),
  169. }
  170. for k, v := range tags {
  171. keyTags[k] = &v
  172. }
  173. params := azkeys.ImportKeyParameters{
  174. Key: &azkey,
  175. KeyAttributes: &azkeys.KeyAttributes{
  176. Enabled: new(true),
  177. },
  178. Tags: keyTags,
  179. }
  180. _, err = a.keysClient.ImportKey(ctx, secretName, params, nil)
  181. metrics.ObserveAPICall(constants.ProviderAzureKV, constants.CallAzureKVImportKey, err)
  182. if err != nil {
  183. return fmt.Errorf("could not import key %v: %w", secretName, parseNewSDKError(err))
  184. }
  185. return nil
  186. }
  187. // isValidSecret checks if a secret is valid and enabled.
  188. func (a *Azure) isValidSecret(secret *azsecrets.SecretProperties) bool {
  189. return secret.ID != nil &&
  190. secret.Attributes != nil &&
  191. *secret.Attributes.Enabled
  192. }
  193. // secretMatchesTags checks if secret matches required tags.
  194. func (a *Azure) secretMatchesTags(secret *azsecrets.SecretProperties, requiredTags map[string]string) bool {
  195. if len(requiredTags) == 0 {
  196. return true
  197. }
  198. for k, v := range requiredTags {
  199. if val, ok := secret.Tags[k]; !ok || val == nil || *val != v {
  200. return false
  201. }
  202. }
  203. return true
  204. }
  205. // secretMatchesNamePattern checks if secret name matches the regex pattern.
  206. // Logs error and returns false if the regex is invalid to ensure failed matches are excluded.
  207. func (a *Azure) secretMatchesNamePattern(secretName string, nameRef *esv1.FindName) bool {
  208. if nameRef == nil || nameRef.RegExp == "" {
  209. return true
  210. }
  211. isMatch, err := regexp.MatchString(nameRef.RegExp, secretName)
  212. if err != nil {
  213. // Log invalid regex pattern and return false to exclude this secret
  214. // This ensures that malformed regex patterns don't silently pass
  215. fmt.Printf("invalid regex pattern %q: %v\n", nameRef.RegExp, err)
  216. return false
  217. }
  218. return isMatch
  219. }
  220. // processSecretsPage processes a single page of secrets from the list operation.
  221. func (a *Azure) processSecretsPage(ctx context.Context, secrets []*azsecrets.SecretProperties, ref esv1.ExternalSecretFind, secretsMap map[string][]byte) error {
  222. for _, secret := range secrets {
  223. if !a.isValidSecret(secret) {
  224. continue
  225. }
  226. if !a.secretMatchesTags(secret, ref.Tags) {
  227. continue
  228. }
  229. secretName := secret.ID.Name()
  230. if !a.secretMatchesNamePattern(secretName, ref.Name) {
  231. continue
  232. }
  233. // Get the secret value
  234. secretResp, err := a.secretsClient.GetSecret(ctx, secretName, "", nil)
  235. metrics.ObserveAPICall(constants.ProviderAzureKV, constants.CallAzureKVGetSecret, err)
  236. if err != nil {
  237. return parseNewSDKError(err)
  238. }
  239. if secretResp.Value != nil {
  240. secretsMap[secretName] = []byte(*secretResp.Value)
  241. }
  242. }
  243. return nil
  244. }
  245. func (a *Azure) getAllSecretsWithNewSDK(ctx context.Context, ref esv1.ExternalSecretFind) (map[string][]byte, error) {
  246. secretsMap := make(map[string][]byte)
  247. pager := a.secretsClient.NewListSecretPropertiesPager(nil)
  248. for pager.More() {
  249. page, err := pager.NextPage(ctx)
  250. metrics.ObserveAPICall(constants.ProviderAzureKV, constants.CallAzureKVGetSecrets, err)
  251. if err != nil {
  252. return nil, parseNewSDKError(err)
  253. }
  254. if err := a.processSecretsPage(ctx, page.Value, ref, secretsMap); err != nil {
  255. return nil, err
  256. }
  257. }
  258. return secretsMap, nil
  259. }
  260. func (a *Azure) getSecretTagsWithNewSDK(ctx context.Context, ref esv1.ExternalSecretDataRemoteRef) (map[string]*string, error) {
  261. _, secretName := getObjType(ref)
  262. secretResp, err := a.secretsClient.GetSecret(ctx, secretName, ref.Version, nil)
  263. metrics.ObserveAPICall(constants.ProviderAzureKV, constants.CallAzureKVGetSecret, err)
  264. if err != nil {
  265. return nil, parseNewSDKError(err)
  266. }
  267. secretTagsData := make(map[string]*string)
  268. for tagname, tagval := range secretResp.Tags {
  269. name := secretName + "_" + tagname
  270. kv := make(map[string]string)
  271. err = json.Unmarshal([]byte(*tagval), &kv)
  272. // if the tagvalue is not in JSON format then we added to secretTagsData we added as it is
  273. if err != nil {
  274. secretTagsData[name] = tagval
  275. } else {
  276. for k, v := range kv {
  277. value := v
  278. secretTagsData[name+"_"+k] = &value
  279. }
  280. }
  281. }
  282. return secretTagsData, nil
  283. }
  284. // Helper functions for new Azure SDK
  285. // getCloudConfiguration returns the appropriate cloud configuration for the environment type.
  286. func getCloudConfiguration(provider *esv1.AzureKVProvider) (cloud.Configuration, error) {
  287. if provider.CustomCloudConfig != nil {
  288. if !ptr.ToBool(provider.UseAzureSDK) {
  289. return cloud.Configuration{}, errors.New("CustomCloudConfig requires UseAzureSDK to be set to true")
  290. }
  291. var baseConfig cloud.Configuration
  292. switch provider.EnvironmentType {
  293. case esv1.AzureEnvironmentGermanCloud:
  294. return cloud.Configuration{}, errors.New(
  295. "Azure Germany (Microsoft Cloud Deutschland) was discontinued on October 29, 2021. Please use AzureStackCloud with custom configuration or migrate to public cloud regions",
  296. )
  297. case esv1.AzureEnvironmentPublicCloud:
  298. baseConfig = cloud.AzurePublic
  299. case esv1.AzureEnvironmentUSGovernmentCloud:
  300. baseConfig = cloud.AzureGovernment
  301. case esv1.AzureEnvironmentChinaCloud:
  302. baseConfig = cloud.AzureChina
  303. case esv1.AzureEnvironmentAzureStackCloud:
  304. baseConfig = cloud.Configuration{
  305. Services: map[cloud.ServiceName]cloud.ServiceConfiguration{},
  306. }
  307. default:
  308. baseConfig = cloud.AzurePublic
  309. }
  310. return buildCustomCloudConfiguration(provider.CustomCloudConfig, baseConfig)
  311. }
  312. // no custom config - use standard cloud configurations
  313. switch provider.EnvironmentType {
  314. case esv1.AzureEnvironmentPublicCloud:
  315. return cloud.AzurePublic, nil
  316. case esv1.AzureEnvironmentUSGovernmentCloud:
  317. return cloud.AzureGovernment, nil
  318. case esv1.AzureEnvironmentChinaCloud:
  319. return cloud.AzureChina, nil
  320. case esv1.AzureEnvironmentGermanCloud:
  321. return cloud.Configuration{}, errors.New(
  322. "Azure Germany (Microsoft Cloud Deutschland) was discontinued on October 29, 2021. Please use AzureStackCloud with custom configuration or migrate to public cloud regions",
  323. )
  324. case esv1.AzureEnvironmentAzureStackCloud:
  325. return cloud.Configuration{}, errors.New("CustomCloudConfig is required when EnvironmentType is AzureStackCloud")
  326. default:
  327. return cloud.AzurePublic, nil
  328. }
  329. }
  330. // buildCustomCloudConfiguration creates a custom cloud.Configuration by merging custom config with base config.
  331. func buildCustomCloudConfiguration(config *esv1.AzureCustomCloudConfig, baseConfig cloud.Configuration) (cloud.Configuration, error) {
  332. cloudConfig := cloud.Configuration{
  333. ActiveDirectoryAuthorityHost: baseConfig.ActiveDirectoryAuthorityHost,
  334. Services: map[cloud.ServiceName]cloud.ServiceConfiguration{},
  335. }
  336. maps.Copy(cloudConfig.Services, baseConfig.Services)
  337. // Set Active Directory endpoint with custom value (required)
  338. cloudConfig.ActiveDirectoryAuthorityHost = config.ActiveDirectoryEndpoint
  339. // Set Resource Manager endpoint if provided
  340. if config.ResourceManagerEndpoint != nil {
  341. cloudConfig.Services[cloud.ResourceManager] = cloud.ServiceConfiguration{
  342. Audience: *config.ResourceManagerEndpoint,
  343. Endpoint: *config.ResourceManagerEndpoint,
  344. }
  345. }
  346. // Note: Key Vault endpoint and DNS suffix are handled directly by the Key Vault client
  347. // through the vault URL, not through the cloud configuration
  348. return cloudConfig, nil
  349. }
  350. // buildManagedIdentityCredential creates a ManagedIdentityCredential.
  351. func buildManagedIdentityCredential(az *Azure, cloudConfig cloud.Configuration) (azcore.TokenCredential, error) {
  352. opts := &azidentity.ManagedIdentityCredentialOptions{
  353. ClientOptions: azcore.ClientOptions{
  354. Cloud: cloudConfig,
  355. },
  356. }
  357. // Configure user-assigned identity if specified
  358. if az.provider.IdentityID != nil {
  359. opts.ID = azidentity.ClientID(*az.provider.IdentityID)
  360. }
  361. return azidentity.NewManagedIdentityCredential(opts)
  362. }
  363. // buildServicePrincipalCredential creates service principal credentials.
  364. func buildServicePrincipalCredential(ctx context.Context, az *Azure, cloudConfig cloud.Configuration) (azcore.TokenCredential, error) {
  365. if az.provider.TenantID == nil {
  366. return nil, errors.New(errMissingTenant)
  367. }
  368. if az.provider.AuthSecretRef == nil {
  369. return nil, errors.New(errMissingSecretRef)
  370. }
  371. if az.provider.AuthSecretRef.ClientID == nil {
  372. return nil, errors.New(errMissingClientIDSecret)
  373. }
  374. // Get clientID
  375. clientID, err := resolvers.SecretKeyRef(
  376. ctx,
  377. az.crClient,
  378. az.store.GetKind(),
  379. az.namespace,
  380. az.provider.AuthSecretRef.ClientID,
  381. )
  382. if err != nil {
  383. return nil, fmt.Errorf("failed to get clientID: %w", err)
  384. }
  385. clientOpts := azcore.ClientOptions{
  386. Cloud: cloudConfig,
  387. }
  388. // Check if using client secret or client certificate
  389. if az.provider.AuthSecretRef.ClientSecret != nil && az.provider.AuthSecretRef.ClientCertificate != nil {
  390. return nil, errors.New(errInvalidClientCredentials)
  391. }
  392. if az.provider.AuthSecretRef.ClientSecret != nil {
  393. // Client secret authentication
  394. clientSecret, err := resolvers.SecretKeyRef(
  395. ctx,
  396. az.crClient,
  397. az.store.GetKind(),
  398. az.namespace,
  399. az.provider.AuthSecretRef.ClientSecret,
  400. )
  401. if err != nil {
  402. return nil, fmt.Errorf("failed to get clientSecret: %w", err)
  403. }
  404. opts := &azidentity.ClientSecretCredentialOptions{
  405. ClientOptions: clientOpts,
  406. }
  407. return azidentity.NewClientSecretCredential(*az.provider.TenantID, clientID, clientSecret, opts)
  408. } else if az.provider.AuthSecretRef.ClientCertificate != nil {
  409. // Client certificate authentication
  410. certData, err := resolvers.SecretKeyRef(
  411. ctx,
  412. az.crClient,
  413. az.store.GetKind(),
  414. az.namespace,
  415. az.provider.AuthSecretRef.ClientCertificate,
  416. )
  417. if err != nil {
  418. return nil, fmt.Errorf("failed to get clientCertificate: %w", err)
  419. }
  420. // Parse certificate and key
  421. certs, key, err := azidentity.ParseCertificates([]byte(certData), nil)
  422. if err != nil {
  423. return nil, fmt.Errorf("failed to parse client certificate: %w", err)
  424. }
  425. opts := &azidentity.ClientCertificateCredentialOptions{
  426. ClientOptions: clientOpts,
  427. }
  428. return azidentity.NewClientCertificateCredential(*az.provider.TenantID, clientID, certs, key, opts)
  429. }
  430. return nil, errors.New(errMissingClientIDSecret)
  431. }
  432. // buildWorkloadIdentityCredential creates workload identity credentials.
  433. func buildWorkloadIdentityCredential(ctx context.Context, az *Azure, cloudConfig cloud.Configuration) (azcore.TokenCredential, error) {
  434. clientOpts := azcore.ClientOptions{
  435. Cloud: cloudConfig,
  436. }
  437. // If no serviceAccountRef is provided, use environment variables (webhook mode)
  438. if az.provider.ServiceAccountRef == nil {
  439. opts := &azidentity.WorkloadIdentityCredentialOptions{
  440. ClientOptions: clientOpts,
  441. }
  442. return azidentity.NewWorkloadIdentityCredential(opts)
  443. }
  444. // ServiceAccountRef mode - get values from service account and secrets
  445. ns := az.namespace
  446. if az.store.GetKind() == esv1.ClusterSecretStoreKind && az.provider.ServiceAccountRef.Namespace != nil {
  447. ns = *az.provider.ServiceAccountRef.Namespace
  448. }
  449. var sa corev1.ServiceAccount
  450. err := az.crClient.Get(ctx, types.NamespacedName{
  451. Name: az.provider.ServiceAccountRef.Name,
  452. Namespace: ns,
  453. }, &sa)
  454. if err != nil {
  455. return nil, fmt.Errorf("failed to get service account: %w", err)
  456. }
  457. // Get clientID from service account annotations
  458. var clientID string
  459. if val, found := sa.ObjectMeta.Annotations[AnnotationClientID]; found {
  460. clientID = val
  461. } else {
  462. return nil, fmt.Errorf(errMissingClient, AnnotationClientID)
  463. }
  464. // Get tenantID
  465. var tenantID string
  466. if az.provider.TenantID != nil {
  467. tenantID = *az.provider.TenantID
  468. } else if val, found := sa.ObjectMeta.Annotations[AnnotationTenantID]; found {
  469. tenantID = val
  470. } else {
  471. return nil, errors.New(errMissingTenant)
  472. }
  473. // Use ClientAssertionCredential to avoid filesystem access in read-only environments
  474. // This provides a callback function that fetches tokens dynamically
  475. getAssertion := func(ctx context.Context) (string, error) {
  476. audiences := []string{AzureDefaultAudience}
  477. if len(az.provider.ServiceAccountRef.Audiences) > 0 {
  478. audiences = append(audiences, az.provider.ServiceAccountRef.Audiences...)
  479. }
  480. token, err := FetchSAToken(ctx, ns, az.provider.ServiceAccountRef.Name, audiences, az.kubeClient)
  481. if err != nil {
  482. return "", fmt.Errorf("failed to fetch service account token: %w", err)
  483. }
  484. return token, nil
  485. }
  486. opts := &azidentity.ClientAssertionCredentialOptions{
  487. ClientOptions: clientOpts,
  488. }
  489. return azidentity.NewClientAssertionCredential(tenantID, clientID, getAssertion, opts)
  490. }
  491. // canDeleteWithNewSDK checks if a resource can be deleted based on tags and error status.
  492. func canDeleteWithNewSDK(tags map[string]*string, err error) (bool, error) {
  493. if err != nil {
  494. var respErr *azcore.ResponseError
  495. if errors.As(err, &respErr) {
  496. if respErr.StatusCode == 404 {
  497. // Resource doesn't exist, nothing to delete
  498. return false, nil
  499. }
  500. // Other API error
  501. return false, fmt.Errorf("unexpected api error: %w", err)
  502. }
  503. // Non-Azure error
  504. return false, fmt.Errorf("could not parse error: %w", err)
  505. }
  506. // Check if managed by external-secrets
  507. if tags == nil {
  508. return false, nil
  509. }
  510. managedByTag, exists := tags[managedBy]
  511. if !exists || managedByTag == nil || *managedByTag != managerLabel {
  512. // Not managed by external-secrets, don't delete
  513. return false, nil
  514. }
  515. return true, nil
  516. }
  517. // Delete methods using new Azure SDK.
  518. func (a *Azure) deleteKeyVaultSecretWithNewSDK(ctx context.Context, secretName string) error {
  519. secret, err := a.secretsClient.GetSecret(ctx, secretName, "", nil)
  520. metrics.ObserveAPICall(constants.ProviderAzureKV, constants.CallAzureKVGetSecret, err)
  521. ok, err := canDeleteWithNewSDK(secret.Tags, err)
  522. if err != nil {
  523. return fmt.Errorf("error getting secret %v: %w", secretName, err)
  524. }
  525. if ok {
  526. _, err = a.secretsClient.DeleteSecret(ctx, secretName, nil)
  527. metrics.ObserveAPICall(constants.ProviderAzureKV, constants.CallAzureKVDeleteSecret, err)
  528. if err != nil {
  529. return fmt.Errorf("error deleting secret %v: %w", secretName, err)
  530. }
  531. }
  532. return nil
  533. }
  534. func (a *Azure) deleteKeyVaultCertificateWithNewSDK(ctx context.Context, certName string) error {
  535. cert, err := a.certsClient.GetCertificate(ctx, certName, "", nil)
  536. metrics.ObserveAPICall(constants.ProviderAzureKV, constants.CallAzureKVGetCertificate, err)
  537. ok, err := canDeleteWithNewSDK(cert.Tags, err)
  538. if err != nil {
  539. return fmt.Errorf("error getting certificate %v: %w", certName, err)
  540. }
  541. if ok {
  542. _, err = a.certsClient.DeleteCertificate(ctx, certName, nil)
  543. metrics.ObserveAPICall(constants.ProviderAzureKV, constants.CallAzureKVDeleteCertificate, err)
  544. if err != nil {
  545. return fmt.Errorf("error deleting certificate %v: %w", certName, err)
  546. }
  547. }
  548. return nil
  549. }
  550. func (a *Azure) deleteKeyVaultKeyWithNewSDK(ctx context.Context, keyName string) error {
  551. key, err := a.keysClient.GetKey(ctx, keyName, "", nil)
  552. metrics.ObserveAPICall(constants.ProviderAzureKV, constants.CallAzureKVGetKey, err)
  553. ok, err := canDeleteWithNewSDK(key.Tags, err)
  554. if err != nil {
  555. return fmt.Errorf("error getting key %v: %w", keyName, err)
  556. }
  557. if ok {
  558. _, err = a.keysClient.DeleteKey(ctx, keyName, nil)
  559. metrics.ObserveAPICall(constants.ProviderAzureKV, constants.CallAzureKVDeleteKey, err)
  560. if err != nil {
  561. return fmt.Errorf("error deleting key %v: %w", keyName, err)
  562. }
  563. }
  564. return nil
  565. }
  566. // GetSecret implementation using new Azure SDK.
  567. func (a *Azure) getSecretWithNewSDK(ctx context.Context, ref esv1.ExternalSecretDataRemoteRef) ([]byte, error) {
  568. objectType, secretName := getObjType(ref)
  569. switch objectType {
  570. case defaultObjType:
  571. // Get secret using new SDK
  572. resp, err := a.secretsClient.GetSecret(ctx, secretName, ref.Version, nil)
  573. metrics.ObserveAPICall(constants.ProviderAzureKV, constants.CallAzureKVGetSecret, err)
  574. if err != nil {
  575. return nil, parseNewSDKError(err)
  576. }
  577. if ref.MetadataPolicy == esv1.ExternalSecretMetadataPolicyFetch {
  578. return getSecretTag(resp.Tags, ref.Property)
  579. }
  580. return getProperty(*resp.Value, ref.Property, ref.Key)
  581. case objectTypeCert:
  582. // Get certificate using new SDK
  583. resp, err := a.certsClient.GetCertificate(ctx, secretName, ref.Version, nil)
  584. metrics.ObserveAPICall(constants.ProviderAzureKV, constants.CallAzureKVGetCertificate, err)
  585. if err != nil {
  586. return nil, parseNewSDKError(err)
  587. }
  588. if ref.MetadataPolicy == esv1.ExternalSecretMetadataPolicyFetch {
  589. return getSecretTag(resp.Tags, ref.Property)
  590. }
  591. return resp.CER, nil
  592. case objectTypeKey:
  593. // Get key using new SDK
  594. resp, err := a.keysClient.GetKey(ctx, secretName, ref.Version, nil)
  595. metrics.ObserveAPICall(constants.ProviderAzureKV, constants.CallAzureKVGetKey, err)
  596. if err != nil {
  597. return nil, parseNewSDKError(err)
  598. }
  599. if ref.MetadataPolicy == esv1.ExternalSecretMetadataPolicyFetch {
  600. return getSecretTag(resp.Tags, ref.Property)
  601. }
  602. keyBytes, err := json.Marshal(resp.Key)
  603. if err != nil {
  604. return nil, fmt.Errorf("failed to marshal key: %w", err)
  605. }
  606. return getProperty(string(keyBytes), ref.Property, ref.Key)
  607. }
  608. return nil, fmt.Errorf(errUnknownObjectType, secretName)
  609. }
  610. // secretExistsWithNewSDK checks if a secret/certificate/key exists in Azure Key Vault using the new SDK.
  611. // Returns (true, nil) if the object exists, (false, nil) if it doesn't exist, or (false, err) on error.
  612. func (a *Azure) secretExistsWithNewSDK(ctx context.Context, remoteRef esv1.PushSecretRemoteRef) (bool, error) {
  613. objectType, secretName := getObjType(esv1.ExternalSecretDataRemoteRef{Key: remoteRef.GetRemoteKey()})
  614. var err error
  615. switch objectType {
  616. case defaultObjType:
  617. _, err = a.secretsClient.GetSecret(ctx, secretName, "", nil)
  618. metrics.ObserveAPICall(constants.ProviderAzureKV, constants.CallAzureKVGetSecret, err)
  619. case objectTypeCert:
  620. _, err = a.certsClient.GetCertificate(ctx, secretName, "", nil)
  621. metrics.ObserveAPICall(constants.ProviderAzureKV, constants.CallAzureKVGetCertificate, err)
  622. case objectTypeKey:
  623. _, err = a.keysClient.GetKey(ctx, secretName, "", nil)
  624. metrics.ObserveAPICall(constants.ProviderAzureKV, constants.CallAzureKVGetKey, err)
  625. default:
  626. errMsg := fmt.Sprintf("secret type '%v' is not supported", objectType)
  627. return false, errors.New(errMsg)
  628. }
  629. err = parseNewSDKError(err)
  630. if err != nil {
  631. var noSecretErr esv1.NoSecretError
  632. if errors.As(err, &noSecretErr) {
  633. return false, nil
  634. }
  635. return false, err
  636. }
  637. return true, nil
  638. }
  639. // parseNewSDKError converts new Azure SDK errors to the same format as legacy errors.
  640. func parseNewSDKError(err error) error {
  641. if err == nil {
  642. return nil
  643. }
  644. var respErr *azcore.ResponseError
  645. if errors.As(err, &respErr) {
  646. if respErr.StatusCode == 404 {
  647. return esv1.NoSecretError{}
  648. }
  649. // Return error in the same format as the legacy parseError function
  650. return err
  651. }
  652. return err
  653. }