Skip to content

Commit

Permalink
Add command to manipulate IAM identity mappings
Browse files Browse the repository at this point in the history
Adds the following commands to get/create/delete IAM role mappings to
Kubernetes username and groups.

    eksctl get iamidentitymapping [arn]
    eksctl create iamidentitymapping <arn> [--username=USER] [--group=GROUP0] [--group=GROUP1]
    eksctl delete iamidentitymapping <arn>

Its behavior with regard to duplicate role ARNs is the same as when we
mappings are manipulated while adding/removing nodegroups:
Deletion removes at most one and Create is accepting duplicates.
  • Loading branch information
rndstr committed May 23, 2019
1 parent 2fa2bc6 commit 9562731
Show file tree
Hide file tree
Showing 9 changed files with 347 additions and 14 deletions.
46 changes: 33 additions & 13 deletions pkg/authconfigmap/authconfigmap.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -48,6 +46,25 @@ 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 the first matching role it encounters.
func (rs MapRoles) Get(arn string) (MapRole, bool) {
for _, r := range rs {
if r.RoleARN == arn {
return r, true
}
}
return MapRole{}, false
}

// AuthConfigMap allows modifying the auth ConfigMap.
type AuthConfigMap struct {
client v1.ConfigMapInterface
Expand Down Expand Up @@ -141,14 +158,16 @@ 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)
Expand All @@ -160,13 +179,13 @@ func (a *AuthConfigMap) RemoveRole(arn string) error {
if arn == "" {
return errors.New("nodegroup instance role ARN is not set")
}
roles, err := a.roles()
roles, err := a.Roles()
if err != nil {
return err
}

for i, role := range roles {
if role["rolearn"] == arn {
if role.RoleARN == arn {
logger.Info("removing role %q from auth ConfigMap", arn)
roles = append(roles[:i], roles[i+1:]...)
return a.setRoles(roles)
Expand All @@ -176,15 +195,16 @@ func (a *AuthConfigMap) RemoveRole(arn string) error {
return fmt.Errorf("instance role ARN %q not found in auth ConfigMap", arn)
}

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")
Expand Down
8 changes: 7 additions & 1 deletion pkg/ctl/cmdutils/cmdutils.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions pkg/ctl/create/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
91 changes: 91 additions & 0 deletions pkg/ctl/create/iamidentitymapping.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
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 <rolearn>",
Short: "Create an IAM identity mapping",
Long: `Creates a mapping from IAM role to Kubernetes user and groups.
To create an admin use
--group=system:masters --username=admin
`,
Run: func(cmd *cobra.Command, args []string) {
if err := doCreateIAMIdentityMapping(p, cfg, id, cmdutils.GetNameArg(args)); 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(&cfg.Metadata.Name, "cluster", "", "EKS cluster name")
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")
})

cmdutils.AddCommonFlagsForAWS(group, p, false)

group.AddTo(cmd)

return cmd
}

func doCreateIAMIdentityMapping(p *api.ProviderConfig, cfg *api.ClusterConfig, id *authconfigmap.MapRole, roleArg string) error {
ctl := eks.New(p, cfg)

if err := ctl.CheckAuth(); err != nil {
return err
}
if id.RoleARN != "" && roleArg != "" {
return cmdutils.ErrFlagAndArg("--role", id.RoleARN, roleArg)
}
roleFilter := id.RoleARN
if roleArg != "" {
roleFilter = roleArg
}
if roleFilter == "" {
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
}

if err := acm.AddRole(roleFilter, id.Username, id.Groups); err != nil {
return err
}
return acm.Save()
}
2 changes: 2 additions & 0 deletions pkg/ctl/delete/delete.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package delete
import (
"github.com/kris-nova/logger"
"github.com/spf13/cobra"

"github.com/weaveworks/eksctl/pkg/ctl/cmdutils"
)

Expand All @@ -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
}
81 changes: 81 additions & 0 deletions pkg/ctl/delete/iamidentitymapping.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
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
cmd := &cobra.Command{
Use: "iamidentitymapping <role>",
Short: "Delete a IAM identity mapping",
Run: func(cmd *cobra.Command, args []string) {
if err := doDeleteIAMIdentityMapping(p, cfg, roleFlag, cmdutils.GetNameArg(args)); 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.StringVar(&cfg.Metadata.Name, "cluster", "", "EKS cluster name")
})

cmdutils.AddCommonFlagsForAWS(group, p, false)

group.AddTo(cmd)

return cmd
}

func doDeleteIAMIdentityMapping(p *api.ProviderConfig, cfg *api.ClusterConfig, roleFlag, roleArg string) error {
ctl := eks.New(p, cfg)

if err := ctl.CheckAuth(); err != nil {
return err
}

if roleFlag != "" && roleArg != "" {
return cmdutils.ErrFlagAndArg("--role", roleFlag, roleArg)
}
roleFilter := roleFlag
if roleArg != "" {
roleFilter = roleArg
}
if roleFilter == "" {
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(roleFilter); err != nil {
return err
}
return acm.Save()
}
1 change: 1 addition & 0 deletions pkg/ctl/get/get.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Loading

0 comments on commit 9562731

Please sign in to comment.