1package configmap 2 3import ( 4 "encoding/json" 5 "errors" 6 "fmt" 7 "strings" 8 "sync" 9 "time" 10 11 "github.com/sirupsen/logrus" 12 "gopkg.in/yaml.v2" 13 core_v1 "k8s.io/api/core/v1" 14 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 15 "k8s.io/apimachinery/pkg/fields" 16 utilyaml "k8s.io/apimachinery/pkg/util/yaml" 17 "k8s.io/apimachinery/pkg/watch" 18 "k8s.io/client-go/kubernetes" 19 "k8s.io/client-go/kubernetes/typed/core/v1" 20 "k8s.io/client-go/tools/clientcmd" 21 "sigs.k8s.io/aws-iam-authenticator/pkg/config" 22) 23 24type MapStore struct { 25 mutex sync.RWMutex 26 users map[string]config.UserMapping 27 roles map[string]config.RoleMapping 28 // Used as set. 29 awsAccounts map[string]interface{} 30 configMap v1.ConfigMapInterface 31} 32 33func New(masterURL, kubeConfig string) (*MapStore, error) { 34 clientconfig, err := clientcmd.BuildConfigFromFlags(masterURL, kubeConfig) 35 if err != nil { 36 return nil, err 37 } 38 clientset, err := kubernetes.NewForConfig(clientconfig) 39 if err != nil { 40 return nil, err 41 } 42 43 ms := MapStore{} 44 ms.configMap = clientset.CoreV1().ConfigMaps("kube-system") 45 return &ms, nil 46} 47 48// Starts a go routine which will watch the configmap and update the in memory data 49// when the values change. 50func (ms *MapStore) startLoadConfigMap(stopCh <-chan struct{}) { 51 go func() { 52 for { 53 select { 54 case <-stopCh: 55 return 56 default: 57 watcher, err := ms.configMap.Watch(metav1.ListOptions{ 58 Watch: true, 59 FieldSelector: fields.OneTermEqualSelector("metadata.name", "aws-auth").String(), 60 }) 61 if err != nil { 62 logrus.Warn("Unable to re-establish watch. Sleeping for 5 seconds") 63 time.Sleep(5 * time.Second) 64 continue 65 } 66 for r := range watcher.ResultChan() { 67 switch r.Type { 68 case watch.Error: 69 logrus.WithFields(logrus.Fields{"error": r}).Error("recieved a watch error") 70 case watch.Deleted: 71 logrus.Info("Resetting configmap on delete") 72 userMappings := make([]config.UserMapping, 0) 73 roleMappings := make([]config.RoleMapping, 0) 74 awsAccounts := make([]string, 0) 75 ms.saveMap(userMappings, roleMappings, awsAccounts) 76 case watch.Added, watch.Modified: 77 switch cm := r.Object.(type) { 78 case *core_v1.ConfigMap: 79 if cm.Name != "aws-auth" { 80 break 81 } 82 logrus.Info("Received aws-auth watch event") 83 userMappings, roleMappings, awsAccounts, err := ms.parseMap(cm.Data) 84 if err != nil { 85 logrus.Errorf("There was an error parsing the config maps. Only saving data that was good, %+v", err) 86 } 87 ms.saveMap(userMappings, roleMappings, awsAccounts) 88 if err != nil { 89 logrus.Error(err) 90 } 91 } 92 93 } 94 } 95 logrus.Error("Watch channel closed.") 96 } 97 } 98 }() 99} 100 101type ErrParsingMap struct { 102 errors []error 103} 104 105func (err ErrParsingMap) Error() string { 106 return fmt.Sprintf("error parsing config map: %v", err.errors) 107} 108 109// Acquire lock before calling 110func (ms *MapStore) parseMap(m map[string]string) ([]config.UserMapping, []config.RoleMapping, []string, error) { 111 errs := make([]error, 0) 112 userMappings := make([]config.UserMapping, 0) 113 if userData, ok := m["mapUsers"]; ok { 114 userJson, err := utilyaml.ToJSON([]byte(userData)) 115 if err != nil { 116 errs = append(errs, err) 117 } else { 118 err = json.Unmarshal(userJson, &userMappings) 119 if err != nil { 120 errs = append(errs, err) 121 } 122 } 123 } 124 125 roleMappings := make([]config.RoleMapping, 0) 126 if roleData, ok := m["mapRoles"]; ok { 127 roleJson, err := utilyaml.ToJSON([]byte(roleData)) 128 if err != nil { 129 errs = append(errs, err) 130 } else { 131 err = json.Unmarshal(roleJson, &roleMappings) 132 if err != nil { 133 errs = append(errs, err) 134 } 135 } 136 } 137 138 awsAccounts := make([]string, 0) 139 if accountsData, ok := m["mapAccounts"]; ok { 140 err := yaml.Unmarshal([]byte(accountsData), &awsAccounts) 141 if err != nil { 142 errs = append(errs, err) 143 } 144 } 145 146 var err error 147 if len(errs) > 0 { 148 logrus.Warnf("Errors parsing configmap: %+v", errs) 149 err = ErrParsingMap{errors: errs} 150 } 151 return userMappings, roleMappings, awsAccounts, err 152} 153 154func (ms *MapStore) saveMap(userMappings []config.UserMapping, roleMappings []config.RoleMapping, awsAccounts []string) { 155 ms.mutex.Lock() 156 defer ms.mutex.Unlock() 157 ms.users = make(map[string]config.UserMapping) 158 ms.roles = make(map[string]config.RoleMapping) 159 ms.awsAccounts = make(map[string]interface{}) 160 161 for _, user := range userMappings { 162 ms.users[strings.ToLower(user.UserARN)] = user 163 } 164 for _, role := range roleMappings { 165 ms.roles[strings.ToLower(role.RoleARN)] = role 166 } 167 for _, awsAccount := range awsAccounts { 168 ms.awsAccounts[awsAccount] = nil 169 } 170} 171 172// UserNotFound is the error returned when the user is not found in the config map. 173var UserNotFound = errors.New("User not found in configmap") 174 175// RoleNotFound is the error returned when the role is not found in the config map. 176var RoleNotFound = errors.New("Role not found in configmap") 177 178func (ms *MapStore) UserMapping(arn string) (config.UserMapping, error) { 179 ms.mutex.RLock() 180 defer ms.mutex.RUnlock() 181 if user, ok := ms.users[arn]; !ok { 182 return config.UserMapping{}, UserNotFound 183 } else { 184 return user, nil 185 } 186} 187 188func (ms *MapStore) RoleMapping(arn string) (config.RoleMapping, error) { 189 ms.mutex.RLock() 190 defer ms.mutex.RUnlock() 191 if role, ok := ms.roles[arn]; !ok { 192 return config.RoleMapping{}, RoleNotFound 193 } else { 194 return role, nil 195 } 196} 197 198func (ms *MapStore) AWSAccount(id string) bool { 199 ms.mutex.RLock() 200 defer ms.mutex.RUnlock() 201 _, ok := ms.awsAccounts[id] 202 return ok 203} 204