Skip to content

Commit

Permalink
Add DynamicBackendMode
Browse files Browse the repository at this point in the history
  • Loading branch information
nnmin-aws committed Jul 25, 2023
1 parent 0f53431 commit 2cada2e
Show file tree
Hide file tree
Showing 11 changed files with 506 additions and 231 deletions.
2 changes: 2 additions & 0 deletions cmd/aws-iam-authenticator/root.go
Expand Up @@ -108,6 +108,8 @@ func getConfig() (config.Config, error) {
DynamicFilePath: viper.GetString("server.dynamicfilepath"),
//DynamicFileUserIDStrict: if true, then aws UserId from sts will be used to look up the roleMapping/userMapping; or aws IdentityArn is used
DynamicFileUserIDStrict: viper.GetBool("server.dynamicfileUserIDStrict"),
//DynamicBackendModePath: the file path containing the backend mode
DynamicBackendModePath: viper.GetString("server.dynamicBackendModePath"),
}
if err := viper.UnmarshalKey("server.mapRoles", &cfg.RoleMappings); err != nil {
return cfg, fmt.Errorf("invalid server role mappings: %v", err)
Expand Down
2 changes: 2 additions & 0 deletions pkg/config/types.go
Expand Up @@ -154,6 +154,8 @@ type Config struct {
DynamicFileUserIDStrict bool
// ReservedPrefixConfig defines reserved username prefixes for each backend
ReservedPrefixConfig map[string]ReservedPrefixConfig
// Dynamic File Path for BackendMode
DynamicBackendModePath string
}

type ReservedPrefixConfig struct {
Expand Down
107 changes: 107 additions & 0 deletions pkg/fileutil/util.go
@@ -0,0 +1,107 @@
package fileutil

import (
"os"
"time"

"github.com/fsnotify/fsnotify"
"github.com/sirupsen/logrus"
"k8s.io/apimachinery/pkg/util/wait"
"sigs.k8s.io/aws-iam-authenticator/pkg/metrics"
)

type FileChangeCallBack interface {
CallBackForFileLoad(dynamicContent []byte) error
CallBackForFileDeletion() error
}

func waitUntilFileAvailable(filename string, stopCh <-chan struct{}) {
if _, err := os.Stat(filename); err == nil {
return
}
ticker := time.NewTicker(1 * time.Second)
defer ticker.Stop()
for {
select {
case <-stopCh:
logrus.Infof("startLoadDynamicFile: waitUntilFileAvailable exit because get stopCh, filename is %s", filename)
return
case <-ticker.C:
if _, err := os.Stat(filename); err == nil {
return
}
}
}
}

func loadDynamicFile(filename string, stopCh <-chan struct{}) ([]byte, error) {
waitUntilFileAvailable(filename, stopCh)
if content, err := os.ReadFile(filename); err == nil {
logrus.Infof("LoadDynamicFile: %v is available. content is %s", filename, string(content))
return content, nil
} else {
return nil, err
}
}

func StartLoadDynamicFile(filename string, callBack FileChangeCallBack, stopCh <-chan struct{}) {
go wait.Until(func() {
// start to watch the file change
watcher, err := fsnotify.NewWatcher()
if err != nil {
logrus.Errorf("startLoadDynamicFile: failed when call fsnotify.NewWatcher, %+v", err)
metrics.Get().DynamicFileFailures.Inc()
return
}
defer watcher.Close()
content, err := loadDynamicFile(filename, stopCh)
if err != nil {
logrus.Errorf("StartLoadDynamicFile: error in loadDynamicFile, %v", err)
return
}
err = watcher.Add(filename)
if err != nil {
logrus.Errorf("startLoadDynamicFile: could not add file to watcher %v", err)
metrics.Get().DynamicFileFailures.Inc()
return
}
if err := callBack.CallBackForFileLoad(content); err != nil {
logrus.Errorf("StartLoadDynamicFile: error in callBackForFileLoad, %v", err)
}
for {
select {
case <-stopCh:
logrus.Infof("startLoadDynamicFile: watching exit because stopCh closed, filename is %s", filename)
return
case event := <-watcher.Events:
switch {
case event.Op&fsnotify.Write == fsnotify.Write, event.Op&fsnotify.Create == fsnotify.Create:
// reload the access entry file
logrus.Info("startLoadDynamicFile: got WRITE/CREATE event reload it the memory")
content, err := loadDynamicFile(filename, stopCh)
if err != nil {
logrus.Errorf("StartLoadDynamicFile: error in loadDynamicFile, %v", err)
return
}
if err := callBack.CallBackForFileLoad(content); err != nil {
logrus.Errorf("StartLoadDynamicFile: error in callBackForFileLoad, %v", err)
}
case event.Op&fsnotify.Rename == fsnotify.Rename, event.Op&fsnotify.Remove == fsnotify.Remove:
logrus.Info("startLoadDynamicFile: got RENAME/REMOVE event")
// test if the "REMOVE" is triggered by vi or cp cmd
_, err := os.Stat(filename)
if os.IsNotExist(err) {
if err := callBack.CallBackForFileDeletion(); err != nil {
logrus.Errorf("StartLoadDynamicFile: error in callBackForFileDeletion, %v", err)
}
}
return
}
case err := <-watcher.Errors:
logrus.Errorf("startLoadDynamicFile: watcher.Errors for dynamic file %v", err)
metrics.Get().DynamicFileFailures.Inc()
return
}
}
}, time.Second, stopCh)
}
105 changes: 105 additions & 0 deletions pkg/fileutil/util_test.go
@@ -0,0 +1,105 @@
package fileutil

import (
"os"
"sync"
"testing"
"time"
)

var origFileContent = `
Abbatxt7095
`

var updatedFileContent = `
efgwht2033
`

type testStruct struct {
content string
expectedContent string
mutex sync.Mutex
}

func (a *testStruct) CallBackForFileLoad(dynamicContent []byte) error {
a.mutex.Lock()
a.expectedContent = string(dynamicContent)
defer a.mutex.Unlock()
return nil
}

func (a *testStruct) CallBackForFileDeletion() error {
a.mutex.Lock()
a.expectedContent = ""
defer a.mutex.Unlock()
return nil
}

func TestLoadDynamicFile(t *testing.T) {
cases := []struct {
input string
want string
}{
{
"abcde",
"abcde",
},
{
"fghijk",
"fghijk",
},
{
"xyzopq",
"xyzopq",
},
{
"eks:test",
"eks:test",
},
}
stopCh := make(chan struct{})
testA := &testStruct{}
StartLoadDynamicFile("/tmp/util_test.txt", testA, stopCh)
defer close(stopCh)
time.Sleep(2 * time.Second)
os.WriteFile("/tmp/util_test.txt", []byte("test"), 0777)
for _, c := range cases {
updateFile(testA, c.input, t)
testA.mutex.Lock()
if testA.expectedContent != c.want {
t.Errorf(
"Unexpected result: TestLoadDynamicFile: got: %s, wanted %s",
testA.expectedContent,
c.want,
)
}
testA.mutex.Unlock()
}
}

func updateFile(testA *testStruct, origFileContent string, t *testing.T) {
testA.content = origFileContent
data := []byte(origFileContent)
err := os.WriteFile("/tmp/util_test.txt", data, 0600)
if err != nil {
t.Errorf("failed to create a local file /tmp/util_test.txt")
}
time.Sleep(1 * time.Second)
}

func TestDeleteDynamicFile(t *testing.T) {
stopCh := make(chan struct{})
testA := &testStruct{}
StartLoadDynamicFile("/tmp/delete.txt", testA, stopCh)
defer close(stopCh)
time.Sleep(2 * time.Second)
os.WriteFile("/tmp/delete.txt", []byte("test"), 0777)
time.Sleep(2 * time.Second)
os.Remove("/tmp/delete.txt")
time.Sleep(2 * time.Second)
testA.mutex.Lock()
if testA.expectedContent != "" {
t.Errorf("failed in TestDeleteDynamicFile")
}
testA.mutex.Unlock()
}
2 changes: 2 additions & 0 deletions pkg/mapper/configmap/configmap.go
Expand Up @@ -52,9 +52,11 @@ func New(masterURL, kubeConfig string) (*MapStore, error) {
// when the values change.
func (ms *MapStore) startLoadConfigMap(stopCh <-chan struct{}) {
go func() {
logrus.Info("startLoadConfigMap in EKSConfigMap")
for {
select {
case <-stopCh:
logrus.Info("stopCh is closed in startLoadConfigMap")
return
default:
watcher, err := ms.configMap.Watch(context.TODO(), metav1.ListOptions{
Expand Down

0 comments on commit 2cada2e

Please sign in to comment.