From 89d7d125e353d75f5b86483dcffa8182d3c0d64a Mon Sep 17 00:00:00 2001 From: Christopher Haar Date: Fri, 16 Aug 2024 14:26:36 +0200 Subject: [PATCH] feat(initial): add pod identity Signed-off-by: Christopher Haar --- .github/renovate.json5 | 42 +++++++ .github/workflows/ci.yaml | 76 +++++++++++ .github/workflows/e2e.yaml | 14 +++ .github/workflows/tag.yml | 26 ++++ .github/workflows/yamllint.yaml | 15 +++ .gitignore | 12 ++ .gitmodules | 3 + .yamllint | 5 + LICENSE | 201 ++++++++++++++++++++++++++++++ Makefile | 123 ++++++++++++++++++ README.md | 135 ++++++++++++++++++++ apis/definition.yaml | 177 ++++++++++++++++++++++++++ apis/kcl/generate.k | 45 +++++++ apis/kcl/main.k | 77 ++++++++++++ apis/pat/composition.yaml | 108 ++++++++++++++++ build | 1 + crossplane.yaml | 15 +++ examples/configuration.yaml | 6 + examples/functions.yaml | 20 +++ examples/kcl/eks-xr.yaml | 18 +++ examples/kcl/network-xr.yaml | 8 ++ examples/kcl/pod-identity-xr.yaml | 34 +++++ examples/pat/eks-xr.yaml | 15 +++ examples/pat/network-xr.yaml | 8 ++ examples/pat/pod-identity-xr.yaml | 31 +++++ images/s3-access-podidentity.png | Bin 0 -> 53227 bytes test/setup.sh | 35 ++++++ 27 files changed, 1250 insertions(+) create mode 100644 .github/renovate.json5 create mode 100644 .github/workflows/ci.yaml create mode 100644 .github/workflows/e2e.yaml create mode 100644 .github/workflows/tag.yml create mode 100644 .github/workflows/yamllint.yaml create mode 100644 .gitignore create mode 100644 .gitmodules create mode 100644 .yamllint create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 README.md create mode 100644 apis/definition.yaml create mode 100644 apis/kcl/generate.k create mode 100644 apis/kcl/main.k create mode 100644 apis/pat/composition.yaml create mode 160000 build create mode 100644 crossplane.yaml create mode 100644 examples/configuration.yaml create mode 100644 examples/functions.yaml create mode 100644 examples/kcl/eks-xr.yaml create mode 100644 examples/kcl/network-xr.yaml create mode 100644 examples/kcl/pod-identity-xr.yaml create mode 100644 examples/pat/eks-xr.yaml create mode 100644 examples/pat/network-xr.yaml create mode 100644 examples/pat/pod-identity-xr.yaml create mode 100644 images/s3-access-podidentity.png create mode 100755 test/setup.sh 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/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 0000000..746f509 --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,76 @@ +name: CI + +on: + push: + branches: + - main + - release-* + workflow_dispatch: {} + +env: + DOCKER_BUILDX_VERSION: 'v0.8.2' + + XPKG_ACCESS_ID: ${{ secrets.XPKG_ACCESS_ID }} + +jobs: + detect-noop: + runs-on: ubuntu-22.04 + outputs: + noop: ${{ steps.noop.outputs.should_skip }} + steps: + - name: Detect No-op Changes + id: noop + uses: fkirc/skip-duplicate-actions@f75f66ce1886f00957d99748a42c724f4330bdcf # v5.3.1 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + paths_ignore: '["**.md", "**.png", "**.jpg"]' + do_not_skip: '["workflow_dispatch", "schedule", "push"]' + + publish-artifacts: + runs-on: ubuntu-22.04 + needs: detect-noop + if: needs.detect-noop.outputs.noop != 'true' + + steps: + - name: Setup QEMU + uses: docker/setup-qemu-action@68827325e0b33c7199eb31dd4e31fbe9023e06e3 # v3 + with: + platforms: all + + - name: Setup Docker Buildx + uses: docker/setup-buildx-action@f95db51fddba0c2d1ec667646a06c2ce06100226 # v3 + with: + version: ${{ env.DOCKER_BUILDX_VERSION }} + install: true + + - name: Checkout + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4 + with: + submodules: true + + - name: Fetch History + run: git fetch --prune --unshallow + + - name: Build Artifacts + run: make -j2 build.all + env: + # We're using docker buildx, which doesn't actually load the images it + # builds by default. Specifying --load does so. + BUILD_ARGS: "--load" + + - name: Publish Artifacts to GitHub + uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4 + with: + name: output + path: _output/** + + - name: Login to Upbound + uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # v3 + if: env.XPKG_ACCESS_ID != '' + with: + registry: xpkg.upbound.io + username: ${{ secrets.XPKG_ACCESS_ID }} + password: ${{ secrets.XPKG_TOKEN }} + + - name: Publish Artifacts + run: make -j2 publish BRANCH_NAME=${GITHUB_REF##*/} \ No newline at end of file diff --git a/.github/workflows/e2e.yaml b/.github/workflows/e2e.yaml new file mode 100644 index 0000000..d310447 --- /dev/null +++ b/.github/workflows/e2e.yaml @@ -0,0 +1,14 @@ +name: End to End Testing + +on: + issue_comment: + types: [created] + +jobs: + e2e: + uses: upbound/official-providers-ci/.github/workflows/pr-comment-trigger.yml@main + with: + package-type: configuration + secrets: + UPTEST_CLOUD_CREDENTIALS: ${{ secrets.UPTEST_CLOUD_CREDENTIALS }} + UPTEST_DATASOURCE: ${{ secrets.UPTEST_DATASOURCE }} diff --git a/.github/workflows/tag.yml b/.github/workflows/tag.yml new file mode 100644 index 0000000..f680650 --- /dev/null +++ b/.github/workflows/tag.yml @@ -0,0 +1,26 @@ +name: Tag + +on: + workflow_dispatch: + inputs: + version: + description: 'Release version (e.g. v0.1.0)' + required: true + message: + description: 'Tag message' + required: true + +jobs: + create-tag: + runs-on: ubuntu-22.04 + + steps: + - name: Checkout + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4 + + - name: Create Tag + uses: negz/create-tag@39bae1e0932567a58c20dea5a1a0d18358503320 # v1 + with: + version: ${{ github.event.inputs.version }} + message: ${{ github.event.inputs.message }} + token: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/yamllint.yaml b/.github/workflows/yamllint.yaml new file mode 100644 index 0000000..54684f4 --- /dev/null +++ b/.github/workflows/yamllint.yaml @@ -0,0 +1,15 @@ +name: yamllint +on: [pull_request] +jobs: + yamllint: + name: runner / yamllint + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4 + - name: yamllint + uses: reviewdog/action-yamllint@e419e43894e391d358ebf996800673d72de6c69b # v1.11.0 + with: + reporter: github-pr-review + filter_mode: nofilter + yamllint_flags: 'apis/' + fail_on_error: true diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..119de6c --- /dev/null +++ b/.gitignore @@ -0,0 +1,12 @@ +/.cache +/.work +/_output +/results +/.idea +/.kclvm + +*.xpkg +kubeconfig + +# generated by kcl +apis/kcl/composition.yaml diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..8f84209 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "build"] + path = build + url = https://github.com/crossplane/build diff --git a/.yamllint b/.yamllint new file mode 100644 index 0000000..669c864 --- /dev/null +++ b/.yamllint @@ -0,0 +1,5 @@ +extends: default + +rules: + line-length: disable + document-start: disable diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d8e2ebf --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [2023] [upbound.io] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..f2446ed --- /dev/null +++ b/Makefile @@ -0,0 +1,123 @@ +# Project Setup +PROJECT_NAME := configuration-aws-eks-pod-identity +PROJECT_REPO := github.com/upbound/$(PROJECT_NAME) + +# NOTE(hasheddan): the platform is insignificant here as Configuration package +# images are not architecture-specific. We constrain to one platform to avoid +# needlessly pushing a multi-arch image. +PLATFORMS ?= linux_amd64 +-include build/makelib/common.mk + +# ==================================================================================== +# Setup Kubernetes tools + +UP_VERSION = v0.32.1 +UP_CHANNEL = stable +UPTEST_VERSION = v0.13.1 +CROSSPLANE_CLI_VERSION=v1.16.0 + +-include build/makelib/k8s_tools.mk +# ==================================================================================== +# Setup XPKG +XPKG_DIR = $(shell pwd) +XPKG_IGNORE = .github/workflows/*.yaml,.github/workflows/*.yml,examples/*.yaml,.work/uptest-datasource.yaml +XPKG_REG_ORGS ?= xpkg.upbound.io/upbound +# NOTE(hasheddan): skip promoting on xpkg.upbound.io as channel tags are +# inferred. +XPKG_REG_ORGS_NO_PROMOTE ?= xpkg.upbound.io/upbound +XPKGS = $(PROJECT_NAME) +-include build/makelib/xpkg.mk + +CROSSPLANE_VERSION = 1.16.0-up.1 +CROSSPLANE_CHART_REPO = https://charts.upbound.io/stable +CROSSPLANE_CHART_NAME = universal-crossplane +CROSSPLANE_NAMESPACE = upbound-system +CROSSPLANE_ARGS = "--enable-usages" +KIND_CLUSTER_NAME = uptest-$(PROJECT_NAME) +-include build/makelib/local.xpkg.mk +-include build/makelib/controlplane.mk + +# ==================================================================================== +# Targets + +# run `make help` to see the targets and options + +# We want submodules to be set up the first time `make` is run. +# We manage the build/ folder and its Makefiles as a submodule. +# The first time `make` is run, the includes of build/*.mk files will +# all fail, and this target will be run. The next time, the default as defined +# by the includes will be run instead. +fallthrough: submodules + @echo Initial setup complete. Running make again . . . + @make + +# Update the submodules, such as the common build scripts. +submodules: + @git submodule sync + @git submodule update --init --recursive + +# We must ensure up is installed in tool cache prior to build as including the k8s_tools machinery prior to the xpkg +# machinery sets UP to point to tool cache. +build.init: $(UP) + +# ==================================================================================== +# End to End Testing + +# This target requires the following environment variables to be set: +# - UPTEST_CLOUD_CREDENTIALS, cloud credentials for the provider being tested, e.g. export UPTEST_CLOUD_CREDENTIALS=$(cat ~/.aws/credentials) +# - To ensure the proper functioning of the end-to-end test resource pre-deletion hook, it is crucial to arrange your resources appropriately. +# You can check the basic implementation here: https://github.com/upbound/uptest/blob/main/internal/templates/01-delete.yaml.tmpl. +# - UPTEST_DATASOURCE_PATH (optional), see https://github.com/upbound/uptest#injecting-dynamic-values-and-datasource +SKIP_DELETE ?= +uptest: $(UPTEST) $(KUBECTL) $(KUTTL) + @$(INFO) running automated tests + @KUBECTL=$(KUBECTL) KUTTL=$(KUTTL) CROSSPLANE_NAMESPACE=$(CROSSPLANE_NAMESPACE) CROSSPLANE_CLI=$(CROSSPLANE_CLI) $(UPTEST) e2e "${UPTEST_EXAMPLE_LIST}" --data-source="${UPTEST_DATASOURCE_PATH}" --setup-script=test/setup.sh --default-timeout=2400 $(SKIP_DELETE) || $(FAIL) + @$(OK) running automated tests + +# This target requires the following environment variables to be set: +# - UPTEST_CLOUD_CREDENTIALS, cloud credentials for the provider being tested, e.g. export UPTEST_CLOUD_CREDENTIALS=$(cat ~/.aws/credentials) +# Use `make e2e SKIP_DELETE=--skip-delete` to skip deletion of resources created during the test. +e2e: build controlplane.up local.xpkg.deploy.configuration.$(PROJECT_NAME) uptest ## Run uptest together with all dependencies. Use `make e2e SKIP_DELETE=--skip-delete` to skip deletion of resources. + +kcl: $(KCL) ## Generate KCL-based Composition + $(KCL) apis/kcl/generate.k + +render: kcl $(CROSSPLANE_CLI) ${YQ} + @indir="./examples"; \ + for file in $$(find $$indir -type f -name '*.yaml' ); do \ + doc_count=$$(grep -c '^---' "$$file"); \ + if [[ $$doc_count -gt 0 ]]; then \ + continue; \ + fi; \ + COMPOSITION=$$(${YQ} eval '.metadata.annotations."render.crossplane.io/composition-path"' $$file); \ + FUNCTION=$$(${YQ} eval '.metadata.annotations."render.crossplane.io/function-path"' $$file); \ + ENVIRONMENT=$$(${YQ} eval '.metadata.annotations."render.crossplane.io/environment-path"' $$file); \ + OBSERVE=$$(${YQ} eval '.metadata.annotations."render.crossplane.io/observe-path"' $$file); \ + if [[ "$$ENVIRONMENT" == "null" ]]; then \ + ENVIRONMENT=""; \ + fi; \ + if [[ "$$OBSERVE" == "null" ]]; then \ + OBSERVE=""; \ + fi; \ + if [[ "$$COMPOSITION" == "null" || "$$FUNCTION" == "null" ]]; then \ + continue; \ + fi; \ + ENVIRONMENT=$${ENVIRONMENT=="null" ? "" : $$ENVIRONMENT}; \ + OBSERVE=$${OBSERVE=="null" ? "" : $$OBSERVE}; \ + $(CROSSPLANE_CLI) beta render $$file $$COMPOSITION $$FUNCTION $${ENVIRONMENT:+-e $$ENVIRONMENT} $${OBSERVE:+-o $$OBSERVE} -x; \ + done + +yamllint: ## Static yamllint check + @$(INFO) running yamllint + @yamllint ./apis || $(FAIL) + @$(OK) running yamllint + +kcllint: ## Static kcllint check + @$(INFO) running kcllint + @$(KCL) lint ./apis/kcl/ || $(FAIL) + @$(OK) running kcllint + +help.local: + @grep -E '^[a-zA-Z_-]+.*:.*?## .*$$' Makefile | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' + +.PHONY: uptest e2e render yamllint kcllint help.local diff --git a/README.md b/README.md new file mode 100644 index 0000000..b98c0d7 --- /dev/null +++ b/README.md @@ -0,0 +1,135 @@ +# AWS EKS Pod Identity Configuration + +At a high level, EKS Pod Identity allows you to use the AWS API to define permissions that specific Kubernetes service accounts should have in AWS: + +## Configuration +This configuration is for implementing AWS EKS Pod Identity, which involves creating the IAM Role and configuring the EKS PodIdentityAssociation. + +```bash +apiVersion: pkg.crossplane.io/v1 +kind: Configuration +metadata: + name: configuration-aws-eks-pod-identity +spec: + package: xpkg.upbound.io/upbound/configuration-aws-eks-pod-identity:v0.1.0 +``` + +## The How + +The following step is automatically completed when you install your EKS Cluster using our predefined configuration for AWS EKS - which is installed as dependency per default: + +```bash +apiVersion: pkg.crossplane.io/v1 +kind: Configuration +metadata: + name: configuration-aws-eks +spec: + package: xpkg.upbound.io/upbound/configuration-aws-eks:v0.12.0 +``` + +Setting up Pod Identity starts by installing an add-on: +https://github.com/aws/eks-pod-identity-agent + +```bash +aws eks create-addon \ + --cluster-name cluster-name \ + --addon-name eks-pod-identity-agent +``` + +This sets up a new DaemonSet in the kube-system namespace: + +```bash +$ kubectl get daemonset -n kube-system +NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE +eks-pod-identity-agent 2 2 2 2 2 +``` + +![pod-identity](images/s3-access-podidentity.png) +https://github.com/awslabs/crossplane-on-eks + +### EKS Pod Identity at a glance + +```bash +aws eks create-pod-identity-association \ + --cluster-name your-cluster \ + --namespace default \ + --service-account pod-service-account \ + --role-arn arn:aws:iam::012345678901:role/YourPodRole +``` + +Here, YourPodRole has the following trust policy: + +```json +{ + "Version": "2012-10-17", + "Statement": [{ + "Effect": "Allow", + "Principal": { + "Service": "pods.eks.amazonaws.com" + }, + "Action": ["sts:AssumeRole","sts:TagSession"] + }] +} +``` + +Once you’ve run the commands to configure Pod Identity, any pod that runs under the pod-service-account service account magically has access to AWS resources, through temporary Security Token Service (STS) credentials: + +```bash + +$ kubectl apply -f - < any { + { annotations = { "krm.kcl.dev/composition-resource-name" = name }} +} + +_defaults ={ + deletionPolicy = option("params")?.oxr?.spec.parameters.deletionPolicy or "Delete" + providerConfigRef.name = option("params")?.oxr?.spec.parameters.providerConfigName or "default" +} + +inlinePolicy = option("params")?.oxr?.spec.parameters.inlinePolicy or False +permissionBoundaryArn = option("params")?.oxr?.spec.parameters.permissionsBoundaryArn or False +managedPolicyArns = option("params")?.oxr?.spec.parameters.managedPolicyArns or False +_items = [{ + apiVersion = "iam.aws.upbound.io/v1beta1" + kind = "Role" + metadata = _metadata("iamRole") + spec = _defaults | { + forProvider = { + if inlinePolicy: + inlinePolicy = inlinePolicy + if permissionBoundaryArn: + permissionBoundary = permissionBoundaryArn + if managedPolicyArns: + managedPolicyArns = managedPolicyArns + assumeRolePolicy: """ + { + "Version":"2012-10-17", + "Statement":[ + { + "Sid":"AllowEksAuthToAssumeRoleForPodIdentity", + "Effect":"Allow", + "Principal":{ + "Service":"pods.eks.amazonaws.com" + }, + "Action":[ + "sts:AssumeRole", + "sts:TagSession" + ] + } + ] + }""" + } + } +}] + +clusterName = option("params")?.oxr?.spec.parameters.clusterName or False +clusterNameRef = option("params")?.oxr?.spec.parameters.clusterNameRef or False +clusterNameSelector = option("params")?.oxr?.spec.parameters.clusterNameSelector or False +serviceAccount = option("params")?.oxr?.spec.parameters.serviceAccount.name or False +namespace = option("params")?.oxr?.spec.parameters.serviceAccount.namespace or False +_items += [{ + apiVersion = "eks.aws.upbound.io/v1beta1" + kind = "PodIdentityAssociation" + metadata = _metadata("podIdentityAssociation") + spec = _defaults | { + forProvider = { + region = region + roleArnSelector: { + matchControllerRef: True + } + if clusterName: + clusterName = clusterName + if clusterNameRef: + clusterNameRef = clusterNameRef + if clusterNameSelector: + clusterNameSelector = clusterNameSelector + serviceAccount = serviceAccount + namespace = namespace + } + } +}] + +items = _items \ No newline at end of file diff --git a/apis/pat/composition.yaml b/apis/pat/composition.yaml new file mode 100644 index 0000000..38232a1 --- /dev/null +++ b/apis/pat/composition.yaml @@ -0,0 +1,108 @@ +apiVersion: apiextensions.crossplane.io/v1 +kind: Composition +metadata: + name: pat.xpodidentities.aws.platform.upbound.io + labels: + provider: aws + function: patch-and-transform +spec: + writeConnectionSecretsToNamespace: upbound-system + compositeTypeRef: + apiVersion: aws.platform.upbound.io/v1alpha1 + kind: XPodIdentity + mode: Pipeline + pipeline: + - step: patch-and-transform + functionRef: + name: crossplane-contrib-function-patch-and-transform + input: + apiVersion: pt.fn.crossplane.io/v1beta1 + kind: Resources + patchSets: + - 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 + - name: region + patches: + - fromFieldPath: spec.parameters.region + toFieldPath: spec.forProvider.region + type: FromCompositeFieldPath + resources: + - name: podIdentityAssociation + base: + apiVersion: eks.aws.upbound.io/v1beta1 + kind: PodIdentityAssociation + spec: + forProvider: + roleArnSelector: + matchControllerRef: true + patches: + - patchSetName: providerConfigRef + type: PatchSet + - patchSetName: deletionPolicy + type: PatchSet + - patchSetName: region + type: PatchSet + - type: FromCompositeFieldPath + fromFieldPath: spec.parameters.clusterName + toFieldPath: spec.forProvider.clusterName + - type: FromCompositeFieldPath + fromFieldPath: spec.parameters.clusterNameRef + toFieldPath: spec.forProvider.clusterNameRef + - type: FromCompositeFieldPath + fromFieldPath: spec.parameters.clusterNameSelector + toFieldPath: spec.forProvider.clusterNameSelector + - type: FromCompositeFieldPath + fromFieldPath: spec.parameters.serviceAccount.name + toFieldPath: spec.forProvider.serviceAccount + - type: FromCompositeFieldPath + fromFieldPath: spec.parameters.serviceAccount.namespace + toFieldPath: spec.forProvider.namespace + + - name: iamRole + base: + apiVersion: iam.aws.upbound.io/v1beta1 + kind: Role + spec: + forProvider: + assumeRolePolicy: | + { + "Version":"2012-10-17", + "Statement":[ + { + "Sid":"AllowEksAuthToAssumeRoleForPodIdentity", + "Effect":"Allow", + "Principal":{ + "Service":"pods.eks.amazonaws.com" + }, + "Action":[ + "sts:AssumeRole", + "sts:TagSession" + ] + } + ] + } + patches: + - patchSetName: providerConfigRef + type: PatchSet + - patchSetName: deletionPolicy + type: PatchSet + - type: FromCompositeFieldPath + fromFieldPath: spec.parameters.inlinePolicy + toFieldPath: spec.forProvider.inlinePolicy + - type: FromCompositeFieldPath + fromFieldPath: spec.parameters.managedPolicyArns + toFieldPath: spec.forProvider.managedPolicyArns + - type: FromCompositeFieldPath + fromFieldPath: spec.parameters.permissionsBoundaryArn + toFieldPath: spec.forProvider.permissionsBoundary + - type: ToCompositeFieldPath + fromFieldPath: status.atProvider.arn + toFieldPath: status.podIdentity.roleArn diff --git a/build b/build new file mode 160000 index 0000000..3cf6663 --- /dev/null +++ b/build @@ -0,0 +1 @@ +Subproject commit 3cf6663fafcf22f5cb3e7b90cf21d981faa52230 diff --git a/crossplane.yaml b/crossplane.yaml new file mode 100644 index 0000000..200b25f --- /dev/null +++ b/crossplane.yaml @@ -0,0 +1,15 @@ +apiVersion: meta.pkg.crossplane.io/v1alpha1 +kind: Configuration +metadata: + name: configuration-aws-eks-pod-identity + annotations: + meta.crossplane.io/maintainer: Upbound + meta.crossplane.io/source: github.com/upbound/configuration-aws-eks-pod-identity + meta.crossplane.io/license: Apache-2.0 +spec: + crossplane: + version: ">=v1.14.1-0" + dependsOn: + - configuration: xpkg.upbound.io/upbound/configuration-aws-eks + # renovate: datasource=github-releases depName=upbound/configuration-aws-eks + version: "v0.12.0" diff --git a/examples/configuration.yaml b/examples/configuration.yaml new file mode 100644 index 0000000..bb8c0d0 --- /dev/null +++ b/examples/configuration.yaml @@ -0,0 +1,6 @@ +apiVersion: pkg.crossplane.io/v1 +kind: Configuration +metadata: + name: configuration-aws-eks-pod-identity +spec: + package: xpkg.upbound.io/upbound/configuration-aws-eks-pod-identity:v0.1.0 diff --git a/examples/functions.yaml b/examples/functions.yaml new file mode 100644 index 0000000..9f8d34e --- /dev/null +++ b/examples/functions.yaml @@ -0,0 +1,20 @@ +apiVersion: pkg.crossplane.io/v1beta1 +kind: Function +metadata: + name: crossplane-contrib-function-patch-and-transform +spec: + package: xpkg.upbound.io/crossplane-contrib/function-patch-and-transform:v0.7.0 +--- +apiVersion: pkg.crossplane.io/v1beta1 +kind: Function +metadata: + name: crossplane-contrib-function-kcl +spec: + package: xpkg.upbound.io/crossplane-contrib/function-kcl:v0.9.4 +--- +apiVersion: pkg.crossplane.io/v1beta1 +kind: Function +metadata: + name: crossplane-contrib-function-auto-ready +spec: + package: xpkg.upbound.io/crossplane-contrib/function-auto-ready:v0.2.1 diff --git a/examples/kcl/eks-xr.yaml b/examples/kcl/eks-xr.yaml new file mode 100644 index 0000000..76b8874 --- /dev/null +++ b/examples/kcl/eks-xr.yaml @@ -0,0 +1,18 @@ +apiVersion: aws.platform.upbound.io/v1alpha1 +kind: XEKS +metadata: + name: configuration-aws-eks-pod-identity-kcl +spec: + compositionSelector: + matchLabels: + function: kcl + parameters: + id: configuration-aws-eks-pod-identity-kcl + region: us-west-2 + version: "1.27" + nodes: + count: 1 + instanceType: t3.small + writeConnectionSecretToRef: + name: configuration-aws-eks-pod-identity-kcl-kubeconfig + namespace: upbound-system diff --git a/examples/kcl/network-xr.yaml b/examples/kcl/network-xr.yaml new file mode 100644 index 0000000..58deac2 --- /dev/null +++ b/examples/kcl/network-xr.yaml @@ -0,0 +1,8 @@ +apiVersion: aws.platform.upbound.io/v1alpha1 +kind: XNetwork +metadata: + name: configuration-aws-eks-pod-identity-kcl +spec: + parameters: + id: configuration-aws-eks-pod-identity-kcl + region: us-west-2 diff --git a/examples/kcl/pod-identity-xr.yaml b/examples/kcl/pod-identity-xr.yaml new file mode 100644 index 0000000..dca0f1e --- /dev/null +++ b/examples/kcl/pod-identity-xr.yaml @@ -0,0 +1,34 @@ +apiVersion: aws.platform.upbound.io/v1alpha1 +kind: XPodIdentity +metadata: + name: configuration-aws-eks-pod-identity-kcl +spec: + compositionSelector: + matchLabels: + function: kcl + parameters: + region: us-west-2 + clusterNameSelector: + matchLabels: + crossplane.io/composite: configuration-aws-eks-pod-identity-kcl + managedPolicyArns: + - arn:aws:iam::aws:policy/AmazonEKS_CNI_Policy + inlinePolicy: + - name: default + policy: | + { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "kms:Decrypt", + "kms:DescribeKey" + ], + "Effect": "Allow", + "Resource": "*" + } + ] + } + serviceAccount: + name: my-controller + namespace: kube-system diff --git a/examples/pat/eks-xr.yaml b/examples/pat/eks-xr.yaml new file mode 100644 index 0000000..c51e8dd --- /dev/null +++ b/examples/pat/eks-xr.yaml @@ -0,0 +1,15 @@ +apiVersion: aws.platform.upbound.io/v1alpha1 +kind: XEKS +metadata: + name: configuration-aws-eks-pod-identity +spec: + parameters: + id: configuration-aws-eks-pod-identity + region: us-west-2 + version: "1.27" + nodes: + count: 1 + instanceType: t3.small + writeConnectionSecretToRef: + name: configuration-aws-eks-pod-identity-kubeconfig + namespace: upbound-system diff --git a/examples/pat/network-xr.yaml b/examples/pat/network-xr.yaml new file mode 100644 index 0000000..a9224f2 --- /dev/null +++ b/examples/pat/network-xr.yaml @@ -0,0 +1,8 @@ +apiVersion: aws.platform.upbound.io/v1alpha1 +kind: XNetwork +metadata: + name: configuration-aws-eks-pod-identity +spec: + parameters: + id: configuration-aws-eks-pod-identity + region: us-west-2 diff --git a/examples/pat/pod-identity-xr.yaml b/examples/pat/pod-identity-xr.yaml new file mode 100644 index 0000000..c52f4c3 --- /dev/null +++ b/examples/pat/pod-identity-xr.yaml @@ -0,0 +1,31 @@ +apiVersion: aws.platform.upbound.io/v1alpha1 +kind: XPodIdentity +metadata: + name: configuration-aws-eks-pod-identity +spec: + parameters: + region: us-west-2 + clusterNameSelector: + matchLabels: + crossplane.io/composite: configuration-aws-eks-pod-identity + managedPolicyArns: + - arn:aws:iam::aws:policy/AmazonEKS_CNI_Policy + inlinePolicy: + - name: default + policy: | + { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "kms:Decrypt", + "kms:DescribeKey" + ], + "Effect": "Allow", + "Resource": "*" + } + ] + } + serviceAccount: + name: my-controller + namespace: kube-system diff --git a/images/s3-access-podidentity.png b/images/s3-access-podidentity.png new file mode 100644 index 0000000000000000000000000000000000000000..426f5093cdc0f7356f33cb025ae3d11cd031a6a8 GIT binary patch literal 53227 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 X>MU@IED{W1fiHO(73oq*qrm?GN4BS= literal 0 HcmV?d00001 diff --git a/test/setup.sh b/test/setup.sh new file mode 100755 index 0000000..ab6516a --- /dev/null +++ b/test/setup.sh @@ -0,0 +1,35 @@ +#!/usr/bin/env bash +set -aeuo pipefail + +echo "Running setup.sh" +echo "Waiting until all configurations are healthy/installed..." +"${KUBECTL}" wait configuration.pkg --all --for=condition=Healthy --timeout 5m +"${KUBECTL}" wait configuration.pkg --all --for=condition=Installed --timeout 5m + +echo "Creating cloud credential secret..." +"${KUBECTL}" -n upbound-system create secret generic aws-creds --from-literal=credentials="${UPTEST_CLOUD_CREDENTIALS}" \ + --dry-run=client -o yaml | "${KUBECTL}" apply -f - + +echo "Waiting until all installed provider packages are healthy..." +"${KUBECTL}" wait provider.pkg --all --for condition=Healthy --timeout 5m + +echo "Waiting for all pods to come online..." +"${KUBECTL}" -n upbound-system wait --for=condition=Available deployment --all --timeout=5m + +echo "Waiting for all XRDs to be established..." +"${KUBECTL}" wait xrd --all --for condition=Established + +echo "Creating a default provider config..." +cat <