Skip to content

Commit

Permalink
Add a file watcher for Azure client cert auth
Browse files Browse the repository at this point in the history
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 <[email protected]>
  • Loading branch information
bryan-cox committed Nov 11, 2024
1 parent d1899f8 commit e5a9fa2
Show file tree
Hide file tree
Showing 2 changed files with 75 additions and 0 deletions.
8 changes: 8 additions & 0 deletions azure/scope/identity.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"context"
"os"
"reflect"
"sigs.k8s.io/cluster-api-provider-azure/util/filewatcher"

"github.com/Azure/azure-sdk-for-go/sdk/azcore"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/cloud"
Expand Down Expand Up @@ -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,
Expand Down
67 changes: 67 additions & 0 deletions util/filewatcher/filewatcher.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
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)
}()
})
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
}

0 comments on commit e5a9fa2

Please sign in to comment.