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