diff --git a/spinifex/gateway/eks/passrole.go b/spinifex/gateway/eks/passrole.go index 8ed6b2962..ac188d6e8 100644 --- a/spinifex/gateway/eks/passrole.go +++ b/spinifex/gateway/eks/passrole.go @@ -23,6 +23,18 @@ func PassedRoleARNs(action string, body []byte) []string { return nil } roleARN = input.NodeRole + case "CreateAddon": + input := new(eks.CreateAddonInput) + if unmarshalIfBody(body, input) != nil { + return nil + } + roleARN = input.ServiceAccountRoleArn + case "UpdateAddon": + input := new(eks.UpdateAddonInput) + if unmarshalIfBody(body, input) != nil { + return nil + } + roleARN = input.ServiceAccountRoleArn } if aws.StringValue(roleARN) == "" { return nil diff --git a/spinifex/gateway/eks_addon_wire_test.go b/spinifex/gateway/eks_addon_wire_test.go index 4e3784043..bbbcedc67 100644 --- a/spinifex/gateway/eks_addon_wire_test.go +++ b/spinifex/gateway/eks_addon_wire_test.go @@ -78,12 +78,18 @@ func TestEKSRequest_InternalAddonsWireBody(t *testing.T) { rec.Body.String()) } -// Only CreateCluster and CreateNodegroup hand a role to iam:PassRole; the add-on -// service-account role is stored and rendered without that check. -func TestPassedRoleARNs_AddonServiceAccountRoleIsNotChecked(t *testing.T) { +// CreateAddon and UpdateAddon hand their service-account role to iam:PassRole, +// same as CreateCluster's roleArn and CreateNodegroup's nodeRole; an absent +// role passes nothing. +func TestPassedRoleARNs_AddonServiceAccountRoleIsChecked(t *testing.T) { body := []byte(`{"addonName":"aws-load-balancer-controller","serviceAccountRoleArn":"arn:aws:iam::123456789012:role/alb"}`) - assert.Empty(t, gateway_eks.PassedRoleARNs("CreateAddon", body)) - assert.Empty(t, gateway_eks.PassedRoleARNs("UpdateAddon", body)) + assert.Equal(t, []string{"arn:aws:iam::123456789012:role/alb"}, gateway_eks.PassedRoleARNs("CreateAddon", body)) + assert.Equal(t, []string{"arn:aws:iam::123456789012:role/alb"}, gateway_eks.PassedRoleARNs("UpdateAddon", body)) + + roleless := []byte(`{"addonName":"spinifex-noop"}`) + assert.Empty(t, gateway_eks.PassedRoleARNs("CreateAddon", roleless)) + assert.Empty(t, gateway_eks.PassedRoleARNs("UpdateAddon", roleless)) + assert.Equal(t, []string{"arn:aws:iam::123456789012:role/node"}, gateway_eks.PassedRoleARNs("CreateNodegroup", []byte(`{"nodeRole":"arn:aws:iam::123456789012:role/node"}`))) } diff --git a/spinifex/gateway/eks_passrole_test.go b/spinifex/gateway/eks_passrole_test.go index 1f1a21ab9..dbda9189b 100644 --- a/spinifex/gateway/eks_passrole_test.go +++ b/spinifex/gateway/eks_passrole_test.go @@ -160,3 +160,49 @@ func TestEKSCreateCluster_PassRole(t *testing.T) { ) assertPermitted(t, dispatchEKS(t, allowed, http.MethodPost, "/clusters", body)) } + +func createAddonBody(roleARN string) string { + return `{"addonName":"aws-load-balancer-controller","serviceAccountRoleArn":"` + roleARN + `"}` +} + +func updateAddonBody(roleARN string) string { + return `{"serviceAccountRoleArn":"` + roleARN + `"}` +} + +func TestEKSCreateAddon_PassRole(t *testing.T) { + body := createAddonBody(eksNodeRoleARN) + + denied := eksPassRoleGateway(statement("Allow", "eks:*", "*")) + assertDenied(t, dispatchEKS(t, denied, http.MethodPost, "/clusters/prod/addons", body)) + + allowed := eksPassRoleGateway( + statement("Allow", "eks:*", "*"), + statement("Allow", "iam:PassRole", eksNodeRoleARN), + ) + assertPermitted(t, dispatchEKS(t, allowed, http.MethodPost, "/clusters/prod/addons", body)) +} + +func TestEKSCreateAddon_NoRoleSkipsPassRole(t *testing.T) { + gw := eksPassRoleGateway(statement("Allow", "eks:*", "*")) + assertPermitted(t, dispatchEKS(t, gw, http.MethodPost, "/clusters/prod/addons", createAddonBody(""))) +} + +func TestEKSUpdateAddon_PassRole(t *testing.T) { + body := updateAddonBody(eksNodeRoleARN) + const path = "/clusters/prod/addons/aws-load-balancer-controller/update" + + denied := eksPassRoleGateway(statement("Allow", "eks:*", "*")) + assertDenied(t, dispatchEKS(t, denied, http.MethodPost, path, body)) + + allowed := eksPassRoleGateway( + statement("Allow", "eks:*", "*"), + statement("Allow", "iam:PassRole", eksNodeRoleARN), + ) + assertPermitted(t, dispatchEKS(t, allowed, http.MethodPost, path, body)) +} + +func TestEKSUpdateAddon_NoRoleSkipsPassRole(t *testing.T) { + gw := eksPassRoleGateway(statement("Allow", "eks:*", "*")) + assertPermitted(t, dispatchEKS(t, gw, http.MethodPost, + "/clusters/prod/addons/aws-load-balancer-controller/update", updateAddonBody(""))) +}