1
0
Fork 0
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:
Moritz Johner 2022-10-29 20:15:50 +02:00 committed by GitHub
parent 5debee41ac
commit dabfa5a589
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
117 changed files with 6942 additions and 822 deletions

147
.github/actions/e2e-managed/action.yml vendored Normal file
View 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
View 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

View file

@ -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,

View file

@ -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

View file

@ -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
View file

@ -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

View file

@ -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 (

View file

@ -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
}

View 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)
}
})
}
}

View file

@ -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)

View file

@ -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.

View file

@ -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

View 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

View 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)
}

View 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"`
}

View 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"`
}

View 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"`
}

View 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"`
}

View 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"`
}

View 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
}

View 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{})
}

View 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
}

View file

@ -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.")

View file

@ -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.")
}

View file

@ -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

View file

@ -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:

View file

@ -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:

View file

@ -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:

View file

@ -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: {}

View file

@ -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: {}

View 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: {}

View file

@ -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: {}

View file

@ -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: {}

View file

@ -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

View file

@ -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:

View file

@ -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

View file

@ -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

View file

@ -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
View 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)

View 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
View 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
View 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' %}
```

View 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
View 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' %}
```

View file

@ -0,0 +1,2 @@
Generators allow you to generate values. See [Generators Guide](../../guides/generator.md)

View 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
View 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 |

View file

@ -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' %}
```

View file

@ -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
View 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"
```

View file

@ -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)

View file

@ -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 |

View file

@ -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

View file

@ -75,7 +75,7 @@ Events: <none>
```
For more advanced examples, please read the other
[guides](introduction.md).
[guides](../guides/introduction.md).
## Installing with OLM

View file

@ -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

View file

@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

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

View 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: []

View 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"

View file

@ -0,0 +1,6 @@
apiVersion: generators.external-secrets.io/v1alpha1
kind: Fake
spec:
data:
foo: bar
baz: bang

View 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: ""

View 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

View file

@ -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" ]

View file

@ -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 \

View file

@ -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()

View file

@ -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",

View file

@ -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()

View file

@ -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)

View file

@ -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

View file

@ -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=

View file

@ -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
View 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),
)
})

View 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),
)
})

View 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),
)
})

View 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"))
}

View 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
}

View file

@ -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",
},
},
},
},

View file

@ -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.

View file

@ -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.

View file

@ -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
View file

@ -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
View file

@ -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=

View file

@ -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

View file

@ -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 \

View 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
}

View file

@ -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
}

View file

@ -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")

View file

@ -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
}

View file

@ -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),

View file

@ -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,

View file

@ -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
View 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