diff --git a/.github/renovate.json5 b/.github/renovate.json5 new file mode 100644 index 0000000..f43c59f --- /dev/null +++ b/.github/renovate.json5 @@ -0,0 +1,42 @@ +{ + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "extends": [ + "config:recommended", + "helpers:pinGitHubActionDigests", + ":semanticCommits" + ], + "rebaseWhen": "conflicted", + "prConcurrentLimit": 5, + "baseBranches": ["main"], + "labels": ["automated"], + "customManagers": [ + { + "customType": "regex", + "description": "Bump up version in the Makefile", + "fileMatch": ["^Makefile$"], + "matchStrings": [ + "UP_VERSION = (?.*?)\\n" + ], + "datasourceTemplate": "github-releases", + "depNameTemplate": "upbound/up", + }, { + "customType": "regex", + "description": "Bump uptest version in the Makefile", + "fileMatch": ["^Makefile$"], + "matchStrings": [ + "UPTEST_VERSION = (?.*?)\\n" + ], + "datasourceTemplate": "github-releases", + "depNameTemplate": "upbound/uptest", + }, { + "customType": "regex", + "description": "Bump providers/functions/configurations in crossplane.yaml", + "fileMatch": ["crossplane.yaml"], + "matchStrings": [ + "#\\s*renovate:\\s*datasource=(?[^\\s]+)\\s+depName=(?[^\\s]+)\\s*\\n\\s*version:\\s*\"(?[^\"]+)\"" + ], + "datasourceTemplate": "{{{datasource}}}", + "depNameTemplate": "{{{depName}}}", + } + ], +} diff --git a/Makefile b/Makefile index 5677b61..c79317b 100644 --- a/Makefile +++ b/Makefile @@ -11,11 +11,9 @@ PLATFORMS ?= linux_amd64 # ==================================================================================== # Setup Kubernetes tools -# set UXP_VERSION because of https://github.com/crossplane/crossplane/issues/5055 -UXP_VERSION = 1.13.2-up.2 -UP_VERSION = v0.19.1 +UP_VERSION = v0.21.0 UP_CHANNEL = stable -UPTEST_VERSION = v0.6.1 +UPTEST_VERSION = v0.9.0 -include build/makelib/k8s_tools.mk # ==================================================================================== @@ -67,7 +65,7 @@ build.init: $(UP) # - UPTEST_DATASOURCE_PATH (optional), see https://github.com/upbound/uptest#injecting-dynamic-values-and-datasource uptest: $(UPTEST) $(KUBECTL) $(KUTTL) @$(INFO) running automated tests - @KUBECTL=$(KUBECTL) KUTTL=$(KUTTL) $(UPTEST) e2e examples/network-xr.yaml,examples/eks-xr.yaml,examples/irsa-xr.yaml --data-source="${UPTEST_DATASOURCE_PATH}" --setup-script=test/setup.sh --default-timeout=2400 || $(FAIL) + @KUBECTL=$(KUBECTL) KUTTL=$(KUTTL) CROSSPLANE_NAMESPACE=$(CROSSPLANE_NAMESPACE) $(UPTEST) e2e examples/network-xr.yaml,examples/eks-xr.yaml,examples/irsa-xr.yaml --data-source="${UPTEST_DATASOURCE_PATH}" --setup-script=test/setup.sh --default-timeout=2400 || $(FAIL) @$(OK) running automated tests # This target requires the following environment variables to be set: diff --git a/README.md b/README.md index 8ca2028..825a2a3 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,71 @@ # AWS EKS IRSA Configuration -AWS EKS IRSA Configuration is reusable Configuration designed to be primarily used in -higher level Configurations. \ No newline at end of file +IRSA (IAM Roles for Service Accounts) is AWS EKS's method for allowing EKS pod applications to access the AWS API through specific AWS IAM roles. +This approach is more advanced than the previous method where applications in pods used the IAM roles of the EKS nodes. +Configuring AWS API access per service account aligns with the principle of least privilege, resulting in a more secure architecture. + +IRSA integrates components from AWS IAM, OpenID Connect, Kubernetes Service Accounts, and Pods. +It involves several interconnected parts from these concepts. +Here's a simplified overview of how these elements work together to enable applications in a pod to utilize an IAM role. + +# Configuration + +This configuration is for implementing AWS IRSA, which involves creating the IAM Role, Policy, Attachment, and configuring the trust relationship between Kubernetes Service Account and AWS EKS OIDC Provider. +## The How + +![irsa-01](images/irsa-01.png) + +- A Pod where the application operates. +- A Service Account, marked with an annotation to indicate its use of an IAM Role. + +![irsa-02](images/irsa-02.png) + +- An IAM Role with a trust relationship directed towards: + - The Kubernetes Service Account authorized to assume that role. + - The AWS OIDC Provider for the EKS Cluster. +## Service Accounts and Service Accounts Tokens + +Creating a Service Account in EKS also generates a Service Account Token. + +![irsa-03](images/irsa-03.png) + +- The Service Account Token is saved as a Kubernetes Secret in the same namespace. +- The token is a JSON Web Token (JWT). + +## Attaching the Service Account to the Pod + +When a Pod is configured to use a Service Account in EKS, several actions are automatically performed: + +![irsa-04](images/irsa-04.png) + +- The Service Account's JWT, which is stored in a secret, gets mounted in the Pod. +- The following environment variables are injected into the Pod: + +```bash +AWS_ROLE_ARN= +AWS_WEB_IDENTITY_TOKEN_FILE= +``` + +## The assume role request + +The AWS SDK, operating within the application inside the Pod, is programmed to reference these environment variables for authentication. +It sends a request to AWS STS to assume the IAM Role, including the Service Account JWT in that request. + +![irsa-05](images/irsa-05.png) + +The request will include: +- The ARN of the IAM Role +- The JWT of the Service Account + +AWS STS will utilize the configuration in the IAM Role's Trust Relationship for validation. + +![irsa-06](images/irsa-06.png) + +- Verify that the Service Account JWT is valid and issued by the trusted OIDC Provider of the EKS Cluster. +- Confirm that the Service Account is authorized to assume the IAM Role. + +## On Success + +AWS STS will provide the application in the Pod with security credentials for the IAM Role, valid for 15 minutes. + +![irsa-07](images/irsa-07.png) diff --git a/apis/composition.yaml b/apis/composition.yaml index 714c724..104d4d0 100644 --- a/apis/composition.yaml +++ b/apis/composition.yaml @@ -6,161 +6,179 @@ spec: compositeTypeRef: apiVersion: aws.platform.upbound.io/v1alpha1 kind: XIRSA - patchSets: - - name: Name - patches: - - type: FromCompositeFieldPath - fromFieldPath: metadata.name - toFieldPath: metadata.annotations[crossplane.io/external-name] - - name: providerConfigRef - patches: - - type: FromCompositeFieldPath - fromFieldPath: spec.parameters.providerConfigName - toFieldPath: spec.providerConfigRef.name - - name: deletionPolicy - patches: - - type: FromCompositeFieldPath - fromFieldPath: spec.parameters.deletionPolicy - toFieldPath: spec.deletionPolicy - resources: - - name: irsaRole - base: - apiVersion: iam.aws.upbound.io/v1beta1 - kind: Role - metadata: - labels: - resource: "Role" - patches: - - type: PatchSet - patchSetName: Name - - type: PatchSet - patchSetName: providerConfigRef - - type: PatchSet - patchSetName: deletionPolicy - - type: ToCompositeFieldPath - fromFieldPath: status.atProvider.arn - toFieldPath: status.roleArn - policy: - fromFieldPath: Optional - - type: ToCompositeFieldPath - fromFieldPath: status.conditions - toFieldPath: status.observed.role.conditions - policy: - fromFieldPath: Optional - - type: CombineFromComposite - toFieldPath: spec.forProvider.assumeRolePolicy - combine: - strategy: string - variables: - - fromFieldPath: status.irsa.oidc_arn - - fromFieldPath: spec.parameters.condition - - fromFieldPath: status.irsa.oidc_host - - fromFieldPath: spec.parameters.serviceAccount.namespace - - fromFieldPath: spec.parameters.serviceAccount.name - string: - fmt: | - { - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Principal": { - "Federated": "%s" - }, - "Action": "sts:AssumeRoleWithWebIdentity", - "Condition": { - "%s": { - "%s:sub": "system:serviceaccount:%s:%s" - } + mode: Pipeline + pipeline: + - step: patch-and-transform + functionRef: + name: upbound-function-patch-and-transform + input: + apiVersion: pt.fn.crossplane.io/v1beta1 + kind: Resources + patchSets: + - name: Name + patches: + - fromFieldPath: metadata.name + toFieldPath: metadata.annotations[crossplane.io/external-name] + type: FromCompositeFieldPath + - name: providerConfigRef + patches: + - fromFieldPath: spec.parameters.providerConfigName + toFieldPath: spec.providerConfigRef.name + type: FromCompositeFieldPath + - name: deletionPolicy + patches: + - fromFieldPath: spec.parameters.deletionPolicy + toFieldPath: spec.deletionPolicy + type: FromCompositeFieldPath + resources: + - name: irsaRole + base: + apiVersion: iam.aws.upbound.io/v1beta1 + kind: Role + metadata: + labels: + resource: Role + patches: + - patchSetName: Name + type: PatchSet + - patchSetName: providerConfigRef + type: PatchSet + - patchSetName: deletionPolicy + type: PatchSet + - fromFieldPath: status.atProvider.arn + policy: + fromFieldPath: Optional + toFieldPath: status.roleArn + type: ToCompositeFieldPath + - fromFieldPath: status.conditions + policy: + fromFieldPath: Optional + toFieldPath: status.observed.role.conditions + type: ToCompositeFieldPath + - combine: + strategy: string + string: + fmt: | + { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": { + "Federated": "%s" + }, + "Action": "sts:AssumeRoleWithWebIdentity", + "Condition": { + "%s": { + "%s:sub": "system:serviceaccount:%s:%s" + } + } + } + ] } - } - ] - } - - name: irsaPolicy - base: - apiVersion: iam.aws.upbound.io/v1beta1 - kind: Policy - metadata: - labels: - resource: "Policy" - patches: - - type: PatchSet - patchSetName: providerConfigRef - - type: PatchSet - patchSetName: deletionPolicy - - fromFieldPath: spec.parameters.policyDocument - toFieldPath: spec.forProvider.policy - - type: ToCompositeFieldPath - fromFieldPath: metadata.annotations[crossplane.io/external-name] - toFieldPath: status.policyArn - - type: ToCompositeFieldPath - fromFieldPath: status.conditions - toFieldPath: status.observed.policy.conditions - policy: - fromFieldPath: Optional - - name: irsaAttachment - base: - apiVersion: iam.aws.upbound.io/v1beta1 - kind: RolePolicyAttachment - metadata: - labels: - resource: "RolePolicyAttachment" - spec: - forProvider: - policyArnSelector: - matchControllerRef: true - matchLabels: - resource: "Policy" - roleSelector: - matchControllerRef: true - matchLabels: - resource: "Role" - patches: - - type: PatchSet - patchSetName: providerConfigRef - - type: PatchSet - patchSetName: deletionPolicy - - type: ToCompositeFieldPath - fromFieldPath: status.conditions - toFieldPath: status.observed.rpa.conditions - policy: - fromFieldPath: Optional - - name: irsaSettings - base: - apiVersion: kubernetes.crossplane.io/v1alpha1 - kind: Object - spec: - managementPolicy: Observe - forProvider: - manifest: - apiVersion: v1 - kind: ConfigMap + variables: + - fromFieldPath: status.irsa.oidc_arn + - fromFieldPath: spec.parameters.condition + - fromFieldPath: status.irsa.oidc_host + - fromFieldPath: spec.parameters.serviceAccount.namespace + - fromFieldPath: spec.parameters.serviceAccount.name + toFieldPath: spec.forProvider.assumeRolePolicy + type: CombineFromComposite + + - name: irsaPolicy + base: + apiVersion: iam.aws.upbound.io/v1beta1 + kind: Policy + metadata: + labels: + resource: Policy + patches: + - patchSetName: providerConfigRef + type: PatchSet + - patchSetName: deletionPolicy + type: PatchSet + - fromFieldPath: spec.parameters.policyDocument + toFieldPath: spec.forProvider.policy + type: FromCompositeFieldPath + - fromFieldPath: metadata.annotations[crossplane.io/external-name] + toFieldPath: status.policyArn + type: ToCompositeFieldPath + - fromFieldPath: status.conditions + policy: + fromFieldPath: Optional + toFieldPath: status.observed.policy.conditions + type: ToCompositeFieldPath + + - name: irsaAttachment + base: + apiVersion: iam.aws.upbound.io/v1beta1 + kind: RolePolicyAttachment metadata: - namespace: default - patches: - - fromFieldPath: spec.parameters.id - toFieldPath: spec.providerConfigRef.name - - type: PatchSet - patchSetName: deletionPolicy - - fromFieldPath: spec.parameters.id - toFieldPath: metadata.annotations[crossplane.io/external-name] - transforms: - - type: string - string: - fmt: "%s-irsa-settings" - - fromFieldPath: spec.parameters.id - toFieldPath: spec.forProvider.manifest.metadata.name - transforms: - - type: string - string: - fmt: "%s-irsa-settings" - - type: ToCompositeFieldPath - fromFieldPath: status.atProvider.manifest.data.oidc_arn - toFieldPath: status.irsa.oidc_arn - policy: - fromFieldPath: Optional - - type: ToCompositeFieldPath - fromFieldPath: status.atProvider.manifest.data.oidc_host - toFieldPath: status.irsa.oidc_host - policy: - fromFieldPath: Optional + labels: + resource: RolePolicyAttachment + spec: + forProvider: + policyArnSelector: + matchControllerRef: true + matchLabels: + resource: Policy + roleSelector: + matchControllerRef: true + matchLabels: + resource: Role + patches: + - patchSetName: providerConfigRef + type: PatchSet + - patchSetName: deletionPolicy + type: PatchSet + - fromFieldPath: status.conditions + policy: + fromFieldPath: Optional + toFieldPath: status.observed.rpa.conditions + type: ToCompositeFieldPath + + - name: irsaSettings + base: + apiVersion: kubernetes.crossplane.io/v1alpha1 + kind: Object + spec: + deletionPolicy: Orphan + forProvider: + manifest: + apiVersion: v1 + kind: ConfigMap + metadata: + namespace: default + managementPolicy: Observe + patches: + - fromFieldPath: spec.parameters.id + toFieldPath: spec.providerConfigRef.name + type: FromCompositeFieldPath + - patchSetName: deletionPolicy + type: PatchSet + - fromFieldPath: spec.parameters.id + toFieldPath: metadata.annotations[crossplane.io/external-name] + transforms: + - string: + fmt: '%s-irsa-settings' + type: Format + type: string + type: FromCompositeFieldPath + - fromFieldPath: spec.parameters.id + toFieldPath: spec.forProvider.manifest.metadata.name + transforms: + - string: + fmt: '%s-irsa-settings' + type: Format + type: string + type: FromCompositeFieldPath + - fromFieldPath: status.atProvider.manifest.data.oidc_arn + policy: + fromFieldPath: Optional + toFieldPath: status.irsa.oidc_arn + type: ToCompositeFieldPath + - fromFieldPath: status.atProvider.manifest.data.oidc_host + policy: + fromFieldPath: Optional + toFieldPath: status.irsa.oidc_host + type: ToCompositeFieldPath diff --git a/crossplane.yaml b/crossplane.yaml index 7c59610..5a58cf5 100644 --- a/crossplane.yaml +++ b/crossplane.yaml @@ -8,7 +8,8 @@ metadata: meta.crossplane.io/license: Apache-2.0 spec: crossplane: - version: ">=v1.13.2-0" + version: ">=v1.14.1-0" dependsOn: - configuration: xpkg.upbound.io/upbound/configuration-aws-eks - version: ">=v0.1.0" + # renovate: datasource=github-releases depName=upbound/configuration-aws-eks + version: "v0.4.0" diff --git a/examples/configuration.yaml b/examples/configuration.yaml index 4917d50..8fefe97 100644 --- a/examples/configuration.yaml +++ b/examples/configuration.yaml @@ -1,6 +1,6 @@ apiVersion: pkg.crossplane.io/v1 kind: Configuration metadata: - name: cofiguration-aws-eks-irsa + name: configuration-aws-eks-irsa spec: - package: xpkg.upbound.io/upbound/configuration-aws-eks-irsa:v0.1.0 + package: xpkg.upbound.io/upbound/configuration-aws-eks-irsa:v0.3.0 diff --git a/images/irsa-01.png b/images/irsa-01.png new file mode 100644 index 0000000..94a771c Binary files /dev/null and b/images/irsa-01.png differ diff --git a/images/irsa-02.png b/images/irsa-02.png new file mode 100644 index 0000000..b0716e1 Binary files /dev/null and b/images/irsa-02.png differ diff --git a/images/irsa-03.png b/images/irsa-03.png new file mode 100644 index 0000000..c73133f Binary files /dev/null and b/images/irsa-03.png differ diff --git a/images/irsa-04.png b/images/irsa-04.png new file mode 100644 index 0000000..b3b33bb Binary files /dev/null and b/images/irsa-04.png differ diff --git a/images/irsa-05.png b/images/irsa-05.png new file mode 100644 index 0000000..e79d1c6 Binary files /dev/null and b/images/irsa-05.png differ diff --git a/images/irsa-06.png b/images/irsa-06.png new file mode 100644 index 0000000..c4fd161 Binary files /dev/null and b/images/irsa-06.png differ diff --git a/images/irsa-07.png b/images/irsa-07.png new file mode 100644 index 0000000..b14a064 Binary files /dev/null and b/images/irsa-07.png differ