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 [--role arn]
    eksctl create iamidentitymapping --role <arn> [--username=USER] --group=GROUP0 [--group=GROUP1]
    eksctl delete iamidentitymapping --role <arn> [--all]

`eksctl get iamidentitymapping`
Returns all mappings; if role filter is given it returns all matching
roles (can be more than one).

`eksctl create iamidentitymapping`
Allows creating duplicates. Will warn if duplicates exist.

`eksctl delete iamidentitymapping`
Deletes a single mapping FIFO unless `--all` is given in which case it
removes all matching. Will warn if more mappings matching this role are
found.
  • Loading branch information
rndstr committed May 24, 2019
1 parent 4f37e13 commit 76f4280
Show file tree
Hide file tree
Showing 12 changed files with 540 additions and 41 deletions.
12 changes: 11 additions & 1 deletion integration/common_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,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
}
149 changes: 137 additions & 12 deletions integration/creategetdelete_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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,
Expand All @@ -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",
Expand Down Expand Up @@ -220,9 +221,133 @@ 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("should marshal authconfigmap.MapRole", func() {

bs1, err := yaml.Marshal([]authconfigmap.MapRole{role1})
Expect(err).ShouldNot(HaveOccurred())
exp1 = string(bs1)
})
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,
Expand All @@ -249,7 +374,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,
Expand Down Expand Up @@ -279,7 +404,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,
Expand Down
83 changes: 58 additions & 25 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,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
Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -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 {
Expand Down
Loading

0 comments on commit 76f4280

Please sign in to comment.