From 008268ee00e1a07e28ef2456ff4a2748b9fa5fc8 Mon Sep 17 00:00:00 2001 From: Moritz Johner Date: Fri, 21 Jan 2022 21:05:37 +0100 Subject: [PATCH] feat(e2e): implement aws tests, enhance gcp tests Signed-off-by: Moritz Johner --- .github/workflows/e2e-managed.yml | 112 +++++++--- .github/workflows/e2e.yml | 8 +- .github/workflows/ok-to-test-managed.yml | 1 + Makefile | 38 ++-- e2e/Dockerfile | 2 +- e2e/Makefile | 46 ++-- e2e/e2e_test.go | 15 +- e2e/entrypoint.sh | 15 +- e2e/framework/addon/addon.go | 2 +- e2e/framework/addon/chart.go | 2 +- e2e/framework/addon/eso.go | 137 ++++++------ e2e/framework/addon/localstack.go | 44 ---- e2e/framework/addon/vault.go | 2 +- e2e/framework/eso.go | 2 +- e2e/framework/framework.go | 17 +- e2e/framework/log/log.go | 6 +- e2e/framework/util/util.go | 22 +- e2e/k8s/eso.scoped.values.yaml | 12 - e2e/k8s/eso.values.yaml | 11 - e2e/run.sh | 20 +- e2e/suite/akeyless/akeyless.go | 12 +- e2e/suite/akeyless/provider.go | 9 +- e2e/suite/alibaba/alibaba.go | 16 +- e2e/suite/alibaba/provider.go | 10 +- e2e/suite/aws/provider.go | 183 ++++++++++++--- e2e/suite/aws/secretsmanager.go | 95 +------- e2e/suite/aws/secretsmanager_managed.go | 102 +++++++++ e2e/suite/azure/azure.go | 17 +- e2e/suite/azure/provider.go | 26 ++- e2e/suite/gcp/gcp.go | 124 +++++------ e2e/suite/gcp/gcp_managed.go | 111 +++++++++ e2e/suite/gcp/provider.go | 210 +++++++++--------- e2e/suite/gcpmanaged/gcpmanaged.go | 86 ------- e2e/suite/gitlab/gitlab.go | 18 +- e2e/suite/gitlab/provider.go | 9 +- e2e/suite/import.go | 1 - e2e/suite/oracle/oracle.go | 14 +- e2e/suite/oracle/provider.go | 12 +- e2e/suite/vault/provider.go | 2 +- e2e/suite/vault/vault.go | 10 +- go.mod | 5 +- go.sum | 4 + main.go | 2 +- .../externalsecret_controller_test.go | 3 +- pkg/controllers/externalsecret/suite_test.go | 4 +- pkg/controllers/secretstore/suite_test.go | 4 +- terraform/aws/main.tf | 8 + terraform/aws/modules/cluster/auth.tf | 60 +++++ terraform/aws/modules/cluster/irsa.tf | 57 +++++ terraform/aws/modules/cluster/main.tf | 127 +++++++++++ terraform/aws/modules/cluster/outputs.tf | 135 +++++++++++ terraform/aws/modules/cluster/provider.tf | 10 + terraform/aws/modules/cluster/variables.tf | 16 ++ terraform/aws/outputs.tf | 11 + terraform/aws/provider.tf | 11 + terraform/aws/variables.tf | 19 ++ terraform/gcp/eso_gcp_modules/gke/main.tf | 20 ++ terraform/gcp/eso_gcp_modules/network/main.tf | 2 + .../gcp/eso_gcp_modules/network/variable.tf | 3 + terraform/gcp/main.tf | 1 + tools.go | 2 +- 61 files changed, 1342 insertions(+), 743 deletions(-) delete mode 100644 e2e/framework/addon/localstack.go delete mode 100644 e2e/k8s/eso.scoped.values.yaml delete mode 100644 e2e/k8s/eso.values.yaml create mode 100644 e2e/suite/aws/secretsmanager_managed.go create mode 100644 e2e/suite/gcp/gcp_managed.go delete mode 100644 e2e/suite/gcpmanaged/gcpmanaged.go create mode 100644 terraform/aws/main.tf create mode 100644 terraform/aws/modules/cluster/auth.tf create mode 100644 terraform/aws/modules/cluster/irsa.tf create mode 100644 terraform/aws/modules/cluster/main.tf create mode 100644 terraform/aws/modules/cluster/outputs.tf create mode 100644 terraform/aws/modules/cluster/provider.tf create mode 100644 terraform/aws/modules/cluster/variables.tf create mode 100644 terraform/aws/outputs.tf create mode 100644 terraform/aws/provider.tf create mode 100644 terraform/aws/variables.tf diff --git a/.github/workflows/e2e-managed.yml b/.github/workflows/e2e-managed.yml index 3ff633828..1e4c78c49 100644 --- a/.github/workflows/e2e-managed.yml +++ b/.github/workflows/e2e-managed.yml @@ -24,24 +24,62 @@ env: GCP_KSA_NAME: ${{ secrets.GCP_KSA_NAME}} # Kubernetes Service Account TF_VAR_GCP_GSA_NAME: ${{ secrets.GCP_GSA_NAME}} # Goolge Service Account for tf TF_VAR_GCP_KSA_NAME: ${{ secrets.GCP_KSA_NAME}} # Kubernetes Service Account for tf + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + AWS_SA_NAME: ${{ secrets.AWS_SA_NAME }} + AWS_SA_NAMESPACE: ${{ secrets.AWS_SA_NAMESPACE }} + AWS_REGION: "eu-west-1" + AWS_CLUSTER_NAME: "eso-e2e-managed" + TF_VAR_AWS_SA_NAME: ${{ secrets.AWS_SA_NAME }} + TF_VAR_AWS_SA_NAMESPACE: ${{ secrets.AWS_SA_NAMESPACE }} + TF_VAR_AWS_REGION: "eu-west-1" + TF_VAR_AWS_CLUSTER_NAME: "eso-e2e-managed" + AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID}} AZURE_CLIENT_SECRET: ${{ secrets.AZURE_CLIENT_SECRET}} TENANT_ID: ${{ secrets.TENANT_ID}} VAULT_URL: ${{ secrets.VAULT_URL}} - IMAGE_REGISTRY: ghcr.io/external-secrets/external-secrets - E2E_IMAGE_REGISTRY: ghcr.io/external-secrets/external-secrets-e2e - E2E_VERSION: test name: e2e tests jobs: - # Repo owner has commented /ok-to-test-managed on a (fork-based) pull request - integration-fork-managed: + integration-managed: runs-on: ubuntu-latest - if: - github.event_name == 'repository_dispatch' + if: github.event_name == 'repository_dispatch' + steps: + # set status=in_progress + - uses: actions/github-script@v1 + id: update-check-run + env: + number: ${{ github.event.client_payload.slash_command.args.named.pull }} + job: ${{ github.job }} + conclusion: ${{ job.status }} + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const { data: pull } = await github.pulls.get({ + ...context.repo, + pull_number: process.env.number + }); + const ref = pull.head.sha; + console.log("\n\nPR sha: " + ref) + const { data: checks } = await github.checks.listForRef({ + ...context.repo, + ref + }); + console.log("\n\nPR CHECKS: " + checks) + const check = checks.check_runs.filter(c => c.name === process.env.job); + console.log("\n\nPR Filtered CHECK: " + check) + console.log(check) + const { data: result } = await github.checks.update({ + ...context.repo, + check_run_id: check[0].id, + status: 'in_progress', + }); + return result; + # Check out merge commit - name: Fork based /ok-to-test-managed checkout uses: actions/checkout@v2 @@ -75,13 +113,7 @@ jobs: path: ${{ steps.go.outputs.mod-cache }} key: ${{ runner.os }}-pkg-${{ hashFiles('**/go.sum') }} restore-keys: ${{ runner.os }}-pkg- - - - name: Setup gcloud CLI - uses: google-github-actions/setup-gcloud@master - with: - service_account_key: ${{ env.GCP_SM_SA_GKE_JSON }} - project_id: ${{ env.GCP_PROJECT_ID }} - + - name: Setup TFLint uses: terraform-linters/setup-tflint@v1 with: @@ -91,40 +123,52 @@ jobs: run: find ${{ github.workspace }} | grep tf$ | xargs -n1 dirname | xargs -IXXX -n1 /bin/sh -c 'set -o errexit; cd XXX; pwd; tflint --loglevel=info .; cd - >/dev/null' - name: Setup TF Gcloud Provider + if: github.event.client_payload.slash_command.args.named.provider == 'gcp' run: |- mkdir -p terraform/gcp/secrets echo ${GCP_SM_SA_GKE_JSON} > terraform/gcp/secrets/gcloud-service-account-key.json - - name: Show TF GKE + - name: Show TF run: |- - make tf.show.gcp + PROVIDER=${{github.event.client_payload.slash_command.args.named.provider}} + make tf.show.${PROVIDER} - name: Setup Infracost uses: infracost/actions/setup@v1 with: api-key: ${{ secrets.INFRACOST_API_KEY }} - - name: Generate Infracost JSON for GKE - run: infracost breakdown --path terraform/gcp/plan.json --format json --out-file /tmp/infracost.json + - name: Generate Infracost JSON for AWS + run: infracost breakdown --path terraform/${{github.event.client_payload.slash_command.args.named.provider}}/plan.json --format json --out-file /tmp/infracost.json - name: Post Infracost comment uses: infracost/actions/comment@v1 with: path: /tmp/infracost.json - # Choose the commenting behavior, 'update' is a good default: - behavior: update # Create a single comment and update it. The "quietest" option. - # behavior: delete-and-new # Delete previous comments and create a new one. - # behavior: hide-and-new # Minimize previous comments and create a new one. - # behavior: new # Create a new cost estimate comment on every push. + behavior: update - - name: Apply TF GKE + - name: Apply TF run: |- - make tf.apply.gcp + PROVIDER=${{github.event.client_payload.slash_command.args.named.provider}} + make tf.apply.${PROVIDER} + + - name: Setup gcloud CLI + if: github.event.client_payload.slash_command.args.named.provider == 'gcp' + uses: google-github-actions/setup-gcloud@master + with: + service_account_key: ${{ env.GCP_SM_SA_GKE_JSON }} + project_id: ${{ env.GCP_PROJECT_ID }} - name: Get the GKE credentials + if: github.event.client_payload.slash_command.args.named.provider == 'gke' run: |- gcloud container clusters get-credentials "$GCP_GKE_CLUSTER" --zone "$GCP_GKE_ZONE" --project "$GCP_PROJECT_ID" + - name: Get the AWS credentials + if: github.event.client_payload.slash_command.args.named.provider == 'aws' + run: |- + aws --region $AWS_REGION eks update-kubeconfig --name $AWS_CLUSTER_NAME + - name: Login to Docker uses: docker/login-action@v1 if: env.GHCR_USERNAME != '' @@ -133,25 +177,25 @@ jobs: username: ${{ secrets.GHCR_USERNAME }} password: ${{ secrets.GHCR_TOKEN }} - - name: Run e2e Tests for GCP + - name: Run managed e2e Tests run: | - export E2E_VERSION=$GITHUB_SHA - export PR_IMG_TAG=$GITHUB_SHA export PATH=$PATH:$(go env GOPATH)/bin - go get github.com/onsi/ginkgo/ginkgo - make test.e2e.managed FOCUS="gcpmanaged" + PROVIDER=${{github.event.client_payload.slash_command.args.named.provider}} + go get github.com/onsi/ginkgo/v2/ginkgo + make test.e2e.managed GINKGO_LABELS="${PROVIDER}" - - name: Destroy TF GKE + - name: Destroy TF if: always() run: |- - make tf.destroy.gcp + PROVIDER=${{github.event.client_payload.slash_command.args.named.provider}} + make tf.destroy.${PROVIDER} - # Update check run called "integration-fork" + # set status=completed - uses: actions/github-script@v1 id: update-check-run if: ${{ always() }} env: - number: ${{ github.event.client_payload.pull_request.number }} + number: ${{ github.event.client_payload.slash_command.args.named.pull }} job: ${{ github.job }} # Conveniently, job.status maps to https://developer.github.com/v3/checks/runs/#update-a-check-run conclusion: ${{ job.status }} diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 24c1c16b3..ed5b5d23e 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -19,12 +19,12 @@ env: GCP_GSA_NAME: ${{ secrets.GCP_GSA_NAME}} # Goolge Service Account GCP_KSA_NAME: ${{ secrets.GCP_KSA_NAME}} # Kubernetes Service Account GCP_PROJECT_ID: ${{ secrets.GCP_PROJECT_ID}} + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID}} AZURE_CLIENT_SECRET: ${{ secrets.AZURE_CLIENT_SECRET}} TENANT_ID: ${{ secrets.TENANT_ID}} VAULT_URL: ${{ secrets.VAULT_URL}} - E2E_IMAGE_REGISTRY: local/external-secrets-e2e - E2E_VERSION: test name: e2e tests @@ -87,7 +87,7 @@ jobs: BUILD_ARGS: "--load" run: | export PATH=$PATH:$(go env GOPATH)/bin - go get github.com/onsi/ginkgo/ginkgo + go get github.com/onsi/ginkgo/v2/ginkgo make test.e2e # Repo owner has commented /ok-to-test on a (fork-based) pull request @@ -150,7 +150,7 @@ jobs: BUILD_ARGS: "--load" run: | export PATH=$PATH:$(go env GOPATH)/bin - go get github.com/onsi/ginkgo/ginkgo + go get github.com/onsi/ginkgo/v2/ginkgo make test.e2e # Update check run called "integration-fork" diff --git a/.github/workflows/ok-to-test-managed.yml b/.github/workflows/ok-to-test-managed.yml index 90e7e6105..e00251ce7 100644 --- a/.github/workflows/ok-to-test-managed.yml +++ b/.github/workflows/ok-to-test-managed.yml @@ -30,6 +30,7 @@ jobs: token: ${{ env.TOKEN }} # GitHub App installation access token # token: ${{ secrets.PERSONAL_ACCESS_TOKEN }} # PAT or OAuth token will also work reaction-token: ${{ secrets.GITHUB_TOKEN }} + static-args: pull=${{ github.event.client_payload.pull_request.number }} issue-type: pull-request commands: ok-to-test-managed permission: maintain diff --git a/Makefile b/Makefile index 81e7c363f..dd8383387 100644 --- a/Makefile +++ b/Makefile @@ -14,9 +14,7 @@ BUILD_ARGS ?= all: $(addprefix build-,$(ARCH)) # Image registry for build/push image targets -IMAGE_REGISTRY ?= ghcr.io/external-secrets/external-secrets - -PR_IMG_TAG ?= +export IMAGE_REGISTRY ?= ghcr.io/external-secrets/external-secrets CRD_DIR ?= deploy/crds @@ -35,10 +33,10 @@ endif # check if there are any existing `git tag` values ifeq ($(shell git tag),) # no tags found - default to initial tag `v0.0.0` -VERSION := $(shell echo "v0.0.0-$$(git rev-list HEAD --count)-g$$(git describe --dirty --always)" | sed 's/-/./2' | sed 's/-/./2') +export VERSION := $(shell echo "v0.0.0-$$(git rev-list HEAD --count)-g$$(git describe --dirty --always)" | sed 's/-/./2' | sed 's/-/./2') else # use tags -VERSION := $(shell git describe --dirty --always --tags --exclude 'helm*' | sed 's/-/./2' | sed 's/-/./2') +export VERSION := $(shell git describe --dirty --always --tags --exclude 'helm*' | sed 's/-/./2' | sed 's/-/./2') endif # ==================================================================================== @@ -87,13 +85,13 @@ test: generate ## Run tests test.e2e: generate ## Run e2e tests @$(INFO) go test e2e-tests $(MAKE) -C ./e2e test - @$(OK) go test unit-tests + @$(OK) go test e2e-tests .PHONY: test.e2e.managed -test.e2e.managed: generate ## Run e2e tests - @$(INFO) go test e2e-tests +test.e2e.managed: generate ## Run e2e tests managed + @$(INFO) go test e2e-tests-managed $(MAKE) -C ./e2e test.managed - @$(OK) go test unit-tests + @$(OK) go test e2e-tests-managed .PHONY: build build: $(addprefix build-,$(ARCH)) ## Build binary @@ -211,7 +209,7 @@ docker.push: ## Push the docker image to the registry @docker push $(IMAGE_REGISTRY):$(VERSION) @$(OK) docker push -# RELEASE_TAG is tag to promote. Default is promooting to main branch, but can be overriden +# RELEASE_TAG is tag to promote. Default is promoting to main branch, but can be overriden # to promote a tag to a specific version. RELEASE_TAG ?= main SOURCE_TAG ?= $(VERSION) @@ -230,29 +228,27 @@ docker.promote: ## Promote the docker image to the registry # ==================================================================================== # Terraform -tf.plan.gcp: ## Runs terrform plan for gcp provider bringing GKE up - @cd $(TF_DIR)/gcp; \ +tf.plan.%: ## Runs terrform plan for a provider + @cd $(TF_DIR)/$*; \ terraform init; \ - terraform plan -auto-approve + terraform plan -tf.apply.gcp: ## Runs terrform apply for gcp provider bringing GKE up - @cd $(TF_DIR)/gcp; \ +tf.apply.%: ## Runs terrform apply for a provider + @cd $(TF_DIR)/$*; \ terraform init; \ terraform apply -auto-approve -tf.destroy.gcp: ## Runs terrform destroy for gcp provider bringing GKE down - @cd $(TF_DIR)/gcp; \ +tf.destroy.%: ## Runs terrform destroy for a provider + @cd $(TF_DIR)/$*; \ terraform init; \ terraform destroy -auto-approve -tf.show.gcp: ## Runs terrform show for gcp and outputs to a file - @cd $(TF_DIR)/gcp; \ +tf.show.%: ## Runs terrform show for a provider and outputs to a file + @cd $(TF_DIR)/$*; \ terraform init; \ terraform plan -out tfplan.binary; \ terraform show -json tfplan.binary > plan.json - - # ==================================================================================== # Help diff --git a/e2e/Dockerfile b/e2e/Dockerfile index 7b5d8ec61..fc7468b30 100644 --- a/e2e/Dockerfile +++ b/e2e/Dockerfile @@ -4,7 +4,7 @@ FROM golang:$GO_VERSION-buster as builder ENV KUBECTL_VERSION="v1.21.2" ENV HELM_VERSION="v3.7.1" -RUN go get -u github.com/onsi/ginkgo/ginkgo +RUN go get -u github.com/onsi/ginkgo/v2/ginkgo RUN wget -q https://storage.googleapis.com/kubernetes-release/release/${KUBECTL_VERSION}/bin/linux/amd64/kubectl -O /usr/local/bin/kubectl && \ chmod +x /usr/local/bin/kubectl && \ wget -q https://get.helm.sh/helm-${HELM_VERSION}-linux-amd64.tar.gz -O - | tar -xzO linux-amd64/helm > /usr/local/bin/helm && \ diff --git a/e2e/Makefile b/e2e/Makefile index 149ce26b4..adaf57e59 100644 --- a/e2e/Makefile +++ b/e2e/Makefile @@ -2,15 +2,11 @@ MAKEFLAGS += --warn-undefined-variables SHELL := /bin/bash .SHELLFLAGS := -euo pipefail -c -IMG_TAG = test -IMG = local/external-secrets-e2e:$(IMG_TAG) -KIND_IMG = "kindest/node:v1.21.1@sha256:69860bda5563ac81e3c0057d654b5253219618a22ec3a346306239bba8cfa1a6" -BUILD_ARGS ?= -IMAGE_REGISTRY ?= -export FOCUS := $(FOCUS) +KIND_IMG = "kindest/node:v1.21.1@sha256:69860bda5563ac81e3c0057d654b5253219618a22ec3a346306239bba8cfa1a6" +BUILD_ARGS ?= -export E2E_IMAGE_REGISTRY ?= -export E2E_VERSION ?= +export E2E_IMAGE_REGISTRY ?= ghcr.io/external-secrets/external-secrets-e2e +export GINKGO_LABELS ?= !managed start-kind: ## Start kind cluster kind create cluster \ @@ -21,49 +17,35 @@ start-kind: ## Start kind cluster test: e2e-image ## Run e2e tests against current kube context $(MAKE) -C ../ docker.build \ - IMAGE_REGISTRY=local/external-secrets \ - VERSION=$(IMG_TAG) \ + IMAGE_REGISTRY=$(IMAGE_REGISTRY) \ + VERSION=$(VERSION) \ ARCH=amd64 \ BUILD_ARGS="${BUILD_ARGS} --build-arg TARGETARCH=amd64 --build-arg TARGETOS=linux" - kind load docker-image --name="external-secrets" local/external-secrets:$(IMG_TAG) - kind load docker-image --name="external-secrets" $(IMG) + kind load docker-image --name="external-secrets" $(IMAGE_REGISTRY):$(VERSION) + kind load docker-image --name="external-secrets" $(E2E_IMAGE_REGISTRY):$(VERSION) ./run.sh -test.managed: e2e-remote-values e2e-image.managed ## Run e2e tests against current kube context +test.managed: e2e-image ## Run e2e tests against current kube context $(MAKE) -C ../ docker.build \ - VERSION=$(PR_IMG_TAG) \ + VERSION=$(VERSION) \ ARCH=amd64 \ BUILD_ARGS="${BUILD_ARGS} --build-arg TARGETARCH=amd64 --build-arg TARGETOS=linux" $(MAKE) -C ../ docker.push \ - VERSION=$(PR_IMG_TAG) + VERSION=$(VERSION) $(MAKE) -C ../ docker.push \ IMAGE_REGISTRY=$(E2E_IMAGE_REGISTRY) \ - VERSION=$(E2E_VERSION) + VERSION=$(VERSION) ./run.sh -e2e-remote-values: - sed -i "s|repository: [^ ]*|repository: $(IMAGE_REGISTRY)|g" k8s/eso.values.yaml - sed -i "s|tag: [^ ]*|tag: $(PR_IMG_TAG)|g" k8s/eso.values.yaml - sed -i "s|repository: [^ ]*|repository: $(IMAGE_REGISTRY)|g" k8s/eso.scoped.values.yaml - sed -i "s|tag: [^ ]*|tag: $(PR_IMG_TAG)|g" k8s/eso.scoped.values.yaml - - e2e-bin: - CGO_ENABLED=0 go run github.com/onsi/ginkgo/ginkgo build . + CGO_ENABLED=0 go run github.com/onsi/ginkgo/v2/ginkgo build . e2e-image: e2e-bin -rm -rf ./k8s/deploy mkdir -p k8s $(MAKE) -C ../ helm.generate cp -r ../deploy ./k8s - docker build $(BUILD_ARGS) -t $(IMG) . - -e2e-image.managed: e2e-bin - -rm -rf ./k8s/deploy - mkdir -p k8s - $(MAKE) -C ../ helm.generate - cp -r ../deploy ./k8s - docker build $(BUILD_ARGS) -t ghcr.io/external-secrets/external-secrets-e2e:$(E2E_VERSION) . + docker build $(BUILD_ARGS) -t $(E2E_IMAGE_REGISTRY):$(VERSION) . stop-kind: ## Stop kind cluster kind delete cluster \ diff --git a/e2e/e2e_test.go b/e2e/e2e_test.go index c3de5343a..3464e403e 100644 --- a/e2e/e2e_test.go +++ b/e2e/e2e_test.go @@ -17,7 +17,7 @@ import ( "testing" // nolint - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" // nolint . "github.com/onsi/gomega" @@ -30,25 +30,16 @@ var _ = SynchronizedBeforeSuite(func() []byte { cfg := &addon.Config{} cfg.KubeConfig, cfg.KubeClientSet, cfg.CRClient = util.NewConfig() - By("installing localstack") - addon.InstallGlobalAddon(addon.NewLocalstack(), cfg) - - By("waiting for localstack") - err := util.WaitForURL("http://localstack.default/health") - Expect(err).ToNot(HaveOccurred()) - By("installing eso") addon.InstallGlobalAddon(addon.NewESO(), cfg) - By("installing scoped eso") - addon.InstallGlobalAddon(addon.NewScopedESO(), cfg) return nil }, func([]byte) {}) var _ = SynchronizedAfterSuite(func() {}, func() { By("Cleaning up global addons") addon.UninstallGlobalAddons() - if CurrentGinkgoTestDescription().Failed { + if CurrentSpecReport().Failed() { addon.PrintLogs() } }) @@ -56,5 +47,5 @@ var _ = SynchronizedAfterSuite(func() {}, func() { func TestE2E(t *testing.T) { NewWithT(t) RegisterFailHandler(Fail) - RunSpecs(t, "external-secrets e2e suite") + RunSpecs(t, "external-secrets e2e suite", Label("e2e")) } diff --git a/e2e/entrypoint.sh b/e2e/entrypoint.sh index e1591d967..b3291dba3 100755 --- a/e2e/entrypoint.sh +++ b/e2e/entrypoint.sh @@ -19,8 +19,6 @@ set -euo pipefail NC='\e[0m' BGREEN='\e[32m' -SLOW_E2E_THRESHOLD=${SLOW_E2E_THRESHOLD:-50} -FOCUS=${FOCUS:-.*} E2E_NODES=${E2E_NODES:-5} if [ ! -f "${HOME}/.kube/config" ]; then @@ -31,13 +29,13 @@ if [ ! -f "${HOME}/.kube/config" ]; then fi ginkgo_args=( - "-randomizeSuites" - "-randomizeAllSpecs" - "-flakeAttempts=2" + "--randomize-suites" + "--randomize-all" + "--flake-attempts=2" "-p" "-progress" "-trace" - "-slowSpecThreshold=${SLOW_E2E_THRESHOLD}" + "--slow-spec-threshold=5m" "-r" "-v" "-timeout=45m" @@ -45,9 +43,8 @@ ginkgo_args=( kubectl apply -f /k8s/deploy/crds -echo -e "${BGREEN}Running e2e test suite (FOCUS=${FOCUS})...${NC}" +echo -e "${BGREEN}Running e2e test suite (LABELS=${GINKGO_LABELS})...${NC}" ACK_GINKGO_RC=true ginkgo "${ginkgo_args[@]}" \ - -focus="${FOCUS}" \ - -skip="\[Serial\]|\[MemoryLeak\]" \ + -label-filter="${GINKGO_LABELS}" \ -nodes="${E2E_NODES}" \ /e2e.test diff --git a/e2e/framework/addon/addon.go b/e2e/framework/addon/addon.go index eb5292793..2722a83f1 100644 --- a/e2e/framework/addon/addon.go +++ b/e2e/framework/addon/addon.go @@ -14,7 +14,7 @@ limitations under the License. package addon import ( - "github.com/onsi/ginkgo" + "github.com/onsi/ginkgo/v2" "github.com/onsi/gomega" "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" diff --git a/e2e/framework/addon/chart.go b/e2e/framework/addon/chart.go index cdd0909f2..ead4f1321 100644 --- a/e2e/framework/addon/chart.go +++ b/e2e/framework/addon/chart.go @@ -63,7 +63,7 @@ func (c *HelmChart) Install() error { args := []string{"install", c.ReleaseName, c.Chart, "--wait", - "--timeout", "600s", + "--timeout", "120s", "--namespace", c.Namespace, } diff --git a/e2e/framework/addon/eso.go b/e2e/framework/addon/eso.go index 71e42711d..27343dbb2 100644 --- a/e2e/framework/addon/eso.go +++ b/e2e/framework/addon/eso.go @@ -14,92 +14,99 @@ limitations under the License. package addon import ( - "fmt" "os" // nolint - . "github.com/onsi/ginkgo" - // nolint - . "github.com/onsi/gomega" - - // nolint - "github.com/external-secrets/external-secrets/e2e/framework/util" + . "github.com/onsi/ginkgo/v2" ) type ESO struct { - Addon + *HelmChart } -func NewESO() *ESO { - return &ESO{ +func NewESO(mutators ...MutationFunc) *ESO { + eso := &ESO{ &HelmChart{ Namespace: "default", ReleaseName: "eso", Chart: "/k8s/deploy/charts/external-secrets", - Values: []string{"/k8s/eso.values.yaml"}, + Vars: []StringTuple{ + { + Key: "image.repository", + Value: os.Getenv("IMAGE_REGISTRY"), + }, + { + Key: "image.tag", + Value: os.Getenv("VERSION"), + }, + { + Key: "installCRDs", + Value: "false", + }, + }, }, } + + for _, f := range mutators { + f(eso) + } + + return eso +} + +type MutationFunc func(eso *ESO) + +func WithReleaseName(name string) MutationFunc { + return func(eso *ESO) { + eso.HelmChart.ReleaseName = name + } +} + +func WithNamespace(namespace string) MutationFunc { + return func(eso *ESO) { + eso.HelmChart.Namespace = namespace + } +} + +func WithNamespaceScope(namespace string) MutationFunc { + return func(eso *ESO) { + eso.HelmChart.Vars = append(eso.HelmChart.Vars, StringTuple{ + Key: "scopedNamespace", + Value: namespace, + }) + } +} + +func WithServiceAccount(saName string) MutationFunc { + return func(eso *ESO) { + eso.HelmChart.Vars = append(eso.HelmChart.Vars, []StringTuple{ + { + Key: "serviceAccount.create", + Value: "false", + }, + { + Key: "serviceAccount.name", + Value: "eso-e2e-test", + }, + }...) + } +} + +func WithControllerClass(class string) MutationFunc { + return func(eso *ESO) { + eso.HelmChart.Vars = append(eso.HelmChart.Vars, StringTuple{ + Key: "extraArgs.controller-class", + Value: class, + }) + } } func (l *ESO) Install() error { By("Installing eso\n") - err := l.Addon.Install() - if err != nil { - return err - } - - By("afterInstall eso\n") - err = l.afterInstall() + err := l.HelmChart.Install() if err != nil { return err } return nil } - -func (l *ESO) afterInstall() error { - err := gcpPreparation() - Expect(err).NotTo(HaveOccurred()) - err = awsPreparation() - Expect(err).NotTo(HaveOccurred()) - if err != nil { - return err - } - return nil -} - -func gcpPreparation() error { - gcpProjectID := os.Getenv("GCP_PROJECT_ID") - gcpGSAName := os.Getenv("GCP_GSA_NAME") - gcpKSAName := os.Getenv("GCP_KSA_NAME") - _, kubeClientSet, _ := util.NewConfig() - - annotations := make(map[string]string) - annotations["iam.gke.io/gcp-service-account"] = fmt.Sprintf("%s@%s.iam.gserviceaccount.com", gcpGSAName, gcpProjectID) - _, err := util.UpdateKubeSA(gcpKSAName, kubeClientSet, "default", annotations) - Expect(err).NotTo(HaveOccurred()) - - _, err = util.UpdateKubeSA("external-secrets-e2e", kubeClientSet, "default", annotations) - Expect(err).NotTo(HaveOccurred()) - - if err != nil { - return err - } - - return nil -} - -func awsPreparation() error { - return nil -} - -func NewScopedESO() *ESO { - return &ESO{ - &HelmChart{ - Namespace: "default", - ReleaseName: "eso-aws-sm", - Chart: "/k8s/deploy/charts/external-secrets", - Values: []string{"/k8s/eso.scoped.values.yaml"}, - }, - } -} diff --git a/e2e/framework/addon/localstack.go b/e2e/framework/addon/localstack.go deleted file mode 100644 index b893cf4b9..000000000 --- a/e2e/framework/addon/localstack.go +++ /dev/null @@ -1,44 +0,0 @@ -/* -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. -*/ -package addon - -import "github.com/external-secrets/external-secrets/e2e/framework/util" - -type Localstack struct { - Addon -} - -func NewLocalstack() *Localstack { - return &Localstack{ - &HelmChart{ - Namespace: "default", - ReleaseName: "localstack", - Chart: "localstack-charts/localstack", - ChartVersion: "0.2.0", - Repo: ChartRepo{ - Name: "localstack-charts", - URL: "https://localstack.github.io/helm-charts", - }, - Values: []string{"/k8s/localstack.values.yaml"}, - }, - } -} - -func (l *Localstack) Install() error { - err := l.Addon.Install() - if err != nil { - return err - } - return util.WaitForURL("http://localstack.default/health") -} diff --git a/e2e/framework/addon/vault.go b/e2e/framework/addon/vault.go index 07d8837f5..55b8c61c8 100644 --- a/e2e/framework/addon/vault.go +++ b/e2e/framework/addon/vault.go @@ -32,7 +32,7 @@ import ( vault "github.com/hashicorp/vault/api" // nolint - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" diff --git a/e2e/framework/eso.go b/e2e/framework/eso.go index 11fc82ee2..2f6c205d0 100644 --- a/e2e/framework/eso.go +++ b/e2e/framework/eso.go @@ -33,7 +33,7 @@ import ( // with the provided values. func (f *Framework) WaitForSecretValue(namespace, name string, expected *v1.Secret) (*v1.Secret, error) { secret := &v1.Secret{} - err := wait.PollImmediate(time.Second*2, time.Minute*2, func() (bool, error) { + err := wait.PollImmediate(time.Second*5, time.Minute, func() (bool, error) { err := f.CRClient.Get(context.Background(), types.NamespacedName{ Namespace: namespace, Name: name, diff --git a/e2e/framework/framework.go b/e2e/framework/framework.go index 138214616..aab848385 100644 --- a/e2e/framework/framework.go +++ b/e2e/framework/framework.go @@ -16,12 +16,10 @@ package framework import ( // nolint - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" // nolint . "github.com/onsi/gomega" - // nolint - . "github.com/onsi/ginkgo/extensions/table" api "k8s.io/api/core/v1" "k8s.io/client-go/kubernetes" kscheme "k8s.io/client-go/kubernetes/scheme" @@ -30,6 +28,7 @@ import ( esv1alpha1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1alpha1" "github.com/external-secrets/external-secrets/e2e/framework/addon" + "github.com/external-secrets/external-secrets/e2e/framework/log" "github.com/external-secrets/external-secrets/e2e/framework/util" ) @@ -72,11 +71,9 @@ func New(baseName string) *Framework { // BeforeEach creates a namespace. func (f *Framework) BeforeEach() { var err error - By("Building a namespace api object") f.Namespace, err = util.CreateKubeNamespace(f.BaseName, f.KubeClientSet) - Expect(err).NotTo(HaveOccurred()) - - By("Using the namespace " + f.Namespace.Name) + log.Logf("created test namespace %s", f.Namespace.Name) + Expect(err).ToNot(HaveOccurred()) } // AfterEach deletes the namespace and cleans up the registered addons. @@ -87,7 +84,7 @@ func (f *Framework) AfterEach() { } // reset addons to default once the run is done f.Addons = []addon.Addon{} - By("deleting test namespace") + log.Logf("deleting test namespace %s", f.Namespace.Name) err := util.DeleteKubeNamespace(f.Namespace.Name, f.KubeClientSet) Expect(err).NotTo(HaveOccurred()) } @@ -111,13 +108,13 @@ func (f *Framework) Install(a addon.Addon) { func Compose(descAppend string, f *Framework, fn func(f *Framework) (string, func(*TestCase)), tweaks ...func(*TestCase)) TableEntry { desc, tfn := fn(f) tweaks = append(tweaks, tfn) - te := Entry(desc + " " + descAppend) // need to convert []func to []interface{} ifs := make([]interface{}, len(tweaks)) for i := 0; i < len(tweaks); i++ { ifs[i] = tweaks[i] } - te.Parameters = ifs + te := Entry(desc+" "+descAppend, ifs...) + return te } diff --git a/e2e/framework/log/log.go b/e2e/framework/log/log.go index b35f682e1..77c645428 100644 --- a/e2e/framework/log/log.go +++ b/e2e/framework/log/log.go @@ -14,12 +14,10 @@ limitations under the License. package log import ( - "fmt" - - "github.com/onsi/ginkgo" + "github.com/onsi/ginkgo/v2" ) // Logf logs the format string to ginkgo stdout. func Logf(format string, args ...interface{}) { - fmt.Fprintf(ginkgo.GinkgoWriter, format, args...) + ginkgo.GinkgoWriter.Printf(format, args) } diff --git a/e2e/framework/util/util.go b/e2e/framework/util/util.go index c4a25f515..c29a90752 100644 --- a/e2e/framework/util/util.go +++ b/e2e/framework/util/util.go @@ -22,9 +22,7 @@ import ( "time" // nolint - . "github.com/onsi/ginkgo" - // nolint - . "github.com/onsi/gomega" + . "github.com/onsi/ginkgo/v2" v1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -242,19 +240,25 @@ func NewConfig() (*restclient.Config, *kubernetes.Clientset, crclient.Client) { kcPath := os.Getenv("KUBECONFIG") if kcPath != "" { kubeConfig, err = clientcmd.BuildConfigFromFlags("", kcPath) - Expect(err).NotTo(HaveOccurred()) + if err != nil { + Fail(err.Error()) + } } else { kubeConfig, err = restclient.InClusterConfig() - Expect(err).NotTo(HaveOccurred()) + if err != nil { + Fail(err.Error()) + } } - By("creating a kubernetes client") kubeClientSet, err := kubernetes.NewForConfig(kubeConfig) - Expect(err).NotTo(HaveOccurred()) + if err != nil { + Fail(err.Error()) + } - By("creating a controller-runtime client") CRClient, err := crclient.New(kubeConfig, crclient.Options{Scheme: Scheme}) - Expect(err).NotTo(HaveOccurred()) + if err != nil { + Fail(err.Error()) + } return kubeConfig, kubeClientSet, CRClient } diff --git a/e2e/k8s/eso.scoped.values.yaml b/e2e/k8s/eso.scoped.values.yaml deleted file mode 100644 index cfe52f1ce..000000000 --- a/e2e/k8s/eso.scoped.values.yaml +++ /dev/null @@ -1,12 +0,0 @@ -installCRDs: false -image: - repository: local/external-secrets - tag: test -scopedNamespace: test -extraEnv: - - name: AWS_SECRETSMANAGER_ENDPOINT - value: "http://localstack.default" - - name: AWS_STS_ENDPOINT - value: "http://localstack.default" - - name: AWS_SSM_ENDPOINT - value: "http://localstack.default" diff --git a/e2e/k8s/eso.values.yaml b/e2e/k8s/eso.values.yaml deleted file mode 100644 index cd5c4540a..000000000 --- a/e2e/k8s/eso.values.yaml +++ /dev/null @@ -1,11 +0,0 @@ -installCRDs: false -image: - repository: local/external-secrets - tag: test -extraEnv: - - name: AWS_SECRETSMANAGER_ENDPOINT - value: "http://localstack.default" - - name: AWS_STS_ENDPOINT - value: "http://localstack.default" - - name: AWS_SSM_ENDPOINT - value: "http://localstack.default" diff --git a/e2e/run.sh b/e2e/run.sh index f396d8239..4e3345304 100755 --- a/e2e/run.sh +++ b/e2e/run.sh @@ -42,20 +42,22 @@ done kubectl apply -f ${DIR}/k8s/deploy/crds -echo -e "Starting the e2e test pod" +echo -e "Starting the e2e test pod ${E2E_IMAGE_REGISTRY}:${VERSION}" kubectl run --rm \ --attach \ --restart=Never \ - --pod-running-timeout=10m \ - --env="FOCUS=${FOCUS:-.*}" \ + --pod-running-timeout=5m \ + --env="GINKGO_LABELS=${GINKGO_LABELS:-.*}" \ --env="GCP_SM_SA_JSON=${GCP_SM_SA_JSON:-}" \ --env="GCP_PROJECT_ID=${GCP_PROJECT_ID:-}" \ - --env="TF_VAR_GCP_PROJECT_ID=${TF_VAR_GCP_PROJECT_ID:-}" \ --env="GCP_GSA_NAME=${GCP_GSA_NAME:-}" \ - --env="GCP_KSA_NAME=${GCP_KSA_NAME:-}" \ - --env="TF_VAR_GCP_GSA_NAME=${TF_VAR_GCP_GSA_NAME:-}" \ - --env="TF_VAR_GCP_KSA_NAME=${TF_VAR_GCP_KSA_NAME:-}" \ + --env="GCP_GKE_ZONE=${GCP_GKE_ZONE:-}" \ + --env="GCP_GKE_CLUSTER=${GCP_GKE_CLUSTER:-}" \ + --env="AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID:-}" \ + --env="AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY:-}" \ + --env="AWS_SA_NAME=${AWS_SA_NAME:-}" \ + --env="AWS_SA_NAMESPACE=${AWS_SA_NAMESPACE:-}" \ --env="AZURE_CLIENT_ID=${AZURE_CLIENT_ID:-}" \ --env="AZURE_CLIENT_SECRET=${AZURE_CLIENT_SECRET:-}" \ --env="AKEYLESS_ACCESS_ID=${AKEYLESS_ACCESS_ID:-}" \ @@ -70,5 +72,7 @@ kubectl run --rm \ --env="ORACLE_REGION=${ORACLE_REGION:-}" \ --env="ORACLE_FINGERPRINT=${ORACLE_FINGERPRINT:-}" \ --env="ORACLE_KEY=${ORACLE_KEY:-}" \ + --env="IMAGE_REGISTRY=${IMAGE_REGISTRY}" \ + --env="VERSION=${VERSION}" \ --overrides='{ "apiVersion": "v1", "spec":{"serviceAccountName": "external-secrets-e2e"}}' \ - e2e --image=${E2E_IMAGE_REGISTRY}:${E2E_VERSION} + e2e --image=${E2E_IMAGE_REGISTRY}:${VERSION} diff --git a/e2e/suite/akeyless/akeyless.go b/e2e/suite/akeyless/akeyless.go index de8ad93b6..998026d00 100644 --- a/e2e/suite/akeyless/akeyless.go +++ b/e2e/suite/akeyless/akeyless.go @@ -15,23 +15,19 @@ limitations under the License. package akeyless import ( - "os" // nolint - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" // nolint - . "github.com/onsi/ginkgo/extensions/table" + . "github.com/onsi/ginkgo/v2/extensions/table" "github.com/external-secrets/external-secrets/e2e/framework" "github.com/external-secrets/external-secrets/e2e/suite/common" ) -var _ = Describe("[akeyless] ", func() { +var _ = Describe("[akeyless]", Label("akeyless"), func() { f := framework.New("eso-akeyless") - accessID := os.Getenv("AKEYLESS_ACCESS_ID") - accessType := os.Getenv("AKEYLESS_ACCESS_TYPE") - accessTypeParam := os.Getenv("AKEYLESS_ACCESS_TYPE_PARAM") - prov := newAkeylessProvider(f, accessID, accessType, accessTypeParam) + prov := newFromEnv(f) DescribeTable("sync secrets", framework.TableFunc(f, prov), Entry(common.SimpleDataSync(f)), diff --git a/e2e/suite/akeyless/provider.go b/e2e/suite/akeyless/provider.go index 8528c41db..56c1d8d2b 100644 --- a/e2e/suite/akeyless/provider.go +++ b/e2e/suite/akeyless/provider.go @@ -29,7 +29,7 @@ import ( "github.com/akeylesslabs/akeyless-go/v2" //nolint - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" //nolint . "github.com/onsi/gomega" @@ -75,6 +75,13 @@ func newAkeylessProvider(f *framework.Framework, accessID, accessType, accessTyp return prov } +func newFromEnv(f *framework.Framework) *akeylessProvider { + accessID := os.Getenv("AKEYLESS_ACCESS_ID") + accessType := os.Getenv("AKEYLESS_ACCESS_TYPE") + accessTypeParam := os.Getenv("AKEYLESS_ACCESS_TYPE_PARAM") + return newAkeylessProvider(f, accessID, accessType, accessTypeParam) +} + // CreateSecret creates a secret. func (a *akeylessProvider) CreateSecret(key, val string) { token, err := a.GetToken() diff --git a/e2e/suite/alibaba/alibaba.go b/e2e/suite/alibaba/alibaba.go index a540ec8c5..d8e6d170f 100644 --- a/e2e/suite/alibaba/alibaba.go +++ b/e2e/suite/alibaba/alibaba.go @@ -15,27 +15,19 @@ limitations under the License. package alibaba import ( - "os" // nolint - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" // nolint - . "github.com/onsi/ginkgo/extensions/table" + . "github.com/onsi/ginkgo/v2/extensions/table" "github.com/external-secrets/external-secrets/e2e/framework" "github.com/external-secrets/external-secrets/e2e/suite/common" ) -var _ = Describe("[alibaba] ", func() { +var _ = Describe("[alibaba]", Label("alibaba"), func() { f := framework.New("eso-alibaba") - accessKeyID := os.Getenv("ACCESS_KEY_ID") - accessKeySecret := os.Getenv("ACCESS_KEY_SECRET") - regionID := os.Getenv("REGION_ID") - prov := &alibabaProvider{} - - if accessKeyID != "" && accessKeySecret != "" && regionID != "" { - prov = newAlibabaProvider(f, accessKeyID, accessKeySecret, regionID) - } + prov := newFromEnv(f) DescribeTable("sync secrets", framework.TableFunc(f, prov), Entry(common.SimpleDataSync(f)), diff --git a/e2e/suite/alibaba/provider.go b/e2e/suite/alibaba/provider.go index 64bc97eab..5199f7502 100644 --- a/e2e/suite/alibaba/provider.go +++ b/e2e/suite/alibaba/provider.go @@ -16,11 +16,12 @@ package alibaba import ( "context" + "os" "github.com/aliyun/alibaba-cloud-sdk-go/services/kms" //nolint - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" //nolint . "github.com/onsi/gomega" @@ -54,6 +55,13 @@ func newAlibabaProvider(f *framework.Framework, accessKeyID, accessKeySecret, re return prov } +func newFromEnv(f *framework.Framework) *alibabaProvider { + accessKeyID := os.Getenv("ACCESS_KEY_ID") + accessKeySecret := os.Getenv("ACCESS_KEY_SECRET") + regionID := os.Getenv("REGION_ID") + return newAlibabaProvider(f, accessKeyID, accessKeySecret, regionID) +} + // CreateSecret creates a secret in both kv v1 and v2 provider. func (s *alibabaProvider) CreateSecret(key, val string) { client, err := kms.NewClientWithAccessKey(s.regionID, s.accessKeyID, s.accessKeySecret) diff --git a/e2e/suite/aws/provider.go b/e2e/suite/aws/provider.go index c2413e58e..3e1bc0185 100644 --- a/e2e/suite/aws/provider.go +++ b/e2e/suite/aws/provider.go @@ -16,6 +16,8 @@ package aws import ( "context" + "os" + "time" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/credentials" @@ -23,79 +25,194 @@ import ( "github.com/aws/aws-sdk-go/service/secretsmanager" //nolint - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" // nolint . "github.com/onsi/gomega" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" esv1alpha1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1alpha1" - esmeta "github.com/external-secrets/external-secrets/apis/meta/v1" + esmetav1 "github.com/external-secrets/external-secrets/apis/meta/v1" "github.com/external-secrets/external-secrets/e2e/framework" - "github.com/external-secrets/external-secrets/pkg/provider/aws/auth" + "github.com/external-secrets/external-secrets/e2e/framework/log" ) type SMProvider struct { - url string + ServiceAccountName string + ServiceAccountNamespace string + + kid string + sak string + region string client *secretsmanager.SecretsManager framework *framework.Framework } -const secretName = "provider-secret" +const ( + staticCredentialsSecretName = "provider-secret" +) -func newSMProvider(f *framework.Framework, url string) *SMProvider { +func NewSMProvider(f *framework.Framework, kid, sak, region, saName, saNamespace string) *SMProvider { sess, err := session.NewSessionWithOptions(session.Options{ Config: aws.Config{ - Credentials: credentials.NewStaticCredentials("foobar", "foobar", "secret-manager"), - EndpointResolver: auth.ResolveEndpointWithServiceMap(map[string]string{ - "secretsmanager": url, - }), - Region: aws.String("eu-east-1"), + Credentials: credentials.NewStaticCredentials(kid, sak, ""), + Region: aws.String(region), }, }) - Expect(err).ToNot(HaveOccurred()) + if err != nil { + Fail(err.Error()) + } sm := secretsmanager.New(sess) prov := &SMProvider{ - url: url, - client: sm, - framework: f, + ServiceAccountName: saName, + ServiceAccountNamespace: saNamespace, + kid: kid, + sak: sak, + region: region, + client: sm, + framework: f, } - BeforeEach(prov.BeforeEach) + + BeforeEach(func() { + prov.SetupStaticStore() + prov.SetupReferencedIRSAStore() + prov.SetupMountedIRSAStore() + }) + + AfterEach(func() { + // Cleanup ClusterSecretStore + err := prov.framework.CRClient.Delete(context.Background(), &esv1alpha1.ClusterSecretStore{ + ObjectMeta: metav1.ObjectMeta{ + Name: prov.ReferencedIRSAStoreName(), + }, + }) + Expect(err).ToNot(HaveOccurred()) + }) + return prov } +func NewFromEnv(f *framework.Framework) *SMProvider { + kid := os.Getenv("AWS_ACCESS_KEY_ID") + sak := os.Getenv("AWS_SECRET_ACCESS_KEY") + region := "eu-west-1" + saName := os.Getenv("AWS_SA_NAME") + saNamespace := os.Getenv("AWS_SA_NAMESPACE") + return NewSMProvider(f, kid, sak, region, saName, saNamespace) +} + +// CreateSecret creates a secret at the provider. func (s *SMProvider) CreateSecret(key, val string) { - _, err := s.client.CreateSecret(&secretsmanager.CreateSecretInput{ - Name: aws.String(key), - SecretString: aws.String(val), - }) - Expect(err).ToNot(HaveOccurred()) + // we re-use some secret names throughout our test suite + // due to the fact that there is a short delay before the secret is actually deleted + // we have to retry creating the secret + attempts := 20 + for { + log.Logf("creating secret %s / attempts left: %d", key, attempts) + _, err := s.client.CreateSecret(&secretsmanager.CreateSecretInput{ + Name: aws.String(key), + SecretString: aws.String(val), + }) + if err == nil { + return + } + attempts-- + if attempts < 0 { + Fail("unable to create secret: " + err.Error()) + } + <-time.After(time.Second * 5) + } } +// DeleteSecret deletes a secret at the provider. +// There may be a short delay between calling this function +// and the removal of the secret on the provider side. func (s *SMProvider) DeleteSecret(key string) { + log.Logf("deleting secret %s", key) _, err := s.client.DeleteSecret(&secretsmanager.DeleteSecretInput{ - SecretId: aws.String(key), + SecretId: aws.String(key), + ForceDeleteWithoutRecovery: aws.Bool(true), }) Expect(err).ToNot(HaveOccurred()) } -func (s *SMProvider) BeforeEach() { - By("creating a AWS SM credentials secret") +// MountedIRSAStore is a SecretStore without auth config +// ESO relies on the pod-mounted ServiceAccount when using this store. +func (s *SMProvider) SetupMountedIRSAStore() { + secretStore := &esv1alpha1.SecretStore{ + ObjectMeta: metav1.ObjectMeta{ + Name: s.MountedIRSAStoreName(), + Namespace: s.framework.Namespace.Name, + }, + Spec: esv1alpha1.SecretStoreSpec{ + Provider: &esv1alpha1.SecretStoreProvider{ + AWS: &esv1alpha1.AWSProvider{ + Service: esv1alpha1.AWSServiceSecretsManager, + Region: s.region, + Auth: esv1alpha1.AWSAuth{}, + }, + }, + }, + } + err := s.framework.CRClient.Create(context.Background(), secretStore) + Expect(err).ToNot(HaveOccurred()) +} + +func (s *SMProvider) MountedIRSAStoreName() string { + return "irsa-mounted-" + s.framework.Namespace.Name +} + +// ReferncedIRSAStore is a ClusterStore +// that references a (IRSA-) ServiceAccount in the default namespace. +func (s *SMProvider) SetupReferencedIRSAStore() { + log.Logf("creating IRSA ClusterSecretStore %s", s.framework.Namespace.Name) + secretStore := &esv1alpha1.ClusterSecretStore{ + ObjectMeta: metav1.ObjectMeta{ + Name: s.ReferencedIRSAStoreName(), + }, + } + _, err := controllerutil.CreateOrUpdate(context.Background(), s.framework.CRClient, secretStore, func() error { + secretStore.Spec.Provider = &esv1alpha1.SecretStoreProvider{ + AWS: &esv1alpha1.AWSProvider{ + Service: esv1alpha1.AWSServiceSecretsManager, + Region: s.region, + Auth: esv1alpha1.AWSAuth{ + JWTAuth: &esv1alpha1.AWSJWTAuth{ + ServiceAccountRef: &esmetav1.ServiceAccountSelector{ + Name: s.ServiceAccountName, + Namespace: &s.ServiceAccountNamespace, + }, + }, + }, + }, + } + return nil + }) + Expect(err).ToNot(HaveOccurred()) +} + +func (s *SMProvider) ReferencedIRSAStoreName() string { + return "irsa-ref-" + s.framework.Namespace.Name +} + +// StaticStore is namespaced and references +// static credentials from a secret. +func (s *SMProvider) SetupStaticStore() { awsCreds := &v1.Secret{ ObjectMeta: metav1.ObjectMeta{ - Name: secretName, + Name: staticCredentialsSecretName, Namespace: s.framework.Namespace.Name, }, StringData: map[string]string{ - "kid": "foobar", - "sak": "foobar", + "kid": s.kid, + "sak": s.sak, }, } err := s.framework.CRClient.Create(context.Background(), awsCreds) Expect(err).ToNot(HaveOccurred()) - By("creating a AWS SM secret store") secretStore := &esv1alpha1.SecretStore{ ObjectMeta: metav1.ObjectMeta{ Name: s.framework.Namespace.Name, @@ -105,15 +222,15 @@ func (s *SMProvider) BeforeEach() { Provider: &esv1alpha1.SecretStoreProvider{ AWS: &esv1alpha1.AWSProvider{ Service: esv1alpha1.AWSServiceSecretsManager, - Region: "us-east-1", + Region: s.region, Auth: esv1alpha1.AWSAuth{ SecretRef: &esv1alpha1.AWSAuthSecretRef{ - AccessKeyID: esmeta.SecretKeySelector{ - Name: secretName, + AccessKeyID: esmetav1.SecretKeySelector{ + Name: staticCredentialsSecretName, Key: "kid", }, - SecretAccessKey: esmeta.SecretKeySelector{ - Name: secretName, + SecretAccessKey: esmetav1.SecretKeySelector{ + Name: staticCredentialsSecretName, Key: "sak", }, }, diff --git a/e2e/suite/aws/secretsmanager.go b/e2e/suite/aws/secretsmanager.go index feafa029f..f51fbd58c 100644 --- a/e2e/suite/aws/secretsmanager.go +++ b/e2e/suite/aws/secretsmanager.go @@ -15,103 +15,17 @@ limitations under the License. package aws import ( - "context" - "fmt" // nolint - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" - // nolint - . "github.com/onsi/ginkgo/extensions/table" - - // nolint - . "github.com/onsi/gomega" - v1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "sigs.k8s.io/controller-runtime/pkg/client" - - esv1alpha1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1alpha1" - esmeta "github.com/external-secrets/external-secrets/apis/meta/v1" "github.com/external-secrets/external-secrets/e2e/framework" "github.com/external-secrets/external-secrets/e2e/suite/common" ) -var _ = Describe("[aws] ", func() { - f := framework.New("eso-aws") - prov := newSMProvider(f, "http://localstack.default") - - jwt := func(tc *framework.TestCase) { - saName := "my-sa" - err := f.CRClient.Create(context.Background(), &v1.ServiceAccount{ - ObjectMeta: metav1.ObjectMeta{ - Name: saName, - Namespace: f.Namespace.Name, - Annotations: map[string]string{ - "eks.amazonaws.com/role-arn": "arn:aws:iam::account:role/my-example-role", - }, - }, - }) - Expect(err).ToNot(HaveOccurred()) - - // create secret store - secretStore := &esv1alpha1.SecretStore{ - TypeMeta: metav1.TypeMeta{ - Kind: esv1alpha1.SecretStoreKind, - APIVersion: esv1alpha1.SchemeGroupVersion.String(), - }, - ObjectMeta: metav1.ObjectMeta{ - Name: f.Namespace.Name, - Namespace: f.Namespace.Name, - }, - Spec: esv1alpha1.SecretStoreSpec{ - Provider: &esv1alpha1.SecretStoreProvider{ - AWS: &esv1alpha1.AWSProvider{ - Service: esv1alpha1.AWSServiceSecretsManager, - Region: "us-east-1", - Auth: esv1alpha1.AWSAuth{ - JWTAuth: &esv1alpha1.AWSJWTAuth{ - ServiceAccountRef: &esmeta.ServiceAccountSelector{ - Name: saName, - Namespace: &f.Namespace.Name, - }, - }, - }, - }, - }, - }, - } - err = f.CRClient.Patch(context.Background(), secretStore, client.Apply, client.FieldOwner("e2e-case"), client.ForceOwnership) - Expect(err).ToNot(HaveOccurred()) - - secretKey1 := fmt.Sprintf("%s-%s", f.Namespace.Name, "one") - secretKey2 := fmt.Sprintf("%s-%s", f.Namespace.Name, "other") - secretValue := "bar" - tc.Secrets = map[string]string{ - secretKey1: secretValue, - secretKey2: secretValue, - } - tc.ExpectedSecret = &v1.Secret{ - Type: v1.SecretTypeOpaque, - Data: map[string][]byte{ - secretKey1: []byte(secretValue), - secretKey2: []byte(secretValue), - }, - } - tc.ExternalSecret.Spec.Data = []esv1alpha1.ExternalSecretData{ - { - SecretKey: secretKey1, - RemoteRef: esv1alpha1.ExternalSecretDataRemoteRef{ - Key: secretKey1, - }, - }, - { - SecretKey: secretKey2, - RemoteRef: esv1alpha1.ExternalSecretDataRemoteRef{ - Key: secretKey2, - }, - }, - } - } +var _ = Describe("[aws] ", Label("aws", "secretsmanager"), func() { + f := framework.New("eso-aws-sm") + prov := NewFromEnv(f) DescribeTable("sync secrets", framework.TableFunc(f, @@ -121,7 +35,6 @@ var _ = Describe("[aws] ", func() { Entry(common.JSONDataFromSync(f)), Entry(common.JSONDataWithProperty(f)), Entry(common.JSONDataWithTemplate(f)), - Entry("should sync secrets with jwt auth", jwt), Entry(common.DockerJSONConfig(f)), Entry(common.DataPropertyDockerconfigJSON(f)), Entry(common.SSHKeySync(f)), diff --git a/e2e/suite/aws/secretsmanager_managed.go b/e2e/suite/aws/secretsmanager_managed.go new file mode 100644 index 000000000..6d83d3d73 --- /dev/null +++ b/e2e/suite/aws/secretsmanager_managed.go @@ -0,0 +1,102 @@ +/* +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. +*/ + +package aws + +import ( + + // nolint + . "github.com/onsi/ginkgo/v2" + + esv1alpha1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1alpha1" + "github.com/external-secrets/external-secrets/e2e/framework" + "github.com/external-secrets/external-secrets/e2e/framework/addon" + "github.com/external-secrets/external-secrets/e2e/suite/common" +) + +const ( + withReferencedIRSA = "with referenced IRSA" + withMountedIRSA = "with mounted IRSA" +) + +// here we use the global eso instance +// that uses the service account in the default namespace +// which was created by terraform. +var _ = Describe("[awsmanaged] IRSA via referenced service account", Label("aws", "secretsmanager", "managed"), func() { + f := framework.New("eso-aws-managed") + prov := NewFromEnv(f) + + DescribeTable("sync secrets", + framework.TableFunc(f, + prov), + framework.Compose(withReferencedIRSA, f, common.SimpleDataSync, useClusterSecretStore(prov)), + framework.Compose(withReferencedIRSA, f, common.NestedJSONWithGJSON, useClusterSecretStore(prov)), + framework.Compose(withReferencedIRSA, f, common.JSONDataFromSync, useClusterSecretStore(prov)), + framework.Compose(withReferencedIRSA, f, common.JSONDataWithProperty, useClusterSecretStore(prov)), + framework.Compose(withReferencedIRSA, f, common.JSONDataWithTemplate, useClusterSecretStore(prov)), + framework.Compose(withReferencedIRSA, f, common.DockerJSONConfig, useClusterSecretStore(prov)), + framework.Compose(withReferencedIRSA, f, common.DataPropertyDockerconfigJSON, useClusterSecretStore(prov)), + framework.Compose(withReferencedIRSA, f, common.SSHKeySync, useClusterSecretStore(prov)), + framework.Compose(withReferencedIRSA, f, common.SSHKeySyncDataProperty, useClusterSecretStore(prov)), + framework.Compose(withReferencedIRSA, f, common.SyncWithoutTargetName, useClusterSecretStore(prov)), + framework.Compose(withReferencedIRSA, f, common.JSONDataWithoutTargetName, useClusterSecretStore(prov)), + ) +}) + +// here we create a central eso instance in the default namespace +// that mounts the service account which was created by terraform. +var _ = Describe("[awsmanaged] with mounted IRSA", Label("aws", "secretsmanager", "managed"), func() { + f := framework.New("eso-aws-managed") + prov := NewFromEnv(f) + + // each test case gets its own ESO instance + BeforeEach(func() { + f.Install(addon.NewESO( + addon.WithControllerClass(f.BaseName), + addon.WithServiceAccount(prov.ServiceAccountName), + addon.WithReleaseName(f.Namespace.Name), + addon.WithNamespace("default"), + )) + }) + + DescribeTable("sync secrets", + framework.TableFunc(f, + prov), + framework.Compose(withMountedIRSA, f, common.SimpleDataSync, useMountedIRSAStore(prov)), + framework.Compose(withMountedIRSA, f, common.NestedJSONWithGJSON, useMountedIRSAStore(prov)), + framework.Compose(withMountedIRSA, f, common.JSONDataFromSync, useMountedIRSAStore(prov)), + framework.Compose(withMountedIRSA, f, common.JSONDataWithProperty, useMountedIRSAStore(prov)), + framework.Compose(withMountedIRSA, f, common.JSONDataWithTemplate, useMountedIRSAStore(prov)), + framework.Compose(withMountedIRSA, f, common.DockerJSONConfig, useMountedIRSAStore(prov)), + framework.Compose(withMountedIRSA, f, common.DataPropertyDockerconfigJSON, useMountedIRSAStore(prov)), + framework.Compose(withMountedIRSA, f, common.SSHKeySync, useMountedIRSAStore(prov)), + framework.Compose(withMountedIRSA, f, common.SSHKeySyncDataProperty, useMountedIRSAStore(prov)), + framework.Compose(withMountedIRSA, f, common.SyncWithoutTargetName, useMountedIRSAStore(prov)), + framework.Compose(withMountedIRSA, f, common.JSONDataWithoutTargetName, useMountedIRSAStore(prov)), + ) +}) + +func useClusterSecretStore(prov *SMProvider) func(*framework.TestCase) { + return func(tc *framework.TestCase) { + tc.ExternalSecret.Spec.SecretStoreRef.Kind = esv1alpha1.ClusterSecretStoreKind + tc.ExternalSecret.Spec.SecretStoreRef.Name = prov.ReferencedIRSAStoreName() + } +} + +func useMountedIRSAStore(prov *SMProvider) func(*framework.TestCase) { + return func(tc *framework.TestCase) { + tc.ExternalSecret.Spec.SecretStoreRef.Kind = esv1alpha1.SecretStoreKind + tc.ExternalSecret.Spec.SecretStoreRef.Name = prov.MountedIRSAStoreName() + } +} diff --git a/e2e/suite/azure/azure.go b/e2e/suite/azure/azure.go index d1c57b1e8..79a96aef5 100644 --- a/e2e/suite/azure/azure.go +++ b/e2e/suite/azure/azure.go @@ -13,28 +13,17 @@ limitations under the License. package azure import ( - "os" // nolint - . "github.com/onsi/ginkgo" - // nolint - . "github.com/onsi/ginkgo/extensions/table" + . "github.com/onsi/ginkgo/v2" "github.com/external-secrets/external-secrets/e2e/framework" "github.com/external-secrets/external-secrets/e2e/suite/common" ) -var _ = Describe("[azure] ", func() { +var _ = Describe("[azure]", Label("azure", "keyvault"), func() { f := framework.New("eso-azure") - vaultURL := os.Getenv("VAULT_URL") - tenantID := os.Getenv("TENANT_ID") - clientID := os.Getenv("AZURE_CLIENT_ID") - clientSecret := os.Getenv("AZURE_CLIENT_SECRET") - prov := &azureProvider{} - - if vaultURL != "" && tenantID != "" && clientID != "" && clientSecret != "" { - prov = newazureProvider(f, clientID, clientSecret, tenantID, vaultURL) - } + prov := newFromEnv(f) DescribeTable("sync secrets", framework.TableFunc(f, prov), Entry(common.SimpleDataSync(f)), diff --git a/e2e/suite/azure/provider.go b/e2e/suite/azure/provider.go index a3d312303..7354176db 100644 --- a/e2e/suite/azure/provider.go +++ b/e2e/suite/azure/provider.go @@ -14,15 +14,17 @@ package azure import ( "context" + "os" "github.com/Azure/azure-sdk-for-go/profiles/latest/keyvault/keyvault" kvauth "github.com/Azure/go-autorest/autorest/azure/auth" // nolint - . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" // nolint - . "github.com/onsi/gomega" + . "github.com/onsi/ginkgo/v2" + v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" utilpointer "k8s.io/utils/pointer" @@ -45,7 +47,9 @@ func newazureProvider(f *framework.Framework, clientID, clientSecret, tenantID, clientCredentialsConfig := kvauth.NewClientCredentialsConfig(clientID, clientSecret, tenantID) clientCredentialsConfig.Resource = "https://vault.azure.net" authorizer, err := clientCredentialsConfig.Authorizer() - Expect(err).ToNot(HaveOccurred()) + if err != nil { + Fail(err.Error()) + } basicClient := keyvault.New() basicClient.Authorizer = authorizer @@ -57,10 +61,22 @@ func newazureProvider(f *framework.Framework, clientID, clientSecret, tenantID, vaultURL: vaultURL, client: &basicClient, } - BeforeEach(prov.BeforeEach) + + BeforeEach(func() { + prov.CreateSecretStore() + }) + return prov } +func newFromEnv(f *framework.Framework) *azureProvider { + vaultURL := os.Getenv("VAULT_URL") + tenantID := os.Getenv("TENANT_ID") + clientID := os.Getenv("AZURE_CLIENT_ID") + clientSecret := os.Getenv("AZURE_CLIENT_SECRET") + return newazureProvider(f, clientID, clientSecret, tenantID, vaultURL) +} + func (s *azureProvider) CreateSecret(key, val string) { _, err := s.client.SetSecret( context.Background(), @@ -84,7 +100,7 @@ func (s *azureProvider) DeleteSecret(key string) { Expect(err).ToNot(HaveOccurred()) } -func (s *azureProvider) BeforeEach() { +func (s *azureProvider) CreateSecretStore() { azureCreds := &v1.Secret{ ObjectMeta: metav1.ObjectMeta{ Name: "provider-secret", diff --git a/e2e/suite/gcp/gcp.go b/e2e/suite/gcp/gcp.go index 2f274c8d2..82c1caead 100644 --- a/e2e/suite/gcp/gcp.go +++ b/e2e/suite/gcp/gcp.go @@ -17,12 +17,9 @@ import ( "crypto/x509" "encoding/pem" "fmt" - "os" // nolint - . "github.com/onsi/ginkgo" - // nolint - . "github.com/onsi/ginkgo/extensions/table" + . "github.com/onsi/ginkgo/v2" v1 "k8s.io/api/core/v1" p12 "software.sslmate.com/src/go-pkcs12" @@ -32,21 +29,32 @@ import ( "github.com/external-secrets/external-secrets/e2e/suite/common" ) -var _ = Describe("[gcp] ", func() { +// This test uses the global ESO. +var _ = Describe("[gcp]", Label("gcp", "secretsmanager"), func() { f := framework.New("eso-gcp") - credentials := os.Getenv("GCP_SM_SA_JSON") - projectID := os.Getenv("GCP_PROJECT_ID") - prov := &GcpProvider{} + prov := NewFromEnv(f, "") - if credentials != "" && projectID != "" { - prov = NewgcpProvider(f, credentials, projectID, "", "", "", "") - } + DescribeTable("sync secrets", framework.TableFunc(f, prov), + Entry(common.SimpleDataSync(f)), + Entry(common.JSONDataWithProperty(f)), + Entry(common.JSONDataFromSync(f)), + Entry(common.NestedJSONWithGJSON(f)), + Entry(common.JSONDataWithTemplate(f)), + Entry(common.DockerJSONConfig(f)), + Entry(common.DataPropertyDockerconfigJSON(f)), + Entry(common.SSHKeySync(f)), + Entry(common.SSHKeySyncDataProperty(f)), + Entry(common.SyncWithoutTargetName(f)), + Entry(common.JSONDataWithoutTargetName(f)), + Entry("should sync p12 encoded cert secret", p12Cert), + ) +}) - // P12Cert case creates a secret with a p12 cert containing a privkey and cert bundled together. - // It uses templating to generate a k8s secret of type tls with pem values - p12Cert := func(tc *framework.TestCase) { - cloudSecretName := fmt.Sprintf("%s-%s", f.Namespace.Name, "p12-cert-example") - certPEM := `-----BEGIN CERTIFICATE----- +// P12Cert case creates a secret with a p12 cert containing a privkey and cert bundled together. +// It uses templating to generate a k8s secret of type tls with pem values. +var p12Cert = func(tc *framework.TestCase) { + cloudSecretName := fmt.Sprintf("%s-%s", tc.Framework.Namespace.Name, "p12-cert-example") + certPEM := `-----BEGIN CERTIFICATE----- MIIFQjCCBCqgAwIBAgISBHszg5W2maz/7CIxGrf7mqukMA0GCSqGSIb3DQEBCwUA MDIxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MQswCQYDVQQD EwJSMzAeFw0yMTA3MjQxMjQyMzNaFw0yMTEwMjIxMjQyMzFaMCgxJjAkBgNVBAMT @@ -78,7 +86,7 @@ XMYitHfpGhc+DTTiTWMQ13J0b1j4yv8A7ZaG2366aa28oSTD6eQFhmVCBwa54j++ IOwzHn5R -----END CERTIFICATE----- ` - privkeyPEM := `-----BEGIN PRIVATE KEY----- + privkeyPEM := `-----BEGIN PRIVATE KEY----- MIIEwAIBADANBgkqhkiG9w0BAQEFAASCBKowggSmAgEAAoIBAQDJFE51myQDyqca egyBDlHLkxVj+WCjcfOWEqrTa7bcnbDXjD4uIRTaFxIkpi/k5fKxt+rszna7bNdh lezqSuRBmVg2kXDul5nQm1RtWRKlJP9fhvUYkoNKRGzt9OL6/6lv05P2tNu13yN8 @@ -107,55 +115,39 @@ Jdx0ECYawviQoreDAyIXV6HouoeRbDtLZ9AJvxMoIjGcjAR2FQHc3yx4h/lf3Tfx x6HaRh+EUwU51von6M9lEF9/p5Q= -----END PRIVATE KEY----- ` - blockCert, _ := pem.Decode([]byte(certPEM)) - cert, _ := x509.ParseCertificate(blockCert.Bytes) - blockPrivKey, _ := pem.Decode([]byte(privkeyPEM)) - privkey, _ := x509.ParsePKCS8PrivateKey(blockPrivKey.Bytes) - emptyCACerts := []*x509.Certificate{} - p12Cert, _ := p12.Encode(rand.Reader, privkey, cert, emptyCACerts, "") + blockCert, _ := pem.Decode([]byte(certPEM)) + cert, _ := x509.ParseCertificate(blockCert.Bytes) + blockPrivKey, _ := pem.Decode([]byte(privkeyPEM)) + privkey, _ := x509.ParsePKCS8PrivateKey(blockPrivKey.Bytes) + emptyCACerts := []*x509.Certificate{} + p12Cert, _ := p12.Encode(rand.Reader, privkey, cert, emptyCACerts, "") - tc.Secrets = map[string]string{ - cloudSecretName: string(p12Cert), - } - - tc.ExpectedSecret = &v1.Secret{ - Type: v1.SecretTypeTLS, - Data: map[string][]byte{ - "tls.crt": []byte(certPEM), - "tls.key": []byte(privkeyPEM), - }, - } - - tc.ExternalSecret.Spec.Data = []esv1alpha1.ExternalSecretData{ - { - SecretKey: "mysecret", - RemoteRef: esv1alpha1.ExternalSecretDataRemoteRef{ - Key: cloudSecretName, - }, - }, - } - - tc.ExternalSecret.Spec.Target.Template = &esv1alpha1.ExternalSecretTemplate{ - Type: v1.SecretTypeTLS, - Data: map[string]string{ - "tls.crt": "{{ .mysecret | pkcs12cert | pemCertificate }}", - "tls.key": "{{ .mysecret | pkcs12key | pemPrivateKey }}", - }, - } + tc.Secrets = map[string]string{ + cloudSecretName: string(p12Cert), } - DescribeTable("sync secrets", framework.TableFunc(f, prov), - Entry(common.SimpleDataSync(f)), - Entry(common.JSONDataWithProperty(f)), - Entry(common.JSONDataFromSync(f)), - Entry(common.NestedJSONWithGJSON(f)), - Entry(common.JSONDataWithTemplate(f)), - Entry(common.DockerJSONConfig(f)), - Entry(common.DataPropertyDockerconfigJSON(f)), - Entry(common.SSHKeySync(f)), - Entry(common.SSHKeySyncDataProperty(f)), - Entry(common.SyncWithoutTargetName(f)), - Entry(common.JSONDataWithoutTargetName(f)), - Entry("should sync p12 encoded cert secret", p12Cert), - ) -}) + tc.ExpectedSecret = &v1.Secret{ + Type: v1.SecretTypeTLS, + Data: map[string][]byte{ + "tls.crt": []byte(certPEM), + "tls.key": []byte(privkeyPEM), + }, + } + + tc.ExternalSecret.Spec.Data = []esv1alpha1.ExternalSecretData{ + { + SecretKey: "mysecret", + RemoteRef: esv1alpha1.ExternalSecretDataRemoteRef{ + Key: cloudSecretName, + }, + }, + } + + tc.ExternalSecret.Spec.Target.Template = &esv1alpha1.ExternalSecretTemplate{ + Type: v1.SecretTypeTLS, + Data: map[string]string{ + "tls.crt": "{{ .mysecret | pkcs12cert | pemCertificate }}", + "tls.key": "{{ .mysecret | pkcs12key | pemPrivateKey }}", + }, + } +} diff --git a/e2e/suite/gcp/gcp_managed.go b/e2e/suite/gcp/gcp_managed.go new file mode 100644 index 000000000..7dea4dab6 --- /dev/null +++ b/e2e/suite/gcp/gcp_managed.go @@ -0,0 +1,111 @@ +/* +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. +limitations under the License. +*/ +package gcp + +import ( + + // nolint + . "github.com/onsi/ginkgo/v2" + + // nolint + // . "github.com/onsi/gomega" + esv1alpha1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1alpha1" + "github.com/external-secrets/external-secrets/e2e/framework" + "github.com/external-secrets/external-secrets/e2e/framework/addon" + "github.com/external-secrets/external-secrets/e2e/suite/common" +) + +const ( + withPodID = "sync secrets with pod identity" + withSpecifcSA = "sync secrets with specificSA identity" +) + +// Deploys eso to the default namespace +// that uses the service account provisioned by terraform +// to test pod-identity authentication. +var _ = Describe("[gcpmanaged] with pod identity", Label("gcp", "secretsmanager", "managed", "pod-identity"), func() { + f := framework.New("eso-gcpmanaged") + prov := NewFromEnv(f, f.BaseName) + + // each test case gets its own ESO instance + BeforeEach(func() { + f.Install(addon.NewESO( + addon.WithControllerClass(f.BaseName), + addon.WithServiceAccount(prov.ServiceAccountName), + addon.WithReleaseName(f.Namespace.Name), + addon.WithNamespace("default"), + )) + }) + + DescribeTable("sync secrets", + framework.TableFunc(f, + prov), + // uses pod id + framework.Compose(withPodID, f, common.SimpleDataSync, usePodIDESReference), + framework.Compose(withPodID, f, common.JSONDataWithProperty, usePodIDESReference), + framework.Compose(withPodID, f, common.JSONDataFromSync, usePodIDESReference), + framework.Compose(withPodID, f, common.NestedJSONWithGJSON, usePodIDESReference), + framework.Compose(withPodID, f, common.JSONDataWithTemplate, usePodIDESReference), + framework.Compose(withPodID, f, common.DockerJSONConfig, usePodIDESReference), + framework.Compose(withPodID, f, common.DataPropertyDockerconfigJSON, usePodIDESReference), + framework.Compose(withPodID, f, common.SSHKeySync, usePodIDESReference), + framework.Compose(withPodID, f, common.SSHKeySyncDataProperty, usePodIDESReference), + framework.Compose(withPodID, f, common.SyncWithoutTargetName, usePodIDESReference), + framework.Compose(withPodID, f, common.JSONDataWithoutTargetName, usePodIDESReference), + ) +}) + +// We're using a namespace scoped ESO +// that runs WITHOUT pod identity (with default sa) +// It uses a specific service account defined in the ClusterSecretStore spec +// to authenticate against cloud provider APIs. +var _ = Describe("[gcpmanaged] with service account", Label("gcp", "secretsmanager", "managed", "service-account"), func() { + f := framework.New("eso-gcpmanaged") + prov := NewFromEnv(f, f.BaseName) + + BeforeEach(func() { + f.Install(addon.NewESO( + addon.WithControllerClass(f.BaseName), + addon.WithReleaseName(f.Namespace.Name), + addon.WithNamespace(f.Namespace.Name), + )) + }) + + DescribeTable("sync secrets", + framework.TableFunc(f, + prov), + // uses specific sa + framework.Compose(withSpecifcSA, f, common.JSONDataFromSync, useSpecifcSAESReference(prov)), + framework.Compose(withSpecifcSA, f, common.JSONDataWithProperty, useSpecifcSAESReference(prov)), + framework.Compose(withSpecifcSA, f, common.JSONDataFromSync, useSpecifcSAESReference(prov)), + framework.Compose(withSpecifcSA, f, common.NestedJSONWithGJSON, useSpecifcSAESReference(prov)), + framework.Compose(withSpecifcSA, f, common.JSONDataWithTemplate, useSpecifcSAESReference(prov)), + framework.Compose(withSpecifcSA, f, common.DockerJSONConfig, useSpecifcSAESReference(prov)), + framework.Compose(withSpecifcSA, f, common.DataPropertyDockerconfigJSON, useSpecifcSAESReference(prov)), + framework.Compose(withSpecifcSA, f, common.SSHKeySync, useSpecifcSAESReference(prov)), + framework.Compose(withSpecifcSA, f, common.SSHKeySyncDataProperty, useSpecifcSAESReference(prov)), + framework.Compose(withSpecifcSA, f, common.SyncWithoutTargetName, useSpecifcSAESReference(prov)), + framework.Compose(withSpecifcSA, f, common.JSONDataWithoutTargetName, useSpecifcSAESReference(prov)), + ) +}) + +func usePodIDESReference(tc *framework.TestCase) { + tc.ExternalSecret.Spec.SecretStoreRef.Name = PodIDSecretStoreName +} + +func useSpecifcSAESReference(prov *GcpProvider) func(*framework.TestCase) { + return func(tc *framework.TestCase) { + tc.ExternalSecret.Spec.SecretStoreRef.Kind = esv1alpha1.ClusterSecretStoreKind + tc.ExternalSecret.Spec.SecretStoreRef.Name = prov.SAClusterSecretStoreName() + } +} diff --git a/e2e/suite/gcp/provider.go b/e2e/suite/gcp/provider.go index bb5ef5d71..7e20b16b2 100644 --- a/e2e/suite/gcp/provider.go +++ b/e2e/suite/gcp/provider.go @@ -15,116 +15,97 @@ package gcp import ( "context" "fmt" + "os" secretmanager "cloud.google.com/go/secretmanager/apiv1" // nolint - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" // nolint . "github.com/onsi/gomega" - "golang.org/x/oauth2" "golang.org/x/oauth2/google" "golang.org/x/oauth2/jwt" "google.golang.org/api/option" secretmanagerpb "google.golang.org/genproto/googleapis/cloud/secretmanager/v1" v1 "k8s.io/api/core/v1" - apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" utilpointer "k8s.io/utils/pointer" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" esv1alpha1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1alpha1" esmeta "github.com/external-secrets/external-secrets/apis/meta/v1" "github.com/external-secrets/external-secrets/e2e/framework" - "github.com/external-secrets/external-secrets/e2e/framework/log" gcpsm "github.com/external-secrets/external-secrets/pkg/provider/gcp/secretmanager" ) const ( - PodIDSecretStoreName = "pod-identity" - SpecifcSASecretStoreName = "specific-sa" + PodIDSecretStoreName = "pod-identity" + staticCredentialsSecretName = "provider-secret" ) -func makeStore(s *GcpProvider) *esv1alpha1.SecretStore { - return &esv1alpha1.SecretStore{ - ObjectMeta: metav1.ObjectMeta{ - Name: s.framework.Namespace.Name, - Namespace: s.framework.Namespace.Name, - }, - Spec: esv1alpha1.SecretStoreSpec{ - Provider: &esv1alpha1.SecretStoreProvider{ - GCPSM: &esv1alpha1.GCPSMProvider{ - ProjectID: s.projectID, - }, - }, - }, - } -} - -func makeCStore(s *GcpProvider) *esv1alpha1.ClusterSecretStore { - return &esv1alpha1.ClusterSecretStore{ - ObjectMeta: metav1.ObjectMeta{ - Name: s.framework.Namespace.Name, - Namespace: s.framework.Namespace.Name, - }, - Spec: esv1alpha1.SecretStoreSpec{ - Provider: &esv1alpha1.SecretStoreProvider{ - GCPSM: &esv1alpha1.GCPSMProvider{ - ProjectID: s.projectID, - }, - }, - }, - } -} - // nolint // Better to keep names consistent even if it stutters; type GcpProvider struct { - credentials string - projectID string - framework *framework.Framework - clusterLocation string - clusterName string - serviceAccountName string - serviceAccountNamespace string + ServiceAccountName string + ServiceAccountNamespace string + + framework *framework.Framework + credentials string + projectID string + clusterLocation string + clusterName string + controllerClass string } -func NewgcpProvider(f *framework.Framework, credentials, projectID string, - clusterLocation string, clusterName string, serviceAccountName string, serviceAccountNamespace string) *GcpProvider { +func NewGCPProvider(f *framework.Framework, credentials, projectID string, + clusterLocation string, clusterName string, serviceAccountName string, serviceAccountNamespace string, controllerClass string) *GcpProvider { prov := &GcpProvider{ credentials: credentials, projectID: projectID, framework: f, clusterLocation: clusterLocation, clusterName: clusterName, - serviceAccountName: serviceAccountName, - serviceAccountNamespace: serviceAccountNamespace, + ServiceAccountName: serviceAccountName, + ServiceAccountNamespace: serviceAccountNamespace, + controllerClass: controllerClass, } - BeforeEach(prov.BeforeEach) + + BeforeEach(func() { + prov.CreateSAKeyStore(f.Namespace.Name) + prov.CreateSpecifcSASecretStore(f.Namespace.Name) + prov.CreatePodIDStore(f.Namespace.Name) + }) + + AfterEach(func() { + prov.DeleteSpecifcSASecretStore() + }) + return prov } -func (s *GcpProvider) getClient(ctx context.Context, credentials string) (client *secretmanager.Client, err error) { - if credentials == "" { - var ts oauth2.TokenSource - ts, err = google.DefaultTokenSource(ctx, gcpsm.CloudPlatformRole) - Expect(err).ToNot(HaveOccurred()) - client, err = secretmanager.NewClient(ctx, option.WithTokenSource(ts)) - Expect(err).ToNot(HaveOccurred()) - } else { - var config *jwt.Config - config, err = google.JWTConfigFromJSON([]byte(s.credentials), gcpsm.CloudPlatformRole) - Expect(err).ToNot(HaveOccurred()) - ts := config.TokenSource(ctx) - client, err = secretmanager.NewClient(ctx, option.WithTokenSource(ts)) - Expect(err).ToNot(HaveOccurred()) - } +func NewFromEnv(f *framework.Framework, controllerClass string) *GcpProvider { + projectID := os.Getenv("GCP_PROJECT_ID") + credentials := os.Getenv("GCP_SM_SA_JSON") + serviceAccountName := os.Getenv("GCP_KSA_NAME") + serviceAccountNamespace := "default" + clusterLocation := os.Getenv("GCP_GKE_ZONE") + clusterName := os.Getenv("GCP_GKE_CLUSTER") + return NewGCPProvider(f, credentials, projectID, clusterLocation, clusterName, serviceAccountName, serviceAccountNamespace, controllerClass) +} + +func (s *GcpProvider) getClient(ctx context.Context) (client *secretmanager.Client, err error) { + var config *jwt.Config + config, err = google.JWTConfigFromJSON([]byte(s.credentials), gcpsm.CloudPlatformRole) + Expect(err).ToNot(HaveOccurred()) + ts := config.TokenSource(ctx) + client, err = secretmanager.NewClient(ctx, option.WithTokenSource(ts)) + Expect(err).ToNot(HaveOccurred()) return client, err } func (s *GcpProvider) CreateSecret(key, val string) { ctx := context.Background() - client, err := s.getClient(ctx, s.credentials) + client, err := s.getClient(ctx) Expect(err).ToNot(HaveOccurred()) defer client.Close() // Create the request to create the secret. @@ -153,7 +134,7 @@ func (s *GcpProvider) CreateSecret(key, val string) { func (s *GcpProvider) DeleteSecret(key string) { ctx := context.Background() - client, err := s.getClient(ctx, s.credentials) + client, err := s.getClient(ctx) Expect(err).ToNot(HaveOccurred()) Expect(err).ToNot(HaveOccurred()) defer client.Close() @@ -164,11 +145,27 @@ func (s *GcpProvider) DeleteSecret(key string) { Expect(err).ToNot(HaveOccurred()) } -func (s *GcpProvider) BeforeEach() { - By("creating a gcp secret") +func makeStore(s *GcpProvider) *esv1alpha1.SecretStore { + return &esv1alpha1.SecretStore{ + ObjectMeta: metav1.ObjectMeta{ + Name: s.framework.Namespace.Name, + Namespace: s.framework.Namespace.Name, + }, + Spec: esv1alpha1.SecretStoreSpec{ + Controller: s.controllerClass, + Provider: &esv1alpha1.SecretStoreProvider{ + GCPSM: &esv1alpha1.GCPSMProvider{ + ProjectID: s.projectID, + }, + }, + }, + } +} + +func (s *GcpProvider) CreateSAKeyStore(ns string) { gcpCreds := &v1.Secret{ ObjectMeta: metav1.ObjectMeta{ - Name: "provider-secret", + Name: staticCredentialsSecretName, Namespace: s.framework.Namespace.Name, }, StringData: map[string]string{ @@ -180,23 +177,16 @@ func (s *GcpProvider) BeforeEach() { err = s.framework.CRClient.Update(context.Background(), gcpCreds) Expect(err).ToNot(HaveOccurred()) } - By("creating an secret stores gcp") - s.CreateSAKeyStore(s.framework.Namespace.Name) - s.CreatePodIDStore(s.framework.Namespace.Name) - s.CreateSpecifcSASecretStore(s.framework.Namespace.Name) -} - -func (s *GcpProvider) CreateSAKeyStore(ns string) { secretStore := makeStore(s) secretStore.Spec.Provider.GCPSM.Auth = esv1alpha1.GCPSMAuth{ SecretRef: &esv1alpha1.GCPSMAuthSecretRef{ SecretAccessKey: esmeta.SecretKeySelector{ - Name: "provider-secret", + Name: staticCredentialsSecretName, Key: "secret-access-credentials", }, }, } - err := s.framework.CRClient.Create(context.Background(), secretStore) + err = s.framework.CRClient.Create(context.Background(), secretStore) Expect(err).ToNot(HaveOccurred()) } @@ -207,29 +197,45 @@ func (s *GcpProvider) CreatePodIDStore(ns string) { Expect(err).ToNot(HaveOccurred()) } +func (s *GcpProvider) SAClusterSecretStoreName() string { + return "gcpsa-" + s.framework.Namespace.Name +} + func (s *GcpProvider) CreateSpecifcSASecretStore(ns string) { - clusterSecretStore := makeCStore(s) - clusterSecretStore.ObjectMeta.Name = SpecifcSASecretStoreName - clusterSecretStore.Spec.Provider.GCPSM.Auth = esv1alpha1.GCPSMAuth{ - WorkloadIdentity: &esv1alpha1.GCPWorkloadIdentity{ - ClusterLocation: s.clusterLocation, - ClusterName: s.clusterName, - ServiceAccountRef: esmeta.ServiceAccountSelector{ - Name: s.serviceAccountName, - Namespace: utilpointer.StringPtr(s.serviceAccountNamespace), - }, + clusterSecretStore := &esv1alpha1.ClusterSecretStore{ + ObjectMeta: metav1.ObjectMeta{ + Name: s.SAClusterSecretStoreName(), }, } - - var cSS esv1alpha1.ClusterSecretStore - - err := s.framework.CRClient.Get(context.Background(), types.NamespacedName{ - Name: SpecifcSASecretStoreName, - }, &cSS) - if apierrors.IsNotFound(err) { - err := s.framework.CRClient.Create(context.Background(), clusterSecretStore) - Expect(err).ToNot(HaveOccurred()) - } else { - log.Logf("%s CSStore already created", SpecifcSASecretStoreName) - } + _, err := controllerutil.CreateOrUpdate(context.Background(), s.framework.CRClient, clusterSecretStore, func() error { + clusterSecretStore.Spec.Controller = s.controllerClass + clusterSecretStore.Spec.Provider = &esv1alpha1.SecretStoreProvider{ + GCPSM: &esv1alpha1.GCPSMProvider{ + ProjectID: s.projectID, + Auth: esv1alpha1.GCPSMAuth{ + WorkloadIdentity: &esv1alpha1.GCPWorkloadIdentity{ + ClusterLocation: s.clusterLocation, + ClusterName: s.clusterName, + ServiceAccountRef: esmeta.ServiceAccountSelector{ + Name: s.ServiceAccountName, + Namespace: utilpointer.StringPtr(s.ServiceAccountNamespace), + }, + }, + }, + }, + } + return nil + }) + Expect(err).ToNot(HaveOccurred()) +} + +// Cleanup removes global resources that may have been +// created by this provider. +func (s *GcpProvider) DeleteSpecifcSASecretStore() { + err := s.framework.CRClient.Delete(context.Background(), &esv1alpha1.ClusterSecretStore{ + ObjectMeta: metav1.ObjectMeta{ + Name: s.SAClusterSecretStoreName(), + }, + }) + Expect(err).ToNot(HaveOccurred()) } diff --git a/e2e/suite/gcpmanaged/gcpmanaged.go b/e2e/suite/gcpmanaged/gcpmanaged.go deleted file mode 100644 index 67001af16..000000000 --- a/e2e/suite/gcpmanaged/gcpmanaged.go +++ /dev/null @@ -1,86 +0,0 @@ -/* -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. -limitations under the License. -*/ -package gcpmanaged - -import ( - "os" - - // nolint - . "github.com/onsi/ginkgo" - // nolint - . "github.com/onsi/ginkgo/extensions/table" - - // nolint - // . "github.com/onsi/gomega" - esv1alpha1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1alpha1" - "github.com/external-secrets/external-secrets/e2e/framework" - "github.com/external-secrets/external-secrets/e2e/suite/common" - "github.com/external-secrets/external-secrets/e2e/suite/gcp" -) - -const ( - withPodID = "sync secrets with pod identity" - withSpecifcSA = "sync secrets with specificSA identity" -) - -var _ = Describe("[gcpmanaged] ", func() { - if os.Getenv("FOCUS") == "gcpmanaged" { - f := framework.New("eso-gcp-managed") - projectID := os.Getenv("GCP_PROJECT_ID") - clusterLocation := "europe-west1-b" - clusterName := "test-cluster" - serviceAccountName := os.Getenv("GCP_KSA_NAME") - serviceAccountNamespace := "default" - prov := &gcp.GcpProvider{} - if projectID != "" { - prov = gcp.NewgcpProvider(f, "", projectID, clusterLocation, clusterName, serviceAccountName, serviceAccountNamespace) - } - DescribeTable("sync secrets", - framework.TableFunc(f, - prov), - // uses pod id - framework.Compose(withPodID, f, common.SimpleDataSync, usePodIDESReference), - framework.Compose(withPodID, f, common.JSONDataWithProperty, usePodIDESReference), - framework.Compose(withPodID, f, common.JSONDataFromSync, usePodIDESReference), - framework.Compose(withPodID, f, common.NestedJSONWithGJSON, usePodIDESReference), - framework.Compose(withPodID, f, common.JSONDataWithTemplate, usePodIDESReference), - framework.Compose(withPodID, f, common.DockerJSONConfig, usePodIDESReference), - framework.Compose(withPodID, f, common.DataPropertyDockerconfigJSON, usePodIDESReference), - framework.Compose(withPodID, f, common.SSHKeySync, usePodIDESReference), - framework.Compose(withPodID, f, common.SSHKeySyncDataProperty, usePodIDESReference), - framework.Compose(withPodID, f, common.SyncWithoutTargetName, usePodIDESReference), - framework.Compose(withPodID, f, common.JSONDataWithoutTargetName, usePodIDESReference), - // uses specific sa - framework.Compose(withSpecifcSA, f, common.JSONDataFromSync, useSpecifcSAESReference), - framework.Compose(withSpecifcSA, f, common.JSONDataWithProperty, useSpecifcSAESReference), - framework.Compose(withSpecifcSA, f, common.JSONDataFromSync, useSpecifcSAESReference), - framework.Compose(withSpecifcSA, f, common.NestedJSONWithGJSON, useSpecifcSAESReference), - framework.Compose(withSpecifcSA, f, common.JSONDataWithTemplate, useSpecifcSAESReference), - framework.Compose(withSpecifcSA, f, common.DockerJSONConfig, useSpecifcSAESReference), - framework.Compose(withSpecifcSA, f, common.DataPropertyDockerconfigJSON, useSpecifcSAESReference), - framework.Compose(withSpecifcSA, f, common.SSHKeySync, useSpecifcSAESReference), - framework.Compose(withSpecifcSA, f, common.SSHKeySyncDataProperty, useSpecifcSAESReference), - framework.Compose(withSpecifcSA, f, common.SyncWithoutTargetName, useSpecifcSAESReference), - framework.Compose(withSpecifcSA, f, common.JSONDataWithoutTargetName, useSpecifcSAESReference), - ) - } -}) - -func usePodIDESReference(tc *framework.TestCase) { - tc.ExternalSecret.Spec.SecretStoreRef.Name = gcp.PodIDSecretStoreName -} - -func useSpecifcSAESReference(tc *framework.TestCase) { - tc.ExternalSecret.Spec.SecretStoreRef.Kind = esv1alpha1.ClusterSecretStoreKind - tc.ExternalSecret.Spec.SecretStoreRef.Name = gcp.SpecifcSASecretStoreName -} diff --git a/e2e/suite/gitlab/gitlab.go b/e2e/suite/gitlab/gitlab.go index 0f643eb84..a80b1f5b7 100644 --- a/e2e/suite/gitlab/gitlab.go +++ b/e2e/suite/gitlab/gitlab.go @@ -18,27 +18,19 @@ package gitlab // and in e2e/suite/common/common.go, but this breaks Azure provider. import ( - "os" // nolint - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" // nolint - . "github.com/onsi/ginkgo/extensions/table" + . "github.com/onsi/ginkgo/v2/extensions/table" "github.com/external-secrets/external-secrets/e2e/framework" "github.com/external-secrets/external-secrets/e2e/suite/common" ) -var _ = Describe("[gitlab] ", func() { - f := framework.New("esogitlab") - credentials := os.Getenv("GITLAB_TOKEN") - projectID := os.Getenv("GITLAB_PROJECT_ID") - - prov := &gitlabProvider{} - - if credentials != "" && projectID != "" { - prov = newGitlabProvider(f, credentials, projectID) - } +var _ = Describe("[gitlab]", Label("gitlab"), func() { + f := framework.New("eso-gitlab") + prov := newFromEnv(f) DescribeTable("sync secrets", framework.TableFunc(f, prov), Entry(common.SimpleDataSync(f)), diff --git a/e2e/suite/gitlab/provider.go b/e2e/suite/gitlab/provider.go index deff8c6ff..4dde4bb17 100644 --- a/e2e/suite/gitlab/provider.go +++ b/e2e/suite/gitlab/provider.go @@ -15,10 +15,11 @@ package gitlab import ( "context" + "os" "strings" // nolint - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" // nolint . "github.com/onsi/gomega" @@ -47,6 +48,12 @@ func newGitlabProvider(f *framework.Framework, credentials, projectID string) *g return prov } +func newFromEnv(f *framework.Framework) *gitlabProvider { + credentials := os.Getenv("GITLAB_TOKEN") + projectID := os.Getenv("GITLAB_PROJECT_ID") + return newGitlabProvider(f, credentials, projectID) +} + func (s *gitlabProvider) CreateSecret(key, val string) { // **Open the client client, err := gitlab.NewClient(s.credentials) diff --git a/e2e/suite/import.go b/e2e/suite/import.go index eb53c7b28..fd9c332f8 100644 --- a/e2e/suite/import.go +++ b/e2e/suite/import.go @@ -19,6 +19,5 @@ import ( _ "github.com/external-secrets/external-secrets/e2e/suite/aws" _ "github.com/external-secrets/external-secrets/e2e/suite/azure" _ "github.com/external-secrets/external-secrets/e2e/suite/gcp" - _ "github.com/external-secrets/external-secrets/e2e/suite/gcpmanaged" _ "github.com/external-secrets/external-secrets/e2e/suite/vault" ) diff --git a/e2e/suite/oracle/oracle.go b/e2e/suite/oracle/oracle.go index 91f2f7568..36bb425c3 100644 --- a/e2e/suite/oracle/oracle.go +++ b/e2e/suite/oracle/oracle.go @@ -13,25 +13,19 @@ limitations under the License. package oracle import ( - "os" // nolint - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" // nolint - . "github.com/onsi/ginkgo/extensions/table" + . "github.com/onsi/ginkgo/v2/extensions/table" "github.com/external-secrets/external-secrets/e2e/framework" "github.com/external-secrets/external-secrets/e2e/suite/common" ) -var _ = Describe("[oracle] ", func() { +var _ = Describe("[oracle]", Label("oracle"), func() { f := framework.New("eso-oracle") - tenancy := os.Getenv("OCI_TENANCY_OCID") - user := os.Getenv("OCI_USER_OCID") - region := os.Getenv("OCI_REGION") - fingerprint := os.Getenv("OCI_FINGERPRINT") - privateKey := os.Getenv("OCI_PRIVATE_KEY") - prov := newOracleProvider(f, tenancy, user, region, fingerprint, privateKey) + prov := newFromEnv(f) DescribeTable("sync secrets", framework.TableFunc(f, prov), Entry(common.SimpleDataSync(f)), diff --git a/e2e/suite/oracle/provider.go b/e2e/suite/oracle/provider.go index 243ca3e5a..66d4d5b5c 100644 --- a/e2e/suite/oracle/provider.go +++ b/e2e/suite/oracle/provider.go @@ -14,9 +14,10 @@ package oracle import ( "context" + "os" // nolint - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" // nolint . "github.com/onsi/gomega" @@ -58,6 +59,15 @@ func newOracleProvider(f *framework.Framework, tenancy, user, region, fingerprin return prov } +func newFromEnv(f *framework.Framework) *oracleProvider { + tenancy := os.Getenv("OCI_TENANCY_OCID") + user := os.Getenv("OCI_USER_OCID") + region := os.Getenv("OCI_REGION") + fingerprint := os.Getenv("OCI_FINGERPRINT") + privateKey := os.Getenv("OCI_PRIVATE_KEY") + return newOracleProvider(f, tenancy, user, region, fingerprint, privateKey) +} + func (p *oracleProvider) CreateSecret(key, val string) { configurationProvider := common.NewRawConfigurationProvider(p.tenancy, p.user, p.region, p.fingerprint, p.privateKey, nil) client, err := vault.NewVaultsClientWithConfigurationProvider(configurationProvider) diff --git a/e2e/suite/vault/provider.go b/e2e/suite/vault/provider.go index b5fbef3ee..262302ae6 100644 --- a/e2e/suite/vault/provider.go +++ b/e2e/suite/vault/provider.go @@ -21,7 +21,7 @@ import ( vault "github.com/hashicorp/vault/api" //nolint - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" //nolint . "github.com/onsi/gomega" diff --git a/e2e/suite/vault/vault.go b/e2e/suite/vault/vault.go index 4fafd1c22..b42167aec 100644 --- a/e2e/suite/vault/vault.go +++ b/e2e/suite/vault/vault.go @@ -15,9 +15,7 @@ package vault import ( // nolint - . "github.com/onsi/ginkgo" - // nolint - . "github.com/onsi/ginkgo/extensions/table" + . "github.com/onsi/ginkgo/v2" "github.com/external-secrets/external-secrets/e2e/framework" "github.com/external-secrets/external-secrets/e2e/suite/common" @@ -32,12 +30,12 @@ const ( withK8s = "with kubernetes provider" ) -var _ = Describe("[vault] ", func() { +var _ = Describe("[vault]", Label("vault"), func() { f := framework.New("eso-vault") + prov := newVaultProvider(f) DescribeTable("sync secrets", - framework.TableFunc(f, - newVaultProvider(f)), + framework.TableFunc(f, prov), // uses token auth framework.Compose(withTokenAuth, f, common.JSONDataFromSync, useTokenAuth), framework.Compose(withTokenAuth, f, common.JSONDataWithProperty, useTokenAuth), diff --git a/go.mod b/go.mod index ca3f0cadc..9ad973fe2 100644 --- a/go.mod +++ b/go.mod @@ -57,7 +57,7 @@ require ( github.com/hashicorp/vault/api v1.3.1 github.com/huandu/xstrings v1.3.2 // indirect github.com/lestrrat-go/jwx v1.2.1 - github.com/onsi/ginkgo v1.16.5 + github.com/onsi/ginkgo/v2 v2.0.0 github.com/onsi/gomega v1.17.0 github.com/oracle/oci-go-sdk/v45 v45.2.0 github.com/prometheus/client_golang v1.11.0 @@ -133,6 +133,7 @@ require ( github.com/golang/snappy v0.0.4 // indirect github.com/google/go-querystring v1.0.0 // indirect github.com/google/gofuzz v1.2.0 // indirect + github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 // indirect github.com/googleapis/gax-go/v2 v2.1.1 // indirect github.com/googleapis/gnostic v0.5.5 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect @@ -174,7 +175,6 @@ require ( github.com/moby/spdystream v0.2.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect - github.com/nxadm/tail v1.4.8 // indirect github.com/oklog/run v1.1.0 // indirect github.com/oklog/ulid v1.3.1 // indirect github.com/pierrec/lz4 v2.6.1+incompatible // indirect @@ -211,7 +211,6 @@ require ( gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/ini.v1 v1.62.0 // indirect gopkg.in/square/go-jose.v2 v2.6.0 // indirect - gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect honnef.co/go/tools v0.1.4 // indirect k8s.io/apiextensions-apiserver v0.23.0 // indirect diff --git a/go.sum b/go.sum index abdc65117..e2bf79563 100644 --- a/go.sum +++ b/go.sum @@ -409,8 +409,10 @@ github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec= github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -678,6 +680,8 @@ github.com/onsi/ginkgo v1.16.2/go.mod h1:CObGmKUOKaSC0RjmoAK7tKyn4Azo5P2IWuoMnvw github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= +github.com/onsi/ginkgo/v2 v2.0.0 h1:CcuG/HvWNkkaqCUpJifQY8z7qEMBJya6aLPx6ftGyjQ= +github.com/onsi/ginkgo/v2 v2.0.0/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= diff --git a/main.go b/main.go index 0286e3009..efcb2b566 100644 --- a/main.go +++ b/main.go @@ -56,7 +56,7 @@ func main() { "Enabling this will ensure there is only one active controller manager.") flag.IntVar(&concurrent, "concurrent", 1, "The number of concurrent ExternalSecret reconciles.") flag.StringVar(&loglevel, "loglevel", "info", "loglevel to use, one of: debug, info, warn, error, dpanic, panic, fatal") - flag.StringVar(&namespace, "namespace", "", "watch external secrets scoped in the provided namespace only") + flag.StringVar(&namespace, "namespace", "", "watch external secrets scoped in the provided namespace only. ClusterSecretStore can be used but only work if it doesn't reference resources from other namespaces") flag.Parse() var lvl zapcore.Level diff --git a/pkg/controllers/externalsecret/externalsecret_controller_test.go b/pkg/controllers/externalsecret/externalsecret_controller_test.go index 693271882..4d963290a 100644 --- a/pkg/controllers/externalsecret/externalsecret_controller_test.go +++ b/pkg/controllers/externalsecret/externalsecret_controller_test.go @@ -20,8 +20,7 @@ import ( "strconv" "time" - . "github.com/onsi/ginkgo" - . "github.com/onsi/ginkgo/extensions/table" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" dto "github.com/prometheus/client_model/go" v1 "k8s.io/api/core/v1" diff --git a/pkg/controllers/externalsecret/suite_test.go b/pkg/controllers/externalsecret/suite_test.go index 4fc46b655..011bbdcb2 100644 --- a/pkg/controllers/externalsecret/suite_test.go +++ b/pkg/controllers/externalsecret/suite_test.go @@ -19,7 +19,7 @@ import ( "testing" "time" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "go.uber.org/zap/zapcore" "k8s.io/client-go/kubernetes/scheme" @@ -89,7 +89,7 @@ var _ = BeforeSuite(func() { defer GinkgoRecover() Expect(k8sManager.Start(ctrl.SetupSignalHandler())).ToNot(HaveOccurred()) }() -}, 60) +}) var _ = AfterSuite(func() { By("tearing down the test environment") diff --git a/pkg/controllers/secretstore/suite_test.go b/pkg/controllers/secretstore/suite_test.go index c3173275d..be41bd75c 100644 --- a/pkg/controllers/secretstore/suite_test.go +++ b/pkg/controllers/secretstore/suite_test.go @@ -18,7 +18,7 @@ import ( "path/filepath" "testing" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "k8s.io/client-go/kubernetes/scheme" "k8s.io/client-go/rest" @@ -65,7 +65,7 @@ var _ = BeforeSuite(func() { k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme}) Expect(err).ToNot(HaveOccurred()) Expect(k8sClient).ToNot(BeNil()) -}, 60) +}) var _ = AfterSuite(func() { By("tearing down the test environment") diff --git a/terraform/aws/main.tf b/terraform/aws/main.tf new file mode 100644 index 000000000..ca514658c --- /dev/null +++ b/terraform/aws/main.tf @@ -0,0 +1,8 @@ +module "cluster" { + source = "./modules/cluster" + + cluster_name = var.AWS_CLUSTER_NAME + cluster_region = var.AWS_REGION + irsa_sa_name = var.AWS_SA_NAME + irsa_sa_namespace = var.AWS_SA_NAMESPACE +} diff --git a/terraform/aws/modules/cluster/auth.tf b/terraform/aws/modules/cluster/auth.tf new file mode 100644 index 000000000..0dce6e3ab --- /dev/null +++ b/terraform/aws/modules/cluster/auth.tf @@ -0,0 +1,60 @@ + +data "aws_eks_cluster_auth" "this" { + name = module.eks.cluster_id +} + +data "aws_caller_identity" "current" {} + +locals { + kubeconfig = yamlencode({ + apiVersion = "v1" + kind = "Config" + current-context = "terraform" + clusters = [{ + name = module.eks.cluster_id + cluster = { + certificate-authority-data = module.eks.cluster_certificate_authority_data + server = module.eks.cluster_endpoint + } + }] + contexts = [{ + name = "terraform" + context = { + cluster = module.eks.cluster_id + user = "terraform" + } + }] + users = [{ + name = "terraform" + user = { + token = data.aws_eks_cluster_auth.this.token + } + }] + }) + + # we have to allow the root account to access the api + aws_auth_configmap_yaml = <<-EOT + ${chomp(module.eks.aws_auth_configmap_yaml)} + - rolearn: arn:aws:iam::${data.aws_caller_identity.current.account_id}:role/admin + username: system:aws:root + groups: + - system:masters + EOT +} + +resource "null_resource" "patch_cm" { + triggers = { + kubeconfig = base64encode(local.kubeconfig) + cmd_patch = <<-EOT + kubectl patch configmap/aws-auth --patch "${local.aws_auth_configmap_yaml}" -n kube-system --kubeconfig <(echo $KUBECONFIG | base64 --decode) + EOT + } + + provisioner "local-exec" { + interpreter = ["/bin/bash", "-c"] + environment = { + KUBECONFIG = self.triggers.kubeconfig + } + command = self.triggers.cmd_patch + } +} diff --git a/terraform/aws/modules/cluster/irsa.tf b/terraform/aws/modules/cluster/irsa.tf new file mode 100644 index 000000000..ba4a256a3 --- /dev/null +++ b/terraform/aws/modules/cluster/irsa.tf @@ -0,0 +1,57 @@ +locals { + sa_manifest = <<-EOT + apiVersion: v1 + kind: ServiceAccount + metadata: + name: ${local.serviceaccount_name} + namespace: ${local.serviceaccount_namespace} + annotations: + eks.amazonaws.com/role-arn: "${aws_iam_role.eso-e2e-irsa.arn}" + EOT +} + +data "aws_iam_policy_document" "assume-policy" { + statement { + actions = ["sts:AssumeRoleWithWebIdentity"] + condition { + test = "StringEquals" + variable = "${trimprefix(module.eks.cluster_oidc_issuer_url, "https://")}:sub" + + values = [ + "system:serviceaccount:${local.serviceaccount_namespace}:${local.serviceaccount_name}" + ] + } + + principals { + type = "Federated" + identifiers = [module.eks.oidc_provider_arn] + } + } +} + +resource "aws_iam_role" "eso-e2e-irsa" { + name = "eso-e2e-irsa" + path = "/" + assume_role_policy = data.aws_iam_policy_document.assume-policy.json + managed_policy_arns = [ + "arn:aws:iam::aws:policy/SecretsManagerReadWrite" + ] + +} + +resource "null_resource" "apply_sa" { + triggers = { + kubeconfig = base64encode(local.kubeconfig) + cmd_patch = <<-EOT + echo '${local.sa_manifest}' | kubectl --kubeconfig <(echo $KUBECONFIG | base64 --decode) apply -f - + EOT + } + + provisioner "local-exec" { + interpreter = ["/bin/bash", "-c"] + environment = { + KUBECONFIG = self.triggers.kubeconfig + } + command = self.triggers.cmd_patch + } +} diff --git a/terraform/aws/modules/cluster/main.tf b/terraform/aws/modules/cluster/main.tf new file mode 100644 index 000000000..a4004445c --- /dev/null +++ b/terraform/aws/modules/cluster/main.tf @@ -0,0 +1,127 @@ +provider "aws" { + region = local.region +} + +locals { + name = var.cluster_name + cluster_version = "1.21" + region = var.cluster_region + + serviceaccount_name = var.irsa_sa_name + serviceaccount_namespace = var.irsa_sa_namespace + + tags = { + Example = local.name + GithubRepo = "external-secrets" + GithubOrg = "external-secrets" + } +} + +module "eks" { + source = "git::https://github.com/terraform-aws-modules/terraform-aws-eks?ref=v18.2.0" + + cluster_name = local.name + cluster_version = local.cluster_version + cluster_endpoint_private_access = true + cluster_endpoint_public_access = true + + cluster_addons = { + coredns = { + resolve_conflicts = "OVERWRITE" + } + kube-proxy = {} + vpc-cni = { + resolve_conflicts = "OVERWRITE" + } + + } + + vpc_id = module.vpc.vpc_id + subnet_ids = module.vpc.private_subnets + enable_irsa = true + + # EKS Managed Node Group(s) + eks_managed_node_group_defaults = { + ami_type = "AL2_x86_64" + disk_size = 50 + instance_types = ["m6i.large", "m5.large", "m5n.large", "m5zn.large"] + vpc_security_group_ids = [aws_security_group.additional.id] + } + + + eks_managed_node_groups = { + example = { + desired_size = 2 + + instance_types = ["t3.large"] + tags = local.tags + } + } + + tags = local.tags +} + +################################################################################ +# Supporting resources +################################################################################ + +module "vpc" { + source = "terraform-aws-modules/vpc/aws" + version = "~> 3.0" + + name = local.name + cidr = "10.0.0.0/16" + + azs = ["${local.region}a", "${local.region}b", "${local.region}c"] + private_subnets = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"] + public_subnets = ["10.0.4.0/24", "10.0.5.0/24", "10.0.6.0/24"] + + enable_nat_gateway = true + single_nat_gateway = true + enable_dns_hostnames = true + + enable_flow_log = true + create_flow_log_cloudwatch_iam_role = true + create_flow_log_cloudwatch_log_group = true + + public_subnet_tags = { + "kubernetes.io/cluster/${local.name}" = "shared" + "kubernetes.io/role/elb" = 1 + } + + private_subnet_tags = { + "kubernetes.io/cluster/${local.name}" = "shared" + "kubernetes.io/role/internal-elb" = 1 + } + + tags = local.tags +} + +resource "aws_security_group" "additional" { + name_prefix = "${local.name}-additional" + vpc_id = module.vpc.vpc_id + + ingress { + from_port = 22 + to_port = 22 + protocol = "tcp" + cidr_blocks = [ + "10.0.0.0/8", + "172.16.0.0/12", + "192.168.0.0/16", + ] + } + + # 443, 53, 123 is already allowed + egress { + from_port = 80 + to_port = 80 + protocol = "tcp" + cidr_blocks = ["0.0.0.0/0"] + ipv6_cidr_blocks = ["::/0"] + } + + + tags = local.tags +} + diff --git a/terraform/aws/modules/cluster/outputs.tf b/terraform/aws/modules/cluster/outputs.tf new file mode 100644 index 000000000..d35b94356 --- /dev/null +++ b/terraform/aws/modules/cluster/outputs.tf @@ -0,0 +1,135 @@ +################################################################################ +# Cluster +################################################################################ + +output "cluster_arn" { + description = "The Amazon Resource Name (ARN) of the cluster" + value = module.eks.cluster_arn +} + +output "cluster_certificate_authority_data" { + description = "Base64 encoded certificate data required to communicate with the cluster" + value = module.eks.cluster_certificate_authority_data +} + +output "cluster_endpoint" { + description = "Endpoint for your Kubernetes API server" + value = module.eks.cluster_endpoint +} + +output "cluster_id" { + description = "The name/id of the EKS cluster. Will block on cluster creation until the cluster is really ready" + value = module.eks.cluster_id +} + +output "cluster_oidc_issuer_url" { + description = "The URL on the EKS cluster for the OpenID Connect identity provider" + value = module.eks.cluster_oidc_issuer_url +} + +output "cluster_platform_version" { + description = "Platform version for the cluster" + value = module.eks.cluster_platform_version +} + +output "cluster_status" { + description = "Status of the EKS cluster. One of `CREATING`, `ACTIVE`, `DELETING`, `FAILED`" + value = module.eks.cluster_status +} + +output "cluster_security_group_id" { + description = "Cluster security group that was created by Amazon EKS for the cluster. Managed node groups use this security group for control-plane-to-data-plane communication. Referred to as 'Cluster security group' in the EKS console" + value = module.eks.cluster_security_group_id +} + +################################################################################ +# Security Group +################################################################################ + +output "cluster_security_group_arn" { + description = "Amazon Resource Name (ARN) of the cluster security group" + value = module.eks.cluster_security_group_arn +} + +################################################################################ +# IRSA +################################################################################ + +output "oidc_provider_arn" { + description = "The ARN of the OIDC Provider if `enable_irsa = true`" + value = module.eks.oidc_provider_arn +} + +################################################################################ +# IAM Role +################################################################################ + +output "cluster_iam_role_name" { + description = "IAM role name of the EKS cluster" + value = module.eks.cluster_iam_role_name +} + +output "cluster_iam_role_arn" { + description = "IAM role ARN of the EKS cluster" + value = module.eks.cluster_iam_role_arn +} + +output "cluster_iam_role_unique_id" { + description = "Stable and unique string identifying the IAM role" + value = module.eks.cluster_iam_role_unique_id +} + +################################################################################ +# EKS Addons +################################################################################ + +output "cluster_addons" { + description = "Map of attribute maps for all EKS cluster addons enabled" + value = module.eks.cluster_addons +} + +################################################################################ +# EKS Identity Provider +################################################################################ + +output "cluster_identity_providers" { + description = "Map of attribute maps for all EKS identity providers enabled" + value = module.eks.cluster_identity_providers +} + +################################################################################ +# CloudWatch Log Group +################################################################################ + +output "cloudwatch_log_group_name" { + description = "Name of cloudwatch log group created" + value = module.eks.cloudwatch_log_group_name +} + +output "cloudwatch_log_group_arn" { + description = "Arn of cloudwatch log group created" + value = module.eks.cloudwatch_log_group_arn +} + +################################################################################ +# Fargate Profile +################################################################################ + +output "fargate_profiles" { + description = "Map of attribute maps for all EKS Fargate Profiles created" + value = module.eks.fargate_profiles +} + +################################################################################ +# Additional +################################################################################ + +output "aws_auth_configmap_yaml" { + description = "Formatted yaml output for base aws-auth configmap containing roles used in cluster node groups/fargate profiles" + value = module.eks.aws_auth_configmap_yaml +} + +output "eks_cluster_auth_token" { + value = data.aws_eks_cluster_auth.this.token + sensitive = true +} diff --git a/terraform/aws/modules/cluster/provider.tf b/terraform/aws/modules/cluster/provider.tf new file mode 100644 index 000000000..fc1d45ab8 --- /dev/null +++ b/terraform/aws/modules/cluster/provider.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 0.13" + + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 3.0" + } + } +} diff --git a/terraform/aws/modules/cluster/variables.tf b/terraform/aws/modules/cluster/variables.tf new file mode 100644 index 000000000..093f1e33e --- /dev/null +++ b/terraform/aws/modules/cluster/variables.tf @@ -0,0 +1,16 @@ +variable "cluster_name" { + type = string + default = "eso-e2e-managed" +} + +variable "irsa_sa_name" { + type = string +} + +variable "irsa_sa_namespace" { + type = string +} + +variable "cluster_region" { + type = string +} diff --git a/terraform/aws/outputs.tf b/terraform/aws/outputs.tf new file mode 100644 index 000000000..2a8c30f85 --- /dev/null +++ b/terraform/aws/outputs.tf @@ -0,0 +1,11 @@ +output "cluster_arn" { + value = module.cluster.cluster_arn +} + +output "cluster_iam_role_arn" { + value = module.cluster.cluster_iam_role_arn +} + +output "aws_auth_configmap_yaml" { + value = module.cluster.aws_auth_configmap_yaml +} diff --git a/terraform/aws/provider.tf b/terraform/aws/provider.tf new file mode 100644 index 000000000..5c278a048 --- /dev/null +++ b/terraform/aws/provider.tf @@ -0,0 +1,11 @@ +terraform { + required_version = ">= 0.13" + + backend "s3" { + bucket = "eso-e2e-aws-tfstate" + key = "aws-tfstate" + region = "eu-west-1" + } + + required_providers {} +} diff --git a/terraform/aws/variables.tf b/terraform/aws/variables.tf new file mode 100644 index 000000000..9bf0f5d4a --- /dev/null +++ b/terraform/aws/variables.tf @@ -0,0 +1,19 @@ +variable "AWS_SA_NAME" { + type = string + default = "eso-e2e-test" +} + +variable "AWS_SA_NAMESPACE" { + type = string + default = "default" +} + +variable "AWS_REGION" { + type = string + default = "eu-west-1" +} + +variable "AWS_CLUSTER_NAME" { + type = string + default = "eso-e2e-managed" +} diff --git a/terraform/gcp/eso_gcp_modules/gke/main.tf b/terraform/gcp/eso_gcp_modules/gke/main.tf index 4a7510f4d..bb47fdcca 100644 --- a/terraform/gcp/eso_gcp_modules/gke/main.tf +++ b/terraform/gcp/eso_gcp_modules/gke/main.tf @@ -1,4 +1,5 @@ resource "google_service_account" "default" { + project = var.project_id account_id = var.GCP_GSA_NAME } @@ -27,6 +28,7 @@ resource "google_service_account_iam_member" "pod_identity_e2e" { } resource "google_container_cluster" "primary" { + project = var.project_id name = "${var.env}-cluster" location = var.zone remove_default_node_pool = true @@ -43,6 +45,7 @@ resource "google_container_cluster" "primary" { } resource "google_container_node_pool" "nodes" { + project = var.project_id name = "${google_container_cluster.primary.name}-node-pool" location = google_container_cluster.primary.location cluster = google_container_cluster.primary.name @@ -57,3 +60,20 @@ resource "google_container_node_pool" "nodes" { ] } } + +provider "kubernetes" { + host = "https://${google_container_cluster.primary.endpoint}" + token = data.google_client_config.default.access_token + cluster_ca_certificate = base64decode(google_container_cluster.primary.master_auth.0.cluster_ca_certificate) +} + +data "google_client_config" "default" {} + +resource "kubernetes_service_account" "test" { + metadata { + name = var.GCP_KSA_NAME + annotations = { + "iam.gke.io/gcp-service-account" : "${var.GCP_GSA_NAME}@${var.project_id}.iam.gserviceaccount.com" + } + } +} diff --git a/terraform/gcp/eso_gcp_modules/network/main.tf b/terraform/gcp/eso_gcp_modules/network/main.tf index a7dadae0b..a2372001e 100644 --- a/terraform/gcp/eso_gcp_modules/network/main.tf +++ b/terraform/gcp/eso_gcp_modules/network/main.tf @@ -1,9 +1,11 @@ resource "google_compute_network" "env-vpc" { + project = var.project_id name = "${var.env}-vpc" auto_create_subnetworks = false } resource "google_compute_subnetwork" "env-subnet" { + project = var.project_id name = "${google_compute_network.env-vpc.name}-subnet" region = var.region network = google_compute_network.env-vpc.name diff --git a/terraform/gcp/eso_gcp_modules/network/variable.tf b/terraform/gcp/eso_gcp_modules/network/variable.tf index 6f35274e1..81a11ac92 100644 --- a/terraform/gcp/eso_gcp_modules/network/variable.tf +++ b/terraform/gcp/eso_gcp_modules/network/variable.tf @@ -13,3 +13,6 @@ variable "ip_service_range" { variable "region" { default = "europe-west1" } +variable "project_id" { + type = string +} diff --git a/terraform/gcp/main.tf b/terraform/gcp/main.tf index d00e21477..b41815a63 100644 --- a/terraform/gcp/main.tf +++ b/terraform/gcp/main.tf @@ -11,6 +11,7 @@ module "test-network" { env = var.env region = var.region ip_cidr_range = var.ip_cidr_range + project_id = var.project_id } module "test-cluster" { diff --git a/tools.go b/tools.go index ce786904b..7d68ab319 100644 --- a/tools.go +++ b/tools.go @@ -5,6 +5,6 @@ package tools import ( _ "github.com/ahmetb/gen-crd-api-reference-docs" - _ "github.com/onsi/ginkgo/ginkgo" + _ "github.com/onsi/ginkgo/v2/ginkgo" _ "sigs.k8s.io/controller-tools/cmd/controller-gen" )