mirror of
https://github.com/external-secrets/external-secrets.git
synced 2024-12-14 11:57:59 +00:00
Feature: initial generator implementation + Github Actions OIDC/AWS (#1539)
Signed-off-by: Moritz Johner <beller.moritz@googlemail.com> Co-authored-by: Gustavo Fernandes de Carvalho <gusfcarvalho@gmail.com>
This commit is contained in:
parent
5debee41ac
commit
dabfa5a589
117 changed files with 6942 additions and 822 deletions
147
.github/actions/e2e-managed/action.yml
vendored
Normal file
147
.github/actions/e2e-managed/action.yml
vendored
Normal file
|
@ -0,0 +1,147 @@
|
|||
name: "e2e"
|
||||
description: "runs our e2e test suite"
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
|
||||
# create new status check for this specific provider
|
||||
- uses: actions/github-script@v6
|
||||
with:
|
||||
github-token: ${{ env.GITHUB_TOKEN }}
|
||||
script: |
|
||||
const { data: pull } = await github.rest.pulls.get({
|
||||
...context.repo,
|
||||
pull_number: process.env.GITHUB_PR_NUMBER
|
||||
});
|
||||
const ref = pull.head.sha;
|
||||
const { data: checks } = await github.rest.checks.listForRef({
|
||||
...context.repo,
|
||||
ref
|
||||
});
|
||||
const job_name = "e2e-managed-" + process.env.CLOUD_PROVIDER
|
||||
const check = checks.check_runs.filter(c => c.name === job_name);
|
||||
if(check && check.length > 0){
|
||||
const { data: result } = await github.rest.checks.update({
|
||||
...context.repo,
|
||||
check_run_id: check[0].id,
|
||||
status: 'in_progress',
|
||||
});
|
||||
return result;
|
||||
}
|
||||
const { data: result } = await github.rest.checks.create({
|
||||
...context.repo,
|
||||
name: job_name,
|
||||
head_sha: pull.head.sha,
|
||||
status: 'in_progress',
|
||||
});
|
||||
return result;
|
||||
|
||||
- name: Configure AWS Credentials
|
||||
uses: aws-actions/configure-aws-credentials@v1
|
||||
with:
|
||||
role-to-assume: ${{ env.AWS_OIDC_ROLE_ARN }}
|
||||
aws-region: ${{ env.AWS_REGION }}
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: "1.19"
|
||||
|
||||
- name: Find the Go Cache
|
||||
id: go
|
||||
shell: bash
|
||||
run: |
|
||||
echo "::set-output name=build-cache::$(go env GOCACHE)"
|
||||
echo "::set-output name=mod-cache::$(go env GOMODCACHE)"
|
||||
|
||||
- name: Cache the Go Build Cache
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ${{ steps.go.outputs.build-cache }}
|
||||
key: ${{ runner.os }}-build-unit-tests-${{ github.sha }}-${{ hashFiles('**/go.sum') }}
|
||||
restore-keys: ${{ runner.os }}-build-unit-tests-${{ github.sha }}-
|
||||
|
||||
- name: Cache Go Dependencies
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ${{ steps.go.outputs.mod-cache }}
|
||||
key: ${{ runner.os }}-pkg-${{ github.sha }}-${{ hashFiles('**/go.sum') }}
|
||||
restore-keys: ${{ runner.os }}-pkg-${{ github.sha }}-
|
||||
|
||||
- name: Setup TFLint
|
||||
uses: terraform-linters/setup-tflint@v2
|
||||
with:
|
||||
tflint_version: v0.28.0 # Must be specified. See: https://github.com/terraform-linters/tflint/releases for latest versions
|
||||
|
||||
- name: Run TFLint
|
||||
shell: bash
|
||||
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
|
||||
shell: bash
|
||||
if: env.CLOUD_PROVIDER == 'gcp'
|
||||
env:
|
||||
GCP_SM_SA_GKE_JSON: ${{ env.GCP_SM_SA_GKE_JSON }}
|
||||
run: |-
|
||||
mkdir -p terraform/gcp/secrets
|
||||
echo ${GCP_SM_SA_GKE_JSON} > terraform/gcp/secrets/gcloud-service-account-key.json
|
||||
|
||||
- name: Show TF
|
||||
shell: bash
|
||||
run: |-
|
||||
PROVIDER=${{env.CLOUD_PROVIDER}}
|
||||
make tf.show.${PROVIDER}
|
||||
|
||||
- name: Apply TF
|
||||
shell: bash
|
||||
env:
|
||||
TF_VAR_OIDC_TOKEN: "${{steps.fetch-token.outputs.result}}"
|
||||
run: |-
|
||||
PROVIDER=${{env.CLOUD_PROVIDER}}
|
||||
make tf.apply.${PROVIDER}
|
||||
|
||||
- name: Setup gcloud CLI
|
||||
if: env.CLOUD_PROVIDER == 'gcp'
|
||||
uses: google-github-actions/setup-gcloud@v0
|
||||
with:
|
||||
service_account_key: ${{ env.GCP_SM_SA_GKE_JSON }}
|
||||
project_id: ${{ env.GCP_PROJECT_ID }}
|
||||
|
||||
- name: Get the GKE credentials
|
||||
shell: bash
|
||||
if: env.CLOUD_PROVIDER == 'gcp'
|
||||
run: |-
|
||||
gcloud container clusters get-credentials "$GCP_GKE_CLUSTER" --zone "$GCP_GKE_ZONE" --project "$GCP_PROJECT_ID"
|
||||
|
||||
- name: Get the AWS credentials
|
||||
shell: bash
|
||||
if: env.CLOUD_PROVIDER == 'aws'
|
||||
run: |-
|
||||
aws --region $AWS_REGION eks update-kubeconfig --name $AWS_CLUSTER_NAME
|
||||
|
||||
- name: Login to Docker
|
||||
uses: docker/login-action@v2
|
||||
if: env.GHCR_USERNAME != ''
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ env.GHCR_USERNAME }}
|
||||
password: ${{ env.GHCR_TOKEN }}
|
||||
|
||||
- name: Run managed e2e Tests
|
||||
shell: bash
|
||||
env:
|
||||
GCP_SM_SA_JSON: ${{ env.GCP_SM_SA_JSON }}
|
||||
run: |
|
||||
export PATH=$PATH:$(go env GOPATH)/bin
|
||||
PROVIDER=${{env.CLOUD_PROVIDER}}
|
||||
go install github.com/onsi/ginkgo/v2/ginkgo@v2.1.6
|
||||
make test.e2e.managed GINKGO_LABELS="${PROVIDER}" TEST_SUITES="provider"
|
||||
|
||||
- name: Destroy TF
|
||||
shell: bash
|
||||
if: always()
|
||||
run: |-
|
||||
PROVIDER=${{env.CLOUD_PROVIDER}}
|
||||
make tf.destroy.${PROVIDER}
|
||||
|
69
.github/actions/e2e/action.yml
vendored
Normal file
69
.github/actions/e2e/action.yml
vendored
Normal file
|
@ -0,0 +1,69 @@
|
|||
name: "e2e"
|
||||
description: "runs our e2e test suite"
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
|
||||
- name: Configure AWS Credentials
|
||||
uses: aws-actions/configure-aws-credentials@v1
|
||||
with:
|
||||
role-to-assume: ${{ env.AWS_OIDC_ROLE_ARN }}
|
||||
aws-region: ${{ env.AWS_REGION }}
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: "${{ env.GO_VERSION }}"
|
||||
|
||||
- name: Find the Go Cache
|
||||
id: go
|
||||
shell: bash
|
||||
run: |
|
||||
echo "::set-output name=build-cache::$(go env GOCACHE)"
|
||||
echo "::set-output name=mod-cache::$(go env GOMODCACHE)"
|
||||
|
||||
- name: Cache the Go Build Cache
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ${{ steps.go.outputs.build-cache }}
|
||||
key: ${{ runner.os }}-build-unit-tests-${{ github.sha }}-${{ hashFiles('**/go.sum') }}
|
||||
restore-keys: ${{ runner.os }}-build-unit-tests-${{ github.sha }}-
|
||||
|
||||
- name: Cache Go Dependencies
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ${{ steps.go.outputs.mod-cache }}
|
||||
key: ${{ runner.os }}-pkg-${{ github.sha }}-${{ hashFiles('**/go.sum') }}
|
||||
restore-keys: ${{ runner.os }}-pkg-${{ github.sha }}-
|
||||
|
||||
- name: Setup kind
|
||||
uses: engineerd/setup-kind@v0.5.0
|
||||
with:
|
||||
version: ${{ env.KIND_VERSION }}
|
||||
wait: 10m
|
||||
image: ${{ env.KIND_IMAGE }}
|
||||
name: external-secrets
|
||||
|
||||
- name: Setup Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
with:
|
||||
version: ${{ env.DOCKER_BUILDX_VERSION }}
|
||||
install: true
|
||||
|
||||
- name: run go mod tidy
|
||||
shell: bash
|
||||
run: |
|
||||
go install github.com/onsi/ginkgo/v2/ginkgo@${{ env.GINKGO_VERSION }}
|
||||
go version
|
||||
ginkgo version
|
||||
cd e2e && go mod tidy && git status && git diff
|
||||
|
||||
- name: Run e2e Tests
|
||||
shell: bash
|
||||
env:
|
||||
BUILD_ARGS: --load
|
||||
run: |
|
||||
export PATH=$PATH:$(go env GOPATH)/bin
|
||||
go install github.com/onsi/ginkgo/v2/ginkgo@${{ env.GINKGO_VERSION }}
|
||||
make test.e2e
|
200
.github/workflows/e2e-managed.yml
vendored
200
.github/workflows/e2e-managed.yml
vendored
|
@ -1,10 +1,14 @@
|
|||
# Run secret-dependent e2e tests only after /ok-to-test-managed approval
|
||||
on:
|
||||
repository_dispatch:
|
||||
types: [ok-to-test-managed-command]
|
||||
|
||||
permissions:
|
||||
id-token: write
|
||||
contents: read
|
||||
|
||||
env:
|
||||
# Common versions
|
||||
GO_VERSION: '1.19'
|
||||
GINKGO_VERSION: 'v2.1.6'
|
||||
DOCKER_BUILDX_VERSION: 'v0.4.2'
|
||||
|
||||
|
@ -22,15 +26,15 @@ 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_OIDC_ROLE_ARN: ${{ secrets.AWS_OIDC_ROLE_ARN}}
|
||||
AWS_SA_NAME: ${{ secrets.AWS_SA_NAME }}
|
||||
AWS_SA_NAMESPACE: ${{ secrets.AWS_SA_NAMESPACE }}
|
||||
AWS_REGION: "eu-west-1"
|
||||
AWS_REGION: "eu-central-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_REGION: "eu-central-1"
|
||||
TF_VAR_AWS_CLUSTER_NAME: "eso-e2e-managed"
|
||||
|
||||
AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID}}
|
||||
|
@ -38,199 +42,71 @@ env:
|
|||
TENANT_ID: ${{ secrets.TENANT_ID}}
|
||||
VAULT_URL: ${{ secrets.VAULT_URL}}
|
||||
|
||||
name: e2e tests
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
GITHUB_PR_NUMBER: ${{ github.event.client_payload.pull_request.number }}
|
||||
CLOUD_PROVIDER: ${{ github.event.client_payload.slash_command.args.named.provider }}
|
||||
INFRACOST_API_KEY: ${{ secrets.INFRACOST_API_KEY }}
|
||||
GHCR_TOKEN: ${{ secrets.GHCR_TOKEN }}
|
||||
|
||||
name: managed e2e tests
|
||||
|
||||
jobs:
|
||||
integration-trusted:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository && github.actor !='dependabot[bot]'
|
||||
steps:
|
||||
|
||||
- name: Branch based PR checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Fetch History
|
||||
run: git fetch --prune --unshallow
|
||||
|
||||
- uses: ./.github/actions/e2e-managed
|
||||
env:
|
||||
CLOUD_PROVIDER: aws
|
||||
GITHUB_PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||
|
||||
integration-managed:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name == 'repository_dispatch'
|
||||
|
||||
steps:
|
||||
|
||||
# create new status check for this specific provider
|
||||
- uses: actions/github-script@v6
|
||||
if: ${{ always() }}
|
||||
env:
|
||||
number: ${{ github.event.client_payload.pull_request.number }}
|
||||
provider: ${{ github.event.client_payload.slash_command.args.named.provider }}
|
||||
job: ${{ github.job }}
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
script: |
|
||||
const { data: pull } = await github.rest.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.rest.checks.listForRef({
|
||||
...context.repo,
|
||||
ref
|
||||
});
|
||||
const job_name = process.env.job + "-" + process.env.provider
|
||||
console.log("\n\nPR CHECKS: " + checks)
|
||||
const check = checks.check_runs.filter(c => c.name === job_name);
|
||||
console.log("\n\nPR Filtered CHECK: " + check)
|
||||
console.log(check)
|
||||
if(check && check.length > 0){
|
||||
const { data: result } = await github.rest.checks.update({
|
||||
...context.repo,
|
||||
check_run_id: check[0].id,
|
||||
status: 'in_progress',
|
||||
});
|
||||
return result;
|
||||
}
|
||||
const { data: result } = await github.rest.checks.create({
|
||||
...context.repo,
|
||||
name: job_name,
|
||||
head_sha: pull.head.sha,
|
||||
status: 'in_progress',
|
||||
});
|
||||
return result;
|
||||
|
||||
|
||||
# Check out merge commit
|
||||
- name: Fork based /ok-to-test-managed checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
ref: 'refs/pull/${{ github.event.client_payload.pull_request.number }}/merge'
|
||||
ref: 'refs/pull/${{ env.GITHUB_PR_NUMBER }}/merge'
|
||||
|
||||
- name: Fetch History
|
||||
run: git fetch --prune --unshallow
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version-file: "go.mod"
|
||||
|
||||
- name: Find the Go Cache
|
||||
id: go
|
||||
run: |
|
||||
echo "::set-output name=build-cache::$(go env GOCACHE)"
|
||||
echo "::set-output name=mod-cache::$(go env GOMODCACHE)"
|
||||
- name: Cache the Go Build Cache
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ${{ steps.go.outputs.build-cache }}
|
||||
key: ${{ runner.os }}-build-unit-tests-${{ hashFiles('**/go.sum') }}
|
||||
restore-keys: ${{ runner.os }}-build-unit-tests-
|
||||
|
||||
- name: Cache Go Dependencies
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ${{ steps.go.outputs.mod-cache }}
|
||||
key: ${{ runner.os }}-pkg-${{ hashFiles('**/go.sum') }}
|
||||
restore-keys: ${{ runner.os }}-pkg-
|
||||
|
||||
- name: Setup TFLint
|
||||
uses: terraform-linters/setup-tflint@v2
|
||||
with:
|
||||
tflint_version: v0.28.0 # Must be specified. See: https://github.com/terraform-linters/tflint/releases for latest versions
|
||||
|
||||
- name: Run TFLint
|
||||
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
|
||||
run: |-
|
||||
PROVIDER=${{github.event.client_payload.slash_command.args.named.provider}}
|
||||
make tf.show.${PROVIDER}
|
||||
|
||||
- name: Setup Infracost
|
||||
uses: infracost/actions/setup@v2
|
||||
with:
|
||||
api-key: ${{ secrets.INFRACOST_API_KEY }}
|
||||
|
||||
- name: Generate Infracost JSON for provider
|
||||
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
|
||||
run: |
|
||||
infracost comment github --path=/tmp/infracost.json \
|
||||
--repo=$GITHUB_REPOSITORY \
|
||||
--github-token=${{ secrets.GITHUB_TOKEN }} \
|
||||
--pull-request=${{ github.event.client_payload.pull_request.number }} \
|
||||
--behavior=update
|
||||
|
||||
- name: Apply TF
|
||||
run: |-
|
||||
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@v0
|
||||
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 == 'gcp'
|
||||
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@v2
|
||||
if: env.GHCR_USERNAME != ''
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ secrets.GHCR_USERNAME }}
|
||||
password: ${{ secrets.GHCR_TOKEN }}
|
||||
|
||||
- name: Run managed e2e Tests
|
||||
run: |
|
||||
export PATH=$PATH:$(go env GOPATH)/bin
|
||||
PROVIDER=${{github.event.client_payload.slash_command.args.named.provider}}
|
||||
go install github.com/onsi/ginkgo/v2/ginkgo@${{env.GINKGO_VERSION}}
|
||||
make test.e2e.managed GINKGO_LABELS="${PROVIDER}" TEST_SUITES="provider"
|
||||
|
||||
- name: Destroy TF
|
||||
if: always()
|
||||
run: |-
|
||||
PROVIDER=${{github.event.client_payload.slash_command.args.named.provider}}
|
||||
make tf.destroy.${PROVIDER}
|
||||
- uses: ./.github/actions/e2e-managed
|
||||
|
||||
# set status=completed
|
||||
- uses: actions/github-script@v6
|
||||
if: ${{ always() }}
|
||||
env:
|
||||
number: ${{ github.event.client_payload.pull_request.number }}
|
||||
provider: ${{ github.event.client_payload.slash_command.args.named.provider }}
|
||||
number: ${{ env.GITHUB_PR_NUMBER }}
|
||||
provider: ${{ env.CLOUD_PROVIDER }}
|
||||
job: ${{ github.job }}
|
||||
# Conveniently, job.status maps to https://developer.github.com/v3/checks/runs/#update-a-check-run
|
||||
conclusion: ${{ job.status }}
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
github-token: ${{ env.GITHUB_TOKEN }}
|
||||
script: |
|
||||
const { data: pull } = await github.rest.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.rest.checks.listForRef({
|
||||
...context.repo,
|
||||
ref
|
||||
});
|
||||
const job_name = process.env.job + "-" + process.env.provider
|
||||
console.log("\n\nPR CHECKS: " + checks)
|
||||
const job_name = "e2e-managed-" + process.env.provider
|
||||
const check = checks.check_runs.filter(c => c.name === job_name);
|
||||
console.log("\n\nPR Filtered CHECK: " + check)
|
||||
console.log(check)
|
||||
const { data: result } = await github.rest.checks.update({
|
||||
...context.repo,
|
||||
check_run_id: check[0].id,
|
||||
|
|
114
.github/workflows/e2e.yml
vendored
114
.github/workflows/e2e.yml
vendored
|
@ -4,6 +4,12 @@ on:
|
|||
repository_dispatch:
|
||||
types: [ok-to-test-command]
|
||||
|
||||
permissions:
|
||||
id-token: write
|
||||
contents: read
|
||||
|
||||
name: e2e tests
|
||||
|
||||
env:
|
||||
# Common versions
|
||||
GO_VERSION: '1.19'
|
||||
|
@ -21,17 +27,18 @@ 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 }}
|
||||
|
||||
AWS_REGION: "eu-central-1"
|
||||
AWS_OIDC_ROLE_ARN: ${{ secrets.AWS_OIDC_ROLE_ARN }}
|
||||
|
||||
AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID}}
|
||||
AZURE_CLIENT_SECRET: ${{ secrets.AZURE_CLIENT_SECRET}}
|
||||
TENANT_ID: ${{ secrets.TENANT_ID}}
|
||||
VAULT_URL: ${{ secrets.VAULT_URL}}
|
||||
|
||||
name: e2e tests
|
||||
|
||||
jobs:
|
||||
# Branch-based pull request
|
||||
|
||||
integration-trusted:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository && github.actor !='dependabot[bot]'
|
||||
|
@ -40,63 +47,15 @@ jobs:
|
|||
- name: Branch based PR checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
# <insert integration tests needing secrets>
|
||||
|
||||
- name: Fetch History
|
||||
run: git fetch --prune --unshallow
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version-file: "go.mod"
|
||||
|
||||
- name: Find the Go Cache
|
||||
id: go
|
||||
run: |
|
||||
echo "::set-output name=build-cache::$(go env GOCACHE)"
|
||||
echo "::set-output name=mod-cache::$(go env GOMODCACHE)"
|
||||
|
||||
- name: Cache the Go Build Cache
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ${{ steps.go.outputs.build-cache }}
|
||||
key: ${{ runner.os }}-build-unit-tests-${{ github.sha }}-${{ hashFiles('**/go.sum') }}
|
||||
restore-keys: ${{ runner.os }}-build-unit-tests-${{ github.sha }}-
|
||||
|
||||
- name: Cache Go Dependencies
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ${{ steps.go.outputs.mod-cache }}
|
||||
key: ${{ runner.os }}-pkg-${{ github.sha }}-${{ hashFiles('**/go.sum') }}
|
||||
restore-keys: ${{ runner.os }}-pkg-${{ github.sha }}-
|
||||
|
||||
- name: Setup kind
|
||||
uses: engineerd/setup-kind@v0.5.0
|
||||
with:
|
||||
version: ${{env.KIND_VERSION}}
|
||||
wait: 10m
|
||||
node_image: ${{env.KIND_IMAGE}}
|
||||
name: external-secrets
|
||||
|
||||
- name: Setup Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
with:
|
||||
version: ${{ env.DOCKER_BUILDX_VERSION }}
|
||||
install: true
|
||||
|
||||
- name: Run e2e Tests
|
||||
env:
|
||||
BUILD_ARGS: "--load"
|
||||
run: |
|
||||
export PATH=$PATH:$(go env GOPATH)/bin
|
||||
go install github.com/onsi/ginkgo/v2/ginkgo@${{env.GINKGO_VERSION}}
|
||||
make test.e2e
|
||||
- uses: ./.github/actions/e2e
|
||||
|
||||
# Repo owner has commented /ok-to-test on a (fork-based) pull request
|
||||
integration-fork:
|
||||
runs-on: ubuntu-latest
|
||||
if:
|
||||
github.event_name == 'repository_dispatch'
|
||||
if: github.event_name == 'repository_dispatch'
|
||||
steps:
|
||||
|
||||
# Check out merge commit
|
||||
|
@ -108,52 +67,7 @@ jobs:
|
|||
- name: Fetch History
|
||||
run: git fetch --prune --unshallow
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version-file: "go.mod"
|
||||
|
||||
- name: Find the Go Cache
|
||||
id: go
|
||||
run: |
|
||||
echo "::set-output name=build-cache::$(go env GOCACHE)"
|
||||
echo "::set-output name=mod-cache::$(go env GOMODCACHE)"
|
||||
|
||||
- name: Cache the Go Build Cache
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ${{ steps.go.outputs.build-cache }}
|
||||
key: ${{ runner.os }}-build-unit-tests-${{ github.sha }}-${{ hashFiles('**/go.sum') }}
|
||||
restore-keys: ${{ runner.os }}-build-unit-tests-${{ github.sha }}-
|
||||
|
||||
- name: Cache Go Dependencies
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ${{ steps.go.outputs.mod-cache }}
|
||||
key: ${{ runner.os }}-pkg-${{ github.sha }}-${{ hashFiles('**/go.sum') }}
|
||||
restore-keys: ${{ runner.os }}-pkg-${{ github.sha }}-
|
||||
|
||||
- name: Setup kind
|
||||
uses: engineerd/setup-kind@v0.5.0
|
||||
with:
|
||||
version: ${{env.KIND_VERSION}}
|
||||
wait: 10m
|
||||
node_image: ${{env.KIND_IMAGE}}
|
||||
name: external-secrets
|
||||
|
||||
- name: Setup Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
with:
|
||||
version: ${{ env.DOCKER_BUILDX_VERSION }}
|
||||
install: true
|
||||
|
||||
- name: Run e2e Tests
|
||||
env:
|
||||
BUILD_ARGS: "--load"
|
||||
run: |
|
||||
export PATH=$PATH:$(go env GOPATH)/bin
|
||||
go install github.com/onsi/ginkgo/v2/ginkgo@${{env.GINKGO_VERSION}}
|
||||
make test.e2e
|
||||
- uses: ./.github/actions/e2e
|
||||
|
||||
# Update check run called "integration-fork"
|
||||
- uses: actions/github-script@v6
|
||||
|
|
5
.github/workflows/ok-to-test.yml
vendored
5
.github/workflows/ok-to-test.yml
vendored
|
@ -24,11 +24,8 @@ jobs:
|
|||
|
||||
- name: Slash Command Dispatch
|
||||
uses: peter-evans/slash-command-dispatch@v3
|
||||
env:
|
||||
TOKEN: ${{ steps.generate_token.outputs.token }}
|
||||
with:
|
||||
token: ${{ env.TOKEN }} # GitHub App installation access token
|
||||
# token: ${{ secrets.PERSONAL_ACCESS_TOKEN }} # PAT or OAuth token will also work
|
||||
token: ${{ steps.generate_token.outputs.token }}
|
||||
reaction-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
issue-type: pull-request
|
||||
commands: ok-to-test
|
||||
|
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -23,6 +23,7 @@ e2e/k8s/deploy
|
|||
e2e/suites/argocd/argocd.test
|
||||
e2e/suites/flux/flux.test
|
||||
e2e/suites/provider/provider.test
|
||||
e2e/suites/generator/generator.test
|
||||
|
||||
# tf ignores
|
||||
# Local .terraform directories
|
||||
|
|
|
@ -156,9 +156,17 @@ type ExternalSecretTarget struct {
|
|||
|
||||
// ExternalSecretData defines the connection between the Kubernetes Secret key (spec.data.<key>) and the Provider data.
|
||||
type ExternalSecretData struct {
|
||||
// SecretKey defines the key in which the controller stores
|
||||
// the value. This is the key in the Kind=Secret
|
||||
SecretKey string `json:"secretKey"`
|
||||
|
||||
// RemoteRef points to the remote secret and defines
|
||||
// which secret (version/property/..) to fetch.
|
||||
RemoteRef ExternalSecretDataRemoteRef `json:"remoteRef"`
|
||||
|
||||
// SourceRef allows you to override the source
|
||||
// from which the value will pulled from.
|
||||
SourceRef *SourceRef `json:"sourceRef,omitempty"`
|
||||
}
|
||||
|
||||
// ExternalSecretDataRemoteRef defines Provider data location.
|
||||
|
@ -214,9 +222,11 @@ const (
|
|||
|
||||
type ExternalSecretDataFromRemoteRef struct {
|
||||
// Used to extract multiple key/value pairs from one secret
|
||||
// Note: Extract does not support sourceRef.Generator or sourceRef.GeneratorRef.
|
||||
// +optional
|
||||
Extract *ExternalSecretDataRemoteRef `json:"extract,omitempty"`
|
||||
// Used to find secrets based on tags or regular expressions
|
||||
// Note: Find does not support sourceRef.Generator or sourceRef.GeneratorRef.
|
||||
// +optional
|
||||
Find *ExternalSecretFind `json:"find,omitempty"`
|
||||
|
||||
|
@ -224,6 +234,14 @@ type ExternalSecretDataFromRemoteRef struct {
|
|||
// Multiple Rewrite operations can be provided. They are applied in a layered order (first to last)
|
||||
// +optional
|
||||
Rewrite []ExternalSecretRewrite `json:"rewrite,omitempty"`
|
||||
|
||||
// SourceRef points to a store or generator
|
||||
// which contains secret values ready to use.
|
||||
// Use this in combination with Extract or Find pull values out of
|
||||
// a specific SecretStore.
|
||||
// When sourceRef points to a generator Extract or Find is not supported.
|
||||
// The generator returns a static map of values
|
||||
SourceRef *SourceRef `json:"sourceRef,omitempty"`
|
||||
}
|
||||
|
||||
type ExternalSecretRewrite struct {
|
||||
|
@ -270,6 +288,7 @@ type FindName struct {
|
|||
|
||||
// ExternalSecretSpec defines the desired state of ExternalSecret.
|
||||
type ExternalSecretSpec struct {
|
||||
// +optional
|
||||
SecretStoreRef SecretStoreRef `json:"secretStoreRef"`
|
||||
// +kubebuilder:default={creationPolicy:Owner,deletionPolicy:Retain}
|
||||
// +optional
|
||||
|
@ -291,6 +310,30 @@ type ExternalSecretSpec struct {
|
|||
DataFrom []ExternalSecretDataFromRemoteRef `json:"dataFrom,omitempty"`
|
||||
}
|
||||
|
||||
// SourceRef allows you to override the source
|
||||
// from which the secret will be pulled from.
|
||||
// You can define at maximum one property.
|
||||
// +kubebuilder:validation:MaxProperties=1
|
||||
type SourceRef struct {
|
||||
// +optional
|
||||
SecretStoreRef *SecretStoreRef `json:"storeRef,omitempty"`
|
||||
|
||||
// GeneratorRef points to a generator custom resource in
|
||||
// +optional
|
||||
GeneratorRef *GeneratorRef `json:"generatorRef,omitempty"`
|
||||
}
|
||||
|
||||
// GeneratorRef points to a generator custom resource.
|
||||
type GeneratorRef struct {
|
||||
// Specify the apiVersion of the generator resource
|
||||
// +kubebuilder:default="generators.external-secrets.io/v1alpha1"
|
||||
APIVersion string `json:"apiVersion,omitempty"`
|
||||
// Specify the Kind of the resource, e.g. Password, ACRAccessToken etc.
|
||||
Kind string `json:"kind"`
|
||||
// Specify the name of the generator resource
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
type ExternalSecretConditionType string
|
||||
|
||||
const (
|
||||
|
|
|
@ -48,5 +48,13 @@ func validateExternalSecret(obj runtime.Object) error {
|
|||
if es.Spec.Target.DeletionPolicy == DeletionPolicyMerge && es.Spec.Target.CreationPolicy == CreatePolicyNone {
|
||||
return fmt.Errorf("deletionPolicy=Merge must not be used with creationPolcy=None. There is no Secret to merge with")
|
||||
}
|
||||
|
||||
for _, ref := range es.Spec.DataFrom {
|
||||
findOrExtract := ref.Find != nil || ref.Extract != nil
|
||||
if findOrExtract && ref.SourceRef != nil && ref.SourceRef.GeneratorRef != nil {
|
||||
return fmt.Errorf("generator can not be used with find or extract")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
105
apis/externalsecrets/v1beta1/externalsecret_validator_test.go
Normal file
105
apis/externalsecrets/v1beta1/externalsecret_validator_test.go
Normal file
|
@ -0,0 +1,105 @@
|
|||
/*
|
||||
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 v1beta1
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
func TestValidateExternalSecret(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
obj runtime.Object
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "nil",
|
||||
obj: nil,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "deletion policy delete",
|
||||
obj: &ExternalSecret{
|
||||
Spec: ExternalSecretSpec{
|
||||
Target: ExternalSecretTarget{
|
||||
DeletionPolicy: DeletionPolicyDelete,
|
||||
CreationPolicy: CreatePolicyMerge,
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "deletion policy merge",
|
||||
obj: &ExternalSecret{
|
||||
Spec: ExternalSecretSpec{
|
||||
Target: ExternalSecretTarget{
|
||||
DeletionPolicy: DeletionPolicyMerge,
|
||||
CreationPolicy: CreatePolicyNone,
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "generator with find",
|
||||
obj: &ExternalSecret{
|
||||
Spec: ExternalSecretSpec{
|
||||
DataFrom: []ExternalSecretDataFromRemoteRef{
|
||||
{
|
||||
Find: &ExternalSecretFind{},
|
||||
SourceRef: &SourceRef{
|
||||
GeneratorRef: &GeneratorRef{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "generator with extract",
|
||||
obj: &ExternalSecret{
|
||||
Spec: ExternalSecretSpec{
|
||||
DataFrom: []ExternalSecretDataFromRemoteRef{
|
||||
{
|
||||
Extract: &ExternalSecretDataRemoteRef{},
|
||||
SourceRef: &SourceRef{
|
||||
GeneratorRef: &GeneratorRef{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "valid",
|
||||
obj: &ExternalSecret{
|
||||
Spec: ExternalSecretSpec{
|
||||
DataFrom: []ExternalSecretDataFromRemoteRef{},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if err := validateExternalSecret(tt.obj); (err != nil) != tt.wantErr {
|
||||
t.Errorf("validateExternalSecret() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -68,6 +68,9 @@ func GetProviderByName(name string) (Provider, bool) {
|
|||
|
||||
// GetProvider returns the provider from the generic store.
|
||||
func GetProvider(s GenericStore) (Provider, error) {
|
||||
if s == nil {
|
||||
return nil, nil
|
||||
}
|
||||
spec := s.GetSpec()
|
||||
if spec == nil {
|
||||
return nil, fmt.Errorf("no spec found in %#v", s)
|
||||
|
|
|
@ -36,6 +36,12 @@ type AWSAuthSecretRef struct {
|
|||
|
||||
// The SecretAccessKey is used for authentication
|
||||
SecretAccessKey esmeta.SecretKeySelector `json:"secretAccessKeySecretRef,omitempty"`
|
||||
|
||||
// The SessionToken used for authentication
|
||||
// This must be defined if AccessKeyID and SecretAccessKey are temporary credentials
|
||||
// see: https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_temp_use-resources.html
|
||||
// +Optional
|
||||
SessionToken *esmeta.SecretKeySelector `json:"sessionTokenSecretRef,omitempty"`
|
||||
}
|
||||
|
||||
// Authenticate against AWS using service account tokens.
|
||||
|
|
|
@ -55,6 +55,11 @@ func (in *AWSAuthSecretRef) DeepCopyInto(out *AWSAuthSecretRef) {
|
|||
*out = *in
|
||||
in.AccessKeyID.DeepCopyInto(&out.AccessKeyID)
|
||||
in.SecretAccessKey.DeepCopyInto(&out.SecretAccessKey)
|
||||
if in.SessionToken != nil {
|
||||
in, out := &in.SessionToken, &out.SessionToken
|
||||
*out = new(metav1.SecretKeySelector)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AWSAuthSecretRef.
|
||||
|
@ -660,6 +665,11 @@ func (in *ExternalSecret) DeepCopyObject() runtime.Object {
|
|||
func (in *ExternalSecretData) DeepCopyInto(out *ExternalSecretData) {
|
||||
*out = *in
|
||||
out.RemoteRef = in.RemoteRef
|
||||
if in.SourceRef != nil {
|
||||
in, out := &in.SourceRef, &out.SourceRef
|
||||
*out = new(SourceRef)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExternalSecretData.
|
||||
|
@ -692,6 +702,11 @@ func (in *ExternalSecretDataFromRemoteRef) DeepCopyInto(out *ExternalSecretDataF
|
|||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
if in.SourceRef != nil {
|
||||
in, out := &in.SourceRef, &out.SourceRef
|
||||
*out = new(SourceRef)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExternalSecretDataFromRemoteRef.
|
||||
|
@ -831,7 +846,9 @@ func (in *ExternalSecretSpec) DeepCopyInto(out *ExternalSecretSpec) {
|
|||
if in.Data != nil {
|
||||
in, out := &in.Data, &out.Data
|
||||
*out = make([]ExternalSecretData, len(*in))
|
||||
copy(*out, *in)
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
if in.DataFrom != nil {
|
||||
in, out := &in.DataFrom, &out.DataFrom
|
||||
|
@ -1117,6 +1134,21 @@ func (in *GCPWorkloadIdentity) DeepCopy() *GCPWorkloadIdentity {
|
|||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *GeneratorRef) DeepCopyInto(out *GeneratorRef) {
|
||||
*out = *in
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GeneratorRef.
|
||||
func (in *GeneratorRef) DeepCopy() *GeneratorRef {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(GeneratorRef)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *GenericStoreValidator) DeepCopyInto(out *GenericStoreValidator) {
|
||||
*out = *in
|
||||
|
@ -1753,6 +1785,31 @@ func (in *SenhaseguraProvider) DeepCopy() *SenhaseguraProvider {
|
|||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *SourceRef) DeepCopyInto(out *SourceRef) {
|
||||
*out = *in
|
||||
if in.SecretStoreRef != nil {
|
||||
in, out := &in.SecretStoreRef, &out.SecretStoreRef
|
||||
*out = new(SecretStoreRef)
|
||||
**out = **in
|
||||
}
|
||||
if in.GeneratorRef != nil {
|
||||
in, out := &in.GeneratorRef, &out.GeneratorRef
|
||||
*out = new(GeneratorRef)
|
||||
**out = **in
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SourceRef.
|
||||
func (in *SourceRef) DeepCopy() *SourceRef {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(SourceRef)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *TemplateFrom) DeepCopyInto(out *TemplateFrom) {
|
||||
*out = *in
|
||||
|
|
19
apis/generators/v1alpha1/doc.go
Normal file
19
apis/generators/v1alpha1/doc.go
Normal file
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
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 v1alpha1 contains resources for generators
|
||||
// +kubebuilder:object:generate=true
|
||||
// +groupName=generators.external-secrets.io
|
||||
// +versionName=v1alpha1
|
||||
package v1alpha1
|
35
apis/generators/v1alpha1/generator.go
Normal file
35
apis/generators/v1alpha1/generator.go
Normal file
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
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 v1alpha1
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
)
|
||||
|
||||
// +kubebuilder:object:root=false
|
||||
// +kubebuilder:object:generate:false
|
||||
// +k8s:deepcopy-gen:interfaces=nil
|
||||
// +k8s:deepcopy-gen=nil
|
||||
type Generator interface {
|
||||
Generate(
|
||||
ctx context.Context,
|
||||
obj *apiextensions.JSON,
|
||||
kube client.Client,
|
||||
namespace string,
|
||||
) (map[string][]byte, error)
|
||||
}
|
122
apis/generators/v1alpha1/generator_acr.go
Normal file
122
apis/generators/v1alpha1/generator_acr.go
Normal file
|
@ -0,0 +1,122 @@
|
|||
/*
|
||||
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 v1alpha1
|
||||
|
||||
import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
"github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
|
||||
smmeta "github.com/external-secrets/external-secrets/apis/meta/v1"
|
||||
)
|
||||
|
||||
// ACRAccessTokenSpec defines how to generate the access token
|
||||
// e.g. how to authenticate and which registry to use.
|
||||
// see: https://github.com/Azure/acr/blob/main/docs/AAD-OAuth.md#overview
|
||||
type ACRAccessTokenSpec struct {
|
||||
Auth ACRAuth `json:"auth"`
|
||||
// TenantID configures the Azure Tenant to send requests to. Required for ServicePrincipal auth type.
|
||||
TenantID string `json:"tenantId,omitempty"`
|
||||
|
||||
// the domain name of the ACR registry
|
||||
// e.g. foobarexample.azurecr.io
|
||||
ACRRegistry string `json:"registry"`
|
||||
|
||||
// Define the scope for the access token, e.g. pull/push access for a repository.
|
||||
// if not provided it will return a refresh token that has full scope.
|
||||
// Note: you need to pin it down to the repository level, there is no wildcard available.
|
||||
//
|
||||
// examples:
|
||||
// repository:my-repository:pull,push
|
||||
// repository:my-repository:pull
|
||||
//
|
||||
// see docs for details: https://docs.docker.com/registry/spec/auth/scope/
|
||||
// +optional
|
||||
Scope string `json:"scope,omitempty"`
|
||||
|
||||
// EnvironmentType specifies the Azure cloud environment endpoints to use for
|
||||
// connecting and authenticating with Azure. By default it points to the public cloud AAD endpoint.
|
||||
// The following endpoints are available, also see here: https://github.com/Azure/go-autorest/blob/main/autorest/azure/environments.go#L152
|
||||
// PublicCloud, USGovernmentCloud, ChinaCloud, GermanCloud
|
||||
// +kubebuilder:default=PublicCloud
|
||||
EnvironmentType v1beta1.AzureEnvironmentType `json:"environmentType,omitempty"`
|
||||
}
|
||||
|
||||
type ACRAuth struct {
|
||||
// ServicePrincipal uses Azure Service Principal credentials to authenticate with Azure.
|
||||
// +optional
|
||||
ServicePrincipal *AzureACRServicePrincipalAuth `json:"servicePrincipal,omitempty"`
|
||||
|
||||
// ManagedIdentity uses Azure Managed Identity to authenticate with Azure.
|
||||
// +optional
|
||||
ManagedIdentity *AzureACRManagedIdentityAuth `json:"managedIdentity,omitempty"`
|
||||
|
||||
// WorkloadIdentity uses Azure Workload Identity to authenticate with Azure.
|
||||
// +optional
|
||||
WorkloadIdentity *AzureACRWorkloadIdentityAuth `json:"workloadIdentity,omitempty"`
|
||||
}
|
||||
|
||||
type AzureACRServicePrincipalAuth struct {
|
||||
SecretRef AzureACRServicePrincipalAuthSecretRef `json:"secretRef"`
|
||||
}
|
||||
|
||||
type AzureACRManagedIdentityAuth struct {
|
||||
// If multiple Managed Identity is assigned to the pod, you can select the one to be used
|
||||
IdentityID string `json:"identityId,omitempty"`
|
||||
}
|
||||
|
||||
type AzureACRWorkloadIdentityAuth struct {
|
||||
// ServiceAccountRef specified the service account
|
||||
// that should be used when authenticating with WorkloadIdentity.
|
||||
// +optional
|
||||
ServiceAccountRef *smmeta.ServiceAccountSelector `json:"serviceAccountRef,omitempty"`
|
||||
}
|
||||
|
||||
// Configuration used to authenticate with Azure using static
|
||||
// credentials stored in a Kind=Secret.
|
||||
type AzureACRServicePrincipalAuthSecretRef struct {
|
||||
// The Azure clientId of the service principle used for authentication.
|
||||
ClientID smmeta.SecretKeySelector `json:"clientId,omitempty"`
|
||||
// The Azure ClientSecret of the service principle used for authentication.
|
||||
ClientSecret smmeta.SecretKeySelector `json:"clientSecret,omitempty"`
|
||||
}
|
||||
|
||||
// ACRAccessToken returns a Azure Container Registry token
|
||||
// that can be used for pushing/pulling images.
|
||||
// Note: by default it will return an ACR Refresh Token with full access
|
||||
// (depending on the identity).
|
||||
// This can be scoped down to the repository level using .spec.scope.
|
||||
// In case scope is defined it will return an ACR Access Token.
|
||||
//
|
||||
// See docs: https://github.com/Azure/acr/blob/main/docs/AAD-OAuth.md
|
||||
//
|
||||
// +kubebuilder:object:root=true
|
||||
// +kubebuilder:storageversion
|
||||
// +kubebuilder:subresource:status
|
||||
// +kubebuilder:resource:scope=Namespaced,categories={acraccesstoken},shortName=acraccesstoken
|
||||
type ACRAccessToken struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ObjectMeta `json:"metadata,omitempty"`
|
||||
|
||||
Spec ACRAccessTokenSpec `json:"spec,omitempty"`
|
||||
}
|
||||
|
||||
// +kubebuilder:object:root=true
|
||||
|
||||
// ACRAccessTokenList contains a list of ExternalSecret resources.
|
||||
type ACRAccessTokenList struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ListMeta `json:"metadata,omitempty"`
|
||||
Items []ACRAccessToken `json:"items"`
|
||||
}
|
92
apis/generators/v1alpha1/generator_ecr.go
Normal file
92
apis/generators/v1alpha1/generator_ecr.go
Normal file
|
@ -0,0 +1,92 @@
|
|||
/*
|
||||
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 v1alpha1
|
||||
|
||||
import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
esmeta "github.com/external-secrets/external-secrets/apis/meta/v1"
|
||||
)
|
||||
|
||||
type ECRAuthorizationTokenSpec struct {
|
||||
// Region specifies the region to operate in.
|
||||
Region string `json:"region"`
|
||||
|
||||
// Auth defines how to authenticate with AWS
|
||||
// +optional
|
||||
Auth AWSAuth `json:"auth"`
|
||||
|
||||
// You can assume a role before making calls to the
|
||||
// desired AWS service.
|
||||
// +optional
|
||||
Role string `json:"role"`
|
||||
}
|
||||
|
||||
// AWSAuth tells the controller how to do authentication with aws.
|
||||
// Only one of secretRef or jwt can be specified.
|
||||
// if none is specified the controller will load credentials using the aws sdk defaults.
|
||||
type AWSAuth struct {
|
||||
// +optional
|
||||
SecretRef *AWSAuthSecretRef `json:"secretRef,omitempty"`
|
||||
// +optional
|
||||
JWTAuth *AWSJWTAuth `json:"jwt,omitempty"`
|
||||
}
|
||||
|
||||
// AWSAuthSecretRef holds secret references for AWS credentials
|
||||
// both AccessKeyID and SecretAccessKey must be defined in order to properly authenticate.
|
||||
type AWSAuthSecretRef struct {
|
||||
// The AccessKeyID is used for authentication
|
||||
AccessKeyID esmeta.SecretKeySelector `json:"accessKeyIDSecretRef,omitempty"`
|
||||
|
||||
// The SecretAccessKey is used for authentication
|
||||
SecretAccessKey esmeta.SecretKeySelector `json:"secretAccessKeySecretRef,omitempty"`
|
||||
|
||||
// The SessionToken used for authentication
|
||||
// This must be defined if AccessKeyID and SecretAccessKey are temporary credentials
|
||||
// see: https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_temp_use-resources.html
|
||||
// +Optional
|
||||
SessionToken *esmeta.SecretKeySelector `json:"sessionTokenSecretRef,omitempty"`
|
||||
}
|
||||
|
||||
// Authenticate against AWS using service account tokens.
|
||||
type AWSJWTAuth struct {
|
||||
ServiceAccountRef *esmeta.ServiceAccountSelector `json:"serviceAccountRef,omitempty"`
|
||||
}
|
||||
|
||||
// ECRAuthorizationTokenSpec uses the GetAuthorizationToken API to retrieve an
|
||||
// authorization token.
|
||||
// The authorization token is valid for 12 hours.
|
||||
// The authorizationToken returned is a base64 encoded string that can be decoded
|
||||
// and used in a docker login command to authenticate to a registry.
|
||||
// For more information, see Registry authentication (https://docs.aws.amazon.com/AmazonECR/latest/userguide/Registries.html#registry_auth) in the Amazon Elastic Container Registry User Guide.
|
||||
// +kubebuilder:object:root=true
|
||||
// +kubebuilder:storageversion
|
||||
// +kubebuilder:subresource:status
|
||||
// +kubebuilder:resource:scope=Namespaced,categories={ecrauthorizationtoken},shortName=ecrauthorizationtoken
|
||||
type ECRAuthorizationToken struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ObjectMeta `json:"metadata,omitempty"`
|
||||
|
||||
Spec ECRAuthorizationTokenSpec `json:"spec,omitempty"`
|
||||
}
|
||||
|
||||
// +kubebuilder:object:root=true
|
||||
|
||||
// ECRAuthorizationTokenList contains a list of ExternalSecret resources.
|
||||
type ECRAuthorizationTokenList struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ListMeta `json:"metadata,omitempty"`
|
||||
Items []ECRAuthorizationToken `json:"items"`
|
||||
}
|
48
apis/generators/v1alpha1/generator_fake.go
Normal file
48
apis/generators/v1alpha1/generator_fake.go
Normal file
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
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 v1alpha1
|
||||
|
||||
import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// FakeSpec contains the static data.
|
||||
type FakeSpec struct {
|
||||
// Data defines the static data returned
|
||||
// by this generator.
|
||||
Data map[string]string `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
// Fake generator is used for testing. It lets you define
|
||||
// a static set of credentials that is always returned.
|
||||
// +kubebuilder:object:root=true
|
||||
// +kubebuilder:storageversion
|
||||
// +kubebuilder:subresource:status
|
||||
// +kubebuilder:resource:scope=Namespaced,categories={fake},shortName=fake
|
||||
type Fake struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ObjectMeta `json:"metadata,omitempty"`
|
||||
|
||||
Spec FakeSpec `json:"spec,omitempty"`
|
||||
}
|
||||
|
||||
// +kubebuilder:object:root=true
|
||||
|
||||
// FakeList contains a list of ExternalSecret resources.
|
||||
type FakeList struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ListMeta `json:"metadata,omitempty"`
|
||||
Items []Fake `json:"items"`
|
||||
}
|
70
apis/generators/v1alpha1/generator_gcr.go
Normal file
70
apis/generators/v1alpha1/generator_gcr.go
Normal file
|
@ -0,0 +1,70 @@
|
|||
/*
|
||||
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 v1alpha1
|
||||
|
||||
import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
esmeta "github.com/external-secrets/external-secrets/apis/meta/v1"
|
||||
)
|
||||
|
||||
type GCRAccessTokenSpec struct {
|
||||
// Auth defines the means for authenticating with GCP
|
||||
Auth GCPSMAuth `json:"auth"`
|
||||
// ProjectID defines which project to use to authenticate with
|
||||
ProjectID string `json:"projectID"`
|
||||
}
|
||||
|
||||
type GCPSMAuth struct {
|
||||
// +optional
|
||||
SecretRef *GCPSMAuthSecretRef `json:"secretRef,omitempty"`
|
||||
// +optional
|
||||
WorkloadIdentity *GCPWorkloadIdentity `json:"workloadIdentity,omitempty"`
|
||||
}
|
||||
|
||||
type GCPSMAuthSecretRef struct {
|
||||
// The SecretAccessKey is used for authentication
|
||||
// +optional
|
||||
SecretAccessKey esmeta.SecretKeySelector `json:"secretAccessKeySecretRef,omitempty"`
|
||||
}
|
||||
|
||||
type GCPWorkloadIdentity struct {
|
||||
ServiceAccountRef esmeta.ServiceAccountSelector `json:"serviceAccountRef"`
|
||||
ClusterLocation string `json:"clusterLocation"`
|
||||
ClusterName string `json:"clusterName"`
|
||||
ClusterProjectID string `json:"clusterProjectID,omitempty"`
|
||||
}
|
||||
|
||||
// GCRAccessToken generates an GCP access token
|
||||
// that can be used to authenticate with GCR.
|
||||
// +kubebuilder:object:root=true
|
||||
// +kubebuilder:storageversion
|
||||
// +kubebuilder:subresource:status
|
||||
// +kubebuilder:resource:scope=Namespaced,categories={gcraccesstoken},shortName=gcraccesstoken
|
||||
type GCRAccessToken struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ObjectMeta `json:"metadata,omitempty"`
|
||||
|
||||
Spec GCRAccessTokenSpec `json:"spec,omitempty"`
|
||||
}
|
||||
|
||||
// +kubebuilder:object:root=true
|
||||
|
||||
// GCRAccessTokenList contains a list of ExternalSecret resources.
|
||||
type GCRAccessTokenList struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ListMeta `json:"metadata,omitempty"`
|
||||
Items []GCRAccessToken `json:"items"`
|
||||
}
|
70
apis/generators/v1alpha1/generator_password.go
Normal file
70
apis/generators/v1alpha1/generator_password.go
Normal file
|
@ -0,0 +1,70 @@
|
|||
/*
|
||||
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 v1alpha1
|
||||
|
||||
import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// PasswordSpec controls the behavior of the password generator.
|
||||
type PasswordSpec struct {
|
||||
// Length of the password to be generated.
|
||||
// Defaults to 24
|
||||
// +kubebuilder:default=24
|
||||
Length int `json:"length"`
|
||||
|
||||
// Digits specifies the number of digits in the generated
|
||||
// password. If omitted it defaults to 25% of the length of the password
|
||||
Digits *int `json:"digits,omitempty"`
|
||||
|
||||
// Symbols specifies the number of symbol characters in the generated
|
||||
// password. If omitted it defaults to 25% of the length of the password
|
||||
Symbols *int `json:"symbols,omitempty"`
|
||||
|
||||
// SymbolCharacters specifies the special characters that should be used
|
||||
// in the generated password.
|
||||
SymbolCharacters *string `json:"symbolCharacters,omitempty"`
|
||||
|
||||
// Set NoUpper to disable uppercase characters
|
||||
// +kubebuilder:default=false
|
||||
NoUpper bool `json:"noUpper"`
|
||||
|
||||
// set AllowRepeat to true to allow repeating characters.
|
||||
// +kubebuilder:default=false
|
||||
AllowRepeat bool `json:"allowRepeat"`
|
||||
}
|
||||
|
||||
// Password generates a random password based on the
|
||||
// configuration parameters in spec.
|
||||
// You can specify the length, characterset and other attributes.
|
||||
// +kubebuilder:object:root=true
|
||||
// +kubebuilder:storageversion
|
||||
// +kubebuilder:subresource:status
|
||||
// +kubebuilder:resource:scope=Namespaced,categories={password},shortName=password
|
||||
type Password struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ObjectMeta `json:"metadata,omitempty"`
|
||||
|
||||
Spec PasswordSpec `json:"spec,omitempty"`
|
||||
}
|
||||
|
||||
// +kubebuilder:object:root=true
|
||||
|
||||
// PasswordList contains a list of ExternalSecret resources.
|
||||
type PasswordList struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ListMeta `json:"metadata,omitempty"`
|
||||
Items []Password `json:"items"`
|
||||
}
|
81
apis/generators/v1alpha1/generator_schema.go
Normal file
81
apis/generators/v1alpha1/generator_schema.go
Normal file
|
@ -0,0 +1,81 @@
|
|||
/*
|
||||
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 v1alpha1
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/util/json"
|
||||
)
|
||||
|
||||
var builder map[string]Generator
|
||||
var buildlock sync.RWMutex
|
||||
|
||||
func init() {
|
||||
builder = make(map[string]Generator)
|
||||
}
|
||||
|
||||
// Register a generator type. Register panics if a
|
||||
// backend with the same generator is already registered.
|
||||
func Register(kind string, g Generator) {
|
||||
buildlock.Lock()
|
||||
defer buildlock.Unlock()
|
||||
_, exists := builder[kind]
|
||||
if exists {
|
||||
panic(fmt.Sprintf("kind %q already registered", kind))
|
||||
}
|
||||
|
||||
builder[kind] = g
|
||||
}
|
||||
|
||||
// ForceRegister adds to the schema, overwriting a generator if
|
||||
// already registered. Should only be used for testing.
|
||||
func ForceRegister(kind string, g Generator) {
|
||||
buildlock.Lock()
|
||||
builder[kind] = g
|
||||
buildlock.Unlock()
|
||||
}
|
||||
|
||||
// GetGeneratorByName returns the provider implementation by name.
|
||||
func GetGeneratorByName(kind string) (Generator, bool) {
|
||||
buildlock.RLock()
|
||||
f, ok := builder[kind]
|
||||
buildlock.RUnlock()
|
||||
return f, ok
|
||||
}
|
||||
|
||||
// GetGenerator returns a implementation from a generator
|
||||
// defined as json.
|
||||
func GetGenerator(obj *apiextensions.JSON) (Generator, error) {
|
||||
type unknownGenerator struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ObjectMeta `json:"metadata,omitempty"`
|
||||
}
|
||||
var res unknownGenerator
|
||||
err := json.Unmarshal(obj.Raw, &res)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
buildlock.RLock()
|
||||
defer buildlock.RUnlock()
|
||||
gen, ok := builder[res.Kind]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("failed to find registered generator for: %s", string(obj.Raw))
|
||||
}
|
||||
return gen, nil
|
||||
}
|
85
apis/generators/v1alpha1/register.go
Normal file
85
apis/generators/v1alpha1/register.go
Normal file
|
@ -0,0 +1,85 @@
|
|||
/*
|
||||
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 v1alpha1
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"sigs.k8s.io/controller-runtime/pkg/scheme"
|
||||
)
|
||||
|
||||
// Package type metadata.
|
||||
const (
|
||||
Group = "generators.external-secrets.io"
|
||||
Version = "v1alpha1"
|
||||
)
|
||||
|
||||
var (
|
||||
// SchemeGroupVersion is group version used to register these objects.
|
||||
SchemeGroupVersion = schema.GroupVersion{Group: Group, Version: Version}
|
||||
|
||||
// SchemeBuilder is used to add go types to the GroupVersionKind scheme.
|
||||
SchemeBuilder = &scheme.Builder{GroupVersion: SchemeGroupVersion}
|
||||
AddToScheme = SchemeBuilder.AddToScheme
|
||||
)
|
||||
|
||||
// ECRAuthorizationToken type metadata.
|
||||
var (
|
||||
ECRAuthorizationTokenKind = reflect.TypeOf(ECRAuthorizationToken{}).Name()
|
||||
ECRAuthorizationTokenGroupKind = schema.GroupKind{Group: Group, Kind: ECRAuthorizationTokenKind}.String()
|
||||
ECRAuthorizationTokenKindAPIVersion = ECRAuthorizationTokenKind + "." + SchemeGroupVersion.String()
|
||||
ECRAuthorizationTokenGroupVersionKind = SchemeGroupVersion.WithKind(ECRAuthorizationTokenKind)
|
||||
)
|
||||
|
||||
// GCRAccessToken type metadata.
|
||||
var (
|
||||
GCRAccessTokenKind = reflect.TypeOf(GCRAccessToken{}).Name()
|
||||
GCRAccessTokenGroupKind = schema.GroupKind{Group: Group, Kind: GCRAccessTokenKind}.String()
|
||||
GCRAccessTokenKindAPIVersion = GCRAccessTokenKind + "." + SchemeGroupVersion.String()
|
||||
GCRAccessTokenGroupVersionKind = SchemeGroupVersion.WithKind(GCRAccessTokenKind)
|
||||
)
|
||||
|
||||
// ACRAccessToken type metadata.
|
||||
var (
|
||||
ACRAccessTokenKind = reflect.TypeOf(ACRAccessToken{}).Name()
|
||||
ACRAccessTokenGroupKind = schema.GroupKind{Group: Group, Kind: ACRAccessTokenKind}.String()
|
||||
ACRAccessTokenKindAPIVersion = ACRAccessTokenKind + "." + SchemeGroupVersion.String()
|
||||
ACRAccessTokenGroupVersionKind = SchemeGroupVersion.WithKind(ACRAccessTokenKind)
|
||||
)
|
||||
|
||||
// Password type metadata.
|
||||
var (
|
||||
PasswordKind = reflect.TypeOf(Password{}).Name()
|
||||
PasswordGroupKind = schema.GroupKind{Group: Group, Kind: PasswordKind}.String()
|
||||
PasswordKindAPIVersion = PasswordKind + "." + SchemeGroupVersion.String()
|
||||
PasswordGroupVersionKind = SchemeGroupVersion.WithKind(PasswordKind)
|
||||
)
|
||||
|
||||
// Fake type metadata.
|
||||
var (
|
||||
FakeKind = reflect.TypeOf(Fake{}).Name()
|
||||
FakeGroupKind = schema.GroupKind{Group: Group, Kind: FakeKind}.String()
|
||||
FakeKindAPIVersion = FakeKind + "." + SchemeGroupVersion.String()
|
||||
FakeGroupVersionKind = SchemeGroupVersion.WithKind(FakeKind)
|
||||
)
|
||||
|
||||
func init() {
|
||||
SchemeBuilder.Register(&ECRAuthorizationToken{}, &ECRAuthorizationToken{})
|
||||
SchemeBuilder.Register(&GCRAccessToken{}, &GCRAccessTokenList{})
|
||||
SchemeBuilder.Register(&ACRAccessToken{}, &ACRAccessTokenList{})
|
||||
SchemeBuilder.Register(&Fake{}, &FakeList{})
|
||||
SchemeBuilder.Register(&Password{}, &PasswordList{})
|
||||
}
|
637
apis/generators/v1alpha1/zz_generated.deepcopy.go
Normal file
637
apis/generators/v1alpha1/zz_generated.deepcopy.go
Normal file
|
@ -0,0 +1,637 @@
|
|||
//go:build !ignore_autogenerated
|
||||
// +build !ignore_autogenerated
|
||||
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
// Code generated by controller-gen. DO NOT EDIT.
|
||||
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
"github.com/external-secrets/external-secrets/apis/meta/v1"
|
||||
runtime "k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ACRAccessToken) DeepCopyInto(out *ACRAccessToken) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
|
||||
in.Spec.DeepCopyInto(&out.Spec)
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ACRAccessToken.
|
||||
func (in *ACRAccessToken) DeepCopy() *ACRAccessToken {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ACRAccessToken)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *ACRAccessToken) DeepCopyObject() runtime.Object {
|
||||
if c := in.DeepCopy(); c != nil {
|
||||
return c
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ACRAccessTokenList) DeepCopyInto(out *ACRAccessTokenList) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
in.ListMeta.DeepCopyInto(&out.ListMeta)
|
||||
if in.Items != nil {
|
||||
in, out := &in.Items, &out.Items
|
||||
*out = make([]ACRAccessToken, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ACRAccessTokenList.
|
||||
func (in *ACRAccessTokenList) DeepCopy() *ACRAccessTokenList {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ACRAccessTokenList)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *ACRAccessTokenList) DeepCopyObject() runtime.Object {
|
||||
if c := in.DeepCopy(); c != nil {
|
||||
return c
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ACRAccessTokenSpec) DeepCopyInto(out *ACRAccessTokenSpec) {
|
||||
*out = *in
|
||||
in.Auth.DeepCopyInto(&out.Auth)
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ACRAccessTokenSpec.
|
||||
func (in *ACRAccessTokenSpec) DeepCopy() *ACRAccessTokenSpec {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ACRAccessTokenSpec)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ACRAuth) DeepCopyInto(out *ACRAuth) {
|
||||
*out = *in
|
||||
if in.ServicePrincipal != nil {
|
||||
in, out := &in.ServicePrincipal, &out.ServicePrincipal
|
||||
*out = new(AzureACRServicePrincipalAuth)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.ManagedIdentity != nil {
|
||||
in, out := &in.ManagedIdentity, &out.ManagedIdentity
|
||||
*out = new(AzureACRManagedIdentityAuth)
|
||||
**out = **in
|
||||
}
|
||||
if in.WorkloadIdentity != nil {
|
||||
in, out := &in.WorkloadIdentity, &out.WorkloadIdentity
|
||||
*out = new(AzureACRWorkloadIdentityAuth)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ACRAuth.
|
||||
func (in *ACRAuth) DeepCopy() *ACRAuth {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ACRAuth)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *AWSAuth) DeepCopyInto(out *AWSAuth) {
|
||||
*out = *in
|
||||
if in.SecretRef != nil {
|
||||
in, out := &in.SecretRef, &out.SecretRef
|
||||
*out = new(AWSAuthSecretRef)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.JWTAuth != nil {
|
||||
in, out := &in.JWTAuth, &out.JWTAuth
|
||||
*out = new(AWSJWTAuth)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AWSAuth.
|
||||
func (in *AWSAuth) DeepCopy() *AWSAuth {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(AWSAuth)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *AWSAuthSecretRef) DeepCopyInto(out *AWSAuthSecretRef) {
|
||||
*out = *in
|
||||
in.AccessKeyID.DeepCopyInto(&out.AccessKeyID)
|
||||
in.SecretAccessKey.DeepCopyInto(&out.SecretAccessKey)
|
||||
if in.SessionToken != nil {
|
||||
in, out := &in.SessionToken, &out.SessionToken
|
||||
*out = new(v1.SecretKeySelector)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AWSAuthSecretRef.
|
||||
func (in *AWSAuthSecretRef) DeepCopy() *AWSAuthSecretRef {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(AWSAuthSecretRef)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *AWSJWTAuth) DeepCopyInto(out *AWSJWTAuth) {
|
||||
*out = *in
|
||||
if in.ServiceAccountRef != nil {
|
||||
in, out := &in.ServiceAccountRef, &out.ServiceAccountRef
|
||||
*out = new(v1.ServiceAccountSelector)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AWSJWTAuth.
|
||||
func (in *AWSJWTAuth) DeepCopy() *AWSJWTAuth {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(AWSJWTAuth)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *AzureACRManagedIdentityAuth) DeepCopyInto(out *AzureACRManagedIdentityAuth) {
|
||||
*out = *in
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AzureACRManagedIdentityAuth.
|
||||
func (in *AzureACRManagedIdentityAuth) DeepCopy() *AzureACRManagedIdentityAuth {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(AzureACRManagedIdentityAuth)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *AzureACRServicePrincipalAuth) DeepCopyInto(out *AzureACRServicePrincipalAuth) {
|
||||
*out = *in
|
||||
in.SecretRef.DeepCopyInto(&out.SecretRef)
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AzureACRServicePrincipalAuth.
|
||||
func (in *AzureACRServicePrincipalAuth) DeepCopy() *AzureACRServicePrincipalAuth {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(AzureACRServicePrincipalAuth)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *AzureACRServicePrincipalAuthSecretRef) DeepCopyInto(out *AzureACRServicePrincipalAuthSecretRef) {
|
||||
*out = *in
|
||||
in.ClientID.DeepCopyInto(&out.ClientID)
|
||||
in.ClientSecret.DeepCopyInto(&out.ClientSecret)
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AzureACRServicePrincipalAuthSecretRef.
|
||||
func (in *AzureACRServicePrincipalAuthSecretRef) DeepCopy() *AzureACRServicePrincipalAuthSecretRef {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(AzureACRServicePrincipalAuthSecretRef)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *AzureACRWorkloadIdentityAuth) DeepCopyInto(out *AzureACRWorkloadIdentityAuth) {
|
||||
*out = *in
|
||||
if in.ServiceAccountRef != nil {
|
||||
in, out := &in.ServiceAccountRef, &out.ServiceAccountRef
|
||||
*out = new(v1.ServiceAccountSelector)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AzureACRWorkloadIdentityAuth.
|
||||
func (in *AzureACRWorkloadIdentityAuth) DeepCopy() *AzureACRWorkloadIdentityAuth {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(AzureACRWorkloadIdentityAuth)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ECRAuthorizationToken) DeepCopyInto(out *ECRAuthorizationToken) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
|
||||
in.Spec.DeepCopyInto(&out.Spec)
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ECRAuthorizationToken.
|
||||
func (in *ECRAuthorizationToken) DeepCopy() *ECRAuthorizationToken {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ECRAuthorizationToken)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *ECRAuthorizationToken) DeepCopyObject() runtime.Object {
|
||||
if c := in.DeepCopy(); c != nil {
|
||||
return c
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ECRAuthorizationTokenList) DeepCopyInto(out *ECRAuthorizationTokenList) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
in.ListMeta.DeepCopyInto(&out.ListMeta)
|
||||
if in.Items != nil {
|
||||
in, out := &in.Items, &out.Items
|
||||
*out = make([]ECRAuthorizationToken, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ECRAuthorizationTokenList.
|
||||
func (in *ECRAuthorizationTokenList) DeepCopy() *ECRAuthorizationTokenList {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ECRAuthorizationTokenList)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *ECRAuthorizationTokenList) DeepCopyObject() runtime.Object {
|
||||
if c := in.DeepCopy(); c != nil {
|
||||
return c
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ECRAuthorizationTokenSpec) DeepCopyInto(out *ECRAuthorizationTokenSpec) {
|
||||
*out = *in
|
||||
in.Auth.DeepCopyInto(&out.Auth)
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ECRAuthorizationTokenSpec.
|
||||
func (in *ECRAuthorizationTokenSpec) DeepCopy() *ECRAuthorizationTokenSpec {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ECRAuthorizationTokenSpec)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *Fake) DeepCopyInto(out *Fake) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
|
||||
in.Spec.DeepCopyInto(&out.Spec)
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Fake.
|
||||
func (in *Fake) DeepCopy() *Fake {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(Fake)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *Fake) DeepCopyObject() runtime.Object {
|
||||
if c := in.DeepCopy(); c != nil {
|
||||
return c
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *FakeList) DeepCopyInto(out *FakeList) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
in.ListMeta.DeepCopyInto(&out.ListMeta)
|
||||
if in.Items != nil {
|
||||
in, out := &in.Items, &out.Items
|
||||
*out = make([]Fake, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FakeList.
|
||||
func (in *FakeList) DeepCopy() *FakeList {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(FakeList)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *FakeList) DeepCopyObject() runtime.Object {
|
||||
if c := in.DeepCopy(); c != nil {
|
||||
return c
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *FakeSpec) DeepCopyInto(out *FakeSpec) {
|
||||
*out = *in
|
||||
if in.Data != nil {
|
||||
in, out := &in.Data, &out.Data
|
||||
*out = make(map[string]string, len(*in))
|
||||
for key, val := range *in {
|
||||
(*out)[key] = val
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FakeSpec.
|
||||
func (in *FakeSpec) DeepCopy() *FakeSpec {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(FakeSpec)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *GCPSMAuth) DeepCopyInto(out *GCPSMAuth) {
|
||||
*out = *in
|
||||
if in.SecretRef != nil {
|
||||
in, out := &in.SecretRef, &out.SecretRef
|
||||
*out = new(GCPSMAuthSecretRef)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.WorkloadIdentity != nil {
|
||||
in, out := &in.WorkloadIdentity, &out.WorkloadIdentity
|
||||
*out = new(GCPWorkloadIdentity)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GCPSMAuth.
|
||||
func (in *GCPSMAuth) DeepCopy() *GCPSMAuth {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(GCPSMAuth)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *GCPSMAuthSecretRef) DeepCopyInto(out *GCPSMAuthSecretRef) {
|
||||
*out = *in
|
||||
in.SecretAccessKey.DeepCopyInto(&out.SecretAccessKey)
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GCPSMAuthSecretRef.
|
||||
func (in *GCPSMAuthSecretRef) DeepCopy() *GCPSMAuthSecretRef {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(GCPSMAuthSecretRef)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *GCPWorkloadIdentity) DeepCopyInto(out *GCPWorkloadIdentity) {
|
||||
*out = *in
|
||||
in.ServiceAccountRef.DeepCopyInto(&out.ServiceAccountRef)
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GCPWorkloadIdentity.
|
||||
func (in *GCPWorkloadIdentity) DeepCopy() *GCPWorkloadIdentity {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(GCPWorkloadIdentity)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *GCRAccessToken) DeepCopyInto(out *GCRAccessToken) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
|
||||
in.Spec.DeepCopyInto(&out.Spec)
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GCRAccessToken.
|
||||
func (in *GCRAccessToken) DeepCopy() *GCRAccessToken {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(GCRAccessToken)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *GCRAccessToken) DeepCopyObject() runtime.Object {
|
||||
if c := in.DeepCopy(); c != nil {
|
||||
return c
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *GCRAccessTokenList) DeepCopyInto(out *GCRAccessTokenList) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
in.ListMeta.DeepCopyInto(&out.ListMeta)
|
||||
if in.Items != nil {
|
||||
in, out := &in.Items, &out.Items
|
||||
*out = make([]GCRAccessToken, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GCRAccessTokenList.
|
||||
func (in *GCRAccessTokenList) DeepCopy() *GCRAccessTokenList {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(GCRAccessTokenList)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *GCRAccessTokenList) DeepCopyObject() runtime.Object {
|
||||
if c := in.DeepCopy(); c != nil {
|
||||
return c
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *GCRAccessTokenSpec) DeepCopyInto(out *GCRAccessTokenSpec) {
|
||||
*out = *in
|
||||
in.Auth.DeepCopyInto(&out.Auth)
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GCRAccessTokenSpec.
|
||||
func (in *GCRAccessTokenSpec) DeepCopy() *GCRAccessTokenSpec {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(GCRAccessTokenSpec)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *Password) DeepCopyInto(out *Password) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
|
||||
in.Spec.DeepCopyInto(&out.Spec)
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Password.
|
||||
func (in *Password) DeepCopy() *Password {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(Password)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *Password) DeepCopyObject() runtime.Object {
|
||||
if c := in.DeepCopy(); c != nil {
|
||||
return c
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *PasswordList) DeepCopyInto(out *PasswordList) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
in.ListMeta.DeepCopyInto(&out.ListMeta)
|
||||
if in.Items != nil {
|
||||
in, out := &in.Items, &out.Items
|
||||
*out = make([]Password, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PasswordList.
|
||||
func (in *PasswordList) DeepCopy() *PasswordList {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(PasswordList)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *PasswordList) DeepCopyObject() runtime.Object {
|
||||
if c := in.DeepCopy(); c != nil {
|
||||
return c
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *PasswordSpec) DeepCopyInto(out *PasswordSpec) {
|
||||
*out = *in
|
||||
if in.Digits != nil {
|
||||
in, out := &in.Digits, &out.Digits
|
||||
*out = new(int)
|
||||
**out = **in
|
||||
}
|
||||
if in.Symbols != nil {
|
||||
in, out := &in.Symbols, &out.Symbols
|
||||
*out = new(int)
|
||||
**out = **in
|
||||
}
|
||||
if in.SymbolCharacters != nil {
|
||||
in, out := &in.SymbolCharacters, &out.SymbolCharacters
|
||||
*out = new(string)
|
||||
**out = **in
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PasswordSpec.
|
||||
func (in *PasswordSpec) DeepCopy() *PasswordSpec {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(PasswordSpec)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
13
cmd/root.go
13
cmd/root.go
|
@ -34,6 +34,7 @@ import (
|
|||
|
||||
esv1alpha1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1alpha1"
|
||||
esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
|
||||
genv1alpha1 "github.com/external-secrets/external-secrets/apis/generators/v1alpha1"
|
||||
"github.com/external-secrets/external-secrets/pkg/controllers/clusterexternalsecret"
|
||||
"github.com/external-secrets/external-secrets/pkg/controllers/externalsecret"
|
||||
"github.com/external-secrets/external-secrets/pkg/controllers/secretstore"
|
||||
|
@ -82,6 +83,7 @@ func init() {
|
|||
_ = clientgoscheme.AddToScheme(scheme)
|
||||
_ = esv1beta1.AddToScheme(scheme)
|
||||
_ = esv1alpha1.AddToScheme(scheme)
|
||||
_ = genv1alpha1.AddToScheme(scheme)
|
||||
_ = apiextensionsv1.AddToScheme(scheme)
|
||||
}
|
||||
|
||||
|
@ -153,6 +155,7 @@ var rootCmd = &cobra.Command{
|
|||
Client: mgr.GetClient(),
|
||||
Log: ctrl.Log.WithName("controllers").WithName("ExternalSecret"),
|
||||
Scheme: mgr.GetScheme(),
|
||||
RestConfig: mgr.GetConfig(),
|
||||
ControllerClass: controllerClass,
|
||||
RequeueInterval: time.Hour,
|
||||
ClusterSecretStoreEnabled: enableClusterStoreReconciler,
|
||||
|
@ -198,7 +201,7 @@ func Execute() {
|
|||
|
||||
func init() {
|
||||
rootCmd.Flags().StringVar(&metricsAddr, "metrics-addr", ":8080", "The address the metric endpoint binds to.")
|
||||
rootCmd.Flags().StringVar(&controllerClass, "controller-class", "default", "the controller is instantiated with a specific controller name and filters ES based on this property")
|
||||
rootCmd.Flags().StringVar(&controllerClass, "controller-class", "default", "The controller is instantiated with a specific controller name and filters ES based on this property")
|
||||
rootCmd.Flags().BoolVar(&enableLeaderElection, "enable-leader-election", false,
|
||||
"Enable leader election for controller manager. "+
|
||||
"Enabling this will ensure there is only one active controller manager.")
|
||||
|
@ -207,10 +210,10 @@ func init() {
|
|||
rootCmd.Flags().IntVar(&clientBurst, "client-burst", 0, "Maximum Burst allowed to be passed to rest.Client")
|
||||
rootCmd.Flags().StringVar(&loglevel, "loglevel", "info", "loglevel to use, one of: debug, info, warn, error, dpanic, panic, fatal")
|
||||
rootCmd.Flags().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")
|
||||
rootCmd.Flags().BoolVar(&enableClusterStoreReconciler, "enable-cluster-store-reconciler", true, "Enable cluster store reconciler.")
|
||||
rootCmd.Flags().BoolVar(&enableClusterExternalSecretReconciler, "enable-cluster-external-secret-reconciler", true, "Enable cluster external secret reconciler.")
|
||||
rootCmd.Flags().BoolVar(&enableSecretsCache, "enable-secrets-caching", false, "Enable secrets caching for external-secrets pod.")
|
||||
rootCmd.Flags().BoolVar(&enableConfigMapsCache, "enable-configmaps-caching", false, "Enable secrets caching for external-secrets pod.")
|
||||
rootCmd.Flags().BoolVar(&enableClusterStoreReconciler, "enable-cluster-store-reconciler", true, "Enables the cluster store reconciler.")
|
||||
rootCmd.Flags().BoolVar(&enableClusterExternalSecretReconciler, "enable-cluster-external-secret-reconciler", true, "Enables the cluster external secret reconciler.")
|
||||
rootCmd.Flags().BoolVar(&enableSecretsCache, "enable-secrets-caching", false, "Enables the secrets caching for external-secrets pod.")
|
||||
rootCmd.Flags().BoolVar(&enableConfigMapsCache, "enable-configmaps-caching", false, "Enables the ConfigMap caching for external-secrets pod.")
|
||||
rootCmd.Flags().DurationVar(&storeRequeueInterval, "store-requeue-interval", time.Minute*5, "Default Time duration between reconciling (Cluster)SecretStores")
|
||||
rootCmd.Flags().BoolVar(&enableFloodGate, "enable-flood-gate", true, "Enable flood gate. External secret will be reconciled only if the ClusterStore or Store have an healthy or unknown state.")
|
||||
rootCmd.Flags().BoolVar(&enableAWSSession, "experimental-enable-aws-session-cache", false, "Enable experimental AWS session cache. External secret will reuse the AWS session without creating a new one on each request.")
|
||||
|
|
|
@ -209,7 +209,7 @@ func init() {
|
|||
rootCmd.AddCommand(webhookCmd)
|
||||
webhookCmd.Flags().StringVar(&metricsAddr, "metrics-addr", ":8080", "The address the metric endpoint binds to.")
|
||||
webhookCmd.Flags().StringVar(&healthzAddr, "healthz-addr", ":8081", "The address the health endpoint binds to.")
|
||||
webhookCmd.Flags().IntVar(&port, "port", 10250, "The address the health endpoint binds to.")
|
||||
webhookCmd.Flags().IntVar(&port, "port", 10250, "Port number that the webhook server will serve.")
|
||||
webhookCmd.Flags().StringVar(&dnsName, "dns-name", "localhost", "DNS name to validate certificates with")
|
||||
webhookCmd.Flags().StringVar(&certDir, "cert-dir", "/tmp/k8s-webhook-server/serving-certs", "path to check for certs")
|
||||
webhookCmd.Flags().StringVar(&loglevel, "loglevel", "info", "loglevel to use, one of: debug, info, warn, error, dpanic, panic, fatal")
|
||||
|
@ -221,5 +221,5 @@ func init() {
|
|||
" The order of this list does not give preference to the ciphers, the ordering is done automatically."+
|
||||
" Full lists of available ciphers can be found at https://pkg.go.dev/crypto/tls#pkg-constants."+
|
||||
" E.g. 'TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256'")
|
||||
webhookCmd.Flags().StringVar(&tlsMinVersion, "tls-min-version", "1.2", "minimum version of TLS supported. Defaults to 1.2")
|
||||
webhookCmd.Flags().StringVar(&tlsMinVersion, "tls-min-version", "1.2", "minimum version of TLS supported.")
|
||||
}
|
||||
|
|
|
@ -68,8 +68,8 @@ spec:
|
|||
data.
|
||||
properties:
|
||||
remoteRef:
|
||||
description: ExternalSecretDataRemoteRef defines Provider
|
||||
data location.
|
||||
description: RemoteRef points to the remote secret and defines
|
||||
which secret (version/property/..) to fetch.
|
||||
properties:
|
||||
conversionStrategy:
|
||||
default: Default
|
||||
|
@ -99,7 +99,49 @@ spec:
|
|||
- key
|
||||
type: object
|
||||
secretKey:
|
||||
description: SecretKey defines the key in which the controller
|
||||
stores the value. This is the key in the Kind=Secret
|
||||
type: string
|
||||
sourceRef:
|
||||
description: SourceRef allows you to override the source
|
||||
from which the value will pulled from.
|
||||
maxProperties: 1
|
||||
properties:
|
||||
generatorRef:
|
||||
description: GeneratorRef points to a generator custom
|
||||
resource in
|
||||
properties:
|
||||
apiVersion:
|
||||
default: generators.external-secrets.io/v1alpha1
|
||||
description: Specify the apiVersion of the generator
|
||||
resource
|
||||
type: string
|
||||
kind:
|
||||
description: Specify the Kind of the resource, e.g.
|
||||
Password, ACRAccessToken etc.
|
||||
type: string
|
||||
name:
|
||||
description: Specify the name of the generator resource
|
||||
type: string
|
||||
required:
|
||||
- kind
|
||||
- name
|
||||
type: object
|
||||
storeRef:
|
||||
description: SecretStoreRef defines which SecretStore
|
||||
to fetch the ExternalSecret data.
|
||||
properties:
|
||||
kind:
|
||||
description: Kind of the SecretStore resource (SecretStore
|
||||
or ClusterSecretStore) Defaults to `SecretStore`
|
||||
type: string
|
||||
name:
|
||||
description: Name of the SecretStore resource
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
type: object
|
||||
type: object
|
||||
required:
|
||||
- remoteRef
|
||||
- secretKey
|
||||
|
@ -112,8 +154,9 @@ spec:
|
|||
items:
|
||||
properties:
|
||||
extract:
|
||||
description: Used to extract multiple key/value pairs from
|
||||
one secret
|
||||
description: 'Used to extract multiple key/value pairs from
|
||||
one secret Note: Extract does not support sourceRef.Generator
|
||||
or sourceRef.GeneratorRef.'
|
||||
properties:
|
||||
conversionStrategy:
|
||||
default: Default
|
||||
|
@ -143,8 +186,9 @@ spec:
|
|||
- key
|
||||
type: object
|
||||
find:
|
||||
description: Used to find secrets based on tags or regular
|
||||
expressions
|
||||
description: 'Used to find secrets based on tags or regular
|
||||
expressions Note: Find does not support sourceRef.Generator
|
||||
or sourceRef.GeneratorRef.'
|
||||
properties:
|
||||
conversionStrategy:
|
||||
default: Default
|
||||
|
@ -196,6 +240,49 @@ spec:
|
|||
type: object
|
||||
type: object
|
||||
type: array
|
||||
sourceRef:
|
||||
description: SourceRef points to a store or generator which
|
||||
contains secret values ready to use. Use this in combination
|
||||
with Extract or Find pull values out of a specific SecretStore.
|
||||
When sourceRef points to a generator Extract or Find is
|
||||
not supported. The generator returns a static map of values
|
||||
maxProperties: 1
|
||||
properties:
|
||||
generatorRef:
|
||||
description: GeneratorRef points to a generator custom
|
||||
resource in
|
||||
properties:
|
||||
apiVersion:
|
||||
default: generators.external-secrets.io/v1alpha1
|
||||
description: Specify the apiVersion of the generator
|
||||
resource
|
||||
type: string
|
||||
kind:
|
||||
description: Specify the Kind of the resource, e.g.
|
||||
Password, ACRAccessToken etc.
|
||||
type: string
|
||||
name:
|
||||
description: Specify the name of the generator resource
|
||||
type: string
|
||||
required:
|
||||
- kind
|
||||
- name
|
||||
type: object
|
||||
storeRef:
|
||||
description: SecretStoreRef defines which SecretStore
|
||||
to fetch the ExternalSecret data.
|
||||
properties:
|
||||
kind:
|
||||
description: Kind of the SecretStore resource (SecretStore
|
||||
or ClusterSecretStore) Defaults to `SecretStore`
|
||||
type: string
|
||||
name:
|
||||
description: Name of the SecretStore resource
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
type: object
|
||||
type: object
|
||||
type: object
|
||||
type: array
|
||||
refreshInterval:
|
||||
|
@ -323,8 +410,6 @@ spec:
|
|||
type: string
|
||||
type: object
|
||||
type: object
|
||||
required:
|
||||
- secretStoreRef
|
||||
type: object
|
||||
namespaceSelector:
|
||||
description: The labels to select by to find the Namespaces to create
|
||||
|
|
|
@ -1914,6 +1914,28 @@ spec:
|
|||
the referent.
|
||||
type: string
|
||||
type: object
|
||||
sessionTokenSecretRef:
|
||||
description: 'The SessionToken used for authentication
|
||||
This must be defined if AccessKeyID and SecretAccessKey
|
||||
are temporary credentials see: https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_temp_use-resources.html'
|
||||
properties:
|
||||
key:
|
||||
description: The key of the entry in the Secret
|
||||
resource's `data` field to be used. Some instances
|
||||
of this field may be defaulted, in others it
|
||||
may be required.
|
||||
type: string
|
||||
name:
|
||||
description: The name of the Secret resource being
|
||||
referred to.
|
||||
type: string
|
||||
namespace:
|
||||
description: Namespace of the resource being referred
|
||||
to. Ignored if referent is not cluster-scoped.
|
||||
cluster-scoped defaults to the namespace of
|
||||
the referent.
|
||||
type: string
|
||||
type: object
|
||||
type: object
|
||||
type: object
|
||||
region:
|
||||
|
|
|
@ -303,8 +303,8 @@ spec:
|
|||
Kubernetes Secret key (spec.data.<key>) and the Provider data.
|
||||
properties:
|
||||
remoteRef:
|
||||
description: ExternalSecretDataRemoteRef defines Provider data
|
||||
location.
|
||||
description: RemoteRef points to the remote secret and defines
|
||||
which secret (version/property/..) to fetch.
|
||||
properties:
|
||||
conversionStrategy:
|
||||
default: Default
|
||||
|
@ -334,7 +334,49 @@ spec:
|
|||
- key
|
||||
type: object
|
||||
secretKey:
|
||||
description: SecretKey defines the key in which the controller
|
||||
stores the value. This is the key in the Kind=Secret
|
||||
type: string
|
||||
sourceRef:
|
||||
description: SourceRef allows you to override the source from
|
||||
which the value will pulled from.
|
||||
maxProperties: 1
|
||||
properties:
|
||||
generatorRef:
|
||||
description: GeneratorRef points to a generator custom resource
|
||||
in
|
||||
properties:
|
||||
apiVersion:
|
||||
default: generators.external-secrets.io/v1alpha1
|
||||
description: Specify the apiVersion of the generator
|
||||
resource
|
||||
type: string
|
||||
kind:
|
||||
description: Specify the Kind of the resource, e.g.
|
||||
Password, ACRAccessToken etc.
|
||||
type: string
|
||||
name:
|
||||
description: Specify the name of the generator resource
|
||||
type: string
|
||||
required:
|
||||
- kind
|
||||
- name
|
||||
type: object
|
||||
storeRef:
|
||||
description: SecretStoreRef defines which SecretStore to
|
||||
fetch the ExternalSecret data.
|
||||
properties:
|
||||
kind:
|
||||
description: Kind of the SecretStore resource (SecretStore
|
||||
or ClusterSecretStore) Defaults to `SecretStore`
|
||||
type: string
|
||||
name:
|
||||
description: Name of the SecretStore resource
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
type: object
|
||||
type: object
|
||||
required:
|
||||
- remoteRef
|
||||
- secretKey
|
||||
|
@ -347,8 +389,9 @@ spec:
|
|||
items:
|
||||
properties:
|
||||
extract:
|
||||
description: Used to extract multiple key/value pairs from one
|
||||
secret
|
||||
description: 'Used to extract multiple key/value pairs from
|
||||
one secret Note: Extract does not support sourceRef.Generator
|
||||
or sourceRef.GeneratorRef.'
|
||||
properties:
|
||||
conversionStrategy:
|
||||
default: Default
|
||||
|
@ -378,7 +421,9 @@ spec:
|
|||
- key
|
||||
type: object
|
||||
find:
|
||||
description: Used to find secrets based on tags or regular expressions
|
||||
description: 'Used to find secrets based on tags or regular
|
||||
expressions Note: Find does not support sourceRef.Generator
|
||||
or sourceRef.GeneratorRef.'
|
||||
properties:
|
||||
conversionStrategy:
|
||||
default: Default
|
||||
|
@ -429,6 +474,49 @@ spec:
|
|||
type: object
|
||||
type: object
|
||||
type: array
|
||||
sourceRef:
|
||||
description: SourceRef points to a store or generator which
|
||||
contains secret values ready to use. Use this in combination
|
||||
with Extract or Find pull values out of a specific SecretStore.
|
||||
When sourceRef points to a generator Extract or Find is not
|
||||
supported. The generator returns a static map of values
|
||||
maxProperties: 1
|
||||
properties:
|
||||
generatorRef:
|
||||
description: GeneratorRef points to a generator custom resource
|
||||
in
|
||||
properties:
|
||||
apiVersion:
|
||||
default: generators.external-secrets.io/v1alpha1
|
||||
description: Specify the apiVersion of the generator
|
||||
resource
|
||||
type: string
|
||||
kind:
|
||||
description: Specify the Kind of the resource, e.g.
|
||||
Password, ACRAccessToken etc.
|
||||
type: string
|
||||
name:
|
||||
description: Specify the name of the generator resource
|
||||
type: string
|
||||
required:
|
||||
- kind
|
||||
- name
|
||||
type: object
|
||||
storeRef:
|
||||
description: SecretStoreRef defines which SecretStore to
|
||||
fetch the ExternalSecret data.
|
||||
properties:
|
||||
kind:
|
||||
description: Kind of the SecretStore resource (SecretStore
|
||||
or ClusterSecretStore) Defaults to `SecretStore`
|
||||
type: string
|
||||
name:
|
||||
description: Name of the SecretStore resource
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
type: object
|
||||
type: object
|
||||
type: object
|
||||
type: array
|
||||
refreshInterval:
|
||||
|
@ -555,8 +643,6 @@ spec:
|
|||
type: string
|
||||
type: object
|
||||
type: object
|
||||
required:
|
||||
- secretStoreRef
|
||||
type: object
|
||||
status:
|
||||
properties:
|
||||
|
|
|
@ -1914,6 +1914,28 @@ spec:
|
|||
the referent.
|
||||
type: string
|
||||
type: object
|
||||
sessionTokenSecretRef:
|
||||
description: 'The SessionToken used for authentication
|
||||
This must be defined if AccessKeyID and SecretAccessKey
|
||||
are temporary credentials see: https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_temp_use-resources.html'
|
||||
properties:
|
||||
key:
|
||||
description: The key of the entry in the Secret
|
||||
resource's `data` field to be used. Some instances
|
||||
of this field may be defaulted, in others it
|
||||
may be required.
|
||||
type: string
|
||||
name:
|
||||
description: The name of the Secret resource being
|
||||
referred to.
|
||||
type: string
|
||||
namespace:
|
||||
description: Namespace of the resource being referred
|
||||
to. Ignored if referent is not cluster-scoped.
|
||||
cluster-scoped defaults to the namespace of
|
||||
the referent.
|
||||
type: string
|
||||
type: object
|
||||
type: object
|
||||
type: object
|
||||
region:
|
||||
|
|
|
@ -0,0 +1,173 @@
|
|||
apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.10.0
|
||||
creationTimestamp: null
|
||||
name: acraccesstokens.generators.external-secrets.io
|
||||
spec:
|
||||
group: generators.external-secrets.io
|
||||
names:
|
||||
categories:
|
||||
- acraccesstoken
|
||||
kind: ACRAccessToken
|
||||
listKind: ACRAccessTokenList
|
||||
plural: acraccesstokens
|
||||
shortNames:
|
||||
- acraccesstoken
|
||||
singular: acraccesstoken
|
||||
scope: Namespaced
|
||||
versions:
|
||||
- name: v1alpha1
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
description: "ACRAccessToken returns a Azure Container Registry token that
|
||||
can be used for pushing/pulling images. Note: by default it will return
|
||||
an ACR Refresh Token with full access (depending on the identity). This
|
||||
can be scoped down to the repository level using .spec.scope. In case scope
|
||||
is defined it will return an ACR Access Token. \n See docs: https://github.com/Azure/acr/blob/main/docs/AAD-OAuth.md"
|
||||
properties:
|
||||
apiVersion:
|
||||
description: 'APIVersion defines the versioned schema of this representation
|
||||
of an object. Servers should convert recognized schemas to the latest
|
||||
internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
|
||||
type: string
|
||||
kind:
|
||||
description: 'Kind is a string value representing the REST resource this
|
||||
object represents. Servers may infer this from the endpoint the client
|
||||
submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
|
||||
type: string
|
||||
metadata:
|
||||
type: object
|
||||
spec:
|
||||
description: 'ACRAccessTokenSpec defines how to generate the access token
|
||||
e.g. how to authenticate and which registry to use. see: https://github.com/Azure/acr/blob/main/docs/AAD-OAuth.md#overview'
|
||||
properties:
|
||||
auth:
|
||||
properties:
|
||||
managedIdentity:
|
||||
description: ManagedIdentity uses Azure Managed Identity to authenticate
|
||||
with Azure.
|
||||
properties:
|
||||
identityId:
|
||||
description: If multiple Managed Identity is assigned to the
|
||||
pod, you can select the one to be used
|
||||
type: string
|
||||
type: object
|
||||
servicePrincipal:
|
||||
description: ServicePrincipal uses Azure Service Principal credentials
|
||||
to authenticate with Azure.
|
||||
properties:
|
||||
secretRef:
|
||||
description: Configuration used to authenticate with Azure
|
||||
using static credentials stored in a Kind=Secret.
|
||||
properties:
|
||||
clientId:
|
||||
description: The Azure clientId of the service principle
|
||||
used for authentication.
|
||||
properties:
|
||||
key:
|
||||
description: The key of the entry in the Secret resource's
|
||||
`data` field to be used. Some instances of this
|
||||
field may be defaulted, in others it may be required.
|
||||
type: string
|
||||
name:
|
||||
description: The name of the Secret resource being
|
||||
referred to.
|
||||
type: string
|
||||
namespace:
|
||||
description: Namespace of the resource being referred
|
||||
to. Ignored if referent is not cluster-scoped. cluster-scoped
|
||||
defaults to the namespace of the referent.
|
||||
type: string
|
||||
type: object
|
||||
clientSecret:
|
||||
description: The Azure ClientSecret of the service principle
|
||||
used for authentication.
|
||||
properties:
|
||||
key:
|
||||
description: The key of the entry in the Secret resource's
|
||||
`data` field to be used. Some instances of this
|
||||
field may be defaulted, in others it may be required.
|
||||
type: string
|
||||
name:
|
||||
description: The name of the Secret resource being
|
||||
referred to.
|
||||
type: string
|
||||
namespace:
|
||||
description: Namespace of the resource being referred
|
||||
to. Ignored if referent is not cluster-scoped. cluster-scoped
|
||||
defaults to the namespace of the referent.
|
||||
type: string
|
||||
type: object
|
||||
type: object
|
||||
required:
|
||||
- secretRef
|
||||
type: object
|
||||
workloadIdentity:
|
||||
description: WorkloadIdentity uses Azure Workload Identity to
|
||||
authenticate with Azure.
|
||||
properties:
|
||||
serviceAccountRef:
|
||||
description: ServiceAccountRef specified the service account
|
||||
that should be used when authenticating with WorkloadIdentity.
|
||||
properties:
|
||||
audiences:
|
||||
description: Audience specifies the `aud` claim for the
|
||||
service account token If the service account uses a
|
||||
well-known annotation for e.g. IRSA or GCP Workload
|
||||
Identity then this audiences will be appended to the
|
||||
list
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
name:
|
||||
description: The name of the ServiceAccount resource being
|
||||
referred to.
|
||||
type: string
|
||||
namespace:
|
||||
description: Namespace of the resource being referred
|
||||
to. Ignored if referent is not cluster-scoped. cluster-scoped
|
||||
defaults to the namespace of the referent.
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
type: object
|
||||
type: object
|
||||
type: object
|
||||
environmentType:
|
||||
default: PublicCloud
|
||||
description: 'EnvironmentType specifies the Azure cloud environment
|
||||
endpoints to use for connecting and authenticating with Azure. By
|
||||
default it points to the public cloud AAD endpoint. The following
|
||||
endpoints are available, also see here: https://github.com/Azure/go-autorest/blob/main/autorest/azure/environments.go#L152
|
||||
PublicCloud, USGovernmentCloud, ChinaCloud, GermanCloud'
|
||||
enum:
|
||||
- PublicCloud
|
||||
- USGovernmentCloud
|
||||
- ChinaCloud
|
||||
- GermanCloud
|
||||
type: string
|
||||
registry:
|
||||
description: the domain name of the ACR registry e.g. foobarexample.azurecr.io
|
||||
type: string
|
||||
scope:
|
||||
description: "Define the scope for the access token, e.g. pull/push
|
||||
access for a repository. if not provided it will return a refresh
|
||||
token that has full scope. Note: you need to pin it down to the
|
||||
repository level, there is no wildcard available. \n examples: repository:my-repository:pull,push
|
||||
repository:my-repository:pull \n see docs for details: https://docs.docker.com/registry/spec/auth/scope/"
|
||||
type: string
|
||||
tenantId:
|
||||
description: TenantID configures the Azure Tenant to send requests
|
||||
to. Required for ServicePrincipal auth type.
|
||||
type: string
|
||||
required:
|
||||
- auth
|
||||
- registry
|
||||
type: object
|
||||
type: object
|
||||
served: true
|
||||
storage: true
|
||||
subresources:
|
||||
status: {}
|
|
@ -0,0 +1,153 @@
|
|||
apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.10.0
|
||||
creationTimestamp: null
|
||||
name: ecrauthorizationtokens.generators.external-secrets.io
|
||||
spec:
|
||||
group: generators.external-secrets.io
|
||||
names:
|
||||
categories:
|
||||
- ecrauthorizationtoken
|
||||
kind: ECRAuthorizationToken
|
||||
listKind: ECRAuthorizationTokenList
|
||||
plural: ecrauthorizationtokens
|
||||
shortNames:
|
||||
- ecrauthorizationtoken
|
||||
singular: ecrauthorizationtoken
|
||||
scope: Namespaced
|
||||
versions:
|
||||
- name: v1alpha1
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
description: ECRAuthorizationTokenSpec uses the GetAuthorizationToken API
|
||||
to retrieve an authorization token. The authorization token is valid for
|
||||
12 hours. The authorizationToken returned is a base64 encoded string that
|
||||
can be decoded and used in a docker login command to authenticate to a registry.
|
||||
For more information, see Registry authentication (https://docs.aws.amazon.com/AmazonECR/latest/userguide/Registries.html#registry_auth)
|
||||
in the Amazon Elastic Container Registry User Guide.
|
||||
properties:
|
||||
apiVersion:
|
||||
description: 'APIVersion defines the versioned schema of this representation
|
||||
of an object. Servers should convert recognized schemas to the latest
|
||||
internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
|
||||
type: string
|
||||
kind:
|
||||
description: 'Kind is a string value representing the REST resource this
|
||||
object represents. Servers may infer this from the endpoint the client
|
||||
submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
|
||||
type: string
|
||||
metadata:
|
||||
type: object
|
||||
spec:
|
||||
properties:
|
||||
auth:
|
||||
description: Auth defines how to authenticate with AWS
|
||||
properties:
|
||||
jwt:
|
||||
description: Authenticate against AWS using service account tokens.
|
||||
properties:
|
||||
serviceAccountRef:
|
||||
description: A reference to a ServiceAccount resource.
|
||||
properties:
|
||||
audiences:
|
||||
description: Audience specifies the `aud` claim for the
|
||||
service account token If the service account uses a
|
||||
well-known annotation for e.g. IRSA or GCP Workload
|
||||
Identity then this audiences will be appended to the
|
||||
list
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
name:
|
||||
description: The name of the ServiceAccount resource being
|
||||
referred to.
|
||||
type: string
|
||||
namespace:
|
||||
description: Namespace of the resource being referred
|
||||
to. Ignored if referent is not cluster-scoped. cluster-scoped
|
||||
defaults to the namespace of the referent.
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
type: object
|
||||
type: object
|
||||
secretRef:
|
||||
description: AWSAuthSecretRef holds secret references for AWS
|
||||
credentials both AccessKeyID and SecretAccessKey must be defined
|
||||
in order to properly authenticate.
|
||||
properties:
|
||||
accessKeyIDSecretRef:
|
||||
description: The AccessKeyID is used for authentication
|
||||
properties:
|
||||
key:
|
||||
description: The key of the entry in the Secret resource's
|
||||
`data` field to be used. Some instances of this field
|
||||
may be defaulted, in others it may be required.
|
||||
type: string
|
||||
name:
|
||||
description: The name of the Secret resource being referred
|
||||
to.
|
||||
type: string
|
||||
namespace:
|
||||
description: Namespace of the resource being referred
|
||||
to. Ignored if referent is not cluster-scoped. cluster-scoped
|
||||
defaults to the namespace of the referent.
|
||||
type: string
|
||||
type: object
|
||||
secretAccessKeySecretRef:
|
||||
description: The SecretAccessKey is used for authentication
|
||||
properties:
|
||||
key:
|
||||
description: The key of the entry in the Secret resource's
|
||||
`data` field to be used. Some instances of this field
|
||||
may be defaulted, in others it may be required.
|
||||
type: string
|
||||
name:
|
||||
description: The name of the Secret resource being referred
|
||||
to.
|
||||
type: string
|
||||
namespace:
|
||||
description: Namespace of the resource being referred
|
||||
to. Ignored if referent is not cluster-scoped. cluster-scoped
|
||||
defaults to the namespace of the referent.
|
||||
type: string
|
||||
type: object
|
||||
sessionTokenSecretRef:
|
||||
description: 'The SessionToken used for authentication This
|
||||
must be defined if AccessKeyID and SecretAccessKey are temporary
|
||||
credentials see: https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_temp_use-resources.html'
|
||||
properties:
|
||||
key:
|
||||
description: The key of the entry in the Secret resource's
|
||||
`data` field to be used. Some instances of this field
|
||||
may be defaulted, in others it may be required.
|
||||
type: string
|
||||
name:
|
||||
description: The name of the Secret resource being referred
|
||||
to.
|
||||
type: string
|
||||
namespace:
|
||||
description: Namespace of the resource being referred
|
||||
to. Ignored if referent is not cluster-scoped. cluster-scoped
|
||||
defaults to the namespace of the referent.
|
||||
type: string
|
||||
type: object
|
||||
type: object
|
||||
type: object
|
||||
region:
|
||||
description: Region specifies the region to operate in.
|
||||
type: string
|
||||
role:
|
||||
description: You can assume a role before making calls to the desired
|
||||
AWS service.
|
||||
type: string
|
||||
required:
|
||||
- region
|
||||
type: object
|
||||
type: object
|
||||
served: true
|
||||
storage: true
|
||||
subresources:
|
||||
status: {}
|
52
config/crds/bases/generators.external-secrets.io_fakes.yaml
Normal file
52
config/crds/bases/generators.external-secrets.io_fakes.yaml
Normal file
|
@ -0,0 +1,52 @@
|
|||
apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.10.0
|
||||
creationTimestamp: null
|
||||
name: fakes.generators.external-secrets.io
|
||||
spec:
|
||||
group: generators.external-secrets.io
|
||||
names:
|
||||
categories:
|
||||
- fake
|
||||
kind: Fake
|
||||
listKind: FakeList
|
||||
plural: fakes
|
||||
shortNames:
|
||||
- fake
|
||||
singular: fake
|
||||
scope: Namespaced
|
||||
versions:
|
||||
- name: v1alpha1
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
description: Fake generator is used for testing. It lets you define a static
|
||||
set of credentials that is always returned.
|
||||
properties:
|
||||
apiVersion:
|
||||
description: 'APIVersion defines the versioned schema of this representation
|
||||
of an object. Servers should convert recognized schemas to the latest
|
||||
internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
|
||||
type: string
|
||||
kind:
|
||||
description: 'Kind is a string value representing the REST resource this
|
||||
object represents. Servers may infer this from the endpoint the client
|
||||
submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
|
||||
type: string
|
||||
metadata:
|
||||
type: object
|
||||
spec:
|
||||
description: FakeSpec contains the static data.
|
||||
properties:
|
||||
data:
|
||||
additionalProperties:
|
||||
type: string
|
||||
description: Data defines the static data returned by this generator.
|
||||
type: object
|
||||
type: object
|
||||
type: object
|
||||
served: true
|
||||
storage: true
|
||||
subresources:
|
||||
status: {}
|
|
@ -0,0 +1,115 @@
|
|||
apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.10.0
|
||||
creationTimestamp: null
|
||||
name: gcraccesstokens.generators.external-secrets.io
|
||||
spec:
|
||||
group: generators.external-secrets.io
|
||||
names:
|
||||
categories:
|
||||
- gcraccesstoken
|
||||
kind: GCRAccessToken
|
||||
listKind: GCRAccessTokenList
|
||||
plural: gcraccesstokens
|
||||
shortNames:
|
||||
- gcraccesstoken
|
||||
singular: gcraccesstoken
|
||||
scope: Namespaced
|
||||
versions:
|
||||
- name: v1alpha1
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
description: GCRAccessToken generates an GCP access token that can be used
|
||||
to authenticate with GCR.
|
||||
properties:
|
||||
apiVersion:
|
||||
description: 'APIVersion defines the versioned schema of this representation
|
||||
of an object. Servers should convert recognized schemas to the latest
|
||||
internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
|
||||
type: string
|
||||
kind:
|
||||
description: 'Kind is a string value representing the REST resource this
|
||||
object represents. Servers may infer this from the endpoint the client
|
||||
submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
|
||||
type: string
|
||||
metadata:
|
||||
type: object
|
||||
spec:
|
||||
properties:
|
||||
auth:
|
||||
description: Auth defines the means for authenticating with GCP
|
||||
properties:
|
||||
secretRef:
|
||||
properties:
|
||||
secretAccessKeySecretRef:
|
||||
description: The SecretAccessKey is used for authentication
|
||||
properties:
|
||||
key:
|
||||
description: The key of the entry in the Secret resource's
|
||||
`data` field to be used. Some instances of this field
|
||||
may be defaulted, in others it may be required.
|
||||
type: string
|
||||
name:
|
||||
description: The name of the Secret resource being referred
|
||||
to.
|
||||
type: string
|
||||
namespace:
|
||||
description: Namespace of the resource being referred
|
||||
to. Ignored if referent is not cluster-scoped. cluster-scoped
|
||||
defaults to the namespace of the referent.
|
||||
type: string
|
||||
type: object
|
||||
type: object
|
||||
workloadIdentity:
|
||||
properties:
|
||||
clusterLocation:
|
||||
type: string
|
||||
clusterName:
|
||||
type: string
|
||||
clusterProjectID:
|
||||
type: string
|
||||
serviceAccountRef:
|
||||
description: A reference to a ServiceAccount resource.
|
||||
properties:
|
||||
audiences:
|
||||
description: Audience specifies the `aud` claim for the
|
||||
service account token If the service account uses a
|
||||
well-known annotation for e.g. IRSA or GCP Workload
|
||||
Identity then this audiences will be appended to the
|
||||
list
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
name:
|
||||
description: The name of the ServiceAccount resource being
|
||||
referred to.
|
||||
type: string
|
||||
namespace:
|
||||
description: Namespace of the resource being referred
|
||||
to. Ignored if referent is not cluster-scoped. cluster-scoped
|
||||
defaults to the namespace of the referent.
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
type: object
|
||||
required:
|
||||
- clusterLocation
|
||||
- clusterName
|
||||
- serviceAccountRef
|
||||
type: object
|
||||
type: object
|
||||
projectID:
|
||||
description: ProjectID defines which project to use to authenticate
|
||||
with
|
||||
type: string
|
||||
required:
|
||||
- auth
|
||||
- projectID
|
||||
type: object
|
||||
type: object
|
||||
served: true
|
||||
storage: true
|
||||
subresources:
|
||||
status: {}
|
|
@ -0,0 +1,76 @@
|
|||
apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.10.0
|
||||
creationTimestamp: null
|
||||
name: passwords.generators.external-secrets.io
|
||||
spec:
|
||||
group: generators.external-secrets.io
|
||||
names:
|
||||
categories:
|
||||
- password
|
||||
kind: Password
|
||||
listKind: PasswordList
|
||||
plural: passwords
|
||||
shortNames:
|
||||
- password
|
||||
singular: password
|
||||
scope: Namespaced
|
||||
versions:
|
||||
- name: v1alpha1
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
description: Password generates a random password based on the configuration
|
||||
parameters in spec. You can specify the length, characterset and other attributes.
|
||||
properties:
|
||||
apiVersion:
|
||||
description: 'APIVersion defines the versioned schema of this representation
|
||||
of an object. Servers should convert recognized schemas to the latest
|
||||
internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
|
||||
type: string
|
||||
kind:
|
||||
description: 'Kind is a string value representing the REST resource this
|
||||
object represents. Servers may infer this from the endpoint the client
|
||||
submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
|
||||
type: string
|
||||
metadata:
|
||||
type: object
|
||||
spec:
|
||||
description: PasswordSpec controls the behavior of the password generator.
|
||||
properties:
|
||||
allowRepeat:
|
||||
default: false
|
||||
description: set AllowRepeat to true to allow repeating characters.
|
||||
type: boolean
|
||||
digits:
|
||||
description: Digits specifies the number of digits in the generated
|
||||
password. If omitted it defaults to 25% of the length of the password
|
||||
type: integer
|
||||
length:
|
||||
default: 24
|
||||
description: Length of the password to be generated. Defaults to 24
|
||||
type: integer
|
||||
noUpper:
|
||||
default: false
|
||||
description: Set NoUpper to disable uppercase characters
|
||||
type: boolean
|
||||
symbolCharacters:
|
||||
description: SymbolCharacters specifies the special characters that
|
||||
should be used in the generated password.
|
||||
type: string
|
||||
symbols:
|
||||
description: Symbols specifies the number of symbol characters in
|
||||
the generated password. If omitted it defaults to 25% of the length
|
||||
of the password
|
||||
type: integer
|
||||
required:
|
||||
- allowRepeat
|
||||
- length
|
||||
- noUpper
|
||||
type: object
|
||||
type: object
|
||||
served: true
|
||||
storage: true
|
||||
subresources:
|
||||
status: {}
|
|
@ -6,3 +6,8 @@ resources:
|
|||
- external-secrets.io_clustersecretstores.yaml
|
||||
- external-secrets.io_externalsecrets.yaml
|
||||
- external-secrets.io_secretstores.yaml
|
||||
- generators.external-secrets.io_acraccesstokens.yaml
|
||||
- generators.external-secrets.io_ecrauthorizationtokens.yaml
|
||||
- generators.external-secrets.io_fakes.yaml
|
||||
- generators.external-secrets.io_gcraccesstokens.yaml
|
||||
- generators.external-secrets.io_passwords.yaml
|
||||
|
|
|
@ -42,6 +42,18 @@ rules:
|
|||
verbs:
|
||||
- "update"
|
||||
- "patch"
|
||||
- apiGroups:
|
||||
- "generators.external-secrets.io"
|
||||
resources:
|
||||
- "fakes"
|
||||
- "passwords"
|
||||
- "acraccesstokens"
|
||||
- "gcraccesstokens"
|
||||
- "ecrauthorizationtokens"
|
||||
verbs:
|
||||
- "get"
|
||||
- "list"
|
||||
- "watch"
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
|
|
|
@ -59,7 +59,7 @@ spec:
|
|||
description: ExternalSecretData defines the connection between the Kubernetes Secret key (spec.data.<key>) and the Provider data.
|
||||
properties:
|
||||
remoteRef:
|
||||
description: ExternalSecretDataRemoteRef defines Provider data location.
|
||||
description: RemoteRef points to the remote secret and defines which secret (version/property/..) to fetch.
|
||||
properties:
|
||||
conversionStrategy:
|
||||
default: Default
|
||||
|
@ -85,7 +85,42 @@ spec:
|
|||
- key
|
||||
type: object
|
||||
secretKey:
|
||||
description: SecretKey defines the key in which the controller stores the value. This is the key in the Kind=Secret
|
||||
type: string
|
||||
sourceRef:
|
||||
description: SourceRef allows you to override the source from which the value will pulled from.
|
||||
maxProperties: 1
|
||||
properties:
|
||||
generatorRef:
|
||||
description: GeneratorRef points to a generator custom resource in
|
||||
properties:
|
||||
apiVersion:
|
||||
default: generators.external-secrets.io/v1alpha1
|
||||
description: Specify the apiVersion of the generator resource
|
||||
type: string
|
||||
kind:
|
||||
description: Specify the Kind of the resource, e.g. Password, ACRAccessToken etc.
|
||||
type: string
|
||||
name:
|
||||
description: Specify the name of the generator resource
|
||||
type: string
|
||||
required:
|
||||
- kind
|
||||
- name
|
||||
type: object
|
||||
storeRef:
|
||||
description: SecretStoreRef defines which SecretStore to fetch the ExternalSecret data.
|
||||
properties:
|
||||
kind:
|
||||
description: Kind of the SecretStore resource (SecretStore or ClusterSecretStore) Defaults to `SecretStore`
|
||||
type: string
|
||||
name:
|
||||
description: Name of the SecretStore resource
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
type: object
|
||||
type: object
|
||||
required:
|
||||
- remoteRef
|
||||
- secretKey
|
||||
|
@ -96,7 +131,7 @@ spec:
|
|||
items:
|
||||
properties:
|
||||
extract:
|
||||
description: Used to extract multiple key/value pairs from one secret
|
||||
description: 'Used to extract multiple key/value pairs from one secret Note: Extract does not support sourceRef.Generator or sourceRef.GeneratorRef.'
|
||||
properties:
|
||||
conversionStrategy:
|
||||
default: Default
|
||||
|
@ -122,7 +157,7 @@ spec:
|
|||
- key
|
||||
type: object
|
||||
find:
|
||||
description: Used to find secrets based on tags or regular expressions
|
||||
description: 'Used to find secrets based on tags or regular expressions Note: Find does not support sourceRef.Generator or sourceRef.GeneratorRef.'
|
||||
properties:
|
||||
conversionStrategy:
|
||||
default: Default
|
||||
|
@ -167,6 +202,40 @@ spec:
|
|||
type: object
|
||||
type: object
|
||||
type: array
|
||||
sourceRef:
|
||||
description: SourceRef points to a store or generator which contains secret values ready to use. Use this in combination with Extract or Find pull values out of a specific SecretStore. When sourceRef points to a generator Extract or Find is not supported. The generator returns a static map of values
|
||||
maxProperties: 1
|
||||
properties:
|
||||
generatorRef:
|
||||
description: GeneratorRef points to a generator custom resource in
|
||||
properties:
|
||||
apiVersion:
|
||||
default: generators.external-secrets.io/v1alpha1
|
||||
description: Specify the apiVersion of the generator resource
|
||||
type: string
|
||||
kind:
|
||||
description: Specify the Kind of the resource, e.g. Password, ACRAccessToken etc.
|
||||
type: string
|
||||
name:
|
||||
description: Specify the name of the generator resource
|
||||
type: string
|
||||
required:
|
||||
- kind
|
||||
- name
|
||||
type: object
|
||||
storeRef:
|
||||
description: SecretStoreRef defines which SecretStore to fetch the ExternalSecret data.
|
||||
properties:
|
||||
kind:
|
||||
description: Kind of the SecretStore resource (SecretStore or ClusterSecretStore) Defaults to `SecretStore`
|
||||
type: string
|
||||
name:
|
||||
description: Name of the SecretStore resource
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
type: object
|
||||
type: object
|
||||
type: object
|
||||
type: array
|
||||
refreshInterval:
|
||||
|
@ -281,8 +350,6 @@ spec:
|
|||
type: string
|
||||
type: object
|
||||
type: object
|
||||
required:
|
||||
- secretStoreRef
|
||||
type: object
|
||||
namespaceSelector:
|
||||
description: The labels to select by to find the Namespaces to create the ExternalSecrets in.
|
||||
|
@ -1755,6 +1822,19 @@ spec:
|
|||
description: Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults to the namespace of the referent.
|
||||
type: string
|
||||
type: object
|
||||
sessionTokenSecretRef:
|
||||
description: 'The SessionToken used for authentication This must be defined if AccessKeyID and SecretAccessKey are temporary credentials see: https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_temp_use-resources.html'
|
||||
properties:
|
||||
key:
|
||||
description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required.
|
||||
type: string
|
||||
name:
|
||||
description: The name of the Secret resource being referred to.
|
||||
type: string
|
||||
namespace:
|
||||
description: Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults to the namespace of the referent.
|
||||
type: string
|
||||
type: object
|
||||
type: object
|
||||
type: object
|
||||
region:
|
||||
|
@ -3076,7 +3156,7 @@ spec:
|
|||
description: ExternalSecretData defines the connection between the Kubernetes Secret key (spec.data.<key>) and the Provider data.
|
||||
properties:
|
||||
remoteRef:
|
||||
description: ExternalSecretDataRemoteRef defines Provider data location.
|
||||
description: RemoteRef points to the remote secret and defines which secret (version/property/..) to fetch.
|
||||
properties:
|
||||
conversionStrategy:
|
||||
default: Default
|
||||
|
@ -3102,7 +3182,42 @@ spec:
|
|||
- key
|
||||
type: object
|
||||
secretKey:
|
||||
description: SecretKey defines the key in which the controller stores the value. This is the key in the Kind=Secret
|
||||
type: string
|
||||
sourceRef:
|
||||
description: SourceRef allows you to override the source from which the value will pulled from.
|
||||
maxProperties: 1
|
||||
properties:
|
||||
generatorRef:
|
||||
description: GeneratorRef points to a generator custom resource in
|
||||
properties:
|
||||
apiVersion:
|
||||
default: generators.external-secrets.io/v1alpha1
|
||||
description: Specify the apiVersion of the generator resource
|
||||
type: string
|
||||
kind:
|
||||
description: Specify the Kind of the resource, e.g. Password, ACRAccessToken etc.
|
||||
type: string
|
||||
name:
|
||||
description: Specify the name of the generator resource
|
||||
type: string
|
||||
required:
|
||||
- kind
|
||||
- name
|
||||
type: object
|
||||
storeRef:
|
||||
description: SecretStoreRef defines which SecretStore to fetch the ExternalSecret data.
|
||||
properties:
|
||||
kind:
|
||||
description: Kind of the SecretStore resource (SecretStore or ClusterSecretStore) Defaults to `SecretStore`
|
||||
type: string
|
||||
name:
|
||||
description: Name of the SecretStore resource
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
type: object
|
||||
type: object
|
||||
required:
|
||||
- remoteRef
|
||||
- secretKey
|
||||
|
@ -3113,7 +3228,7 @@ spec:
|
|||
items:
|
||||
properties:
|
||||
extract:
|
||||
description: Used to extract multiple key/value pairs from one secret
|
||||
description: 'Used to extract multiple key/value pairs from one secret Note: Extract does not support sourceRef.Generator or sourceRef.GeneratorRef.'
|
||||
properties:
|
||||
conversionStrategy:
|
||||
default: Default
|
||||
|
@ -3139,7 +3254,7 @@ spec:
|
|||
- key
|
||||
type: object
|
||||
find:
|
||||
description: Used to find secrets based on tags or regular expressions
|
||||
description: 'Used to find secrets based on tags or regular expressions Note: Find does not support sourceRef.Generator or sourceRef.GeneratorRef.'
|
||||
properties:
|
||||
conversionStrategy:
|
||||
default: Default
|
||||
|
@ -3184,6 +3299,40 @@ spec:
|
|||
type: object
|
||||
type: object
|
||||
type: array
|
||||
sourceRef:
|
||||
description: SourceRef points to a store or generator which contains secret values ready to use. Use this in combination with Extract or Find pull values out of a specific SecretStore. When sourceRef points to a generator Extract or Find is not supported. The generator returns a static map of values
|
||||
maxProperties: 1
|
||||
properties:
|
||||
generatorRef:
|
||||
description: GeneratorRef points to a generator custom resource in
|
||||
properties:
|
||||
apiVersion:
|
||||
default: generators.external-secrets.io/v1alpha1
|
||||
description: Specify the apiVersion of the generator resource
|
||||
type: string
|
||||
kind:
|
||||
description: Specify the Kind of the resource, e.g. Password, ACRAccessToken etc.
|
||||
type: string
|
||||
name:
|
||||
description: Specify the name of the generator resource
|
||||
type: string
|
||||
required:
|
||||
- kind
|
||||
- name
|
||||
type: object
|
||||
storeRef:
|
||||
description: SecretStoreRef defines which SecretStore to fetch the ExternalSecret data.
|
||||
properties:
|
||||
kind:
|
||||
description: Kind of the SecretStore resource (SecretStore or ClusterSecretStore) Defaults to `SecretStore`
|
||||
type: string
|
||||
name:
|
||||
description: Name of the SecretStore resource
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
type: object
|
||||
type: object
|
||||
type: object
|
||||
type: array
|
||||
refreshInterval:
|
||||
|
@ -3298,8 +3447,6 @@ spec:
|
|||
type: string
|
||||
type: object
|
||||
type: object
|
||||
required:
|
||||
- secretStoreRef
|
||||
type: object
|
||||
status:
|
||||
properties:
|
||||
|
@ -4726,6 +4873,19 @@ spec:
|
|||
description: Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults to the namespace of the referent.
|
||||
type: string
|
||||
type: object
|
||||
sessionTokenSecretRef:
|
||||
description: 'The SessionToken used for authentication This must be defined if AccessKeyID and SecretAccessKey are temporary credentials see: https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_temp_use-resources.html'
|
||||
properties:
|
||||
key:
|
||||
description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required.
|
||||
type: string
|
||||
name:
|
||||
description: The name of the Secret resource being referred to.
|
||||
type: string
|
||||
namespace:
|
||||
description: Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults to the namespace of the referent.
|
||||
type: string
|
||||
type: object
|
||||
type: object
|
||||
type: object
|
||||
region:
|
||||
|
@ -5775,3 +5935,516 @@ spec:
|
|||
name: kubernetes
|
||||
namespace: default
|
||||
path: /convert
|
||||
---
|
||||
apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.10.0
|
||||
creationTimestamp: null
|
||||
name: acraccesstokens.generators.external-secrets.io
|
||||
spec:
|
||||
group: generators.external-secrets.io
|
||||
names:
|
||||
categories:
|
||||
- acraccesstoken
|
||||
kind: ACRAccessToken
|
||||
listKind: ACRAccessTokenList
|
||||
plural: acraccesstokens
|
||||
shortNames:
|
||||
- acraccesstoken
|
||||
singular: acraccesstoken
|
||||
scope: Namespaced
|
||||
versions:
|
||||
- name: v1alpha1
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
description: "ACRAccessToken returns a Azure Container Registry token that can be used for pushing/pulling images. Note: by default it will return an ACR Refresh Token with full access (depending on the identity). This can be scoped down to the repository level using .spec.scope. In case scope is defined it will return an ACR Access Token. \n See docs: https://github.com/Azure/acr/blob/main/docs/AAD-OAuth.md"
|
||||
properties:
|
||||
apiVersion:
|
||||
description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
|
||||
type: string
|
||||
kind:
|
||||
description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
|
||||
type: string
|
||||
metadata:
|
||||
type: object
|
||||
spec:
|
||||
description: 'ACRAccessTokenSpec defines how to generate the access token e.g. how to authenticate and which registry to use. see: https://github.com/Azure/acr/blob/main/docs/AAD-OAuth.md#overview'
|
||||
properties:
|
||||
auth:
|
||||
properties:
|
||||
managedIdentity:
|
||||
description: ManagedIdentity uses Azure Managed Identity to authenticate with Azure.
|
||||
properties:
|
||||
identityId:
|
||||
description: If multiple Managed Identity is assigned to the pod, you can select the one to be used
|
||||
type: string
|
||||
type: object
|
||||
servicePrincipal:
|
||||
description: ServicePrincipal uses Azure Service Principal credentials to authenticate with Azure.
|
||||
properties:
|
||||
secretRef:
|
||||
description: Configuration used to authenticate with Azure using static credentials stored in a Kind=Secret.
|
||||
properties:
|
||||
clientId:
|
||||
description: The Azure clientId of the service principle used for authentication.
|
||||
properties:
|
||||
key:
|
||||
description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required.
|
||||
type: string
|
||||
name:
|
||||
description: The name of the Secret resource being referred to.
|
||||
type: string
|
||||
namespace:
|
||||
description: Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults to the namespace of the referent.
|
||||
type: string
|
||||
type: object
|
||||
clientSecret:
|
||||
description: The Azure ClientSecret of the service principle used for authentication.
|
||||
properties:
|
||||
key:
|
||||
description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required.
|
||||
type: string
|
||||
name:
|
||||
description: The name of the Secret resource being referred to.
|
||||
type: string
|
||||
namespace:
|
||||
description: Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults to the namespace of the referent.
|
||||
type: string
|
||||
type: object
|
||||
type: object
|
||||
required:
|
||||
- secretRef
|
||||
type: object
|
||||
workloadIdentity:
|
||||
description: WorkloadIdentity uses Azure Workload Identity to authenticate with Azure.
|
||||
properties:
|
||||
serviceAccountRef:
|
||||
description: ServiceAccountRef specified the service account that should be used when authenticating with WorkloadIdentity.
|
||||
properties:
|
||||
audiences:
|
||||
description: Audience specifies the `aud` claim for the service account token If the service account uses a well-known annotation for e.g. IRSA or GCP Workload Identity then this audiences will be appended to the list
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
name:
|
||||
description: The name of the ServiceAccount resource being referred to.
|
||||
type: string
|
||||
namespace:
|
||||
description: Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults to the namespace of the referent.
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
type: object
|
||||
type: object
|
||||
type: object
|
||||
environmentType:
|
||||
default: PublicCloud
|
||||
description: 'EnvironmentType specifies the Azure cloud environment endpoints to use for connecting and authenticating with Azure. By default it points to the public cloud AAD endpoint. The following endpoints are available, also see here: https://github.com/Azure/go-autorest/blob/main/autorest/azure/environments.go#L152 PublicCloud, USGovernmentCloud, ChinaCloud, GermanCloud'
|
||||
enum:
|
||||
- PublicCloud
|
||||
- USGovernmentCloud
|
||||
- ChinaCloud
|
||||
- GermanCloud
|
||||
type: string
|
||||
registry:
|
||||
description: the domain name of the ACR registry e.g. foobarexample.azurecr.io
|
||||
type: string
|
||||
scope:
|
||||
description: "Define the scope for the access token, e.g. pull/push access for a repository. if not provided it will return a refresh token that has full scope. Note: you need to pin it down to the repository level, there is no wildcard available. \n examples: repository:my-repository:pull,push repository:my-repository:pull \n see docs for details: https://docs.docker.com/registry/spec/auth/scope/"
|
||||
type: string
|
||||
tenantId:
|
||||
description: TenantID configures the Azure Tenant to send requests to. Required for ServicePrincipal auth type.
|
||||
type: string
|
||||
required:
|
||||
- auth
|
||||
- registry
|
||||
type: object
|
||||
type: object
|
||||
served: true
|
||||
storage: true
|
||||
subresources:
|
||||
status: {}
|
||||
conversion:
|
||||
strategy: Webhook
|
||||
webhook:
|
||||
conversionReviewVersions:
|
||||
- v1
|
||||
clientConfig:
|
||||
service:
|
||||
name: kubernetes
|
||||
namespace: default
|
||||
path: /convert
|
||||
---
|
||||
apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.10.0
|
||||
creationTimestamp: null
|
||||
name: ecrauthorizationtokens.generators.external-secrets.io
|
||||
spec:
|
||||
group: generators.external-secrets.io
|
||||
names:
|
||||
categories:
|
||||
- ecrauthorizationtoken
|
||||
kind: ECRAuthorizationToken
|
||||
listKind: ECRAuthorizationTokenList
|
||||
plural: ecrauthorizationtokens
|
||||
shortNames:
|
||||
- ecrauthorizationtoken
|
||||
singular: ecrauthorizationtoken
|
||||
scope: Namespaced
|
||||
versions:
|
||||
- name: v1alpha1
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
description: ECRAuthorizationTokenSpec uses the GetAuthorizationToken API to retrieve an authorization token. The authorization token is valid for 12 hours. The authorizationToken returned is a base64 encoded string that can be decoded and used in a docker login command to authenticate to a registry. For more information, see Registry authentication (https://docs.aws.amazon.com/AmazonECR/latest/userguide/Registries.html#registry_auth) in the Amazon Elastic Container Registry User Guide.
|
||||
properties:
|
||||
apiVersion:
|
||||
description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
|
||||
type: string
|
||||
kind:
|
||||
description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
|
||||
type: string
|
||||
metadata:
|
||||
type: object
|
||||
spec:
|
||||
properties:
|
||||
auth:
|
||||
description: Auth defines how to authenticate with AWS
|
||||
properties:
|
||||
jwt:
|
||||
description: Authenticate against AWS using service account tokens.
|
||||
properties:
|
||||
serviceAccountRef:
|
||||
description: A reference to a ServiceAccount resource.
|
||||
properties:
|
||||
audiences:
|
||||
description: Audience specifies the `aud` claim for the service account token If the service account uses a well-known annotation for e.g. IRSA or GCP Workload Identity then this audiences will be appended to the list
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
name:
|
||||
description: The name of the ServiceAccount resource being referred to.
|
||||
type: string
|
||||
namespace:
|
||||
description: Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults to the namespace of the referent.
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
type: object
|
||||
type: object
|
||||
secretRef:
|
||||
description: AWSAuthSecretRef holds secret references for AWS credentials both AccessKeyID and SecretAccessKey must be defined in order to properly authenticate.
|
||||
properties:
|
||||
accessKeyIDSecretRef:
|
||||
description: The AccessKeyID is used for authentication
|
||||
properties:
|
||||
key:
|
||||
description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required.
|
||||
type: string
|
||||
name:
|
||||
description: The name of the Secret resource being referred to.
|
||||
type: string
|
||||
namespace:
|
||||
description: Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults to the namespace of the referent.
|
||||
type: string
|
||||
type: object
|
||||
secretAccessKeySecretRef:
|
||||
description: The SecretAccessKey is used for authentication
|
||||
properties:
|
||||
key:
|
||||
description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required.
|
||||
type: string
|
||||
name:
|
||||
description: The name of the Secret resource being referred to.
|
||||
type: string
|
||||
namespace:
|
||||
description: Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults to the namespace of the referent.
|
||||
type: string
|
||||
type: object
|
||||
sessionTokenSecretRef:
|
||||
description: 'The SessionToken used for authentication This must be defined if AccessKeyID and SecretAccessKey are temporary credentials see: https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_temp_use-resources.html'
|
||||
properties:
|
||||
key:
|
||||
description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required.
|
||||
type: string
|
||||
name:
|
||||
description: The name of the Secret resource being referred to.
|
||||
type: string
|
||||
namespace:
|
||||
description: Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults to the namespace of the referent.
|
||||
type: string
|
||||
type: object
|
||||
type: object
|
||||
type: object
|
||||
region:
|
||||
description: Region specifies the region to operate in.
|
||||
type: string
|
||||
role:
|
||||
description: You can assume a role before making calls to the desired AWS service.
|
||||
type: string
|
||||
required:
|
||||
- region
|
||||
type: object
|
||||
type: object
|
||||
served: true
|
||||
storage: true
|
||||
subresources:
|
||||
status: {}
|
||||
conversion:
|
||||
strategy: Webhook
|
||||
webhook:
|
||||
conversionReviewVersions:
|
||||
- v1
|
||||
clientConfig:
|
||||
service:
|
||||
name: kubernetes
|
||||
namespace: default
|
||||
path: /convert
|
||||
---
|
||||
apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.10.0
|
||||
creationTimestamp: null
|
||||
name: fakes.generators.external-secrets.io
|
||||
spec:
|
||||
group: generators.external-secrets.io
|
||||
names:
|
||||
categories:
|
||||
- fake
|
||||
kind: Fake
|
||||
listKind: FakeList
|
||||
plural: fakes
|
||||
shortNames:
|
||||
- fake
|
||||
singular: fake
|
||||
scope: Namespaced
|
||||
versions:
|
||||
- name: v1alpha1
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
description: Fake generator is used for testing. It lets you define a static set of credentials that is always returned.
|
||||
properties:
|
||||
apiVersion:
|
||||
description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
|
||||
type: string
|
||||
kind:
|
||||
description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
|
||||
type: string
|
||||
metadata:
|
||||
type: object
|
||||
spec:
|
||||
description: FakeSpec contains the static data.
|
||||
properties:
|
||||
data:
|
||||
additionalProperties:
|
||||
type: string
|
||||
description: Data defines the static data returned by this generator.
|
||||
type: object
|
||||
type: object
|
||||
type: object
|
||||
served: true
|
||||
storage: true
|
||||
subresources:
|
||||
status: {}
|
||||
conversion:
|
||||
strategy: Webhook
|
||||
webhook:
|
||||
conversionReviewVersions:
|
||||
- v1
|
||||
clientConfig:
|
||||
service:
|
||||
name: kubernetes
|
||||
namespace: default
|
||||
path: /convert
|
||||
---
|
||||
apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.10.0
|
||||
creationTimestamp: null
|
||||
name: gcraccesstokens.generators.external-secrets.io
|
||||
spec:
|
||||
group: generators.external-secrets.io
|
||||
names:
|
||||
categories:
|
||||
- gcraccesstoken
|
||||
kind: GCRAccessToken
|
||||
listKind: GCRAccessTokenList
|
||||
plural: gcraccesstokens
|
||||
shortNames:
|
||||
- gcraccesstoken
|
||||
singular: gcraccesstoken
|
||||
scope: Namespaced
|
||||
versions:
|
||||
- name: v1alpha1
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
description: GCRAccessToken generates an GCP access token that can be used to authenticate with GCR.
|
||||
properties:
|
||||
apiVersion:
|
||||
description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
|
||||
type: string
|
||||
kind:
|
||||
description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
|
||||
type: string
|
||||
metadata:
|
||||
type: object
|
||||
spec:
|
||||
properties:
|
||||
auth:
|
||||
description: Auth defines the means for authenticating with GCP
|
||||
properties:
|
||||
secretRef:
|
||||
properties:
|
||||
secretAccessKeySecretRef:
|
||||
description: The SecretAccessKey is used for authentication
|
||||
properties:
|
||||
key:
|
||||
description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required.
|
||||
type: string
|
||||
name:
|
||||
description: The name of the Secret resource being referred to.
|
||||
type: string
|
||||
namespace:
|
||||
description: Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults to the namespace of the referent.
|
||||
type: string
|
||||
type: object
|
||||
type: object
|
||||
workloadIdentity:
|
||||
properties:
|
||||
clusterLocation:
|
||||
type: string
|
||||
clusterName:
|
||||
type: string
|
||||
clusterProjectID:
|
||||
type: string
|
||||
serviceAccountRef:
|
||||
description: A reference to a ServiceAccount resource.
|
||||
properties:
|
||||
audiences:
|
||||
description: Audience specifies the `aud` claim for the service account token If the service account uses a well-known annotation for e.g. IRSA or GCP Workload Identity then this audiences will be appended to the list
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
name:
|
||||
description: The name of the ServiceAccount resource being referred to.
|
||||
type: string
|
||||
namespace:
|
||||
description: Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults to the namespace of the referent.
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
type: object
|
||||
required:
|
||||
- clusterLocation
|
||||
- clusterName
|
||||
- serviceAccountRef
|
||||
type: object
|
||||
type: object
|
||||
projectID:
|
||||
description: ProjectID defines which project to use to authenticate with
|
||||
type: string
|
||||
required:
|
||||
- auth
|
||||
- projectID
|
||||
type: object
|
||||
type: object
|
||||
served: true
|
||||
storage: true
|
||||
subresources:
|
||||
status: {}
|
||||
conversion:
|
||||
strategy: Webhook
|
||||
webhook:
|
||||
conversionReviewVersions:
|
||||
- v1
|
||||
clientConfig:
|
||||
service:
|
||||
name: kubernetes
|
||||
namespace: default
|
||||
path: /convert
|
||||
---
|
||||
apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.10.0
|
||||
creationTimestamp: null
|
||||
name: passwords.generators.external-secrets.io
|
||||
spec:
|
||||
group: generators.external-secrets.io
|
||||
names:
|
||||
categories:
|
||||
- password
|
||||
kind: Password
|
||||
listKind: PasswordList
|
||||
plural: passwords
|
||||
shortNames:
|
||||
- password
|
||||
singular: password
|
||||
scope: Namespaced
|
||||
versions:
|
||||
- name: v1alpha1
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
description: Password generates a random password based on the configuration parameters in spec. You can specify the length, characterset and other attributes.
|
||||
properties:
|
||||
apiVersion:
|
||||
description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
|
||||
type: string
|
||||
kind:
|
||||
description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
|
||||
type: string
|
||||
metadata:
|
||||
type: object
|
||||
spec:
|
||||
description: PasswordSpec controls the behavior of the password generator.
|
||||
properties:
|
||||
allowRepeat:
|
||||
default: false
|
||||
description: set AllowRepeat to true to allow repeating characters.
|
||||
type: boolean
|
||||
digits:
|
||||
description: Digits specifies the number of digits in the generated password. If omitted it defaults to 25% of the length of the password
|
||||
type: integer
|
||||
length:
|
||||
default: 24
|
||||
description: Length of the password to be generated. Defaults to 24
|
||||
type: integer
|
||||
noUpper:
|
||||
default: false
|
||||
description: Set NoUpper to disable uppercase characters
|
||||
type: boolean
|
||||
symbolCharacters:
|
||||
description: SymbolCharacters specifies the special characters that should be used in the generated password.
|
||||
type: string
|
||||
symbols:
|
||||
description: Symbols specifies the number of symbol characters in the generated password. If omitted it defaults to 25% of the length of the password
|
||||
type: integer
|
||||
required:
|
||||
- allowRepeat
|
||||
- length
|
||||
- noUpper
|
||||
type: object
|
||||
type: object
|
||||
served: true
|
||||
storage: true
|
||||
subresources:
|
||||
status: {}
|
||||
conversion:
|
||||
strategy: Webhook
|
||||
webhook:
|
||||
conversionReviewVersions:
|
||||
- v1
|
||||
clientConfig:
|
||||
service:
|
||||
name: kubernetes
|
||||
namespace: default
|
||||
path: /convert
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
The `ClusterExternalSecret` is a cluster scoped resource that can be used to push an `ExternalSecret` to specific namespaces.
|
||||
![ClusterExternalSecret](../pictures/diagrams-cluster-external-secrets.png)
|
||||
|
||||
Using the `namespaceSelector` you can select namespaces, and any matching namespaces will have the `ExternalSecret` specified in the `externalSecretSpec` created in it.
|
||||
The `ClusterExternalSecret` is a cluster scoped resource that can be used to manage `ExternalSecret` resources in specific namespaces.
|
||||
|
||||
With `namespaceSelector` you can select namespaces in which the ExternalSecret should be created.
|
||||
If there is a conflict with an existing resource the controller will error out.
|
||||
|
||||
## Example
|
||||
|
||||
|
|
|
@ -3,6 +3,11 @@
|
|||
The `ClusterSecretStore` is a cluster scoped SecretStore that can be referenced by all
|
||||
`ExternalSecrets` from all namespaces. Use it to offer a central gateway to your secret backend.
|
||||
|
||||
|
||||
## Example
|
||||
|
||||
For a full list of supported fields see [spec](./spec.md) or dig into our [guides](../guides).
|
||||
|
||||
``` yaml
|
||||
{% include 'full-cluster-secret-store.yaml' %}
|
||||
```
|
||||
|
|
18
docs/api/components.md
Normal file
18
docs/api/components.md
Normal file
|
@ -0,0 +1,18 @@
|
|||
---
|
||||
hide:
|
||||
- toc
|
||||
---
|
||||
|
||||
# Components
|
||||
|
||||
## Overview
|
||||
|
||||
Exernal Secrets comes with three components: `Core Controller`, `Webhook` and `Cert Controller`.
|
||||
|
||||
This is due to the need to implement conversion webhooks in order to convert custom resources between api versions and
|
||||
to provide a ValidatingWebhook for the `ExternalSecret` and `SecretStore` resources.
|
||||
|
||||
These features are optional but highly recommended. You can disable them with hem chart values `certController.create=false` and `webhook.create=false`.
|
||||
|
||||
<br/>
|
||||
![Component Overview](../pictures/diagrams-component-overview.png)
|
62
docs/api/controller-options.md
Normal file
62
docs/api/controller-options.md
Normal file
|
@ -0,0 +1,62 @@
|
|||
---
|
||||
hide:
|
||||
- toc
|
||||
---
|
||||
|
||||
# Controller Options
|
||||
|
||||
The external-secrets binary includes three components: `core controller`, `certcontroller` and `webook`.
|
||||
|
||||
## Core Controller Flags
|
||||
|
||||
The core controller is invoked without a subcommand and can be configured with the following flags:
|
||||
|
||||
| Name | Type | Default | Descripton |
|
||||
| --------------------------------------------- | -------- | ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| `--client-burst` | int | uses rest client default (10) | Maximum Burst allowed to be passed to rest.Client |
|
||||
| `--client-qps` | float32 | uses rest client default (5) | QPS configuration to be passed to rest.Client |
|
||||
| `--concurrent` | int | 1 | The number of concurrent ExternalSecret reconciles. |
|
||||
| `--controller-class` | string | default | The controller is instantiated with a specific controller name and filters ES based on this property |
|
||||
| `--enable-cluster-external-secret-reconciler` | boolean | true | Enables the cluster external secret reconciler. |
|
||||
| `--enable-cluster-store-reconciler` | boolean | true | Enables the cluster store reconciler. |
|
||||
| `--enable-secrets-caching` | boolean | false | Enables the secrets caching for external-secrets pod. |
|
||||
| `--enable-configmaps-caching` | boolean | false | Enables the ConfigMap caching for external-secrets pod. |
|
||||
| `--enable-flood-gate` | boolean | true | Enable flood gate. External secret will be reconciled only if the ClusterStore or Store have an healthy or unknown state. |
|
||||
| `--enable-leader-election` | boolean | false | Enable leader election for controller manager. Enabling this will ensure there is only one active controller manager. |
|
||||
| `--experimental-enable-aws-session-cache` | boolean | false | Enable experimental AWS session cache. External secret will reuse the AWS session without creating a new one on each request. |
|
||||
| `--help` | | | help for external-secrets |
|
||||
| `--loglevel` | string | info | loglevel to use, one of: debug, info, warn, error, dpanic, panic, fatal |
|
||||
| `--metrics-addr` | string | :8080 | The address the metric endpoint binds to. |
|
||||
| `--namespace` | string | - | 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 |
|
||||
| `--store-requeue-interval` | duration | 5m0s | Default Time duration between reconciling (Cluster)SecretStores |
|
||||
|
||||
## Cert Controller Flags
|
||||
|
||||
| Name | Type | Default | Descripton |
|
||||
| -------------------------- | -------- | ------------------------ | --------------------------------------------------------------------------------------------------------------------- |
|
||||
| `--crd-requeue-interval` | duration | 5m0s | Time duration between reconciling CRDs for new certs |
|
||||
| `--enable-leader-election` | boolean | false | Enable leader election for controller manager. Enabling this will ensure there is only one active controller manager. |
|
||||
| `--healthz-addr` | string | :8081 | The address the health endpoint binds to. |
|
||||
| `--help` | | | help for certcontroller |
|
||||
| `--loglevel` | string | info | loglevel to use, one of: debug, info, warn, error, dpanic, panic, fatal |
|
||||
| `--metrics-addr` | string | :8080 | The address the metric endpoint binds to. |
|
||||
| `--secret-name` | string | external-secrets-webhook | Secret to store certs for webhook |
|
||||
| `--secret-namespace` | string | default | namespace of the secret to store certs |
|
||||
| `--service-name` | string | external-secrets-webhook | Webhook service name |
|
||||
| `--service-namespace` | string | default | Webhook service namespace |
|
||||
|
||||
## Webhook Flags
|
||||
|
||||
| Name | Type | Default | Descripton |
|
||||
| ---------------------- | -------- | ------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| `--cert-dir` | string | /tmp/k8s-webhook-server/serving-certs | path to check for certs |
|
||||
| `--check-interval` | duration | 5m0s | certificate check interval |
|
||||
| `--dns-name` | string | localhost | DNS name to validate certificates with |
|
||||
| `--healthz-addr` | string | :8081 | The address the health endpoint binds to. |
|
||||
| `--help` | | | help for webhook |
|
||||
| `--loglevel` | string | info | loglevel to use, one of: debug, info, warn, error, dpanic, panic, fatal |
|
||||
| `--lookahead-interval` | duration | 2160h0m0s (90d) | certificate check interval |
|
||||
| `--metrics-addr` | string | :8080 | The address the metric endpoint binds to. |
|
||||
| `--port` | number | 10250 | Port number that the webhook server will serve. |
|
||||
| `--tls-ciphers` | string | | comma separated list of tls ciphers allowed. This does not apply to TLS 1.3 as the ciphers are selected automatically. The order of this list does not give preference to the ciphers, the ordering is done automatically. Full lists of available ciphers can be found at https://pkg.go.dev/crypto/tls#pkg-constants. E.g. 'TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256' |
|
||||
| `--tls-min-version` | string | 1.2 | minimum version of TLS supported. |
|
49
docs/api/generator/acr.md
Normal file
49
docs/api/generator/acr.md
Normal file
|
@ -0,0 +1,49 @@
|
|||
|
||||
The Azure Container Registry (ACR) generator creates a short-lived refresh or access token for accessing ACR.
|
||||
The token is generated for a particular ACR registry defined in `spec.registry`.
|
||||
|
||||
## Output Keys and Values
|
||||
|
||||
| Key | Description |
|
||||
| -------- | ----------- |
|
||||
| username | username for the `docker login` command |
|
||||
| password | password for the `docker login` command |
|
||||
|
||||
|
||||
## Authentication
|
||||
|
||||
You must choose one out of three authentication mechanisms:
|
||||
|
||||
- service principal
|
||||
- managed identity
|
||||
- workload identity
|
||||
|
||||
The generated token will inherit the permissions from the assigned policy. I.e. when you assign a read-only policy all generated tokens will be read-only.
|
||||
|
||||
You can scope tokens to a particular repository using `spec.scope`.
|
||||
|
||||
## Scope
|
||||
|
||||
First, an Azure Active Directory access token is obtained with the desired authentication method.
|
||||
This AAD access token will be used to authenticate against ACR to issue a refresh token or access token.
|
||||
If `spec.scope` if it is defined it obtains an ACR access token. If `spec.scope` is missing it obtains an ACR refresh token:
|
||||
|
||||
- access tokens are scoped to a specific repository or action (pull,push)
|
||||
- refresh tokens can are scoped to whatever policy is attached to the identity that creates the acr refresh token
|
||||
|
||||
The Scope grammar is defined in the [Docker Registry spec](https://docs.docker.com/registry/spec/auth/scope/).
|
||||
Note: You **can not** use a wildcards in the scope parameter, you can match exactly one repository and defined multiple actions like `pull` or `push`.
|
||||
|
||||
Example scopes:
|
||||
|
||||
```
|
||||
repository:my-repository:pull,push
|
||||
repository:my-repository:pull
|
||||
```
|
||||
|
||||
## Example Manifest
|
||||
|
||||
```yaml
|
||||
{% include 'generator-acr.yaml' %}
|
||||
```
|
||||
|
26
docs/api/generator/ecr.md
Normal file
26
docs/api/generator/ecr.md
Normal file
|
@ -0,0 +1,26 @@
|
|||
ECRAuthorizationTokenSpec uses the GetAuthorizationToken API to retrieve an authorization token.
|
||||
The authorization token is valid for 12 hours. For more information, see [registry authentication](https://docs.aws.amazon.com/AmazonECR/latest/userguide/Registries.html#registry_auth) in the Amazon Elastic Container Registry User Guide.
|
||||
|
||||
|
||||
## Output Keys and Values
|
||||
|
||||
| Key | Description |
|
||||
| -------------- | --------------------------------------------------------------------------------- |
|
||||
| username | username for the `docker login` command. |
|
||||
| password | password for the `docker login` command. |
|
||||
| proxy_endpoint | The registry URL to use for this authorization token in a `docker login` command. |
|
||||
| expires_at | time when token expires in UNIX time (seconds since January 1, 1970 UTC). |
|
||||
|
||||
## Authentication
|
||||
|
||||
You can choose from three authentication mechanisms:
|
||||
|
||||
* static credentials using `spec.auth.secretRef`
|
||||
* point to a IRSA Service Account with `spec.auth.jwt`
|
||||
* use credentials from the [SDK default credentials chain](https://docs.aws.amazon.com/sdk-for-java/v1/developer-guide/credentials.html#credentials-default) from the controller environment
|
||||
|
||||
## Example Manifest
|
||||
|
||||
```yaml
|
||||
{% include 'generator-ecr.yaml' %}
|
||||
```
|
8
docs/api/generator/fake.md
Normal file
8
docs/api/generator/fake.md
Normal file
|
@ -0,0 +1,8 @@
|
|||
The Fake generator provides hard-coded key/value pairs. The intended use is just for debugging and testing.
|
||||
The key/value pairs defined in `spec.data` is returned as-is.
|
||||
|
||||
## Example Manifest
|
||||
|
||||
```yaml
|
||||
{% include 'generator-fake.yaml' %}
|
||||
```
|
30
docs/api/generator/gcr.md
Normal file
30
docs/api/generator/gcr.md
Normal file
|
@ -0,0 +1,30 @@
|
|||
GCRAccessToken creates a GCP Access token that can be used to authenticate with GCR in order to pull OCI images. You won't need any extra permissions to request for a token, but the token would only work against a GCR if the token requester (service Account or WI) has the appropriate access
|
||||
|
||||
You must specify the `spec.projectID` in which GCR is located.
|
||||
|
||||
## Output Keys and Values
|
||||
|
||||
| Key | Description |
|
||||
| ---------- | ------------------------------------------------------------------------- |
|
||||
| username | username for the `docker login` command. |
|
||||
| password | password for the `docker login` command. |
|
||||
| expiry | time when token expires in UNIX time (seconds since January 1, 1970 UTC). |
|
||||
|
||||
## Authentication
|
||||
|
||||
### Workload Identity
|
||||
|
||||
Use `spec.auth.workloadIdentity` to point to a Service Account that has Workload Identity enabled.
|
||||
For details see [GCP Secret Manager](../../provider/google-secrets-manager.md#authentication).
|
||||
|
||||
|
||||
### GCP Service Account
|
||||
|
||||
Use `spec.auth.secretRef` to point to a Secret that contains a GCP Service Account.
|
||||
For details see [GCP Secret Manager](../../provider/google-secrets-manager.md#authentication).
|
||||
|
||||
## Example Manifest
|
||||
|
||||
```yaml
|
||||
{% include 'generator-gcr.yaml' %}
|
||||
```
|
2
docs/api/generator/index.md
Normal file
2
docs/api/generator/index.md
Normal file
|
@ -0,0 +1,2 @@
|
|||
|
||||
Generators allow you to generate values. See [Generators Guide](../../guides/generator.md)
|
46
docs/api/generator/password.md
Normal file
46
docs/api/generator/password.md
Normal file
|
@ -0,0 +1,46 @@
|
|||
The Password generator provides random passwords that you can feed into your applications. It uses lower and uppercase alphanumeric characters as well as symbols. Please see below for the symbols in use.
|
||||
|
||||
!!! warning "Passwords are completely randomized"
|
||||
It is possible that we may generate passwords that don't match the expected character set from your application.
|
||||
|
||||
## Output Keys and Values
|
||||
|
||||
| Key | Description |
|
||||
| -------- | ---------------------- |
|
||||
| password | the generated password |
|
||||
|
||||
## Parameters
|
||||
|
||||
You can influence the behavior of the generator by providing the following args
|
||||
|
||||
| Key | Default | Description |
|
||||
| ---------------- | ---------------------------------- | --------------------------------------------------------------------------- |
|
||||
| length | 24 | Length of the password to be generated. |
|
||||
| digits | 25% of the length | Specify the number of digits in the generated password. |
|
||||
| symbols | 25% of the length | Specify the number of symbol characters in the generated. |
|
||||
| symbolCharacters | ~!@#$%^&\*()\_+`-={}\|[]\\:"<>?,./ | Specify the character set that should be used when generating the password. |
|
||||
| noUpper | false | disable uppercase characters. |
|
||||
| allowRepeat | false | allow repeating characters. |
|
||||
|
||||
## Example Manifest
|
||||
|
||||
```yaml
|
||||
{% include 'generator-password.yaml' %}
|
||||
```
|
||||
|
||||
May produce values such as:
|
||||
|
||||
```
|
||||
RMngCHKtZ@@h@3aja$WZDuDVhkCkN48JBa9OF8jH$R
|
||||
VB$pX8SSUMIlk9K8g@XxJAhGz$0$ktbJ1ArMukg-bD
|
||||
Hi$-aK_3Rrrw1Pj9-sIpPZuk5abvEDJlabUYUcS$9L
|
||||
```
|
||||
|
||||
With default values you would get something like:
|
||||
|
||||
```
|
||||
2Cp=O*&8x6sdwM!<74G_gUz5
|
||||
-MS`e#n24K|h5A<&6q9Yv7Cj
|
||||
ZRv-k!y6x/V"29:43aErSf$1
|
||||
Vk9*mwXE30Q+>H?lY$5I64_q
|
||||
```
|
27
docs/api/metrics.md
Normal file
27
docs/api/metrics.md
Normal file
|
@ -0,0 +1,27 @@
|
|||
---
|
||||
hide:
|
||||
- toc
|
||||
---
|
||||
|
||||
# Metrics
|
||||
|
||||
The External Secrets Operator exposes its Prometheus metrics in the `/metrics` path. To enable it, set the `serviceMonitor.enabled` Helm flag to `true`. In addition you can also set `webhook.serviceMonitor.enabled=true` and `certController.serviceMonitor.enabled=true` to create `ServiceMonitor` resources for the other components.
|
||||
|
||||
If you are using a different monitoring tool that also needs a `/metrics` endpoint, you can set the `metrics.service.enabled` Helm flag to `true`. In addition you can also set `webhook.metrics.service.enabled` and `certController.metrics.service.enabled` to scrape the other components.
|
||||
|
||||
The Operator has the metrics inherited from Kubebuilder plus some custom metrics with the `externalsecret` prefix.
|
||||
|
||||
## External Secret Metrics
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---------------------------------------------- | --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `externalsecret_sync_calls_total` | Counter | Total number of the External Secret sync calls |
|
||||
| `externalsecret_sync_calls_error` | Counter | Total number of the External Secret sync errors |
|
||||
| `externalsecret_status_condition` | Gauge | The status condition of a specific External Secret |
|
||||
| `externalsecret_reconcile_duration` | Gauge | The duration time to reconcile the External Secret |
|
||||
| `controller_runtime_reconcile_total` | Counter | Holds the totalnumber of reconciliations per controller. It has two labels. controller label refers to the controller name and result label refers to the reconcile result i.e success, error, requeue, requeue_after. |
|
||||
| `controller_runtime_reconcile_errors_total` | Counter | Total number of reconcile errors per controller |
|
||||
| `controller_runtime_reconcile_time_seconds` | Histogram | Length of time per reconcile per controller |
|
||||
| `controller_runtime_reconcile_queue_length` | Gauge | Length of reconcile queue per controller |
|
||||
| `controller_runtime_max_concurrent_reconciles` | Gauge | Maximum number of concurrent reconciles per controller |
|
||||
| `controller_runtime_active_workers` | Gauge | Number of currently used workers per controller |
|
|
@ -4,6 +4,13 @@
|
|||
The `SecretStore` is namespaced and specifies how to access the external API.
|
||||
The SecretStore maps to exactly one instance of an external API.
|
||||
|
||||
By design, SecretStores are bound to a namespace and can not reference resources across namespaces.
|
||||
If you want to design cross-namespace SecretStores you must use [ClusterSecretStores](./clustersecretstore.md) which do not have this limitation.
|
||||
|
||||
## Example
|
||||
|
||||
For a full list of supported fields see [spec](./spec.md) or dig into our [guides](../guides).
|
||||
|
||||
``` yaml
|
||||
{% include 'full-secret-store.yaml' %}
|
||||
```
|
||||
|
|
|
@ -21,7 +21,7 @@ FluxCD is composed by several controllers dedicated to manage different custom r
|
|||
ones are **Kustomization** (to clarify, Flux one, not Kubernetes' one) and **HelmRelease** to deploy using the approaches
|
||||
of the same names.
|
||||
|
||||
External Secrets can be deployed using Helm [as explained here](../guides/getting-started.md). The deployment includes the
|
||||
External Secrets can be deployed using Helm [as explained here](../introduction/getting-started.md). The deployment includes the
|
||||
CRDs if enabled on the `values.yaml`, but after this, you need to deploy some `SecretStore` to start
|
||||
getting credentials from your secrets manager with External Secrets.
|
||||
|
||||
|
|
27
docs/guides/generator.md
Normal file
27
docs/guides/generator.md
Normal file
|
@ -0,0 +1,27 @@
|
|||
|
||||
Generators allow you to generate values. They are used through a ExternalSecret `spec.DataFrom`. They are referenced from a custom resource using `sourceRef.generatorRef`.
|
||||
|
||||
If the External Secret should be refreshed via `spec.refreshInterval` the generator produces a map of values with the `generator.spec` as input. The generator does not keep track of the produced values. Every invocation produces a new set of values.
|
||||
|
||||
These values can be used with the other features like `rewrite` or `template`. I.e. you can modify, encode, decode, pack the values as needed.
|
||||
|
||||
## Reference Custom Resource
|
||||
|
||||
Generators can be defined as a custom resource and re-used across different ExternalSecrets. **Every invocation creates a new set of values**. I.e. you can not share the same value produced by a generator across different `ExternalSecrets` or `spec.dataFrom[]` entries.
|
||||
|
||||
```yaml
|
||||
apiVersion: external-secrets.io/v1beta1
|
||||
kind: ExternalSecret
|
||||
metadata:
|
||||
name: "ecr-token"
|
||||
spec:
|
||||
refreshInterval: "30m"
|
||||
target:
|
||||
name: ecr-token
|
||||
dataFrom:
|
||||
- sourceRef:
|
||||
generatorRef:
|
||||
apiVersion: generators.external-secrets.io/v1alpha1
|
||||
kind: ECRAuthorizationToken
|
||||
name: "my-ecr"
|
||||
```
|
|
@ -3,6 +3,13 @@
|
|||
The following guides demonstrate use-cases and provide examples of how to use
|
||||
the API. Please pick one of the following guides:
|
||||
|
||||
* [Getting started](getting-started.md)
|
||||
* [Advanced Templating](templating.md)
|
||||
* [Multi-Tenancy Design Considerations](multi-tenancy.md)
|
||||
* [Find multiple secrets & Extract Secret values](getallsecrets.md)
|
||||
* [Advanced Templating](templating.md)
|
||||
* [Generating Passwords using generators](generator.md)
|
||||
* [Ownership and Deletion Policy](ownership-deletion-policy.md)
|
||||
* [Key Rewriting](datafrom-rewrite.md)
|
||||
* [Controller Class](controller-class.md)
|
||||
* [Decoding Strategy](decoding-strategy.md)
|
||||
* [v1beta1 Migration](v1beta1.md)
|
||||
* [Deploying image from main](using-latest-image.md)
|
||||
|
|
|
@ -1,15 +0,0 @@
|
|||
# Metrics
|
||||
|
||||
The External Secrets Operator exposes its Prometheus metrics in the `/metrics` path. To enable it, set the `serviceMonitor.enabled` Helm flag to `true`. In addition you can also set `webhook.serviceMonitor.enabled=true` and `certController.serviceMonitor.enabled=true` to create `ServiceMonitor` resources for the other components.
|
||||
|
||||
If you are using a different monitoring tool that also needs a `/metrics` endpoint, you can set the `metrics.service.enabled` Helm flag to `true`. In addition you can also set `webhook.metrics.service.enabled` and `certController.metrics.service.enabled` to scrape the other components.
|
||||
|
||||
The Operator has the metrics inherited from Kubebuilder plus some custom metrics with the `externalsecret` prefix.
|
||||
|
||||
## External Secret Metrics
|
||||
|
||||
| Name | Type | Description |
|
||||
| ------------------------------- | ------- | -------------------------------------------------- |
|
||||
| externalsecret_sync_calls_total | Counter | Total number of the External Secret sync calls |
|
||||
| externalsecret_sync_calls_error | Counter | Total number of the External Secret sync errors |
|
||||
| externalsecret_status_condition | Gauge | The status condition of a specific External Secret |
|
|
@ -22,13 +22,13 @@ lifecycle of the secrets for you.
|
|||
|
||||
### Where to get started
|
||||
|
||||
To get started, please read through [API overview](overview.md) this should
|
||||
To get started, please read through [API overview](introduction/overview.md) this should
|
||||
give you a high-level overview to understand the API and use-cases. After that
|
||||
please follow one of our [guides](guides/introduction.md) to get a jump start
|
||||
using the operator. See our [getting started guide](guides/getting-started.md) for installation instructions.
|
||||
using the operator. See our [getting started guide](introduction/getting-started.md) for installation instructions.
|
||||
|
||||
For a complete reference of the API types please refer to our [API
|
||||
Reference](spec.md).
|
||||
Reference](api/spec.md).
|
||||
|
||||
### How to get involved
|
||||
|
||||
|
|
|
@ -75,7 +75,7 @@ Events: <none>
|
|||
```
|
||||
|
||||
For more advanced examples, please read the other
|
||||
[guides](introduction.md).
|
||||
[guides](../guides/introduction.md).
|
||||
|
||||
## Installing with OLM
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
# API Overview
|
||||
|
||||
## Architecture
|
||||
![high-level](./pictures/diagrams-high-level-simple.png)
|
||||
![high-level](../pictures/diagrams-high-level-simple.png)
|
||||
|
||||
The External Secrets Operator extends Kubernetes with [Custom
|
||||
Resources](https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/),
|
||||
|
@ -21,11 +21,11 @@ KeyVault or a AWS Secrets Manager in a certain AWS Account and region. Please
|
|||
take a look at the provider documentation to see what the Bucket actually maps
|
||||
to.
|
||||
|
||||
![Resource Mapping](./pictures/diagrams-resource-mapping.png)
|
||||
![Resource Mapping](../pictures/diagrams-resource-mapping.png)
|
||||
|
||||
### SecretStore
|
||||
|
||||
The idea behind the [SecretStore](api/secretstore.md) resource is to separate concerns of
|
||||
The idea behind the [SecretStore](../api/secretstore.md) resource is to separate concerns of
|
||||
authentication/access and the actual Secret and configuration needed for
|
||||
workloads. The ExternalSecret specifies what to fetch, the SecretStore specifies
|
||||
how to access. This resource is namespaced.
|
||||
|
@ -37,7 +37,7 @@ The `SecretStore` contains references to secrets which hold credentials to
|
|||
access the external API.
|
||||
|
||||
### ExternalSecret
|
||||
An [ExternalSecret](api/externalsecret.md) declares what data to fetch. It has a reference to a
|
||||
An [ExternalSecret](../api/externalsecret.md) declares what data to fetch. It has a reference to a
|
||||
`SecretStore` which knows how to access that data. The controller uses that
|
||||
`ExternalSecret` as a blueprint to create secrets.
|
||||
|
||||
|
@ -47,7 +47,7 @@ An [ExternalSecret](api/externalsecret.md) declares what data to fetch. It has a
|
|||
|
||||
### ClusterSecretStore
|
||||
|
||||
The [ClusterSecretStore](api/clustersecretstore.md) is a global, cluster-wide SecretStore that can be
|
||||
The [ClusterSecretStore](../api/clustersecretstore.md) is a global, cluster-wide SecretStore that can be
|
||||
referenced from all namespaces. You can use it to provide a central gateway to your secret provider.
|
||||
|
||||
## Behavior
|
|
@ -1,3 +1,8 @@
|
|||
---
|
||||
hide:
|
||||
- toc
|
||||
---
|
||||
|
||||
This page lists the status, timeline and policy for currently supported ESO releases and its providers. Please also see our [deprecation policy](deprecation-policy.md) that describes API versioning, deprecation and API surface.
|
||||
|
||||
## External Secrets Operator
|
BIN
docs/pictures/diagrams-cluster-external-secrets.png
Normal file
BIN
docs/pictures/diagrams-cluster-external-secrets.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 60 KiB |
BIN
docs/pictures/diagrams-component-overview.png
Normal file
BIN
docs/pictures/diagrams-component-overview.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 163 KiB |
Binary file not shown.
Before Width: | Height: | Size: 70 KiB After Width: | Height: | Size: 85 KiB |
Binary file not shown.
Before Width: | Height: | Size: 72 KiB After Width: | Height: | Size: 93 KiB |
File diff suppressed because one or more lines are too long
38
docs/snippets/generator-acr.yaml
Normal file
38
docs/snippets/generator-acr.yaml
Normal file
|
@ -0,0 +1,38 @@
|
|||
apiVersion: generators.external-secrets.io/v1alpha1
|
||||
kind: ACRAccessToken
|
||||
spec:
|
||||
tenantId: 11111111-2222-3333-4444-111111111111
|
||||
registry: example.azurecr.io
|
||||
|
||||
# optional; scope token down to a single repository/action
|
||||
# if set, it will generate an access token instead of an refresh token.
|
||||
scope: "repository:foo:pull,push"
|
||||
|
||||
# Specify Azure cloud type, defaults to PublicCloud.
|
||||
# This is used for authenticating with Azure Active Directory.
|
||||
# available options: PublicCloud, USGovernmentCloud, ChinaCloud, GermanCloud
|
||||
environmentType: "PublicCloud"
|
||||
|
||||
# choose one authentication method
|
||||
auth:
|
||||
|
||||
# option 1: point to a secret that contains a client-id and client-secret
|
||||
servicePrincipal:
|
||||
secretRef:
|
||||
clientSecret:
|
||||
name: az-secret
|
||||
key: clientsecret
|
||||
clientId:
|
||||
name: az-secret
|
||||
key: clientid
|
||||
|
||||
# option 2:
|
||||
managedIdentity:
|
||||
identityId: "xxxxx"
|
||||
|
||||
# option 3:
|
||||
workloadIdentity:
|
||||
# note: you can reference service accounts across namespaces.
|
||||
serviceAccountRef:
|
||||
name: "my-service-account"
|
||||
audiences: []
|
32
docs/snippets/generator-ecr.yaml
Normal file
32
docs/snippets/generator-ecr.yaml
Normal file
|
@ -0,0 +1,32 @@
|
|||
apiVersion: generators.external-secrets.io/v1alpha1
|
||||
kind: ECRAuthorizationToken
|
||||
spec:
|
||||
|
||||
# specify aws region (mandatory)
|
||||
region: eu-west-1
|
||||
|
||||
# assume role with the given authentication credentials
|
||||
role: "my-role"
|
||||
|
||||
# choose an authentication strategy
|
||||
# if no auth strategy is defined it falls back to using
|
||||
# credentials from the environment of the controller.
|
||||
auth:
|
||||
|
||||
# 1: static credentials
|
||||
# point to a secret that contains static credentials
|
||||
# like AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY
|
||||
secretRef:
|
||||
accessKeyIDSecretRef:
|
||||
name: "my-aws-creds"
|
||||
key: "key-id"
|
||||
secretAccessKeySecretRef:
|
||||
name: "my-aws-creds"
|
||||
key: "access-secret"
|
||||
|
||||
# option 2: IAM Roles for Service Accounts
|
||||
# point to a service account that should be used
|
||||
# that is configured for IAM Roles for Service Accounts (IRSA)
|
||||
jwt:
|
||||
serviceAccountRef:
|
||||
name: "oci-token-sync"
|
6
docs/snippets/generator-fake.yaml
Normal file
6
docs/snippets/generator-fake.yaml
Normal file
|
@ -0,0 +1,6 @@
|
|||
apiVersion: generators.external-secrets.io/v1alpha1
|
||||
kind: Fake
|
||||
spec:
|
||||
data:
|
||||
foo: bar
|
||||
baz: bang
|
27
docs/snippets/generator-gcr.yaml
Normal file
27
docs/snippets/generator-gcr.yaml
Normal file
|
@ -0,0 +1,27 @@
|
|||
apiVersion: generators.external-secrets.io/v1alpha1
|
||||
kind: GCRAccessToken
|
||||
spec:
|
||||
# project where gcr lives in
|
||||
projectID: ""
|
||||
|
||||
# choose authentication strategy
|
||||
auth:
|
||||
# option 1: workload identity
|
||||
workloadIdentity:
|
||||
# point to the workload identity
|
||||
# service account
|
||||
serviceAccountRef:
|
||||
name: ""
|
||||
audiences: []
|
||||
# the cluster can live in a different project or location
|
||||
# use the following fields to configure where the cluster lives
|
||||
clusterLocation: ""
|
||||
clusterName: ""
|
||||
clusterProjectID: ""
|
||||
|
||||
|
||||
# option 2: GCP service account
|
||||
secretRef:
|
||||
secretAccessKeySecretRef:
|
||||
name: ""
|
||||
key: ""
|
9
docs/snippets/generator-password.yaml
Normal file
9
docs/snippets/generator-password.yaml
Normal file
|
@ -0,0 +1,9 @@
|
|||
apiVersion: generators.external-secrets.io/v1alpha1
|
||||
kind: Password
|
||||
spec:
|
||||
length: 42
|
||||
digits: 5
|
||||
symbols: 5
|
||||
symbolCharacters: "-_$@"
|
||||
noUpper: false
|
||||
allowRepeat: true
|
|
@ -23,10 +23,11 @@ COPY --from=builder /go/bin/ginkgo /usr/local/bin/
|
|||
COPY --from=builder /usr/local/bin/kubectl /usr/local/bin/
|
||||
COPY --from=builder /usr/local/bin/helm /usr/local/bin/
|
||||
|
||||
COPY entrypoint.sh /entrypoint.sh
|
||||
COPY suites/provider/provider.test /provider.test
|
||||
COPY suites/argocd/argocd.test /argocd.test
|
||||
COPY suites/flux/flux.test /flux.test
|
||||
COPY k8s /k8s
|
||||
COPY entrypoint.sh /entrypoint.sh
|
||||
COPY suites/provider/provider.test /provider.test
|
||||
COPY suites/argocd/argocd.test /argocd.test
|
||||
COPY suites/flux/flux.test /flux.test
|
||||
COPY suites/generator/generator.test /generator.test
|
||||
COPY k8s /k8s
|
||||
|
||||
CMD [ "/entrypoint.sh" ]
|
||||
|
|
|
@ -7,7 +7,7 @@ BUILD_ARGS ?=
|
|||
|
||||
export E2E_IMAGE_NAME ?= ghcr.io/external-secrets/external-secrets-e2e
|
||||
export GINKGO_LABELS ?= !managed
|
||||
export TEST_SUITES ?= provider flux argocd
|
||||
export TEST_SUITES ?= provider generator flux argocd
|
||||
|
||||
start-kind: ## Start kind cluster
|
||||
kind create cluster \
|
||||
|
|
|
@ -155,6 +155,7 @@ func (c *HelmChart) Logs() error {
|
|||
return err
|
||||
}
|
||||
log.Logf("logs: found %d pods", len(podList.Items))
|
||||
tailLines := int64(200)
|
||||
for i := range podList.Items {
|
||||
pod := podList.Items[i]
|
||||
for _, con := range pod.Spec.Containers {
|
||||
|
@ -162,6 +163,7 @@ func (c *HelmChart) Logs() error {
|
|||
resp := kc.CoreV1().Pods(pod.Namespace).GetLogs(pod.Name, &corev1.PodLogOptions{
|
||||
Container: con.Name,
|
||||
Previous: b,
|
||||
TailLines: &tailLines,
|
||||
}).Do(context.TODO())
|
||||
|
||||
err := resp.Error()
|
||||
|
|
|
@ -49,6 +49,10 @@ func NewESO(mutators ...MutationFunc) *ESO {
|
|||
Key: "image.tag",
|
||||
Value: os.Getenv("VERSION"),
|
||||
},
|
||||
{
|
||||
Key: "extraArgs.loglevel",
|
||||
Value: "debug",
|
||||
},
|
||||
{
|
||||
Key: installCRDsVar,
|
||||
Value: "false",
|
||||
|
|
|
@ -71,6 +71,7 @@ func (f *Framework) BeforeEach() {
|
|||
|
||||
// AfterEach deletes the namespace and cleans up the registered addons.
|
||||
func (f *Framework) AfterEach() {
|
||||
addon.PrintLogs()
|
||||
for _, a := range f.Addons {
|
||||
if CurrentSpecReport().Failed() {
|
||||
err := a.Logs()
|
||||
|
|
|
@ -3,7 +3,7 @@ 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
|
||||
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,
|
||||
|
@ -42,6 +42,7 @@ import (
|
|||
|
||||
esv1alpha1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1alpha1"
|
||||
esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
|
||||
genv1alpha1 "github.com/external-secrets/external-secrets/apis/generators/v1alpha1"
|
||||
)
|
||||
|
||||
var Scheme = runtime.NewScheme()
|
||||
|
@ -50,6 +51,7 @@ func init() {
|
|||
_ = scheme.AddToScheme(Scheme)
|
||||
_ = esv1beta1.AddToScheme(Scheme)
|
||||
_ = esv1alpha1.AddToScheme(Scheme)
|
||||
_ = genv1alpha1.AddToScheme(Scheme)
|
||||
_ = argoapp.AddToScheme(Scheme)
|
||||
_ = fluxhelm.AddToScheme(Scheme)
|
||||
_ = fluxsrc.AddToScheme(Scheme)
|
||||
|
|
|
@ -2,8 +2,12 @@ module github.com/external-secrets/external-secrets-e2e
|
|||
|
||||
go 1.18
|
||||
|
||||
replace github.com/external-secrets/external-secrets => ../
|
||||
|
||||
replace (
|
||||
github.com/argoproj/gitops-engine => github.com/argoproj/gitops-engine v0.7.1-0.20220829125054-c036d3f6b0e2
|
||||
github.com/external-secrets/external-secrets v0.0.0 => ../
|
||||
github.com/go-check/check => github.com/go-check/check v0.0.0-20180628173108-788fd7840127
|
||||
k8s.io/api => k8s.io/api v0.24.2
|
||||
k8s.io/apiextensions-apiserver => k8s.io/apiextensions-apiserver v0.24.2
|
||||
k8s.io/apimachinery => k8s.io/apimachinery v0.24.2
|
||||
|
@ -24,7 +28,6 @@ replace (
|
|||
k8s.io/kube-scheduler => k8s.io/kube-scheduler v0.24.2
|
||||
k8s.io/kubectl => k8s.io/kubectl v0.24.2
|
||||
k8s.io/kubelet => k8s.io/kubelet v0.24.2
|
||||
|
||||
k8s.io/kubernetes => k8s.io/kubernetes v1.24.2
|
||||
k8s.io/legacy-cloud-providers => k8s.io/legacy-cloud-providers v0.24.2
|
||||
k8s.io/metrics => k8s.io/metrics v0.24.2
|
||||
|
@ -83,7 +86,7 @@ require (
|
|||
github.com/Microsoft/go-winio v0.4.17 // indirect
|
||||
github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7 // indirect
|
||||
github.com/acomagu/bufpipe v1.0.3 // indirect
|
||||
github.com/argoproj/gitops-engine v0.7.1-0.20220916142200-3951079de199 // indirect
|
||||
github.com/argoproj/gitops-engine v0.7.3 // indirect
|
||||
github.com/argoproj/pkg v0.11.1-0.20211203175135-36c59d8fafe0 // indirect
|
||||
github.com/armon/go-metrics v0.4.0 // indirect
|
||||
github.com/armon/go-radix v1.0.0 // indirect
|
||||
|
@ -105,6 +108,7 @@ require (
|
|||
github.com/fatih/color v1.13.0 // indirect
|
||||
github.com/fluxcd/pkg/apis/acl v0.0.3 // indirect
|
||||
github.com/fluxcd/pkg/apis/kustomize v0.4.1 // indirect
|
||||
github.com/frankban/quicktest v1.14.3 // indirect
|
||||
github.com/fsnotify/fsnotify v1.5.4 // indirect
|
||||
github.com/fvbommel/sortorder v1.0.1 // indirect
|
||||
github.com/ghodss/yaml v1.0.0 // indirect
|
||||
|
|
13
e2e/go.sum
13
e2e/go.sum
|
@ -133,8 +133,8 @@ github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb
|
|||
github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
|
||||
github.com/argoproj/argo-cd/v2 v2.4.8 h1:LwPJr6AS8bQA5RkT6vkvThJcQGmap20ek2QHId5C2Oc=
|
||||
github.com/argoproj/argo-cd/v2 v2.4.8/go.mod h1:G/kCAjTX0SjKs6hE/fE7OGOVSvw30PZWsO0XjbwLq6w=
|
||||
github.com/argoproj/gitops-engine v0.7.1-0.20220916142200-3951079de199 h1:T0DfsMhthuPnfr9toM7vC7QM4NACP7WUPvIKciO3ArA=
|
||||
github.com/argoproj/gitops-engine v0.7.1-0.20220916142200-3951079de199/go.mod h1:WpA/B7tgwfz+sdNE3LqrTrb7ArEY1FOPI2pAGI0hfPc=
|
||||
github.com/argoproj/gitops-engine v0.7.1-0.20220829125054-c036d3f6b0e2 h1:ezScNGHpV3j0qep2sI4VGVmO4s+9+v+NMJct6jWszWs=
|
||||
github.com/argoproj/gitops-engine v0.7.1-0.20220829125054-c036d3f6b0e2/go.mod h1:WpA/B7tgwfz+sdNE3LqrTrb7ArEY1FOPI2pAGI0hfPc=
|
||||
github.com/argoproj/pkg v0.11.1-0.20211203175135-36c59d8fafe0 h1:Cfp7rO/HpVxnwlRqJe0jHiBbZ77ZgXhB6HWlYD02Xdc=
|
||||
github.com/argoproj/pkg v0.11.1-0.20211203175135-36c59d8fafe0/go.mod h1:ra+bQPmbVAoEL+gYSKesuigt4m49i3Qa3mE/xQcjCiA=
|
||||
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
|
||||
|
@ -319,7 +319,8 @@ github.com/form3tech-oss/jwt-go v3.2.3+incompatible/go.mod h1:pbq4aXjuKjdthFRnoD
|
|||
github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4=
|
||||
github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20=
|
||||
github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k=
|
||||
github.com/frankban/quicktest v1.13.0 h1:yNZif1OkDfNoDfb9zZa9aXIpejNR4F23Wely0c+Qdqk=
|
||||
github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE=
|
||||
github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI=
|
||||
|
@ -469,6 +470,7 @@ github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
|||
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-github/v41 v41.0.0 h1:HseJrM2JFf2vfiZJ8anY2hqBjdfY1Vlj/K27ueww4gg=
|
||||
|
@ -664,8 +666,9 @@ github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
|
|||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
||||
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
|
@ -889,6 +892,8 @@ github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzG
|
|||
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
||||
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k=
|
||||
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
|
||||
github.com/rs/zerolog v1.21.0/go.mod h1:ZPhntP/xmq1nnND05hhpAh2QMhSsA4UN3MGZ6O2J3hM=
|
||||
github.com/rubiojr/go-vhd v0.0.0-20200706105327-02e210299021/go.mod h1:DM5xW0nvfNNm2uytzsvhI3OnX8uzaRAg8UX/CnDqbto=
|
||||
|
|
|
@ -52,8 +52,10 @@ kubectl run --rm \
|
|||
--env="GCP_KSA_NAME=${GCP_KSA_NAME:-}" \
|
||||
--env="GCP_GKE_ZONE=${GCP_GKE_ZONE:-}" \
|
||||
--env="GCP_GKE_CLUSTER=${GCP_GKE_CLUSTER:-}" \
|
||||
--env="AWS_REGION=${AWS_REGION:-}" \
|
||||
--env="AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID:-}" \
|
||||
--env="AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY:-}" \
|
||||
--env="AWS_SESSION_TOKEN=${AWS_SESSION_TOKEN:-}" \
|
||||
--env="AWS_SA_NAME=${AWS_SA_NAME:-}" \
|
||||
--env="AWS_SA_NAMESPACE=${AWS_SA_NAMESPACE:-}" \
|
||||
--env="AZURE_CLIENT_ID=${AZURE_CLIENT_ID:-}" \
|
||||
|
|
107
e2e/suites/generator/ecr.go
Normal file
107
e2e/suites/generator/ecr.go
Normal file
|
@ -0,0 +1,107 @@
|
|||
/*
|
||||
Copyright 2020 The cert-manager Authors.
|
||||
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 generator
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
|
||||
//nolint
|
||||
. "github.com/onsi/gomega"
|
||||
|
||||
// nolint
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
|
||||
"github.com/external-secrets/external-secrets-e2e/framework"
|
||||
esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
|
||||
genv1alpha1 "github.com/external-secrets/external-secrets/apis/generators/v1alpha1"
|
||||
esmeta "github.com/external-secrets/external-secrets/apis/meta/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
var _ = Describe("ecr generator", Label("ecr"), func() {
|
||||
f := framework.New("ecr")
|
||||
|
||||
const awsCredsSecretName = "aws-creds"
|
||||
|
||||
injectGenerator := func(tc *testCase) {
|
||||
err := f.CRClient.Create(context.Background(), &v1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: awsCredsSecretName,
|
||||
Namespace: f.Namespace.Name,
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
"akid": []byte(os.Getenv("AWS_ACCESS_KEY_ID")),
|
||||
"sak": []byte(os.Getenv("AWS_SECRET_ACCESS_KEY")),
|
||||
"st": []byte(os.Getenv("AWS_SESSION_TOKEN")),
|
||||
},
|
||||
})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
tc.Generator = &genv1alpha1.ECRAuthorizationToken{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
APIVersion: genv1alpha1.Group + "/" + genv1alpha1.Version,
|
||||
Kind: genv1alpha1.ECRAuthorizationTokenKind,
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: generatorName,
|
||||
Namespace: f.Namespace.Name,
|
||||
},
|
||||
Spec: genv1alpha1.ECRAuthorizationTokenSpec{
|
||||
Region: os.Getenv("AWS_REGION"),
|
||||
Auth: genv1alpha1.AWSAuth{
|
||||
SecretRef: &genv1alpha1.AWSAuthSecretRef{
|
||||
AccessKeyID: esmeta.SecretKeySelector{
|
||||
Name: awsCredsSecretName,
|
||||
Key: "akid",
|
||||
},
|
||||
SecretAccessKey: esmeta.SecretKeySelector{
|
||||
Name: awsCredsSecretName,
|
||||
Key: "sak",
|
||||
},
|
||||
SessionToken: &esmeta.SecretKeySelector{
|
||||
Name: awsCredsSecretName,
|
||||
Key: "st",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
customResourceGenerator := func(tc *testCase) {
|
||||
tc.ExternalSecret.Spec.DataFrom = []esv1beta1.ExternalSecretDataFromRemoteRef{
|
||||
{
|
||||
SourceRef: &esv1beta1.SourceRef{
|
||||
GeneratorRef: &esv1beta1.GeneratorRef{
|
||||
// we don't need to specify the apiVersion,
|
||||
// this should be inferred by the controller.
|
||||
Kind: "ECRAuthorizationToken",
|
||||
Name: generatorName,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
tc.AfterSync = func(secret *v1.Secret) {
|
||||
Expect(string(secret.Data["username"])).To(Equal("AWS"))
|
||||
Expect(string(secret.Data["password"])).ToNot(BeEmpty())
|
||||
}
|
||||
}
|
||||
|
||||
DescribeTable("generate secrets with password generator", generatorTableFunc,
|
||||
Entry("using custom resource generator", f, injectGenerator, customResourceGenerator),
|
||||
)
|
||||
})
|
81
e2e/suites/generator/fake.go
Normal file
81
e2e/suites/generator/fake.go
Normal file
|
@ -0,0 +1,81 @@
|
|||
/*
|
||||
Copyright 2020 The cert-manager Authors.
|
||||
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 generator
|
||||
|
||||
import (
|
||||
|
||||
//nolint
|
||||
. "github.com/onsi/gomega"
|
||||
|
||||
// nolint
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
|
||||
"github.com/external-secrets/external-secrets-e2e/framework"
|
||||
esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
|
||||
genv1alpha1 "github.com/external-secrets/external-secrets/apis/generators/v1alpha1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
var _ = Describe("fake generator", Label("fake"), func() {
|
||||
f := framework.New("fake")
|
||||
|
||||
var (
|
||||
fakeGenData = map[string]string{
|
||||
"foo": "bar",
|
||||
"baz": "bang",
|
||||
}
|
||||
)
|
||||
|
||||
injectGenerator := func(tc *testCase) {
|
||||
tc.Generator = &genv1alpha1.Fake{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
APIVersion: genv1alpha1.Group + "/" + genv1alpha1.Version,
|
||||
Kind: genv1alpha1.FakeKind,
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: generatorName,
|
||||
Namespace: f.Namespace.Name,
|
||||
},
|
||||
Spec: genv1alpha1.FakeSpec{
|
||||
Data: fakeGenData,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
customResourceGenerator := func(tc *testCase) {
|
||||
tc.ExternalSecret.Spec.DataFrom = []esv1beta1.ExternalSecretDataFromRemoteRef{
|
||||
{
|
||||
SourceRef: &esv1beta1.SourceRef{
|
||||
GeneratorRef: &esv1beta1.GeneratorRef{
|
||||
// we don't need to specify the apiVersion,
|
||||
// this should be inferred by the controller.
|
||||
Kind: "Fake",
|
||||
Name: generatorName,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
tc.AfterSync = func(secret *v1.Secret) {
|
||||
for k, v := range fakeGenData {
|
||||
Expect(secret.Data[k]).To(Equal([]byte(v)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DescribeTable("generate secrets with fake generator", generatorTableFunc,
|
||||
Entry("using custom resource generator", f, injectGenerator, customResourceGenerator),
|
||||
)
|
||||
})
|
72
e2e/suites/generator/password.go
Normal file
72
e2e/suites/generator/password.go
Normal file
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
Copyright 2020 The cert-manager Authors.
|
||||
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 generator
|
||||
|
||||
import (
|
||||
|
||||
//nolint
|
||||
. "github.com/onsi/gomega"
|
||||
|
||||
// nolint
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
|
||||
"github.com/external-secrets/external-secrets-e2e/framework"
|
||||
esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
|
||||
genv1alpha1 "github.com/external-secrets/external-secrets/apis/generators/v1alpha1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
var _ = Describe("password generator", Label("password"), func() {
|
||||
f := framework.New("password")
|
||||
|
||||
injectGenerator := func(tc *testCase) {
|
||||
tc.Generator = &genv1alpha1.Password{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
APIVersion: genv1alpha1.Group + "/" + genv1alpha1.Version,
|
||||
Kind: genv1alpha1.PasswordKind,
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: generatorName,
|
||||
Namespace: f.Namespace.Name,
|
||||
},
|
||||
Spec: genv1alpha1.PasswordSpec{
|
||||
Length: 24,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
customResourceGenerator := func(tc *testCase) {
|
||||
tc.ExternalSecret.Spec.DataFrom = []esv1beta1.ExternalSecretDataFromRemoteRef{
|
||||
{
|
||||
SourceRef: &esv1beta1.SourceRef{
|
||||
GeneratorRef: &esv1beta1.GeneratorRef{
|
||||
// we don't need to specify the apiVersion,
|
||||
// this should be inferred by the controller.
|
||||
Kind: "Password",
|
||||
Name: generatorName,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
tc.AfterSync = func(secret *v1.Secret) {
|
||||
Expect(len(string(secret.Data["password"]))).To(Equal(24))
|
||||
}
|
||||
}
|
||||
|
||||
DescribeTable("generate secrets with password generator", generatorTableFunc,
|
||||
Entry("using custom resource generator", f, injectGenerator, customResourceGenerator),
|
||||
)
|
||||
})
|
54
e2e/suites/generator/suite_test.go
Normal file
54
e2e/suites/generator/suite_test.go
Normal file
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
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 generator
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
// nolint
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
// nolint
|
||||
. "github.com/onsi/gomega"
|
||||
|
||||
"github.com/external-secrets/external-secrets-e2e/framework/addon"
|
||||
"github.com/external-secrets/external-secrets-e2e/framework/util"
|
||||
)
|
||||
|
||||
var _ = SynchronizedBeforeSuite(func() []byte {
|
||||
cfg := &addon.Config{}
|
||||
cfg.KubeConfig, cfg.KubeClientSet, cfg.CRClient = util.NewConfig()
|
||||
|
||||
By("installing eso")
|
||||
addon.InstallGlobalAddon(addon.NewESO(addon.WithCRDs()), cfg)
|
||||
|
||||
return nil
|
||||
}, func([]byte) {
|
||||
// noop
|
||||
})
|
||||
|
||||
var _ = SynchronizedAfterSuite(func() {
|
||||
// noop
|
||||
}, func() {
|
||||
By("Cleaning up global addons")
|
||||
addon.UninstallGlobalAddons()
|
||||
if CurrentSpecReport().Failed() {
|
||||
addon.PrintLogs()
|
||||
}
|
||||
})
|
||||
|
||||
func TestE2E(t *testing.T) {
|
||||
NewWithT(t)
|
||||
RegisterFailHandler(Fail)
|
||||
RunSpecs(t, "external-secrets generator e2e suite", Label("generator"))
|
||||
}
|
108
e2e/suites/generator/testcase.go
Normal file
108
e2e/suites/generator/testcase.go
Normal file
|
@ -0,0 +1,108 @@
|
|||
/*
|
||||
Copyright 2020 The cert-manager Authors.
|
||||
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 generator
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
//nolint
|
||||
. "github.com/onsi/gomega"
|
||||
|
||||
// nolint
|
||||
v1 "k8s.io/api/core/v1"
|
||||
|
||||
"github.com/external-secrets/external-secrets-e2e/framework"
|
||||
esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
)
|
||||
|
||||
type testCase struct {
|
||||
Framework *framework.Framework
|
||||
ExternalSecret *esv1beta1.ExternalSecret
|
||||
Generator client.Object
|
||||
AfterSync func(*v1.Secret)
|
||||
}
|
||||
|
||||
var (
|
||||
generatorName = "myfake"
|
||||
)
|
||||
|
||||
func generatorTableFunc(f *framework.Framework, tweaks ...func(*testCase)) {
|
||||
tc := &testCase{
|
||||
Framework: f,
|
||||
ExternalSecret: &esv1beta1.ExternalSecret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "e2e-es",
|
||||
Namespace: f.Namespace.Name,
|
||||
},
|
||||
Spec: esv1beta1.ExternalSecretSpec{
|
||||
RefreshInterval: &metav1.Duration{Duration: time.Second * 5},
|
||||
Target: esv1beta1.ExternalSecretTarget{
|
||||
Name: "generated-secret",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, t := range tweaks {
|
||||
t(tc)
|
||||
}
|
||||
|
||||
err := f.CRClient.Create(context.Background(), tc.Generator)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
err = f.CRClient.Create(context.Background(), tc.ExternalSecret)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
Eventually(func() bool {
|
||||
var es esv1beta1.ExternalSecret
|
||||
err = f.CRClient.Get(context.Background(), types.NamespacedName{
|
||||
Namespace: tc.ExternalSecret.Namespace,
|
||||
Name: tc.ExternalSecret.Name,
|
||||
}, &es)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
cond := getESCond(es.Status, esv1beta1.ExternalSecretReady)
|
||||
if cond == nil || cond.Status != v1.ConditionTrue {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}).WithTimeout(time.Second * 30).Should(BeTrue())
|
||||
|
||||
var secret v1.Secret
|
||||
err = f.CRClient.Get(context.Background(), types.NamespacedName{
|
||||
Namespace: tc.ExternalSecret.Namespace,
|
||||
Name: tc.ExternalSecret.Spec.Target.Name,
|
||||
}, &secret)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
tc.AfterSync(&secret)
|
||||
}
|
||||
|
||||
// getESCond returns the condition with the provided type.
|
||||
func getESCond(status esv1beta1.ExternalSecretStatus, condType esv1beta1.ExternalSecretConditionType) *esv1beta1.ExternalSecretStatusCondition {
|
||||
for i := range status.Conditions {
|
||||
c := status.Conditions[i]
|
||||
if c.Type == condType {
|
||||
return &c
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -52,7 +52,7 @@ func UseMountedIRSAStore(tc *framework.TestCase) {
|
|||
|
||||
// StaticStore is namespaced and references
|
||||
// static credentials from a secret.
|
||||
func SetupStaticStore(f *framework.Framework, kid, sak, region string, serviceType esv1beta1.AWSServiceType) {
|
||||
func SetupStaticStore(f *framework.Framework, kid, sak, st, region string, serviceType esv1beta1.AWSServiceType) {
|
||||
awsCreds := &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: StaticCredentialsSecretName,
|
||||
|
@ -61,6 +61,7 @@ func SetupStaticStore(f *framework.Framework, kid, sak, region string, serviceTy
|
|||
StringData: map[string]string{
|
||||
"kid": kid,
|
||||
"sak": sak,
|
||||
"st": st,
|
||||
},
|
||||
}
|
||||
err := f.CRClient.Create(context.Background(), awsCreds)
|
||||
|
@ -86,6 +87,10 @@ func SetupStaticStore(f *framework.Framework, kid, sak, region string, serviceTy
|
|||
Name: StaticCredentialsSecretName,
|
||||
Key: "sak",
|
||||
},
|
||||
SessionToken: &esmetav1.SecretKeySelector{
|
||||
Name: StaticCredentialsSecretName,
|
||||
Key: "st",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
@ -48,10 +48,10 @@ type Provider struct {
|
|||
framework *framework.Framework
|
||||
}
|
||||
|
||||
func NewProvider(f *framework.Framework, kid, sak, region, saName, saNamespace string) *Provider {
|
||||
func NewProvider(f *framework.Framework, kid, sak, st, region, saName, saNamespace string) *Provider {
|
||||
sess, err := session.NewSessionWithOptions(session.Options{
|
||||
Config: aws.Config{
|
||||
Credentials: credentials.NewStaticCredentials(kid, sak, ""),
|
||||
Credentials: credentials.NewStaticCredentials(kid, sak, st),
|
||||
Region: aws.String(region),
|
||||
},
|
||||
})
|
||||
|
@ -68,7 +68,7 @@ func NewProvider(f *framework.Framework, kid, sak, region, saName, saNamespace s
|
|||
}
|
||||
|
||||
BeforeEach(func() {
|
||||
common.SetupStaticStore(f, kid, sak, region, esv1beta1.AWSServiceParameterStore)
|
||||
common.SetupStaticStore(f, kid, sak, st, region, esv1beta1.AWSServiceParameterStore)
|
||||
prov.SetupReferencedIRSAStore()
|
||||
prov.SetupMountedIRSAStore()
|
||||
})
|
||||
|
@ -89,10 +89,11 @@ func NewProvider(f *framework.Framework, kid, sak, region, saName, saNamespace s
|
|||
func NewFromEnv(f *framework.Framework) *Provider {
|
||||
kid := os.Getenv("AWS_ACCESS_KEY_ID")
|
||||
sak := os.Getenv("AWS_SECRET_ACCESS_KEY")
|
||||
region := "eu-west-1"
|
||||
st := os.Getenv("AWS_SESSION_TOKEN")
|
||||
region := os.Getenv("AWS_REGION")
|
||||
saName := os.Getenv("AWS_SA_NAME")
|
||||
saNamespace := os.Getenv("AWS_SA_NAMESPACE")
|
||||
return NewProvider(f, kid, sak, region, saName, saNamespace)
|
||||
return NewProvider(f, kid, sak, st, region, saName, saNamespace)
|
||||
}
|
||||
|
||||
// CreateSecret creates a secret at the provider.
|
||||
|
|
|
@ -49,10 +49,10 @@ type Provider struct {
|
|||
framework *framework.Framework
|
||||
}
|
||||
|
||||
func NewProvider(f *framework.Framework, kid, sak, region, saName, saNamespace string) *Provider {
|
||||
func NewProvider(f *framework.Framework, kid, sak, st, region, saName, saNamespace string) *Provider {
|
||||
sess, err := session.NewSessionWithOptions(session.Options{
|
||||
Config: aws.Config{
|
||||
Credentials: credentials.NewStaticCredentials(kid, sak, ""),
|
||||
Credentials: credentials.NewStaticCredentials(kid, sak, st),
|
||||
Region: aws.String(region),
|
||||
},
|
||||
})
|
||||
|
@ -69,7 +69,7 @@ func NewProvider(f *framework.Framework, kid, sak, region, saName, saNamespace s
|
|||
}
|
||||
|
||||
BeforeEach(func() {
|
||||
common.SetupStaticStore(f, kid, sak, region, esv1beta1.AWSServiceSecretsManager)
|
||||
common.SetupStaticStore(f, kid, sak, st, region, esv1beta1.AWSServiceSecretsManager)
|
||||
prov.SetupReferencedIRSAStore()
|
||||
prov.SetupMountedIRSAStore()
|
||||
})
|
||||
|
@ -90,10 +90,11 @@ func NewProvider(f *framework.Framework, kid, sak, region, saName, saNamespace s
|
|||
func NewFromEnv(f *framework.Framework) *Provider {
|
||||
kid := os.Getenv("AWS_ACCESS_KEY_ID")
|
||||
sak := os.Getenv("AWS_SECRET_ACCESS_KEY")
|
||||
region := "eu-west-1"
|
||||
st := os.Getenv("AWS_SESSION_TOKEN")
|
||||
region := os.Getenv("AWS_REGION")
|
||||
saName := os.Getenv("AWS_SA_NAME")
|
||||
saNamespace := os.Getenv("AWS_SA_NAMESPACE")
|
||||
return NewProvider(f, kid, sak, region, saName, saNamespace)
|
||||
return NewProvider(f, kid, sak, st, region, saName, saNamespace)
|
||||
}
|
||||
|
||||
// CreateSecret creates a secret at the provider.
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
package tools
|
||||
|
||||
import (
|
||||
_ "github.com/onsi/ginkgo/v2/ginkgo"
|
||||
_ "github.com/onsi/ginkgo/v2/ginkgo/generators"
|
||||
_ "github.com/onsi/ginkgo/v2/ginkgo/internal"
|
||||
_ "github.com/onsi/ginkgo/v2/ginkgo/labels"
|
||||
|
|
5
go.mod
5
go.mod
|
@ -93,12 +93,16 @@ require (
|
|||
require github.com/1Password/connect-sdk-go v1.5.0
|
||||
|
||||
require (
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v0.22.0
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v0.13.2
|
||||
github.com/hashicorp/golang-lru v0.5.4
|
||||
github.com/sethvargo/go-password v0.2.0
|
||||
sigs.k8s.io/yaml v1.3.0
|
||||
)
|
||||
|
||||
require (
|
||||
cloud.google.com/go/compute v1.10.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v0.9.1 // indirect
|
||||
github.com/Azure/go-autorest v14.2.0+incompatible // indirect
|
||||
github.com/Azure/go-autorest/autorest/azure/cli v0.4.6 // indirect
|
||||
github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect
|
||||
|
@ -189,6 +193,7 @@ require (
|
|||
github.com/oklog/ulid v1.3.1 // indirect
|
||||
github.com/opentracing/opentracing-go v1.2.0 // indirect
|
||||
github.com/pierrec/lz4 v2.6.1+incompatible // indirect
|
||||
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/prometheus/common v0.37.0 // indirect
|
||||
|
|
23
go.sum
23
go.sum
|
@ -48,6 +48,14 @@ github.com/1Password/connect-sdk-go v1.5.0 h1:F0WJcLSzGg3iXEDY49/ULdszYKsQLGTzn+
|
|||
github.com/1Password/connect-sdk-go v1.5.0/go.mod h1:TdynFeyvaRoackENbJ8RfJokH+WAowAu1MLmUbdMq6s=
|
||||
github.com/Azure/azure-sdk-for-go v66.0.0+incompatible h1:bmmC38SlE8/E81nNADlgmVGurPWMHDX2YNXVQMrBpEE=
|
||||
github.com/Azure/azure-sdk-for-go v66.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v0.21.0/go.mod h1:fBF9PQNqB8scdgpZ3ufzaLntG0AG7C1WjPMsiFOmfHM=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v0.22.0 h1:zBJcBJwte0x6PcPK7XaWDMvK2o2ZM2f1sMaqNNavQ5g=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v0.22.0/go.mod h1:fBF9PQNqB8scdgpZ3ufzaLntG0AG7C1WjPMsiFOmfHM=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v0.13.2 h1:mM/yraAumqMMIYev6zX0oxHqX6hreUs5wXf76W47r38=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v0.13.2/go.mod h1:+nVKciyKD2J9TyVcEQ82Bo9b+3F92PiQfHrIE/zqLqM=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v0.8.3/go.mod h1:KLF4gFr6DcKFZwSuH8w8yEK6DpFl3LP5rhdvAb7Yz5I=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v0.9.1 h1:sLZ/Y+P/5RRtsXWylBjB5lkgixYfm0MQPiwrSX//JSo=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v0.9.1/go.mod h1:KLF4gFr6DcKFZwSuH8w8yEK6DpFl3LP5rhdvAb7Yz5I=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
|
||||
github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs=
|
||||
github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
|
||||
|
@ -77,6 +85,7 @@ github.com/Azure/go-autorest/logger v0.2.1 h1:IG7i4p/mDa2Ce4TRyAO8IHnVhAVF3RFU+Z
|
|||
github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8=
|
||||
github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo=
|
||||
github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v0.4.0/go.mod h1:Vt9sXTKwMyGcOxSmLDMnGPgqsUg7m8pe215qMLrDXw4=
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v0.7.0 h1:VgSJlZH5u0k2qxSpqyghcFQKmvYckj46uymKK5XzkBM=
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v0.7.0/go.mod h1:BDJ5qMFKx9DugEg3+uQSDCdbYPr5s9vBTrL9P8TpqOU=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
|
@ -196,6 +205,8 @@ github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZm
|
|||
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
|
||||
github.com/dimchansky/utfbom v1.1.1 h1:vV6w1AhK4VMnhBno/TPVCoK9U/LP0PkLCS9tbxHdi/U=
|
||||
github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE=
|
||||
github.com/dnaeon/go-vcr v1.1.0 h1:ReYa/UBrRyQdant9B4fNHGoCNKw6qh6P0fsdGmZpR7c=
|
||||
github.com/dnaeon/go-vcr v1.1.0/go.mod h1:M7tiix8f0r6mKKJ3Yq/kqU1OYf3MnfmBWVbPx/yU9ko=
|
||||
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
|
||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
|
||||
|
@ -299,6 +310,7 @@ github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXP
|
|||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptGaCkuDUx6wNykzzlUixGxvkme+H/lnzb+A=
|
||||
github.com/golang-jwt/jwt v3.2.1+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
|
||||
github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
|
||||
github.com/golang-jwt/jwt/v4 v4.1.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
|
||||
github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
|
||||
|
@ -619,7 +631,9 @@ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lN
|
|||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8=
|
||||
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
|
||||
github.com/montanaflynn/stats v0.6.6/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow=
|
||||
github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||
|
@ -665,6 +679,9 @@ github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR
|
|||
github.com/pierrec/lz4 v2.5.2+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
|
||||
github.com/pierrec/lz4 v2.6.1+incompatible h1:9UY3+iC23yxF0UfGaYrGplQ+79Rg+h/q9FV9ix19jjM=
|
||||
github.com/pierrec/lz4 v2.6.1+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
|
||||
github.com/pkg/browser v0.0.0-20210115035449-ce105d075bb4/go.mod h1:N6UoU20jOqggOuDwUaBQpluzLNDqif3kq9z2wpdYEfQ=
|
||||
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU=
|
||||
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
|
@ -720,6 +737,8 @@ github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFo
|
|||
github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk=
|
||||
github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc=
|
||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
||||
github.com/sethvargo/go-password v0.2.0 h1:BTDl4CC/gjf/axHMaDQtw507ogrXLci6XRiLc7i/UHI=
|
||||
github.com/sethvargo/go-password v0.2.0/go.mod h1:Ym4Mr9JXLBycr02MFuVQ/0JHidNetSgbzutTr3zsYXE=
|
||||
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
|
||||
github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8=
|
||||
github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
|
||||
|
@ -867,6 +886,7 @@ golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPh
|
|||
golang.org/x/crypto v0.0.0-20200414173820-0848c9571904/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
|
@ -955,6 +975,7 @@ golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/
|
|||
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
|
@ -966,6 +987,7 @@ golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLd
|
|||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
|
||||
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20210825183410-e898025ed96a/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
|
@ -1066,6 +1088,7 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
|
|
|
@ -7,6 +7,10 @@ edit_uri: ./edit/main/docs/
|
|||
remote_branch: gh-pages
|
||||
theme:
|
||||
name: material
|
||||
features:
|
||||
- navigation.tabs
|
||||
- navigation.indexes
|
||||
- navigation.expand
|
||||
custom_dir: ../../overrides
|
||||
markdown_extensions:
|
||||
- pymdownx.highlight
|
||||
|
@ -31,19 +35,37 @@ extra:
|
|||
provider: google
|
||||
property: G-QP38TD8K7V
|
||||
nav:
|
||||
- Introduction: index.md
|
||||
- Overview: overview.md
|
||||
- API Types:
|
||||
ExternalSecret: api/externalsecret.md
|
||||
SecretStore: api/secretstore.md
|
||||
ClusterSecretStore: api/clustersecretstore.md
|
||||
ClusterExternalSecret: api/clusterexternalsecret.md
|
||||
- Introduction:
|
||||
- Introduction: index.md
|
||||
- Overview: introduction/overview.md
|
||||
- Getting started: introduction/getting-started.md
|
||||
- FAQ: introduction/faq.md
|
||||
- Stability and Support: introduction/stability-support.md
|
||||
- Deprecation Policy: introduction/deprecation-policy.md
|
||||
- API:
|
||||
- Components: api/components.md
|
||||
- Core Resources:
|
||||
- ExternalSecret: api/externalsecret.md
|
||||
- SecretStore: api/secretstore.md
|
||||
- ClusterSecretStore: api/clustersecretstore.md
|
||||
- ClusterExternalSecret: api/clusterexternalsecret.md
|
||||
- Generators:
|
||||
- "api/generator/index.md"
|
||||
- Azure Container Registry: api/generator/acr.md
|
||||
- AWS Elastic Container Registry: api/generator/ecr.md
|
||||
- Google Container Registry: api/generator/gcr.md
|
||||
- Password: api/generator/password.md
|
||||
- Fake: api/generator/fake.md
|
||||
- Reference Docs:
|
||||
- API specification: api/spec.md
|
||||
- Controller Options: api/controller-options.md
|
||||
- Metrics: api/metrics.md
|
||||
- Guides:
|
||||
- Introduction: guides/introduction.md
|
||||
- Getting started: guides/getting-started.md
|
||||
- Advanced Templating:
|
||||
v2: guides/templating.md
|
||||
v1: guides/templating-v1.md
|
||||
- Generators: guides/generator.md
|
||||
- All keys, One secret: guides/all-keys-one-secret.md
|
||||
- Common K8S Secret Types: guides/common-k8s-secret-types.md
|
||||
- Controller Classes: guides/controller-class.md
|
||||
|
@ -51,54 +73,39 @@ nav:
|
|||
- Decoding Strategies: guides/decoding-strategy.md
|
||||
- Getting Multiple Secrets: guides/getallsecrets.md
|
||||
- Multi Tenancy: guides/multi-tenancy.md
|
||||
- Metrics: guides/metrics.md
|
||||
- Rewriting Keys: guides/datafrom-rewrite.md
|
||||
- Upgrading to v1beta1: guides/v1beta1.md
|
||||
- Using Latest Image: guides/using-latest-image.md
|
||||
- Provider:
|
||||
- AWS:
|
||||
- Secrets Manager: provider/aws-secrets-manager.md
|
||||
- Parameter Store: provider/aws-parameter-store.md
|
||||
- Azure:
|
||||
- Key Vault: provider/azure-key-vault.md
|
||||
|
||||
- Google:
|
||||
- Secret Manager: provider/google-secrets-manager.md
|
||||
- IBM:
|
||||
- Secrets Manager: provider/ibm-secrets-manager.md
|
||||
- AWS Secrets Manager: provider/aws-secrets-manager.md
|
||||
- AWS Parameter Store: provider/aws-parameter-store.md
|
||||
- Azure Key Vault: provider/azure-key-vault.md
|
||||
- Google Secret Manager: provider/google-secrets-manager.md
|
||||
- IBM Secrets Manager: provider/ibm-secrets-manager.md
|
||||
- Akeyless: provider/akeyless.md
|
||||
- HashiCorp Vault: provider/hashicorp-vault.md
|
||||
- Yandex:
|
||||
- Certificate Manager: provider/yandex-certificate-manager.md
|
||||
- Lockbox: provider/yandex-lockbox.md
|
||||
- Gitlab:
|
||||
- Gitlab Project Variables: provider/gitlab-project-variables.md
|
||||
- Oracle:
|
||||
- Oracle Vault: provider/oracle-vault.md
|
||||
- 1Password:
|
||||
- Secrets Automation: provider/1password-automation.md
|
||||
- Yandex Certificate Manager: provider/yandex-certificate-manager.md
|
||||
- Yandex Lockbox: provider/yandex-lockbox.md
|
||||
- Gitlab Project Variables: provider/gitlab-project-variables.md
|
||||
- Oracle Vault: provider/oracle-vault.md
|
||||
- 1Password Secrets Automation: provider/1password-automation.md
|
||||
- Webhook: provider/webhook.md
|
||||
- Fake: provider/fake.md
|
||||
- Kubernetes: provider/kubernetes.md
|
||||
- senhasegura:
|
||||
- DevOps Secrets Management (DSM): provider/senhasegura-dsm.md
|
||||
- senhasegura DevOps Secrets Management (DSM): provider/senhasegura-dsm.md
|
||||
- Doppler: provider/doppler.md
|
||||
- Examples:
|
||||
- FluxCD: examples/gitops-using-fluxcd.md
|
||||
- Anchore Engine: examples/anchore-engine-credentials.md
|
||||
- Jenkins: examples/jenkins-kubernetes-credentials.md
|
||||
- External Resources:
|
||||
- Talks: eso-talks.md
|
||||
- Demos: eso-demos.md
|
||||
- Blogs: eso-blogs.md
|
||||
- References:
|
||||
- API specification: spec.md
|
||||
- Contributing:
|
||||
- Developer guide: contributing/devguide.md
|
||||
- Contributing Process: contributing/process.md
|
||||
- Release Process: contributing/release.md
|
||||
- Code of Conduct: contributing/coc.md
|
||||
- Roadmap: contributing/roadmap.md
|
||||
- FAQ: faq.md
|
||||
- Stability and Support: stability-support.md
|
||||
- Deprecation Policy: deprecation-policy.md
|
||||
- Community:
|
||||
- Contributing:
|
||||
- Developer guide: contributing/devguide.md
|
||||
- Contributing Process: contributing/process.md
|
||||
- Release Process: contributing/release.md
|
||||
- Code of Conduct: contributing/coc.md
|
||||
- Roadmap: contributing/roadmap.md
|
||||
- External Resources:
|
||||
- Talks: eso-talks.md
|
||||
- Demos: eso-demos.md
|
||||
- Blogs: eso-blogs.md
|
||||
|
|
|
@ -8,7 +8,7 @@ BUNDLE_YAML="${BUNDLE_DIR}/bundle.yaml"
|
|||
|
||||
cd "${SCRIPT_DIR}"/../
|
||||
|
||||
go run sigs.k8s.io/controller-tools/cmd/controller-gen \
|
||||
go run sigs.k8s.io/controller-tools/cmd/controller-gen object \
|
||||
object:headerFile="hack/boilerplate.go.txt" \
|
||||
paths="./apis/..."
|
||||
go run sigs.k8s.io/controller-tools/cmd/controller-gen crd \
|
||||
|
|
256
pkg/controllers/externalsecret/clientmanager/client_manager.go
Normal file
256
pkg/controllers/externalsecret/clientmanager/client_manager.go
Normal file
|
@ -0,0 +1,256 @@
|
|||
/*
|
||||
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 clientmanager
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/go-logr/logr"
|
||||
"golang.org/x/exp/slices"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
|
||||
"github.com/external-secrets/external-secrets/pkg/controllers/secretstore"
|
||||
)
|
||||
|
||||
const (
|
||||
errGetClusterSecretStore = "could not get ClusterSecretStore %q, %w"
|
||||
errGetSecretStore = "could not get SecretStore %q, %w"
|
||||
errSecretStoreNotReady = "the desired SecretStore %s is not ready"
|
||||
errClusterStoreMismatch = "using cluster store %q is not allowed from namespace %q: denied by spec.condition"
|
||||
)
|
||||
|
||||
// Manager stores instances of provider clients
|
||||
// At any given time we must have no more than one instance
|
||||
// of a client (due to limitations in GCP / see mutexlock there)
|
||||
// If the controller requests another instance of a given client
|
||||
// we will close the old client first and then construct a new one.
|
||||
type Manager struct {
|
||||
log logr.Logger
|
||||
client client.Client
|
||||
controllerClass string
|
||||
enableFloodgate bool
|
||||
|
||||
// store clients by provider type
|
||||
clientMap map[clientKey]*clientVal
|
||||
}
|
||||
|
||||
type clientKey struct {
|
||||
providerType string
|
||||
}
|
||||
|
||||
type clientVal struct {
|
||||
client esv1beta1.SecretsClient
|
||||
store esv1beta1.GenericStore
|
||||
}
|
||||
|
||||
// New constructs a new manager with defaults.
|
||||
func New(ctrlClient client.Client, controllerClass string, enableFloodgate bool) *Manager {
|
||||
log := ctrl.Log.WithName("clientmanager")
|
||||
return &Manager{
|
||||
log: log,
|
||||
client: ctrlClient,
|
||||
controllerClass: controllerClass,
|
||||
enableFloodgate: enableFloodgate,
|
||||
clientMap: make(map[clientKey]*clientVal),
|
||||
}
|
||||
}
|
||||
|
||||
// Get returns a provider client from the given storeRef or sourceRef.secretStoreRef
|
||||
// while sourceRef.SecretStoreRef takes precedence over storeRef.
|
||||
// Do not close the client returned from this func, instead close
|
||||
// the manager once you're done with recinciling the external secret.
|
||||
func (m *Manager) Get(ctx context.Context, storeRef esv1beta1.SecretStoreRef, namespace string, sourceRef *esv1beta1.SourceRef) (esv1beta1.SecretsClient, error) {
|
||||
if sourceRef != nil && sourceRef.SecretStoreRef != nil {
|
||||
storeRef = *sourceRef.SecretStoreRef
|
||||
}
|
||||
store, err := m.getStore(ctx, &storeRef, namespace)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// check if store should be handled by this controller instance
|
||||
if !secretstore.ShouldProcessStore(store, m.controllerClass) {
|
||||
return nil, fmt.Errorf("can not reference unmanaged store")
|
||||
}
|
||||
// when using ClusterSecretStore, validate the ClusterSecretStore namespace conditions
|
||||
shouldProcess, err := m.shouldProcessSecret(store, namespace)
|
||||
if err != nil || !shouldProcess {
|
||||
if err == nil && !shouldProcess {
|
||||
err = fmt.Errorf(errClusterStoreMismatch, store.GetName(), namespace)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
if m.enableFloodgate {
|
||||
err := assertStoreIsUsable(store)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
storeProvider, err := esv1beta1.GetProvider(store)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
secretClient := m.getStoredClient(ctx, storeProvider, store)
|
||||
if secretClient != nil {
|
||||
return secretClient, nil
|
||||
}
|
||||
m.log.V(1).Info("creating new client",
|
||||
"provider", fmt.Sprintf("%T", storeProvider),
|
||||
"store", fmt.Sprintf("%s/%s", store.GetNamespace(), store.GetName()))
|
||||
// secret client is created only if we are going to refresh
|
||||
// this skip an unnecessary check/request in the case we are not going to do anything
|
||||
secretClient, err = storeProvider.NewClient(ctx, store, m.client, namespace)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
idx := storeKey(storeProvider)
|
||||
m.clientMap[idx] = &clientVal{
|
||||
client: secretClient,
|
||||
store: store,
|
||||
}
|
||||
return secretClient, nil
|
||||
}
|
||||
|
||||
// returns a previously stored client from the cache if store and store-version match
|
||||
// if a client exists for the same provider which points to a different store or store version
|
||||
// it will be cleaned up.
|
||||
func (m *Manager) getStoredClient(ctx context.Context, storeProvider esv1beta1.Provider, store esv1beta1.GenericStore) esv1beta1.SecretsClient {
|
||||
idx := storeKey(storeProvider)
|
||||
val, ok := m.clientMap[idx]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
storeName := fmt.Sprintf("%s/%s", store.GetNamespace(), store.GetName())
|
||||
// return client if it points to the very same store
|
||||
if val.store.GetObjectMeta().Generation == store.GetGeneration() &&
|
||||
val.store.GetTypeMeta().Kind == store.GetTypeMeta().Kind &&
|
||||
val.store.GetName() == store.GetName() &&
|
||||
val.store.GetNamespace() == store.GetNamespace() {
|
||||
m.log.V(1).Info("reusing stored client",
|
||||
"provider", fmt.Sprintf("%T", storeProvider),
|
||||
"store", storeName)
|
||||
return val.client
|
||||
}
|
||||
m.log.V(1).Info("cleaning up client",
|
||||
"provider", fmt.Sprintf("%T", storeProvider),
|
||||
"store", storeName)
|
||||
// if we have a client but it points to a different store
|
||||
// we must clean it up
|
||||
val.client.Close(ctx)
|
||||
delete(m.clientMap, idx)
|
||||
return nil
|
||||
}
|
||||
|
||||
func storeKey(storeProvider esv1beta1.Provider) clientKey {
|
||||
return clientKey{
|
||||
providerType: fmt.Sprintf("%T", storeProvider),
|
||||
}
|
||||
}
|
||||
|
||||
// getStore fetches the (Cluster)SecretStore from the kube-apiserver
|
||||
// and returns a GenericStore representing it.
|
||||
func (m *Manager) getStore(ctx context.Context, storeRef *esv1beta1.SecretStoreRef, namespace string) (esv1beta1.GenericStore, error) {
|
||||
ref := types.NamespacedName{
|
||||
Name: storeRef.Name,
|
||||
}
|
||||
if storeRef.Kind == esv1beta1.ClusterSecretStoreKind {
|
||||
var store esv1beta1.ClusterSecretStore
|
||||
err := m.client.Get(ctx, ref, &store)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(errGetClusterSecretStore, ref.Name, err)
|
||||
}
|
||||
return &store, nil
|
||||
}
|
||||
ref.Namespace = namespace
|
||||
var store esv1beta1.SecretStore
|
||||
err := m.client.Get(ctx, ref, &store)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(errGetSecretStore, ref.Name, err)
|
||||
}
|
||||
return &store, nil
|
||||
}
|
||||
|
||||
// Close cleans up all clients.
|
||||
func (m *Manager) Close(ctx context.Context) error {
|
||||
var errs []string
|
||||
for key, val := range m.clientMap {
|
||||
err := val.client.Close(ctx)
|
||||
if err != nil {
|
||||
errs = append(errs, err.Error())
|
||||
}
|
||||
delete(m.clientMap, key)
|
||||
}
|
||||
if len(errs) != 0 {
|
||||
return fmt.Errorf("errors while closing clients: %s", strings.Join(errs, ", "))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Manager) shouldProcessSecret(store esv1beta1.GenericStore, ns string) (bool, error) {
|
||||
if store.GetKind() != esv1beta1.ClusterSecretStoreKind {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
if len(store.GetSpec().Conditions) == 0 {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
namespaceList := &v1.NamespaceList{}
|
||||
|
||||
for _, condition := range store.GetSpec().Conditions {
|
||||
if condition.NamespaceSelector != nil {
|
||||
namespaceSelector, err := metav1.LabelSelectorAsSelector(condition.NamespaceSelector)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if err := m.client.List(context.Background(), namespaceList, client.MatchingLabelsSelector{Selector: namespaceSelector}); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
for _, namespace := range namespaceList.Items {
|
||||
if namespace.GetName() == ns {
|
||||
return true, nil // namespace matches the labelselector
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if condition.Namespaces != nil {
|
||||
if slices.Contains(condition.Namespaces, ns) {
|
||||
return true, nil // namespace in the namespaces list
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// assertStoreIsUsable assert that the store is ready to use.
|
||||
func assertStoreIsUsable(store esv1beta1.GenericStore) error {
|
||||
if store == nil {
|
||||
return nil
|
||||
}
|
||||
condition := secretstore.GetSecretStoreCondition(store.GetStatus(), esv1beta1.SecretStoreReady)
|
||||
if condition == nil || condition.Status != v1.ConditionTrue {
|
||||
return fmt.Errorf(errSecretStoreNotReady, store.GetName())
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,366 @@
|
|||
/*
|
||||
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 clientmanager
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/go-logr/logr"
|
||||
"github.com/stretchr/testify/assert"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
fakeclient "sigs.k8s.io/controller-runtime/pkg/client/fake"
|
||||
|
||||
esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
|
||||
)
|
||||
|
||||
func TestManagerGet(t *testing.T) {
|
||||
scheme := runtime.NewScheme()
|
||||
_ = clientgoscheme.AddToScheme(scheme)
|
||||
_ = esv1beta1.AddToScheme(scheme)
|
||||
_ = apiextensionsv1.AddToScheme(scheme)
|
||||
|
||||
// We have a test provider to control
|
||||
// the behavior of the NewClient func.
|
||||
fakeProvider := &WrapProvider{}
|
||||
esv1beta1.ForceRegister(fakeProvider, &esv1beta1.SecretStoreProvider{
|
||||
Fake: &esv1beta1.FakeProvider{},
|
||||
})
|
||||
|
||||
// fake clients are re-used to compare the
|
||||
// in-memory reference
|
||||
clientA := &FakeClient{id: "1"}
|
||||
clientB := &FakeClient{id: "2"}
|
||||
|
||||
const testNamespace = "foo"
|
||||
|
||||
readyStatus := esv1beta1.SecretStoreStatus{
|
||||
Conditions: []esv1beta1.SecretStoreStatusCondition{
|
||||
{
|
||||
Type: esv1beta1.SecretStoreReady,
|
||||
Status: corev1.ConditionTrue,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
fakeSpec := esv1beta1.SecretStoreSpec{
|
||||
Provider: &esv1beta1.SecretStoreProvider{
|
||||
Fake: &esv1beta1.FakeProvider{
|
||||
Data: []esv1beta1.FakeProviderData{
|
||||
{
|
||||
Key: "foo",
|
||||
Value: "bar",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
defaultStore := &esv1beta1.SecretStore{
|
||||
TypeMeta: metav1.TypeMeta{Kind: esv1beta1.SecretStoreKind},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "foo",
|
||||
Namespace: testNamespace,
|
||||
},
|
||||
Spec: fakeSpec,
|
||||
Status: readyStatus,
|
||||
}
|
||||
|
||||
otherStore := &esv1beta1.SecretStore{
|
||||
TypeMeta: metav1.TypeMeta{Kind: esv1beta1.SecretStoreKind},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "other",
|
||||
Namespace: testNamespace,
|
||||
},
|
||||
Spec: fakeSpec,
|
||||
Status: readyStatus,
|
||||
}
|
||||
|
||||
var mgr *Manager
|
||||
|
||||
provKey := clientKey{
|
||||
providerType: "*clientmanager.WrapProvider",
|
||||
}
|
||||
|
||||
type fields struct {
|
||||
client client.Client
|
||||
clientMap map[clientKey]*clientVal
|
||||
}
|
||||
type args struct {
|
||||
storeRef esv1beta1.SecretStoreRef
|
||||
namespace string
|
||||
sourceRef *esv1beta1.SourceRef
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
clientConstructor func(
|
||||
ctx context.Context,
|
||||
store esv1beta1.GenericStore,
|
||||
kube client.Client,
|
||||
namespace string) (esv1beta1.SecretsClient, error)
|
||||
verify func(esv1beta1.SecretsClient)
|
||||
afterClose func()
|
||||
want esv1beta1.SecretsClient
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "creates a new client from storeRef and stores it",
|
||||
wantErr: false,
|
||||
fields: fields{
|
||||
client: fakeclient.NewClientBuilder().
|
||||
WithScheme(scheme).
|
||||
WithObjects(defaultStore).
|
||||
Build(),
|
||||
clientMap: make(map[clientKey]*clientVal),
|
||||
},
|
||||
args: args{
|
||||
storeRef: esv1beta1.SecretStoreRef{
|
||||
Name: defaultStore.Name,
|
||||
Kind: esv1beta1.SecretStoreKind,
|
||||
},
|
||||
namespace: defaultStore.Namespace,
|
||||
sourceRef: nil,
|
||||
},
|
||||
clientConstructor: func(ctx context.Context, store esv1beta1.GenericStore, kube client.Client, namespace string) (esv1beta1.SecretsClient, error) {
|
||||
return clientA, nil
|
||||
},
|
||||
verify: func(sc esv1beta1.SecretsClient) {
|
||||
// we now must have this provider in the clientMap
|
||||
// and it mustbe the client defined in clientConstructor
|
||||
assert.NotNil(t, sc)
|
||||
c, ok := mgr.clientMap[provKey]
|
||||
assert.True(t, ok)
|
||||
assert.Same(t, c.client, clientA)
|
||||
},
|
||||
|
||||
afterClose: func() {
|
||||
v, ok := mgr.clientMap[provKey]
|
||||
assert.False(t, ok)
|
||||
assert.Nil(t, v)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "creates a new client using both storeRef and sourceRef",
|
||||
wantErr: false,
|
||||
fields: fields{
|
||||
client: fakeclient.NewClientBuilder().
|
||||
WithScheme(scheme).
|
||||
WithObjects(otherStore).
|
||||
Build(),
|
||||
clientMap: make(map[clientKey]*clientVal),
|
||||
},
|
||||
args: args{
|
||||
storeRef: esv1beta1.SecretStoreRef{
|
||||
Name: defaultStore.Name,
|
||||
Kind: esv1beta1.SecretStoreKind,
|
||||
},
|
||||
// this should take precedence
|
||||
sourceRef: &esv1beta1.SourceRef{
|
||||
SecretStoreRef: &esv1beta1.SecretStoreRef{
|
||||
Name: otherStore.Name,
|
||||
Kind: esv1beta1.SecretStoreKind,
|
||||
},
|
||||
},
|
||||
namespace: defaultStore.Namespace,
|
||||
},
|
||||
clientConstructor: func(ctx context.Context, store esv1beta1.GenericStore, kube client.Client, namespace string) (esv1beta1.SecretsClient, error) {
|
||||
return clientB, nil
|
||||
},
|
||||
verify: func(sc esv1beta1.SecretsClient) {
|
||||
// we now must have this provider in the clientMap
|
||||
// and it mustbe the client defined in clientConstructor
|
||||
assert.NotNil(t, sc)
|
||||
c, ok := mgr.clientMap[provKey]
|
||||
assert.True(t, ok)
|
||||
assert.Same(t, c.client, clientB)
|
||||
},
|
||||
|
||||
afterClose: func() {
|
||||
v, ok := mgr.clientMap[provKey]
|
||||
assert.False(t, ok)
|
||||
assert.True(t, clientB.closeCalled)
|
||||
assert.Nil(t, v)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "retrieve cached client when store matches",
|
||||
wantErr: false,
|
||||
fields: fields{
|
||||
client: fakeclient.NewClientBuilder().
|
||||
WithScheme(scheme).
|
||||
WithObjects(defaultStore).
|
||||
Build(),
|
||||
clientMap: map[clientKey]*clientVal{
|
||||
provKey: {
|
||||
client: clientA,
|
||||
store: defaultStore,
|
||||
},
|
||||
},
|
||||
},
|
||||
args: args{
|
||||
storeRef: esv1beta1.SecretStoreRef{
|
||||
Name: defaultStore.Name,
|
||||
Kind: esv1beta1.SecretStoreKind,
|
||||
},
|
||||
namespace: defaultStore.Namespace,
|
||||
sourceRef: nil,
|
||||
},
|
||||
clientConstructor: func(ctx context.Context, store esv1beta1.GenericStore, kube client.Client, namespace string) (esv1beta1.SecretsClient, error) {
|
||||
// constructor should not be called,
|
||||
// the client from the cache should be returned instead
|
||||
t.Fail()
|
||||
return nil, nil
|
||||
},
|
||||
verify: func(sc esv1beta1.SecretsClient) {
|
||||
// verify that the secretsClient is the one from cache
|
||||
assert.NotNil(t, sc)
|
||||
c, ok := mgr.clientMap[provKey]
|
||||
assert.True(t, ok)
|
||||
assert.Same(t, c.client, clientA)
|
||||
assert.Same(t, sc, clientA)
|
||||
},
|
||||
|
||||
afterClose: func() {
|
||||
v, ok := mgr.clientMap[provKey]
|
||||
assert.False(t, ok)
|
||||
assert.True(t, clientA.closeCalled)
|
||||
assert.Nil(t, v)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "create new client when store doesn't match",
|
||||
wantErr: false,
|
||||
fields: fields{
|
||||
client: fakeclient.NewClientBuilder().
|
||||
WithScheme(scheme).
|
||||
WithObjects(otherStore).
|
||||
Build(),
|
||||
clientMap: map[clientKey]*clientVal{
|
||||
provKey: {
|
||||
// we have clientA in cache pointing at defaultStore
|
||||
client: clientA,
|
||||
store: defaultStore,
|
||||
},
|
||||
},
|
||||
},
|
||||
args: args{
|
||||
storeRef: esv1beta1.SecretStoreRef{
|
||||
Name: otherStore.Name,
|
||||
Kind: esv1beta1.SecretStoreKind,
|
||||
},
|
||||
namespace: otherStore.Namespace,
|
||||
sourceRef: nil,
|
||||
},
|
||||
clientConstructor: func(ctx context.Context, store esv1beta1.GenericStore, kube client.Client, namespace string) (esv1beta1.SecretsClient, error) {
|
||||
// because there is a store mismatch
|
||||
// we create a new client
|
||||
return clientB, nil
|
||||
},
|
||||
verify: func(sc esv1beta1.SecretsClient) {
|
||||
// verify that SecretsClient is NOT the one from cache
|
||||
assert.NotNil(t, sc)
|
||||
c, ok := mgr.clientMap[provKey]
|
||||
assert.True(t, ok)
|
||||
assert.Same(t, c.client, clientB)
|
||||
assert.Same(t, sc, clientB)
|
||||
assert.True(t, clientA.closeCalled)
|
||||
},
|
||||
afterClose: func() {
|
||||
v, ok := mgr.clientMap[provKey]
|
||||
assert.False(t, ok)
|
||||
assert.True(t, clientB.closeCalled)
|
||||
assert.Nil(t, v)
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
mgr = &Manager{
|
||||
log: logr.Discard(),
|
||||
client: tt.fields.client,
|
||||
enableFloodgate: true,
|
||||
clientMap: tt.fields.clientMap,
|
||||
}
|
||||
fakeProvider.newClientFunc = tt.clientConstructor
|
||||
clientA.closeCalled = false
|
||||
clientB.closeCalled = false
|
||||
got, err := mgr.Get(context.Background(), tt.args.storeRef, tt.args.namespace, tt.args.sourceRef)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("Manager.Get() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
tt.verify(got)
|
||||
mgr.Close(context.Background())
|
||||
tt.afterClose()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type WrapProvider struct {
|
||||
newClientFunc func(
|
||||
context.Context,
|
||||
esv1beta1.GenericStore,
|
||||
client.Client,
|
||||
string) (esv1beta1.SecretsClient, error)
|
||||
}
|
||||
|
||||
// NewClient constructs a SecretsManager Provider.
|
||||
func (f *WrapProvider) NewClient(
|
||||
ctx context.Context,
|
||||
store esv1beta1.GenericStore,
|
||||
kube client.Client,
|
||||
namespace string) (esv1beta1.SecretsClient, error) {
|
||||
return f.newClientFunc(ctx, store, kube, namespace)
|
||||
}
|
||||
|
||||
// ValidateStore checks if the provided store is valid.
|
||||
func (f *WrapProvider) ValidateStore(store esv1beta1.GenericStore) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type FakeClient struct {
|
||||
id string
|
||||
closeCalled bool
|
||||
}
|
||||
|
||||
func (c *FakeClient) GetSecret(ctx context.Context, ref esv1beta1.ExternalSecretDataRemoteRef) ([]byte, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (c *FakeClient) Validate() (esv1beta1.ValidationResult, error) {
|
||||
return esv1beta1.ValidationResultReady, nil
|
||||
}
|
||||
|
||||
// GetSecretMap returns multiple k/v pairs from the provider.
|
||||
func (c *FakeClient) GetSecretMap(ctx context.Context, ref esv1beta1.ExternalSecretDataRemoteRef) (map[string][]byte, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// GetAllSecrets returns multiple k/v pairs from the provider.
|
||||
func (c *FakeClient) GetAllSecrets(ctx context.Context, ref esv1beta1.ExternalSecretFind) (map[string][]byte, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (c *FakeClient) Close(ctx context.Context) error {
|
||||
c.closeCalled = true
|
||||
return nil
|
||||
}
|
|
@ -17,20 +17,19 @@ package externalsecret
|
|||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-logr/logr"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"golang.org/x/exp/slices"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/equality"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/tools/record"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/builder"
|
||||
|
@ -39,45 +38,43 @@ import (
|
|||
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
|
||||
|
||||
esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
|
||||
"github.com/external-secrets/external-secrets/pkg/controllers/secretstore"
|
||||
// Loading registered generators.
|
||||
_ "github.com/external-secrets/external-secrets/pkg/generator/register"
|
||||
// Loading registered providers.
|
||||
_ "github.com/external-secrets/external-secrets/pkg/provider/register"
|
||||
"github.com/external-secrets/external-secrets/pkg/utils"
|
||||
)
|
||||
|
||||
const (
|
||||
requeueAfter = time.Second * 30
|
||||
fieldOwnerTemplate = "externalsecrets.external-secrets.io/%v"
|
||||
errGetES = "could not get ExternalSecret"
|
||||
errConvert = "could not apply conversion strategy to keys: %v"
|
||||
errDecode = "could not apply decoding strategy to %v[%d]: %v"
|
||||
errRewrite = "could not rewrite spec.dataFrom[%d]: %v"
|
||||
errInvalidKeys = "secret keys from spec.dataFrom.%v[%d] can only have alphanumeric,'-', '_' or '.' characters. Convert them using rewrite (https://external-secrets.io/latest/guides-datafrom-rewrite)"
|
||||
errUpdateSecret = "could not update Secret"
|
||||
errPatchStatus = "unable to patch status"
|
||||
errGetSecretStore = "could not get SecretStore %q, %w"
|
||||
errSecretStoreNotReady = "the desired SecretStore %s is not ready"
|
||||
errGetClusterSecretStore = "could not get ClusterSecretStore %q, %w"
|
||||
errStoreRef = "could not get store reference"
|
||||
errStoreUsability = "could not use store reference"
|
||||
errStoreProvider = "could not get store provider"
|
||||
errStoreClient = "could not get provider client"
|
||||
errGetExistingSecret = "could not get existing secret: %w"
|
||||
errClusterStoreMismatch = "using cluster store %q is not allowed from namespace %q: denied by spec.condition"
|
||||
errCloseStoreClient = "could not close provider client"
|
||||
errSetCtrlReference = "could not set ExternalSecret controller reference: %w"
|
||||
errFetchTplFrom = "error fetching templateFrom data: %w"
|
||||
errGetSecretData = "could not get secret data from provider"
|
||||
errDeleteSecret = "could not delete secret"
|
||||
errApplyTemplate = "could not apply template: %w"
|
||||
errExecTpl = "could not execute template: %w"
|
||||
errInvalidCreatePolicy = "invalid creationPolicy=%s. Can not delete secret i do not own"
|
||||
errPolicyMergeNotFound = "the desired secret %s was not found. With creationPolicy=Merge the secret won't be created"
|
||||
errPolicyMergeGetSecret = "unable to get secret %s: %w"
|
||||
errPolicyMergeMutate = "unable to mutate secret %s: %w"
|
||||
errPolicyMergePatch = "unable to patch secret %s: %w"
|
||||
errTplCMMissingKey = "error in configmap %s: missing key %s"
|
||||
errTplSecMissingKey = "error in secret %s: missing key %s"
|
||||
requeueAfter = time.Second * 30
|
||||
fieldOwnerTemplate = "externalsecrets.external-secrets.io/%v"
|
||||
errGetES = "could not get ExternalSecret"
|
||||
errConvert = "could not apply conversion strategy to keys: %v"
|
||||
errDecode = "could not apply decoding strategy to %v[%d]: %v"
|
||||
errGenerate = "could not generate [%d]: %w"
|
||||
errRewrite = "could not rewrite spec.dataFrom[%d]: %v"
|
||||
errInvalidKeys = "secret keys from spec.dataFrom.%v[%d] can only have alphanumeric,'-', '_' or '.' characters. Convert them using rewrite (https://external-secrets.io/latest/guides-datafrom-rewrite)"
|
||||
errUpdateSecret = "could not update Secret"
|
||||
errPatchStatus = "unable to patch status"
|
||||
errStoreRef = "could not get store reference"
|
||||
errStoreUsability = "could not use store reference"
|
||||
errStoreProvider = "could not get store provider"
|
||||
errStoreClient = "could not get provider client"
|
||||
errGetExistingSecret = "could not get existing secret: %w"
|
||||
errCloseStoreClient = "could not close provider client"
|
||||
errSetCtrlReference = "could not set ExternalSecret controller reference: %w"
|
||||
errFetchTplFrom = "error fetching templateFrom data: %w"
|
||||
errGetSecretData = "could not get secret data from provider"
|
||||
errDeleteSecret = "could not delete secret"
|
||||
errApplyTemplate = "could not apply template: %w"
|
||||
errExecTpl = "could not execute template: %w"
|
||||
errInvalidCreatePolicy = "invalid creationPolicy=%s. Can not delete secret i do not own"
|
||||
errPolicyMergeNotFound = "the desired secret %s was not found. With creationPolicy=Merge the secret won't be created"
|
||||
errPolicyMergeGetSecret = "unable to get secret %s: %w"
|
||||
errPolicyMergeMutate = "unable to mutate secret %s: %w"
|
||||
errPolicyMergePatch = "unable to patch secret %s: %w"
|
||||
errTplCMMissingKey = "error in configmap %s: missing key %s"
|
||||
errTplSecMissingKey = "error in secret %s: missing key %s"
|
||||
)
|
||||
|
||||
// Reconciler reconciles a ExternalSecret object.
|
||||
|
@ -85,6 +82,7 @@ type Reconciler struct {
|
|||
client.Client
|
||||
Log logr.Logger
|
||||
Scheme *runtime.Scheme
|
||||
RestConfig *rest.Config
|
||||
ControllerClass string
|
||||
RequeueInterval time.Duration
|
||||
ClusterSecretStoreEnabled bool
|
||||
|
@ -131,6 +129,13 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu
|
|||
return ctrl.Result{}, nil
|
||||
}
|
||||
|
||||
// skip when pointing to an unmanaged store
|
||||
skip, err := shouldSkipUnmanagedStore(ctx, req.Namespace, r, externalSecret)
|
||||
if skip {
|
||||
log.Info("skipping unmanaged store as it points to a unmanaged controllerClass")
|
||||
return ctrl.Result{}, nil
|
||||
}
|
||||
|
||||
// patch status when done processing
|
||||
p := client.MergeFrom(externalSecret.DeepCopy())
|
||||
defer func() {
|
||||
|
@ -140,57 +145,6 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu
|
|||
}
|
||||
}()
|
||||
|
||||
store, err := r.getStore(ctx, &externalSecret)
|
||||
if err != nil {
|
||||
log.Error(err, errStoreRef)
|
||||
r.recorder.Event(&externalSecret, v1.EventTypeWarning, esv1beta1.ReasonInvalidStoreRef, err.Error())
|
||||
conditionSynced := NewExternalSecretCondition(esv1beta1.ExternalSecretReady, v1.ConditionFalse, esv1beta1.ConditionReasonSecretSyncedError, errStoreRef)
|
||||
SetExternalSecretCondition(&externalSecret, *conditionSynced)
|
||||
syncCallsError.With(syncCallsMetricLabels).Inc()
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
|
||||
log = log.WithValues("SecretStore", store.GetNamespacedName())
|
||||
|
||||
// check if store should be handled by this controller instance
|
||||
if !secretstore.ShouldProcessStore(store, r.ControllerClass) {
|
||||
log.Info("skipping unmanaged store")
|
||||
return ctrl.Result{}, nil
|
||||
}
|
||||
|
||||
// when using ClusterSecretStore, validate the ClusterSecretStore namespace conditions
|
||||
shouldProcess, err := r.ShouldProcessSecret(ctx, store, externalSecret.Namespace)
|
||||
if err != nil || !shouldProcess {
|
||||
if err == nil && !shouldProcess {
|
||||
err = fmt.Errorf(errClusterStoreMismatch, store.GetName(), externalSecret.Namespace)
|
||||
}
|
||||
|
||||
log.Error(err, err.Error())
|
||||
r.recorder.Event(&externalSecret, v1.EventTypeWarning, esv1beta1.ReasonInvalidStoreRef, err.Error())
|
||||
conditionSynced := NewExternalSecretCondition(esv1beta1.ExternalSecretReady, v1.ConditionFalse, esv1beta1.ConditionReasonSecretSyncedError, errStoreUsability)
|
||||
SetExternalSecretCondition(&externalSecret, *conditionSynced)
|
||||
syncCallsError.With(syncCallsMetricLabels).Inc()
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
|
||||
if r.EnableFloodGate {
|
||||
if err = assertStoreIsUsable(store); err != nil {
|
||||
log.Error(err, errStoreUsability)
|
||||
r.recorder.Event(&externalSecret, v1.EventTypeWarning, esv1beta1.ReasonUnavailableStore, err.Error())
|
||||
conditionSynced := NewExternalSecretCondition(esv1beta1.ExternalSecretReady, v1.ConditionFalse, esv1beta1.ConditionReasonSecretSyncedError, errStoreUsability)
|
||||
SetExternalSecretCondition(&externalSecret, *conditionSynced)
|
||||
syncCallsError.With(syncCallsMetricLabels).Inc()
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
}
|
||||
|
||||
storeProvider, err := esv1beta1.GetProvider(store)
|
||||
if err != nil {
|
||||
log.Error(err, errStoreProvider)
|
||||
syncCallsError.With(syncCallsMetricLabels).Inc()
|
||||
return ctrl.Result{RequeueAfter: requeueAfter}, nil
|
||||
}
|
||||
|
||||
refreshInt := r.RequeueInterval
|
||||
if externalSecret.Spec.RefreshInterval != nil {
|
||||
refreshInt = externalSecret.Spec.RefreshInterval.Duration
|
||||
|
@ -228,25 +182,6 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu
|
|||
}, nil
|
||||
}
|
||||
|
||||
// secret client is created only if we are going to refresh
|
||||
// this skip an unnecessary check/request in the case we are not going to do anything
|
||||
secretClient, err := storeProvider.NewClient(ctx, store, r.Client, req.Namespace)
|
||||
if err != nil {
|
||||
log.Error(err, errStoreClient)
|
||||
conditionSynced := NewExternalSecretCondition(esv1beta1.ExternalSecretReady, v1.ConditionFalse, esv1beta1.ConditionReasonSecretSyncedError, errStoreClient)
|
||||
SetExternalSecretCondition(&externalSecret, *conditionSynced)
|
||||
r.recorder.Event(&externalSecret, v1.EventTypeWarning, esv1beta1.ReasonProviderClientConfig, err.Error())
|
||||
syncCallsError.With(syncCallsMetricLabels).Inc()
|
||||
return ctrl.Result{RequeueAfter: requeueAfter}, nil
|
||||
}
|
||||
|
||||
defer func() {
|
||||
err = secretClient.Close(ctx)
|
||||
if err != nil {
|
||||
log.Error(err, errCloseStoreClient)
|
||||
}
|
||||
}()
|
||||
|
||||
secret := &v1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: secretName,
|
||||
|
@ -256,7 +191,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu
|
|||
Data: make(map[string][]byte),
|
||||
}
|
||||
|
||||
dataMap, err := r.getProviderSecretData(ctx, secretClient, &externalSecret)
|
||||
dataMap, err := r.getProviderSecretData(ctx, &externalSecret)
|
||||
if err != nil {
|
||||
log.Error(err, errGetSecretData)
|
||||
r.recorder.Event(&externalSecret, v1.EventTypeWarning, esv1beta1.ReasonUpdateFailed, err.Error())
|
||||
|
@ -463,6 +398,54 @@ func shouldSkipClusterSecretStore(r *Reconciler, es esv1beta1.ExternalSecret) bo
|
|||
return !r.ClusterSecretStoreEnabled && es.Spec.SecretStoreRef.Kind == esv1beta1.ClusterSecretStoreKind
|
||||
}
|
||||
|
||||
// shouldSkipUnmanagedStore iterates over all secretStore references in the externalSecret spec,
|
||||
// fetches the store and evaluates the controllerClass property.
|
||||
// Returns true if any storeRef points to store with a non-matching controllerClass.
|
||||
func shouldSkipUnmanagedStore(ctx context.Context, namespace string, r *Reconciler, es esv1beta1.ExternalSecret) (bool, error) {
|
||||
var storeList []esv1beta1.SecretStoreRef
|
||||
|
||||
if es.Spec.SecretStoreRef.Name != "" {
|
||||
storeList = append(storeList, es.Spec.SecretStoreRef)
|
||||
}
|
||||
|
||||
for _, ref := range es.Spec.Data {
|
||||
if ref.SourceRef != nil && ref.SourceRef.SecretStoreRef != nil {
|
||||
storeList = append(storeList, *ref.SourceRef.SecretStoreRef)
|
||||
}
|
||||
}
|
||||
|
||||
for _, ref := range es.Spec.DataFrom {
|
||||
if ref.SourceRef != nil && ref.SourceRef.SecretStoreRef != nil {
|
||||
storeList = append(storeList, *ref.SourceRef.SecretStoreRef)
|
||||
}
|
||||
}
|
||||
|
||||
for _, ref := range storeList {
|
||||
var store esv1beta1.GenericStore
|
||||
|
||||
switch ref.Kind {
|
||||
case esv1beta1.SecretStoreKind, "":
|
||||
store = &esv1beta1.SecretStore{}
|
||||
case esv1beta1.ClusterSecretStoreKind:
|
||||
store = &esv1beta1.ClusterSecretStore{}
|
||||
namespace = ""
|
||||
}
|
||||
|
||||
err := r.Client.Get(ctx, types.NamespacedName{
|
||||
Name: ref.Name,
|
||||
Namespace: namespace,
|
||||
}, store)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
class := store.GetSpec().Controller
|
||||
if class != "" && class != r.ControllerClass {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func shouldRefresh(es esv1beta1.ExternalSecret) bool {
|
||||
// refresh if resource version changed
|
||||
if es.Status.SyncedResourceVersion != getResourceVersion(es) {
|
||||
|
@ -509,162 +492,6 @@ func isSecretValid(existingSecret v1.Secret) bool {
|
|||
return true
|
||||
}
|
||||
|
||||
// assertStoreIsUsable assert that the store is ready to use.
|
||||
func assertStoreIsUsable(store esv1beta1.GenericStore) error {
|
||||
condition := secretstore.GetSecretStoreCondition(store.GetStatus(), esv1beta1.SecretStoreReady)
|
||||
if condition == nil || condition.Status != v1.ConditionTrue {
|
||||
return fmt.Errorf(errSecretStoreNotReady, store.GetName())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Reconciler) getStore(ctx context.Context, externalSecret *esv1beta1.ExternalSecret) (esv1beta1.GenericStore, error) {
|
||||
ref := types.NamespacedName{
|
||||
Name: externalSecret.Spec.SecretStoreRef.Name,
|
||||
}
|
||||
|
||||
if externalSecret.Spec.SecretStoreRef.Kind == esv1beta1.ClusterSecretStoreKind {
|
||||
var store esv1beta1.ClusterSecretStore
|
||||
err := r.Get(ctx, ref, &store)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(errGetClusterSecretStore, ref.Name, err)
|
||||
}
|
||||
return &store, nil
|
||||
}
|
||||
|
||||
ref.Namespace = externalSecret.Namespace
|
||||
|
||||
var store esv1beta1.SecretStore
|
||||
err := r.Get(ctx, ref, &store)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(errGetSecretStore, ref.Name, err)
|
||||
}
|
||||
return &store, nil
|
||||
}
|
||||
|
||||
func (r *Reconciler) ShouldProcessSecret(ctx context.Context, store esv1beta1.GenericStore, ns string) (bool, error) {
|
||||
if store.GetKind() != esv1beta1.ClusterSecretStoreKind {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
if len(store.GetSpec().Conditions) == 0 {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
namespaceList := &v1.NamespaceList{}
|
||||
|
||||
for _, condition := range store.GetSpec().Conditions {
|
||||
if condition.NamespaceSelector != nil {
|
||||
namespaceSelector, err := metav1.LabelSelectorAsSelector(condition.NamespaceSelector)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if err := r.Client.List(context.Background(), namespaceList, client.MatchingLabelsSelector{Selector: namespaceSelector}); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
for _, namespace := range namespaceList.Items {
|
||||
if namespace.GetName() == ns {
|
||||
return true, nil // namespace matches the labelselector
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if condition.Namespaces != nil {
|
||||
if slices.Contains(condition.Namespaces, ns) {
|
||||
return true, nil // namespace in the namespaces list
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// getProviderSecretData returns the provider's secret data with the provided ExternalSecret.
|
||||
func (r *Reconciler) getProviderSecretData(ctx context.Context, providerClient esv1beta1.SecretsClient, externalSecret *esv1beta1.ExternalSecret) (map[string][]byte, error) {
|
||||
providerData := make(map[string][]byte)
|
||||
|
||||
for i, remoteRef := range externalSecret.Spec.DataFrom {
|
||||
var secretMap map[string][]byte
|
||||
var err error
|
||||
if remoteRef.Find != nil {
|
||||
secretMap, err = providerClient.GetAllSecrets(ctx, *remoteRef.Find)
|
||||
if errors.Is(err, esv1beta1.NoSecretErr) && externalSecret.Spec.Target.DeletionPolicy != esv1beta1.DeletionPolicyRetain {
|
||||
r.recorder.Event(externalSecret, v1.EventTypeNormal, esv1beta1.ReasonDeleted, fmt.Sprintf("secret does not exist at provider using .dataFrom[%d]", i))
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
secretMap, err = utils.RewriteMap(remoteRef.Rewrite, secretMap)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(errRewrite, i, err)
|
||||
}
|
||||
if len(remoteRef.Rewrite) == 0 {
|
||||
// ConversionStrategy is deprecated. Use RewriteMap instead.
|
||||
r.recorder.Event(externalSecret, v1.EventTypeWarning, esv1beta1.ReasonDeprecated, fmt.Sprintf("dataFrom[%d].find.conversionStrategy=%v is deprecated and will be removed in further releases. Use dataFrom.rewrite instead", i, remoteRef.Find.ConversionStrategy))
|
||||
secretMap, err = utils.ConvertKeys(remoteRef.Find.ConversionStrategy, secretMap)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(errConvert, err)
|
||||
}
|
||||
}
|
||||
if !utils.ValidateKeys(secretMap) {
|
||||
return nil, fmt.Errorf(errInvalidKeys, "find", i)
|
||||
}
|
||||
secretMap, err = utils.DecodeMap(remoteRef.Find.DecodingStrategy, secretMap)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(errDecode, "spec.dataFrom", i, err)
|
||||
}
|
||||
} else if remoteRef.Extract != nil {
|
||||
secretMap, err = providerClient.GetSecretMap(ctx, *remoteRef.Extract)
|
||||
if errors.Is(err, esv1beta1.NoSecretErr) && externalSecret.Spec.Target.DeletionPolicy != esv1beta1.DeletionPolicyRetain {
|
||||
r.recorder.Event(externalSecret, v1.EventTypeNormal, esv1beta1.ReasonDeleted, fmt.Sprintf("secret does not exist at provider using .dataFrom[%d]", i))
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
secretMap, err = utils.RewriteMap(remoteRef.Rewrite, secretMap)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(errRewrite, i, err)
|
||||
}
|
||||
if len(remoteRef.Rewrite) == 0 {
|
||||
secretMap, err = utils.ConvertKeys(remoteRef.Extract.ConversionStrategy, secretMap)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(errConvert, err)
|
||||
}
|
||||
}
|
||||
if !utils.ValidateKeys(secretMap) {
|
||||
return nil, fmt.Errorf(errInvalidKeys, "extract", i)
|
||||
}
|
||||
secretMap, err = utils.DecodeMap(remoteRef.Extract.DecodingStrategy, secretMap)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(errDecode, "spec.dataFrom", i, err)
|
||||
}
|
||||
}
|
||||
providerData = utils.MergeByteMap(providerData, secretMap)
|
||||
}
|
||||
|
||||
for i, secretRef := range externalSecret.Spec.Data {
|
||||
secretData, err := providerClient.GetSecret(ctx, secretRef.RemoteRef)
|
||||
if errors.Is(err, esv1beta1.NoSecretErr) && externalSecret.Spec.Target.DeletionPolicy != esv1beta1.DeletionPolicyRetain {
|
||||
r.recorder.Event(externalSecret, v1.EventTypeNormal, esv1beta1.ReasonDeleted, fmt.Sprintf("secret does not exist at provider using .data[%d] key=%s", i, secretRef.RemoteRef.Key))
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
secretData, err = utils.Decode(secretRef.RemoteRef.DecodingStrategy, secretData)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(errDecode, "spec.data", i, err)
|
||||
}
|
||||
providerData[secretRef.SecretKey] = secretData
|
||||
}
|
||||
|
||||
return providerData, nil
|
||||
}
|
||||
|
||||
// SetupWithManager returns a new controller builder that will be started by the provided Manager.
|
||||
func (r *Reconciler) SetupWithManager(mgr ctrl.Manager, opts controller.Options) error {
|
||||
r.recorder = mgr.GetEventRecorderFor("external-secrets")
|
||||
|
|
|
@ -0,0 +1,231 @@
|
|||
/*
|
||||
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 externalsecret
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
v1 "k8s.io/api/core/v1"
|
||||
apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/client-go/discovery"
|
||||
"k8s.io/client-go/dynamic"
|
||||
"k8s.io/client-go/restmapper"
|
||||
|
||||
esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
|
||||
genv1alpha1 "github.com/external-secrets/external-secrets/apis/generators/v1alpha1"
|
||||
"github.com/external-secrets/external-secrets/pkg/controllers/externalsecret/clientmanager"
|
||||
// Loading registered generators.
|
||||
_ "github.com/external-secrets/external-secrets/pkg/generator/register"
|
||||
// Loading registered providers.
|
||||
_ "github.com/external-secrets/external-secrets/pkg/provider/register"
|
||||
"github.com/external-secrets/external-secrets/pkg/utils"
|
||||
)
|
||||
|
||||
// getProviderSecretData returns the provider's secret data with the provided ExternalSecret.
|
||||
func (r *Reconciler) getProviderSecretData(ctx context.Context, externalSecret *esv1beta1.ExternalSecret) (map[string][]byte, error) {
|
||||
// We MUST NOT create multiple instances of a provider client (mostly due to limitations with GCP)
|
||||
// Clientmanager keeps track of the client instances
|
||||
// that are created during the fetching process and closes clients
|
||||
// if needed.
|
||||
mgr := clientmanager.New(r.Client, r.ControllerClass, r.EnableFloodGate)
|
||||
defer mgr.Close(ctx)
|
||||
|
||||
providerData := make(map[string][]byte)
|
||||
for i, remoteRef := range externalSecret.Spec.DataFrom {
|
||||
var secretMap map[string][]byte
|
||||
var err error
|
||||
|
||||
if remoteRef.Find != nil {
|
||||
secretMap, err = r.handleFindAllSecrets(ctx, externalSecret, remoteRef, mgr, i)
|
||||
} else if remoteRef.Extract != nil {
|
||||
secretMap, err = r.handleExtractSecrets(ctx, externalSecret, remoteRef, mgr, i)
|
||||
} else if remoteRef.SourceRef != nil && remoteRef.SourceRef.GeneratorRef != nil {
|
||||
secretMap, err = r.handleGenerateSecrets(ctx, externalSecret.Namespace, remoteRef, i)
|
||||
}
|
||||
if errors.Is(err, esv1beta1.NoSecretErr) && externalSecret.Spec.Target.DeletionPolicy != esv1beta1.DeletionPolicyRetain {
|
||||
r.recorder.Event(
|
||||
externalSecret,
|
||||
v1.EventTypeNormal,
|
||||
esv1beta1.ReasonDeleted,
|
||||
fmt.Sprintf("secret does not exist at provider using .dataFrom[%d]", i),
|
||||
)
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
providerData = utils.MergeByteMap(providerData, secretMap)
|
||||
}
|
||||
|
||||
for i, secretRef := range externalSecret.Spec.Data {
|
||||
err := r.handleSecretData(ctx, i, *externalSecret, secretRef, providerData, mgr)
|
||||
if errors.Is(err, esv1beta1.NoSecretErr) && externalSecret.Spec.Target.DeletionPolicy != esv1beta1.DeletionPolicyRetain {
|
||||
r.recorder.Event(externalSecret, v1.EventTypeNormal, esv1beta1.ReasonDeleted, fmt.Sprintf("secret does not exist at provider using .data[%d] key=%s", i, secretRef.RemoteRef.Key))
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return providerData, nil
|
||||
}
|
||||
|
||||
func (r *Reconciler) handleSecretData(ctx context.Context, i int, externalSecret esv1beta1.ExternalSecret, secretRef esv1beta1.ExternalSecretData, providerData map[string][]byte, cmgr *clientmanager.Manager) error {
|
||||
client, err := cmgr.Get(ctx, externalSecret.Spec.SecretStoreRef, externalSecret.Namespace, secretRef.SourceRef)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
secretData, err := client.GetSecret(ctx, secretRef.RemoteRef)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
secretData, err = utils.Decode(secretRef.RemoteRef.DecodingStrategy, secretData)
|
||||
if err != nil {
|
||||
return fmt.Errorf(errDecode, "spec.data", i, err)
|
||||
}
|
||||
providerData[secretRef.SecretKey] = secretData
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Reconciler) handleGenerateSecrets(ctx context.Context, namespace string, remoteRef esv1beta1.ExternalSecretDataFromRemoteRef, i int) (map[string][]byte, error) {
|
||||
genDef, err := r.getGeneratorDefinition(ctx, namespace, remoteRef.SourceRef)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
gen, err := genv1alpha1.GetGenerator(genDef)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
secretMap, err := gen.Generate(ctx, genDef, r.Client, namespace)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(errGenerate, i, err)
|
||||
}
|
||||
secretMap, err = utils.RewriteMap(remoteRef.Rewrite, secretMap)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(errRewrite, i, err)
|
||||
}
|
||||
if !utils.ValidateKeys(secretMap) {
|
||||
return nil, fmt.Errorf(errInvalidKeys, "generator", i)
|
||||
}
|
||||
return secretMap, err
|
||||
}
|
||||
|
||||
// getGeneratorDefinition returns the generator JSON for a given sourceRef
|
||||
// when it uses a generatorRef it fetches the resource and returns the JSON.
|
||||
func (r *Reconciler) getGeneratorDefinition(ctx context.Context, namespace string, sourceRef *esv1beta1.SourceRef) (*apiextensions.JSON, error) {
|
||||
// client-go dynamic client needs a GVR to fetch the resource
|
||||
// But we only have the GVK in our generatorRef.
|
||||
//
|
||||
// TODO: there is no need to discover the GroupVersionResource
|
||||
// this should be cached.
|
||||
c := discovery.NewDiscoveryClientForConfigOrDie(r.RestConfig)
|
||||
groupResources, err := restmapper.GetAPIGroupResources(c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
gv, err := schema.ParseGroupVersion(sourceRef.GeneratorRef.APIVersion)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mapper := restmapper.NewDiscoveryRESTMapper(groupResources)
|
||||
mapping, err := mapper.RESTMapping(schema.GroupKind{
|
||||
Group: gv.Group,
|
||||
Kind: sourceRef.GeneratorRef.Kind,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
d, err := dynamic.NewForConfig(r.RestConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, err := d.Resource(mapping.Resource).
|
||||
Namespace(namespace).
|
||||
Get(ctx, sourceRef.GeneratorRef.Name, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
jsonRes, err := res.MarshalJSON()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &apiextensions.JSON{Raw: jsonRes}, nil
|
||||
}
|
||||
|
||||
func (r *Reconciler) handleExtractSecrets(ctx context.Context, externalSecret *esv1beta1.ExternalSecret, remoteRef esv1beta1.ExternalSecretDataFromRemoteRef, cmgr *clientmanager.Manager, i int) (map[string][]byte, error) {
|
||||
client, err := cmgr.Get(ctx, externalSecret.Spec.SecretStoreRef, externalSecret.Namespace, remoteRef.SourceRef)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
secretMap, err := client.GetSecretMap(ctx, *remoteRef.Extract)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
secretMap, err = utils.RewriteMap(remoteRef.Rewrite, secretMap)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(errRewrite, i, err)
|
||||
}
|
||||
if len(remoteRef.Rewrite) == 0 {
|
||||
secretMap, err = utils.ConvertKeys(remoteRef.Extract.ConversionStrategy, secretMap)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(errConvert, err)
|
||||
}
|
||||
}
|
||||
if !utils.ValidateKeys(secretMap) {
|
||||
return nil, fmt.Errorf(errInvalidKeys, "extract", i)
|
||||
}
|
||||
secretMap, err = utils.DecodeMap(remoteRef.Extract.DecodingStrategy, secretMap)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(errDecode, "spec.dataFrom", i, err)
|
||||
}
|
||||
return secretMap, err
|
||||
}
|
||||
|
||||
func (r *Reconciler) handleFindAllSecrets(ctx context.Context, externalSecret *esv1beta1.ExternalSecret, remoteRef esv1beta1.ExternalSecretDataFromRemoteRef, cmgr *clientmanager.Manager, i int) (map[string][]byte, error) {
|
||||
client, err := cmgr.Get(ctx, externalSecret.Spec.SecretStoreRef, externalSecret.Namespace, remoteRef.SourceRef)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
secretMap, err := client.GetAllSecrets(ctx, *remoteRef.Find)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
secretMap, err = utils.RewriteMap(remoteRef.Rewrite, secretMap)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(errRewrite, i, err)
|
||||
}
|
||||
if len(remoteRef.Rewrite) == 0 {
|
||||
// ConversionStrategy is deprecated. Use RewriteMap instead.
|
||||
r.recorder.Event(externalSecret, v1.EventTypeWarning, esv1beta1.ReasonDeprecated, fmt.Sprintf("dataFrom[%d].find.conversionStrategy=%v is deprecated and will be removed in further releases. Use dataFrom.rewrite instead", i, remoteRef.Find.ConversionStrategy))
|
||||
secretMap, err = utils.ConvertKeys(remoteRef.Find.ConversionStrategy, secretMap)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(errConvert, err)
|
||||
}
|
||||
}
|
||||
if !utils.ValidateKeys(secretMap) {
|
||||
return nil, fmt.Errorf(errInvalidKeys, "find", i)
|
||||
}
|
||||
secretMap, err = utils.DecodeMap(remoteRef.Find.DecodingStrategy, secretMap)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(errDecode, "spec.dataFrom", i, err)
|
||||
}
|
||||
return secretMap, err
|
||||
}
|
|
@ -31,6 +31,7 @@ import (
|
|||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
|
||||
genv1alpha1 "github.com/external-secrets/external-secrets/apis/generators/v1alpha1"
|
||||
ctest "github.com/external-secrets/external-secrets/pkg/controllers/commontest"
|
||||
"github.com/external-secrets/external-secrets/pkg/provider/testing/fake"
|
||||
)
|
||||
|
@ -438,6 +439,124 @@ var _ = Describe("ExternalSecret controller", func() {
|
|||
}
|
||||
}
|
||||
|
||||
syncWithGeneratorRef := func(tc *testCase) {
|
||||
const secretKey = "somekey"
|
||||
const secretVal = "someValue"
|
||||
|
||||
Expect(k8sClient.Create(context.Background(), &genv1alpha1.Fake{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "mytestfake",
|
||||
Namespace: ExternalSecretNamespace,
|
||||
},
|
||||
Spec: genv1alpha1.FakeSpec{
|
||||
Data: map[string]string{
|
||||
secretKey: secretVal,
|
||||
},
|
||||
},
|
||||
})).To(Succeed())
|
||||
|
||||
// reset secretStoreRef
|
||||
tc.externalSecret.Spec.SecretStoreRef = esv1beta1.SecretStoreRef{}
|
||||
tc.externalSecret.Spec.Data = nil
|
||||
tc.externalSecret.Spec.DataFrom = []esv1beta1.ExternalSecretDataFromRemoteRef{
|
||||
{
|
||||
SourceRef: &esv1beta1.SourceRef{
|
||||
GeneratorRef: &esv1beta1.GeneratorRef{
|
||||
APIVersion: genv1alpha1.Group + "/" + genv1alpha1.Version,
|
||||
Kind: "Fake",
|
||||
Name: "mytestfake",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
tc.checkSecret = func(es *esv1beta1.ExternalSecret, secret *v1.Secret) {
|
||||
// check values
|
||||
Expect(string(secret.Data[secretKey])).To(Equal(secretVal))
|
||||
}
|
||||
}
|
||||
|
||||
syncWithMultipleSecretStores := func(tc *testCase) {
|
||||
Expect(k8sClient.Create(context.Background(), &esv1beta1.SecretStore{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "foo",
|
||||
Namespace: ExternalSecretNamespace,
|
||||
},
|
||||
Spec: esv1beta1.SecretStoreSpec{
|
||||
Provider: &esv1beta1.SecretStoreProvider{
|
||||
Fake: &esv1beta1.FakeProvider{
|
||||
Data: []esv1beta1.FakeProviderData{
|
||||
{
|
||||
Key: "foo",
|
||||
Version: "",
|
||||
ValueMap: map[string]string{
|
||||
"foo": "bar",
|
||||
"foo2": "bar2",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})).To(Succeed())
|
||||
|
||||
Expect(k8sClient.Create(context.Background(), &esv1beta1.SecretStore{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "baz",
|
||||
Namespace: ExternalSecretNamespace,
|
||||
},
|
||||
Spec: esv1beta1.SecretStoreSpec{
|
||||
Provider: &esv1beta1.SecretStoreProvider{
|
||||
Fake: &esv1beta1.FakeProvider{
|
||||
Data: []esv1beta1.FakeProviderData{
|
||||
{
|
||||
Key: "baz",
|
||||
Version: "",
|
||||
ValueMap: map[string]string{
|
||||
"baz": "bang",
|
||||
"baz2": "bang2",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})).To(Succeed())
|
||||
|
||||
tc.externalSecret.Spec.DataFrom = []esv1beta1.ExternalSecretDataFromRemoteRef{
|
||||
{
|
||||
Extract: &esv1beta1.ExternalSecretDataRemoteRef{
|
||||
Key: "foo",
|
||||
},
|
||||
SourceRef: &esv1beta1.SourceRef{
|
||||
SecretStoreRef: &esv1beta1.SecretStoreRef{
|
||||
Name: "foo",
|
||||
Kind: esv1beta1.SecretStoreKind,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Extract: &esv1beta1.ExternalSecretDataRemoteRef{
|
||||
Key: "baz",
|
||||
},
|
||||
SourceRef: &esv1beta1.SourceRef{
|
||||
SecretStoreRef: &esv1beta1.SecretStoreRef{
|
||||
Name: "baz",
|
||||
Kind: esv1beta1.SecretStoreKind,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
tc.checkSecret = func(es *esv1beta1.ExternalSecret, secret *v1.Secret) {
|
||||
// check values
|
||||
Expect(string(secret.Data["foo"])).To(Equal("bar"))
|
||||
Expect(string(secret.Data["foo2"])).To(Equal("bar2"))
|
||||
Expect(string(secret.Data["baz"])).To(Equal("bang"))
|
||||
Expect(string(secret.Data["baz2"])).To(Equal("bang2"))
|
||||
}
|
||||
}
|
||||
|
||||
// when using a template it should be used as a blueprint
|
||||
// to construct a new secret: labels, annotations and type
|
||||
syncWithTemplate := func(tc *testCase) {
|
||||
|
@ -1669,6 +1788,8 @@ var _ = Describe("ExternalSecret controller", func() {
|
|||
Entry("should not resolve conflicts with creationPolicy=Merge", mergeWithConflict),
|
||||
Entry("should not update unchanged secret using creationPolicy=Merge", mergeWithSecretNoChange),
|
||||
Entry("should not delete pre-existing secret with creationPolicy=Orphan", createSecretPolicyOrphan),
|
||||
Entry("should sync with generatorRef", syncWithGeneratorRef),
|
||||
Entry("should sync with multiple secret stores via sourceRef", syncWithMultipleSecretStores),
|
||||
Entry("should sync with template", syncWithTemplate),
|
||||
Entry("should sync with template engine v2", syncWithTemplateV2),
|
||||
Entry("should sync template with correct value precedence", syncWithTemplatePrecedence),
|
||||
|
|
|
@ -33,6 +33,7 @@ import (
|
|||
"sigs.k8s.io/controller-runtime/pkg/log/zap"
|
||||
|
||||
esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
|
||||
genv1alpha1 "github.com/external-secrets/external-secrets/apis/generators/v1alpha1"
|
||||
)
|
||||
|
||||
// These tests use Ginkgo (BDD-style Go testing framework). Refer to
|
||||
|
@ -69,6 +70,9 @@ var _ = BeforeSuite(func() {
|
|||
err = esv1beta1.AddToScheme(scheme.Scheme)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
err = genv1alpha1.AddToScheme(scheme.Scheme)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
k8sManager, err := ctrl.NewManager(cfg, ctrl.Options{
|
||||
Scheme: scheme.Scheme,
|
||||
MetricsBindAddress: "0", // avoid port collision when testing
|
||||
|
@ -83,6 +87,7 @@ var _ = BeforeSuite(func() {
|
|||
|
||||
err = (&Reconciler{
|
||||
Client: k8sClient,
|
||||
RestConfig: cfg,
|
||||
Scheme: k8sManager.GetScheme(),
|
||||
Log: ctrl.Log.WithName("controllers").WithName("ExternalSecrets"),
|
||||
RequeueInterval: time.Second,
|
||||
|
|
|
@ -111,7 +111,7 @@ func validateStore(ctx context.Context, namespace string, store esapi.GenericSto
|
|||
|
||||
// ShouldProcessStore returns true if the store should be processed.
|
||||
func ShouldProcessStore(store esapi.GenericStore, class string) bool {
|
||||
if store.GetSpec().Controller == "" || store.GetSpec().Controller == class {
|
||||
if store == nil || store.GetSpec().Controller == "" || store.GetSpec().Controller == class {
|
||||
return true
|
||||
}
|
||||
|
||||
|
|
380
pkg/generator/acr/acr.go
Normal file
380
pkg/generator/acr/acr.go
Normal file
|
@ -0,0 +1,380 @@
|
|||
/*
|
||||
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 acr
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/Azure/azure-sdk-for-go/sdk/azcore"
|
||||
"github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
|
||||
"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
|
||||
"github.com/Azure/go-autorest/autorest/azure"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apimachinery/pkg/util/json"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
kcorev1 "k8s.io/client-go/kubernetes/typed/core/v1"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
ctrlcfg "sigs.k8s.io/controller-runtime/pkg/client/config"
|
||||
"sigs.k8s.io/yaml"
|
||||
|
||||
"github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
|
||||
genv1alpha1 "github.com/external-secrets/external-secrets/apis/generators/v1alpha1"
|
||||
smmeta "github.com/external-secrets/external-secrets/apis/meta/v1"
|
||||
"github.com/external-secrets/external-secrets/pkg/provider/azure/keyvault"
|
||||
)
|
||||
|
||||
type Generator struct {
|
||||
clientSecretCreds clientSecretCredentialFunc
|
||||
}
|
||||
|
||||
type clientSecretCredentialFunc func(tenantID string, clientID string, clientSecret string, options *azidentity.ClientSecretCredentialOptions) (TokenGetter, error)
|
||||
|
||||
type TokenGetter interface {
|
||||
GetToken(ctx context.Context, opts policy.TokenRequestOptions) (*azcore.AccessToken, error)
|
||||
}
|
||||
|
||||
const (
|
||||
defaultLoginUsername = "00000000-0000-0000-0000-000000000000"
|
||||
|
||||
errNoSpec = "no config spec provided"
|
||||
errParseSpec = "unable to parse spec: %w"
|
||||
errCreateSess = "unable to create aws session: %w"
|
||||
errGetToken = "unable to get authorization token: %w"
|
||||
)
|
||||
|
||||
// Generate generates a token that can be used to authenticate against Azure Container Registry.
|
||||
// First, an Azure Active Directory access token is obtained with the desired authentication method.
|
||||
// This AAD access token will be used to authenticate against ACR.
|
||||
// Depending on the generator spec it generates an ACR access token or an ACR refresh token.
|
||||
// * access tokens are scoped to a specific repository or action (pull,push)
|
||||
// * refresh tokens can are scoped to whatever policy is attached to the identity that creates the acr refresh token
|
||||
// details can be found here: https://github.com/Azure/acr/blob/main/docs/AAD-OAuth.md#overview
|
||||
func (g *Generator) Generate(ctx context.Context, jsonSpec *apiextensions.JSON, crClient client.Client, namespace string) (map[string][]byte, error) {
|
||||
cfg, err := ctrlcfg.GetConfig()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
kubeClient, err := kubernetes.NewForConfig(cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
g.clientSecretCreds = func(tenantID, clientID, clientSecret string, options *azidentity.ClientSecretCredentialOptions) (TokenGetter, error) {
|
||||
return azidentity.NewClientSecretCredential(clientID, clientID, clientSecret, options)
|
||||
}
|
||||
|
||||
return g.generate(
|
||||
ctx,
|
||||
jsonSpec,
|
||||
crClient,
|
||||
namespace,
|
||||
kubeClient,
|
||||
fetchACRAccessToken,
|
||||
fetchACRRefreshToken)
|
||||
}
|
||||
|
||||
func (g *Generator) generate(
|
||||
ctx context.Context,
|
||||
jsonSpec *apiextensions.JSON,
|
||||
crClient client.Client,
|
||||
namespace string,
|
||||
kubeClient kubernetes.Interface,
|
||||
fetchAccessToken accessTokenFetcher,
|
||||
fetchRefreshToken refreshTokenFetcher) (map[string][]byte, error) {
|
||||
if jsonSpec == nil {
|
||||
return nil, fmt.Errorf(errNoSpec)
|
||||
}
|
||||
res, err := parseSpec(jsonSpec.Raw)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(errParseSpec, err)
|
||||
}
|
||||
var accessToken string
|
||||
// pick authentication strategy to create an AAD access token
|
||||
if res.Spec.Auth.ServicePrincipal != nil {
|
||||
accessToken, err = g.accessTokenForServicePrincipal(
|
||||
ctx,
|
||||
crClient,
|
||||
namespace,
|
||||
res.Spec.EnvironmentType,
|
||||
res.Spec.TenantID,
|
||||
res.Spec.Auth.ServicePrincipal.SecretRef.ClientID,
|
||||
res.Spec.Auth.ServicePrincipal.SecretRef.ClientSecret,
|
||||
)
|
||||
} else if res.Spec.Auth.ManagedIdentity != nil {
|
||||
accessToken, err = accessTokenForManagedIdentity(
|
||||
ctx,
|
||||
res.Spec.EnvironmentType,
|
||||
res.Spec.Auth.ManagedIdentity.IdentityID,
|
||||
)
|
||||
} else if res.Spec.Auth.WorkloadIdentity != nil {
|
||||
accessToken, err = accessTokenForWorkloadIdentity(
|
||||
ctx,
|
||||
crClient,
|
||||
kubeClient.CoreV1(),
|
||||
res.Spec.ACRRegistry,
|
||||
res.Spec.EnvironmentType,
|
||||
res.Spec.Auth.WorkloadIdentity.ServiceAccountRef,
|
||||
namespace,
|
||||
)
|
||||
} else {
|
||||
return nil, fmt.Errorf("unexpeted configuration")
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var acrToken string
|
||||
acrToken, err = fetchRefreshToken(accessToken, res.Spec.TenantID, res.Spec.ACRRegistry)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if res.Spec.Scope != "" {
|
||||
acrToken, err = fetchAccessToken(acrToken, res.Spec.TenantID, res.Spec.ACRRegistry, res.Spec.Scope)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return map[string][]byte{
|
||||
"username": []byte(defaultLoginUsername),
|
||||
"password": []byte(acrToken),
|
||||
}, nil
|
||||
}
|
||||
|
||||
type accessTokenFetcher func(acrRefreshToken, tenantID, registryURL, scope string) (string, error)
|
||||
|
||||
func fetchACRAccessToken(acrRefreshToken, tenantID, registryURL, scope string) (string, error) {
|
||||
formData := url.Values{
|
||||
"grant_type": {"refresh_token"},
|
||||
"service": {registryURL},
|
||||
"scope": {scope},
|
||||
"refresh_token": {acrRefreshToken},
|
||||
}
|
||||
res, err := http.PostForm(fmt.Sprintf("https://%s/oauth2/token", registryURL), formData)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
if res.StatusCode != http.StatusOK {
|
||||
return "", fmt.Errorf("unexpected status code: %d", res.StatusCode)
|
||||
}
|
||||
body, err := io.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
var payload map[string]string
|
||||
err = json.Unmarshal(body, &payload)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
accessToken, ok := payload["access_token"]
|
||||
if !ok {
|
||||
return "", fmt.Errorf("unable to get token")
|
||||
}
|
||||
return accessToken, nil
|
||||
}
|
||||
|
||||
type refreshTokenFetcher func(aadAccessToken, tenantID, registryURL string) (string, error)
|
||||
|
||||
func fetchACRRefreshToken(aadAccessToken, tenantID, registryURL string) (string, error) {
|
||||
// https://github.com/Azure/acr/blob/main/docs/AAD-OAuth.md#overview
|
||||
// https://docs.microsoft.com/en-us/azure/container-registry/container-registry-authentication?tabs=azure-cli
|
||||
formData := url.Values{
|
||||
"grant_type": {"access_token"},
|
||||
"service": {registryURL},
|
||||
"tenant": {tenantID},
|
||||
"access_token": {aadAccessToken},
|
||||
}
|
||||
res, err := http.PostForm(fmt.Sprintf("https://%s/oauth2/exchange", registryURL), formData)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
if res.StatusCode != http.StatusOK {
|
||||
return "", fmt.Errorf("unexpected status code %d, expected %d", res.StatusCode, http.StatusOK)
|
||||
}
|
||||
body, err := io.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
var payload map[string]string
|
||||
err = json.Unmarshal(body, &payload)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
refreshToken, ok := payload["refresh_token"]
|
||||
if !ok {
|
||||
return "", fmt.Errorf("unable to get token")
|
||||
}
|
||||
return refreshToken, nil
|
||||
}
|
||||
|
||||
func accessTokenForWorkloadIdentity(ctx context.Context, crClient client.Client, kubeClient kcorev1.CoreV1Interface, acrRegistry string, envType v1beta1.AzureEnvironmentType, serviceAccountRef *smmeta.ServiceAccountSelector, namespace string) (string, error) {
|
||||
aadEndpoint := keyvault.AadEndpointForType(envType)
|
||||
if !strings.HasSuffix(acrRegistry, "/") {
|
||||
acrRegistry += "/"
|
||||
}
|
||||
acrResource := fmt.Sprintf("https://%s/.default", acrRegistry)
|
||||
// if no serviceAccountRef was provided
|
||||
// we expect certain env vars to be present.
|
||||
// They are set by the azure workload identity webhook.
|
||||
if serviceAccountRef == nil {
|
||||
clientID := os.Getenv("AZURE_CLIENT_ID")
|
||||
tenantID := os.Getenv("AZURE_TENANT_ID")
|
||||
tokenFilePath := os.Getenv("AZURE_FEDERATED_TOKEN_FILE")
|
||||
if clientID == "" || tenantID == "" || tokenFilePath == "" {
|
||||
return "", errors.New("missing environment variables")
|
||||
}
|
||||
token, err := os.ReadFile(tokenFilePath)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("unable to read token file %s: %w", tokenFilePath, err)
|
||||
}
|
||||
tp, err := keyvault.NewTokenProvider(ctx, string(token), clientID, tenantID, aadEndpoint, acrResource)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return tp.OAuthToken(), nil
|
||||
}
|
||||
var sa corev1.ServiceAccount
|
||||
err := crClient.Get(ctx, types.NamespacedName{
|
||||
Name: serviceAccountRef.Name,
|
||||
Namespace: namespace,
|
||||
}, &sa)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
clientID, ok := sa.ObjectMeta.Annotations[keyvault.AnnotationClientID]
|
||||
if !ok {
|
||||
return "", fmt.Errorf("service account is missing annoation: %s", keyvault.AnnotationClientID)
|
||||
}
|
||||
tenantID, ok := sa.ObjectMeta.Annotations[keyvault.AnnotationTenantID]
|
||||
if !ok {
|
||||
return "", fmt.Errorf("service account is missing annotation: %s", keyvault.AnnotationTenantID)
|
||||
}
|
||||
audiences := []string{keyvault.AzureDefaultAudience}
|
||||
if len(serviceAccountRef.Audiences) > 0 {
|
||||
audiences = append(audiences, serviceAccountRef.Audiences...)
|
||||
}
|
||||
token, err := keyvault.FetchSAToken(ctx, namespace, serviceAccountRef.Name, audiences, kubeClient)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
tp, err := keyvault.NewTokenProvider(ctx, token, clientID, tenantID, aadEndpoint, acrResource)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return tp.OAuthToken(), nil
|
||||
}
|
||||
|
||||
func accessTokenForManagedIdentity(ctx context.Context, envType v1beta1.AzureEnvironmentType, identityID string) (string, error) {
|
||||
// handle workload identity
|
||||
creds, err := azidentity.NewManagedIdentityCredential(
|
||||
&azidentity.ManagedIdentityCredentialOptions{
|
||||
ID: azidentity.ResourceID(identityID),
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
aud := audienceForType(envType)
|
||||
accessToken, err := creds.GetToken(ctx, policy.TokenRequestOptions{
|
||||
Scopes: []string{aud},
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return accessToken.Token, nil
|
||||
}
|
||||
|
||||
func (g *Generator) accessTokenForServicePrincipal(ctx context.Context, crClient client.Client, namespace string, envType v1beta1.AzureEnvironmentType, tenantID string, idRef, secretRef smmeta.SecretKeySelector) (string, error) {
|
||||
cid, err := secretKeyRef(ctx, crClient, namespace, idRef)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
csec, err := secretKeyRef(ctx, crClient, namespace, secretRef)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
aadEndpoint := keyvault.AadEndpointForType(envType)
|
||||
creds, err := g.clientSecretCreds(
|
||||
tenantID,
|
||||
cid,
|
||||
csec,
|
||||
&azidentity.ClientSecretCredentialOptions{
|
||||
AuthorityHost: azidentity.AuthorityHost(aadEndpoint),
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
aud := audienceForType(envType)
|
||||
accessToken, err := creds.GetToken(ctx, policy.TokenRequestOptions{
|
||||
Scopes: []string{aud},
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return accessToken.Token, nil
|
||||
}
|
||||
|
||||
// secretKeyRef fetches a secret key.
|
||||
func secretKeyRef(ctx context.Context, crClient client.Client, namespace string, secretRef smmeta.SecretKeySelector) (string, error) {
|
||||
var secret corev1.Secret
|
||||
ref := types.NamespacedName{
|
||||
Namespace: namespace,
|
||||
Name: secretRef.Name,
|
||||
}
|
||||
err := crClient.Get(ctx, ref, &secret)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("unable to find namespace=%q secret=%q %w", ref.Namespace, ref.Name, err)
|
||||
}
|
||||
keyBytes, ok := secret.Data[secretRef.Key]
|
||||
if !ok {
|
||||
return "", fmt.Errorf("unable to find key=%q secret=%q namespace=%q", secretRef.Key, secretRef.Name, namespace)
|
||||
}
|
||||
value := strings.TrimSpace(string(keyBytes))
|
||||
return value, nil
|
||||
}
|
||||
|
||||
func audienceForType(t v1beta1.AzureEnvironmentType) string {
|
||||
suffix := ".default"
|
||||
switch t {
|
||||
case v1beta1.AzureEnvironmentChinaCloud:
|
||||
return azure.ChinaCloud.TokenAudience + suffix
|
||||
case v1beta1.AzureEnvironmentGermanCloud:
|
||||
return azure.GermanCloud.TokenAudience + suffix
|
||||
case v1beta1.AzureEnvironmentUSGovernmentCloud:
|
||||
return azure.USGovernmentCloud.TokenAudience + suffix
|
||||
case v1beta1.AzureEnvironmentPublicCloud, "":
|
||||
return azure.PublicCloud.TokenAudience + suffix
|
||||
}
|
||||
return azure.PublicCloud.TokenAudience + suffix
|
||||
}
|
||||
|
||||
func parseSpec(data []byte) (*genv1alpha1.ACRAccessToken, error) {
|
||||
var spec genv1alpha1.ACRAccessToken
|
||||
err := yaml.Unmarshal(data, &spec)
|
||||
return &spec, err
|
||||
}
|
||||
|
||||
func init() {
|
||||
genv1alpha1.Register(genv1alpha1.ACRAccessTokenKind, &Generator{})
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue