Skip to content

Commit

Permalink
Add support for IRSA in he api
Browse files Browse the repository at this point in the history
Apply suggestions from code review

Co-authored-by: John Gardiner Myers <[email protected]>
  • Loading branch information
Ole Markus With and johngmyers committed May 1, 2021
1 parent 3704ffd commit 06851c5
Show file tree
Hide file tree
Showing 34 changed files with 2,265 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 @@ -414,6 +414,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 @@ -615,7 +623,6 @@ func (i *integrationTest) runTestTerraformAWS(t *testing.T) {
}...)
}
}

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

i.runTest(t, h, expectedFilenames, tfFileName, tfFileName, nil)
Expand Down
27 changes: 27 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,33 @@ 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:
type: string
policyARNs:
items:
type: string
type: array
type: object
name:
type: string
namespace:
type: string
required:
- name
- namespace
type: object
type: array
required:
- legacy
type: object
Expand Down
16 changes: 16 additions & 0 deletions pkg/apis/kops/cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,20 @@ type ServiceAccountIssuerDiscoveryConfig struct {
EnableAWSOIDCProvider bool `json:"enableAWSOIDCProvider,omitempty"`
}

// ServiceAccountExternalPermissions grants a ServiceAccount permissions to external resources.
type ServiceAccountExternalPermission struct {
Name string `json:"name"`
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 []string `json:"policyARNs,omitempty"`
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 +294,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
16 changes: 16 additions & 0 deletions pkg/apis/kops/v1alpha2/cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,20 @@ type ServiceAccountIssuerDiscoveryConfig struct {
EnableAWSOIDCProvider bool `json:"enableAWSOIDCProvider,omitempty"`
}

// ServiceAccountExternalPermissions grants a ServiceAccount permissions to external resources.
type ServiceAccountExternalPermission struct {
Name string `json:"name"`
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 []string `json:"policyARNs,omitempty"`
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 +292,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.

1 change: 1 addition & 0 deletions pkg/apis/kops/validation/BUILD.bazel

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.Required(fieldPath.Child("serviceAccountIssuerDiscovery", "enableAWSOIDCProvider"), "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 06851c5

Please sign in to comment.