Skip to content

Commit

Permalink
Merge pull request #11016 from olemarkus/irsa-custom
Browse files Browse the repository at this point in the history
user-configurable IAM roles for ServiceAccounts
  • Loading branch information
k8s-ci-robot authored May 2, 2021
2 parents fdce663 + 6199174 commit b054fb3
Show file tree
Hide file tree
Showing 33 changed files with 2,270 additions and 20 deletions.
9 changes: 8 additions & 1 deletion cmd/kops/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -419,6 +419,14 @@ func TestNTHQueueProcessor(t *testing.T) {
newIntegrationTest("nthsqsresources.example.com", "nth_sqs_resources").runTestCloudformation(t)
}

// TestCustomIRSA runs a simple configuration, but with some additional IAM roles for ServiceAccounts
func TestCustomIRSA(t *testing.T) {
newIntegrationTest("minimal.example.com", "irsa").
withServiceAccountRole("myserviceaccount.default", false).
withServiceAccountRole("myotherserviceaccount.myapp", true).
runTestTerraformAWS(t)
}

func (i *integrationTest) runTest(t *testing.T, h *testutils.IntegrationTestHarness, expectedDataFilenames []string, tfFileName string, expectedTfFileName string, phase *cloudup.Phase) {
ctx := context.Background()

Expand Down Expand Up @@ -620,7 +628,6 @@ func (i *integrationTest) runTestTerraformAWS(t *testing.T) {
}...)
}
}

expectedFilenames = append(expectedFilenames, i.expectServiceAccountRolePolicies...)

i.runTest(t, h, expectedFilenames, tfFileName, tfFileName, nil)
Expand Down
33 changes: 33 additions & 0 deletions k8s/crds/kops.k8s.io_clusters.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1163,6 +1163,39 @@ spec:
type: boolean
permissionsBoundary:
type: string
serviceAccountExternalPermissions:
description: ServiceAccountExternalPermissions defines the relatinship
between Kubernetes ServiceAccounts and permissions with external
resources.
items:
description: ServiceAccountExternalPermissions grants a ServiceAccount
permissions to external resources.
properties:
aws:
description: AWS grants permissions to AWS resources.
properties:
inlinePolicy:
description: InlinePolicy is an IAM Policy that will
be attached inline to the IAM Role.
type: string
policyARNs:
description: PolicyARNs is a list of existing IAM Policies.
items:
type: string
type: array
type: object
name:
description: Name is the name of the Kubernetes ServiceAccount.
type: string
namespace:
description: Namespace is the namespace of the Kubernetes
ServiceAccount.
type: string
required:
- name
- namespace
type: object
type: array
required:
- legacy
type: object
Expand Down
20 changes: 20 additions & 0 deletions pkg/apis/kops/cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,24 @@ type ServiceAccountIssuerDiscoveryConfig struct {
EnableAWSOIDCProvider bool `json:"enableAWSOIDCProvider,omitempty"`
}

// ServiceAccountExternalPermissions grants a ServiceAccount permissions to external resources.
type ServiceAccountExternalPermission struct {
// Name is the name of the Kubernetes ServiceAccount.
Name string `json:"name"`
// Namespace is the namespace of the Kubernetes ServiceAccount.
Namespace string `json:"namespace"`
// AWS grants permissions to AWS resources.
AWS *AWSPermission `json:"aws,omitempty"`
}

// AWSPermission grants permissions to AWS resources.
type AWSPermission struct {
// PolicyARNs is a list of existing IAM Policies.
PolicyARNs []string `json:"policyARNs,omitempty"`
// InlinePolicy is an IAM Policy that will be attached inline to the IAM Role.
InlinePolicy string `json:"inlinePolicy,omitempty"`
}

// NodeAuthorizationSpec is used to node authorization
type NodeAuthorizationSpec struct {
// NodeAuthorizer defined the configuration for the node authorizer
Expand Down Expand Up @@ -280,6 +298,8 @@ type IAMSpec struct {
Legacy bool `json:"legacy"`
AllowContainerRegistry bool `json:"allowContainerRegistry,omitempty"`
PermissionsBoundary *string `json:"permissionsBoundary,omitempty"`
// ServiceAccountExternalPermissions defines the relatinship between Kubernetes ServiceAccounts and permissions with external resources.
ServiceAccountExternalPermissions []ServiceAccountExternalPermission `json:"serviceAccountExternalPermissions,omitempty"`
}

// HookSpec is a definition hook
Expand Down
20 changes: 20 additions & 0 deletions pkg/apis/kops/v1alpha2/cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,24 @@ type ServiceAccountIssuerDiscoveryConfig struct {
EnableAWSOIDCProvider bool `json:"enableAWSOIDCProvider,omitempty"`
}

// ServiceAccountExternalPermissions grants a ServiceAccount permissions to external resources.
type ServiceAccountExternalPermission struct {
// Name is the name of the Kubernetes ServiceAccount.
Name string `json:"name"`
// Namespace is the namespace of the Kubernetes ServiceAccount.
Namespace string `json:"namespace"`
// AWS grants permissions to AWS resources.
AWS *AWSPermission `json:"aws,omitempty"`
}

// AWSPermission grants permissions to AWS resources.
type AWSPermission struct {
// PolicyARNs is a list of existing IAM Policies.
PolicyARNs []string `json:"policyARNs,omitempty"`
// InlinePolicy is an IAM Policy that will be attached inline to the IAM Role.
InlinePolicy string `json:"inlinePolicy,omitempty"`
}

// NodeAuthorizationSpec is used to node authorization
type NodeAuthorizationSpec struct {
// NodeAuthorizer defined the configuration for the node authorizer
Expand Down Expand Up @@ -278,6 +296,8 @@ type IAMSpec struct {
Legacy bool `json:"legacy"`
AllowContainerRegistry bool `json:"allowContainerRegistry,omitempty"`
PermissionsBoundary *string `json:"permissionsBoundary,omitempty"`
// ServiceAccountExternalPermissions defines the relatinship between Kubernetes ServiceAccounts and permissions with external resources.
ServiceAccountExternalPermissions []ServiceAccountExternalPermission `json:"serviceAccountExternalPermissions,omitempty"`
}

// HookSpec is a definition hook
Expand Down
104 changes: 104 additions & 0 deletions pkg/apis/kops/v1alpha2/zz_generated.conversion.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

49 changes: 49 additions & 0 deletions pkg/apis/kops/v1alpha2/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

46 changes: 46 additions & 0 deletions pkg/apis/kops/validation/validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,52 @@ func validateClusterSpec(spec *kops.ClusterSpec, c *kops.Cluster, fieldPath *fie
}
}

if spec.IAM != nil {
if len(spec.IAM.ServiceAccountExternalPermissions) > 0 {
if spec.ServiceAccountIssuerDiscovery == nil || !spec.ServiceAccountIssuerDiscovery.EnableAWSOIDCProvider {
allErrs = append(allErrs, field.Forbidden(fieldPath.Child("iam", "serviceAccountExternalPermissions"), "serviceAccountExternalPermissions requires AWS OIDC Provider to be enabled"))
}
allErrs = append(allErrs, validateSAExternalPermissions(spec.IAM.ServiceAccountExternalPermissions, fieldPath.Child("iam", "serviceAccountExternalPermissions"))...)
}
}

return allErrs
}

func validateSAExternalPermissions(externalPermissions []kops.ServiceAccountExternalPermission, path *field.Path) (allErrs field.ErrorList) {
if len(externalPermissions) == 0 {
return allErrs
}

sas := make(map[string]string)
for _, sa := range externalPermissions {
key := fmt.Sprintf("%s/%s", sa.Namespace, sa.Name)
p := path.Key(key)
if sa.Namespace == "" {
allErrs = append(allErrs, field.Required(p.Child("namespace"), "namespace cannot be empty"))
}
if sa.Name == "" {
allErrs = append(allErrs, field.Required(p.Child("name"), "name cannot be empty"))
}
_, duplicate := sas[key]
if duplicate {
allErrs = append(allErrs, field.Duplicate(p, key))
}
sas[key] = ""
aws := sa.AWS
ap := p.Child("aws")
if aws == nil {
allErrs = append(allErrs, field.Required(ap, "AWS permissions must be set"))
continue
}

if len(aws.PolicyARNs) == 0 && aws.InlinePolicy == "" {
allErrs = append(allErrs, field.Required(ap, "either inlinePolicy or policyARN must be set"))
}
if len(aws.PolicyARNs) > 0 && aws.InlinePolicy != "" {
allErrs = append(allErrs, field.Forbidden(ap, "cannot set both inlinePolicy and policyARN"))
}
}
return allErrs
}

Expand Down
Loading

0 comments on commit b054fb3

Please sign in to comment.