Browse Source

chore: azure sdk update (#5162)

* Support new azure sdk and custom clouds via feature flag

Signed-off-by: will haus <william.o.haus.civ@army.mil>

* fix(provider/kubernetes): make auth field optional (#5064)

Signed-off-by: Martin Hrabovcin <martin.hrabovcin@nutanix.com>
Co-authored-by: Gergely Brautigam <182850+Skarlso@users.noreply.github.com>
Co-authored-by: Gergely Brautigam <skarlso777@gmail.com>
Signed-off-by: will haus <william.o.haus.civ@army.mil>

* chore: Fix Markdown spelling issues found by codespell (#5139)

See,

```
codespell  $(find . -type f -name '*.md')
```

Signed-off-by: Mario Trangoni <mjtrangoni@gmail.com>
Co-authored-by: Gergely Brautigam <skarlso777@gmail.com>
Signed-off-by: will haus <william.o.haus.civ@army.mil>

* Fix yaml codeblock for oracle-vault provider (#5146)

Signed-off-by: Anton Mu <34460584+muckelba@users.noreply.github.com>
Signed-off-by: will haus <william.o.haus.civ@army.mil>

* feat: add liveness probe to eso controller (#4930)

* feat: add liveness probe to eso controller

Signed-off-by: Gergely Brautigam <182850+Skarlso@users.noreply.github.com>

On-behalf-of: gergely.brautigam@sap.com

* fixed the linter warning

Signed-off-by: Gergely Brautigam <182850+Skarlso@users.noreply.github.com>

On-behalf-of: gergely.brautigam@sap.com

* fixed the helm schema generator as values and output was removed effectively

Signed-off-by: Gergely Brautigam <182850+Skarlso@users.noreply.github.com>

On-behalf-of: gergely.brautigam@sap.com

* add named port and add a helm test for the new value

Signed-off-by: Gergely Brautigam <182850+Skarlso@users.noreply.github.com>

On-behalf-of: gergely.brautigam@sap.com
Signed-off-by: will haus <william.o.haus.civ@army.mil>

* fix(helm): add boolean for  processClusterGenerator (#5144)

* fix(helm): add boolean to remove clusterGenerator

Signed-off-by: DrummyFloyd <jonathan.monnet28@gmail.com>

* style(helm): typo missing space

Signed-off-by: DrummyFloyd <jonathan.monnet28@gmail.com>

---------

Signed-off-by: DrummyFloyd <jonathan.monnet28@gmail.com>
Signed-off-by: will haus <william.o.haus.civ@army.mil>

* chore: add Cisco to ADOPTERS.md (#5159)

- Add Cisco.com to the ADOPTERS.md

Signed-off-by: Sri Aradhyula <sraradhy@cisco.com>
Signed-off-by: will haus <william.o.haus.civ@army.mil>

* docs: Fix provider stability and support table (#5161)

Signed-off-by: Jonathan Stacks <jonstacks@users.noreply.github.com>
Signed-off-by: will haus <william.o.haus.civ@army.mil>

* feat(helm): Add control of response to missing prometheus (#5087)

Signed-off-by: Pat Riehecky <riehecky@fnal.gov>
Co-authored-by: Gergely Brautigam <skarlso777@gmail.com>
Signed-off-by: will haus <william.o.haus.civ@army.mil>

* fix lint errors

Signed-off-by: will haus <william.o.haus.civ@army.mil>

* address code comments, move new code to new file

Signed-off-by: will haus <william.o.haus.civ@army.mil>

* test suite and make fmt-ed

Signed-off-by: will haus <william.o.haus.civ@army.mil>

* reduce complexity of getAllSecrets with new sdk

Signed-off-by: will haus <william.o.haus.civ@army.mil>

* forgot to lint

Signed-off-by: william-o-haus-civ_S2VA <william.o.haus.civ@army.mil>

* requested changes and check-diff run

Signed-off-by: hauswio <william.o.haus.civ@army.mil>

* marshall key into new type order

Signed-off-by: hauswio <william.o.haus.civ@army.mil>

* fix: run go mod tidy on e2e

Signed-off-by: Gergely Brautigam <182850+Skarlso@users.noreply.github.com>

---------

Signed-off-by: will haus <william.o.haus.civ@army.mil>
Signed-off-by: Martin Hrabovcin <martin.hrabovcin@nutanix.com>
Signed-off-by: Mario Trangoni <mjtrangoni@gmail.com>
Signed-off-by: Anton Mu <34460584+muckelba@users.noreply.github.com>
Signed-off-by: DrummyFloyd <jonathan.monnet28@gmail.com>
Signed-off-by: Sri Aradhyula <sraradhy@cisco.com>
Signed-off-by: Jonathan Stacks <jonstacks@users.noreply.github.com>
Signed-off-by: Pat Riehecky <riehecky@fnal.gov>
Signed-off-by: william-o-haus-civ_S2VA <william.o.haus.civ@army.mil>
Signed-off-by: hauswio <92599939+hauswio@users.noreply.github.com>
Signed-off-by: hauswio <william.o.haus.civ@army.mil>
Signed-off-by: Gergely Brautigam <182850+Skarlso@users.noreply.github.com>
Co-authored-by: will haus <william.o.haus.civ@army.mil>
Co-authored-by: Martin Hrabovcin <martin.hrabovcin@nutanix.com>
Co-authored-by: Gergely Brautigam <182850+Skarlso@users.noreply.github.com>
Co-authored-by: Gergely Brautigam <skarlso777@gmail.com>
Co-authored-by: Mario Trangoni <mjtrangoni@gmail.com>
Co-authored-by: Anton Mu <34460584+muckelba@users.noreply.github.com>
Co-authored-by: DrummyFloyd <jonathan.monnet28@gmail.com>
Co-authored-by: Sri Aradhyula <sraradhy@cisco.com>
Co-authored-by: Jonathan Stacks <jonstacks@users.noreply.github.com>
Co-authored-by: Pat Riehecky <3534830+jcpunk@users.noreply.github.com>
hauswio 7 months ago
parent
commit
5a70dfbc12

+ 40 - 3
apis/externalsecrets/v1/secretstore_azurekv_types.go

@@ -37,8 +37,8 @@ const (
 // AzureEnvironmentType specifies the Azure cloud environment endpoints to use for
 // connecting and authenticating with Azure. By default it points to the public cloud AAD endpoint.
 // The following endpoints are available, also see here: https://github.com/Azure/go-autorest/blob/main/autorest/azure/environments.go#L152
-// PublicCloud, USGovernmentCloud, ChinaCloud, GermanCloud
-// +kubebuilder:validation:Enum=PublicCloud;USGovernmentCloud;ChinaCloud;GermanCloud
+// PublicCloud, USGovernmentCloud, ChinaCloud, GermanCloud, AzureStackCloud
+// +kubebuilder:validation:Enum=PublicCloud;USGovernmentCloud;ChinaCloud;GermanCloud;AzureStackCloud
 type AzureEnvironmentType string
 
 const (
@@ -46,8 +46,31 @@ const (
 	AzureEnvironmentUSGovernmentCloud AzureEnvironmentType = "USGovernmentCloud"
 	AzureEnvironmentChinaCloud        AzureEnvironmentType = "ChinaCloud"
 	AzureEnvironmentGermanCloud       AzureEnvironmentType = "GermanCloud"
+	AzureEnvironmentAzureStackCloud   AzureEnvironmentType = "AzureStackCloud"
 )
 
+// AzureCustomCloudConfig specifies custom cloud configuration for private Azure environments
+// IMPORTANT: Custom cloud configuration is ONLY supported when UseAzureSDK is true.
+// The legacy go-autorest SDK does not support custom cloud endpoints.
+type AzureCustomCloudConfig struct {
+	// ActiveDirectoryEndpoint is the AAD endpoint for authentication
+	// Required when using custom cloud configuration
+	// +kubebuilder:validation:Required
+	ActiveDirectoryEndpoint string `json:"activeDirectoryEndpoint"`
+
+	// KeyVaultEndpoint is the Key Vault service endpoint
+	// +optional
+	KeyVaultEndpoint *string `json:"keyVaultEndpoint,omitempty"`
+
+	// KeyVaultDNSSuffix is the DNS suffix for Key Vault URLs
+	// +optional
+	KeyVaultDNSSuffix *string `json:"keyVaultDNSSuffix,omitempty"`
+
+	// ResourceManagerEndpoint is the Azure Resource Manager endpoint
+	// +optional
+	ResourceManagerEndpoint *string `json:"resourceManagerEndpoint,omitempty"`
+}
+
 // Configures an store to sync secrets using Azure KV.
 type AzureKVProvider struct {
 	// Auth type defines how to authenticate to the keyvault service.
@@ -68,7 +91,8 @@ type AzureKVProvider struct {
 	// EnvironmentType specifies the Azure cloud environment endpoints to use for
 	// connecting and authenticating with Azure. By default it points to the public cloud AAD endpoint.
 	// The following endpoints are available, also see here: https://github.com/Azure/go-autorest/blob/main/autorest/azure/environments.go#L152
-	// PublicCloud, USGovernmentCloud, ChinaCloud, GermanCloud
+	// PublicCloud, USGovernmentCloud, ChinaCloud, GermanCloud, AzureStackCloud
+	// Use AzureStackCloud when you need to configure custom Azure Stack Hub or Azure Stack Edge endpoints.
 	// +kubebuilder:default=PublicCloud
 	EnvironmentType AzureEnvironmentType `json:"environmentType,omitempty"`
 
@@ -84,6 +108,19 @@ type AzureKVProvider struct {
 	// If multiple Managed Identity is assigned to the pod, you can select the one to be used
 	// +optional
 	IdentityID *string `json:"identityId,omitempty"`
+
+	// UseAzureSDK enables the use of the new Azure SDK for Go (azcore-based) instead of the legacy go-autorest SDK.
+	// This is experimental and may have behavioral differences. Defaults to false (legacy SDK).
+	// +optional
+	// +kubebuilder:default=false
+	UseAzureSDK *bool `json:"useAzureSDK,omitempty"`
+
+	// CustomCloudConfig defines custom Azure Stack Hub or Azure Stack Edge endpoints.
+	// Required when EnvironmentType is AzureStackCloud.
+	// IMPORTANT: This feature REQUIRES UseAzureSDK to be set to true. Custom cloud
+	// configuration is not supported with the legacy go-autorest SDK.
+	// +optional
+	CustomCloudConfig *AzureCustomCloudConfig `json:"customCloudConfig,omitempty"`
 }
 
 // Configuration used to authenticate with Azure.

+ 40 - 0
apis/externalsecrets/v1/zz_generated.deepcopy.go

@@ -379,6 +379,36 @@ func (in *AzureAuthCredentials) DeepCopy() *AzureAuthCredentials {
 }
 
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *AzureCustomCloudConfig) DeepCopyInto(out *AzureCustomCloudConfig) {
+	*out = *in
+	if in.KeyVaultEndpoint != nil {
+		in, out := &in.KeyVaultEndpoint, &out.KeyVaultEndpoint
+		*out = new(string)
+		**out = **in
+	}
+	if in.KeyVaultDNSSuffix != nil {
+		in, out := &in.KeyVaultDNSSuffix, &out.KeyVaultDNSSuffix
+		*out = new(string)
+		**out = **in
+	}
+	if in.ResourceManagerEndpoint != nil {
+		in, out := &in.ResourceManagerEndpoint, &out.ResourceManagerEndpoint
+		*out = new(string)
+		**out = **in
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AzureCustomCloudConfig.
+func (in *AzureCustomCloudConfig) DeepCopy() *AzureCustomCloudConfig {
+	if in == nil {
+		return nil
+	}
+	out := new(AzureCustomCloudConfig)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *AzureKVAuth) DeepCopyInto(out *AzureKVAuth) {
 	*out = *in
 	if in.ClientID != nil {
@@ -446,6 +476,16 @@ func (in *AzureKVProvider) DeepCopyInto(out *AzureKVProvider) {
 		*out = new(string)
 		**out = **in
 	}
+	if in.UseAzureSDK != nil {
+		in, out := &in.UseAzureSDK, &out.UseAzureSDK
+		*out = new(bool)
+		**out = **in
+	}
+	if in.CustomCloudConfig != nil {
+		in, out := &in.CustomCloudConfig, &out.CustomCloudConfig
+		*out = new(AzureCustomCloudConfig)
+		(*in).DeepCopyInto(*out)
+	}
 }
 
 // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AzureKVProvider.

+ 36 - 1
config/crds/bases/external-secrets.io_clustersecretstores.yaml

@@ -804,18 +804,47 @@ spec:
                         - ManagedIdentity
                         - WorkloadIdentity
                         type: string
+                      customCloudConfig:
+                        description: |-
+                          CustomCloudConfig defines custom Azure Stack Hub or Azure Stack Edge endpoints.
+                          Required when EnvironmentType is AzureStackCloud.
+                          IMPORTANT: This feature REQUIRES UseAzureSDK to be set to true. Custom cloud
+                          configuration is not supported with the legacy go-autorest SDK.
+                        properties:
+                          activeDirectoryEndpoint:
+                            description: |-
+                              ActiveDirectoryEndpoint is the AAD endpoint for authentication
+                              Required when using custom cloud configuration
+                            type: string
+                          keyVaultDNSSuffix:
+                            description: KeyVaultDNSSuffix is the DNS suffix for Key
+                              Vault URLs
+                            type: string
+                          keyVaultEndpoint:
+                            description: KeyVaultEndpoint is the Key Vault service
+                              endpoint
+                            type: string
+                          resourceManagerEndpoint:
+                            description: ResourceManagerEndpoint is the Azure Resource
+                              Manager endpoint
+                            type: string
+                        required:
+                        - activeDirectoryEndpoint
+                        type: object
                       environmentType:
                         default: PublicCloud
                         description: |-
                           EnvironmentType specifies the Azure cloud environment endpoints to use for
                           connecting and authenticating with Azure. By default it points to the public cloud AAD endpoint.
                           The following endpoints are available, also see here: https://github.com/Azure/go-autorest/blob/main/autorest/azure/environments.go#L152
-                          PublicCloud, USGovernmentCloud, ChinaCloud, GermanCloud
+                          PublicCloud, USGovernmentCloud, ChinaCloud, GermanCloud, AzureStackCloud
+                          Use AzureStackCloud when you need to configure custom Azure Stack Hub or Azure Stack Edge endpoints.
                         enum:
                         - PublicCloud
                         - USGovernmentCloud
                         - ChinaCloud
                         - GermanCloud
+                        - AzureStackCloud
                         type: string
                       identityId:
                         description: If multiple Managed Identity is assigned to the
@@ -857,6 +886,12 @@ spec:
                           requests to. Required for ServicePrincipal auth type. Optional
                           for WorkloadIdentity.
                         type: string
+                      useAzureSDK:
+                        default: false
+                        description: |-
+                          UseAzureSDK enables the use of the new Azure SDK for Go (azcore-based) instead of the legacy go-autorest SDK.
+                          This is experimental and may have behavioral differences. Defaults to false (legacy SDK).
+                        type: boolean
                       vaultUrl:
                         description: Vault Url from which the secrets to be fetched
                           from.

+ 36 - 1
config/crds/bases/external-secrets.io_secretstores.yaml

@@ -804,18 +804,47 @@ spec:
                         - ManagedIdentity
                         - WorkloadIdentity
                         type: string
+                      customCloudConfig:
+                        description: |-
+                          CustomCloudConfig defines custom Azure Stack Hub or Azure Stack Edge endpoints.
+                          Required when EnvironmentType is AzureStackCloud.
+                          IMPORTANT: This feature REQUIRES UseAzureSDK to be set to true. Custom cloud
+                          configuration is not supported with the legacy go-autorest SDK.
+                        properties:
+                          activeDirectoryEndpoint:
+                            description: |-
+                              ActiveDirectoryEndpoint is the AAD endpoint for authentication
+                              Required when using custom cloud configuration
+                            type: string
+                          keyVaultDNSSuffix:
+                            description: KeyVaultDNSSuffix is the DNS suffix for Key
+                              Vault URLs
+                            type: string
+                          keyVaultEndpoint:
+                            description: KeyVaultEndpoint is the Key Vault service
+                              endpoint
+                            type: string
+                          resourceManagerEndpoint:
+                            description: ResourceManagerEndpoint is the Azure Resource
+                              Manager endpoint
+                            type: string
+                        required:
+                        - activeDirectoryEndpoint
+                        type: object
                       environmentType:
                         default: PublicCloud
                         description: |-
                           EnvironmentType specifies the Azure cloud environment endpoints to use for
                           connecting and authenticating with Azure. By default it points to the public cloud AAD endpoint.
                           The following endpoints are available, also see here: https://github.com/Azure/go-autorest/blob/main/autorest/azure/environments.go#L152
-                          PublicCloud, USGovernmentCloud, ChinaCloud, GermanCloud
+                          PublicCloud, USGovernmentCloud, ChinaCloud, GermanCloud, AzureStackCloud
+                          Use AzureStackCloud when you need to configure custom Azure Stack Hub or Azure Stack Edge endpoints.
                         enum:
                         - PublicCloud
                         - USGovernmentCloud
                         - ChinaCloud
                         - GermanCloud
+                        - AzureStackCloud
                         type: string
                       identityId:
                         description: If multiple Managed Identity is assigned to the
@@ -857,6 +886,12 @@ spec:
                           requests to. Required for ServicePrincipal auth type. Optional
                           for WorkloadIdentity.
                         type: string
+                      useAzureSDK:
+                        default: false
+                        description: |-
+                          UseAzureSDK enables the use of the new Azure SDK for Go (azcore-based) instead of the legacy go-autorest SDK.
+                          This is experimental and may have behavioral differences. Defaults to false (legacy SDK).
+                        type: boolean
                       vaultUrl:
                         description: Vault Url from which the secrets to be fetched
                           from.

+ 1 - 0
config/crds/bases/generators.external-secrets.io_acraccesstokens.yaml

@@ -183,6 +183,7 @@ spec:
                 - USGovernmentCloud
                 - ChinaCloud
                 - GermanCloud
+                - AzureStackCloud
                 type: string
               registry:
                 description: |-

+ 1 - 0
config/crds/bases/generators.external-secrets.io_clustergenerators.yaml

@@ -184,6 +184,7 @@ spec:
                         - USGovernmentCloud
                         - ChinaCloud
                         - GermanCloud
+                        - AzureStackCloud
                         type: string
                       registry:
                         description: |-

+ 68 - 2
deploy/crds/bundle.yaml

@@ -2776,18 +2776,44 @@ spec:
                             - ManagedIdentity
                             - WorkloadIdentity
                           type: string
+                        customCloudConfig:
+                          description: |-
+                            CustomCloudConfig defines custom Azure Stack Hub or Azure Stack Edge endpoints.
+                            Required when EnvironmentType is AzureStackCloud.
+                            IMPORTANT: This feature REQUIRES UseAzureSDK to be set to true. Custom cloud
+                            configuration is not supported with the legacy go-autorest SDK.
+                          properties:
+                            activeDirectoryEndpoint:
+                              description: |-
+                                ActiveDirectoryEndpoint is the AAD endpoint for authentication
+                                Required when using custom cloud configuration
+                              type: string
+                            keyVaultDNSSuffix:
+                              description: KeyVaultDNSSuffix is the DNS suffix for Key Vault URLs
+                              type: string
+                            keyVaultEndpoint:
+                              description: KeyVaultEndpoint is the Key Vault service endpoint
+                              type: string
+                            resourceManagerEndpoint:
+                              description: ResourceManagerEndpoint is the Azure Resource Manager endpoint
+                              type: string
+                          required:
+                            - activeDirectoryEndpoint
+                          type: object
                         environmentType:
                           default: PublicCloud
                           description: |-
                             EnvironmentType specifies the Azure cloud environment endpoints to use for
                             connecting and authenticating with Azure. By default it points to the public cloud AAD endpoint.
                             The following endpoints are available, also see here: https://github.com/Azure/go-autorest/blob/main/autorest/azure/environments.go#L152
-                            PublicCloud, USGovernmentCloud, ChinaCloud, GermanCloud
+                            PublicCloud, USGovernmentCloud, ChinaCloud, GermanCloud, AzureStackCloud
+                            Use AzureStackCloud when you need to configure custom Azure Stack Hub or Azure Stack Edge endpoints.
                           enum:
                             - PublicCloud
                             - USGovernmentCloud
                             - ChinaCloud
                             - GermanCloud
+                            - AzureStackCloud
                           type: string
                         identityId:
                           description: If multiple Managed Identity is assigned to the pod, you can select the one to be used
@@ -2825,6 +2851,12 @@ spec:
                         tenantId:
                           description: TenantID configures the Azure Tenant to send requests to. Required for ServicePrincipal auth type. Optional for WorkloadIdentity.
                           type: string
+                        useAzureSDK:
+                          default: false
+                          description: |-
+                            UseAzureSDK enables the use of the new Azure SDK for Go (azcore-based) instead of the legacy go-autorest SDK.
+                            This is experimental and may have behavioral differences. Defaults to false (legacy SDK).
+                          type: boolean
                         vaultUrl:
                           description: Vault Url from which the secrets to be fetched from.
                           type: string
@@ -13756,18 +13788,44 @@ spec:
                             - ManagedIdentity
                             - WorkloadIdentity
                           type: string
+                        customCloudConfig:
+                          description: |-
+                            CustomCloudConfig defines custom Azure Stack Hub or Azure Stack Edge endpoints.
+                            Required when EnvironmentType is AzureStackCloud.
+                            IMPORTANT: This feature REQUIRES UseAzureSDK to be set to true. Custom cloud
+                            configuration is not supported with the legacy go-autorest SDK.
+                          properties:
+                            activeDirectoryEndpoint:
+                              description: |-
+                                ActiveDirectoryEndpoint is the AAD endpoint for authentication
+                                Required when using custom cloud configuration
+                              type: string
+                            keyVaultDNSSuffix:
+                              description: KeyVaultDNSSuffix is the DNS suffix for Key Vault URLs
+                              type: string
+                            keyVaultEndpoint:
+                              description: KeyVaultEndpoint is the Key Vault service endpoint
+                              type: string
+                            resourceManagerEndpoint:
+                              description: ResourceManagerEndpoint is the Azure Resource Manager endpoint
+                              type: string
+                          required:
+                            - activeDirectoryEndpoint
+                          type: object
                         environmentType:
                           default: PublicCloud
                           description: |-
                             EnvironmentType specifies the Azure cloud environment endpoints to use for
                             connecting and authenticating with Azure. By default it points to the public cloud AAD endpoint.
                             The following endpoints are available, also see here: https://github.com/Azure/go-autorest/blob/main/autorest/azure/environments.go#L152
-                            PublicCloud, USGovernmentCloud, ChinaCloud, GermanCloud
+                            PublicCloud, USGovernmentCloud, ChinaCloud, GermanCloud, AzureStackCloud
+                            Use AzureStackCloud when you need to configure custom Azure Stack Hub or Azure Stack Edge endpoints.
                           enum:
                             - PublicCloud
                             - USGovernmentCloud
                             - ChinaCloud
                             - GermanCloud
+                            - AzureStackCloud
                           type: string
                         identityId:
                           description: If multiple Managed Identity is assigned to the pod, you can select the one to be used
@@ -13805,6 +13863,12 @@ spec:
                         tenantId:
                           description: TenantID configures the Azure Tenant to send requests to. Required for ServicePrincipal auth type. Optional for WorkloadIdentity.
                           type: string
+                        useAzureSDK:
+                          default: false
+                          description: |-
+                            UseAzureSDK enables the use of the new Azure SDK for Go (azcore-based) instead of the legacy go-autorest SDK.
+                            This is experimental and may have behavioral differences. Defaults to false (legacy SDK).
+                          type: boolean
                         vaultUrl:
                           description: Vault Url from which the secrets to be fetched from.
                           type: string
@@ -22417,6 +22481,7 @@ spec:
                     - USGovernmentCloud
                     - ChinaCloud
                     - GermanCloud
+                    - AzureStackCloud
                   type: string
                 registry:
                   description: |-
@@ -22623,6 +22688,7 @@ spec:
                             - USGovernmentCloud
                             - ChinaCloud
                             - GermanCloud
+                            - AzureStackCloud
                           type: string
                         registry:
                           description: |-

+ 105 - 3
docs/api/spec.md

@@ -946,6 +946,75 @@ is ServicePrincipal.</p>
 </td>
 </tr></tbody>
 </table>
+<h3 id="external-secrets.io/v1.AzureCustomCloudConfig">AzureCustomCloudConfig
+</h3>
+<p>
+(<em>Appears on:</em>
+<a href="#external-secrets.io/v1.AzureKVProvider">AzureKVProvider</a>)
+</p>
+<p>
+<p>AzureCustomCloudConfig specifies custom cloud configuration for private Azure environments
+IMPORTANT: Custom cloud configuration is ONLY supported when UseAzureSDK is true.
+The legacy go-autorest SDK does not support custom cloud endpoints.</p>
+</p>
+<table>
+<thead>
+<tr>
+<th>Field</th>
+<th>Description</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td>
+<code>activeDirectoryEndpoint</code></br>
+<em>
+string
+</em>
+</td>
+<td>
+<p>ActiveDirectoryEndpoint is the AAD endpoint for authentication
+Required when using custom cloud configuration</p>
+</td>
+</tr>
+<tr>
+<td>
+<code>keyVaultEndpoint</code></br>
+<em>
+string
+</em>
+</td>
+<td>
+<em>(Optional)</em>
+<p>KeyVaultEndpoint is the Key Vault service endpoint</p>
+</td>
+</tr>
+<tr>
+<td>
+<code>keyVaultDNSSuffix</code></br>
+<em>
+string
+</em>
+</td>
+<td>
+<em>(Optional)</em>
+<p>KeyVaultDNSSuffix is the DNS suffix for Key Vault URLs</p>
+</td>
+</tr>
+<tr>
+<td>
+<code>resourceManagerEndpoint</code></br>
+<em>
+string
+</em>
+</td>
+<td>
+<em>(Optional)</em>
+<p>ResourceManagerEndpoint is the Azure Resource Manager endpoint</p>
+</td>
+</tr>
+</tbody>
+</table>
 <h3 id="external-secrets.io/v1.AzureEnvironmentType">AzureEnvironmentType
 (<code>string</code> alias)</p></h3>
 <p>
@@ -956,7 +1025,7 @@ is ServicePrincipal.</p>
 <p>AzureEnvironmentType specifies the Azure cloud environment endpoints to use for
 connecting and authenticating with Azure. By default it points to the public cloud AAD endpoint.
 The following endpoints are available, also see here: <a href="https://github.com/Azure/go-autorest/blob/main/autorest/azure/environments.go#L152">https://github.com/Azure/go-autorest/blob/main/autorest/azure/environments.go#L152</a>
-PublicCloud, USGovernmentCloud, ChinaCloud, GermanCloud</p>
+PublicCloud, USGovernmentCloud, ChinaCloud, GermanCloud, AzureStackCloud</p>
 </p>
 <table>
 <thead>
@@ -965,7 +1034,9 @@ PublicCloud, USGovernmentCloud, ChinaCloud, GermanCloud</p>
 <th>Description</th>
 </tr>
 </thead>
-<tbody><tr><td><p>&#34;ChinaCloud&#34;</p></td>
+<tbody><tr><td><p>&#34;AzureStackCloud&#34;</p></td>
+<td></td>
+</tr><tr><td><p>&#34;ChinaCloud&#34;</p></td>
 <td></td>
 </tr><tr><td><p>&#34;GermanCloud&#34;</p></td>
 <td></td>
@@ -1120,7 +1191,8 @@ AzureEnvironmentType
 <p>EnvironmentType specifies the Azure cloud environment endpoints to use for
 connecting and authenticating with Azure. By default it points to the public cloud AAD endpoint.
 The following endpoints are available, also see here: <a href="https://github.com/Azure/go-autorest/blob/main/autorest/azure/environments.go#L152">https://github.com/Azure/go-autorest/blob/main/autorest/azure/environments.go#L152</a>
-PublicCloud, USGovernmentCloud, ChinaCloud, GermanCloud</p>
+PublicCloud, USGovernmentCloud, ChinaCloud, GermanCloud, AzureStackCloud
+Use AzureStackCloud when you need to configure custom Azure Stack Hub or Azure Stack Edge endpoints.</p>
 </td>
 </tr>
 <tr>
@@ -1164,6 +1236,36 @@ string
 <p>If multiple Managed Identity is assigned to the pod, you can select the one to be used</p>
 </td>
 </tr>
+<tr>
+<td>
+<code>useAzureSDK</code></br>
+<em>
+bool
+</em>
+</td>
+<td>
+<em>(Optional)</em>
+<p>UseAzureSDK enables the use of the new Azure SDK for Go (azcore-based) instead of the legacy go-autorest SDK.
+This is experimental and may have behavioral differences. Defaults to false (legacy SDK).</p>
+</td>
+</tr>
+<tr>
+<td>
+<code>customCloudConfig</code></br>
+<em>
+<a href="#external-secrets.io/v1.AzureCustomCloudConfig">
+AzureCustomCloudConfig
+</a>
+</em>
+</td>
+<td>
+<em>(Optional)</em>
+<p>CustomCloudConfig defines custom Azure Stack Hub or Azure Stack Edge endpoints.
+Required when EnvironmentType is AzureStackCloud.
+IMPORTANT: This feature REQUIRES UseAzureSDK to be set to true. Custom cloud
+configuration is not supported with the legacy go-autorest SDK.</p>
+</td>
+</tr>
 </tbody>
 </table>
 <h3 id="external-secrets.io/v1.BeyondTrustProviderSecretRef">BeyondTrustProviderSecretRef

+ 45 - 1
docs/provider/azure-key-vault.md

@@ -11,7 +11,9 @@ We support authentication with Microsoft Entra identities that can be used as Wo
 
 Since the [AAD Pod Identity](https://azure.github.io/aad-pod-identity/docs/) is deprecated, it is recommended to use the [Workload Identity](https://azure.github.io/azure-workload-identity) authentication.
 
-We support connecting to different cloud flavours azure supports: `PublicCloud`, `USGovernmentCloud`, `ChinaCloud` and `GermanCloud`. You have to specify the `environmentType` and point to the correct cloud flavour. This defaults to `PublicCloud`.
+We support connecting to different cloud flavours azure supports: `PublicCloud`, `USGovernmentCloud`, `ChinaCloud`, `GermanCloud` and `AzureStackCloud` (for Azure Stack Hub/Edge). You have to specify the `environmentType` and point to the correct cloud flavour. This defaults to `PublicCloud`.
+
+For Azure Stack Hub or Azure Stack Edge environments, you must also provide custom cloud configuration. See the [Azure Stack Configuration](#azure-stack-configuration) section below.
 
 ```yaml
 apiVersion: external-secrets.io/v1
@@ -98,6 +100,48 @@ The following example demonstrates using the secretRef field to directly deliver
 {% include 'azkv-workload-identity-secretref.yaml' %}
 ```
 
+### Azure Stack Configuration
+
+External Secrets Operator supports Azure Stack Hub and Azure Stack Edge through custom cloud configuration. This feature requires using the new Azure SDK.
+
+```yaml
+apiVersion: external-secrets.io/v1beta1
+kind: SecretStore
+metadata:
+  name: azure-stack-backend
+spec:
+  provider:
+    azurekv:
+      vaultUrl: "https://my-vault.vault.local.azurestack.external/"
+      # REQUIRED: Must be set to AzureStackCloud for custom environments
+      environmentType: AzureStackCloud
+      # REQUIRED: Must be true for Azure Stack (legacy SDK doesn't support custom clouds)
+      useAzureSDK: true
+      # REQUIRED: Custom cloud endpoints for your Azure Stack deployment
+      customCloudConfig:
+        # Azure Active Directory endpoint for authentication
+        activeDirectoryEndpoint: "https://login.microsoftonline.com/"
+        # Optional: Key Vault endpoint if different from vaultUrl domain
+        keyVaultEndpoint: "https://vault.local.azurestack.external/"
+        # Optional: Resource Manager endpoint for resource operations
+        resourceManagerEndpoint: "https://management.local.azurestack.external/"
+      # ... rest of authentication configuration (Service Principal example)
+      authType: ServicePrincipal
+      tenantId: "your-tenant-id"
+      authSecretRef:
+        clientId:
+          name: azure-secret
+          key: client-id
+        clientSecret:
+          name: azure-secret
+          key: client-secret
+```
+
+**Important Notes:**
+- `useAzureSDK: true` is mandatory for Azure Stack environments
+- The `customCloudConfig` is only valid when `environmentType: AzureStackCloud`
+- Contact your Azure Stack administrator for the correct endpoint URLs
+
 ### Update secret store
 Be sure the `azurekv` provider is listed in the `Kind=SecretStore`
 

+ 4 - 0
e2e/go.mod

@@ -41,6 +41,7 @@ replace (
 require (
 	cloud.google.com/go/secretmanager v1.15.0
 	github.com/Azure/azure-sdk-for-go v68.0.0+incompatible
+	github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.4.0
 	github.com/Azure/go-autorest/autorest v0.11.30
 	github.com/Azure/go-autorest/autorest/azure/auth v0.5.13
 	github.com/DelineaXPM/dsv-sdk-go/v2 v2.2.0
@@ -88,6 +89,9 @@ require (
 	github.com/Azure/azure-sdk-for-go/sdk/azcore v1.19.0 // indirect
 	github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.11.0 // indirect
 	github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 // indirect
+	github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azcertificates v1.4.0 // indirect
+	github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets v1.4.0 // indirect
+	github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.2.0 // indirect
 	github.com/Azure/go-autorest v14.2.0+incompatible // indirect
 	github.com/Azure/go-autorest/autorest/adal v0.9.24 // indirect
 	github.com/Azure/go-autorest/autorest/azure/cli v0.4.7 // indirect

+ 8 - 0
e2e/go.sum

@@ -69,6 +69,14 @@ github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0/go.mod h1:okt5dMMTOFjX/aov
 github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.1/go.mod h1:s4kgfzA0covAXNicZHDMN58jExvcng2mC/DepXiF1EI=
 github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 h1:9iefClla7iYpfYWdzPCRDozdmndjTm8DXdpCzPajMgA=
 github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2/go.mod h1:XtLgD3ZD34DAaVIIAyG3objl5DynM3CQ/vMcbBNJZGI=
+github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azcertificates v1.4.0 h1:mtvR5ZXH5Ew6PSONd5lO5OXovWP1E3oAlgC8fpxor2Q=
+github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azcertificates v1.4.0/go.mod h1:u560+RFVfG0CBPzkXlDW43slESbBAQjgDGi3r6z+wk8=
+github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.4.0 h1:E4MgwLBGeVB5f2MdcIVD3ELVAWpr+WD6MUe1i+tM/PA=
+github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.4.0/go.mod h1:Y2b/1clN4zsAoUd/pgNAQHjLDnTis/6ROkUfyob6psM=
+github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets v1.4.0 h1:/g8S6wk65vfC6m3FIxJ+i5QDyN9JWwXI8Hb0Img10hU=
+github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets v1.4.0/go.mod h1:gpl+q95AzZlKVI3xSoseF9QPrypk0hQqBiJYeB/cR/I=
+github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.2.0 h1:nCYfgcSyHZXJI8J0IWE5MsCGlb2xp9fJiXyxWgmOFg4=
+github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.2.0/go.mod h1:ucUjca2JtSZboY8IoUqyQyuuXvwbMBVwFOm0vdQPNhA=
 github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs=
 github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
 github.com/Azure/go-autorest/autorest v0.11.28/go.mod h1:MrkzG3Y3AH668QyF9KRk5neJnGgmhQ6krbhR8Q5eMvA=

+ 22 - 0
e2e/suites/provider/cases/azure/azure_cert.go

@@ -63,4 +63,26 @@ var _ = Describe("[azure]", Label("azure", "keyvault", "cert"), func() {
 		})
 	})
 
+	It("should sync keyvault objects with type=cert using new SDK", func() {
+		ff(func(tc *framework.TestCase) {
+			secretKey := "azkv-cert-new-sdk"
+
+			tc.ExpectedSecret = &v1.Secret{
+				Type: v1.SecretTypeOpaque,
+				Data: map[string][]byte{
+					secretKey: certBytes,
+				},
+			}
+			tc.ExternalSecret.Spec.SecretStoreRef.Name = tc.Framework.Namespace.Name + "-new-sdk"
+			tc.ExternalSecret.Spec.Data = []esv1.ExternalSecretData{
+				{
+					SecretKey: secretKey,
+					RemoteRef: esv1.ExternalSecretDataRemoteRef{
+						Key: "cert/" + certName,
+					},
+				},
+			}
+		})
+	})
+
 })

+ 34 - 0
e2e/suites/provider/cases/azure/azure_key.go

@@ -17,6 +17,7 @@ import (
 	"fmt"
 
 	"github.com/Azure/azure-sdk-for-go/profiles/latest/keyvault/keyvault"
+	"github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys"
 
 	// nolint
 	. "github.com/onsi/ginkgo/v2"
@@ -66,4 +67,37 @@ var _ = Describe("[azure]", Label("azure", "keyvault", "key"), func() {
 		})
 	})
 
+	It("should sync keyvault objects with type=key using new SDK", func() {
+		ff(func(tc *framework.TestCase) {
+			secretKey := "azkv-key-new-sdk"
+			
+			// Convert old SDK key to new SDK key format
+			// First marshal the old SDK key
+			oldKeyBytes, _ := json.Marshal(jwk)
+			
+			// Unmarshal into the new SDK type
+			var newSDKKey azkeys.JSONWebKey
+			json.Unmarshal(oldKeyBytes, &newSDKKey)
+			
+			// Marshal the new SDK key - this will have the new SDK's field ordering
+			keyBytes, _ := json.Marshal(newSDKKey)
+
+			tc.ExpectedSecret = &v1.Secret{
+				Type: v1.SecretTypeOpaque,
+				Data: map[string][]byte{
+					secretKey: keyBytes,
+				},
+			}
+			tc.ExternalSecret.Spec.SecretStoreRef.Name = tc.Framework.Namespace.Name + "-new-sdk"
+			tc.ExternalSecret.Spec.Data = []esv1.ExternalSecretData{
+				{
+					SecretKey: secretKey,
+					RemoteRef: esv1.ExternalSecretDataRemoteRef{
+						Key: "key/" + keyName,
+					},
+				},
+			}
+		})
+	})
+
 })

+ 26 - 0
e2e/suites/provider/cases/azure/azure_secret.go

@@ -25,6 +25,7 @@ import (
 const (
 	withStaticCredentials = "with static credentials"
 	withReferentAuth      = "with referent auth"
+	withNewSDK            = "with new SDK"
 )
 
 // keyvault type=secret should behave just like any other secret store.
@@ -47,6 +48,22 @@ var _ = Describe("[azure]", Label("azure", "keyvault", "secret"), func() {
 		framework.Compose(withStaticCredentials, f, common.JSONDataWithoutTargetName, useStaticCredentials),
 
 		framework.Compose(withStaticCredentials, f, common.SimpleDataSync, useReferentAuth),
+
+		// New SDK tests
+		framework.Compose(withNewSDK, f, common.SimpleDataSync, useNewSDK),
+		framework.Compose(withNewSDK, f, common.NestedJSONWithGJSON, useNewSDK),
+		framework.Compose(withNewSDK, f, common.JSONDataFromSync, useNewSDK),
+		framework.Compose(withNewSDK, f, common.JSONDataFromRewrite, useNewSDK),
+		framework.Compose(withNewSDK, f, common.JSONDataWithProperty, useNewSDK),
+		framework.Compose(withNewSDK, f, common.JSONDataWithTemplate, useNewSDK),
+		framework.Compose(withNewSDK, f, common.DockerJSONConfig, useNewSDK),
+		framework.Compose(withNewSDK, f, common.DataPropertyDockerconfigJSON, useNewSDK),
+		framework.Compose(withNewSDK, f, common.SSHKeySync, useNewSDK),
+		framework.Compose(withNewSDK, f, common.SSHKeySyncDataProperty, useNewSDK),
+		framework.Compose(withNewSDK, f, common.SyncWithoutTargetName, useNewSDK),
+		framework.Compose(withNewSDK, f, common.JSONDataWithoutTargetName, useNewSDK),
+		
+		framework.Compose(withNewSDK, f, common.SimpleDataSync, useReferentAuthNewSDK),
 	)
 })
 
@@ -58,3 +75,12 @@ func useReferentAuth(tc *framework.TestCase) {
 	tc.ExternalSecret.Spec.SecretStoreRef.Name = referentAuthName(tc.Framework)
 	tc.ExternalSecret.Spec.SecretStoreRef.Kind = esapi.ClusterSecretStoreKind
 }
+
+func useNewSDK(tc *framework.TestCase) {
+	tc.ExternalSecret.Spec.SecretStoreRef.Name = tc.Framework.Namespace.Name + "-new-sdk"
+}
+
+func useReferentAuthNewSDK(tc *framework.TestCase) {
+	tc.ExternalSecret.Spec.SecretStoreRef.Name = referentAuthName(tc.Framework) + "-new-sdk"
+	tc.ExternalSecret.Spec.SecretStoreRef.Kind = esapi.ClusterSecretStoreKind
+}

+ 81 - 0
e2e/suites/provider/cases/azure/provider.go

@@ -29,6 +29,7 @@ import (
 	// nolint
 	. "github.com/onsi/gomega"
 	v1 "k8s.io/api/core/v1"
+	apierrors "k8s.io/apimachinery/pkg/api/errors"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	utilpointer "k8s.io/utils/pointer"
 
@@ -81,6 +82,8 @@ func newFromEnv(f *framework.Framework) *azureProvider {
 		})
 		prov.CreateSecretStore()
 		prov.CreateReferentSecretStore()
+		prov.CreateSecretStoreNewSDK()
+		prov.CreateReferentSecretStoreNewSDK()
 	})
 
 	return prov
@@ -258,6 +261,25 @@ func newProviderWithStaticCredentials(tenantID, vaultURL, secretName string) *es
 	}
 }
 
+func newProviderWithStaticCredentialsNewSDK(tenantID, vaultURL, secretName string) *esv1.AzureKVProvider {
+	useNewSDK := true
+	return &esv1.AzureKVProvider{
+		TenantID:     &tenantID,
+		VaultURL:     &vaultURL,
+		UseAzureSDK:  &useNewSDK,
+		AuthSecretRef: &esv1.AzureKVAuth{
+			ClientID: &esmeta.SecretKeySelector{
+				Name: staticSecretName,
+				Key:  credentialKeyClientID,
+			},
+			ClientSecret: &esmeta.SecretKeySelector{
+				Name: staticSecretName,
+				Key:  credentialKeyClientSecret,
+			},
+		},
+	}
+}
+
 func newProviderWithServiceAccount(tenantID, vaultURL string, authType esv1.AzureAuthType, serviceAccountName string, serviceAccountNamespace *string) *esv1.AzureKVProvider {
 	return &esv1.AzureKVProvider{
 		TenantID: &tenantID,
@@ -298,6 +320,37 @@ func (s *azureProvider) CreateSecretStore() {
 	Expect(err).ToNot(HaveOccurred())
 }
 
+func (s *azureProvider) CreateSecretStoreNewSDK() {
+	azureCreds := &v1.Secret{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      staticSecretName,
+			Namespace: s.framework.Namespace.Name,
+		},
+		StringData: map[string]string{
+			credentialKeyClientID:     s.clientID,
+			credentialKeyClientSecret: s.clientSecret,
+		},
+	}
+	err := s.framework.CRClient.Create(context.Background(), azureCreds)
+	// Ignore AlreadyExists error since CreateSecretStore() might have already created this secret
+	if err != nil && !apierrors.IsAlreadyExists(err) {
+		Expect(err).ToNot(HaveOccurred())
+	}
+	secretStore := &esv1.SecretStore{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      s.framework.Namespace.Name + "-new-sdk",
+			Namespace: s.framework.Namespace.Name,
+		},
+		Spec: esv1.SecretStoreSpec{
+			Provider: &esv1.SecretStoreProvider{
+				AzureKV: newProviderWithStaticCredentialsNewSDK(s.tenantID, s.vaultURL, staticSecretName),
+			},
+		},
+	}
+	err = s.framework.CRClient.Create(context.Background(), secretStore)
+	Expect(err).ToNot(HaveOccurred())
+}
+
 func (s *azureProvider) CreateReferentSecretStore() {
 	azureCreds := &v1.Secret{
 		ObjectMeta: metav1.ObjectMeta{
@@ -326,6 +379,34 @@ func (s *azureProvider) CreateReferentSecretStore() {
 	Expect(err).ToNot(HaveOccurred())
 }
 
+func (s *azureProvider) CreateReferentSecretStoreNewSDK() {
+	azureCreds := &v1.Secret{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      referentSecretName + "-new-sdk",
+			Namespace: s.framework.Namespace.Name,
+		},
+		StringData: map[string]string{
+			credentialKeyClientID:     s.clientID,
+			credentialKeyClientSecret: s.clientSecret,
+		},
+	}
+	err := s.framework.CRClient.Create(context.Background(), azureCreds)
+	Expect(err).ToNot(HaveOccurred())
+	secretStore := &esv1.ClusterSecretStore{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      referentAuthName(s.framework) + "-new-sdk",
+			Namespace: s.framework.Namespace.Name,
+		},
+		Spec: esv1.SecretStoreSpec{
+			Provider: &esv1.SecretStoreProvider{
+				AzureKV: newProviderWithStaticCredentialsNewSDK(s.tenantID, s.vaultURL, referentSecretName + "-new-sdk"),
+			},
+		},
+	}
+	err = s.framework.CRClient.Create(context.Background(), secretStore)
+	Expect(err).ToNot(HaveOccurred())
+}
+
 func referentAuthName(f *framework.Framework) string {
 	return "referent-auth-" + f.Namespace.Name
 }

+ 4 - 0
go.mod

@@ -66,6 +66,9 @@ require (
 	github.com/1password/onepassword-sdk-go v0.3.1
 	github.com/Azure/azure-sdk-for-go/sdk/azcore v1.19.0
 	github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.11.0
+	github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azcertificates v1.4.0
+	github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.4.0
+	github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets v1.4.0
 	github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358
 	github.com/BeyondTrust/go-client-library-passwordsafe v0.22.1
 	github.com/DelineaXPM/dsv-sdk-go/v2 v2.2.0
@@ -122,6 +125,7 @@ require (
 	al.essio.dev/pkg/shellescape v1.6.0 // indirect
 	cloud.google.com/go/auth v0.16.5 // indirect
 	cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
+	github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.2.0 // indirect
 	github.com/BurntSushi/toml v1.5.0 // indirect
 	github.com/Microsoft/go-winio v0.6.2 // indirect
 	github.com/ProtonMail/go-crypto v1.3.0 // indirect

+ 8 - 0
go.sum

@@ -75,6 +75,14 @@ github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0/go.mod h1:okt5dMMTOFjX/aov
 github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.1/go.mod h1:s4kgfzA0covAXNicZHDMN58jExvcng2mC/DepXiF1EI=
 github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 h1:9iefClla7iYpfYWdzPCRDozdmndjTm8DXdpCzPajMgA=
 github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2/go.mod h1:XtLgD3ZD34DAaVIIAyG3objl5DynM3CQ/vMcbBNJZGI=
+github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azcertificates v1.4.0 h1:mtvR5ZXH5Ew6PSONd5lO5OXovWP1E3oAlgC8fpxor2Q=
+github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azcertificates v1.4.0/go.mod h1:u560+RFVfG0CBPzkXlDW43slESbBAQjgDGi3r6z+wk8=
+github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.4.0 h1:E4MgwLBGeVB5f2MdcIVD3ELVAWpr+WD6MUe1i+tM/PA=
+github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.4.0/go.mod h1:Y2b/1clN4zsAoUd/pgNAQHjLDnTis/6ROkUfyob6psM=
+github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets v1.4.0 h1:/g8S6wk65vfC6m3FIxJ+i5QDyN9JWwXI8Hb0Img10hU=
+github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets v1.4.0/go.mod h1:gpl+q95AzZlKVI3xSoseF9QPrypk0hQqBiJYeB/cR/I=
+github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.2.0 h1:nCYfgcSyHZXJI8J0IWE5MsCGlb2xp9fJiXyxWgmOFg4=
+github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.2.0/go.mod h1:ucUjca2JtSZboY8IoUqyQyuuXvwbMBVwFOm0vdQPNhA=
 github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs=
 github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
 github.com/Azure/go-autorest/autorest v0.11.28/go.mod h1:MrkzG3Y3AH668QyF9KRk5neJnGgmhQ6krbhR8Q5eMvA=

+ 1 - 0
pkg/constants/constants.go

@@ -42,6 +42,7 @@ const (
 	CallAzureKVImportKey         = "ImportKey"
 	CallAzureKVGetSecret         = "GetSecret"
 	CallAzureKVGetSecrets        = "GetSecrets"
+	CallAzureKVSetSecret         = "SetSecret"
 	CallAzureKVDeleteSecret      = "DeleteSecret"
 	CallAzureKVGetCertificate    = "GetCertificate"
 	CallAzureKVDeleteCertificate = "DeleteCertificate"

+ 4 - 0
pkg/generator/acr/acr.go

@@ -377,6 +377,10 @@ func audienceForType(t esv1.AzureEnvironmentType) string {
 		return azure.USGovernmentCloud.TokenAudience + suffix
 	case esv1.AzureEnvironmentPublicCloud, "":
 		return azure.PublicCloud.TokenAudience + suffix
+	case esv1.AzureEnvironmentAzureStackCloud:
+		// Azure Stack Cloud requires custom configuration
+		// Return empty string to indicate it's not supported
+		return ""
 	}
 	return azure.PublicCloud.TokenAudience + suffix
 }

+ 215 - 49
pkg/provider/azure/keyvault/keyvault.go

@@ -29,6 +29,10 @@ import (
 	"strings"
 	"time"
 
+	"github.com/Azure/azure-sdk-for-go/sdk/azcore"
+	"github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azcertificates"
+	"github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys"
+	"github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets"
 	"github.com/Azure/azure-sdk-for-go/services/keyvault/v7.0/keyvault"
 	"github.com/Azure/go-autorest/autorest"
 	"github.com/Azure/go-autorest/autorest/adal"
@@ -120,8 +124,15 @@ type Azure struct {
 	kubeClient kcorev1.CoreV1Interface
 	store      esv1.GenericStore
 	provider   *esv1.AzureKVProvider
-	baseClient SecretClient
 	namespace  string
+
+	// Legacy go-autorest client
+	baseClient SecretClient
+
+	// New Azure SDK clients (used when UseAzureSDK is true)
+	secretsClient *azsecrets.Client
+	keysClient    *azkeys.Client
+	certsClient   *azcertificates.Client
 }
 
 type PushSecretMetadataSpec struct {
@@ -174,8 +185,22 @@ func newClient(ctx context.Context, store esv1.GenericStore, kube client.Client,
 		return az, nil
 	}
 
+	// Check if the new Azure SDK should be used
+	if provider.UseAzureSDK != nil && *provider.UseAzureSDK {
+		err = initializeNewAzureSDK(ctx, az)
+	} else {
+		err = initializeLegacyClient(ctx, az)
+	}
+
+	return az, err
+}
+
+// initializeLegacyClient sets up the Azure Key Vault client using the legacy go-autorest SDK.
+func initializeLegacyClient(ctx context.Context, az *Azure) error {
 	var authorizer autorest.Authorizer
-	switch *provider.AuthType {
+	var err error
+
+	switch *az.provider.AuthType {
 	case esv1.AzureManagedIdentity:
 		authorizer, err = az.authorizerForManagedIdentity()
 	case esv1.AzureServicePrincipal:
@@ -183,14 +208,74 @@ func newClient(ctx context.Context, store esv1.GenericStore, kube client.Client,
 	case esv1.AzureWorkloadIdentity:
 		authorizer, err = az.authorizerForWorkloadIdentity(ctx, NewTokenProvider)
 	default:
-		err = errors.New(errMissingAuthType)
+		return errors.New(errMissingAuthType)
+	}
+
+	if err != nil {
+		return err
 	}
 
 	cl := keyvault.New()
 	cl.Authorizer = authorizer
 	az.baseClient = &cl
 
-	return az, err
+	return nil
+}
+
+// initializeNewAzureSDK sets up the Azure Key Vault client using the new azcore-based SDK.
+func initializeNewAzureSDK(ctx context.Context, az *Azure) error {
+	// Get cloud configuration
+	cloudConfig, err := getCloudConfiguration(az.provider)
+	if err != nil {
+		return fmt.Errorf("failed to get cloud configuration: %w", err)
+	}
+
+	// Build credential based on auth type
+	var credential azcore.TokenCredential
+
+	switch *az.provider.AuthType {
+	case esv1.AzureManagedIdentity:
+		credential, err = buildManagedIdentityCredential(az, cloudConfig)
+	case esv1.AzureServicePrincipal:
+		credential, err = buildServicePrincipalCredential(ctx, az, cloudConfig)
+	case esv1.AzureWorkloadIdentity:
+		credential, err = buildWorkloadIdentityCredential(ctx, az, cloudConfig)
+	default:
+		return errors.New(errMissingAuthType)
+	}
+
+	if err != nil {
+		return err
+	}
+
+	// Create Azure SDK clients and store them directly
+	az.secretsClient, err = azsecrets.NewClient(*az.provider.VaultURL, credential, &azsecrets.ClientOptions{
+		ClientOptions: azcore.ClientOptions{Cloud: cloudConfig},
+	})
+	if err != nil {
+		return fmt.Errorf("failed to create secrets client: %w", err)
+	}
+
+	az.keysClient, err = azkeys.NewClient(*az.provider.VaultURL, credential, &azkeys.ClientOptions{
+		ClientOptions: azcore.ClientOptions{Cloud: cloudConfig},
+	})
+	if err != nil {
+		return fmt.Errorf("failed to create keys client: %w", err)
+	}
+
+	az.certsClient, err = azcertificates.NewClient(*az.provider.VaultURL, credential, &azcertificates.ClientOptions{
+		ClientOptions: azcore.ClientOptions{Cloud: cloudConfig},
+	})
+	if err != nil {
+		return fmt.Errorf("failed to create certificates client: %w", err)
+	}
+
+	return nil
+}
+
+// useNewSDK returns true if the new Azure SDK should be used.
+func (a *Azure) useNewSDK() bool {
+	return a.provider.UseAzureSDK != nil && *a.provider.UseAzureSDK
 }
 
 func getProvider(store esv1.GenericStore) (*esv1.AzureKVProvider, error) {
@@ -234,6 +319,26 @@ func (a *Azure) ValidateStore(store esv1.GenericStore) (admission.Warnings, erro
 			return nil, fmt.Errorf(errInvalidSARef, err)
 		}
 	}
+
+	// Validate Azure Stack Cloud configuration
+	if p.EnvironmentType == esv1.AzureEnvironmentAzureStackCloud {
+		// Azure Stack requires custom cloud config
+		if p.CustomCloudConfig == nil {
+			return nil, errors.New("CustomCloudConfig is required when EnvironmentType is AzureStackCloud")
+		}
+		// Azure Stack requires new SDK
+		if p.UseAzureSDK == nil || !*p.UseAzureSDK {
+			return nil, errors.New("AzureStackCloud environment requires UseAzureSDK to be set to true - the legacy SDK does not support custom clouds")
+		}
+		// Validate required fields
+		if p.CustomCloudConfig.ActiveDirectoryEndpoint == "" {
+			return nil, errors.New("activeDirectoryEndpoint is required in CustomCloudConfig")
+		}
+	} else if p.CustomCloudConfig != nil {
+		// CustomCloudConfig should only be used with AzureStackCloud
+		return nil, errors.New("CustomCloudConfig should only be specified when EnvironmentType is AzureStackCloud")
+	}
+
 	return nil, nil
 }
 
@@ -311,10 +416,19 @@ func (a *Azure) DeleteSecret(ctx context.Context, remoteRef esv1.PushSecretRemot
 	objectType, secretName := getObjType(esv1.ExternalSecretDataRemoteRef{Key: remoteRef.GetRemoteKey()})
 	switch objectType {
 	case defaultObjType:
+		if a.useNewSDK() {
+			return a.deleteKeyVaultSecretWithNewSDK(ctx, secretName)
+		}
 		return a.deleteKeyVaultSecret(ctx, secretName)
 	case objectTypeCert:
+		if a.useNewSDK() {
+			return a.deleteKeyVaultCertificateWithNewSDK(ctx, secretName)
+		}
 		return a.deleteKeyVaultCertificate(ctx, secretName)
 	case objectTypeKey:
+		if a.useNewSDK() {
+			return a.deleteKeyVaultKeyWithNewSDK(ctx, secretName)
+		}
 		return a.deleteKeyVaultKey(ctx, secretName)
 	default:
 		return fmt.Errorf("secret type '%v' is not supported", objectType)
@@ -322,6 +436,10 @@ func (a *Azure) DeleteSecret(ctx context.Context, remoteRef esv1.PushSecretRemot
 }
 
 func (a *Azure) SecretExists(ctx context.Context, remoteRef esv1.PushSecretRemoteRef) (bool, error) {
+	if a.useNewSDK() {
+		return a.secretExistsWithNewSDK(ctx, remoteRef)
+	}
+
 	objectType, secretName := getObjType(esv1.ExternalSecretDataRemoteRef{Key: remoteRef.GetRemoteKey()})
 
 	var err error
@@ -619,10 +737,19 @@ func (a *Azure) PushSecret(ctx context.Context, secret *corev1.Secret, data esv1
 	objectType, secretName := getObjType(esv1.ExternalSecretDataRemoteRef{Key: data.GetRemoteKey()})
 	switch objectType {
 	case defaultObjType:
+		if a.useNewSDK() {
+			return a.setKeyVaultSecretWithNewSDK(ctx, secretName, value, nil, tags)
+		}
 		return a.setKeyVaultSecret(ctx, secretName, value, expires, tags)
 	case objectTypeCert:
+		if a.useNewSDK() {
+			return a.setKeyVaultCertificateWithNewSDK(ctx, secretName, value, tags)
+		}
 		return a.setKeyVaultCertificate(ctx, secretName, value, tags)
 	case objectTypeKey:
+		if a.useNewSDK() {
+			return a.setKeyVaultKeyWithNewSDK(ctx, secretName, value, tags)
+		}
 		return a.setKeyVaultKey(ctx, secretName, value, tags)
 	default:
 		return fmt.Errorf("secret type %v not supported", objectType)
@@ -632,6 +759,13 @@ func (a *Azure) PushSecret(ctx context.Context, secret *corev1.Secret, data esv1
 // Implements store.Client.GetAllSecrets Interface.
 // Retrieves a map[string][]byte with the secret names as key and the secret itself as the calue.
 func (a *Azure) GetAllSecrets(ctx context.Context, ref esv1.ExternalSecretFind) (map[string][]byte, error) {
+	if a.useNewSDK() {
+		return a.getAllSecretsWithNewSDK(ctx, ref)
+	}
+	return a.getAllSecretsWithLegacySDK(ctx, ref)
+}
+
+func (a *Azure) getAllSecretsWithLegacySDK(ctx context.Context, ref esv1.ExternalSecretFind) (map[string][]byte, error) {
 	basicClient := a.baseClient
 	secretsMap := make(map[string][]byte)
 	checkTags := len(ref.Tags) > 0
@@ -734,56 +868,21 @@ func parseError(err error) error {
 // Retrieves a secret/Key/Certificate/Tag with the secret name defined in ref.Name
 // The Object Type is defined as a prefix in the ref.Name , if no prefix is defined , we assume a secret is required.
 func (a *Azure) GetSecret(ctx context.Context, ref esv1.ExternalSecretDataRemoteRef) ([]byte, error) {
-	objectType, secretName := getObjType(ref)
-
-	switch objectType {
-	case defaultObjType:
-		// returns a SecretBundle with the secret value
-		// https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/services/keyvault/v7.0/keyvault#SecretBundle
-		secretResp, err := a.baseClient.GetSecret(ctx, *a.provider.VaultURL, secretName, ref.Version)
-		metrics.ObserveAPICall(constants.ProviderAzureKV, constants.CallAzureKVGetSecret, err)
-		err = parseError(err)
-		if err != nil {
-			return nil, err
-		}
-		if ref.MetadataPolicy == esv1.ExternalSecretMetadataPolicyFetch {
-			return getSecretTag(secretResp.Tags, ref.Property)
-		}
-		return getProperty(*secretResp.Value, ref.Property, ref.Key)
-	case objectTypeCert:
-		// returns a CertBundle. We return CER contents of x509 certificate
-		// see: https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/services/keyvault/v7.0/keyvault#CertificateBundle
-		certResp, err := a.baseClient.GetCertificate(ctx, *a.provider.VaultURL, secretName, ref.Version)
-		metrics.ObserveAPICall(constants.ProviderAzureKV, constants.CallAzureKVGetCertificate, err)
-		err = parseError(err)
-		if err != nil {
-			return nil, err
-		}
-		if ref.MetadataPolicy == esv1.ExternalSecretMetadataPolicyFetch {
-			return getSecretTag(certResp.Tags, ref.Property)
-		}
-		return *certResp.Cer, nil
-	case objectTypeKey:
-		// returns a KeyBundle that contains a jwk
-		// azure kv returns only public keys
-		// see: https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/services/keyvault/v7.0/keyvault#KeyBundle
-		keyResp, err := a.baseClient.GetKey(ctx, *a.provider.VaultURL, secretName, ref.Version)
-		metrics.ObserveAPICall(constants.ProviderAzureKV, constants.CallAzureKVGetKey, err)
-		err = parseError(err)
-		if err != nil {
-			return nil, err
-		}
-		if ref.MetadataPolicy == esv1.ExternalSecretMetadataPolicyFetch {
-			return getSecretTag(keyResp.Tags, ref.Property)
-		}
-		return json.Marshal(keyResp.Key)
+	if a.useNewSDK() {
+		return a.getSecretWithNewSDK(ctx, ref)
 	}
-
-	return nil, fmt.Errorf(errUnknownObjectType, secretName)
+	return a.getSecretWithLegacySDK(ctx, ref)
 }
 
 // returns a SecretBundle with the tags values.
 func (a *Azure) getSecretTags(ctx context.Context, ref esv1.ExternalSecretDataRemoteRef) (map[string]*string, error) {
+	if a.useNewSDK() {
+		return a.getSecretTagsWithNewSDK(ctx, ref)
+	}
+	return a.getSecretTagsWithLegacySDK(ctx, ref)
+}
+
+func (a *Azure) getSecretTagsWithLegacySDK(ctx context.Context, ref esv1.ExternalSecretDataRemoteRef) (map[string]*string, error) {
 	_, secretName := getObjType(ref)
 	secretResp, err := a.baseClient.GetSecret(ctx, *a.provider.VaultURL, secretName, ref.Version)
 	metrics.ObserveAPICall(constants.ProviderAzureKV, constants.CallAzureKVGetSecret, err)
@@ -1172,6 +1271,10 @@ func AadEndpointForType(t esv1.AzureEnvironmentType) string {
 		return azure.USGovernmentCloud.ActiveDirectoryEndpoint
 	case esv1.AzureEnvironmentGermanCloud:
 		return azure.GermanCloud.ActiveDirectoryEndpoint
+	case esv1.AzureEnvironmentAzureStackCloud:
+		// Azure Stack Cloud requires custom configuration and new SDK
+		// Return empty string to indicate it's not supported in old SDK
+		return ""
 	default:
 		return azure.PublicCloud.ActiveDirectoryEndpoint
 	}
@@ -1187,6 +1290,10 @@ func ServiceManagementEndpointForType(t esv1.AzureEnvironmentType) string {
 		return azure.USGovernmentCloud.ServiceManagementEndpoint
 	case esv1.AzureEnvironmentGermanCloud:
 		return azure.GermanCloud.ServiceManagementEndpoint
+	case esv1.AzureEnvironmentAzureStackCloud:
+		// Azure Stack Cloud requires custom configuration and new SDK
+		// Return empty string to indicate it's not supported in old SDK
+		return ""
 	default:
 		return azure.PublicCloud.ServiceManagementEndpoint
 	}
@@ -1203,6 +1310,10 @@ func kvResourceForProviderConfig(t esv1.AzureEnvironmentType) string {
 		res = azure.USGovernmentCloud.KeyVaultEndpoint
 	case esv1.AzureEnvironmentGermanCloud:
 		res = azure.GermanCloud.KeyVaultEndpoint
+	case esv1.AzureEnvironmentAzureStackCloud:
+		// Azure Stack Cloud requires custom configuration and new SDK
+		// Return empty string to indicate it's not supported in old SDK
+		res = ""
 	default:
 		res = azure.PublicCloud.KeyVaultEndpoint
 	}
@@ -1255,3 +1366,58 @@ func okByTags(ref esv1.ExternalSecretFind, secret keyvault.SecretItem) bool {
 	}
 	return tagsFound
 }
+
+// GetSecret implementation using legacy go-autorest SDK.
+func (a *Azure) getSecretWithLegacySDK(ctx context.Context, ref esv1.ExternalSecretDataRemoteRef) ([]byte, error) {
+	objectType, secretName := getObjType(ref)
+
+	switch objectType {
+	case defaultObjType:
+		// returns a SecretBundle with the secret value
+		// https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/services/keyvault/v7.0/keyvault#SecretBundle
+		secretResp, err := a.baseClient.GetSecret(ctx, *a.provider.VaultURL, secretName, ref.Version)
+		metrics.ObserveAPICall(constants.ProviderAzureKV, constants.CallAzureKVGetSecret, err)
+		err = parseError(err)
+		if err != nil {
+			return nil, err
+		}
+		if ref.MetadataPolicy == esv1.ExternalSecretMetadataPolicyFetch {
+			return getSecretTag(secretResp.Tags, ref.Property)
+		}
+		return getProperty(*secretResp.Value, ref.Property, ref.Key)
+
+	case objectTypeCert:
+		// returns a CertBundle. We return CER contents of x509 certificate
+		// see: https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/services/keyvault/v7.0/keyvault#CertificateBundle
+		certResp, err := a.baseClient.GetCertificate(ctx, *a.provider.VaultURL, secretName, ref.Version)
+		metrics.ObserveAPICall(constants.ProviderAzureKV, constants.CallAzureKVGetCertificate, err)
+		err = parseError(err)
+		if err != nil {
+			return nil, err
+		}
+		if ref.MetadataPolicy == esv1.ExternalSecretMetadataPolicyFetch {
+			return getSecretTag(certResp.Tags, ref.Property)
+		}
+		return *certResp.Cer, nil
+
+	case objectTypeKey:
+		// returns a KeyBundle
+		// https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/services/keyvault/v7.0/keyvault#KeyBundle
+		keyResp, err := a.baseClient.GetKey(ctx, *a.provider.VaultURL, secretName, ref.Version)
+		metrics.ObserveAPICall(constants.ProviderAzureKV, constants.CallAzureKVGetKey, err)
+		err = parseError(err)
+		if err != nil {
+			return nil, err
+		}
+		if ref.MetadataPolicy == esv1.ExternalSecretMetadataPolicyFetch {
+			return getSecretTag(keyResp.Tags, ref.Property)
+		}
+		keyBytes, err := json.Marshal(keyResp.Key)
+		if err != nil {
+			return nil, fmt.Errorf("failed to marshal key: %w", err)
+		}
+		return getProperty(string(keyBytes), ref.Property, ref.Key)
+	}
+
+	return nil, fmt.Errorf(errUnknownObjectType, secretName)
+}

+ 522 - 0
pkg/provider/azure/keyvault/keyvault_dual_sdk_test.go

@@ -0,0 +1,522 @@
+/*
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package keyvault
+
+import (
+	"context"
+	"testing"
+
+	esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
+	v1 "github.com/external-secrets/external-secrets/apis/meta/v1"
+	corev1 "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/utils/ptr"
+	"sigs.k8s.io/controller-runtime/pkg/client/fake"
+)
+
+// TestFeatureFlagRouting tests that the UseAzureSDK feature flag correctly routes to the appropriate implementation.
+func TestFeatureFlagRouting(t *testing.T) {
+	testCases := []struct {
+		name         string
+		useAzureSDK  *bool
+		expectNewSDK bool
+		description  string
+	}{
+		{
+			name:         "default_legacy_sdk",
+			useAzureSDK:  nil,
+			expectNewSDK: false,
+			description:  "When UseAzureSDK is nil (default), should use legacy SDK",
+		},
+		{
+			name:         "explicit_legacy_sdk",
+			useAzureSDK:  ptr.To(false),
+			expectNewSDK: false,
+			description:  "When UseAzureSDK is explicitly false, should use legacy SDK",
+		},
+		{
+			name:         "explicit_new_sdk",
+			useAzureSDK:  ptr.To(true),
+			expectNewSDK: true,
+			description:  "When UseAzureSDK is true, should use new SDK",
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			// Create test provider with the specified feature flag
+			provider := &esv1.AzureKVProvider{
+				VaultURL:    ptr.To("https://test-vault.vault.azure.net/"),
+				TenantID:    ptr.To("test-tenant"),
+				AuthType:    ptr.To(esv1.AzureServicePrincipal),
+				UseAzureSDK: tc.useAzureSDK,
+				AuthSecretRef: &esv1.AzureKVAuth{
+					ClientID: &v1.SecretKeySelector{
+						Name: "test-secret",
+						Key:  "client-id",
+					},
+					ClientSecret: &v1.SecretKeySelector{
+						Name: "test-secret",
+						Key:  "client-secret",
+					},
+				},
+			}
+
+			// Create Azure client
+			azure := &Azure{
+				provider: provider,
+			}
+
+			// Test the useNewSDK() method
+			result := azure.useNewSDK()
+			if result != tc.expectNewSDK {
+				t.Errorf("Expected useNewSDK() to return %v for %s, got %v", tc.expectNewSDK, tc.description, result)
+			}
+		})
+	}
+}
+
+// TestClientInitialization tests that both client initialization paths work correctly.
+func TestClientInitialization(t *testing.T) {
+	// Create test secret with credentials
+	secret := &corev1.Secret{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      "test-secret",
+			Namespace: "test-namespace",
+		},
+		Data: map[string][]byte{
+			"client-id":     []byte("test-client-id"),
+			"client-secret": []byte("test-client-secret"),
+		},
+	}
+
+	fakeClient := fake.NewClientBuilder().WithObjects(secret).Build()
+
+	testCases := []struct {
+		name                string
+		useAzureSDK         *bool
+		expectedErrorPrefix string
+		description         string
+	}{
+		{
+			name:                "legacy_client_init",
+			useAzureSDK:         ptr.To(false),
+			expectedErrorPrefix: "", // May succeed or fail with auth errors, but should not panic
+			description:         "Legacy client initialization should not panic",
+		},
+		{
+			name:                "new_sdk_client_init",
+			useAzureSDK:         ptr.To(true),
+			expectedErrorPrefix: "", // May succeed or fail with auth errors, but should not panic
+			description:         "New SDK client initialization should not panic",
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			provider := &esv1.AzureKVProvider{
+				VaultURL:    ptr.To("https://test-vault.vault.azure.net/"),
+				TenantID:    ptr.To("test-tenant"),
+				AuthType:    ptr.To(esv1.AzureServicePrincipal),
+				UseAzureSDK: tc.useAzureSDK,
+				AuthSecretRef: &esv1.AzureKVAuth{
+					ClientID: &v1.SecretKeySelector{
+						Name: "test-secret",
+						Key:  "client-id",
+					},
+					ClientSecret: &v1.SecretKeySelector{
+						Name: "test-secret",
+						Key:  "client-secret",
+					},
+				},
+			}
+
+			store := &esv1.SecretStore{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      "test-store",
+					Namespace: "test-namespace",
+				},
+				Spec: esv1.SecretStoreSpec{
+					Provider: &esv1.SecretStoreProvider{
+						AzureKV: provider,
+					},
+				},
+			}
+
+			// Test that client initialization doesn't panic
+			defer func() {
+				if r := recover(); r != nil {
+					t.Errorf("Client initialization panicked for %s: %v", tc.description, r)
+				}
+			}()
+
+			azure := &Azure{}
+			_, err := azure.NewClient(context.Background(), store, fakeClient, "test-namespace")
+
+			// We expect errors due to authentication issues in tests, but no panics
+			// The important thing is that the code paths are exercised without crashing
+			if err != nil {
+				t.Logf("Expected auth error for %s: %v", tc.description, err)
+			}
+		})
+	}
+}
+
+// TestConfigurationValidation tests that the feature flag is properly validated and accepted.
+func TestConfigurationValidation(t *testing.T) {
+	testCases := []struct {
+		name        string
+		useAzureSDK *bool
+		expectValid bool
+		description string
+	}{
+		{
+			name:        "nil_feature_flag",
+			useAzureSDK: nil,
+			expectValid: true,
+			description: "Nil feature flag should be valid (defaults to legacy)",
+		},
+		{
+			name:        "false_feature_flag",
+			useAzureSDK: ptr.To(false),
+			expectValid: true,
+			description: "False feature flag should be valid (legacy SDK)",
+		},
+		{
+			name:        "true_feature_flag",
+			useAzureSDK: ptr.To(true),
+			expectValid: true,
+			description: "True feature flag should be valid (new SDK)",
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			provider := &esv1.AzureKVProvider{
+				VaultURL:    ptr.To("https://test-vault.vault.azure.net/"),
+				TenantID:    ptr.To("test-tenant"),
+				AuthType:    ptr.To(esv1.AzureServicePrincipal),
+				UseAzureSDK: tc.useAzureSDK,
+				AuthSecretRef: &esv1.AzureKVAuth{
+					ClientID: &v1.SecretKeySelector{
+						Name: "test-secret",
+						Key:  "client-id",
+					},
+					ClientSecret: &v1.SecretKeySelector{
+						Name: "test-secret",
+						Key:  "client-secret",
+					},
+				},
+			}
+
+			store := &esv1.SecretStore{
+				Spec: esv1.SecretStoreSpec{
+					Provider: &esv1.SecretStoreProvider{
+						AzureKV: provider,
+					},
+				},
+			}
+
+			azure := &Azure{}
+			warnings, err := azure.ValidateStore(store)
+
+			if tc.expectValid {
+				if err != nil {
+					t.Errorf("Expected validation to pass for %s, got error: %v", tc.description, err)
+				}
+				if len(warnings) > 0 {
+					t.Logf("Validation warnings for %s: %v", tc.description, warnings)
+				}
+			} else if err == nil {
+				t.Errorf("Expected validation to fail for %s, but it passed", tc.description)
+			}
+		})
+	}
+}
+
+// TestBackwardCompatibility ensures that existing configurations continue to work.
+func TestBackwardCompatibility(t *testing.T) {
+	// Test that existing configurations without UseAzureSDK still work
+	provider := &esv1.AzureKVProvider{
+		VaultURL: ptr.To("https://test-vault.vault.azure.net/"),
+		TenantID: ptr.To("test-tenant"),
+		AuthType: ptr.To(esv1.AzureServicePrincipal),
+		// UseAzureSDK intentionally omitted to test backward compatibility
+		AuthSecretRef: &esv1.AzureKVAuth{
+			ClientID: &v1.SecretKeySelector{
+				Name: "test-secret",
+				Key:  "client-id",
+			},
+			ClientSecret: &v1.SecretKeySelector{
+				Name: "test-secret",
+				Key:  "client-secret",
+			},
+		},
+	}
+
+	azure := &Azure{
+		provider: provider,
+	}
+
+	// Should default to legacy SDK (false)
+	if azure.useNewSDK() {
+		t.Error("Expected backward compatibility: nil UseAzureSDK should default to legacy SDK (false)")
+	}
+
+	// Validation should still pass
+	store := &esv1.SecretStore{
+		Spec: esv1.SecretStoreSpec{
+			Provider: &esv1.SecretStoreProvider{
+				AzureKV: provider,
+			},
+		},
+	}
+
+	warnings, err := azure.ValidateStore(store)
+	if err != nil {
+		t.Errorf("Backward compatibility failed: existing configuration should validate, got error: %v", err)
+	}
+	if len(warnings) > 0 {
+		t.Logf("Backward compatibility warnings: %v", warnings)
+	}
+}
+
+// TestAzureStackCloudConfiguration tests Azure Stack Cloud configuration validation.
+func TestAzureStackCloudConfiguration(t *testing.T) {
+	testCases := []struct {
+		name          string
+		useAzureSDK   *bool
+		envType       esv1.AzureEnvironmentType
+		customConfig  *esv1.AzureCustomCloudConfig
+		expectError   bool
+		expectedError string
+		description   string
+	}{
+		{
+			name:        "azure_stack_with_new_sdk_and_config",
+			useAzureSDK: ptr.To(true),
+			envType:     esv1.AzureEnvironmentAzureStackCloud,
+			customConfig: &esv1.AzureCustomCloudConfig{
+				ActiveDirectoryEndpoint: "https://login.microsoftonline.com/",
+				KeyVaultEndpoint:        ptr.To("https://vault.local.azurestack.external/"),
+				KeyVaultDNSSuffix:       ptr.To(".vault.local.azurestack.external"),
+			},
+			expectError: false,
+			description: "Azure Stack with new SDK and custom config should be valid",
+		},
+		{
+			name:          "azure_stack_without_custom_config",
+			useAzureSDK:   ptr.To(true),
+			envType:       esv1.AzureEnvironmentAzureStackCloud,
+			customConfig:  nil,
+			expectError:   true,
+			expectedError: "CustomCloudConfig is required when EnvironmentType is AzureStackCloud",
+			description:   "Azure Stack without custom config should fail",
+		},
+		{
+			name:        "azure_stack_with_legacy_sdk",
+			useAzureSDK: ptr.To(false),
+			envType:     esv1.AzureEnvironmentAzureStackCloud,
+			customConfig: &esv1.AzureCustomCloudConfig{
+				ActiveDirectoryEndpoint: "https://login.microsoftonline.com/",
+			},
+			expectError:   true,
+			expectedError: "AzureStackCloud environment requires UseAzureSDK to be set to true - the legacy SDK does not support custom clouds",
+			description:   "Azure Stack with legacy SDK should fail",
+		},
+		{
+			name:        "azure_stack_without_new_sdk_flag",
+			useAzureSDK: nil, // defaults to false
+			envType:     esv1.AzureEnvironmentAzureStackCloud,
+			customConfig: &esv1.AzureCustomCloudConfig{
+				ActiveDirectoryEndpoint: "https://login.microsoftonline.com/",
+			},
+			expectError:   true,
+			expectedError: "AzureStackCloud environment requires UseAzureSDK to be set to true - the legacy SDK does not support custom clouds",
+			description:   "Azure Stack without explicit new SDK flag should fail",
+		},
+		{
+			name:        "azure_stack_missing_aad_endpoint",
+			useAzureSDK: ptr.To(true),
+			envType:     esv1.AzureEnvironmentAzureStackCloud,
+			customConfig: &esv1.AzureCustomCloudConfig{
+				KeyVaultEndpoint: ptr.To("https://vault.custom.cloud/"),
+			},
+			expectError:   true,
+			expectedError: "activeDirectoryEndpoint is required in CustomCloudConfig",
+			description:   "Azure Stack without AAD endpoint should fail",
+		},
+		{
+			name:        "custom_config_without_azure_stack",
+			useAzureSDK: ptr.To(true),
+			envType:     esv1.AzureEnvironmentPublicCloud,
+			customConfig: &esv1.AzureCustomCloudConfig{
+				ActiveDirectoryEndpoint: "https://login.microsoftonline.com/",
+			},
+			expectError:   true,
+			expectedError: "CustomCloudConfig should only be specified when EnvironmentType is AzureStackCloud",
+			description:   "Custom config with non-AzureStack environment should fail",
+		},
+		{
+			name:         "public_cloud_without_custom_config",
+			useAzureSDK:  ptr.To(true),
+			envType:      esv1.AzureEnvironmentPublicCloud,
+			customConfig: nil,
+			expectError:  false,
+			description:  "Public cloud without custom config should be valid",
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			provider := &esv1.AzureKVProvider{
+				VaultURL:          ptr.To("https://test-vault.vault.azure.net/"),
+				TenantID:          ptr.To("test-tenant"),
+				AuthType:          ptr.To(esv1.AzureServicePrincipal),
+				UseAzureSDK:       tc.useAzureSDK,
+				EnvironmentType:   tc.envType,
+				CustomCloudConfig: tc.customConfig,
+				AuthSecretRef: &esv1.AzureKVAuth{
+					ClientID: &v1.SecretKeySelector{
+						Name: "test-secret",
+						Key:  "client-id",
+					},
+					ClientSecret: &v1.SecretKeySelector{
+						Name: "test-secret",
+						Key:  "client-secret",
+					},
+				},
+			}
+
+			azure := &Azure{provider: provider}
+			store := &esv1.SecretStore{
+				Spec: esv1.SecretStoreSpec{
+					Provider: &esv1.SecretStoreProvider{
+						AzureKV: provider,
+					},
+				},
+			}
+
+			warnings, err := azure.ValidateStore(store)
+			if tc.expectError {
+				if err == nil {
+					t.Errorf("Expected validation to fail for %s, but it succeeded", tc.description)
+				} else if tc.expectedError != "" && err.Error() != tc.expectedError {
+					t.Errorf("Expected error message '%s' for %s, got: %v", tc.expectedError, tc.description, err)
+				}
+			} else {
+				if err != nil {
+					t.Errorf("Expected validation to succeed for %s, but got error: %v", tc.description, err)
+				}
+			}
+			if len(warnings) > 0 {
+				t.Logf("Warnings for %s: %v", tc.name, warnings)
+			}
+		})
+	}
+}
+
+// TestGetCloudConfiguration tests the cloud configuration resolution.
+func TestGetCloudConfiguration(t *testing.T) {
+	testCases := []struct {
+		name          string
+		provider      *esv1.AzureKVProvider
+		expectError   bool
+		expectedError string
+		description   string
+	}{
+		{
+			name: "public_cloud",
+			provider: &esv1.AzureKVProvider{
+				EnvironmentType: esv1.AzureEnvironmentPublicCloud,
+			},
+			expectError: false,
+			description: "Public cloud should return valid configuration",
+		},
+		{
+			name: "us_government_cloud",
+			provider: &esv1.AzureKVProvider{
+				EnvironmentType: esv1.AzureEnvironmentUSGovernmentCloud,
+			},
+			expectError: false,
+			description: "US Government cloud should return valid configuration",
+		},
+		{
+			name: "china_cloud",
+			provider: &esv1.AzureKVProvider{
+				EnvironmentType: esv1.AzureEnvironmentChinaCloud,
+			},
+			expectError: false,
+			description: "China cloud should return valid configuration",
+		},
+		{
+			name: "azure_stack_with_config",
+			provider: &esv1.AzureKVProvider{
+				EnvironmentType: esv1.AzureEnvironmentAzureStackCloud,
+				UseAzureSDK:     ptr.To(true),
+				CustomCloudConfig: &esv1.AzureCustomCloudConfig{
+					ActiveDirectoryEndpoint: "https://login.local.azurestack.external/",
+					KeyVaultEndpoint:        ptr.To("https://vault.local.azurestack.external/"),
+				},
+			},
+			expectError: false,
+			description: "Azure Stack with valid config should return custom configuration",
+		},
+		{
+			name: "azure_stack_without_new_sdk",
+			provider: &esv1.AzureKVProvider{
+				EnvironmentType: esv1.AzureEnvironmentAzureStackCloud,
+				UseAzureSDK:     ptr.To(false),
+				CustomCloudConfig: &esv1.AzureCustomCloudConfig{
+					ActiveDirectoryEndpoint: "https://login.local.azurestack.external/",
+				},
+			},
+			expectError:   true,
+			expectedError: "AzureStackCloud environment requires UseAzureSDK to be set to true",
+			description:   "Azure Stack without new SDK should fail",
+		},
+		{
+			name: "azure_stack_without_config",
+			provider: &esv1.AzureKVProvider{
+				EnvironmentType: esv1.AzureEnvironmentAzureStackCloud,
+				UseAzureSDK:     ptr.To(true),
+			},
+			expectError:   true,
+			expectedError: "CustomCloudConfig is required when EnvironmentType is AzureStackCloud",
+			description:   "Azure Stack without custom config should fail",
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			config, err := getCloudConfiguration(tc.provider)
+			if tc.expectError {
+				if err == nil {
+					t.Errorf("Expected error for %s, but got none", tc.description)
+				} else if tc.expectedError != "" && err.Error() != tc.expectedError {
+					t.Errorf("Expected error '%s' for %s, got: %v", tc.expectedError, tc.description, err)
+				}
+			} else {
+				if err != nil {
+					t.Errorf("Expected no error for %s, but got: %v", tc.description, err)
+				}
+				if config.ActiveDirectoryAuthorityHost == "" && tc.provider.EnvironmentType != esv1.AzureEnvironmentAzureStackCloud {
+					// For predefined clouds, we should have a valid config
+					t.Errorf("Expected valid cloud configuration for %s", tc.description)
+				}
+			}
+		})
+	}
+}

+ 729 - 0
pkg/provider/azure/keyvault/keyvault_new_sdk.go

@@ -0,0 +1,729 @@
+/*
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+	http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package keyvault
+
+import (
+	"context"
+	"crypto/sha3"
+	b64 "encoding/base64"
+	"encoding/json"
+	"errors"
+	"fmt"
+	"regexp"
+	"time"
+
+	"github.com/Azure/azure-sdk-for-go/sdk/azcore"
+	"github.com/Azure/azure-sdk-for-go/sdk/azcore/cloud"
+	"github.com/Azure/azure-sdk-for-go/sdk/azcore/to"
+	"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
+	"github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azcertificates"
+	"github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys"
+	"github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets"
+	"github.com/lestrrat-go/jwx/v2/jwk"
+	corev1 "k8s.io/api/core/v1"
+	"k8s.io/apimachinery/pkg/types"
+
+	esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
+	"github.com/external-secrets/external-secrets/pkg/constants"
+	"github.com/external-secrets/external-secrets/pkg/metrics"
+	"github.com/external-secrets/external-secrets/pkg/utils/resolvers"
+)
+
+// New SDK implementations for setter methods.
+func (a *Azure) setKeyVaultSecretWithNewSDK(ctx context.Context, secretName string, value []byte, _ *time.Time, tags map[string]string) error {
+	// Check if secret exists and if we can create/update it
+	existingSecret, err := a.secretsClient.GetSecret(ctx, secretName, "", nil)
+	metrics.ObserveAPICall(constants.ProviderAzureKV, constants.CallAzureKVGetSecret, err)
+
+	if err != nil {
+		var respErr *azcore.ResponseError
+		if !errors.As(err, &respErr) || respErr.StatusCode != 404 {
+			return fmt.Errorf("cannot get secret %v: %w", secretName, parseNewSDKError(err))
+		}
+	} else {
+		// Check if managed by external-secrets using new SDK tags
+		if existingSecret.Tags != nil {
+			if managedByTag, exists := existingSecret.Tags[managedBy]; !exists || managedByTag == nil || *managedByTag != managerLabel {
+				return fmt.Errorf("secret %v not managed by external-secrets", secretName)
+			}
+		}
+
+		// Check if secret content is the same
+		val := string(value)
+		if existingSecret.Value != nil && val == *existingSecret.Value {
+			// Note: We're not checking expiration here since the new SDK doesn't support setting it
+			// This means the new SDK implementation will always update the secret if the content is the same
+			// but different expiration is requested
+			return nil
+		}
+	}
+
+	// Prepare tags for new SDK
+	secretTags := map[string]*string{
+		managedBy: to.Ptr(managerLabel),
+	}
+	for k, v := range tags {
+		secretTags[k] = &v
+	}
+
+	// Set the secret
+	val := string(value)
+	params := azsecrets.SetSecretParameters{
+		Value: &val,
+		Tags:  secretTags,
+	}
+
+	// Note: The new SDK doesn't support setting expiration in SetSecretParameters
+	// This is a limitation compared to the legacy SDK - expiration would need to be handled differently
+
+	_, err = a.secretsClient.SetSecret(ctx, secretName, params, nil)
+	metrics.ObserveAPICall(constants.ProviderAzureKV, constants.CallAzureKVSetSecret, err)
+	if err != nil {
+		return fmt.Errorf("could not set secret %v: %w", secretName, parseNewSDKError(err))
+	}
+	return nil
+}
+
+func (a *Azure) setKeyVaultCertificateWithNewSDK(ctx context.Context, secretName string, value []byte, tags map[string]string) error {
+	val := b64.StdEncoding.EncodeToString(value)
+	localCert, err := getCertificateFromValue(value)
+	if err != nil {
+		return fmt.Errorf("value from secret is not a valid certificate: %w", err)
+	}
+
+	// Check if certificate exists
+	cert, err := a.certsClient.GetCertificate(ctx, secretName, "", nil)
+	metrics.ObserveAPICall(constants.ProviderAzureKV, constants.CallAzureKVGetCertificate, err)
+
+	if err != nil {
+		var respErr *azcore.ResponseError
+		if !errors.As(err, &respErr) || respErr.StatusCode != 404 {
+			return fmt.Errorf("cannot get certificate %v: %w", secretName, parseNewSDKError(err))
+		}
+	} else {
+		// Check if managed by external-secrets
+		if cert.Tags != nil {
+			if managedByTag, exists := cert.Tags[managedBy]; !exists || managedByTag == nil || *managedByTag != managerLabel {
+				return fmt.Errorf("certificate %v not managed by external-secrets", secretName)
+			}
+		}
+
+		// Check if certificate content is the same
+		b512 := sha3.Sum512(localCert.Raw)
+		if cert.CER != nil && b512 == sha3.Sum512(cert.CER) {
+			return nil
+		}
+	}
+
+	// Prepare tags for new SDK
+	certTags := map[string]*string{
+		managedBy: to.Ptr(managerLabel),
+	}
+	for k, v := range tags {
+		certTags[k] = &v
+	}
+
+	params := azcertificates.ImportCertificateParameters{
+		Base64EncodedCertificate: &val,
+		Tags:                     certTags,
+	}
+
+	_, err = a.certsClient.ImportCertificate(ctx, secretName, params, nil)
+	metrics.ObserveAPICall(constants.ProviderAzureKV, constants.CallAzureKVImportCertificate, err)
+	if err != nil {
+		return fmt.Errorf("could not import certificate %v: %w", secretName, parseNewSDKError(err))
+	}
+	return nil
+}
+
+func (a *Azure) setKeyVaultKeyWithNewSDK(ctx context.Context, secretName string, value []byte, tags map[string]string) error {
+	key, err := getKeyFromValue(value)
+	if err != nil {
+		return fmt.Errorf("could not load private key %v: %w", secretName, err)
+	}
+	jwKey, err := jwk.FromRaw(key)
+	if err != nil {
+		return fmt.Errorf("failed to generate a JWK from secret %v content: %w", secretName, err)
+	}
+	buf, err := json.Marshal(jwKey)
+	if err != nil {
+		return fmt.Errorf("error parsing key: %w", err)
+	}
+
+	var azkey azkeys.JSONWebKey
+	err = json.Unmarshal(buf, &azkey)
+	if err != nil {
+		return fmt.Errorf("error unmarshalling key: %w", err)
+	}
+
+	// Check if key exists
+	keyFromVault, err := a.keysClient.GetKey(ctx, secretName, "", nil)
+	metrics.ObserveAPICall(constants.ProviderAzureKV, constants.CallAzureKVGetKey, err)
+
+	if err != nil {
+		var respErr *azcore.ResponseError
+		if !errors.As(err, &respErr) || respErr.StatusCode != 404 {
+			return fmt.Errorf("cannot get key %v: %w", secretName, parseNewSDKError(err))
+		}
+	} else if keyFromVault.Tags != nil {
+		// Check if managed by external-secrets
+		if managedByTag, exists := keyFromVault.Tags[managedBy]; !exists || managedByTag == nil || *managedByTag != managerLabel {
+			return fmt.Errorf("key %v not managed by external-secrets", secretName)
+		}
+	}
+
+	// For key comparison, we'll do a simple check - if we get here and the key exists, we'll update it
+	// A more sophisticated comparison could be added later if needed
+
+	// Prepare tags for new SDK
+	keyTags := map[string]*string{
+		managedBy: to.Ptr(managerLabel),
+	}
+	for k, v := range tags {
+		keyTags[k] = &v
+	}
+
+	params := azkeys.ImportKeyParameters{
+		Key: &azkey,
+		KeyAttributes: &azkeys.KeyAttributes{
+			Enabled: to.Ptr(true),
+		},
+		Tags: keyTags,
+	}
+
+	_, err = a.keysClient.ImportKey(ctx, secretName, params, nil)
+	metrics.ObserveAPICall(constants.ProviderAzureKV, constants.CallAzureKVImportKey, err)
+	if err != nil {
+		return fmt.Errorf("could not import key %v: %w", secretName, parseNewSDKError(err))
+	}
+	return nil
+}
+
+// isValidSecret checks if a secret is valid and enabled.
+func (a *Azure) isValidSecret(secret *azsecrets.SecretProperties) bool {
+	return secret.ID != nil &&
+		secret.Attributes != nil &&
+		*secret.Attributes.Enabled
+}
+
+// secretMatchesTags checks if secret matches required tags.
+func (a *Azure) secretMatchesTags(secret *azsecrets.SecretProperties, requiredTags map[string]string) bool {
+	if len(requiredTags) == 0 {
+		return true
+	}
+
+	for k, v := range requiredTags {
+		if val, ok := secret.Tags[k]; !ok || val == nil || *val != v {
+			return false
+		}
+	}
+	return true
+}
+
+// secretMatchesNamePattern checks if secret name matches the regex pattern.
+// Logs error and returns false if the regex is invalid to ensure failed matches are excluded.
+func (a *Azure) secretMatchesNamePattern(secretName string, nameRef *esv1.FindName) bool {
+	if nameRef == nil || nameRef.RegExp == "" {
+		return true
+	}
+
+	isMatch, err := regexp.MatchString(nameRef.RegExp, secretName)
+	if err != nil {
+		// Log invalid regex pattern and return false to exclude this secret
+		// This ensures that malformed regex patterns don't silently pass
+		fmt.Printf("invalid regex pattern %q: %v\n", nameRef.RegExp, err)
+		return false
+	}
+	return isMatch
+}
+
+// processSecretsPage processes a single page of secrets from the list operation.
+func (a *Azure) processSecretsPage(ctx context.Context, secrets []*azsecrets.SecretProperties, ref esv1.ExternalSecretFind, secretsMap map[string][]byte) error {
+	for _, secret := range secrets {
+		if !a.isValidSecret(secret) {
+			continue
+		}
+
+		if !a.secretMatchesTags(secret, ref.Tags) {
+			continue
+		}
+
+		secretName := secret.ID.Name()
+		if !a.secretMatchesNamePattern(secretName, ref.Name) {
+			continue
+		}
+
+		// Get the secret value
+		secretResp, err := a.secretsClient.GetSecret(ctx, secretName, "", nil)
+		metrics.ObserveAPICall(constants.ProviderAzureKV, constants.CallAzureKVGetSecret, err)
+		if err != nil {
+			return parseNewSDKError(err)
+		}
+
+		if secretResp.Value != nil {
+			secretsMap[secretName] = []byte(*secretResp.Value)
+		}
+	}
+	return nil
+}
+
+func (a *Azure) getAllSecretsWithNewSDK(ctx context.Context, ref esv1.ExternalSecretFind) (map[string][]byte, error) {
+	secretsMap := make(map[string][]byte)
+	pager := a.secretsClient.NewListSecretPropertiesPager(nil)
+
+	for pager.More() {
+		page, err := pager.NextPage(ctx)
+		metrics.ObserveAPICall(constants.ProviderAzureKV, constants.CallAzureKVGetSecrets, err)
+		if err != nil {
+			return nil, parseNewSDKError(err)
+		}
+
+		if err := a.processSecretsPage(ctx, page.Value, ref, secretsMap); err != nil {
+			return nil, err
+		}
+	}
+	return secretsMap, nil
+}
+
+func (a *Azure) getSecretTagsWithNewSDK(ctx context.Context, ref esv1.ExternalSecretDataRemoteRef) (map[string]*string, error) {
+	_, secretName := getObjType(ref)
+	secretResp, err := a.secretsClient.GetSecret(ctx, secretName, ref.Version, nil)
+	metrics.ObserveAPICall(constants.ProviderAzureKV, constants.CallAzureKVGetSecret, err)
+	if err != nil {
+		return nil, parseNewSDKError(err)
+	}
+
+	secretTagsData := make(map[string]*string)
+
+	for tagname, tagval := range secretResp.Tags {
+		name := secretName + "_" + tagname
+		kv := make(map[string]string)
+		err = json.Unmarshal([]byte(*tagval), &kv)
+		// if the tagvalue is not in JSON format then we added to secretTagsData we added as it is
+		if err != nil {
+			secretTagsData[name] = tagval
+		} else {
+			for k, v := range kv {
+				value := v
+				secretTagsData[name+"_"+k] = &value
+			}
+		}
+	}
+	return secretTagsData, nil
+}
+
+// Helper functions for new Azure SDK
+
+// getCloudConfiguration returns the appropriate cloud configuration for the environment type.
+func getCloudConfiguration(provider *esv1.AzureKVProvider) (cloud.Configuration, error) {
+	switch provider.EnvironmentType {
+	case esv1.AzureEnvironmentPublicCloud:
+		return cloud.AzurePublic, nil
+	case esv1.AzureEnvironmentUSGovernmentCloud:
+		return cloud.AzureGovernment, nil
+	case esv1.AzureEnvironmentChinaCloud:
+		return cloud.AzureChina, nil
+	case esv1.AzureEnvironmentGermanCloud:
+		return cloud.Configuration{}, errors.New("Azure Germany (Microsoft Cloud Deutschland) was discontinued on October 29, 2021. Please use AzureStackCloud with custom configuration or migrate to public cloud regions")
+	case esv1.AzureEnvironmentAzureStackCloud:
+		// Azure Stack requires custom configuration
+		if provider.CustomCloudConfig == nil {
+			return cloud.Configuration{}, errors.New("CustomCloudConfig is required when EnvironmentType is AzureStackCloud")
+		}
+		// Validate that new SDK is enabled
+		if provider.UseAzureSDK == nil || !*provider.UseAzureSDK {
+			return cloud.Configuration{}, errors.New("AzureStackCloud environment requires UseAzureSDK to be set to true")
+		}
+		return buildCustomCloudConfiguration(provider.CustomCloudConfig)
+	default:
+		return cloud.AzurePublic, nil
+	}
+}
+
+// buildCustomCloudConfiguration creates a custom cloud.Configuration for Azure Stack.
+func buildCustomCloudConfiguration(config *esv1.AzureCustomCloudConfig) (cloud.Configuration, error) {
+	cloudConfig := cloud.Configuration{
+		Services: map[cloud.ServiceName]cloud.ServiceConfiguration{},
+	}
+
+	// Set Active Directory endpoint (required)
+	cloudConfig.ActiveDirectoryAuthorityHost = config.ActiveDirectoryEndpoint
+
+	// Set Resource Manager endpoint if provided
+	if config.ResourceManagerEndpoint != nil {
+		cloudConfig.Services[cloud.ResourceManager] = cloud.ServiceConfiguration{
+			Audience: *config.ResourceManagerEndpoint,
+			Endpoint: *config.ResourceManagerEndpoint,
+		}
+	}
+
+	// Note: Key Vault endpoint and DNS suffix are handled directly by the Key Vault client
+	// through the vault URL, not through the cloud configuration
+
+	return cloudConfig, nil
+}
+
+// buildManagedIdentityCredential creates a ManagedIdentityCredential.
+func buildManagedIdentityCredential(az *Azure, cloudConfig cloud.Configuration) (azcore.TokenCredential, error) {
+	opts := &azidentity.ManagedIdentityCredentialOptions{
+		ClientOptions: azcore.ClientOptions{
+			Cloud: cloudConfig,
+		},
+	}
+
+	// Configure user-assigned identity if specified
+	if az.provider.IdentityID != nil {
+		opts.ID = azidentity.ClientID(*az.provider.IdentityID)
+	}
+
+	return azidentity.NewManagedIdentityCredential(opts)
+}
+
+// buildServicePrincipalCredential creates service principal credentials.
+func buildServicePrincipalCredential(ctx context.Context, az *Azure, cloudConfig cloud.Configuration) (azcore.TokenCredential, error) {
+	if az.provider.TenantID == nil {
+		return nil, errors.New(errMissingTenant)
+	}
+	if az.provider.AuthSecretRef == nil {
+		return nil, errors.New(errMissingSecretRef)
+	}
+	if az.provider.AuthSecretRef.ClientID == nil {
+		return nil, errors.New(errMissingClientIDSecret)
+	}
+
+	// Get clientID
+	clientID, err := resolvers.SecretKeyRef(
+		ctx,
+		az.crClient,
+		az.store.GetKind(),
+		az.namespace,
+		az.provider.AuthSecretRef.ClientID,
+	)
+	if err != nil {
+		return nil, fmt.Errorf("failed to get clientID: %w", err)
+	}
+
+	clientOpts := azcore.ClientOptions{
+		Cloud: cloudConfig,
+	}
+
+	// Check if using client secret or client certificate
+	if az.provider.AuthSecretRef.ClientSecret != nil && az.provider.AuthSecretRef.ClientCertificate != nil {
+		return nil, errors.New(errInvalidClientCredentials)
+	}
+
+	if az.provider.AuthSecretRef.ClientSecret != nil {
+		// Client secret authentication
+		clientSecret, err := resolvers.SecretKeyRef(
+			ctx,
+			az.crClient,
+			az.store.GetKind(),
+			az.namespace,
+			az.provider.AuthSecretRef.ClientSecret,
+		)
+		if err != nil {
+			return nil, fmt.Errorf("failed to get clientSecret: %w", err)
+		}
+
+		opts := &azidentity.ClientSecretCredentialOptions{
+			ClientOptions: clientOpts,
+		}
+
+		return azidentity.NewClientSecretCredential(*az.provider.TenantID, clientID, clientSecret, opts)
+	} else if az.provider.AuthSecretRef.ClientCertificate != nil {
+		// Client certificate authentication
+		certData, err := resolvers.SecretKeyRef(
+			ctx,
+			az.crClient,
+			az.store.GetKind(),
+			az.namespace,
+			az.provider.AuthSecretRef.ClientCertificate,
+		)
+		if err != nil {
+			return nil, fmt.Errorf("failed to get clientCertificate: %w", err)
+		}
+
+		// Parse certificate and key
+		certs, key, err := azidentity.ParseCertificates([]byte(certData), nil)
+		if err != nil {
+			return nil, fmt.Errorf("failed to parse client certificate: %w", err)
+		}
+
+		opts := &azidentity.ClientCertificateCredentialOptions{
+			ClientOptions: clientOpts,
+		}
+
+		return azidentity.NewClientCertificateCredential(*az.provider.TenantID, clientID, certs, key, opts)
+	}
+
+	return nil, errors.New(errMissingClientIDSecret)
+}
+
+// buildWorkloadIdentityCredential creates workload identity credentials.
+func buildWorkloadIdentityCredential(ctx context.Context, az *Azure, cloudConfig cloud.Configuration) (azcore.TokenCredential, error) {
+	clientOpts := azcore.ClientOptions{
+		Cloud: cloudConfig,
+	}
+
+	// If no serviceAccountRef is provided, use environment variables (webhook mode)
+	if az.provider.ServiceAccountRef == nil {
+		opts := &azidentity.WorkloadIdentityCredentialOptions{
+			ClientOptions: clientOpts,
+		}
+		return azidentity.NewWorkloadIdentityCredential(opts)
+	}
+
+	// ServiceAccountRef mode - get values from service account and secrets
+	ns := az.namespace
+	if az.store.GetKind() == esv1.ClusterSecretStoreKind && az.provider.ServiceAccountRef.Namespace != nil {
+		ns = *az.provider.ServiceAccountRef.Namespace
+	}
+
+	var sa corev1.ServiceAccount
+	err := az.crClient.Get(ctx, types.NamespacedName{
+		Name:      az.provider.ServiceAccountRef.Name,
+		Namespace: ns,
+	}, &sa)
+	if err != nil {
+		return nil, fmt.Errorf("failed to get service account: %w", err)
+	}
+
+	// Get clientID from service account annotations
+	var clientID string
+	if val, found := sa.ObjectMeta.Annotations[AnnotationClientID]; found {
+		clientID = val
+	} else {
+		return nil, fmt.Errorf(errMissingClient, AnnotationClientID)
+	}
+
+	// Get tenantID
+	var tenantID string
+	if az.provider.TenantID != nil {
+		tenantID = *az.provider.TenantID
+	} else if val, found := sa.ObjectMeta.Annotations[AnnotationTenantID]; found {
+		tenantID = val
+	} else {
+		return nil, errors.New(errMissingTenant)
+	}
+
+	// Use ClientAssertionCredential to avoid filesystem access in read-only environments
+	// This provides a callback function that fetches tokens dynamically
+	getAssertion := func(ctx context.Context) (string, error) {
+		audiences := []string{AzureDefaultAudience}
+		if len(az.provider.ServiceAccountRef.Audiences) > 0 {
+			audiences = append(audiences, az.provider.ServiceAccountRef.Audiences...)
+		}
+
+		token, err := FetchSAToken(ctx, ns, az.provider.ServiceAccountRef.Name, audiences, az.kubeClient)
+		if err != nil {
+			return "", fmt.Errorf("failed to fetch service account token: %w", err)
+		}
+		return token, nil
+	}
+
+	opts := &azidentity.ClientAssertionCredentialOptions{
+		ClientOptions: clientOpts,
+	}
+
+	return azidentity.NewClientAssertionCredential(tenantID, clientID, getAssertion, opts)
+}
+
+// canDeleteWithNewSDK checks if a resource can be deleted based on tags and error status.
+func canDeleteWithNewSDK(tags map[string]*string, err error) (bool, error) {
+	if err != nil {
+		var respErr *azcore.ResponseError
+		if errors.As(err, &respErr) {
+			if respErr.StatusCode == 404 {
+				// Resource doesn't exist, nothing to delete
+				return false, nil
+			}
+			// Other API error
+			return false, fmt.Errorf("unexpected api error: %w", err)
+		}
+		// Non-Azure error
+		return false, fmt.Errorf("could not parse error: %w", err)
+	}
+
+	// Check if managed by external-secrets
+	if tags == nil {
+		return false, nil
+	}
+
+	managedByTag, exists := tags[managedBy]
+	if !exists || managedByTag == nil || *managedByTag != managerLabel {
+		// Not managed by external-secrets, don't delete
+		return false, nil
+	}
+
+	return true, nil
+}
+
+// Delete methods using new Azure SDK.
+func (a *Azure) deleteKeyVaultSecretWithNewSDK(ctx context.Context, secretName string) error {
+	secret, err := a.secretsClient.GetSecret(ctx, secretName, "", nil)
+	metrics.ObserveAPICall(constants.ProviderAzureKV, constants.CallAzureKVGetSecret, err)
+
+	ok, err := canDeleteWithNewSDK(secret.Tags, err)
+	if err != nil {
+		return fmt.Errorf("error getting secret %v: %w", secretName, err)
+	}
+
+	if ok {
+		_, err = a.secretsClient.DeleteSecret(ctx, secretName, nil)
+		metrics.ObserveAPICall(constants.ProviderAzureKV, constants.CallAzureKVDeleteSecret, err)
+		if err != nil {
+			return fmt.Errorf("error deleting secret %v: %w", secretName, err)
+		}
+	}
+	return nil
+}
+
+func (a *Azure) deleteKeyVaultCertificateWithNewSDK(ctx context.Context, certName string) error {
+	cert, err := a.certsClient.GetCertificate(ctx, certName, "", nil)
+	metrics.ObserveAPICall(constants.ProviderAzureKV, constants.CallAzureKVGetCertificate, err)
+
+	ok, err := canDeleteWithNewSDK(cert.Tags, err)
+	if err != nil {
+		return fmt.Errorf("error getting certificate %v: %w", certName, err)
+	}
+
+	if ok {
+		_, err = a.certsClient.DeleteCertificate(ctx, certName, nil)
+		metrics.ObserveAPICall(constants.ProviderAzureKV, constants.CallAzureKVDeleteCertificate, err)
+		if err != nil {
+			return fmt.Errorf("error deleting certificate %v: %w", certName, err)
+		}
+	}
+	return nil
+}
+
+func (a *Azure) deleteKeyVaultKeyWithNewSDK(ctx context.Context, keyName string) error {
+	key, err := a.keysClient.GetKey(ctx, keyName, "", nil)
+	metrics.ObserveAPICall(constants.ProviderAzureKV, constants.CallAzureKVGetKey, err)
+
+	ok, err := canDeleteWithNewSDK(key.Tags, err)
+	if err != nil {
+		return fmt.Errorf("error getting key %v: %w", keyName, err)
+	}
+
+	if ok {
+		_, err = a.keysClient.DeleteKey(ctx, keyName, nil)
+		metrics.ObserveAPICall(constants.ProviderAzureKV, constants.CallAzureKVDeleteKey, err)
+		if err != nil {
+			return fmt.Errorf("error deleting key %v: %w", keyName, err)
+		}
+	}
+	return nil
+}
+
+// GetSecret implementation using new Azure SDK.
+func (a *Azure) getSecretWithNewSDK(ctx context.Context, ref esv1.ExternalSecretDataRemoteRef) ([]byte, error) {
+	objectType, secretName := getObjType(ref)
+
+	switch objectType {
+	case defaultObjType:
+		// Get secret using new SDK
+		resp, err := a.secretsClient.GetSecret(ctx, secretName, ref.Version, nil)
+		metrics.ObserveAPICall(constants.ProviderAzureKV, constants.CallAzureKVGetSecret, err)
+		if err != nil {
+			return nil, parseNewSDKError(err)
+		}
+		if ref.MetadataPolicy == esv1.ExternalSecretMetadataPolicyFetch {
+			return getSecretTag(resp.Tags, ref.Property)
+		}
+		return getProperty(*resp.Value, ref.Property, ref.Key)
+
+	case objectTypeCert:
+		// Get certificate using new SDK
+		resp, err := a.certsClient.GetCertificate(ctx, secretName, ref.Version, nil)
+		metrics.ObserveAPICall(constants.ProviderAzureKV, constants.CallAzureKVGetCertificate, err)
+		if err != nil {
+			return nil, parseNewSDKError(err)
+		}
+		if ref.MetadataPolicy == esv1.ExternalSecretMetadataPolicyFetch {
+			return getSecretTag(resp.Tags, ref.Property)
+		}
+		return resp.CER, nil
+
+	case objectTypeKey:
+		// Get key using new SDK
+		resp, err := a.keysClient.GetKey(ctx, secretName, ref.Version, nil)
+		metrics.ObserveAPICall(constants.ProviderAzureKV, constants.CallAzureKVGetKey, err)
+		if err != nil {
+			return nil, parseNewSDKError(err)
+		}
+		if ref.MetadataPolicy == esv1.ExternalSecretMetadataPolicyFetch {
+			return getSecretTag(resp.Tags, ref.Property)
+		}
+		keyBytes, err := json.Marshal(resp.Key)
+		if err != nil {
+			return nil, fmt.Errorf("failed to marshal key: %w", err)
+		}
+		return getProperty(string(keyBytes), ref.Property, ref.Key)
+	}
+
+	return nil, fmt.Errorf(errUnknownObjectType, secretName)
+}
+
+// secretExistsWithNewSDK checks if a secret/certificate/key exists in Azure Key Vault using the new SDK.
+// Returns (true, nil) if the object exists, (false, nil) if it doesn't exist, or (false, err) on error.
+func (a *Azure) secretExistsWithNewSDK(ctx context.Context, remoteRef esv1.PushSecretRemoteRef) (bool, error) {
+	objectType, secretName := getObjType(esv1.ExternalSecretDataRemoteRef{Key: remoteRef.GetRemoteKey()})
+
+	var err error
+	switch objectType {
+	case defaultObjType:
+		_, err = a.secretsClient.GetSecret(ctx, secretName, "", nil)
+		metrics.ObserveAPICall(constants.ProviderAzureKV, constants.CallAzureKVGetSecret, err)
+	case objectTypeCert:
+		_, err = a.certsClient.GetCertificate(ctx, secretName, "", nil)
+		metrics.ObserveAPICall(constants.ProviderAzureKV, constants.CallAzureKVGetCertificate, err)
+	case objectTypeKey:
+		_, err = a.keysClient.GetKey(ctx, secretName, "", nil)
+		metrics.ObserveAPICall(constants.ProviderAzureKV, constants.CallAzureKVGetKey, err)
+	default:
+		errMsg := fmt.Sprintf("secret type '%v' is not supported", objectType)
+		return false, errors.New(errMsg)
+	}
+
+	err = parseNewSDKError(err)
+	if err != nil {
+		var noSecretErr esv1.NoSecretError
+		if errors.As(err, &noSecretErr) {
+			return false, nil
+		}
+		return false, err
+	}
+
+	return true, nil
+}
+
+// parseNewSDKError converts new Azure SDK errors to the same format as legacy errors.
+func parseNewSDKError(err error) error {
+	if err == nil {
+		return nil
+	}
+
+	var respErr *azcore.ResponseError
+	if errors.As(err, &respErr) {
+		if respErr.StatusCode == 404 {
+			return esv1.NoSecretError{}
+		}
+		// Return error in the same format as the legacy parseError function
+		return err
+	}
+
+	return err
+}