From 97352d2dca077a0487272987d929acca3984546c Mon Sep 17 00:00:00 2001 From: Bryan Cox Date: Mon, 11 Nov 2024 12:15:50 -0500 Subject: [PATCH] Add a file watcher for Azure client cert auth Add a file watcher, utilizing fsnotify, to monitor the Azure client certificate authentication. If the file changes, the pod will need to be restarted since Azure SDK doesn't support hotloading a new certificate yet. Signed-off-by: Bryan Cox --- azure/scope/identity.go | 8 ++++ go.mod | 2 +- util/filewatcher/filewatcher.go | 84 +++++++++++++++++++++++++++++++++ 3 files changed, 93 insertions(+), 1 deletion(-) create mode 100644 util/filewatcher/filewatcher.go diff --git a/azure/scope/identity.go b/azure/scope/identity.go index 194cc2465b8..862581ba252 100644 --- a/azure/scope/identity.go +++ b/azure/scope/identity.go @@ -33,6 +33,7 @@ import ( infrav1 "sigs.k8s.io/cluster-api-provider-azure/api/v1beta1" "sigs.k8s.io/cluster-api-provider-azure/pkg/ot" + "sigs.k8s.io/cluster-api-provider-azure/util/filewatcher" "sigs.k8s.io/cluster-api-provider-azure/util/tele" ) @@ -145,6 +146,13 @@ func (p *AzureCredentialsProvider) GetTokenCredential(ctx context.Context, resou if err != nil { return nil, errors.Wrap(err, "failed to parse certificate data") } + + // Watch the certificate for changes; if the certificate changes, the pod will be restarted + err = filewatcher.WatchFileForChanges(p.Identity.Spec.CertPath) + if err != nil { + return nil, errors.Wrap(err, "failed to watch certificate file") + } + cred, authErr = azidentity.NewClientCertificateCredential(p.GetTenantID(), p.Identity.Spec.ClientID, certs, key, &azidentity.ClientCertificateCredentialOptions{ ClientOptions: azcore.ClientOptions{ TracingProvider: tracingProvider, diff --git a/go.mod b/go.mod index 924d75a9d6f..365306603ea 100644 --- a/go.mod +++ b/go.mod @@ -122,7 +122,7 @@ require ( github.com/evanphx/json-patch/v5 v5.9.0 github.com/fatih/camelcase v1.0.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect - github.com/fsnotify/fsnotify v1.7.0 // indirect + github.com/fsnotify/fsnotify v1.7.0 github.com/go-errors/errors v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-openapi/jsonpointer v0.21.0 // indirect diff --git a/util/filewatcher/filewatcher.go b/util/filewatcher/filewatcher.go new file mode 100644 index 00000000000..7d576977176 --- /dev/null +++ b/util/filewatcher/filewatcher.go @@ -0,0 +1,84 @@ +/* +Copyright 2023 The Kubernetes Authors. + +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 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.V(2).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.V(2).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 +}