diff --git a/integration/common_test.go b/integration/common_test.go index 85c624b2d9..4254acb1e6 100644 --- a/integration/common_test.go +++ b/integration/common_test.go @@ -15,11 +15,6 @@ import ( "github.com/onsi/gomega/gexec" ) -type tInterface interface { - GinkgoTInterface - Helper() -} - type tHelper struct{ GinkgoTInterface } func (t *tHelper) Helper() { return } @@ -61,8 +56,18 @@ func eksctl(args ...string) *gexec.Session { default: t *= 30 } - session.Wait(t) + return session +} + +func eksctlSuccess(args ...string) *gexec.Session { + session := eksctl(args...) Expect(session.ExitCode()).To(Equal(0)) return session } + +func eksctlFail(args ...string) *gexec.Session { + session := eksctl(args...) + Expect(session.ExitCode()).To(Not(Equal(0))) + return session +} diff --git a/integration/creategetdelete_test.go b/integration/creategetdelete_test.go index 37bf90018e..18b733ee44 100644 --- a/integration/creategetdelete_test.go +++ b/integration/creategetdelete_test.go @@ -10,16 +10,17 @@ import ( awseks "github.com/aws/aws-sdk-go/service/eks" harness "github.com/dlespiau/kube-test-harness" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + "github.com/onsi/gomega/gexec" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/tools/clientcmd" + "sigs.k8s.io/yaml" api "github.com/weaveworks/eksctl/pkg/apis/eksctl.io/v1alpha5" + "github.com/weaveworks/eksctl/pkg/authconfigmap" "github.com/weaveworks/eksctl/pkg/ctl/cmdutils" - - . "github.com/onsi/ginkgo" - . "github.com/onsi/gomega" - "github.com/onsi/gomega/gexec" - + "github.com/weaveworks/eksctl/pkg/iam" "github.com/weaveworks/eksctl/pkg/testutils/aws" . "github.com/weaveworks/eksctl/pkg/testutils/matchers" ) @@ -63,7 +64,7 @@ var _ = Describe("(Integration) Create, Get, Scale & Delete", func() { clusterName = cmdutils.ClusterName("", "") } - eksctl("create", "cluster", + eksctlSuccess("create", "cluster", "--verbose", "4", "--name", clusterName, "--tags", "alpha.eksctl.io/description=eksctl integration test", @@ -101,14 +102,14 @@ var _ = Describe("(Integration) Create, Get, Scale & Delete", func() { Context("and listing clusters", func() { It("should return the previously created cluster", func() { - cmdSession := eksctl("get", "clusters", "--region", region) + cmdSession := eksctlSuccess("get", "clusters", "--region", region) Expect(string(cmdSession.Buffer().Contents())).To(ContainSubstring(clusterName)) }) }) Context("and scale the initial nodegroup", func() { It("should not return an error", func() { - eksctl("scale", "nodegroup", + eksctlSuccess("scale", "nodegroup", "--verbose", "4", "--cluster", clusterName, "--region", region, @@ -134,7 +135,7 @@ var _ = Describe("(Integration) Create, Get, Scale & Delete", func() { Context("and add the second nodegroup", func() { It("should not return an error", func() { - eksctl("create", "nodegroup", + eksctlSuccess("create", "nodegroup", "--cluster", clusterName, "--region", region, "--nodes", "4", @@ -220,9 +221,127 @@ var _ = Describe("(Integration) Create, Get, Scale & Delete", func() { }) }) + Context("and manipulating iam identity mappings", func() { + var ( + role, exp0, exp1 string + role0, role1 authconfigmap.MapRole + ) + + BeforeEach(func() { + role = "arn:aws:iam::123456:role/eksctl-testing-XYZ" + + role0 = authconfigmap.MapRole{ + RoleARN: role, + Identity: iam.Identity{ + Username: "admin", + Groups: []string{"system:masters", "system:nodes"}, + }, + } + role1 = authconfigmap.MapRole{ + RoleARN: role, + Identity: iam.Identity{ + Groups: []string{"system:something"}, + }, + } + + bs, err := yaml.Marshal([]authconfigmap.MapRole{role0}) + Expect(err).ShouldNot(HaveOccurred()) + exp0 = string(bs) + + bs, err = yaml.Marshal([]authconfigmap.MapRole{role1}) + Expect(err).ShouldNot(HaveOccurred()) + exp1 = string(bs) + }) + + It("fails getting unknown mapping", func() { + eksctlFail("get", "iamidentitymapping", + "--cluster", clusterName, + "--role", "idontexist", + "-o", "yaml", + ) + }) + It("creates mapping", func() { + eksctlSuccess("create", "iamidentitymapping", + "--cluster", clusterName, + "--role", role0.RoleARN, + "--username", role0.Username, + "--group", role0.Groups[0], + "--group", role0.Groups[1], + ) + + get := eksctlSuccess("get", "iamidentitymapping", + "--cluster", clusterName, + "--role", role0.RoleARN, + "-o", "yaml", + ) + Expect(string(get.Buffer().Contents())).To(MatchYAML(exp0)) + }) + It("creates a duplicate mapping", func() { + eksctlSuccess("create", "iamidentitymapping", + "--cluster", clusterName, + "--role", role0.RoleARN, + "--username", role0.Username, + "--group", role0.Groups[0], + "--group", role0.Groups[1], + ) + + get := eksctlSuccess("get", "iamidentitymapping", + "--cluster", clusterName, + "--role", role0.RoleARN, + "-o", "yaml", + ) + Expect(string(get.Buffer().Contents())).To(MatchYAML(exp0 + exp0)) + }) + It("creates a duplicate mapping with different identity", func() { + eksctlSuccess("create", "iamidentitymapping", + "--cluster", clusterName, + "--role", role1.RoleARN, + "--group", role1.Groups[0], + ) + + get := eksctlSuccess("get", "iamidentitymapping", + "--cluster", clusterName, + "--role", role1.RoleARN, + "-o", "yaml", + ) + Expect(string(get.Buffer().Contents())).To(MatchYAML(exp0 + exp0 + exp1)) + }) + It("deletes a single mapping fifo", func() { + eksctlSuccess("delete", "iamidentitymapping", + "--cluster", clusterName, + "--role", role, + ) + + get := eksctlSuccess("get", "iamidentitymapping", + "--cluster", clusterName, + "--role", role, + "-o", "yaml", + ) + Expect(string(get.Buffer().Contents())).To(MatchYAML(exp0 + exp1)) + }) + It("fails when deleting unknown mapping", func() { + eksctlFail("delete", "iamidentitymapping", + "--cluster", clusterName, + "--role", "idontexist", + ) + }) + It("deletes duplicate mappings with --all", func() { + eksctlSuccess("delete", "iamidentitymapping", + "--cluster", clusterName, + "--role", role, + "--all", + ) + eksctlFail("get", "iamidentitymapping", + "--cluster", clusterName, + "--role", role, + "-o", "yaml", + ) + }) + }) + Context("and delete the second nodegroup", func() { It("should not return an error", func() { - eksctl("delete", "nodegroup", + eksctlSuccess("delete", "nodegroup", "--verbose", "4", "--cluster", clusterName, "--region", region, @@ -249,7 +368,7 @@ var _ = Describe("(Integration) Create, Get, Scale & Delete", func() { Context("and scale the initial nodegroup back to 1 node", func() { It("should not return an error", func() { - eksctl("scale", "nodegroup", + eksctlSuccess("scale", "nodegroup", "--verbose", "4", "--cluster", clusterName, "--region", region, @@ -279,7 +398,7 @@ var _ = Describe("(Integration) Create, Get, Scale & Delete", func() { Skip("will not delete cluster " + clusterName) } - eksctl("delete", "cluster", + eksctlSuccess("delete", "cluster", "--verbose", "4", "--name", clusterName, "--region", region, diff --git a/pkg/authconfigmap/authconfigmap.go b/pkg/authconfigmap/authconfigmap.go index f64c32f4fb..b2f1b5cd4e 100644 --- a/pkg/authconfigmap/authconfigmap.go +++ b/pkg/authconfigmap/authconfigmap.go @@ -21,11 +21,9 @@ import ( "sigs.k8s.io/yaml" api "github.com/weaveworks/eksctl/pkg/apis/eksctl.io/v1alpha5" + "github.com/weaveworks/eksctl/pkg/iam" ) -type mapRole map[string]interface{} -type mapRoles []mapRole - const ( // ObjectName is the Kubernetes resource name of the auth ConfigMap ObjectName = "aws-auth" @@ -48,6 +46,27 @@ const ( // with the cluster, required for the instance role ARNs of node groups. var RoleNodeGroupGroups = []string{"system:bootstrappers", "system:nodes"} +// MapRole represents an IAM identity with role. +type MapRole struct { + iam.Identity `json:",inline"` + RoleARN string `json:"rolearn"` +} + +// MapRoles is a list of IAM identities with roles. +type MapRoles []MapRole + +// Get returns all matching role mappings. Note that at this moment +// aws-iam-authenticator only considers the last one! +func (rs MapRoles) Get(arn string) MapRoles { + var m MapRoles + for _, r := range rs { + if r.RoleARN == arn { + m = append(m, r) + } + } + return m +} + // AuthConfigMap allows modifying the auth ConfigMap. type AuthConfigMap struct { client v1.ConfigMapInterface @@ -141,50 +160,60 @@ func (a *AuthConfigMap) setAccounts(accounts []string) error { // a role with given groups. If you are calling // this as part of node creation you should use DefaultNodeGroups. func (a *AuthConfigMap) AddRole(arn string, username string, groups []string) error { - roles, err := a.roles() + roles, err := a.Roles() if err != nil { return err } - roles = append(roles, mapRole{ - "rolearn": arn, - "username": username, - "groups": groups, + roles = append(roles, MapRole{ + RoleARN: arn, + Identity: iam.Identity{ + Username: username, + Groups: groups, + }, }) logger.Info("adding role %q to auth ConfigMap", arn) return a.setRoles(roles) } -// RemoveRole removes exactly one entry, even if there are duplicates. -// If it cannot find the role it returns an error. -func (a *AuthConfigMap) RemoveRole(arn string) error { - if arn == "" { - return errors.New("nodegroup instance role ARN is not set") - } - roles, err := a.roles() +// RemoveRole removes a role. If `all` is false it will only +// remove the first it encounters and return an error if it cannot +// find it. +// If `all` is true it will remove all of them and not return an +// error if it cannot be found. +func (a *AuthConfigMap) RemoveRole(arn string, all bool) error { + roles, err := a.Roles() if err != nil { return err } + newroles := MapRoles{} for i, role := range roles { - if role["rolearn"] == arn { - logger.Info("removing role %q from auth ConfigMap", arn) - roles = append(roles[:i], roles[i+1:]...) - return a.setRoles(roles) + if role.RoleARN == arn { + logger.Info("removing role %q from auth ConfigMap (username = %q, groups = %q)", arn, role.Username, role.Groups) + if !all { + roles = append(roles[:i], roles[i+1:]...) + return a.setRoles(roles) + } + } else if all { + newroles = append(newroles, role) } } - - return fmt.Errorf("instance role ARN %q not found in auth ConfigMap", arn) + if !all { + return fmt.Errorf("instance role ARN %q not found in auth ConfigMap", arn) + } + return a.setRoles(newroles) } -func (a *AuthConfigMap) roles() (mapRoles, error) { - var roles mapRoles +// Roles returns a list of roles that are currently in the (cached) configmap. +func (a *AuthConfigMap) Roles() (MapRoles, error) { + var roles MapRoles if err := yaml.Unmarshal([]byte(a.cm.Data[rolesData]), &roles); err != nil { return nil, errors.Wrap(err, "unmarshalling mapRoles") } return roles, nil } -func (a *AuthConfigMap) setRoles(r mapRoles) error { +func (a *AuthConfigMap) setRoles(r MapRoles) error { bs, err := yaml.Marshal(r) if err != nil { return errors.Wrap(err, "marshalling mapRoles") @@ -233,11 +262,15 @@ func AddNodeGroup(clientSet kubernetes.Interface, ng *api.NodeGroup) error { // RemoveNodeGroup removes a nodegroup from the ConfigMap and // does a client update. func RemoveNodeGroup(clientSet kubernetes.Interface, ng *api.NodeGroup) error { + arn := ng.IAM.InstanceRoleARN + if arn == "" { + return errors.New("nodegroup instance role ARN is not set") + } acm, err := NewFromClientSet(clientSet) if err != nil { return err } - if err := acm.RemoveRole(ng.IAM.InstanceRoleARN); err != nil { + if err := acm.RemoveRole(arn, false); err != nil { return errors.Wrap(err, "removing nodegroup from auth ConfigMap") } if err := acm.Save(); err != nil { diff --git a/pkg/authconfigmap/authconfigmap_test.go b/pkg/authconfigmap/authconfigmap_test.go index d83621a892..eaf8ff0406 100644 --- a/pkg/authconfigmap/authconfigmap_test.go +++ b/pkg/authconfigmap/authconfigmap_test.go @@ -165,7 +165,7 @@ var _ = Describe("AuthConfigMap{}", func() { removeAndSave := func(arn string) *corev1.ConfigMap { client.reset() - err := acm.RemoveRole(arn) + err := acm.RemoveRole(arn, false) Expect(err).NotTo(HaveOccurred()) err = acm.Save() @@ -187,9 +187,20 @@ var _ = Describe("AuthConfigMap{}", func() { Expect(cm.Data["mapRoles"]).To(MatchYAML("[]")) }) It("should fail if role not found", func() { - err := acm.RemoveRole(roleA) + err := acm.RemoveRole(roleA, false) Expect(err).To(HaveOccurred()) }) + It("should remove all if specified", func() { + err := acm.AddRole(roleA, RoleNodeGroupUsername, RoleNodeGroupGroups) + Expect(err).NotTo(HaveOccurred()) + err = acm.AddRole(roleA, RoleNodeGroupUsername, RoleNodeGroupGroups) + Expect(err).NotTo(HaveOccurred()) + Expect(client.updated.Data["mapRoles"]).To(Not(MatchYAML("[]"))) + + err = acm.RemoveRole(roleA, true) + Expect(err).NotTo(HaveOccurred()) + Expect(client.updated.Data["mapRoles"]).To(MatchYAML("[]")) + }) }) Describe("AddAccount()", func() { existing := &corev1.ConfigMap{ diff --git a/pkg/ctl/cmdutils/cmdutils.go b/pkg/ctl/cmdutils/cmdutils.go index f86675c053..fe1d701033 100644 --- a/pkg/ctl/cmdutils/cmdutils.go +++ b/pkg/ctl/cmdutils/cmdutils.go @@ -135,7 +135,13 @@ func ErrUnsupportedRegion(p *api.ProviderConfig) error { // ErrNameFlagAndArg is a common error message func ErrNameFlagAndArg(nameFlag, nameArg string) error { - return fmt.Errorf("--name=%s and argument %s %s", nameFlag, nameArg, IncompatibleFlags) + return ErrFlagAndArg("--name", nameFlag, nameArg) +} + +// ErrFlagAndArg may be used to err for options that can be given +// as flags /and/ arg but only one is allowed to be used. +func ErrFlagAndArg(kind, flag, arg string) error { + return fmt.Errorf("%s=%s and argument %s %s", kind, flag, arg, IncompatibleFlags) } // ErrMustBeSet is a common error message diff --git a/pkg/ctl/create/create.go b/pkg/ctl/create/create.go index 292fb870f4..557fe30223 100644 --- a/pkg/ctl/create/create.go +++ b/pkg/ctl/create/create.go @@ -24,6 +24,7 @@ func Command(g *cmdutils.Grouping) *cobra.Command { cmd.AddCommand(createClusterCmd(g)) cmd.AddCommand(createNodeGroupCmd(g)) + cmd.AddCommand(createIAMIdentityMappingCmd(g)) return cmd } diff --git a/pkg/ctl/create/iamidentitymapping.go b/pkg/ctl/create/iamidentitymapping.go new file mode 100644 index 0000000000..518d02a638 --- /dev/null +++ b/pkg/ctl/create/iamidentitymapping.go @@ -0,0 +1,95 @@ +package create + +import ( + "os" + + "github.com/kris-nova/logger" + "github.com/spf13/cobra" + "github.com/spf13/pflag" + + api "github.com/weaveworks/eksctl/pkg/apis/eksctl.io/v1alpha5" + "github.com/weaveworks/eksctl/pkg/authconfigmap" + "github.com/weaveworks/eksctl/pkg/ctl/cmdutils" + "github.com/weaveworks/eksctl/pkg/eks" +) + +func createIAMIdentityMappingCmd(g *cmdutils.Grouping) *cobra.Command { + p := &api.ProviderConfig{} + cfg := api.NewClusterConfig() + id := &authconfigmap.MapRole{} + cmd := &cobra.Command{ + Use: "iamidentitymapping", + Short: "Create an IAM identity mapping", + Long: `Creates a mapping from IAM role to Kubernetes user and groups. + +Note aws-iam-authenticator only considers the last entry for any given +role. If you create a duplicate entry it will shadow all the previous +username and groups mapping. +`, + Run: func(cmd *cobra.Command, args []string) { + if err := doCreateIAMIdentityMapping(p, cfg, id); err != nil { + logger.Critical("%s\n", err.Error()) + os.Exit(1) + } + }, + } + group := g.New(cmd) + + group.InFlagSet("General", func(fs *pflag.FlagSet) { + fs.StringVar(&id.RoleARN, "role", "", "ARN of the IAM role to create") + fs.StringVar(&id.Username, "username", "", "User name within Kubernetes to map to IAM role") + fs.StringArrayVar(&id.Groups, "group", []string{}, "Group within Kubernetes to which IAM role is mapped") + fs.StringVar(&cfg.Metadata.Name, "cluster", "", "EKS cluster name") + cmdutils.AddRegionFlag(fs, p) + }) + + cmdutils.AddCommonFlagsForAWS(group, p, false) + + group.AddTo(cmd) + + return cmd +} + +func doCreateIAMIdentityMapping(p *api.ProviderConfig, cfg *api.ClusterConfig, id *authconfigmap.MapRole) error { + ctl := eks.New(p, cfg) + + if err := ctl.CheckAuth(); err != nil { + return err + } + if id.RoleARN == "" { + return cmdutils.ErrMustBeSet("--role") + } + if cfg.Metadata.Name == "" { + return cmdutils.ErrMustBeSet("--cluster") + } + if err := id.Valid(); err != nil { + return err + } + + if err := ctl.GetCredentials(cfg); err != nil { + return err + } + clientSet, err := ctl.NewStdClientSet(cfg) + if err != nil { + return err + } + acm, err := authconfigmap.NewFromClientSet(clientSet) + if err != nil { + return err + } + + // Check whether role already exists. + roles, err := acm.Roles() + if err != nil { + return err + } + filtered := roles.Get(id.RoleARN) + if len(filtered) > 0 { + logger.Warning("found %d mappings with same role %q (which will be shadowed by your new mapping)", len(filtered), id.RoleARN) + } + + if err := acm.AddRole(id.RoleARN, id.Username, id.Groups); err != nil { + return err + } + return acm.Save() +} diff --git a/pkg/ctl/delete/delete.go b/pkg/ctl/delete/delete.go index 980207cae7..c304441ea3 100644 --- a/pkg/ctl/delete/delete.go +++ b/pkg/ctl/delete/delete.go @@ -3,6 +3,7 @@ package delete import ( "github.com/kris-nova/logger" "github.com/spf13/cobra" + "github.com/weaveworks/eksctl/pkg/ctl/cmdutils" ) @@ -27,6 +28,7 @@ func Command(g *cmdutils.Grouping) *cobra.Command { cmd.AddCommand(deleteClusterCmd(g)) cmd.AddCommand(deleteNodeGroupCmd(g)) + cmd.AddCommand(deleteIAMIdentityMappingCmd(g)) return cmd } diff --git a/pkg/ctl/delete/iamidentitymapping.go b/pkg/ctl/delete/iamidentitymapping.go new file mode 100644 index 0000000000..73c2f74e10 --- /dev/null +++ b/pkg/ctl/delete/iamidentitymapping.go @@ -0,0 +1,90 @@ +package delete + +import ( + "os" + + "github.com/kris-nova/logger" + "github.com/spf13/cobra" + "github.com/spf13/pflag" + + api "github.com/weaveworks/eksctl/pkg/apis/eksctl.io/v1alpha5" + "github.com/weaveworks/eksctl/pkg/authconfigmap" + "github.com/weaveworks/eksctl/pkg/ctl/cmdutils" + "github.com/weaveworks/eksctl/pkg/eks" +) + +func deleteIAMIdentityMappingCmd(g *cmdutils.Grouping) *cobra.Command { + p := &api.ProviderConfig{} + cfg := api.NewClusterConfig() + var roleFlag string + var all bool + cmd := &cobra.Command{ + Use: "iamidentitymapping", + Short: "Delete a IAM identity mapping", + Run: func(cmd *cobra.Command, args []string) { + if err := doDeleteIAMIdentityMapping(p, cfg, roleFlag, all); err != nil { + logger.Critical("%s\n", err.Error()) + os.Exit(1) + } + }, + } + group := g.New(cmd) + + group.InFlagSet("General", func(fs *pflag.FlagSet) { + fs.StringVar(&roleFlag, "role", "", "ARN of the IAM role to delete") + fs.BoolVar(&all, "all", false, "Delete all matching mappings instead of just one") + fs.StringVar(&cfg.Metadata.Name, "cluster", "", "EKS cluster name") + cmdutils.AddRegionFlag(fs, p) + }) + + cmdutils.AddCommonFlagsForAWS(group, p, false) + + group.AddTo(cmd) + + return cmd +} + +func doDeleteIAMIdentityMapping(p *api.ProviderConfig, cfg *api.ClusterConfig, roleFlag string, all bool) error { + ctl := eks.New(p, cfg) + + if err := ctl.CheckAuth(); err != nil { + return err + } + + if roleFlag == "" { + return cmdutils.ErrMustBeSet("--role") + } + if cfg.Metadata.Name == "" { + return cmdutils.ErrMustBeSet("--cluster") + } + + if err := ctl.GetCredentials(cfg); err != nil { + return err + } + clientSet, err := ctl.NewStdClientSet(cfg) + if err != nil { + return err + } + acm, err := authconfigmap.NewFromClientSet(clientSet) + if err != nil { + return err + } + + if err := acm.RemoveRole(roleFlag, all); err != nil { + return err + } + if err := acm.Save(); err != nil { + return err + } + + // Check whether we have more roles that match + roles, err := acm.Roles() + if err != nil { + return err + } + filtered := roles.Get(roleFlag) + if len(filtered) > 0 { + logger.Warning("there are %d mappings left with same role %q (use --all to delete them at once)", len(filtered), roleFlag) + } + return nil +} diff --git a/pkg/ctl/get/get.go b/pkg/ctl/get/get.go index 86b9410e9f..4d7acdec74 100644 --- a/pkg/ctl/get/get.go +++ b/pkg/ctl/get/get.go @@ -25,6 +25,7 @@ func Command(g *cmdutils.Grouping) *cobra.Command { cmd.AddCommand(getClusterCmd(g)) cmd.AddCommand(getNodegroupCmd(g)) + cmd.AddCommand(getIAMIdentityMappingCmd(g)) return cmd } diff --git a/pkg/ctl/get/iamidentitymapping.go b/pkg/ctl/get/iamidentitymapping.go new file mode 100644 index 0000000000..66943ba265 --- /dev/null +++ b/pkg/ctl/get/iamidentitymapping.go @@ -0,0 +1,108 @@ +package get + +import ( + "fmt" + "os" + "strings" + + "github.com/kris-nova/logger" + "github.com/spf13/cobra" + "github.com/spf13/pflag" + + api "github.com/weaveworks/eksctl/pkg/apis/eksctl.io/v1alpha5" + "github.com/weaveworks/eksctl/pkg/authconfigmap" + "github.com/weaveworks/eksctl/pkg/ctl/cmdutils" + "github.com/weaveworks/eksctl/pkg/eks" + "github.com/weaveworks/eksctl/pkg/printers" +) + +func getIAMIdentityMappingCmd(g *cmdutils.Grouping) *cobra.Command { + p := &api.ProviderConfig{} + cfg := api.NewClusterConfig() + var roleFlag string + cmd := &cobra.Command{ + Use: "iamidentitymapping", + Short: "Get IAM identity mapping(s)", + Run: func(cmd *cobra.Command, args []string) { + if err := doGetIAMIdentityMapping(p, cfg, roleFlag); err != nil { + logger.Critical("%s\n", err.Error()) + os.Exit(1) + } + }, + } + group := g.New(cmd) + + group.InFlagSet("General", func(fs *pflag.FlagSet) { + fs.StringVar(&roleFlag, "role", "", "ARN of the IAM role") + fs.StringVar(&cfg.Metadata.Name, "cluster", "", "EKS cluster name") + cmdutils.AddRegionFlag(fs, p) + cmdutils.AddCommonFlagsForGetCmd(fs, &chunkSize, &output) + }) + + cmdutils.AddCommonFlagsForAWS(group, p, false) + + group.AddTo(cmd) + + return cmd +} + +func doGetIAMIdentityMapping(p *api.ProviderConfig, cfg *api.ClusterConfig, roleFlag string) error { + ctl := eks.New(p, cfg) + + if err := ctl.CheckAuth(); err != nil { + return err + } + + if cfg.Metadata.Name == "" { + return cmdutils.ErrMustBeSet("--cluster") + } + + if err := ctl.GetCredentials(cfg); err != nil { + return err + } + clientSet, err := ctl.NewStdClientSet(cfg) + if err != nil { + return err + } + acm, err := authconfigmap.NewFromClientSet(clientSet) + if err != nil { + return err + } + roles, err := acm.Roles() + if err != nil { + return err + } + if roleFlag != "" { + roles = roles.Get(roleFlag) + // If a filter was given, we error if none was found + if len(roles) == 0 { + return fmt.Errorf("no iamidentitymapping with role %q found", roleFlag) + } + } + + printer, err := printers.NewPrinter(output) + if err != nil { + return err + } + if output == "table" { + addIAMIdentityMappingTableColumns(printer.(*printers.TablePrinter)) + } + + if err := printer.PrintObjWithKind("iamidentitymappings", roles, os.Stdout); err != nil { + return err + } + + return nil +} + +func addIAMIdentityMappingTableColumns(printer *printers.TablePrinter) { + printer.AddColumn("ROLE", func(r authconfigmap.MapRole) string { + return r.RoleARN + }) + printer.AddColumn("USERNAME", func(r authconfigmap.MapRole) string { + return r.Username + }) + printer.AddColumn("GROUPS", func(r authconfigmap.MapRole) string { + return strings.Join(r.Groups, ",") + }) +} diff --git a/pkg/iam/mapping.go b/pkg/iam/mapping.go new file mode 100644 index 0000000000..94be7f1e90 --- /dev/null +++ b/pkg/iam/mapping.go @@ -0,0 +1,17 @@ +package iam + +import "errors" + +// Identity represents an IAM identity. +type Identity struct { + Username string `json:"username"` + Groups []string `json:"groups"` +} + +// Valid ensures the identity is proper. +func (i Identity) Valid() error { + if len(i.Groups) == 0 { + return errors.New("identity mapping needs at least 1 group") + } + return nil +}