Skip to content

Commit 56a8946

Browse files
Merge pull request #10019 from patrickdillon/OCPBUGS-36360-azure-cert
OCPBUGS-36360: Support certificate authentication with CAPZ
2 parents d9d84ea + 6ff73ae commit 56a8946

File tree

2 files changed

+86
-24
lines changed

2 files changed

+86
-24
lines changed

pkg/asset/manifests/azure/cluster.go

Lines changed: 29 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -275,41 +275,47 @@ func GenerateClusterAssets(installConfig *installconfig.InstallConfig, clusterID
275275
File: asset.File{Filename: "02_azure-cluster.yaml"},
276276
})
277277

278-
azureClientSecret := &corev1.Secret{
279-
ObjectMeta: metav1.ObjectMeta{
280-
Name: clusterID.InfraID + "-azure-client-secret",
281-
Namespace: capiutils.Namespace,
282-
},
283-
StringData: map[string]string{
284-
"clientSecret": session.Credentials.ClientSecret,
285-
},
286-
}
287-
azureClientSecret.SetGroupVersionKind(corev1.SchemeGroupVersion.WithKind("Secret"))
288-
manifests = append(manifests, &asset.RuntimeFile{
289-
Object: azureClientSecret,
290-
File: asset.File{Filename: "01_azure-client-secret.yaml"},
291-
})
292-
293278
id := &capz.AzureClusterIdentity{
294279
ObjectMeta: metav1.ObjectMeta{
295280
Name: clusterID.InfraID,
296281
Namespace: capiutils.Namespace,
297282
},
298283
Spec: capz.AzureClusterIdentitySpec{
299-
Type: capz.ServicePrincipal,
300284
AllowedNamespaces: &capz.AllowedNamespaces{}, // Allow all namespaces.
301285
ClientID: session.Credentials.ClientID,
302-
ClientSecret: corev1.SecretReference{
303-
Name: azureClientSecret.Name,
304-
Namespace: azureClientSecret.Namespace,
305-
},
306-
TenantID: session.Credentials.TenantID,
286+
TenantID: session.Credentials.TenantID,
307287
},
308288
}
309-
if session.AuthType == azic.ManagedIdentityAuth {
289+
290+
switch session.AuthType {
291+
case azic.ManagedIdentityAuth:
310292
id.Spec.Type = capz.UserAssignedMSI
311-
id.Spec.ClientSecret = corev1.SecretReference{}
293+
case azic.ClientSecretAuth:
294+
id.Spec.Type = capz.ServicePrincipal
295+
azureClientSecret := &corev1.Secret{
296+
ObjectMeta: metav1.ObjectMeta{
297+
Name: clusterID.InfraID + "-azure-client-secret",
298+
Namespace: capiutils.Namespace,
299+
},
300+
StringData: map[string]string{
301+
"clientSecret": session.Credentials.ClientSecret,
302+
},
303+
}
304+
azureClientSecret.SetGroupVersionKind(corev1.SchemeGroupVersion.WithKind("Secret"))
305+
manifests = append(manifests, &asset.RuntimeFile{
306+
Object: azureClientSecret,
307+
File: asset.File{Filename: "01_azure-client-secret.yaml"},
308+
})
309+
310+
id.Spec.ClientSecret = corev1.SecretReference{
311+
Name: azureClientSecret.Name,
312+
Namespace: azureClientSecret.Namespace,
313+
}
314+
case azic.ClientCertificateAuth:
315+
id.Spec.Type = capz.ServicePrincipalCertificate
316+
id.Spec.CertPath = session.Credentials.ClientCertificatePath
312317
}
318+
313319
id.SetGroupVersionKind(capz.GroupVersion.WithKind("AzureClusterIdentity"))
314320
manifests = append(manifests, &asset.RuntimeFile{
315321
Object: id,

pkg/clusterapi/system.go

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ package clusterapi
33
import (
44
"bytes"
55
"context"
6+
"crypto/x509"
67
"encoding/json"
8+
"encoding/pem"
79
"fmt"
810
"io"
911
"net"
@@ -15,6 +17,7 @@ import (
1517
"text/template"
1618
"time"
1719

20+
"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
1821
"github.com/pkg/errors"
1922
"github.com/sirupsen/logrus"
2023
"sigs.k8s.io/controller-runtime/pkg/client"
@@ -231,6 +234,28 @@ func (c *system) Run(ctx context.Context) error { //nolint:gocyclo
231234
}
232235
}
233236

237+
// ASO expects the contents of the cert--not the path--in the env var.
238+
// Since .pfx is a binary format, we need to parse it and convert to PEM format.
239+
var certPEM string
240+
if session.AuthType == azic.ClientCertificateAuth {
241+
certPath := session.Credentials.ClientCertificatePath
242+
certData, err := os.ReadFile(certPath)
243+
if err != nil {
244+
return fmt.Errorf("unable to read client certificate contents from %s: %w", certPath, err)
245+
}
246+
247+
// Parse the .pfx file to get certificates and private key
248+
certs, key, err := azidentity.ParseCertificates(certData, []byte(session.Credentials.ClientCertificatePassword))
249+
if err != nil {
250+
return fmt.Errorf("failed to parse client certificate: %w", err)
251+
}
252+
253+
// Convert certificates and key to PEM format
254+
certPEM, err = certificatesToPEM(certs, key)
255+
if err != nil {
256+
return fmt.Errorf("failed to convert certificate to PEM: %w", err)
257+
}
258+
}
234259
controllers = append(controllers,
235260
c.getInfrastructureController(
236261
&azProvider,
@@ -259,7 +284,7 @@ func (c *system) Run(ctx context.Context) error { //nolint:gocyclo
259284
"POD_NAMESPACE": "capz-system",
260285
"AZURE_CLIENT_ID": session.Credentials.ClientID,
261286
"AZURE_CLIENT_SECRET": session.Credentials.ClientSecret,
262-
"AZURE_CLIENT_CERTIFICATE": session.Credentials.ClientCertificatePath,
287+
"AZURE_CLIENT_CERTIFICATE": certPEM,
263288
"AZURE_CLIENT_CERTIFICATE_PASSWORD": session.Credentials.ClientCertificatePassword,
264289
"AZURE_TENANT_ID": session.Credentials.TenantID,
265290
"AZURE_SUBSCRIPTION_ID": session.Credentials.SubscriptionID,
@@ -685,3 +710,34 @@ func (c *system) runController(ctx context.Context, ct *controller) error {
685710
ct.state = pr
686711
return nil
687712
}
713+
714+
// certificatesToPEM converts x509 certificates and a private key to PEM format.
715+
// The output is a concatenated string of PEM-encoded certificates followed by the PEM-encoded private key.
716+
func certificatesToPEM(certs []*x509.Certificate, key any) (string, error) {
717+
var pemData strings.Builder
718+
719+
// Encode each certificate
720+
for _, cert := range certs {
721+
if err := pem.Encode(&pemData, &pem.Block{
722+
Type: "CERTIFICATE",
723+
Bytes: cert.Raw,
724+
}); err != nil {
725+
return "", fmt.Errorf("failed to encode certificate: %w", err)
726+
}
727+
}
728+
729+
// Encode the private key
730+
keyBytes, err := x509.MarshalPKCS8PrivateKey(key)
731+
if err != nil {
732+
return "", fmt.Errorf("failed to marshal private key: %w", err)
733+
}
734+
735+
if err := pem.Encode(&pemData, &pem.Block{
736+
Type: "PRIVATE KEY",
737+
Bytes: keyBytes,
738+
}); err != nil {
739+
return "", fmt.Errorf("failed to encode private key: %w", err)
740+
}
741+
742+
return pemData.String(), nil
743+
}

0 commit comments

Comments
 (0)