Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add command to manipulate IAM identity mappings #814

Merged
merged 2 commits into from
Jun 5, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 11 additions & 6 deletions integration/common_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,6 @@ import (
"github.com/onsi/gomega/gexec"
)

type tInterface interface {
GinkgoTInterface
Helper()
}

type tHelper struct{ GinkgoTInterface }

func (t *tHelper) Helper() { return }
Expand Down Expand Up @@ -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
}
143 changes: 131 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,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))
martina-if marked this conversation as resolved.
Show resolved Hide resolved
})
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 +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,
Expand Down Expand Up @@ -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,
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