Browse Source

Add support for Authentication against Azure Key Vault using Client Certificate (#3469)

* Implementation of Certificate Based Authz against Azure Key Vault

Signed-off-by: Luis Schweigard <luis.schweigard@gmail.com>

* Add tests for new Azure certificate auth functionality

Signed-off-by: Luis Schweigard <luis.schweigard@gmail.com>

* Add documentation for Azure Cert based Auth

Signed-off-by: Luis Schweigard <luis.schweigard@gmail.com>

* Generate spec.md

Signed-off-by: Luis Schweigard <luis.schweigard@gmail.com>

* Add changes from code review

Signed-off-by: Luis Schweigard <luis.schweigard@gmail.com>

* Fix naming in test error case

Signed-off-by: Luis Schweigard <luis.schweigard@gmail.com>

---------

Signed-off-by: Luis Schweigard <luis.schweigard@gmail.com>
Luis Schweigard 1 year ago
parent
commit
0abb3e9cc4

+ 4 - 0
apis/externalsecrets/v1beta1/secretstore_azurekv_types.go

@@ -99,4 +99,8 @@ type AzureKVAuth struct {
 	// The Azure ClientSecret of the service principle used for authentication.
 	// +optional
 	ClientSecret *smmeta.SecretKeySelector `json:"clientSecret,omitempty"`
+
+	// The Azure ClientCertificate of the service principle used for authentication.
+	// +optional
+	ClientCertificate *smmeta.SecretKeySelector `json:"clientCertificate,omitempty"`
 }

+ 5 - 0
apis/externalsecrets/v1beta1/zz_generated.deepcopy.go

@@ -329,6 +329,11 @@ func (in *AzureKVAuth) DeepCopyInto(out *AzureKVAuth) {
 		*out = new(metav1.SecretKeySelector)
 		(*in).DeepCopyInto(*out)
 	}
+	if in.ClientCertificate != nil {
+		in, out := &in.ClientCertificate, &out.ClientCertificate
+		*out = new(metav1.SecretKeySelector)
+		(*in).DeepCopyInto(*out)
+	}
 }
 
 // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AzureKVAuth.

+ 19 - 0
config/crds/bases/external-secrets.io_clustersecretstores.yaml

@@ -2144,6 +2144,25 @@ spec:
                           with Azure. Required for ServicePrincipal auth type. Optional
                           for WorkloadIdentity.
                         properties:
+                          clientCertificate:
+                            description: The Azure ClientCertificate of the service
+                              principle used for authentication.
+                            properties:
+                              key:
+                                description: |-
+                                  The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be
+                                  defaulted, in others it may be required.
+                                type: string
+                              name:
+                                description: The name of the Secret resource being
+                                  referred to.
+                                type: string
+                              namespace:
+                                description: |-
+                                  Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults
+                                  to the namespace of the referent.
+                                type: string
+                            type: object
                           clientId:
                             description: The Azure clientId of the service principle
                               or managed identity used for authentication.

+ 19 - 0
config/crds/bases/external-secrets.io_secretstores.yaml

@@ -2144,6 +2144,25 @@ spec:
                           with Azure. Required for ServicePrincipal auth type. Optional
                           for WorkloadIdentity.
                         properties:
+                          clientCertificate:
+                            description: The Azure ClientCertificate of the service
+                              principle used for authentication.
+                            properties:
+                              key:
+                                description: |-
+                                  The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be
+                                  defaulted, in others it may be required.
+                                type: string
+                              name:
+                                description: The name of the Secret resource being
+                                  referred to.
+                                type: string
+                              namespace:
+                                description: |-
+                                  Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults
+                                  to the namespace of the referent.
+                                type: string
+                            type: object
                           clientId:
                             description: The Azure clientId of the service principle
                               or managed identity used for authentication.

+ 34 - 0
deploy/crds/bundle.yaml

@@ -2665,6 +2665,23 @@ spec:
                         authSecretRef:
                           description: Auth configures how the operator authenticates with Azure. Required for ServicePrincipal auth type. Optional for WorkloadIdentity.
                           properties:
+                            clientCertificate:
+                              description: The Azure ClientCertificate of the service principle used for authentication.
+                              properties:
+                                key:
+                                  description: |-
+                                    The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be
+                                    defaulted, in others it may be required.
+                                  type: string
+                                name:
+                                  description: The name of the Secret resource being referred to.
+                                  type: string
+                                namespace:
+                                  description: |-
+                                    Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults
+                                    to the namespace of the referent.
+                                  type: string
+                              type: object
                             clientId:
                               description: The Azure clientId of the service principle or managed identity used for authentication.
                               properties:
@@ -8010,6 +8027,23 @@ spec:
                         authSecretRef:
                           description: Auth configures how the operator authenticates with Azure. Required for ServicePrincipal auth type. Optional for WorkloadIdentity.
                           properties:
+                            clientCertificate:
+                              description: The Azure ClientCertificate of the service principle used for authentication.
+                              properties:
+                                key:
+                                  description: |-
+                                    The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be
+                                    defaulted, in others it may be required.
+                                  type: string
+                                name:
+                                  description: The name of the Secret resource being referred to.
+                                  type: string
+                                namespace:
+                                  description: |-
+                                    Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults
+                                    to the namespace of the referent.
+                                  type: string
+                              type: object
                             clientId:
                               description: The Azure clientId of the service principle or managed identity used for authentication.
                               properties:

+ 14 - 0
docs/api/spec.md

@@ -869,6 +869,20 @@ External Secrets meta/v1.SecretKeySelector
 <p>The Azure ClientSecret of the service principle used for authentication.</p>
 </td>
 </tr>
+<tr>
+<td>
+<code>clientCertificate</code></br>
+<em>
+<a href="https://pkg.go.dev/github.com/external-secrets/external-secrets/apis/meta/v1#SecretKeySelector">
+External Secrets meta/v1.SecretKeySelector
+</a>
+</em>
+</td>
+<td>
+<em>(Optional)</em>
+<p>The Azure ClientCertificate of the service principle used for authentication.</p>
+</td>
+</tr>
 </tbody>
 </table>
 <h3 id="external-secrets.io/v1beta1.AzureKVProvider">AzureKVProvider

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

@@ -34,7 +34,7 @@ az keyvault set-policy --name kv-name-with-certs --object-id "$KUBELET_IDENTITY_
 
 #### Service Principal key authentication
 
-A service Principal client and Secret is created and the JSON keyfile is stored in a `Kind=Secret`. The `ClientID` and `ClientSecret` should be configured for the secret. This service principal should have proper access rights to the keyvault to be managed by the operator
+A service Principal client and Secret is created and the JSON keyfile is stored in a `Kind=Secret`. The `ClientID` and `ClientSecret` or `ClientCertificate` (in PEM format) should be configured for the secret. This service principal should have proper access rights to the keyvault to be managed by the operator.
 
 #### Managed Identity authentication
 

+ 89 - 37
pkg/provider/azure/keyvault/keyvault.go

@@ -64,30 +64,32 @@ const (
 	AnnotationTenantID   = "azure.workload.identity/tenant-id"
 	managerLabel         = "external-secrets"
 
-	errUnexpectedStoreSpec   = "unexpected store spec"
-	errMissingAuthType       = "cannot initialize Azure Client: no valid authType was specified"
-	errPropNotExist          = "property %s does not exist in key %s"
-	errTagNotExist           = "tag %s does not exist"
-	errUnknownObjectType     = "unknown Azure Keyvault object Type for %s"
-	errUnmarshalJSONData     = "error unmarshalling json data: %w"
-	errDataFromCert          = "cannot get use dataFrom to get certificate secret"
-	errDataFromKey           = "cannot get use dataFrom to get key secret"
-	errMissingTenant         = "missing tenantID in store config"
-	errMissingClient         = "missing clientID: either serviceAccountRef or service account annotation '%s' is missing"
-	errMissingSecretRef      = "missing secretRef in provider config"
-	errMissingClientIDSecret = "missing accessKeyID/secretAccessKey in store config"
-	errMultipleClientID      = "multiple clientID found. Check secretRef and serviceAccountRef"
-	errMultipleTenantID      = "multiple tenantID found. Check secretRef, 'spec.provider.azurekv.tenantId', and serviceAccountRef"
-	errFindSecret            = "could not find secret %s/%s: %w"
-	errFindDataKey           = "no data for %q in secret '%s/%s'"
-
-	errInvalidStore              = "invalid store"
-	errInvalidStoreSpec          = "invalid store spec"
-	errInvalidStoreProv          = "invalid store provider"
-	errInvalidAzureProv          = "invalid azure keyvault provider"
-	errInvalidSecRefClientID     = "invalid AuthSecretRef.ClientID: %w"
-	errInvalidSecRefClientSecret = "invalid AuthSecretRef.ClientSecret: %w"
-	errInvalidSARef              = "invalid ServiceAccountRef: %w"
+	errUnexpectedStoreSpec      = "unexpected store spec"
+	errMissingAuthType          = "cannot initialize Azure Client: no valid authType was specified"
+	errPropNotExist             = "property %s does not exist in key %s"
+	errTagNotExist              = "tag %s does not exist"
+	errUnknownObjectType        = "unknown Azure Keyvault object Type for %s"
+	errUnmarshalJSONData        = "error unmarshalling json data: %w"
+	errDataFromCert             = "cannot get use dataFrom to get certificate secret"
+	errDataFromKey              = "cannot get use dataFrom to get key secret"
+	errMissingTenant            = "missing tenantID in store config"
+	errMissingClient            = "missing clientID: either serviceAccountRef or service account annotation '%s' is missing"
+	errMissingSecretRef         = "missing secretRef in provider config"
+	errMissingClientIDSecret    = "missing accessKeyID/secretAccessKey in store config"
+	errInvalidClientCredentials = "both clientSecret and clientCredentials set"
+	errMultipleClientID         = "multiple clientID found. Check secretRef and serviceAccountRef"
+	errMultipleTenantID         = "multiple tenantID found. Check secretRef, 'spec.provider.azurekv.tenantId', and serviceAccountRef"
+	errFindSecret               = "could not find secret %s/%s: %w"
+	errFindDataKey              = "no data for %q in secret '%s/%s'"
+
+	errInvalidStore                   = "invalid store"
+	errInvalidStoreSpec               = "invalid store spec"
+	errInvalidStoreProv               = "invalid store provider"
+	errInvalidAzureProv               = "invalid azure keyvault provider"
+	errInvalidSecRefClientID          = "invalid AuthSecretRef.ClientID: %w"
+	errInvalidSecRefClientSecret      = "invalid AuthSecretRef.ClientSecret: %w"
+	errInvalidSecRefClientCertificate = "invalid AuthSecretRef.ClientCertificate: %w"
+	errInvalidSARef                   = "invalid ServiceAccountRef: %w"
 
 	errMissingWorkloadEnvVars = "missing environment variables. AZURE_CLIENT_ID, AZURE_TENANT_ID and AZURE_FEDERATED_TOKEN_FILE must be set"
 	errReadTokenFile          = "unable to read token file %s: %w"
@@ -877,7 +879,7 @@ func (a *Azure) authorizerForWorkloadIdentity(ctx context.Context, tokenProvider
 			}
 		}
 	}
-	// Check if spec.provider.azurekv.tenantId is set
+	// Check if spec.provider.azurekv.tenantID is set
 	if tenantID == "" && a.provider.TenantID != nil {
 		tenantID = *a.provider.TenantID
 	}
@@ -979,31 +981,81 @@ func (a *Azure) authorizerForServicePrincipal(ctx context.Context) (autorest.Aut
 	if a.provider.AuthSecretRef == nil {
 		return nil, fmt.Errorf(errMissingSecretRef)
 	}
-	if a.provider.AuthSecretRef.ClientID == nil || a.provider.AuthSecretRef.ClientSecret == nil {
+	if a.provider.AuthSecretRef.ClientID == nil || (a.provider.AuthSecretRef.ClientSecret == nil && a.provider.AuthSecretRef.ClientCertificate == nil) {
 		return nil, fmt.Errorf(errMissingClientIDSecret)
 	}
+	if a.provider.AuthSecretRef.ClientSecret != nil && a.provider.AuthSecretRef.ClientCertificate != nil {
+		return nil, fmt.Errorf(errInvalidClientCredentials)
+	}
+
+	return a.getAuthorizerFromCredentials(ctx)
+}
+
+func (a *Azure) getAuthorizerFromCredentials(ctx context.Context) (autorest.Authorizer, error) {
 	clientID, err := resolvers.SecretKeyRef(
 		ctx,
 		a.crClient,
 		a.store.GetKind(),
-		a.namespace, a.provider.AuthSecretRef.ClientID)
+		a.namespace, a.provider.AuthSecretRef.ClientID,
+	)
+
 	if err != nil {
 		return nil, err
 	}
-	clientSecret, err := resolvers.SecretKeyRef(
-		ctx,
-		a.crClient,
-		a.store.GetKind(),
-		a.namespace, a.provider.AuthSecretRef.ClientSecret)
-	if err != nil {
-		return nil, err
+
+	if a.provider.AuthSecretRef.ClientSecret != nil {
+		clientSecret, err := resolvers.SecretKeyRef(
+			ctx,
+			a.crClient,
+			a.store.GetKind(),
+			a.namespace, a.provider.AuthSecretRef.ClientSecret,
+		)
+
+		if err != nil {
+			return nil, err
+		}
+
+		return getAuthorizerForClientSecret(
+			clientID,
+			clientSecret,
+			*a.provider.TenantID,
+			a.provider.EnvironmentType,
+		)
+	} else {
+		clientCertificate, err := resolvers.SecretKeyRef(
+			ctx,
+			a.crClient,
+			a.store.GetKind(),
+			a.namespace, a.provider.AuthSecretRef.ClientCertificate,
+		)
+
+		if err != nil {
+			return nil, err
+		}
+
+		return getAuthorizerForClientCertificate(
+			clientID,
+			[]byte(clientCertificate),
+			*a.provider.TenantID,
+			a.provider.EnvironmentType,
+		)
 	}
-	clientCredentialsConfig := kvauth.NewClientCredentialsConfig(clientID, clientSecret, *a.provider.TenantID)
-	clientCredentialsConfig.Resource = kvResourceForProviderConfig(a.provider.EnvironmentType)
-	clientCredentialsConfig.AADEndpoint = AadEndpointForType(a.provider.EnvironmentType)
+}
+
+func getAuthorizerForClientSecret(clientID, clientSecret, tenantID string, environmentType esv1beta1.AzureEnvironmentType) (autorest.Authorizer, error) {
+	clientCredentialsConfig := kvauth.NewClientCredentialsConfig(clientID, clientSecret, tenantID)
+	clientCredentialsConfig.Resource = kvResourceForProviderConfig(environmentType)
+	clientCredentialsConfig.AADEndpoint = AadEndpointForType(environmentType)
 	return clientCredentialsConfig.Authorizer()
 }
 
+func getAuthorizerForClientCertificate(clientID string, certificateBytes []byte, tenantID string, environmentType esv1beta1.AzureEnvironmentType) (autorest.Authorizer, error) {
+	clientCertificateConfig := NewClientInMemoryCertificateConfig(clientID, certificateBytes, tenantID)
+	clientCertificateConfig.Resource = kvResourceForProviderConfig(environmentType)
+	clientCertificateConfig.AADEndpoint = AadEndpointForType(environmentType)
+	return clientCertificateConfig.Authorizer()
+}
+
 func (a *Azure) Close(_ context.Context) error {
 	return nil
 }

+ 115 - 1
pkg/provider/azure/keyvault/keyvault_auth_test.go

@@ -37,6 +37,32 @@ import (
 
 var vaultURL = "https://local.vault.url"
 
+var mockCertificate = `
+-----BEGIN CERTIFICATE-----
+MIICBzCCAbGgAwIBAgIUSoCD1fgywDbmeRaGrkYzGWUd1wMwDQYJKoZIhvcNAQEL
+BQAwcTELMAkGA1UEBhMCQVoxGTAXBgNVBAgMEE1vY2sgQ2VydGlmaWNhdGUxMzAx
+BgNVBAoMKkV4dGVybmFsIFNlY3JldHMgT3BlcmF0b3IgTW9jayBDZXJ0aWZpY2F0
+ZTESMBAGA1UEAwwJTW9jayBDZXJ0MB4XDTI0MDUwODA4NDkzMFoXDTI1MDUwODA4
+NDkzMFowcTELMAkGA1UEBhMCQVoxGTAXBgNVBAgMEE1vY2sgQ2VydGlmaWNhdGUx
+MzAxBgNVBAoMKkV4dGVybmFsIFNlY3JldHMgT3BlcmF0b3IgTW9jayBDZXJ0aWZp
+Y2F0ZTESMBAGA1UEAwwJTW9jayBDZXJ0MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJB
+ALkU1YgMk1Dk149F/HsHA0TjzLwfDa9tT0cfqA1u0hoJkb2r9jdWUyiugGaEz/PU
+TGWrvp8aiXPrGuu5Y6PY27ECAwEAAaMhMB8wHQYDVR0OBBYEFAMB0YwnYjUm00og
+kGce8Yhr4I03MA0GCSqGSIb3DQEBCwUAA0EAr0BMs/3hIOdZc0WHZUCTZ0GGor3G
+ViYUPHOw8z6UZGPGN6qiAejmkT6uP3LkkSW+7TIIQ1pkQxcn5xfFJXBexw==
+-----END CERTIFICATE-----
+-----BEGIN PRIVATE KEY-----
+MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAuRTViAyTUOTXj0X8
+ewcDROPMvB8Nr21PRx+oDW7SGgmRvav2N1ZTKK6AZoTP89RMZau+nxqJc+sa67lj
+o9jbsQIDAQABAkA35CnDpwCJykGqW5kuUeTT1fMK0FnioyDwuoeWXuQFxmB6Md89
++ABxyjAt3nmwRRVBrVFdNibb9asR5KFHwn1NAiEA4NlrSnJrY1xODIjEXf0fLTwu
+wpyUO1lX585OjYDiOYsCIQDSuP4ttH/1Hg3f9veEE4RgDEk+QcisrzF8q4Oa5sDP
+MwIgfejiTtcR0ZsPza8Mn0EuIyuPV8VMsItQUWtSy6R/ig8CIQC86cBmNUXp+HGz
+8fLg46ZvfVREjjFcLwwMmq83tdvxZQIgPAbezuRCrduH19xgMO8BXndS5DAovgvE
+/MpQnEyQtVA=
+-----END PRIVATE KEY-----
+`
+
 func TestNewClientManagedIdentityNoNeedForCredentials(t *testing.T) {
 	namespace := "internal"
 	identityID := "1234"
@@ -405,7 +431,7 @@ func TestAuth(t *testing.T) {
 			},
 		},
 		{
-			name: "correct cluster secret store",
+			name: "correct cluster secret store with ClientSecret",
 			objects: []client.Object{&corev1.Secret{
 				ObjectMeta: metav1.ObjectMeta{
 					Name:      "password",
@@ -432,6 +458,94 @@ func TestAuth(t *testing.T) {
 				},
 			},
 		},
+		{
+			name:   "bad config: both clientSecret and clientCredentials are configured",
+			expErr: "both clientSecret and clientCredentials set",
+			objects: []client.Object{&corev1.Secret{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      "password",
+					Namespace: "foo",
+				},
+				Data: map[string][]byte{
+					"id":          []byte("foo"),
+					"certificate": []byte("bar"),
+					"secret":      []byte("bar"),
+				},
+			}},
+			store: &esv1beta1.ClusterSecretStore{
+				TypeMeta: metav1.TypeMeta{
+					Kind: esv1beta1.ClusterSecretStoreKind,
+				},
+				Spec: esv1beta1.SecretStoreSpec{Provider: &esv1beta1.SecretStoreProvider{}},
+			},
+			provider: &esv1beta1.AzureKVProvider{
+				AuthType: &authType,
+				VaultURL: &vaultURL,
+				TenantID: pointer.To("mytenant"),
+				AuthSecretRef: &esv1beta1.AzureKVAuth{
+					ClientID:          &v1.SecretKeySelector{Name: "password", Namespace: pointer.To("foo"), Key: "id"},
+					ClientCertificate: &v1.SecretKeySelector{Name: "password", Namespace: pointer.To("foo"), Key: "certificate"},
+					ClientSecret:      &v1.SecretKeySelector{Name: "password", Namespace: pointer.To("foo"), Key: "secret"},
+				},
+			},
+		},
+		{
+			name:   "bad config: no valid client certificate in pem file",
+			expErr: "failed to get oauth token from certificate auth: failed to decode certificate: no certificate found in PEM file",
+			objects: []client.Object{&corev1.Secret{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      "password",
+					Namespace: "foo",
+				},
+				Data: map[string][]byte{
+					"id":          []byte("foo"),
+					"certificate": []byte("bar"),
+				},
+			}},
+			store: &esv1beta1.ClusterSecretStore{
+				TypeMeta: metav1.TypeMeta{
+					Kind: esv1beta1.ClusterSecretStoreKind,
+				},
+				Spec: esv1beta1.SecretStoreSpec{Provider: &esv1beta1.SecretStoreProvider{}},
+			},
+			provider: &esv1beta1.AzureKVProvider{
+				AuthType: &authType,
+				VaultURL: &vaultURL,
+				TenantID: pointer.To("mytenant"),
+				AuthSecretRef: &esv1beta1.AzureKVAuth{
+					ClientID:          &v1.SecretKeySelector{Name: "password", Namespace: pointer.To("foo"), Key: "id"},
+					ClientCertificate: &v1.SecretKeySelector{Name: "password", Namespace: pointer.To("foo"), Key: "certificate"},
+				},
+			},
+		},
+		{
+			name: "correct configuration with certificate authentication",
+			objects: []client.Object{&corev1.Secret{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      "password",
+					Namespace: "foo",
+				},
+				Data: map[string][]byte{
+					"id":          []byte("foo"),
+					"certificate": []byte(mockCertificate),
+				},
+			}},
+			store: &esv1beta1.ClusterSecretStore{
+				TypeMeta: metav1.TypeMeta{
+					Kind: esv1beta1.ClusterSecretStoreKind,
+				},
+				Spec: esv1beta1.SecretStoreSpec{Provider: &esv1beta1.SecretStoreProvider{}},
+			},
+			provider: &esv1beta1.AzureKVProvider{
+				AuthType: &authType,
+				VaultURL: &vaultURL,
+				TenantID: pointer.To("mytenant"),
+				AuthSecretRef: &esv1beta1.AzureKVAuth{
+					ClientID:          &v1.SecretKeySelector{Name: "password", Namespace: pointer.To("foo"), Key: "id"},
+					ClientCertificate: &v1.SecretKeySelector{Name: "password", Namespace: pointer.To("foo"), Key: "certificate"},
+				},
+			},
+		},
 	} {
 		t.Run(row.name, func(t *testing.T) {
 			k8sClient := clientfake.NewClientBuilder().WithObjects(row.objects...).Build()

+ 121 - 0
pkg/provider/azure/keyvault/keyvault_certificate.go

@@ -0,0 +1,121 @@
+// /*
+// 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 (
+	"crypto/rsa"
+	"crypto/x509"
+	"encoding/pem"
+	"errors"
+	"fmt"
+
+	"github.com/Azure/go-autorest/autorest"
+	"github.com/Azure/go-autorest/autorest/adal"
+	"github.com/Azure/go-autorest/autorest/azure"
+)
+
+// ClientInMemoryCertificateConfig struct includes a Certificate field to hold the certificate data as a byte slice.
+type ClientInMemoryCertificateConfig struct {
+	ClientID    string
+	Certificate []byte // Certificate data as a byte slice
+	TenantID    string
+	AuxTenants  []string
+	AADEndpoint string
+	Resource    string
+}
+
+func NewClientInMemoryCertificateConfig(clientID string, certificate []byte, tenantID string) ClientInMemoryCertificateConfig {
+	return ClientInMemoryCertificateConfig{
+		ClientID:    clientID,
+		Certificate: certificate,
+		TenantID:    tenantID,
+		Resource:    azure.PublicCloud.ResourceManagerEndpoint,
+		AADEndpoint: azure.PublicCloud.ActiveDirectoryEndpoint,
+	}
+}
+
+// ServicePrincipalToken creates a adal.ServicePrincipalToken from client certificate using the certificate byte slice.
+func (ccc ClientInMemoryCertificateConfig) ServicePrincipalToken() (*adal.ServicePrincipalToken, error) {
+	oauthConfig, err := adal.NewOAuthConfig(ccc.AADEndpoint, ccc.TenantID)
+	if err != nil {
+		return nil, err
+	}
+	// Use the byte slice directly instead of reading from a file
+	certificate, rsaPrivateKey, err := loadCertificateFromBytes(ccc.Certificate)
+
+	if err != nil {
+		return nil, fmt.Errorf("failed to decode certificate: %w", err)
+	}
+	return adal.NewServicePrincipalTokenFromCertificate(*oauthConfig, ccc.ClientID, certificate, rsaPrivateKey, ccc.Resource)
+}
+
+func loadCertificateFromBytes(certificateBytes []byte) (*x509.Certificate, *rsa.PrivateKey, error) {
+	var cert *x509.Certificate
+	var privateKey *rsa.PrivateKey
+	var err error
+
+	// Extract certificate and private key
+	for {
+		block, rest := pem.Decode(certificateBytes)
+		if block == nil {
+			break
+		}
+		if block.Type == "CERTIFICATE" {
+			cert, err = x509.ParseCertificate(block.Bytes)
+			if err != nil {
+				return nil, nil, fmt.Errorf("failed to parse PEM certificate: %w", err)
+			}
+		} else {
+			privateKey, err = parsePrivateKey(block.Bytes)
+			if err != nil {
+				return nil, nil, fmt.Errorf("failed to extract private key from PEM certificate: %w", err)
+			}
+		}
+		certificateBytes = rest
+	}
+
+	if cert == nil {
+		return nil, nil, errors.New("no certificate found in PEM file")
+	}
+
+	if privateKey == nil {
+		return nil, nil, errors.New("no private key found in PEM file")
+	}
+
+	return cert, privateKey, nil
+}
+
+func parsePrivateKey(der []byte) (*rsa.PrivateKey, error) {
+	if key, err := x509.ParsePKCS1PrivateKey(der); err == nil {
+		return key, nil
+	}
+	if key, err := x509.ParsePKCS8PrivateKey(der); err == nil {
+		switch key := key.(type) {
+		case *rsa.PrivateKey:
+			return key, nil
+		default:
+			return nil, errors.New("found unknown private key type in PKCS#8 wrapping")
+		}
+	}
+	return nil, errors.New("failed to parse private key")
+}
+
+// Implementation of the AuthorizerConfig interface.
+func (ccc ClientInMemoryCertificateConfig) Authorizer() (autorest.Authorizer, error) {
+	spToken, err := ccc.ServicePrincipalToken()
+	if err != nil {
+		return nil, fmt.Errorf("failed to get oauth token from certificate auth: %w", err)
+	}
+	return autorest.NewBearerAuthorizer(spToken), nil
+}