From ba581063960cd03d484e4bb7545c76498a16a464 Mon Sep 17 00:00:00 2001 From: Bryan Cox Date: Sat, 19 Oct 2024 13:06:31 -0400 Subject: [PATCH] Use Client Cert Auth for ARO HCP deployments Use Client Certificate Authentication for ARO HCP deployments. HyperShift will pass the needed environment variables for this authentication method: ARO_HCP_MI_CLIENT_ID, ARO_HCP_TENANT_ID, and ARO_HCP_CLIENT_CERTIFICATE_PATH. Signed-off-by: Bryan Cox --- go.mod | 2 +- pkg/cloudprovider/azure.go | 33 ++++++++++++++--- pkg/filewatcher/filewatcher.go | 68 ++++++++++++++++++++++++++++++++++ 3 files changed, 96 insertions(+), 7 deletions(-) create mode 100644 pkg/filewatcher/filewatcher.go diff --git a/go.mod b/go.mod index 2b043e983..035e92a38 100644 --- a/go.mod +++ b/go.mod @@ -10,6 +10,7 @@ require ( github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.0 github.com/Azure/go-autorest/autorest v0.11.27 github.com/aws/aws-sdk-go v1.37.8 + github.com/fsnotify/fsnotify v1.7.0 github.com/google/uuid v1.6.0 github.com/gophercloud/gophercloud v0.25.1-0.20220718160629-0721d75e876f github.com/gophercloud/utils v0.0.0-20220307143606-8e7800759d16 @@ -52,7 +53,6 @@ require ( github.com/evanphx/json-patch v5.9.0+incompatible // indirect github.com/evanphx/json-patch/v5 v5.9.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect - github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/fxamacker/cbor/v2 v2.7.0 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect diff --git a/pkg/cloudprovider/azure.go b/pkg/cloudprovider/azure.go index 0f005d47a..6e36857e9 100644 --- a/pkg/cloudprovider/azure.go +++ b/pkg/cloudprovider/azure.go @@ -20,6 +20,7 @@ import ( "github.com/jongio/azidext/go/azidext" v1 "github.com/openshift/api/cloudnetwork/v1" configv1 "github.com/openshift/api/config/v1" + "github.com/openshift/cloud-network-config-controller/pkg/filewatcher" corev1 "k8s.io/api/core/v1" "k8s.io/klog/v2" utilnet "k8s.io/utils/net" @@ -586,17 +587,37 @@ func (a *Azure) getAuthorizer(env azureapi.Environment, cfg *azureCredentialsCon err error ) - // MSI Override for ARO HCP - msi := os.Getenv("AZURE_MSI_AUTHENTICATION") - if msi == "true" { - options := azidentity.ManagedIdentityCredentialOptions{ + // Managed Identity Override for ARO HCP + managedIdentityClientID := os.Getenv("ARO_HCP_MI_CLIENT_ID") + if managedIdentityClientID != "" { + klog.Info("Using client certification Azure authentication for ARO HCP") + options := &azidentity.ClientCertificateCredentialOptions{ ClientOptions: azcore.ClientOptions{ Cloud: cloudConfig, }, + SendCertificateChain: true, } - var err error - cred, err = azidentity.NewManagedIdentityCredential(&options) + tenantID := os.Getenv("ARO_HCP_TENANT_ID") + certPath := os.Getenv("ARO_HCP_CLIENT_CERTIFICATE_PATH") + + certData, err := os.ReadFile(certPath) + if err != nil { + return nil, fmt.Errorf(`failed to read certificate file "%s": %v`, certPath, err) + } + + certs, key, err := azidentity.ParseCertificates(certData, []byte{}) + if err != nil { + return nil, fmt.Errorf(`failed to parse certificate data "%s": %v`, certPath, err) + } + + // Watch the certificate for changes; if the certificate changes, the pod will be restarted + err = filewatcher.WatchFileForChanges(certPath) + if err != nil { + return nil, err + } + + cred, err = azidentity.NewClientCertificateCredential(tenantID, managedIdentityClientID, certs, key, options) if err != nil { return nil, err } diff --git a/pkg/filewatcher/filewatcher.go b/pkg/filewatcher/filewatcher.go new file mode 100644 index 000000000..422eb0e42 --- /dev/null +++ b/pkg/filewatcher/filewatcher.go @@ -0,0 +1,68 @@ +package filewatcher + +import ( + "os" + "path/filepath" + "sync" + + "github.com/fsnotify/fsnotify" + "k8s.io/klog/v2" +) + +var watchCertificateFileOnce sync.Once + +// WatchFileForChanges watches the file, fileToWatch, for changes. If the file contents have changed, the pod this +// function is running on will be restarted. +func WatchFileForChanges(fileToWatch string) error { + var err error + + // This starts only one occurrence of the file watcher, which watches the file, fileToWatch. + watchCertificateFileOnce.Do(func() { + klog.Infof("Starting the file change watcher on file, %s", fileToWatch) + + // Update the file path to watch in case this is a symlink + fileToWatch, err = filepath.EvalSymlinks(fileToWatch) + if err != nil { + return + } + klog.Infof("Watching file, %s", fileToWatch) + + // Start the file watcher to monitor file changes + go func() { + err := checkForFileChanges(fileToWatch) + klog.Errorf("Error checking for file changes: %v", err) + }() + }) + return err +} + +// checkForFileChanges starts a new file watcher. If the file is changed, the pod running this function will exit. +func checkForFileChanges(path string) error { + watcher, err := fsnotify.NewWatcher() + if err != nil { + return err + } + + go func() { + for { + select { + case event, ok := <-watcher.Events: + if ok && (event.Has(fsnotify.Write) || event.Has(fsnotify.Chmod) || event.Has(fsnotify.Remove)) { + klog.Infof("file, %s, was modified, exiting...", event.Name) + os.Exit(0) + } + case err, ok := <-watcher.Errors: + if ok { + klog.Errorf("file watcher error: %v", err) + } + } + } + }() + + err = watcher.Add(path) + if err != nil { + return err + } + + return nil +}