2
0

authconfigmap.go 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311
  1. package aws
  2. // Copy of eksctl that uses authconfigmap
  3. // https://github.com/weaveworks/eksctl/blob/35d0d6dc169e13b1fb655aea19d3aa2e7691dc81/pkg/authconfigmap/authconfigmap.go#L1
  4. //
  5. // eksctl is still pinned on v0.16.8 of Kubernetes, while we use v0.18.8
  6. import (
  7. "context"
  8. "fmt"
  9. "strings"
  10. "github.com/kris-nova/logger"
  11. "github.com/pkg/errors"
  12. "k8s.io/client-go/kubernetes"
  13. "sigs.k8s.io/yaml"
  14. corev1 "k8s.io/api/core/v1"
  15. apierrors "k8s.io/apimachinery/pkg/api/errors"
  16. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  17. v1 "k8s.io/client-go/kubernetes/typed/core/v1"
  18. "github.com/aws/aws-sdk-go/aws/arn"
  19. "github.com/aws/aws-sdk-go/aws/awsutil"
  20. )
  21. const (
  22. // ObjectName is the Kubernetes resource name of the auth ConfigMap
  23. ObjectName = "aws-auth"
  24. // ObjectNamespace is the namespace the object can be found
  25. ObjectNamespace = metav1.NamespaceSystem
  26. rolesData = "mapRoles"
  27. usersData = "mapUsers"
  28. accountsData = "mapAccounts"
  29. // GroupMasters is the admin group which is also automatically
  30. // granted to the IAM role that creates the cluster.
  31. GroupMasters = "system:masters"
  32. // RoleNodeGroupUsername is the default username for a nodegroup
  33. // role mapping.
  34. RoleNodeGroupUsername = "system:node:{{EC2PrivateDNSName}}"
  35. )
  36. // AuthConfigMap allows modifying the auth ConfigMap.
  37. type AuthConfigMap struct {
  38. client v1.ConfigMapInterface
  39. cm *corev1.ConfigMap
  40. }
  41. // New creates an AuthConfigMap instance that manipulates
  42. // a ConfigMap. If it is nil, one is created.
  43. func New(client v1.ConfigMapInterface, cm *corev1.ConfigMap) *AuthConfigMap {
  44. if cm == nil {
  45. cm = &corev1.ConfigMap{
  46. ObjectMeta: ObjectMeta(),
  47. Data: map[string]string{},
  48. }
  49. }
  50. if cm.Data == nil {
  51. cm.ObjectMeta = ObjectMeta()
  52. cm.Data = map[string]string{}
  53. }
  54. return &AuthConfigMap{client: client, cm: cm}
  55. }
  56. // ObjectMeta constructs metadata for the ConfigMap.
  57. func ObjectMeta() metav1.ObjectMeta {
  58. return metav1.ObjectMeta{
  59. Name: ObjectName,
  60. Namespace: ObjectNamespace,
  61. }
  62. }
  63. // NewFromClientSet fetches the auth ConfigMap.
  64. func NewFromClientSet(clientSet kubernetes.Interface) (*AuthConfigMap, error) {
  65. client := clientSet.CoreV1().ConfigMaps(ObjectNamespace)
  66. cm, err := client.Get(context.TODO(), ObjectName, metav1.GetOptions{})
  67. // It is fine for the configmap not to exist. Any other error is fatal.
  68. if err != nil && !apierrors.IsNotFound(err) {
  69. return nil, errors.Wrapf(err, "getting auth ConfigMap")
  70. }
  71. logger.Debug("aws-auth = %s", awsutil.Prettify(cm))
  72. return New(client, cm), nil
  73. }
  74. // AddIdentity maps an IAM role or user ARN to a k8s group dynamically. It modifies the
  75. // role or user with given groups. If you are calling
  76. // this as part of node creation you should use DefaultNodeGroups.
  77. func (a *AuthConfigMap) AddIdentity(identity Identity) error {
  78. identities, err := a.Identities()
  79. if err != nil {
  80. return err
  81. }
  82. identities = append(identities, identity)
  83. logger.Info("adding identity %q to auth ConfigMap", identity.ARN())
  84. return a.setIdentities(identities)
  85. }
  86. // Identities returns a list of iam users and roles that are currently in the (cached) configmap.
  87. func (a *AuthConfigMap) Identities() ([]Identity, error) {
  88. var roles []RoleIdentity
  89. if err := yaml.Unmarshal([]byte(a.cm.Data[rolesData]), &roles); err != nil {
  90. return nil, errors.Wrapf(err, "unmarshalling %q", rolesData)
  91. }
  92. var users []UserIdentity
  93. if err := yaml.Unmarshal([]byte(a.cm.Data[usersData]), &users); err != nil {
  94. return nil, errors.Wrapf(err, "unmarshalling %q", usersData)
  95. }
  96. all := make([]Identity, len(users)+len(roles))
  97. for i, r := range roles {
  98. all[i] = r
  99. }
  100. for i, u := range users {
  101. all[i+len(roles)] = u
  102. }
  103. return all, nil
  104. }
  105. func (a *AuthConfigMap) setIdentities(identities []Identity) error {
  106. // Split identities into list of roles and list of users
  107. users, roles := []Identity{}, []Identity{}
  108. for _, identity := range identities {
  109. switch identity.Type() {
  110. case ResourceTypeRole:
  111. roles = append(roles, identity)
  112. case ResourceTypeUser:
  113. users = append(users, identity)
  114. default:
  115. return errors.Errorf("cannot determine if %q refers to a user or role during setIdentities preprocessing", identity.ARN())
  116. }
  117. }
  118. // Update the corresponding keys
  119. _roles, err := yaml.Marshal(roles)
  120. if err != nil {
  121. return errors.Wrapf(err, "marshalling %q", rolesData)
  122. }
  123. a.cm.Data[rolesData] = string(_roles)
  124. _users, err := yaml.Marshal(users)
  125. if err != nil {
  126. return errors.Wrapf(err, "marshalling %q", usersData)
  127. }
  128. a.cm.Data[usersData] = string(_users)
  129. return nil
  130. }
  131. // Save persists the ConfigMap to the cluster. It determines
  132. // whether to create or update by looking at the ConfigMap's UID.
  133. func (a *AuthConfigMap) Save() (err error) {
  134. if a.cm.UID == "" {
  135. a.cm, err = a.client.Create(context.TODO(), a.cm, metav1.CreateOptions{})
  136. return err
  137. }
  138. a.cm, err = a.client.Update(context.TODO(), a.cm, metav1.UpdateOptions{})
  139. return err
  140. }
  141. var (
  142. // ErrNeitherUserNorRole is the error returned when an identity is missing both UserARN
  143. // and RoleARN.
  144. ErrNeitherUserNorRole = errors.New("arn is neither user nor role")
  145. // ErrNoKubernetesIdentity is the error returned when an identity has neither a Kubernetes
  146. // username nor a list of groups.
  147. ErrNoKubernetesIdentity = errors.New("neither username nor group are set for iam identity")
  148. )
  149. // Identity represents an IAM identity and its corresponding Kubernetes identity
  150. type Identity interface {
  151. ARN() string
  152. Type() string
  153. Username() string
  154. Groups() []string
  155. }
  156. // KubernetesIdentity represents a kubernetes identity to be used in iam mappings
  157. type KubernetesIdentity struct {
  158. KubernetesUsername string `json:"username,omitempty"`
  159. KubernetesGroups []string `json:"groups,omitempty"`
  160. }
  161. // UserIdentity represents a mapping from an IAM user to a kubernetes identity
  162. type UserIdentity struct {
  163. UserARN string `json:"userarn,omitempty"`
  164. KubernetesIdentity
  165. }
  166. // RoleIdentity represents a mapping from an IAM role to a kubernetes identity
  167. type RoleIdentity struct {
  168. RoleARN string `json:"rolearn,omitempty"`
  169. KubernetesIdentity
  170. }
  171. // Username returns the Kubernetes username
  172. func (k KubernetesIdentity) Username() string {
  173. return k.KubernetesUsername
  174. }
  175. // Groups returns the Kubernetes groups
  176. func (k KubernetesIdentity) Groups() []string {
  177. return k.KubernetesGroups
  178. }
  179. // ARN returns the ARN of the iam mapping
  180. func (u UserIdentity) ARN() string {
  181. return u.UserARN
  182. }
  183. // Type returns the resource type of the iam mapping
  184. func (u UserIdentity) Type() string {
  185. return ResourceTypeUser
  186. }
  187. // ARN returns the ARN of the iam mapping
  188. func (r RoleIdentity) ARN() string {
  189. return r.RoleARN
  190. }
  191. // Type returns the resource type of the iam mapping
  192. func (r RoleIdentity) Type() string {
  193. return ResourceTypeRole
  194. }
  195. // NewIdentity determines into which field the given arn goes and returns the new identity
  196. // alongside any error resulting for checking its validity.
  197. func NewIdentity(arn string, username string, groups []string) (Identity, error) {
  198. if arn == "" {
  199. return nil, fmt.Errorf("expected a valid arn but got empty string")
  200. }
  201. if username == "" && len(groups) == 0 {
  202. return nil, ErrNoKubernetesIdentity
  203. }
  204. parsedARN, err := Parse(arn)
  205. if err != nil {
  206. return nil, err
  207. }
  208. switch {
  209. case parsedARN.IsUser():
  210. return &UserIdentity{
  211. UserARN: arn,
  212. KubernetesIdentity: KubernetesIdentity{
  213. KubernetesUsername: username,
  214. KubernetesGroups: groups,
  215. },
  216. }, nil
  217. case parsedARN.IsRole():
  218. return &RoleIdentity{
  219. RoleARN: arn,
  220. KubernetesIdentity: KubernetesIdentity{
  221. KubernetesUsername: username,
  222. KubernetesGroups: groups,
  223. },
  224. }, nil
  225. default:
  226. return nil, ErrNeitherUserNorRole
  227. }
  228. }
  229. const (
  230. // ResourceTypeRole is the resource type of the role ARN
  231. ResourceTypeRole = "role"
  232. // ResourceTypeUser is the resource type of the user ARN
  233. ResourceTypeUser = "user"
  234. )
  235. // ARN implements the pflag.Value interface for aws-sdk-go/aws/arn.ARN
  236. type ARN struct {
  237. arn.ARN
  238. }
  239. // Parse wraps the aws-sdk-go/aws/arn.Parse function and instead returns a
  240. // iam.ARN
  241. func Parse(s string) (ARN, error) {
  242. a, err := arn.Parse(s)
  243. return ARN{a}, err
  244. }
  245. // ResourceType returns the type of the resource specified in the ARN.
  246. // Typically, in the case of IAM, it is a role or a user
  247. func (a *ARN) ResourceType() string {
  248. t := a.Resource
  249. if idx := strings.Index(t, "/"); idx >= 0 {
  250. t = t[:idx] // remove everything following the forward slash
  251. }
  252. return t
  253. }
  254. // IsUser returns whether the arn represents a IAM user or not
  255. func (a *ARN) IsUser() bool {
  256. return a.ResourceType() == ResourceTypeUser
  257. }
  258. // IsRole returns whether the arn represents a IAM role or not
  259. func (a *ARN) IsRole() bool {
  260. return a.ResourceType() == ResourceTypeRole
  261. }