1
0
Fork 0
mirror of https://github.com/external-secrets/external-secrets.git synced 2024-12-14 11:57:59 +00:00

Merge branch 'main' into ibm-enable-retries

This commit is contained in:
Daniel Hix 2021-12-13 13:27:57 -06:00
commit 082cee230f
59 changed files with 2477 additions and 419 deletions

5
.github/PAUL.yaml vendored
View file

@ -47,6 +47,11 @@ pull_requests:
If this is your first time contributing, please make If this is your first time contributing, please make
sure to read the [Developer](https://www.external-secrets.io/contributing-devguide/) and [Contributing Process](https://www.external-secrets.io/contributing-process/) guides. sure to read the [Developer](https://www.external-secrets.io/contributing-devguide/) and [Contributing Process](https://www.external-secrets.io/contributing-process/) guides.
Please also mind and follow our [Code of Conduct](https://www.external-secrets.io/contributing-coc/). Please also mind and follow our [Code of Conduct](https://www.external-secrets.io/contributing-coc/).
Useful commands:
- `make fmt`: Formats the code
- `make check-diff`: Ensures the branch is clean
- `make reviewable`: Ensures a PR is ready for review
# Enables the /cat command # Enables the /cat command
cats_enabled: true cats_enabled: true
# enables the /dog command # enables the /dog command

View file

@ -11,7 +11,7 @@ on:
env: env:
# Common versions # Common versions
GO_VERSION: '1.16' GO_VERSION: '1.16'
GOLANGCI_VERSION: 'v1.33' GOLANGCI_VERSION: 'v1.42.1'
# list of available versions: https://storage.googleapis.com/kubebuilder-tools # list of available versions: https://storage.googleapis.com/kubebuilder-tools
# TODO: 1.21.2 does not shut down properly with controller-runtime 0.9.2 # TODO: 1.21.2 does not shut down properly with controller-runtime 0.9.2
KUBEBUILDER_TOOLS_VERSION: '1.20.2' KUBEBUILDER_TOOLS_VERSION: '1.20.2'
@ -61,14 +61,14 @@ jobs:
echo "::set-output name=mod-cache::$(go env GOMODCACHE)" echo "::set-output name=mod-cache::$(go env GOMODCACHE)"
- name: Cache the Go Build Cache - name: Cache the Go Build Cache
uses: actions/cache@v2.1.6 uses: actions/cache@v2.1.7
with: with:
path: ${{ steps.go.outputs.build-cache }} path: ${{ steps.go.outputs.build-cache }}
key: ${{ runner.os }}-build-lint-${{ hashFiles('**/go.sum') }} key: ${{ runner.os }}-build-lint-${{ hashFiles('**/go.sum') }}
restore-keys: ${{ runner.os }}-build-lint- restore-keys: ${{ runner.os }}-build-lint-
- name: Cache Go Dependencies - name: Cache Go Dependencies
uses: actions/cache@v2.1.6 uses: actions/cache@v2.1.7
with: with:
path: ${{ steps.go.outputs.mod-cache }} path: ${{ steps.go.outputs.mod-cache }}
key: ${{ runner.os }}-pkg-${{ hashFiles('**/go.sum') }} key: ${{ runner.os }}-pkg-${{ hashFiles('**/go.sum') }}
@ -107,21 +107,25 @@ jobs:
echo "::set-output name=mod-cache::$(go env GOMODCACHE)" echo "::set-output name=mod-cache::$(go env GOMODCACHE)"
- name: Cache the Go Build Cache - name: Cache the Go Build Cache
uses: actions/cache@v2.1.6 uses: actions/cache@v2.1.7
with: with:
path: ${{ steps.go.outputs.build-cache }} path: ${{ steps.go.outputs.build-cache }}
key: ${{ runner.os }}-build-check-diff-${{ hashFiles('**/go.sum') }} key: ${{ runner.os }}-build-check-diff-${{ hashFiles('**/go.sum') }}
restore-keys: ${{ runner.os }}-build-check-diff- restore-keys: ${{ runner.os }}-build-check-diff-
- name: Cache Go Dependencies - name: Cache Go Dependencies
uses: actions/cache@v2.1.6 uses: actions/cache@v2.1.7
with: with:
path: ${{ steps.go.outputs.mod-cache }} path: ${{ steps.go.outputs.mod-cache }}
key: ${{ runner.os }}-pkg-${{ hashFiles('**/go.sum') }} key: ${{ runner.os }}-pkg-${{ hashFiles('**/go.sum') }}
restore-keys: ${{ runner.os }}-pkg- restore-keys: ${{ runner.os }}-pkg-
# Check DIff also runs Reviewable which needs golangci-lint installed
- name: Check Diff - name: Check Diff
run: make check-diff run: |
wget -O- -nv https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s v1.42.1
export PATH=$PATH:./bin
make check-diff
unit-tests: unit-tests:
runs-on: ubuntu-18.04 runs-on: ubuntu-18.04
@ -147,14 +151,14 @@ jobs:
echo "::set-output name=mod-cache::$(go env GOMODCACHE)" echo "::set-output name=mod-cache::$(go env GOMODCACHE)"
- name: Cache the Go Build Cache - name: Cache the Go Build Cache
uses: actions/cache@v2.1.6 uses: actions/cache@v2.1.7
with: with:
path: ${{ steps.go.outputs.build-cache }} path: ${{ steps.go.outputs.build-cache }}
key: ${{ runner.os }}-build-unit-tests-${{ hashFiles('**/go.sum') }} key: ${{ runner.os }}-build-unit-tests-${{ hashFiles('**/go.sum') }}
restore-keys: ${{ runner.os }}-build-unit-tests- restore-keys: ${{ runner.os }}-build-unit-tests-
- name: Cache Go Dependencies - name: Cache Go Dependencies
uses: actions/cache@v2.1.6 uses: actions/cache@v2.1.7
with: with:
path: ${{ steps.go.outputs.mod-cache }} path: ${{ steps.go.outputs.mod-cache }}
key: ${{ runner.os }}-pkg-${{ hashFiles('**/go.sum') }} key: ${{ runner.os }}-pkg-${{ hashFiles('**/go.sum') }}
@ -167,7 +171,7 @@ jobs:
sudo tar -C /usr/local/kubebuilder --strip-components=1 -zvxf envtest-bins.tar.gz sudo tar -C /usr/local/kubebuilder --strip-components=1 -zvxf envtest-bins.tar.gz
- name: Cache envtest binaries - name: Cache envtest binaries
uses: actions/cache@v2.1.6 uses: actions/cache@v2.1.7
with: with:
path: /usr/local/kubebuilder path: /usr/local/kubebuilder
key: ${{ runner.os }}-kubebuilder-${{env.KUBEBUILDER_TOOLS_VERSION}} key: ${{ runner.os }}-kubebuilder-${{env.KUBEBUILDER_TOOLS_VERSION}}
@ -214,14 +218,14 @@ jobs:
echo "::set-output name=mod-cache::$(go env GOMODCACHE)" echo "::set-output name=mod-cache::$(go env GOMODCACHE)"
- name: Cache the Go Build Cache - name: Cache the Go Build Cache
uses: actions/cache@v2.1.6 uses: actions/cache@v2.1.7
with: with:
path: ${{ steps.go.outputs.build-cache }} path: ${{ steps.go.outputs.build-cache }}
key: ${{ runner.os }}-build-publish-artifacts-${{ hashFiles('**/go.sum') }} key: ${{ runner.os }}-build-publish-artifacts-${{ hashFiles('**/go.sum') }}
restore-keys: ${{ runner.os }}-build-publish-artifacts- restore-keys: ${{ runner.os }}-build-publish-artifacts-
- name: Cache Go Dependencies - name: Cache Go Dependencies
uses: actions/cache@v2.1.6 uses: actions/cache@v2.1.7
with: with:
path: ${{ steps.go.outputs.mod-cache }} path: ${{ steps.go.outputs.mod-cache }}
key: ${{ runner.os }}-pkg-${{ hashFiles('**/go.sum') }} key: ${{ runner.os }}-pkg-${{ hashFiles('**/go.sum') }}

View file

@ -50,14 +50,14 @@ jobs:
echo "::set-output name=mod-cache::$(go env GOMODCACHE)" echo "::set-output name=mod-cache::$(go env GOMODCACHE)"
- name: Cache the Go Build Cache - name: Cache the Go Build Cache
uses: actions/cache@v2.1.6 uses: actions/cache@v2.1.7
with: with:
path: ${{ steps.go.outputs.build-cache }} path: ${{ steps.go.outputs.build-cache }}
key: ${{ runner.os }}-build-unit-tests-${{ hashFiles('**/go.sum') }} key: ${{ runner.os }}-build-unit-tests-${{ hashFiles('**/go.sum') }}
restore-keys: ${{ runner.os }}-build-unit-tests- restore-keys: ${{ runner.os }}-build-unit-tests-
- name: Cache Go Dependencies - name: Cache Go Dependencies
uses: actions/cache@v2.1.6 uses: actions/cache@v2.1.7
with: with:
path: ${{ steps.go.outputs.mod-cache }} path: ${{ steps.go.outputs.mod-cache }}
key: ${{ runner.os }}-pkg-${{ hashFiles('**/go.sum') }} key: ${{ runner.os }}-pkg-${{ hashFiles('**/go.sum') }}
@ -113,14 +113,14 @@ jobs:
echo "::set-output name=mod-cache::$(go env GOMODCACHE)" echo "::set-output name=mod-cache::$(go env GOMODCACHE)"
- name: Cache the Go Build Cache - name: Cache the Go Build Cache
uses: actions/cache@v2.1.6 uses: actions/cache@v2.1.7
with: with:
path: ${{ steps.go.outputs.build-cache }} path: ${{ steps.go.outputs.build-cache }}
key: ${{ runner.os }}-build-unit-tests-${{ hashFiles('**/go.sum') }} key: ${{ runner.os }}-build-unit-tests-${{ hashFiles('**/go.sum') }}
restore-keys: ${{ runner.os }}-build-unit-tests- restore-keys: ${{ runner.os }}-build-unit-tests-
- name: Cache Go Dependencies - name: Cache Go Dependencies
uses: actions/cache@v2.1.6 uses: actions/cache@v2.1.7
with: with:
path: ${{ steps.go.outputs.mod-cache }} path: ${{ steps.go.outputs.mod-cache }}
key: ${{ runner.os }}-pkg-${{ hashFiles('**/go.sum') }} key: ${{ runner.os }}-pkg-${{ hashFiles('**/go.sum') }}
@ -149,7 +149,7 @@ jobs:
make test.e2e make test.e2e
# Update check run called "integration-fork" # Update check run called "integration-fork"
- uses: actions/github-script@v5 - uses: actions/github-script@v1
id: update-check-run id: update-check-run
if: ${{ always() }} if: ${{ always() }}
env: env:
@ -165,19 +165,19 @@ jobs:
pull_number: process.env.number pull_number: process.env.number
}); });
const ref = pull.head.sha; const ref = pull.head.sha;
console.log("\n\nPR sha: " + ref)
const { data: checks } = await github.checks.listForRef({ const { data: checks } = await github.checks.listForRef({
...context.repo, ...context.repo,
ref ref
}); });
console.log("\n\nPR CHECKS: " + checks)
const check = checks.check_runs.filter(c => c.name === process.env.job); const check = checks.check_runs.filter(c => c.name === process.env.job);
console.log("\n\nPR Filtered CHECK: " + check)
console.log(check)
const { data: result } = await github.checks.update({ const { data: result } = await github.checks.update({
...context.repo, ...context.repo,
check_run_id: check[0].id, check_run_id: check[0].id,
status: 'completed', status: 'completed',
conclusion: process.env.conclusion conclusion: process.env.conclusion
}); });
return result; return result;

View file

@ -32,14 +32,10 @@ linters-settings:
min-complexity: 16 min-complexity: 16
goheader: goheader:
template-path: ./hack/boilerplate.go.txt template-path: ./hack/boilerplate.go.txt
golint:
min-confidence: 0
govet: govet:
check-shadowing: false check-shadowing: false
lll: lll:
line-length: 300 line-length: 300
maligned:
suggest-new: true
misspell: misspell:
locale: US locale: US
@ -62,7 +58,6 @@ linters:
- gocritic - gocritic
- godot - godot
- gofmt - gofmt
- golint
- goprintffuncname - goprintffuncname
- gosec - gosec
- gosimple - gosimple
@ -70,13 +65,12 @@ linters:
- ineffassign - ineffassign
- interfacer - interfacer
- lll - lll
- maligned
- misspell - misspell
- nakedret - nakedret
- nolintlint - nolintlint
- prealloc - prealloc
- revive
- rowserrcheck - rowserrcheck
- scopelint
- sqlclosecheck - sqlclosecheck
- staticcheck - staticcheck
- structcheck - structcheck
@ -102,7 +96,6 @@ issues:
- errcheck - errcheck
- dupl - dupl
- gosec - gosec
- scopelint
- unparam - unparam
- lll - lll

View file

@ -1,4 +1,4 @@
FROM alpine:3.14.2 FROM gcr.io/distroless/static
ARG TARGETOS ARG TARGETOS
ARG TARGETARCH ARG TARGETARCH
COPY bin/external-secrets-${TARGETOS}-${TARGETARCH} /bin/external-secrets COPY bin/external-secrets-${TARGETOS}-${TARGETARCH} /bin/external-secrets

View file

@ -65,12 +65,10 @@ FAIL = (echo ${TIME} ${RED}[FAIL]${CNone} && false)
# ==================================================================================== # ====================================================================================
# Conformance # Conformance
# Ensure a PR is ready for review. reviewable: generate helm.generate lint ## Ensure a PR is ready for review.
reviewable: generate helm.generate
@go mod tidy @go mod tidy
# Ensure branch is clean. check-diff: reviewable ## Ensure branch is clean.
check-diff: reviewable
@$(INFO) checking that branch is clean @$(INFO) checking that branch is clean
@test -z "$$(git status --porcelain)" || (echo "$$(git status --porcelain)" && $(FAIL)) @test -z "$$(git status --porcelain)" || (echo "$$(git status --porcelain)" && $(FAIL))
@$(OK) branch is clean @$(OK) branch is clean
@ -91,7 +89,7 @@ test.e2e: generate ## Run e2e tests
@$(OK) go test unit-tests @$(OK) go test unit-tests
.PHONY: build .PHONY: build
build: $(addprefix build-,$(ARCH)) build: $(addprefix build-,$(ARCH)) ## Build binary
.PHONY: build-% .PHONY: build-%
build-%: generate ## Build binary for the specified arch build-%: generate ## Build binary for the specified arch
@ -100,27 +98,26 @@ build-%: generate ## Build binary for the specified arch
go build -o '$(OUTPUT_DIR)/external-secrets-linux-$*' main.go go build -o '$(OUTPUT_DIR)/external-secrets-linux-$*' main.go
@$(OK) go build $* @$(OK) go build $*
# Check install of golanci-lint lint.check: ## Check install of golanci-lint
lint.check:
@if ! golangci-lint --version > /dev/null 2>&1; then \ @if ! golangci-lint --version > /dev/null 2>&1; then \
echo -e "\033[0;33mgolangci-lint is not installed: run \`\033[0;32mmake lint.install\033[0m\033[0;33m\` or install it from https://golangci-lint.run\033[0m"; \ echo -e "\033[0;33mgolangci-lint is not installed: run \`\033[0;32mmake lint.install\033[0m\033[0;33m\` or install it from https://golangci-lint.run\033[0m"; \
exit 1; \ exit 1; \
fi fi
# installs golangci-lint to the go bin dir lint.install: ## Install golangci-lint to the go bin dir
lint.install:
@if ! golangci-lint --version > /dev/null 2>&1; then \ @if ! golangci-lint --version > /dev/null 2>&1; then \
echo "Installing golangci-lint"; \ echo "Installing golangci-lint"; \
curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(GOBIN) v1.33.0; \ curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(GOBIN) v1.42.1; \
fi fi
lint: lint.check ## run golangci-lint lint: lint.check ## Run golangci-lint
@if ! golangci-lint run; then \ @if ! golangci-lint run; then \
echo -e "\033[0;33mgolangci-lint failed: some checks can be fixed with \`\033[0;32mmake fmt\033[0m\033[0;33m\`\033[0m"; \ echo -e "\033[0;33mgolangci-lint failed: some checks can be fixed with \`\033[0;32mmake fmt\033[0m\033[0;33m\`\033[0m"; \
exit 1; \ exit 1; \
fi fi
@$(OK) Finished linting
fmt: lint.check ## ensure consistent code style fmt: lint.check ## Ensure consistent code style
@go mod tidy @go mod tidy
@go fmt ./... @go fmt ./...
@golangci-lint run --fix > /dev/null 2>&1 || true @golangci-lint run --fix > /dev/null 2>&1 || true
@ -142,20 +139,17 @@ generate: ## Generate code and crds
# This is for running out-of-cluster locally, and is for convenience. # This is for running out-of-cluster locally, and is for convenience.
# For more control, try running the binary directly with different arguments. # For more control, try running the binary directly with different arguments.
run: generate run: generate ## Run app locally (without a k8s cluster)
go run ./main.go go run ./main.go
# Generate manifests from helm chart manifests: helm.generate ## Generate manifests from helm chart
manifests: helm.generate
mkdir -p $(OUTPUT_DIR)/deploy/manifests mkdir -p $(OUTPUT_DIR)/deploy/manifests
helm template external-secrets $(HELM_DIR) -f deploy/manifests/helm-values.yaml > $(OUTPUT_DIR)/deploy/manifests/external-secrets.yaml helm template external-secrets $(HELM_DIR) -f deploy/manifests/helm-values.yaml > $(OUTPUT_DIR)/deploy/manifests/external-secrets.yaml
# Install CRDs into a cluster. This is for convenience. crds.install: generate ## Install CRDs into a cluster. This is for convenience
crds.install: generate
kubectl apply -f $(CRD_DIR) kubectl apply -f $(CRD_DIR)
# Uninstall CRDs from a cluster. This is for convenience. crds.uninstall: ## Uninstall CRDs from a cluster. This is for convenience
crds.uninstall:
kubectl delete -f $(CRD_DIR) kubectl delete -f $(CRD_DIR)
# ==================================================================================== # ====================================================================================
@ -173,8 +167,7 @@ helm.build: helm.generate ## Build helm chart
@mv $(OUTPUT_DIR)/chart/external-secrets-$(HELM_VERSION).tgz $(OUTPUT_DIR)/chart/external-secrets.tgz @mv $(OUTPUT_DIR)/chart/external-secrets-$(HELM_VERSION).tgz $(OUTPUT_DIR)/chart/external-secrets.tgz
@$(OK) helm package @$(OK) helm package
# Copy crds to helm chart directory helm.generate: helm.docs ## Copy crds to helm chart directory
helm.generate: helm.docs
@cp $(CRD_DIR)/*.yaml $(HELM_DIR)/templates/crds/ @cp $(CRD_DIR)/*.yaml $(HELM_DIR)/templates/crds/
# Add helm if statement for controlling the install of CRDs # Add helm if statement for controlling the install of CRDs
@for i in $(HELM_DIR)/templates/crds/*.yaml; do \ @for i in $(HELM_DIR)/templates/crds/*.yaml; do \
@ -189,24 +182,24 @@ helm.generate: helm.docs
# ==================================================================================== # ====================================================================================
# Documentation # Documentation
.PHONY: docs .PHONY: docs
docs: generate docs: generate ## Generate docs
$(MAKE) -C ./hack/api-docs build $(MAKE) -C ./hack/api-docs build
.PHONY: serve-docs .PHONY: serve-docs
serve-docs: serve-docs: ## Serve docs
$(MAKE) -C ./hack/api-docs serve $(MAKE) -C ./hack/api-docs serve
# ==================================================================================== # ====================================================================================
# Build Artifacts # Build Artifacts
build.all: docker.build helm.build build.all: docker.build helm.build ## Build all artifacts (docker image, helm chart)
docker.build: $(addprefix build-,$(ARCH)) ## Build the docker image docker.build: $(addprefix build-,$(ARCH)) ## Build the docker image
@$(INFO) docker build @$(INFO) docker build
@docker build . $(BUILD_ARGS) -t $(IMAGE_REGISTRY):$(VERSION) @docker build . $(BUILD_ARGS) -t $(IMAGE_REGISTRY):$(VERSION)
@$(OK) docker build @$(OK) docker build
docker.push: docker.push: ## Push the docker image to the registry
@$(INFO) docker push @$(INFO) docker push
@docker push $(IMAGE_REGISTRY):$(VERSION) @docker push $(IMAGE_REGISTRY):$(VERSION)
@$(OK) docker push @$(OK) docker push
@ -216,7 +209,7 @@ docker.push:
RELEASE_TAG ?= main RELEASE_TAG ?= main
SOURCE_TAG ?= $(VERSION) SOURCE_TAG ?= $(VERSION)
docker.promote: docker.promote: ## Promote the docker image to the registry
@$(INFO) promoting $(SOURCE_TAG) to $(RELEASE_TAG) @$(INFO) promoting $(SOURCE_TAG) to $(RELEASE_TAG)
docker manifest inspect $(IMAGE_REGISTRY):$(SOURCE_TAG) > .tagmanifest docker manifest inspect $(IMAGE_REGISTRY):$(SOURCE_TAG) > .tagmanifest
for digest in $$(jq -r '.manifests[].digest' < .tagmanifest); do \ for digest in $$(jq -r '.manifests[].digest' < .tagmanifest); do \
@ -231,5 +224,5 @@ docker.promote:
# Help # Help
# only comments after make target name are shown as help text # only comments after make target name are shown as help text
help: ## displays this help message help: ## Displays this help message
@echo -e "$$(grep -hE '^\S+:.*##' $(MAKEFILE_LIST) | sed -e 's/:.*##\s*/:/' -e 's/^\(.\+\):\(.*\)/\\x1b[36m\1\\x1b[m:\2/' | column -c2 -t -s : | sort)" @echo -e "$$(grep -hE '^\S+:.*##' $(MAKEFILE_LIST) | sed -e 's/:.*##\s*/:/' -e 's/^\(.\+\):\(.*\)/\\x1b[36m\1\\x1b[m:\2/' | column -c2 -t -s : | sort)"

View file

@ -13,6 +13,7 @@ Multiple people and organizations are joining efforts to create a single Externa
- [AWS Secrets Manager](https://external-secrets.io/provider-aws-secrets-manager/) - [AWS Secrets Manager](https://external-secrets.io/provider-aws-secrets-manager/)
- [AWS Parameter Store](https://external-secrets.io/provider-aws-parameter-store/) - [AWS Parameter Store](https://external-secrets.io/provider-aws-parameter-store/)
- [Akeyless](https://www.akeyless.io/)
- [Hashicorp Vault](https://www.vaultproject.io/) - [Hashicorp Vault](https://www.vaultproject.io/)
- [Google Cloud Secrets Manager](https://external-secrets.io/provider-google-secrets-manager/) - [Google Cloud Secrets Manager](https://external-secrets.io/provider-google-secrets-manager/)
- [Azure Key Vault](https://external-secrets.io/provider-azure-key-vault/) - [Azure Key Vault](https://external-secrets.io/provider-azure-key-vault/)
@ -28,10 +29,10 @@ Multiple people and organizations are joining efforts to create a single Externa
| Provider | Stability | Contact | | Provider | Stability | Contact |
| ------------------------------------------------------------------------ | :-------: | ---------------------------------------------: | | ------------------------------------------------------------------------ | :-------: | ---------------------------------------------: |
| [AWS SM](https://external-secrets.io/provider-aws-secrets-manager/) | alpha | [ESO Org](https://github.com/external-secrets) | | [AWS SM](https://external-secrets.io/provider-aws-secrets-manager/) | beta | [ESO Org](https://github.com/external-secrets) |
| [AWS PS](https://external-secrets.io/provider-aws-parameter-store/) | alpha | [ESO Org](https://github.com/external-secrets) | | [AWS PS](https://external-secrets.io/provider-aws-parameter-store/) | beta | [ESO Org](https://github.com/external-secrets) |
| [Hashicorp Vault](https://external-secrets.io/provider-hashicorp-vault/) | alpha | [ESO Org](https://github.com/external-secrets) | | [Hashicorp Vault](https://external-secrets.io/provider-hashicorp-vault/) | stable | [ESO Org](https://github.com/external-secrets) |
| [GCP SM](https://external-secrets.io/provider-google-secrets-manager/) | alpha | [ESO Org](https://github.com/external-secrets) | | [GCP SM](https://external-secrets.io/provider-google-secrets-manager/) | beta | [ESO Org](https://github.com/external-secrets) |
### Community maintained: ### Community maintained:
@ -43,6 +44,7 @@ Multiple people and organizations are joining efforts to create a single Externa
| [Gitlab Project Variables](https://external-secrets.io/provider-gitlab-project-variables/) | alpha | [@Jabray5](https://github.com/Jabray5) | | [Gitlab Project Variables](https://external-secrets.io/provider-gitlab-project-variables/) | alpha | [@Jabray5](https://github.com/Jabray5) |
| Alibaba Cloud KMS | alpha | [@ElsaChelala](https://github.com/ElsaChelala) | | Alibaba Cloud KMS | alpha | [@ElsaChelala](https://github.com/ElsaChelala) |
| [Oracle Vault]( https://external-secrets.io/provider-oracle-vault) | alpha | [@KianTigger](https://github.com/KianTigger) | | [Oracle Vault]( https://external-secrets.io/provider-oracle-vault) | alpha | [@KianTigger](https://github.com/KianTigger) |
| [Akeyless]( https://external-secrets.io/provider-akeyless) | alpha | [@renanaAkeyless](https://github.com/renanaAkeyless) |
## Documentation ## Documentation

View file

@ -0,0 +1,42 @@
/*
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 (
esmeta "github.com/external-secrets/external-secrets/apis/meta/v1"
)
// AkeylessProvider Configures an store to sync secrets using Akeyless KV.
type AkeylessProvider struct {
// Akeyless GW API Url from which the secrets to be fetched from.
AkeylessGWApiURL *string `json:"akeylessGWApiURL"`
// Auth configures how the operator authenticates with Akeyless.
Auth *AkeylessAuth `json:"authSecretRef"`
}
type AkeylessAuth struct {
SecretRef AkeylessAuthSecretRef `json:"secretRef"`
}
// AkeylessAuthSecretRef
//AKEYLESS_ACCESS_TYPE_PARAM: AZURE_OBJ_ID OR GCP_AUDIENCE OR ACCESS_KEY OR KUB_CONFIG_NAME.
type AkeylessAuthSecretRef struct {
// The SecretAccessID is used for authentication
AccessID esmeta.SecretKeySelector `json:"accessID,omitempty"`
AccessType esmeta.SecretKeySelector `json:"accessType,omitempty"`
AccessTypeParam esmeta.SecretKeySelector `json:"accessTypeParam,omitempty"`
}

View file

@ -16,14 +16,41 @@ package v1alpha1
import smmeta "github.com/external-secrets/external-secrets/apis/meta/v1" import smmeta "github.com/external-secrets/external-secrets/apis/meta/v1"
// AuthType describes how to authenticate to the Azure Keyvault
// Only one of the following auth types may be specified.
// If none of the following auth type is specified, the default one
// is ServicePrincipal.
// +kubebuilder:validation:Enum=ServicePrincipal;ManagedIdentity
type AuthType string
const (
// Using service principal to authenticate, which needs a tenantId, a clientId and a clientSecret.
ServicePrincipal AuthType = "ServicePrincipal"
// Using Managed Identity to authenticate. Used with aad-pod-identity instelled in the clister.
ManagedIdentity AuthType = "ManagedIdentity"
)
// Configures an store to sync secrets using Azure KV. // Configures an store to sync secrets using Azure KV.
type AzureKVProvider struct { type AzureKVProvider struct {
// Auth type defines how to authenticate to the keyvault service.
// Valid values are:
// - "ServicePrincipal" (default): Using a service principal (tenantId, clientId, clientSecret)
// - "ManagedIdentity": Using Managed Identity assigned to the pod (see aad-pod-identity)
// +optional
// +kubebuilder:default=ServicePrincipal
AuthType *AuthType `json:"authType,omitempty"`
// Vault Url from which the secrets to be fetched from. // Vault Url from which the secrets to be fetched from.
VaultURL *string `json:"vaultUrl"` VaultURL *string `json:"vaultUrl"`
// TenantID configures the Azure Tenant to send requests to. // TenantID configures the Azure Tenant to send requests to. Required for ServicePrincipal auth type.
TenantID *string `json:"tenantId"` // +optional
// Auth configures how the operator authenticates with Azure. TenantID *string `json:"tenantId,omitempty"`
AuthSecretRef *AzureKVAuth `json:"authSecretRef"` // Auth configures how the operator authenticates with Azure. Required for ServicePrincipal auth type.
// +optional
AuthSecretRef *AzureKVAuth `json:"authSecretRef,omitempty"`
// If multiple Managed Identity is assigned to the pod, you can select the one to be used
// +optional
IdentityID *string `json:"identityId,omitempty"`
} }
// Configuration used to authenticate with Azure. // Configuration used to authenticate with Azure.

View file

@ -46,6 +46,10 @@ type SecretStoreProvider struct {
// +optional // +optional
AzureKV *AzureKVProvider `json:"azurekv,omitempty"` AzureKV *AzureKVProvider `json:"azurekv,omitempty"`
// Akeyless configures this store to sync secrets using Akeyless Vault provider
// +optional
Akeyless *AkeylessProvider `json:"akeyless,omitempty"`
// Vault configures this store to sync secrets using Hashi provider // Vault configures this store to sync secrets using Hashi provider
// +optional // +optional
Vault *VaultProvider `json:"vault,omitempty"` Vault *VaultProvider `json:"vault,omitempty"`

View file

@ -102,6 +102,65 @@ func (in *AWSProvider) DeepCopy() *AWSProvider {
return out return out
} }
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *AkeylessAuth) DeepCopyInto(out *AkeylessAuth) {
*out = *in
in.SecretRef.DeepCopyInto(&out.SecretRef)
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AkeylessAuth.
func (in *AkeylessAuth) DeepCopy() *AkeylessAuth {
if in == nil {
return nil
}
out := new(AkeylessAuth)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *AkeylessAuthSecretRef) DeepCopyInto(out *AkeylessAuthSecretRef) {
*out = *in
in.AccessID.DeepCopyInto(&out.AccessID)
in.AccessType.DeepCopyInto(&out.AccessType)
in.AccessTypeParam.DeepCopyInto(&out.AccessTypeParam)
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AkeylessAuthSecretRef.
func (in *AkeylessAuthSecretRef) DeepCopy() *AkeylessAuthSecretRef {
if in == nil {
return nil
}
out := new(AkeylessAuthSecretRef)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *AkeylessProvider) DeepCopyInto(out *AkeylessProvider) {
*out = *in
if in.AkeylessGWApiURL != nil {
in, out := &in.AkeylessGWApiURL, &out.AkeylessGWApiURL
*out = new(string)
**out = **in
}
if in.Auth != nil {
in, out := &in.Auth, &out.Auth
*out = new(AkeylessAuth)
(*in).DeepCopyInto(*out)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AkeylessProvider.
func (in *AkeylessProvider) DeepCopy() *AkeylessProvider {
if in == nil {
return nil
}
out := new(AkeylessProvider)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *AlibabaAuth) DeepCopyInto(out *AlibabaAuth) { func (in *AlibabaAuth) DeepCopyInto(out *AlibabaAuth) {
*out = *in *out = *in
@ -183,6 +242,11 @@ func (in *AzureKVAuth) DeepCopy() *AzureKVAuth {
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *AzureKVProvider) DeepCopyInto(out *AzureKVProvider) { func (in *AzureKVProvider) DeepCopyInto(out *AzureKVProvider) {
*out = *in *out = *in
if in.AuthType != nil {
in, out := &in.AuthType, &out.AuthType
*out = new(AuthType)
**out = **in
}
if in.VaultURL != nil { if in.VaultURL != nil {
in, out := &in.VaultURL, &out.VaultURL in, out := &in.VaultURL, &out.VaultURL
*out = new(string) *out = new(string)
@ -198,6 +262,11 @@ func (in *AzureKVProvider) DeepCopyInto(out *AzureKVProvider) {
*out = new(AzureKVAuth) *out = new(AzureKVAuth)
(*in).DeepCopyInto(*out) (*in).DeepCopyInto(*out)
} }
if in.IdentityID != nil {
in, out := &in.IdentityID, &out.IdentityID
*out = new(string)
**out = **in
}
} }
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AzureKVProvider. // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AzureKVProvider.
@ -794,6 +863,11 @@ func (in *SecretStoreProvider) DeepCopyInto(out *SecretStoreProvider) {
*out = new(AzureKVProvider) *out = new(AzureKVProvider)
(*in).DeepCopyInto(*out) (*in).DeepCopyInto(*out)
} }
if in.Akeyless != nil {
in, out := &in.Akeyless, &out.Akeyless
*out = new(AkeylessProvider)
(*in).DeepCopyInto(*out)
}
if in.Vault != nil { if in.Vault != nil {
in, out := &in.Vault, &out.Vault in, out := &in.Vault, &out.Vault
*out = new(VaultProvider) *out = new(VaultProvider)

View file

@ -2,8 +2,8 @@ apiVersion: v2
name: external-secrets name: external-secrets
description: External secret management for Kubernetes description: External secret management for Kubernetes
type: application type: application
version: "0.3.6" version: "0.3.9"
appVersion: "v0.3.6" appVersion: "v0.3.9"
kubeVersion: ">= 1.11.0-0" kubeVersion: ">= 1.11.0-0"
keywords: keywords:
- kubernetes-external-secrets - kubernetes-external-secrets

View file

@ -4,7 +4,7 @@
[//]: # (README.md generated by gotmpl. DO NOT EDIT.) [//]: # (README.md generated by gotmpl. DO NOT EDIT.)
![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![Version: 0.3.6](https://img.shields.io/badge/Version-0.3.6-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![Version: 0.3.9](https://img.shields.io/badge/Version-0.3.9-informational?style=flat-square)
External secret management for Kubernetes External secret management for Kubernetes

View file

@ -54,6 +54,94 @@ spec:
maxProperties: 1 maxProperties: 1
minProperties: 1 minProperties: 1
properties: properties:
akeyless:
description: Akeyless configures this store to sync secrets using
Akeyless Vault provider
properties:
akeylessGWApiURL:
description: Akeyless GW API Url from which the secrets to
be fetched from.
type: string
authSecretRef:
description: Auth configures how the operator authenticates
with Akeyless.
properties:
secretRef:
description: 'AkeylessAuthSecretRef AKEYLESS_ACCESS_TYPE_PARAM:
AZURE_OBJ_ID OR GCP_AUDIENCE OR ACCESS_KEY OR KUB_CONFIG_NAME.'
properties:
accessID:
description: The SecretAccessID 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
accessType:
description: A reference to a specific 'key' within
a Secret resource, In some instances, `key` is a
required field.
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
accessTypeParam:
description: A reference to a specific 'key' within
a Secret resource, In some instances, `key` is a
required field.
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
required:
- akeylessGWApiURL
- authSecretRef
type: object
alibaba: alibaba:
description: Alibaba configures this store to sync secrets using description: Alibaba configures this store to sync secrets using
Alibaba Cloud provider Alibaba Cloud provider
@ -222,7 +310,7 @@ spec:
properties: properties:
authSecretRef: authSecretRef:
description: Auth configures how the operator authenticates description: Auth configures how the operator authenticates
with Azure. with Azure. Required for ServicePrincipal auth type.
properties: properties:
clientId: clientId:
description: The Azure clientId of the service principle description: The Azure clientId of the service principle
@ -266,17 +354,30 @@ spec:
- clientId - clientId
- clientSecret - clientSecret
type: object type: object
authType:
default: ServicePrincipal
description: 'Auth type defines how to authenticate to the
keyvault service. Valid values are: - "ServicePrincipal"
(default): Using a service principal (tenantId, clientId,
clientSecret) - "ManagedIdentity": Using Managed Identity
assigned to the pod (see aad-pod-identity)'
enum:
- ServicePrincipal
- ManagedIdentity
type: string
identityId:
description: If multiple Managed Identity is assigned to the
pod, you can select the one to be used
type: string
tenantId: tenantId:
description: TenantID configures the Azure Tenant to send description: TenantID configures the Azure Tenant to send
requests to. requests to. Required for ServicePrincipal auth type.
type: string type: string
vaultUrl: vaultUrl:
description: Vault Url from which the secrets to be fetched description: Vault Url from which the secrets to be fetched
from. from.
type: string type: string
required: required:
- authSecretRef
- tenantId
- vaultUrl - vaultUrl
type: object type: object
gcpsm: gcpsm:

View file

@ -54,6 +54,94 @@ spec:
maxProperties: 1 maxProperties: 1
minProperties: 1 minProperties: 1
properties: properties:
akeyless:
description: Akeyless configures this store to sync secrets using
Akeyless Vault provider
properties:
akeylessGWApiURL:
description: Akeyless GW API Url from which the secrets to
be fetched from.
type: string
authSecretRef:
description: Auth configures how the operator authenticates
with Akeyless.
properties:
secretRef:
description: 'AkeylessAuthSecretRef AKEYLESS_ACCESS_TYPE_PARAM:
AZURE_OBJ_ID OR GCP_AUDIENCE OR ACCESS_KEY OR KUB_CONFIG_NAME.'
properties:
accessID:
description: The SecretAccessID 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
accessType:
description: A reference to a specific 'key' within
a Secret resource, In some instances, `key` is a
required field.
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
accessTypeParam:
description: A reference to a specific 'key' within
a Secret resource, In some instances, `key` is a
required field.
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
required:
- akeylessGWApiURL
- authSecretRef
type: object
alibaba: alibaba:
description: Alibaba configures this store to sync secrets using description: Alibaba configures this store to sync secrets using
Alibaba Cloud provider Alibaba Cloud provider
@ -222,7 +310,7 @@ spec:
properties: properties:
authSecretRef: authSecretRef:
description: Auth configures how the operator authenticates description: Auth configures how the operator authenticates
with Azure. with Azure. Required for ServicePrincipal auth type.
properties: properties:
clientId: clientId:
description: The Azure clientId of the service principle description: The Azure clientId of the service principle
@ -266,17 +354,30 @@ spec:
- clientId - clientId
- clientSecret - clientSecret
type: object type: object
authType:
default: ServicePrincipal
description: 'Auth type defines how to authenticate to the
keyvault service. Valid values are: - "ServicePrincipal"
(default): Using a service principal (tenantId, clientId,
clientSecret) - "ManagedIdentity": Using Managed Identity
assigned to the pod (see aad-pod-identity)'
enum:
- ServicePrincipal
- ManagedIdentity
type: string
identityId:
description: If multiple Managed Identity is assigned to the
pod, you can select the one to be used
type: string
tenantId: tenantId:
description: TenantID configures the Azure Tenant to send description: TenantID configures the Azure Tenant to send
requests to. requests to. Required for ServicePrincipal auth type.
type: string type: string
vaultUrl: vaultUrl:
description: Vault Url from which the secrets to be fetched description: Vault Url from which the secrets to be fetched
from. from.
type: string type: string
required: required:
- authSecretRef
- tenantId
- vaultUrl - vaultUrl
type: object type: object
gcpsm: gcpsm:

68
docs/provider-akeyless.md Normal file
View file

@ -0,0 +1,68 @@
## Akeyless Vault
External Secrets Operator integrates with [Akeyless API](https://docs.akeyless.io/reference#v2).
### Authentication
The API requires an access-id, access-type and access-Type-param.
The supported auth-methods and their params are:
| accessType | accessTypeParam |
| ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `api_key` | The access key. |
| `k8s` | The k8s configuration name |
| `aws_iam` | - |
| `gcp` | The gcp audience |
| `azure_ad` | azure object id (optional) |
form more information about [Akeyless Authentication Methods](https://docs.akeyless.io/docs/access-and-authentication-methods)
### Akeless credentials secret
Create a secret containing your credentials:
```yaml
apiVersion: v1
kind: Secret
metadata:
name: akeylss-secret-creds
type: Opaque
stringData:
accessId: "p-XXXX"
accessType: # k8s/aws_iam/gcp/azure_ad/api_key
accessTypeParam: # can be one of the following: k8s-conf-name/gcp-audience/azure-obj-id/access-key
```
### Update secret store
Be sure the `akeyless` provider is listed in the `Kind=SecretStore` and the `akeylessGWApiURL` is set (def: "https://api.akeless.io".
```yaml
{% include 'akeyless-secret-store.yaml' %}
```
### Creating external secret
To get a secret from Akeyless and secret it on the Kubernetes cluster, a `Kind=ExternalSecret` is needed.
```yaml
{% include 'akeyless-external-secret.yaml' %}
```
#### Using DataFrom
DataFrom can be used to get a secret as a JSON string and attempt to parse it.
```yaml
{% include 'akeyless-external-secret-json.yaml' %}
```
### Getting the Kubernetes secret
The operator will fetch the secret and inject it as a `Kind=Secret`.
```
kubectl get secret akeyless-secret-to-create -o jsonpath='{.data.secretKey}' | base64 -d
```
```
kubectl get secret akeyless-secret-to-create-json -o jsonpath='{.data}'
```

View file

@ -7,23 +7,37 @@ External Secrets Operator integrates with [Azure Key vault](https://azure.micros
### Authentication ### Authentication
At the moment, we only support [service principals](https://docs.microsoft.com/en-us/azure/key-vault/general/authentication) authentication. We support Service Principals and Managed Identity [authentication](https://docs.microsoft.com/en-us/azure/key-vault/general/authentication).
To use Managed Identity authentication, you should use [aad-pod-identity](https://azure.github.io/aad-pod-identity/docs/) to assign the identity to external-secrets operator. To add the selector to external-secrets operator, use `podLabels` in your values.yaml in case of Helm installation of external-secrets.
#### Service Principal key authentication #### Service Principal key authentication
A service Principal client and Secret is created and the JSON keyfile is stored in a `Kind=Secret`. The `ClientID` and `ClientSecret` should be configured for the secret. This service principal should have proper access rights to the keyvault to be managed by the operator A service Principal client and Secret is created and the JSON keyfile is stored in a `Kind=Secret`. The `ClientID` and `ClientSecret` should be configured for the secret. This service principal should have proper access rights to the keyvault to be managed by the operator
#### Managed Identity authentication
A Managed Identity should be created in Azure, and that Identity should have proper rights to the keyvault to be managed by the operator.
If there are multiple Managed Identitites for different keyvaults, the operator should have been assigned all identities via [aad-pod-identity](https://azure.github.io/aad-pod-identity/docs/), then the SecretStore configuration should include the Id of the idenetity to be used via the `identityId` field.
```yaml ```yaml
{% include 'azkv-credentials-secret.yaml' %} {% include 'azkv-credentials-secret.yaml' %}
``` ```
### Update secret store ### Update secret store
Be sure the `azkv` provider is listed in the `Kind=SecretStore` Be sure the `azurekv` provider is listed in the `Kind=SecretStore`
```yaml ```yaml
{% include 'azkv-secret-store.yaml' %} {% include 'azkv-secret-store.yaml' %}
``` ```
Or in case of Managed Idenetity authentication:
```yaml
{% include 'azkv-secret-store-mi.yaml' %}
```
### Object Types ### Object Types
Azure KeyVault manages different [object types](https://docs.microsoft.com/en-us/azure/key-vault/general/about-keys-secrets-certificates#object-types), we support `keys`, `secrets` and `certificates`. Simply prefix the key with `key`, `secret` or `cert` to retrieve the desired type (defaults to secret). Azure KeyVault manages different [object types](https://docs.microsoft.com/en-us/azure/key-vault/general/about-keys-secrets-certificates#object-types), we support `keys`, `secrets` and `certificates`. Simply prefix the key with `key`, `secret` or `cert` to retrieve the desired type (defaults to secret).

View file

@ -0,0 +1,12 @@
apiVersion: v1
kind: Secret
metadata:
name: akeylss-secret-creds
type: Opaque
stringData:
accessId: "p-XXXX"
accessType: # k8s/aws_iam/gcp/azure_ad/api_key
accessTypeParam: # can be one of the following: k8s-conf-name/gcp-audience/azure-obj-id/access-key

View file

@ -0,0 +1,18 @@
apiVersion: external-secrets.io/v1alpha1
kind: ExternalSecret
metadata:
name: akeyless-external-secret-example-json
spec:
refreshInterval: 1h
secretStoreRef:
kind: SecretStore
name: akeyless-secret-store # Must match SecretStore on the cluster
target:
name: akeyless-secret-to-create-json # Name for the secret to be created on the cluster
creationPolicy: Owner
# for json formatted secrets: each key in the json will be used as the secret key in the SECRET k8s target object
dataFrom:
- key: secret-name # Full path of the secret on Akeyless

View file

@ -0,0 +1,19 @@
apiVersion: external-secrets.io/v1alpha1
kind: ExternalSecret
metadata:
name: akeyless-external-secret-example
spec:
refreshInterval: 1h
secretStoreRef:
kind: SecretStore
name: akeyless-secret-store # Must match SecretStore on the cluster
target:
name: akeyless-secret-to-create # Name for the secret to be created on the cluster
creationPolicy: Owner
data:
- secretKey: secretKey # Key given to the secret to be created on the cluster
remoteRef:
key: secret-name # Full path of the secret on Akeyless

View file

@ -0,0 +1,20 @@
apiVersion: external-secrets.io/v1alpha1
kind: SecretStore
metadata:
name: akeyless-secret-store
spec:
provider:
akeyless:
# URL of your akeyless API
akeylessGWApiURL: "https://api.akeyless.io"
authSecretRef:
secretRef:
accessID:
name: akeylss-secret-creds
key: accessId
accessType:
name: akeylss-secret-creds
key: accessType
accessTypeParam:
name: akeylss-secret-creds
key: accessTypeParam

View file

@ -0,0 +1,13 @@
apiVersion: external-secrets.io/v1alpha1
kind: SecretStore
metadata:
name: example-secret-store
spec:
provider:
# provider type: azure keyvault
azurekv:
authType: ManagedIdentity
# Optionally set the Id of the Managed Identity, if multiple identities is assignet to external-secrets operator
identityId: "<MI_clientId>"
# URL of your vault instance, see: https://docs.microsoft.com/en-us/azure/key-vault/general/about-keys-secrets-certificates
vaultUrl: "https://my-keyvault-name.vault.azure.net"

View file

@ -1,5 +1,5 @@
{% raw %} {% raw %}
# define your tempalte in a config map # define your template in a config map
apiVersion: v1 apiVersion: v1
kind: ConfigMap kind: ConfigMap
metadata: metadata:

View file

@ -1,8 +1,8 @@
ARG GO_VERSION=1.16 ARG GO_VERSION=1.16
FROM golang:$GO_VERSION-buster as builder FROM golang:$GO_VERSION-buster as builder
ENV KUBECTL_VERSION="v1.19.2" ENV KUBECTL_VERSION="v1.21.2"
ENV HELM_VERSION="v3.3.4" ENV HELM_VERSION="v3.7.1"
RUN go get -u github.com/onsi/ginkgo/ginkgo RUN go get -u github.com/onsi/ginkgo/ginkgo
RUN wget -q https://storage.googleapis.com/kubernetes-release/release/${KUBECTL_VERSION}/bin/linux/amd64/kubectl -O /usr/local/bin/kubectl && \ RUN wget -q https://storage.googleapis.com/kubernetes-release/release/${KUBECTL_VERSION}/bin/linux/amd64/kubectl -O /usr/local/bin/kubectl && \
@ -10,7 +10,7 @@ RUN wget -q https://storage.googleapis.com/kubernetes-release/release/${KUBECTL_
wget -q https://get.helm.sh/helm-${HELM_VERSION}-linux-amd64.tar.gz -O - | tar -xzO linux-amd64/helm > /usr/local/bin/helm && \ wget -q https://get.helm.sh/helm-${HELM_VERSION}-linux-amd64.tar.gz -O - | tar -xzO linux-amd64/helm > /usr/local/bin/helm && \
chmod +x /usr/local/bin/helm chmod +x /usr/local/bin/helm
FROM alpine:3.12 FROM alpine:3.15.0
RUN apk add -U --no-cache \ RUN apk add -U --no-cache \
ca-certificates \ ca-certificates \
bash \ bash \

View file

@ -4,7 +4,7 @@ SHELL := /bin/bash
IMG_TAG = test IMG_TAG = test
IMG = local/external-secrets-e2e:$(IMG_TAG) IMG = local/external-secrets-e2e:$(IMG_TAG)
KIND_IMG = "kindest/node:v1.20.7@sha256:cbeaf907fc78ac97ce7b625e4bf0de16e3ea725daf6b04f930bd14c67c671ff9" KIND_IMG = "kindest/node:v1.21.1@sha256:69860bda5563ac81e3c0057d654b5253219618a22ec3a346306239bba8cfa1a6"
BUILD_ARGS ?= BUILD_ARGS ?=
export FOCUS := $(FOCUS) export FOCUS := $(FOCUS)
@ -19,13 +19,14 @@ test: e2e-image ## Run e2e tests against current kube context
$(MAKE) -C ../ docker.build \ $(MAKE) -C ../ docker.build \
IMAGE_REGISTRY=local/external-secrets \ IMAGE_REGISTRY=local/external-secrets \
VERSION=$(IMG_TAG) \ VERSION=$(IMG_TAG) \
ARCH=amd64 ARCH=amd64 \
BUILD_ARGS="${BUILD_ARGS} --build-arg TARGETARCH=amd64 --build-arg TARGETOS=linux"
kind load docker-image --name="external-secrets" local/external-secrets:$(IMG_TAG) kind load docker-image --name="external-secrets" local/external-secrets:$(IMG_TAG)
kind load docker-image --name="external-secrets" $(IMG) kind load docker-image --name="external-secrets" $(IMG)
./run.sh ./run.sh
e2e-bin: e2e-bin:
CGO_ENABLED=0 ginkgo build . CGO_ENABLED=0 go run github.com/onsi/ginkgo/ginkgo build .
e2e-image: e2e-bin e2e-image: e2e-bin
-rm -rf ./k8s/deploy -rm -rf ./k8s/deploy

View file

@ -14,7 +14,7 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
set -e set -euo pipefail
NC='\e[0m' NC='\e[0m'
BGREEN='\e[32m' BGREEN='\e[32m'
@ -46,7 +46,7 @@ ginkgo_args=(
kubectl apply -f /k8s/deploy/crds kubectl apply -f /k8s/deploy/crds
echo -e "${BGREEN}Running e2e test suite (FOCUS=${FOCUS})...${NC}" echo -e "${BGREEN}Running e2e test suite (FOCUS=${FOCUS})...${NC}"
ginkgo "${ginkgo_args[@]}" \ ACK_GINKGO_RC=true ginkgo "${ginkgo_args[@]}" \
-focus="${FOCUS}" \ -focus="${FOCUS}" \
-skip="\[Serial\]|\[MemoryLeak\]" \ -skip="\[Serial\]|\[MemoryLeak\]" \
-nodes="${E2E_NODES}" \ -nodes="${E2E_NODES}" \

View file

@ -28,7 +28,7 @@ import (
"os" "os"
"time" "time"
"github.com/golang-jwt/jwt" "github.com/golang-jwt/jwt/v4"
vault "github.com/hashicorp/vault/api" vault "github.com/hashicorp/vault/api"
// nolint // nolint
@ -286,6 +286,7 @@ func (l *Vault) Setup(cfg *Config) error {
return l.chart.Setup(cfg) return l.chart.Setup(cfg)
} }
// nolint:gocritic
func genVaultCertificates(namespace string) ([]byte, []byte, []byte, []byte, []byte, []byte, error) { func genVaultCertificates(namespace string) ([]byte, []byte, []byte, []byte, []byte, []byte, error) {
// gen server ca + certs // gen server ca + certs
serverRootCert, serverRootPem, serverRootKey, err := genCARoot() serverRootCert, serverRootPem, serverRootKey, err := genCARoot()

View file

@ -13,9 +13,7 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
set -o errexit set -euo pipefail
set -o nounset
set -o pipefail
if ! command -v kind --version &> /dev/null; then if ! command -v kind --version &> /dev/null; then
echo "kind is not installed. Use the package manager or visit the official site https://kind.sigs.k8s.io/" echo "kind is not installed. Use the package manager or visit the official site https://kind.sigs.k8s.io/"
@ -56,6 +54,9 @@ kubectl run --rm \
--env="GCP_PROJECT_ID=${GCP_PROJECT_ID:-}" \ --env="GCP_PROJECT_ID=${GCP_PROJECT_ID:-}" \
--env="AZURE_CLIENT_ID=${AZURE_CLIENT_ID:-}" \ --env="AZURE_CLIENT_ID=${AZURE_CLIENT_ID:-}" \
--env="AZURE_CLIENT_SECRET=${AZURE_CLIENT_SECRET:-}" \ --env="AZURE_CLIENT_SECRET=${AZURE_CLIENT_SECRET:-}" \
--env="AKEYLESS_ACCESS_ID=${AKEYLESS_ACCESS_ID:-}" \
--env="AKEYLESS_ACCESS_TYPE=${AKEYLESS_ACCESS_TYPE:-}" \
--env="AKEYLESS_ACCESS_TYPE_PARAM=${AKEYLESS_ACCESS_TYPE_PARAM:-}" \
--env="TENANT_ID=${TENANT_ID:-}" \ --env="TENANT_ID=${TENANT_ID:-}" \
--env="VAULT_URL=${VAULT_URL:-}" \ --env="VAULT_URL=${VAULT_URL:-}" \
--env="GITLAB_TOKEN=${GITLAB_TOKEN:-}" \ --env="GITLAB_TOKEN=${GITLAB_TOKEN:-}" \

View file

@ -0,0 +1,49 @@
/*
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 akeyless
import (
"os"
// nolint
. "github.com/onsi/ginkgo"
// nolint
. "github.com/onsi/ginkgo/extensions/table"
"github.com/external-secrets/external-secrets/e2e/framework"
"github.com/external-secrets/external-secrets/e2e/suite/common"
)
var _ = Describe("[akeyless] ", func() {
f := framework.New("eso-akeyless")
accessID := os.Getenv("AKEYLESS_ACCESS_ID")
accessType := os.Getenv("AKEYLESS_ACCESS_TYPE")
accessTypeParam := os.Getenv("AKEYLESS_ACCESS_TYPE_PARAM")
prov := newAkeylessProvider(f, accessID, accessType, accessTypeParam)
DescribeTable("sync secrets", framework.TableFunc(f, prov),
Entry(common.SimpleDataSync(f)),
Entry(common.NestedJSONWithGJSON(f)),
Entry(common.JSONDataFromSync(f)),
Entry(common.JSONDataWithProperty(f)),
Entry(common.JSONDataWithTemplate(f)),
Entry(common.DockerJSONConfig(f)),
Entry(common.DataPropertyDockerconfigJSON(f)),
Entry(common.SSHKeySync(f)),
Entry(common.SSHKeySyncDataProperty(f)),
Entry(common.SyncWithoutTargetName(f)),
Entry(common.JSONDataWithoutTargetName(f)),
)
})

View file

@ -0,0 +1,227 @@
/*
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 akeyless
import (
"context"
"encoding/base64"
"errors"
"fmt"
"io/ioutil"
"os"
"strings"
aws_cloud_id "github.com/akeylesslabs/akeyless-go-cloud-id/cloudprovider/aws"
azure_cloud_id "github.com/akeylesslabs/akeyless-go-cloud-id/cloudprovider/azure"
gcp_cloud_id "github.com/akeylesslabs/akeyless-go-cloud-id/cloudprovider/gcp"
"github.com/akeylesslabs/akeyless-go/v2"
//nolint
. "github.com/onsi/ginkgo"
//nolint
. "github.com/onsi/gomega"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
esv1alpha1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1alpha1"
esmeta "github.com/external-secrets/external-secrets/apis/meta/v1"
"github.com/external-secrets/external-secrets/e2e/framework"
)
type akeylessProvider struct {
accessID string
accessType string
accessTypeParam string
framework *framework.Framework
restAPIClient *akeyless.V2ApiService
}
var apiErr akeyless.GenericOpenAPIError
const DefServiceAccountFile = "/var/run/secrets/kubernetes.io/serviceaccount/token"
func newAkeylessProvider(f *framework.Framework, accessID, accessType, accessTypeParam string) *akeylessProvider {
prov := &akeylessProvider{
accessID: accessID,
accessType: accessType,
accessTypeParam: accessTypeParam,
framework: f,
}
restAPIClient := akeyless.NewAPIClient(&akeyless.Configuration{
Servers: []akeyless.ServerConfiguration{
{
URL: "https://api.akeyless.io",
},
},
}).V2Api
prov.restAPIClient = restAPIClient
BeforeEach(prov.BeforeEach)
return prov
}
// CreateSecret creates a secret.
func (a *akeylessProvider) CreateSecret(key, val string) {
token, err := a.GetToken()
Expect(err).ToNot(HaveOccurred())
ctx := context.Background()
gsvBody := akeyless.CreateSecret{
Name: key,
Value: val,
Token: &token,
}
_, _, err = a.restAPIClient.CreateSecret(ctx).Body(gsvBody).Execute()
Expect(err).ToNot(HaveOccurred())
}
func (a *akeylessProvider) DeleteSecret(key string) {
token, err := a.GetToken()
Expect(err).ToNot(HaveOccurred())
ctx := context.Background()
gsvBody := akeyless.DeleteItem{
Name: key,
Token: &token,
}
_, _, err = a.restAPIClient.DeleteItem(ctx).Body(gsvBody).Execute()
Expect(err).ToNot(HaveOccurred())
}
func (a *akeylessProvider) BeforeEach() {
// Creating an Akeyless secret
akeylessCreds := &v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "provider-secret",
Namespace: a.framework.Namespace.Name,
},
StringData: map[string]string{
"access-id": a.accessID,
"access-type": a.accessType,
"access-type-param": a.accessTypeParam,
},
}
err := a.framework.CRClient.Create(context.Background(), akeylessCreds)
Expect(err).ToNot(HaveOccurred())
// Creating Akeyless secret store
secretStore := &esv1alpha1.SecretStore{
ObjectMeta: metav1.ObjectMeta{
Name: a.framework.Namespace.Name,
Namespace: a.framework.Namespace.Name,
},
Spec: esv1alpha1.SecretStoreSpec{
Provider: &esv1alpha1.SecretStoreProvider{
Akeyless: &esv1alpha1.AkeylessProvider{
Auth: &esv1alpha1.AkeylessAuth{
SecretRef: esv1alpha1.AkeylessAuthSecretRef{
AccessID: esmeta.SecretKeySelector{
Name: "access-id-secret",
Key: "access-id",
},
AccessType: esmeta.SecretKeySelector{
Name: "access-type-secret",
Key: "access-type",
},
AccessTypeParam: esmeta.SecretKeySelector{
Name: "access-type-param-secert",
Key: "access-type-param",
},
},
},
},
},
},
}
err = a.framework.CRClient.Create(context.Background(), secretStore)
Expect(err).ToNot(HaveOccurred())
}
func (a *akeylessProvider) GetToken() (string, error) {
ctx := context.Background()
authBody := akeyless.NewAuthWithDefaults()
authBody.AccessId = akeyless.PtrString(a.accessID)
if a.accessType == "api_key" {
authBody.AccessKey = akeyless.PtrString(a.accessTypeParam)
} else if a.accessType == "k8s" {
jwtString, err := readK8SServiceAccountJWT()
if err != nil {
return "", fmt.Errorf("failed to read JWT with Kubernetes Auth from %v. error: %w", DefServiceAccountFile, err)
}
K8SAuthConfigName := a.accessTypeParam
authBody.AccessType = akeyless.PtrString(a.accessType)
authBody.K8sServiceAccountToken = akeyless.PtrString(jwtString)
authBody.K8sAuthConfigName = akeyless.PtrString(K8SAuthConfigName)
} else {
cloudID, err := a.getCloudID(a.accessType, a.accessTypeParam)
if err != nil {
return "", fmt.Errorf("Require Cloud ID " + err.Error())
}
authBody.AccessType = akeyless.PtrString(a.accessType)
authBody.CloudId = akeyless.PtrString(cloudID)
}
authOut, _, err := a.restAPIClient.Auth(ctx).Body(*authBody).Execute()
if err != nil {
if errors.As(err, &apiErr) {
return "", fmt.Errorf("authentication failed: %v", string(apiErr.Body()))
}
return "", fmt.Errorf("authentication failed: %w", err)
}
token := authOut.GetToken()
return token, nil
}
func (a *akeylessProvider) getCloudID(provider, accTypeParam string) (string, error) {
var cloudID string
var err error
switch provider {
case "azure_ad":
cloudID, err = azure_cloud_id.GetCloudId(accTypeParam)
case "aws_iam":
cloudID, err = aws_cloud_id.GetCloudId()
case "gcp":
cloudID, err = gcp_cloud_id.GetCloudID(accTypeParam)
default:
return "", fmt.Errorf("unable to determine provider: %s", provider)
}
return cloudID, err
}
// readK8SServiceAccountJWT reads the JWT data for the Agent to submit to Akeyless Gateway.
func readK8SServiceAccountJWT() (string, error) {
data, err := os.Open(DefServiceAccountFile)
if err != nil {
return "", err
}
defer data.Close()
contentBytes, err := ioutil.ReadAll(data)
if err != nil {
return "", err
}
a := strings.TrimSpace(string(contentBytes))
return base64.StdEncoding.EncodeToString([]byte(a)), nil
}

View file

@ -31,7 +31,11 @@ var _ = Describe("[alibaba] ", func() {
accessKeyID := os.Getenv("ACCESS_KEY_ID") accessKeyID := os.Getenv("ACCESS_KEY_ID")
accessKeySecret := os.Getenv("ACCESS_KEY_SECRET") accessKeySecret := os.Getenv("ACCESS_KEY_SECRET")
regionID := os.Getenv("REGION_ID") regionID := os.Getenv("REGION_ID")
prov := newAlibabaProvider(f, accessKeyID, accessKeySecret, regionID) prov := &alibabaProvider{}
if accessKeyID != "" && accessKeySecret != "" && regionID != "" {
prov = newAlibabaProvider(f, accessKeyID, accessKeySecret, regionID)
}
DescribeTable("sync secrets", framework.TableFunc(f, prov), DescribeTable("sync secrets", framework.TableFunc(f, prov),
Entry(common.SimpleDataSync(f)), Entry(common.SimpleDataSync(f)),

View file

@ -30,7 +30,11 @@ var _ = Describe("[azure] ", func() {
tenantID := os.Getenv("TENANT_ID") tenantID := os.Getenv("TENANT_ID")
clientID := os.Getenv("AZURE_CLIENT_ID") clientID := os.Getenv("AZURE_CLIENT_ID")
clientSecret := os.Getenv("AZURE_CLIENT_SECRET") clientSecret := os.Getenv("AZURE_CLIENT_SECRET")
prov := newazureProvider(f, clientID, clientSecret, tenantID, vaultURL) prov := &azureProvider{}
if vaultURL != "" && tenantID != "" && clientID != "" && clientSecret != "" {
prov = newazureProvider(f, clientID, clientSecret, tenantID, vaultURL)
}
DescribeTable("sync secrets", framework.TableFunc(f, prov), DescribeTable("sync secrets", framework.TableFunc(f, prov),
Entry(common.SimpleDataSync(f)), Entry(common.SimpleDataSync(f)),

View file

@ -22,6 +22,14 @@ import (
"github.com/external-secrets/external-secrets/e2e/framework" "github.com/external-secrets/external-secrets/e2e/framework"
) )
const (
// Constants.
dockerConfigExampleName = "docker-config-example"
dockerConfigJSONKey = ".dockerconfigjson"
mysecretToStringTemplating = "{{ .mysecret | toString }}"
sshPrivateKey = "ssh-privatekey"
)
// This case creates multiple secrets with simple key/value pairs and syncs them using multiple .Spec.Data blocks. // This case creates multiple secrets with simple key/value pairs and syncs them using multiple .Spec.Data blocks.
// Not supported by: vault. // Not supported by: vault.
func SimpleDataSync(f *framework.Framework) (string, func(*framework.TestCase)) { func SimpleDataSync(f *framework.Framework) (string, func(*framework.TestCase)) {
@ -289,7 +297,7 @@ func NestedJSONWithGJSON(f *framework.Framework) (string, func(*framework.TestCa
// not supported by: vault. // not supported by: vault.
func DockerJSONConfig(f *framework.Framework) (string, func(*framework.TestCase)) { func DockerJSONConfig(f *framework.Framework) (string, func(*framework.TestCase)) {
return "[common] should sync docker configurated json secrets with template simple", func(tc *framework.TestCase) { return "[common] should sync docker configurated json secrets with template simple", func(tc *framework.TestCase) {
cloudSecretName := fmt.Sprintf("%s-%s", f.Namespace.Name, "docker-config-example") cloudSecretName := fmt.Sprintf("%s-%s", f.Namespace.Name, dockerConfigExampleName)
dockerconfig := `{"auths":{"https://index.docker.io/v1/": {"auth": "c3R...zE2"}}}` dockerconfig := `{"auths":{"https://index.docker.io/v1/": {"auth": "c3R...zE2"}}}`
cloudSecretValue := fmt.Sprintf(`{"dockerconfig": %s}`, dockerconfig) cloudSecretValue := fmt.Sprintf(`{"dockerconfig": %s}`, dockerconfig)
tc.Secrets = map[string]string{ tc.Secrets = map[string]string{
@ -299,7 +307,7 @@ func DockerJSONConfig(f *framework.Framework) (string, func(*framework.TestCase)
tc.ExpectedSecret = &v1.Secret{ tc.ExpectedSecret = &v1.Secret{
Type: v1.SecretTypeOpaque, Type: v1.SecretTypeOpaque,
Data: map[string][]byte{ Data: map[string][]byte{
".dockerconfigjson": []byte(dockerconfig), dockerConfigJSONKey: []byte(dockerconfig),
}, },
} }
@ -315,7 +323,7 @@ func DockerJSONConfig(f *framework.Framework) (string, func(*framework.TestCase)
tc.ExternalSecret.Spec.Target.Template = &esv1alpha1.ExternalSecretTemplate{ tc.ExternalSecret.Spec.Target.Template = &esv1alpha1.ExternalSecretTemplate{
Data: map[string]string{ Data: map[string]string{
".dockerconfigjson": "{{ .mysecret | toString }}", dockerConfigJSONKey: mysecretToStringTemplating,
}, },
} }
} }
@ -326,7 +334,7 @@ func DockerJSONConfig(f *framework.Framework) (string, func(*framework.TestCase)
// Need to have a key holding dockerconfig to be supported by vault. // Need to have a key holding dockerconfig to be supported by vault.
func DataPropertyDockerconfigJSON(f *framework.Framework) (string, func(*framework.TestCase)) { func DataPropertyDockerconfigJSON(f *framework.Framework) (string, func(*framework.TestCase)) {
return "[common] should sync docker configurated json secrets with template", func(tc *framework.TestCase) { return "[common] should sync docker configurated json secrets with template", func(tc *framework.TestCase) {
cloudSecretName := fmt.Sprintf("%s-%s", f.Namespace.Name, "docker-config-example") cloudSecretName := fmt.Sprintf("%s-%s", f.Namespace.Name, dockerConfigExampleName)
dockerconfigString := `"{\"auths\":{\"https://index.docker.io/v1/\": {\"auth\": \"c3R...zE2\"}}}"` dockerconfigString := `"{\"auths\":{\"https://index.docker.io/v1/\": {\"auth\": \"c3R...zE2\"}}}"`
dockerconfig := `{"auths":{"https://index.docker.io/v1/": {"auth": "c3R...zE2"}}}` dockerconfig := `{"auths":{"https://index.docker.io/v1/": {"auth": "c3R...zE2"}}}`
cloudSecretValue := fmt.Sprintf(`{"dockerconfig": %s}`, dockerconfigString) cloudSecretValue := fmt.Sprintf(`{"dockerconfig": %s}`, dockerconfigString)
@ -337,7 +345,7 @@ func DataPropertyDockerconfigJSON(f *framework.Framework) (string, func(*framewo
tc.ExpectedSecret = &v1.Secret{ tc.ExpectedSecret = &v1.Secret{
Type: v1.SecretTypeDockerConfigJson, Type: v1.SecretTypeDockerConfigJson,
Data: map[string][]byte{ Data: map[string][]byte{
".dockerconfigjson": []byte(dockerconfig), dockerConfigJSONKey: []byte(dockerconfig),
}, },
} }
@ -354,7 +362,7 @@ func DataPropertyDockerconfigJSON(f *framework.Framework) (string, func(*framewo
tc.ExternalSecret.Spec.Target.Template = &esv1alpha1.ExternalSecretTemplate{ tc.ExternalSecret.Spec.Target.Template = &esv1alpha1.ExternalSecretTemplate{
Type: v1.SecretTypeDockerConfigJson, Type: v1.SecretTypeDockerConfigJson,
Data: map[string]string{ Data: map[string]string{
".dockerconfigjson": "{{ .mysecret | toString }}", dockerConfigJSONKey: mysecretToStringTemplating,
}, },
} }
} }
@ -411,7 +419,7 @@ func SSHKeySync(f *framework.Framework) (string, func(*framework.TestCase)) {
tc.ExpectedSecret = &v1.Secret{ tc.ExpectedSecret = &v1.Secret{
Type: v1.SecretTypeSSHAuth, Type: v1.SecretTypeSSHAuth,
Data: map[string][]byte{ Data: map[string][]byte{
"ssh-privatekey": []byte(sshSecretValue), sshPrivateKey: []byte(sshSecretValue),
}, },
} }
@ -427,7 +435,7 @@ func SSHKeySync(f *framework.Framework) (string, func(*framework.TestCase)) {
tc.ExternalSecret.Spec.Target.Template = &esv1alpha1.ExternalSecretTemplate{ tc.ExternalSecret.Spec.Target.Template = &esv1alpha1.ExternalSecretTemplate{
Type: v1.SecretTypeSSHAuth, Type: v1.SecretTypeSSHAuth,
Data: map[string]string{ Data: map[string]string{
"ssh-privatekey": "{{ .mysecret | toString }}", sshPrivateKey: mysecretToStringTemplating,
}, },
} }
} }
@ -436,7 +444,7 @@ func SSHKeySync(f *framework.Framework) (string, func(*framework.TestCase)) {
// This case adds an ssh private key secret and syncs it. // This case adds an ssh private key secret and syncs it.
func SSHKeySyncDataProperty(f *framework.Framework) (string, func(*framework.TestCase)) { func SSHKeySyncDataProperty(f *framework.Framework) (string, func(*framework.TestCase)) {
return "[common] should sync ssh key with provider.", func(tc *framework.TestCase) { return "[common] should sync ssh key with provider.", func(tc *framework.TestCase) {
cloudSecretName := fmt.Sprintf("%s-%s", f.Namespace.Name, "docker-config-example") cloudSecretName := fmt.Sprintf("%s-%s", f.Namespace.Name, dockerConfigExampleName)
SSHKey := `-----BEGIN OPENSSH PRIVATE KEY----- SSHKey := `-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABlwAAAAdzc2gtcn b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABlwAAAAdzc2gtcn
NhAAAAAwEAAQAAAYEAsARoZUqo6L5dd0WRjZ2QPq/kKlbjtUY1njzJ01UtdC1u1eSJFUnV NhAAAAAwEAAQAAAYEAsARoZUqo6L5dd0WRjZ2QPq/kKlbjtUY1njzJ01UtdC1u1eSJFUnV
@ -483,7 +491,7 @@ func SSHKeySyncDataProperty(f *framework.Framework) (string, func(*framework.Tes
tc.ExpectedSecret = &v1.Secret{ tc.ExpectedSecret = &v1.Secret{
Type: v1.SecretTypeSSHAuth, Type: v1.SecretTypeSSHAuth,
Data: map[string][]byte{ Data: map[string][]byte{
"ssh-privatekey": []byte(SSHKey), sshPrivateKey: []byte(SSHKey),
}, },
} }
@ -500,7 +508,7 @@ func SSHKeySyncDataProperty(f *framework.Framework) (string, func(*framework.Tes
tc.ExternalSecret.Spec.Target.Template = &esv1alpha1.ExternalSecretTemplate{ tc.ExternalSecret.Spec.Target.Template = &esv1alpha1.ExternalSecretTemplate{
Type: v1.SecretTypeSSHAuth, Type: v1.SecretTypeSSHAuth,
Data: map[string]string{ Data: map[string]string{
"ssh-privatekey": "{{ .mysecret | toString }}", sshPrivateKey: mysecretToStringTemplating,
}, },
} }
} }

View file

@ -36,7 +36,11 @@ var _ = Describe("[gcp] ", func() {
f := framework.New("eso-gcp") f := framework.New("eso-gcp")
credentials := os.Getenv("GCP_SM_SA_JSON") credentials := os.Getenv("GCP_SM_SA_JSON")
projectID := os.Getenv("GCP_PROJECT_ID") projectID := os.Getenv("GCP_PROJECT_ID")
prov := newgcpProvider(f, credentials, projectID) prov := &gcpProvider{}
if credentials != "" && projectID != "" {
prov = newgcpProvider(f, credentials, projectID)
}
// P12Cert case creates a secret with a p12 cert containing a privkey and cert bundled together. // P12Cert case creates a secret with a p12 cert containing a privkey and cert bundled together.
// It uses templating to generate a k8s secret of type tls with pem values // It uses templating to generate a k8s secret of type tls with pem values

View file

@ -33,7 +33,12 @@ var _ = Describe("[gitlab] ", func() {
f := framework.New("esogitlab") f := framework.New("esogitlab")
credentials := os.Getenv("GITLAB_TOKEN") credentials := os.Getenv("GITLAB_TOKEN")
projectID := os.Getenv("GITLAB_PROJECT_ID") projectID := os.Getenv("GITLAB_PROJECT_ID")
prov := newGitlabProvider(f, credentials, projectID)
prov := &gitlabProvider{}
if credentials != "" && projectID != "" {
prov = newGitlabProvider(f, credentials, projectID)
}
DescribeTable("sync secrets", framework.TableFunc(f, prov), DescribeTable("sync secrets", framework.TableFunc(f, prov),
Entry(common.SimpleDataSync(f)), Entry(common.SimpleDataSync(f)),

View file

@ -23,6 +23,15 @@ import (
"github.com/external-secrets/external-secrets/e2e/suite/common" "github.com/external-secrets/external-secrets/e2e/suite/common"
) )
const (
withTokenAuth = "with token auth"
withCertAuth = "with cert auth"
withApprole = "with approle auth"
withV1 = "with v1 provider"
withJWT = "with jwt provider"
withK8s = "with kubernetes provider"
)
var _ = Describe("[vault] ", func() { var _ = Describe("[vault] ", func() {
f := framework.New("eso-vault") f := framework.New("eso-vault")
@ -30,41 +39,41 @@ var _ = Describe("[vault] ", func() {
framework.TableFunc(f, framework.TableFunc(f,
newVaultProvider(f)), newVaultProvider(f)),
// uses token auth // uses token auth
compose("with token auth", f, common.JSONDataFromSync, useTokenAuth), compose(withTokenAuth, f, common.JSONDataFromSync, useTokenAuth),
compose("with token auth", f, common.JSONDataWithProperty, useTokenAuth), compose(withTokenAuth, f, common.JSONDataWithProperty, useTokenAuth),
compose("with token auth", f, common.JSONDataWithTemplate, useTokenAuth), compose(withTokenAuth, f, common.JSONDataWithTemplate, useTokenAuth),
compose("with token auth", f, common.DataPropertyDockerconfigJSON, useTokenAuth), compose(withTokenAuth, f, common.DataPropertyDockerconfigJSON, useTokenAuth),
compose("with token auth", f, common.JSONDataWithoutTargetName, useTokenAuth), compose(withTokenAuth, f, common.JSONDataWithoutTargetName, useTokenAuth),
// use cert auth // use cert auth
compose("with cert auth", f, common.JSONDataFromSync, useCertAuth), compose(withCertAuth, f, common.JSONDataFromSync, useCertAuth),
compose("with cert auth", f, common.JSONDataWithProperty, useCertAuth), compose(withCertAuth, f, common.JSONDataWithProperty, useCertAuth),
compose("with cert auth", f, common.JSONDataWithTemplate, useCertAuth), compose(withCertAuth, f, common.JSONDataWithTemplate, useCertAuth),
compose("with cert auth", f, common.DataPropertyDockerconfigJSON, useCertAuth), compose(withCertAuth, f, common.DataPropertyDockerconfigJSON, useCertAuth),
compose("with cert auth", f, common.JSONDataWithoutTargetName, useTokenAuth), compose(withCertAuth, f, common.JSONDataWithoutTargetName, useTokenAuth),
// use approle auth // use approle auth
compose("with appRole auth", f, common.JSONDataFromSync, useApproleAuth), compose(withApprole, f, common.JSONDataFromSync, useApproleAuth),
compose("with appRole auth", f, common.JSONDataWithProperty, useApproleAuth), compose(withApprole, f, common.JSONDataWithProperty, useApproleAuth),
compose("with appRole auth", f, common.JSONDataWithTemplate, useApproleAuth), compose(withApprole, f, common.JSONDataWithTemplate, useApproleAuth),
compose("with appRole auth", f, common.DataPropertyDockerconfigJSON, useApproleAuth), compose(withApprole, f, common.DataPropertyDockerconfigJSON, useApproleAuth),
compose("with appRole auth", f, common.JSONDataWithoutTargetName, useTokenAuth), compose(withApprole, f, common.JSONDataWithoutTargetName, useTokenAuth),
// use v1 provider // use v1 provider
compose("with v1 kv provider", f, common.JSONDataFromSync, useV1Provider), compose(withV1, f, common.JSONDataFromSync, useV1Provider),
compose("with v1 kv provider", f, common.JSONDataWithProperty, useV1Provider), compose(withV1, f, common.JSONDataWithProperty, useV1Provider),
compose("with v1 kv provider", f, common.JSONDataWithTemplate, useV1Provider), compose(withV1, f, common.JSONDataWithTemplate, useV1Provider),
compose("with v1 kv provider", f, common.DataPropertyDockerconfigJSON, useV1Provider), compose(withV1, f, common.DataPropertyDockerconfigJSON, useV1Provider),
compose("with v1 kv provider", f, common.JSONDataWithoutTargetName, useTokenAuth), compose(withV1, f, common.JSONDataWithoutTargetName, useTokenAuth),
// use jwt provider // use jwt provider
compose("with jwt provider", f, common.JSONDataFromSync, useJWTProvider), compose(withJWT, f, common.JSONDataFromSync, useJWTProvider),
compose("with jwt provider", f, common.JSONDataWithProperty, useJWTProvider), compose(withJWT, f, common.JSONDataWithProperty, useJWTProvider),
compose("with jwt provider", f, common.JSONDataWithTemplate, useJWTProvider), compose(withJWT, f, common.JSONDataWithTemplate, useJWTProvider),
compose("with jwt provider", f, common.DataPropertyDockerconfigJSON, useJWTProvider), compose(withJWT, f, common.DataPropertyDockerconfigJSON, useJWTProvider),
compose("with jwt provider", f, common.JSONDataWithoutTargetName, useTokenAuth), compose(withJWT, f, common.JSONDataWithoutTargetName, useTokenAuth),
// use kubernetes provider // use kubernetes provider
compose("with kubernetes provider", f, common.JSONDataFromSync, useKubernetesProvider), compose(withK8s, f, common.JSONDataFromSync, useKubernetesProvider),
compose("with kubernetes provider", f, common.JSONDataWithProperty, useKubernetesProvider), compose(withK8s, f, common.JSONDataWithProperty, useKubernetesProvider),
compose("with kubernetes provider", f, common.JSONDataWithTemplate, useKubernetesProvider), compose(withK8s, f, common.JSONDataWithTemplate, useKubernetesProvider),
compose("with kubernetes provider", f, common.DataPropertyDockerconfigJSON, useKubernetesProvider), compose(withK8s, f, common.DataPropertyDockerconfigJSON, useKubernetesProvider),
compose("with kubernetes provider", f, common.JSONDataWithoutTargetName, useTokenAuth), compose(withK8s, f, common.JSONDataWithoutTargetName, useTokenAuth),
) )
}) })

22
go.mod
View file

@ -33,20 +33,23 @@ replace (
) )
require ( require (
cloud.google.com/go v0.65.0 cloud.google.com/go v0.81.0
github.com/Azure/azure-sdk-for-go v54.1.0+incompatible github.com/Azure/azure-sdk-for-go v54.1.0+incompatible
github.com/Azure/go-autorest/autorest/azure/auth v0.5.7 github.com/Azure/go-autorest/autorest/azure/auth v0.5.7
github.com/Azure/go-autorest/autorest/to v0.4.0 // indirect github.com/Azure/go-autorest/autorest/to v0.4.0 // indirect
github.com/Azure/go-autorest/autorest/validation v0.3.1 // indirect github.com/Azure/go-autorest/autorest/validation v0.3.1 // indirect
github.com/IBM/go-sdk-core/v5 v5.5.0 github.com/IBM/go-sdk-core/v5 v5.5.0
github.com/IBM/secrets-manager-go-sdk v1.0.23 github.com/IBM/secrets-manager-go-sdk v1.0.23
github.com/akeylesslabs/akeyless-go-cloud-id v0.3.2
github.com/akeylesslabs/akeyless-go/v2 v2.5.11
github.com/aliyun/alibaba-cloud-sdk-go v1.61.1192 github.com/aliyun/alibaba-cloud-sdk-go v1.61.1192
github.com/aws/aws-sdk-go v1.38.6 github.com/aws/aws-sdk-go v1.38.6
github.com/crossplane/crossplane-runtime v0.13.0 github.com/crossplane/crossplane-runtime v0.13.0
github.com/fatih/color v1.10.0 // indirect github.com/fatih/color v1.10.0 // indirect
github.com/frankban/quicktest v1.10.0 // indirect github.com/frankban/quicktest v1.10.0 // indirect
github.com/fsnotify/fsnotify v1.5.1 // indirect
github.com/go-logr/logr v0.4.0 github.com/go-logr/logr v0.4.0
github.com/golang-jwt/jwt v3.2.2+incompatible github.com/golang-jwt/jwt/v4 v4.2.0
github.com/google/go-cmp v0.5.5 github.com/google/go-cmp v0.5.5
github.com/google/gofuzz v1.2.0 // indirect github.com/google/gofuzz v1.2.0 // indirect
github.com/google/uuid v1.2.0 github.com/google/uuid v1.2.0
@ -57,7 +60,7 @@ require (
github.com/hashicorp/vault/api v1.0.5-0.20210224012239-b540be4b7ec4 github.com/hashicorp/vault/api v1.0.5-0.20210224012239-b540be4b7ec4
github.com/kr/pretty v0.2.1 // indirect github.com/kr/pretty v0.2.1 // indirect
github.com/lestrrat-go/jwx v1.2.1 github.com/lestrrat-go/jwx v1.2.1
github.com/onsi/ginkgo v1.16.4 github.com/onsi/ginkgo v1.16.5
github.com/onsi/gomega v1.16.0 github.com/onsi/gomega v1.16.0
github.com/oracle/oci-go-sdk/v45 v45.2.0 github.com/oracle/oci-go-sdk/v45 v45.2.0
github.com/pierrec/lz4 v2.5.2+incompatible // indirect github.com/pierrec/lz4 v2.5.2+incompatible // indirect
@ -65,7 +68,7 @@ require (
github.com/prometheus/client_model v0.2.0 github.com/prometheus/client_model v0.2.0
github.com/spf13/cobra v1.1.3 // indirect github.com/spf13/cobra v1.1.3 // indirect
github.com/stretchr/testify v1.7.0 github.com/stretchr/testify v1.7.0
github.com/tidwall/gjson v1.7.5 github.com/tidwall/gjson v1.12.1
github.com/xanzy/go-gitlab v0.50.1 github.com/xanzy/go-gitlab v0.50.1
github.com/yandex-cloud/go-genproto v0.0.0-20210809082946-a97da516c588 github.com/yandex-cloud/go-genproto v0.0.0-20210809082946-a97da516c588
github.com/yandex-cloud/go-sdk v0.0.0-20210809100642-c13c40a429fa github.com/yandex-cloud/go-sdk v0.0.0-20210809100642-c13c40a429fa
@ -73,11 +76,12 @@ require (
go.uber.org/zap v1.17.0 go.uber.org/zap v1.17.0
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83 golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83
golang.org/x/exp v0.0.0-20200331195152-e8c3332aa8e5 // indirect golang.org/x/exp v0.0.0-20200331195152-e8c3332aa8e5 // indirect
golang.org/x/oauth2 v0.0.0-20210201163806-010130855d6c golang.org/x/oauth2 v0.0.0-20210413134643-5e61552d6c78
golang.org/x/tools v0.1.2-0.20210512205948-8287d5da45e4 // indirect golang.org/x/sys v0.0.0-20211102061401-a2f17f7b995c // indirect
google.golang.org/api v0.30.0 golang.org/x/tools v0.1.7 // indirect
google.golang.org/genproto v0.0.0-20201110150050-8816d57aaa9a google.golang.org/api v0.45.0
google.golang.org/grpc v1.31.0 google.golang.org/genproto v0.0.0-20210413151531-c14fb6ef47c3
google.golang.org/grpc v1.37.0
honnef.co/go/tools v0.1.4 // indirect honnef.co/go/tools v0.1.4 // indirect
k8s.io/api v0.21.3 k8s.io/api v0.21.3
k8s.io/apimachinery v0.21.3 k8s.io/apimachinery v0.21.3

119
go.sum
View file

@ -11,8 +11,13 @@ cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bP
cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
cloud.google.com/go v0.65.0 h1:Dg9iHVQfrhq82rUNu9ZxUDrJLaxFUe/HlCVaLyRruq8=
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI=
cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk=
cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg=
cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8=
cloud.google.com/go v0.81.0 h1:at8Tk2zUz63cLPR0JPWm5vp77pEZmzxEQBEfRKn1VV8=
cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
@ -72,6 +77,10 @@ github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMo
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
github.com/akeylesslabs/akeyless-go-cloud-id v0.3.2 h1:1h4udX3Y5KgSG0m4Th2bHfaYxZB9fbngiij9PrKEp6c=
github.com/akeylesslabs/akeyless-go-cloud-id v0.3.2/go.mod h1:ionnWiARf5TYoFGuHS7syh51/7lYosZejpZbnECFJcU=
github.com/akeylesslabs/akeyless-go/v2 v2.5.11 h1:Z7xJAUPk1h4/YGS7yr/nx9RSMaSESzPwClqe21WFYHo=
github.com/akeylesslabs/akeyless-go/v2 v2.5.11/go.mod h1:uOdXD49NCCe4rexeSc2aBU5Qv4KZgJE6YlbtYalvb+I=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
@ -92,6 +101,8 @@ github.com/aws/aws-sdk-go v1.25.37/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpi
github.com/aws/aws-sdk-go v1.34.28/go.mod h1:H7NKnBqNVzoTJpGfLrQkkD+ytBA93eiDYi/+8rV9s48= github.com/aws/aws-sdk-go v1.34.28/go.mod h1:H7NKnBqNVzoTJpGfLrQkkD+ytBA93eiDYi/+8rV9s48=
github.com/aws/aws-sdk-go v1.38.6 h1:h0AKIaz/A1kEJ50HxCv7tL1GW+KbxYbp75+lZ/nvFOI= github.com/aws/aws-sdk-go v1.38.6 h1:h0AKIaz/A1kEJ50HxCv7tL1GW+KbxYbp75+lZ/nvFOI=
github.com/aws/aws-sdk-go v1.38.6/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro= github.com/aws/aws-sdk-go v1.38.6/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
github.com/aws/aws-sdk-go-v2 v0.23.0 h1:+E1q1LLSfHSDn/DzOtdJOX+pLZE2HiNV2yO5AjZINwM=
github.com/aws/aws-sdk-go-v2 v0.23.0/go.mod h1:2LhT7UgHOXK3UXONKI5OMgIyoQL6zTAw/jwIeX6yqzw=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
@ -168,8 +179,9 @@ github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoD
github.com/frankban/quicktest v1.10.0 h1:Gfh+GAJZOAoKZsIZeZbdn2JF10kN1XHNvjsvQK8gVkE= github.com/frankban/quicktest v1.10.0 h1:Gfh+GAJZOAoKZsIZeZbdn2JF10kN1XHNvjsvQK8gVkE=
github.com/frankban/quicktest v1.10.0/go.mod h1:ui7WezCLWMWxVWr1GETZY3smRy0G4KWq9vcPtJmFl7Y= github.com/frankban/quicktest v1.10.0/go.mod h1:ui7WezCLWMWxVWr1GETZY3smRy0G4KWq9vcPtJmFl7Y=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI=
github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU=
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-asn1-ber/asn1-ber v1.3.1/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= github.com/go-asn1-ber/asn1-ber v1.3.1/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
@ -210,6 +222,7 @@ github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
github.com/go-test/deep v1.0.2-0.20181118220953-042da051cf31/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= github.com/go-test/deep v1.0.2-0.20181118220953-042da051cf31/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
github.com/go-test/deep v1.0.2 h1:onZX1rnHT3Wv6cqNgYyFOOlgVKJrksuCMCRvJStbMYw= github.com/go-test/deep v1.0.2 h1:onZX1rnHT3Wv6cqNgYyFOOlgVKJrksuCMCRvJStbMYw=
@ -248,8 +261,8 @@ github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zV
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= 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/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptGaCkuDUx6wNykzzlUixGxvkme+H/lnzb+A=
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= github.com/golang-jwt/jwt/v4 v4.2.0 h1:besgBTC8w8HjP6NzQdxwKH9Z5oQMZ24ThTrHp3cZ8eU=
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
@ -265,6 +278,7 @@ github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
@ -280,6 +294,7 @@ github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QD
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM=
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
@ -294,6 +309,7 @@ github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
@ -305,6 +321,7 @@ github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
@ -312,6 +329,10 @@ github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hf
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
@ -394,6 +415,7 @@ github.com/hashicorp/vault/sdk v0.1.14-0.20200519221838-e0cfd64bc267/go.mod h1:W
github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
github.com/imdario/mergo v0.3.10/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/imdario/mergo v0.3.10/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU=
@ -533,8 +555,9 @@ github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108
github.com/onsi/ginkgo v1.14.1/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= github.com/onsi/ginkgo v1.14.1/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
github.com/onsi/ginkgo v1.14.2/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= github.com/onsi/ginkgo v1.14.2/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
github.com/onsi/ginkgo v1.16.2/go.mod h1:CObGmKUOKaSC0RjmoAK7tKyn4Azo5P2IWuoMnvwxz1E= github.com/onsi/ginkgo v1.16.2/go.mod h1:CObGmKUOKaSC0RjmoAK7tKyn4Azo5P2IWuoMnvwxz1E=
github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc=
github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
@ -645,13 +668,13 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/tidwall/gjson v1.7.5 h1:zmAN/xmX7OtpAkv4Ovfso60r/BiCi5IErCDYGNJu+uc= github.com/tidwall/gjson v1.12.1 h1:ikuZsLdhr8Ws0IdROXUS1Gi4v9Z4pGqpX/CvJkxvfpo=
github.com/tidwall/gjson v1.7.5/go.mod h1:5/xDoumyyDNerp2U36lyolv46b3uF/9Bu6OfyQ9GImk= github.com/tidwall/gjson v1.12.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/match v1.0.3 h1:FQUVvBImDutD8wJLN6c5eMzWtjgONK9MwIBCOrUJKeE= github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
github.com/tidwall/match v1.0.3/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
github.com/tidwall/pretty v1.1.0 h1:K3hMW5epkdAVwibsQEfR/7Zj0Qgt4DxtNumTq/VloO8= github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
github.com/tidwall/pretty v1.1.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
@ -676,7 +699,7 @@ github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
@ -687,8 +710,10 @@ go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.4 h1:LYy1Hy3MJdrCdMwwzxA/dRok4ejH+RwNGbuoD9fCjto=
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M=
go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
@ -747,8 +772,9 @@ golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHl
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b h1:Wh+f8QHJXR411sJR8/vRBTZ7YapZaRvUcLFFJhusH0k=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5 h1:2M3HP5CCK1Si9FQhwnzYhXdG6DXeebvUHFpre8QvbyI=
golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
@ -758,6 +784,7 @@ golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzB
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.1-0.20200828183125-ce943fd02449/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.1-0.20200828183125-ce943fd02449/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo= golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
@ -800,19 +827,30 @@ golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81R
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0/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-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= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210224082022-3d97a244fca7/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210224082022-3d97a244fca7/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781 h1:DzZ89McO9/gWPsQXS/FVKAlG02ZjaQ6AlZRBimEYOd0= golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d h1:20cMwl2fHAzkJMEA+8J4JgqBQcQGzbisXo31MIeenXI=
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20210201163806-010130855d6c h1:HiAZXo96zOhVhtFHchj/ojzoxCFiPrp9/j0GtS38V3g= golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210201163806-010130855d6c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210413134643-5e61552d6c78 h1:rPRtHfUb0UKZeZ6GH4K4Nt4YRbE9V1u+QZX5upZXqJQ=
golang.org/x/oauth2 v0.0.0-20210413134643-5e61552d6c78/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@ -875,17 +913,26 @@ golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210412220455-f1c623a9e750/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210426230700-d19ff857e887/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210426230700-d19ff857e887/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40 h1:JWgyZ1qgdTaF3N3oxC+MdTV7qvEEgHo3otj+HB5CM7Q=
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/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-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211102061401-a2f17f7b995c h1:QOfDMdrf/UwlVR0UBq2Mpr58UzNtvgJRXA4BgPfFACs=
golang.org/x/sys v0.0.0-20211102061401-a2f17f7b995c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d h1:SZxvLBoTP5yHO3Frd4z4vrF+DBX9vMVanchswa69toE= golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d h1:SZxvLBoTP5yHO3Frd4z4vrF+DBX9vMVanchswa69toE=
@ -963,13 +1010,18 @@ golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roY
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
golang.org/x/tools v0.0.0-20200918232735-d647fc253266/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= golang.org/x/tools v0.0.0-20200918232735-d647fc253266/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU=
golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210114065538-d78b04bdf963/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210114065538-d78b04bdf963/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/tools v0.1.2-0.20210512205948-8287d5da45e4 h1:cYSqdOzmV9wJ7lWurRAws06Dmif0Wv6UL4gQLlz+im0= golang.org/x/tools v0.1.7 h1:6j8CgantCy3yc8JGBqkDLMKWqZ0RDU2g1HVgacojGWQ=
golang.org/x/tools v0.1.2-0.20210512205948-8287d5da45e4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@ -993,8 +1045,14 @@ google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/
google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
google.golang.org/api v0.30.0 h1:yfrXXP61wVuLb0vBcG6qaOoIoqYEzOQS8jum51jkv2w=
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=
google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE=
google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU=
google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94=
google.golang.org/api v0.45.0 h1:pqMffJFLBVUDIoYsHcqtxgQVTsmxMDpYLOc5MT4Jrww=
google.golang.org/api v0.45.0/go.mod h1:ISLIJCedJolbZvDfAk+Ctuq5hf+aJ33WgtUsfyFoLXA=
google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
@ -1033,9 +1091,20 @@ google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7Fc
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201110150050-8816d57aaa9a h1:pOwg4OoaRYScjmR4LlLgdtnyoHYTSAVhhqe5uPdpII8= google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201110150050-8816d57aaa9a/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201110150050-8816d57aaa9a/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A=
google.golang.org/genproto v0.0.0-20210413151531-c14fb6ef47c3 h1:K+7Ig5hjiLVA/i1UFUUbCGimWz5/Ey0lAQjT3QiLaPY=
google.golang.org/genproto v0.0.0-20210413151531-c14fb6ef47c3/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=
google.golang.org/grpc v1.27.0 h1:rRYRFMVgRv6E0D70Skyfsr28tDXIuuPZyWGMPdMcnXg= google.golang.org/grpc v1.27.0 h1:rRYRFMVgRv6E0D70Skyfsr28tDXIuuPZyWGMPdMcnXg=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=

View file

@ -45,6 +45,7 @@ nav:
- Secrets Manager: provider-google-secrets-manager.md - Secrets Manager: provider-google-secrets-manager.md
- IBM: - IBM:
- Secrets Manager: provider-ibm-secrets-manager.md - Secrets Manager: provider-ibm-secrets-manager.md
- Akeyless: provider-akeyless.md
- HashiCorp Vault: provider-hashicorp-vault.md - HashiCorp Vault: provider-hashicorp-vault.md
- Yandex: - Yandex:
- Lockbox: provider-yandex-lockbox.md - Lockbox: provider-yandex-lockbox.md

View file

@ -238,11 +238,16 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu
} }
conditionSynced := NewExternalSecretCondition(esv1alpha1.ExternalSecretReady, v1.ConditionTrue, esv1alpha1.ConditionReasonSecretSynced, "Secret was synced") conditionSynced := NewExternalSecretCondition(esv1alpha1.ExternalSecretReady, v1.ConditionTrue, esv1alpha1.ConditionReasonSecretSynced, "Secret was synced")
currCond := GetExternalSecretCondition(externalSecret.Status, esv1alpha1.ExternalSecretReady)
SetExternalSecretCondition(&externalSecret, *conditionSynced) SetExternalSecretCondition(&externalSecret, *conditionSynced)
externalSecret.Status.RefreshTime = metav1.NewTime(time.Now()) externalSecret.Status.RefreshTime = metav1.NewTime(time.Now())
externalSecret.Status.SyncedResourceVersion = getResourceVersion(externalSecret) externalSecret.Status.SyncedResourceVersion = getResourceVersion(externalSecret)
syncCallsTotal.With(syncCallsMetricLabels).Inc() syncCallsTotal.With(syncCallsMetricLabels).Inc()
log.V(1).Info("reconciled secret") if currCond == nil || currCond.Status != conditionSynced.Status {
log.Info("reconciled secret") // Log once if on success in any verbosity
} else {
log.V(1).Info("reconciled secret") // Log all reconciliation cycles if higher verbosity applied
}
return ctrl.Result{ return ctrl.Result{
RequeueAfter: refreshInt, RequeueAfter: refreshInt,

View file

@ -134,6 +134,11 @@ var _ = Describe("ExternalSecret controller", func() {
ExternalSecretName = "test-es" ExternalSecretName = "test-es"
ExternalSecretStore = "test-store" ExternalSecretStore = "test-store"
ExternalSecretTargetSecretName = "test-secret" ExternalSecretTargetSecretName = "test-secret"
FakeManager = "fake.manager"
expectedSecretVal = "SOMEVALUE was templated"
targetPropObj = "{{ .targetProperty | toString | upper }} was templated"
FooValue = "map-foo-value"
BarValue = "map-bar-value"
) )
var ExternalSecretNamespace string var ExternalSecretNamespace string
@ -283,13 +288,13 @@ var _ = Describe("ExternalSecret controller", func() {
// create secret beforehand // create secret beforehand
Expect(k8sClient.Create(context.Background(), &v1.Secret{ Expect(k8sClient.Create(context.Background(), &v1.Secret{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: "test-secret", Name: ExternalSecretTargetSecretName,
Namespace: ExternalSecretNamespace, Namespace: ExternalSecretNamespace,
}, },
Data: map[string][]byte{ Data: map[string][]byte{
existingKey: []byte(existingVal), existingKey: []byte(existingVal),
}, },
}, client.FieldOwner("fake.manager"))).To(Succeed()) }, client.FieldOwner(FakeManager))).To(Succeed())
fakeProvider.WithGetSecret([]byte(secretVal), nil) fakeProvider.WithGetSecret([]byte(secretVal), nil)
tc.checkSecret = func(es *esv1alpha1.ExternalSecret, secret *v1.Secret) { tc.checkSecret = func(es *esv1alpha1.ExternalSecret, secret *v1.Secret) {
@ -309,7 +314,7 @@ var _ = Describe("ExternalSecret controller", func() {
"external-secrets", "external-secrets",
fmt.Sprintf("{\"f:data\":{\"f:targetProperty\":{}},\"f:immutable\":{},\"f:metadata\":{\"f:annotations\":{\"f:%s\":{}}}}", esv1alpha1.AnnotationDataHash)), fmt.Sprintf("{\"f:data\":{\"f:targetProperty\":{}},\"f:immutable\":{},\"f:metadata\":{\"f:annotations\":{\"f:%s\":{}}}}", esv1alpha1.AnnotationDataHash)),
).To(BeTrue()) ).To(BeTrue())
Expect(hasFieldOwnership(secret.ObjectMeta, "fake.manager", "{\"f:data\":{\".\":{},\"f:pre-existing-key\":{}},\"f:type\":{}}")).To(BeTrue()) Expect(hasFieldOwnership(secret.ObjectMeta, FakeManager, "{\"f:data\":{\".\":{},\"f:pre-existing-key\":{}},\"f:type\":{}}")).To(BeTrue())
} }
} }
@ -348,13 +353,13 @@ var _ = Describe("ExternalSecret controller", func() {
// create secret beforehand // create secret beforehand
Expect(k8sClient.Create(context.Background(), &v1.Secret{ Expect(k8sClient.Create(context.Background(), &v1.Secret{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: "test-secret", Name: ExternalSecretTargetSecretName,
Namespace: ExternalSecretNamespace, Namespace: ExternalSecretNamespace,
}, },
Data: map[string][]byte{ Data: map[string][]byte{
existingKey: []byte(existingVal), existingKey: []byte(existingVal),
}, },
}, client.FieldOwner("fake.manager"))).To(Succeed()) }, client.FieldOwner(FakeManager))).To(Succeed())
fakeProvider.WithGetSecret([]byte(secretVal), nil) fakeProvider.WithGetSecret([]byte(secretVal), nil)
tc.checkCondition = func(es *esv1alpha1.ExternalSecret) bool { tc.checkCondition = func(es *esv1alpha1.ExternalSecret) bool {
@ -373,7 +378,7 @@ var _ = Describe("ExternalSecret controller", func() {
// check owner/managedFields // check owner/managedFields
Expect(hasOwnerRef(secret.ObjectMeta, "ExternalSecret", ExternalSecretName)).To(BeFalse()) Expect(hasOwnerRef(secret.ObjectMeta, "ExternalSecret", ExternalSecretName)).To(BeFalse())
Expect(secret.ObjectMeta.ManagedFields).To(HaveLen(1)) Expect(secret.ObjectMeta.ManagedFields).To(HaveLen(1))
Expect(hasFieldOwnership(secret.ObjectMeta, "fake.manager", "{\"f:data\":{\".\":{},\"f:targetProperty\":{}},\"f:type\":{}}")).To(BeTrue()) Expect(hasFieldOwnership(secret.ObjectMeta, FakeManager, "{\"f:data\":{\".\":{},\"f:targetProperty\":{}},\"f:type\":{}}")).To(BeTrue())
} }
} }
@ -381,7 +386,6 @@ var _ = Describe("ExternalSecret controller", func() {
// to construct a new secret: labels, annotations and type // to construct a new secret: labels, annotations and type
syncWithTemplate := func(tc *testCase) { syncWithTemplate := func(tc *testCase) {
const secretVal = "someValue" const secretVal = "someValue"
const expectedSecretVal = "SOMEVALUE was templated"
const tplStaticKey = "tplstatickey" const tplStaticKey = "tplstatickey"
const tplStaticVal = "tplstaticvalue" const tplStaticVal = "tplstaticvalue"
tc.externalSecret.ObjectMeta.Labels = map[string]string{ tc.externalSecret.ObjectMeta.Labels = map[string]string{
@ -401,7 +405,7 @@ var _ = Describe("ExternalSecret controller", func() {
}, },
Type: v1.SecretTypeOpaque, Type: v1.SecretTypeOpaque,
Data: map[string]string{ Data: map[string]string{
targetProp: "{{ .targetProperty | toString | upper }} was templated", targetProp: targetPropObj,
tplStaticKey: tplStaticVal, tplStaticKey: tplStaticVal,
}, },
} }
@ -426,7 +430,6 @@ var _ = Describe("ExternalSecret controller", func() {
// * dataFrom // * dataFrom
syncWithTemplatePrecedence := func(tc *testCase) { syncWithTemplatePrecedence := func(tc *testCase) {
const secretVal = "someValue" const secretVal = "someValue"
const expectedSecretVal = "SOMEVALUE was templated"
const tplStaticKey = "tplstatickey" const tplStaticKey = "tplstatickey"
const tplStaticVal = "tplstaticvalue" const tplStaticVal = "tplstaticvalue"
const tplFromCMName = "template-cm" const tplFromCMName = "template-cm"
@ -480,7 +483,7 @@ var _ = Describe("ExternalSecret controller", func() {
}, },
Data: map[string]string{ Data: map[string]string{
// this should be the data value, not dataFrom // this should be the data value, not dataFrom
targetProp: "{{ .targetProperty | toString | upper }} was templated", targetProp: targetPropObj,
// this should use the value from the map // this should use the value from the map
"bar": "value from map: {{ .bar | toString }}", "bar": "value from map: {{ .bar | toString }}",
// just a static value // just a static value
@ -494,8 +497,8 @@ var _ = Describe("ExternalSecret controller", func() {
} }
fakeProvider.WithGetSecret([]byte(secretVal), nil) fakeProvider.WithGetSecret([]byte(secretVal), nil)
fakeProvider.WithGetSecretMap(map[string][]byte{ fakeProvider.WithGetSecretMap(map[string][]byte{
"targetProperty": []byte("map-foo-value"), "targetProperty": []byte(FooValue),
"bar": []byte("map-bar-value"), "bar": []byte(BarValue),
}, nil) }, nil)
tc.checkSecret = func(es *esv1alpha1.ExternalSecret, secret *v1.Secret) { tc.checkSecret = func(es *esv1alpha1.ExternalSecret, secret *v1.Secret) {
// check values // check values
@ -509,7 +512,6 @@ var _ = Describe("ExternalSecret controller", func() {
refreshWithTemplate := func(tc *testCase) { refreshWithTemplate := func(tc *testCase) {
const secretVal = "someValue" const secretVal = "someValue"
const expectedSecretVal = "SOMEVALUE was templated"
const tplStaticKey = "tplstatickey" const tplStaticKey = "tplstatickey"
const tplStaticVal = "tplstaticvalue" const tplStaticVal = "tplstaticvalue"
tc.externalSecret.Spec.RefreshInterval = &metav1.Duration{Duration: time.Second} tc.externalSecret.Spec.RefreshInterval = &metav1.Duration{Duration: time.Second}
@ -520,7 +522,7 @@ var _ = Describe("ExternalSecret controller", func() {
}, },
Type: v1.SecretTypeOpaque, Type: v1.SecretTypeOpaque,
Data: map[string]string{ Data: map[string]string{
targetProp: "{{ .targetProperty | toString | upper }} was templated", targetProp: targetPropObj,
tplStaticKey: tplStaticVal, tplStaticKey: tplStaticVal,
}, },
} }
@ -660,13 +662,13 @@ var _ = Describe("ExternalSecret controller", func() {
}, },
} }
fakeProvider.WithGetSecretMap(map[string][]byte{ fakeProvider.WithGetSecretMap(map[string][]byte{
"foo": []byte("map-foo-value"), "foo": []byte(FooValue),
"bar": []byte("map-bar-value"), "bar": []byte(BarValue),
}, nil) }, nil)
tc.checkSecret = func(es *esv1alpha1.ExternalSecret, secret *v1.Secret) { tc.checkSecret = func(es *esv1alpha1.ExternalSecret, secret *v1.Secret) {
// check values // check values
Expect(string(secret.Data["foo"])).To(Equal("map-foo-value")) Expect(string(secret.Data["foo"])).To(Equal(FooValue))
Expect(string(secret.Data["bar"])).To(Equal("map-bar-value")) Expect(string(secret.Data["bar"])).To(Equal(BarValue))
} }
} }
@ -687,14 +689,14 @@ var _ = Describe("ExternalSecret controller", func() {
}, },
} }
fakeProvider.WithGetSecretMap(map[string][]byte{ fakeProvider.WithGetSecretMap(map[string][]byte{
"tls.crt": []byte("map-foo-value"), "tls.crt": []byte(FooValue),
"tls.key": []byte("map-bar-value"), "tls.key": []byte(BarValue),
}, nil) }, nil)
tc.checkSecret = func(es *esv1alpha1.ExternalSecret, secret *v1.Secret) { tc.checkSecret = func(es *esv1alpha1.ExternalSecret, secret *v1.Secret) {
Expect(secret.Type).To(Equal(v1.SecretTypeTLS)) Expect(secret.Type).To(Equal(v1.SecretTypeTLS))
// check values // check values
Expect(string(secret.Data["tls.crt"])).To(Equal("map-foo-value")) Expect(string(secret.Data["tls.crt"])).To(Equal(FooValue))
Expect(string(secret.Data["tls.key"])).To(Equal("map-bar-value")) Expect(string(secret.Data["tls.key"])).To(Equal(BarValue))
} }
} }
@ -851,7 +853,7 @@ var _ = Describe("ExternalSecret controller", func() {
// When we amend the created kind=secret, refresh operation should be run again regardless of refresh interval // When we amend the created kind=secret, refresh operation should be run again regardless of refresh interval
checkSecretDataHashAnnotationChange := func(tc *testCase) { checkSecretDataHashAnnotationChange := func(tc *testCase) {
fakeData := map[string][]byte{ fakeData := map[string][]byte{
"targetProperty": []byte("map-foo-value"), "targetProperty": []byte(FooValue),
} }
fakeProvider.WithGetSecretMap(fakeData, nil) fakeProvider.WithGetSecretMap(fakeData, nil)
tc.externalSecret.Spec.RefreshInterval = &metav1.Duration{Duration: time.Minute * 10} tc.externalSecret.Spec.RefreshInterval = &metav1.Duration{Duration: time.Minute * 10}

View file

@ -0,0 +1,155 @@
/*
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 akeyless
import (
"context"
"encoding/json"
"fmt"
"strconv"
"github.com/akeylesslabs/akeyless-go/v2"
"sigs.k8s.io/controller-runtime/pkg/client"
esv1alpha1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1alpha1"
"github.com/external-secrets/external-secrets/pkg/provider"
"github.com/external-secrets/external-secrets/pkg/provider/schema"
"github.com/external-secrets/external-secrets/pkg/utils"
)
const (
defaultAPIUrl = "https://api.akeyless.io"
)
// Provider satisfies the provider interface.
type Provider struct{}
// akeylessBase satisfies the provider.SecretsClient interface.
type akeylessBase struct {
kube client.Client
store esv1alpha1.GenericStore
namespace string
akeylessGwAPIURL string
RestAPI *akeyless.V2ApiService
}
type Akeyless struct {
Client akeylessVaultInterface
}
type akeylessVaultInterface interface {
GetSecretByType(secretName, token string, version int32) (string, error)
TokenFromSecretRef(ctx context.Context) (string, error)
}
func init() {
schema.Register(&Provider{}, &esv1alpha1.SecretStoreProvider{
Akeyless: &esv1alpha1.AkeylessProvider{},
})
}
// NewClient constructs a new secrets client based on the provided store.
func (p *Provider) NewClient(ctx context.Context, store esv1alpha1.GenericStore, kube client.Client, namespace string) (provider.SecretsClient, error) {
return newClient(ctx, store, kube, namespace)
}
func newClient(_ context.Context, store esv1alpha1.GenericStore, kube client.Client, namespace string) (provider.SecretsClient, error) {
akl := &akeylessBase{
kube: kube,
store: store,
namespace: namespace,
}
spec, err := GetAKeylessProvider(store)
if err != nil {
return nil, err
}
akeylessGwAPIURL := defaultAPIUrl
if spec != nil && spec.AkeylessGWApiURL != nil && *spec.AkeylessGWApiURL != "" {
akeylessGwAPIURL = getV2Url(*spec.AkeylessGWApiURL)
}
if spec.Auth == nil {
return nil, fmt.Errorf("missing Auth in store config")
}
RestAPIClient := akeyless.NewAPIClient(&akeyless.Configuration{
Servers: []akeyless.ServerConfiguration{
{
URL: akeylessGwAPIURL,
},
},
}).V2Api
akl.akeylessGwAPIURL = akeylessGwAPIURL
akl.RestAPI = RestAPIClient
return &Akeyless{Client: akl}, nil
}
func (a *Akeyless) Close(ctx context.Context) error {
return nil
}
// Implements store.Client.GetSecret Interface.
// Retrieves a secret with the secret name defined in ref.Name.
func (a *Akeyless) GetSecret(ctx context.Context, ref esv1alpha1.ExternalSecretDataRemoteRef) ([]byte, error) {
if utils.IsNil(a.Client) {
return nil, fmt.Errorf(errUninitalizedAkeylessProvider)
}
token, err := a.Client.TokenFromSecretRef(ctx)
if err != nil {
return nil, err
}
version := int32(0)
if ref.Version != "" {
i, err := strconv.ParseInt(ref.Version, 10, 32)
if err == nil {
version = int32(i)
}
}
value, err := a.Client.GetSecretByType(ref.Key, token, version)
if err != nil {
return nil, err
}
return []byte(value), nil
}
// Implements store.Client.GetSecretMap Interface.
// New version of GetSecretMap.
func (a *Akeyless) GetSecretMap(ctx context.Context, ref esv1alpha1.ExternalSecretDataRemoteRef) (map[string][]byte, error) {
if utils.IsNil(a.Client) {
return nil, fmt.Errorf(errUninitalizedAkeylessProvider)
}
val, err := a.GetSecret(ctx, ref)
if err != nil {
return nil, err
}
// Maps the json data to a string:string map
kv := make(map[string]string)
err = json.Unmarshal(val, &kv)
if err != nil {
return nil, fmt.Errorf(errJSONSecretUnmarshal, err)
}
// Converts values in K:V pairs into bytes, while leaving keys as strings
secretData := make(map[string][]byte)
for k, v := range kv {
secretData[k] = []byte(v)
}
return secretData, nil
}

View file

@ -0,0 +1,250 @@
/*
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 akeyless
import (
"context"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"os"
"strings"
aws_cloud_id "github.com/akeylesslabs/akeyless-go-cloud-id/cloudprovider/aws"
azure_cloud_id "github.com/akeylesslabs/akeyless-go-cloud-id/cloudprovider/azure"
gcp_cloud_id "github.com/akeylesslabs/akeyless-go-cloud-id/cloudprovider/gcp"
"github.com/akeylesslabs/akeyless-go/v2"
)
var apiErr akeyless.GenericOpenAPIError
const DefServiceAccountFile = "/var/run/secrets/kubernetes.io/serviceaccount/token"
func (a *akeylessBase) GetToken(accessID, accType, accTypeParam string) (string, error) {
ctx := context.Background()
authBody := akeyless.NewAuthWithDefaults()
authBody.AccessId = akeyless.PtrString(accessID)
if accType == "api_key" || accType == "access_key" {
authBody.AccessKey = akeyless.PtrString(accTypeParam)
} else if accType == "k8s" {
jwtString, err := readK8SServiceAccountJWT()
if err != nil {
return "", fmt.Errorf("failed to read JWT with Kubernetes Auth from %v. error: %w", DefServiceAccountFile, err)
}
K8SAuthConfigName := accTypeParam
authBody.AccessType = akeyless.PtrString(accType)
authBody.K8sServiceAccountToken = akeyless.PtrString(jwtString)
authBody.K8sAuthConfigName = akeyless.PtrString(K8SAuthConfigName)
} else {
cloudID, err := a.getCloudID(accType, accTypeParam)
if err != nil {
return "", fmt.Errorf("Require Cloud ID " + err.Error())
}
authBody.AccessType = akeyless.PtrString(accType)
authBody.CloudId = akeyless.PtrString(cloudID)
}
authOut, _, err := a.RestAPI.Auth(ctx).Body(*authBody).Execute()
if err != nil {
if errors.As(err, &apiErr) {
return "", fmt.Errorf("authentication failed: %v", string(apiErr.Body()))
}
return "", fmt.Errorf("authentication failed: %w", err)
}
token := authOut.GetToken()
return token, nil
}
func (a *akeylessBase) GetSecretByType(secretName, token string, version int32) (string, error) {
item, err := a.DescribeItem(secretName, token)
if err != nil {
return "", err
}
secretType := item.GetItemType()
switch secretType {
case "STATIC_SECRET":
return a.GetStaticSecret(secretName, token, version)
case "DYNAMIC_SECRET":
return a.GetDynamicSecrets(secretName, token)
case "ROTATED_SECRET":
return a.GetRotatedSecrets(secretName, token, version)
default:
return "", fmt.Errorf("invalid item type: %v", secretType)
}
}
func (a *akeylessBase) DescribeItem(itemName, token string) (*akeyless.Item, error) {
ctx := context.Background()
body := akeyless.DescribeItem{
Name: itemName,
}
if strings.HasPrefix(token, "u-") {
body.UidToken = &token
} else {
body.Token = &token
}
gsvOut, _, err := a.RestAPI.DescribeItem(ctx).Body(body).Execute()
if err != nil {
if errors.As(err, &apiErr) {
return nil, fmt.Errorf("can't describe item: %v", string(apiErr.Body()))
}
return nil, fmt.Errorf("can't describe item: %w", err)
}
return &gsvOut, nil
}
func (a *akeylessBase) GetRotatedSecrets(secretName, token string, version int32) (string, error) {
ctx := context.Background()
body := akeyless.GetRotatedSecretValue{
Names: secretName,
Version: &version,
}
if strings.HasPrefix(token, "u-") {
body.UidToken = &token
} else {
body.Token = &token
}
gsvOut, _, err := a.RestAPI.GetRotatedSecretValue(ctx).Body(body).Execute()
if err != nil {
if errors.As(err, &apiErr) {
return "", fmt.Errorf("can't get rotated secret value: %v", string(apiErr.Body()))
}
return "", fmt.Errorf("can't get rotated secret value: %w", err)
}
val, ok := gsvOut["value"]
if ok {
if _, ok := val["payload"]; ok {
return fmt.Sprintf("%v", val["payload"]), nil
} else if _, ok := val["target_value"]; ok {
out, err := json.Marshal(val["target_value"])
if err != nil {
return "", fmt.Errorf("can't marshal rotated secret value: %w", err)
}
return string(out), nil
} else {
out, err := json.Marshal(val)
if err != nil {
return "", fmt.Errorf("can't marshal rotated secret value: %w", err)
}
return string(out), nil
}
}
out, err := json.Marshal(gsvOut)
if err != nil {
return "", fmt.Errorf("can't marshal rotated secret value: %w", err)
}
return string(out), nil
}
func (a *akeylessBase) GetDynamicSecrets(secretName, token string) (string, error) {
ctx := context.Background()
body := akeyless.GetDynamicSecretValue{
Name: secretName,
}
if strings.HasPrefix(token, "u-") {
body.UidToken = &token
} else {
body.Token = &token
}
gsvOut, _, err := a.RestAPI.GetDynamicSecretValue(ctx).Body(body).Execute()
if err != nil {
if errors.As(err, &apiErr) {
return "", fmt.Errorf("can't get dynamic secret value: %v", string(apiErr.Body()))
}
return "", fmt.Errorf("can't get dynamic secret value: %w", err)
}
out, err := json.Marshal(gsvOut)
if err != nil {
return "", fmt.Errorf("can't marshal dynamic secret value: %w", err)
}
return string(out), nil
}
func (a *akeylessBase) GetStaticSecret(secretName, token string, version int32) (string, error) {
ctx := context.Background()
gsvBody := akeyless.GetSecretValue{
Names: []string{secretName},
Version: &version,
}
if strings.HasPrefix(token, "u-") {
gsvBody.UidToken = &token
} else {
gsvBody.Token = &token
}
gsvOut, _, err := a.RestAPI.GetSecretValue(ctx).Body(gsvBody).Execute()
if err != nil {
if errors.As(err, &apiErr) {
return "", fmt.Errorf("can't get secret value: %v", string(apiErr.Body()))
}
return "", fmt.Errorf("can't get secret value: %w", err)
}
val, ok := gsvOut[secretName]
if !ok {
return "", fmt.Errorf("can't get secret: %v", secretName)
}
return val, nil
}
func (a *akeylessBase) getCloudID(provider, accTypeParam string) (string, error) {
var cloudID string
var err error
switch provider {
case "azure_ad":
cloudID, err = azure_cloud_id.GetCloudId(accTypeParam)
case "aws_iam":
cloudID, err = aws_cloud_id.GetCloudId()
case "gcp":
cloudID, err = gcp_cloud_id.GetCloudID(accTypeParam)
default:
return "", fmt.Errorf("unable to determine provider: %s", provider)
}
return cloudID, err
}
// readK8SServiceAccountJWT reads the JWT data for the Agent to submit to Akeyless Gateway.
func readK8SServiceAccountJWT() (string, error) {
data, err := os.Open(DefServiceAccountFile)
if err != nil {
return "", err
}
defer data.Close()
contentBytes, err := ioutil.ReadAll(data)
if err != nil {
return "", err
}
a := strings.TrimSpace(string(contentBytes))
return base64.StdEncoding.EncodeToString([]byte(a)), nil
}

View file

@ -0,0 +1,168 @@
/*
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 akeyless
import (
"context"
"fmt"
"reflect"
"strings"
"testing"
esv1alpha1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1alpha1"
fakeakeyless "github.com/external-secrets/external-secrets/pkg/provider/akeyless/fake"
)
type akeylessTestCase struct {
mockClient *fakeakeyless.AkeylessMockClient
apiInput *fakeakeyless.Input
apiOutput *fakeakeyless.Output
ref *esv1alpha1.ExternalSecretDataRemoteRef
expectError string
expectedSecret string
// for testing secretmap
expectedData map[string][]byte
}
func makeValidAkeylessTestCase() *akeylessTestCase {
smtc := akeylessTestCase{
mockClient: &fakeakeyless.AkeylessMockClient{},
apiInput: makeValidInput(),
ref: makeValidRef(),
apiOutput: makeValidOutput(),
expectError: "",
expectedSecret: "",
expectedData: map[string][]byte{},
}
smtc.mockClient.WithValue(smtc.apiInput, smtc.apiOutput)
return &smtc
}
func makeValidRef() *esv1alpha1.ExternalSecretDataRemoteRef {
return &esv1alpha1.ExternalSecretDataRemoteRef{
Key: "test-secret",
Version: "1",
}
}
func makeValidInput() *fakeakeyless.Input {
return &fakeakeyless.Input{
SecretName: "name",
Version: 0,
Token: "token",
}
}
func makeValidOutput() *fakeakeyless.Output {
return &fakeakeyless.Output{
Value: "secret-val",
Err: nil,
}
}
func makeValidAkeylessTestCaseCustom(tweaks ...func(smtc *akeylessTestCase)) *akeylessTestCase {
smtc := makeValidAkeylessTestCase()
for _, fn := range tweaks {
fn(smtc)
}
smtc.mockClient.WithValue(smtc.apiInput, smtc.apiOutput)
return smtc
}
// This case can be shared by both GetSecret and GetSecretMap tests.
// bad case: set apiErr.
var setAPIErr = func(smtc *akeylessTestCase) {
smtc.apiOutput.Err = fmt.Errorf("oh no")
smtc.expectError = "oh no"
}
var setNilMockClient = func(smtc *akeylessTestCase) {
smtc.mockClient = nil
smtc.expectError = errUninitalizedAkeylessProvider
}
func TestAkeylessGetSecret(t *testing.T) {
secretValue := "changedvalue"
// good case: default version is set
// key is passed in, output is sent back
setSecretString := func(smtc *akeylessTestCase) {
smtc.apiOutput = &fakeakeyless.Output{
Value: secretValue,
Err: nil,
}
smtc.expectedSecret = secretValue
}
successCases := []*akeylessTestCase{
makeValidAkeylessTestCaseCustom(setAPIErr),
makeValidAkeylessTestCaseCustom(setSecretString),
makeValidAkeylessTestCaseCustom(setNilMockClient),
}
sm := Akeyless{}
for k, v := range successCases {
sm.Client = v.mockClient
fmt.Println(*v.ref)
out, err := sm.GetSecret(context.Background(), *v.ref)
if !ErrorContains(err, v.expectError) {
t.Errorf("[%d] unexpected error: %s, expected: '%s'", k, err.Error(), v.expectError)
}
if string(out) != v.expectedSecret {
t.Errorf("[%d] unexpected secret: expected %s, got %s", k, v.expectedSecret, string(out))
}
}
}
func TestGetSecretMap(t *testing.T) {
// good case: default version & deserialization
setDeserialization := func(smtc *akeylessTestCase) {
smtc.apiOutput.Value = `{"foo":"bar"}`
smtc.expectedData["foo"] = []byte("bar")
}
// bad case: invalid json
setInvalidJSON := func(smtc *akeylessTestCase) {
smtc.apiOutput.Value = `-----------------`
smtc.expectError = "unable to unmarshal secret"
}
successCases := []*akeylessTestCase{
makeValidAkeylessTestCaseCustom(setDeserialization),
makeValidAkeylessTestCaseCustom(setInvalidJSON),
makeValidAkeylessTestCaseCustom(setAPIErr),
makeValidAkeylessTestCaseCustom(setNilMockClient),
}
sm := Akeyless{}
for k, v := range successCases {
sm.Client = v.mockClient
out, err := sm.GetSecretMap(context.Background(), *v.ref)
if !ErrorContains(err, v.expectError) {
t.Errorf("[%d] unexpected error: %s, expected: '%s'", k, err.Error(), v.expectError)
}
if err == nil && !reflect.DeepEqual(out, v.expectedData) {
t.Errorf("[%d] unexpected secret data: expected %#v, got %#v", k, v.expectedData, out)
}
}
}
func ErrorContains(out error, want string) bool {
if out == nil {
return want == ""
}
if want == "" {
return false
}
return strings.Contains(out.Error(), want)
}

View file

@ -0,0 +1,103 @@
/*
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 akeyless
import (
"context"
"fmt"
v1 "k8s.io/api/core/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
esv1alpha1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1alpha1"
)
const (
errInvalidClusterStoreMissingAKIDNamespace = "invalid ClusterSecretStore: missing Akeyless AccessID Namespace"
errInvalidClusterStoreMissingSAKNamespace = "invalid ClusterSecretStore: missing Akeyless AccessType Namespace"
errFetchAKIDSecret = "could not fetch accessID secret: %w"
errFetchSAKSecret = "could not fetch AccessType secret: %w"
errMissingSAK = "missing SecretAccessKey"
errMissingAKID = "missing AccessKeyID"
)
func (a *akeylessBase) TokenFromSecretRef(ctx context.Context) (string, error) {
prov, err := GetAKeylessProvider(a.store)
if err != nil {
return "", err
}
ke := client.ObjectKey{
Name: prov.Auth.SecretRef.AccessID.Name,
Namespace: a.namespace, // default to ExternalSecret namespace
}
// only ClusterStore is allowed to set namespace (and then it's required)
if a.store.GetObjectKind().GroupVersionKind().Kind == esv1alpha1.ClusterSecretStoreKind {
if prov.Auth.SecretRef.AccessID.Namespace == nil {
return "", fmt.Errorf(errInvalidClusterStoreMissingAKIDNamespace)
}
ke.Namespace = *prov.Auth.SecretRef.AccessID.Namespace
}
accessIDSecret := v1.Secret{}
err = a.kube.Get(ctx, ke, &accessIDSecret)
if err != nil {
return "", fmt.Errorf(errFetchAKIDSecret, err)
}
ke = client.ObjectKey{
Name: prov.Auth.SecretRef.AccessType.Name,
Namespace: a.namespace, // default to ExternalSecret namespace
}
// only ClusterStore is allowed to set namespace (and then it's required)
if a.store.GetObjectKind().GroupVersionKind().Kind == esv1alpha1.ClusterSecretStoreKind {
if prov.Auth.SecretRef.AccessType.Namespace == nil {
return "", fmt.Errorf(errInvalidClusterStoreMissingSAKNamespace)
}
ke.Namespace = *prov.Auth.SecretRef.AccessType.Namespace
}
accessTypeSecret := v1.Secret{}
err = a.kube.Get(ctx, ke, &accessTypeSecret)
if err != nil {
return "", fmt.Errorf(errFetchSAKSecret, err)
}
ke = client.ObjectKey{
Name: prov.Auth.SecretRef.AccessTypeParam.Name,
Namespace: a.namespace, // default to ExternalSecret namespace
}
// only ClusterStore is allowed to set namespace (and then it's required)
if a.store.GetObjectKind().GroupVersionKind().Kind == esv1alpha1.ClusterSecretStoreKind {
if prov.Auth.SecretRef.AccessType.Namespace == nil {
return "", fmt.Errorf(errInvalidClusterStoreMissingSAKNamespace)
}
ke.Namespace = *prov.Auth.SecretRef.AccessType.Namespace
}
accessTypeParamSecret := v1.Secret{}
err = a.kube.Get(ctx, ke, &accessTypeParamSecret)
if err != nil {
return "", fmt.Errorf(errFetchSAKSecret, err)
}
accessID := string(accessIDSecret.Data[prov.Auth.SecretRef.AccessID.Key])
accessType := string(accessTypeSecret.Data[prov.Auth.SecretRef.AccessType.Key])
accessTypeParam := string(accessTypeSecret.Data[prov.Auth.SecretRef.AccessTypeParam.Key])
if accessID == "" {
return "", fmt.Errorf(errMissingSAK)
}
if accessType == "" {
return "", fmt.Errorf(errMissingAKID)
}
return a.GetToken(accessID, accessType, accessTypeParam)
}

View file

@ -0,0 +1,49 @@
/*
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 fake
import (
"context"
)
type AkeylessMockClient struct {
getSecret func(secretName, token string, version int32) (string, error)
}
func (mc *AkeylessMockClient) TokenFromSecretRef(ctx context.Context) (string, error) {
return "newToken", nil
}
func (mc *AkeylessMockClient) GetSecretByType(secretName, token string, version int32) (string, error) {
return mc.getSecret(secretName, token, version)
}
func (mc *AkeylessMockClient) WithValue(in *Input, out *Output) {
if mc != nil {
mc.getSecret = func(secretName, token string, version int32) (string, error) {
return out.Value, out.Err
}
}
}
type Input struct {
SecretName string
Token string
Version int32
}
type Output struct {
Value string
Err error
}

View file

@ -0,0 +1,99 @@
/*
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 akeyless
import (
"fmt"
"io/ioutil"
"net/http"
"net/url"
"strings"
"time"
esv1alpha1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1alpha1"
)
const (
errNilStore = "found nil store"
errMissingStoreSpec = "store is missing spec"
errMissingProvider = "storeSpec is missing provider"
errInvalidProvider = "invalid provider spec. Missing Akeyless field in store %s"
errJSONSecretUnmarshal = "unable to unmarshal secret: %w"
errUninitalizedAkeylessProvider = "provider akeyless is not initialized"
)
// GetAKeylessProvider does the necessary nil checks and returns the akeyless provider or an error.
func GetAKeylessProvider(store esv1alpha1.GenericStore) (*esv1alpha1.AkeylessProvider, error) {
if store == nil {
return nil, fmt.Errorf(errNilStore)
}
spc := store.GetSpec()
if spc == nil {
return nil, fmt.Errorf(errMissingStoreSpec)
}
if spc.Provider == nil {
return nil, fmt.Errorf(errMissingProvider)
}
prov := spc.Provider.Akeyless
if prov == nil {
return nil, fmt.Errorf(errInvalidProvider, store.GetObjectMeta().String())
}
return prov, nil
}
func getV2Url(path string) string {
// add check if not v2
rebody := sendReq(path)
if strings.Contains(rebody, "unknown command") {
return path
}
if strings.HasSuffix(path, "/v2") {
return path
}
url, err := url.Parse(path)
if err != nil {
return path
}
if strings.HasSuffix(url.Host, "/v2") {
return path
}
url.Host += "/v2"
p := url.Scheme + "://" + url.Host
if url.Port() != "" {
p = p + ":" + url.Port()
}
return p
}
func sendReq(url string) string {
req, err := http.NewRequest("POST", url, nil)
if err != nil {
return ""
}
req.Header.Set("Content-Type", "application/json")
client := &http.Client{
Timeout: 10 * time.Second,
}
resp, err := client.Do(req)
if err != nil {
return ""
}
defer resp.Body.Close()
body, _ := ioutil.ReadAll(resp.Body)
return string(body)
}

View file

@ -160,6 +160,9 @@ func sessionFromSecretRef(ctx context.Context, prov *esv1alpha1.AWSProvider, sto
func sessionFromServiceAccount(ctx context.Context, prov *esv1alpha1.AWSProvider, store esv1alpha1.GenericStore, kube client.Client, namespace string, jwtProvider jwtProviderFactory) (*credentials.Credentials, error) { func sessionFromServiceAccount(ctx context.Context, prov *esv1alpha1.AWSProvider, store esv1alpha1.GenericStore, kube client.Client, namespace string, jwtProvider jwtProviderFactory) (*credentials.Credentials, error) {
if store.GetObjectKind().GroupVersionKind().Kind == esv1alpha1.ClusterSecretStoreKind { if store.GetObjectKind().GroupVersionKind().Kind == esv1alpha1.ClusterSecretStoreKind {
if prov.Auth.JWTAuth.ServiceAccountRef.Namespace == nil {
return nil, fmt.Errorf("serviceAccountRef has no Namespace field (mandatory for ClusterSecretStore specs)")
}
namespace = *prov.Auth.JWTAuth.ServiceAccountRef.Namespace namespace = *prov.Auth.JWTAuth.ServiceAccountRef.Namespace
} }
name := prov.Auth.JWTAuth.ServiceAccountRef.Name name := prov.Auth.JWTAuth.ServiceAccountRef.Name

View file

@ -38,16 +38,13 @@ import (
) )
const ( const (
myServiceAcc = "my-service-account" esNamespaceKey = "es-namespace"
otherNs = "other-ns" platformTeamNsKey = "platform-team-ns"
myServiceAccountKey = "my-service-account"
otherNsName = "other-ns"
) )
func TestNewSession(t *testing.T) { func TestNewSession(t *testing.T) {
const (
esNamespace = "es-namespace"
platformTeamNs = "platform-team-ns"
)
rows := []TestSessionRow{ rows := []TestSessionRow{
{ {
name: "nil store", name: "nil store",
@ -271,7 +268,7 @@ func TestNewSession(t *testing.T) {
}, },
{ {
name: "ClusterStore should use credentials from a specific namespace", name: "ClusterStore should use credentials from a specific namespace",
namespace: esNamespace, namespace: esNamespaceKey,
store: &esv1alpha1.ClusterSecretStore{ store: &esv1alpha1.ClusterSecretStore{
TypeMeta: metav1.TypeMeta{ TypeMeta: metav1.TypeMeta{
APIVersion: esv1alpha1.ClusterSecretStoreKindAPIVersion, APIVersion: esv1alpha1.ClusterSecretStoreKindAPIVersion,
@ -284,12 +281,12 @@ func TestNewSession(t *testing.T) {
SecretRef: &esv1alpha1.AWSAuthSecretRef{ SecretRef: &esv1alpha1.AWSAuthSecretRef{
AccessKeyID: esmeta.SecretKeySelector{ AccessKeyID: esmeta.SecretKeySelector{
Name: "onesecret", Name: "onesecret",
Namespace: aws.String(platformTeamNs), Namespace: aws.String(platformTeamNsKey),
Key: "one", Key: "one",
}, },
SecretAccessKey: esmeta.SecretKeySelector{ SecretAccessKey: esmeta.SecretKeySelector{
Name: "onesecret", Name: "onesecret",
Namespace: aws.String(platformTeamNs), Namespace: aws.String(platformTeamNsKey),
Key: "two", Key: "two",
}, },
}, },
@ -302,7 +299,7 @@ func TestNewSession(t *testing.T) {
{ {
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: "onesecret", Name: "onesecret",
Namespace: platformTeamNs, Namespace: platformTeamNsKey,
}, },
Data: map[string][]byte{ Data: map[string][]byte{
"one": []byte("1111"), "one": []byte("1111"),
@ -316,7 +313,7 @@ func TestNewSession(t *testing.T) {
}, },
{ {
name: "namespace is mandatory when using ClusterStore with SecretKeySelector", name: "namespace is mandatory when using ClusterStore with SecretKeySelector",
namespace: esNamespace, namespace: esNamespaceKey,
store: &esv1alpha1.ClusterSecretStore{ store: &esv1alpha1.ClusterSecretStore{
TypeMeta: metav1.TypeMeta{ TypeMeta: metav1.TypeMeta{
APIVersion: esv1alpha1.ClusterSecretStoreKindAPIVersion, APIVersion: esv1alpha1.ClusterSecretStoreKindAPIVersion,
@ -345,19 +342,19 @@ func TestNewSession(t *testing.T) {
}, },
{ {
name: "jwt auth via cluster secret store", name: "jwt auth via cluster secret store",
namespace: esNamespace, namespace: esNamespaceKey,
sa: &v1.ServiceAccount{ sa: &v1.ServiceAccount{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: myServiceAcc, Name: myServiceAccountKey,
Namespace: otherNs, Namespace: otherNsName,
Annotations: map[string]string{ Annotations: map[string]string{
roleARNAnnotation: "my-sa-role", roleARNAnnotation: "my-sa-role",
}, },
}, },
}, },
jwtProvider: func(name, namespace, roleArn, region string) (credentials.Provider, error) { jwtProvider: func(name, namespace, roleArn, region string) (credentials.Provider, error) {
assert.Equal(t, myServiceAcc, name) assert.Equal(t, myServiceAccountKey, name)
assert.Equal(t, otherNs, namespace) assert.Equal(t, otherNsName, namespace)
assert.Equal(t, "my-sa-role", roleArn) assert.Equal(t, "my-sa-role", roleArn)
return fakesess.CredentialsProvider{ return fakesess.CredentialsProvider{
RetrieveFunc: func() (credentials.Value, error) { RetrieveFunc: func() (credentials.Value, error) {
@ -382,8 +379,8 @@ func TestNewSession(t *testing.T) {
Auth: esv1alpha1.AWSAuth{ Auth: esv1alpha1.AWSAuth{
JWTAuth: &esv1alpha1.AWSJWTAuth{ JWTAuth: &esv1alpha1.AWSJWTAuth{
ServiceAccountRef: &esmeta.ServiceAccountSelector{ ServiceAccountRef: &esmeta.ServiceAccountSelector{
Name: myServiceAcc, Name: myServiceAccountKey,
Namespace: aws.String(otherNs), Namespace: aws.String(otherNsName),
}, },
}, },
}, },
@ -434,8 +431,8 @@ func testRow(t *testing.T, row TestSessionRow) {
} }
err := kc.Create(context.Background(), &authv1.TokenRequest{ err := kc.Create(context.Background(), &authv1.TokenRequest{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: myServiceAcc, Name: myServiceAccountKey,
Namespace: otherNs, Namespace: otherNsName,
}, },
}) })
assert.Nil(t, err) assert.Nil(t, err)

View file

@ -35,6 +35,7 @@ import (
const ( const (
defaultObjType = "secret" defaultObjType = "secret"
vaultResource = "https://vault.azure.net"
) )
// Provider satisfies the provider interface. // Provider satisfies the provider interface.
@ -74,15 +75,18 @@ func newClient(ctx context.Context, store esv1alpha1.GenericStore, kube client.C
store: store, store: store,
namespace: namespace, namespace: namespace,
} }
azClient, vaultURL, err := anAzure.newAzureClient(ctx)
if err != nil { clientSet, err := anAzure.setAzureClientWithManagedIdentity()
return nil, err if clientSet {
return anAzure, err
} }
anAzure.baseClient = azClient clientSet, err = anAzure.setAzureClientWithServicePrincipal(ctx)
anAzure.vaultURL = vaultURL if clientSet {
return anAzure, nil return anAzure, err
}
return nil, fmt.Errorf("cannot initialize Azure Client: no valid authType was specified")
} }
// Implements store.Client.GetSecret Interface. // Implements store.Client.GetSecret Interface.
@ -168,42 +172,75 @@ func (a *Azure) GetSecretMap(ctx context.Context, ref esv1alpha1.ExternalSecretD
return nil, fmt.Errorf("unknown Azure Keyvault object Type for %s", secretName) return nil, fmt.Errorf("unknown Azure Keyvault object Type for %s", secretName)
} }
func (a *Azure) newAzureClient(ctx context.Context) (*keyvault.BaseClient, string, error) { func (a *Azure) setAzureClientWithManagedIdentity() (bool, error) {
spec := *a.store.GetSpec().Provider.AzureKV spec := *a.store.GetSpec().Provider.AzureKV
tenantID := *spec.TenantID
vaultURL := *spec.VaultURL
if spec.AuthSecretRef == nil { if *spec.AuthType != esv1alpha1.ManagedIdentity {
return nil, "", fmt.Errorf("missing clientID/clientSecret in store config") return false, nil
}
clusterScoped := false
if a.store.GetObjectKind().GroupVersionKind().Kind == esv1alpha1.ClusterSecretStoreKind {
clusterScoped = true
}
if spec.AuthSecretRef.ClientID == nil || spec.AuthSecretRef.ClientSecret == nil {
return nil, "", fmt.Errorf("missing accessKeyID/secretAccessKey in store config")
}
cid, err := a.secretKeyRef(ctx, a.store.GetNamespace(), *spec.AuthSecretRef.ClientID, clusterScoped)
if err != nil {
return nil, "", err
}
csec, err := a.secretKeyRef(ctx, a.store.GetNamespace(), *spec.AuthSecretRef.ClientSecret, clusterScoped)
if err != nil {
return nil, "", err
} }
clientCredentialsConfig := kvauth.NewClientCredentialsConfig(cid, csec, tenantID) msiConfig := kvauth.NewMSIConfig()
// the default resource api is the management URL and not the vault URL which we need for keyvault operations msiConfig.Resource = vaultResource
clientCredentialsConfig.Resource = "https://vault.azure.net" if spec.IdentityID != nil {
authorizer, err := clientCredentialsConfig.Authorizer() msiConfig.ClientID = *spec.IdentityID
}
authorizer, err := msiConfig.Authorizer()
if err != nil { if err != nil {
return nil, "", err return true, err
} }
basicClient := keyvault.New() basicClient := keyvault.New()
basicClient.Authorizer = authorizer basicClient.Authorizer = authorizer
return &basicClient, vaultURL, nil a.baseClient = basicClient
a.vaultURL = *spec.VaultURL
return true, nil
}
func (a *Azure) setAzureClientWithServicePrincipal(ctx context.Context) (bool, error) {
spec := *a.store.GetSpec().Provider.AzureKV
if *spec.AuthType != esv1alpha1.ServicePrincipal {
return false, nil
}
if spec.TenantID == nil {
return true, fmt.Errorf("missing tenantID in store config")
}
if spec.AuthSecretRef == nil {
return true, fmt.Errorf("missing clientID/clientSecret in store config")
}
if spec.AuthSecretRef.ClientID == nil || spec.AuthSecretRef.ClientSecret == nil {
return true, fmt.Errorf("missing accessKeyID/secretAccessKey in store config")
}
clusterScoped := false
if a.store.GetObjectKind().GroupVersionKind().Kind == esv1alpha1.ClusterSecretStoreKind {
clusterScoped = true
}
cid, err := a.secretKeyRef(ctx, a.store.GetNamespace(), *spec.AuthSecretRef.ClientID, clusterScoped)
if err != nil {
return true, err
}
csec, err := a.secretKeyRef(ctx, a.store.GetNamespace(), *spec.AuthSecretRef.ClientSecret, clusterScoped)
if err != nil {
return true, err
}
clientCredentialsConfig := kvauth.NewClientCredentialsConfig(cid, csec, *spec.TenantID)
clientCredentialsConfig.Resource = vaultResource
authorizer, err := clientCredentialsConfig.Authorizer()
if err != nil {
return true, err
}
basicClient := keyvault.New()
basicClient.Authorizer = authorizer
a.baseClient = &basicClient
a.vaultURL = *spec.VaultURL
return true, nil
} }
func (a *Azure) secretKeyRef(ctx context.Context, namespace string, secretRef smmeta.SecretKeySelector, clusterScoped bool) (string, error) { func (a *Azure) secretKeyRef(ctx context.Context, namespace string, secretRef smmeta.SecretKeySelector, clusterScoped bool) (string, error) {

View file

@ -39,15 +39,46 @@ func newAzure() (Azure, *fake.AzureMock) {
return testAzure, azureMock return testAzure, azureMock
} }
func TestNewClientNoCreds(t *testing.T) { func TestNewClientManagedIdentityNoNeedForCredentials(t *testing.T) {
namespace := "internal" namespace := "internal"
vaultURL := "https://local.vault.url" vaultURL := "https://local.vault.url"
tenantID := "1234" identityID := "1234"
authType := esv1alpha1.ManagedIdentity
store := esv1alpha1.SecretStore{ store := esv1alpha1.SecretStore{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Namespace: namespace, Namespace: namespace,
}, },
Spec: esv1alpha1.SecretStoreSpec{Provider: &esv1alpha1.SecretStoreProvider{AzureKV: &esv1alpha1.AzureKVProvider{ Spec: esv1alpha1.SecretStoreSpec{Provider: &esv1alpha1.SecretStoreProvider{AzureKV: &esv1alpha1.AzureKVProvider{
AuthType: &authType,
IdentityID: &identityID,
VaultURL: &vaultURL,
}}},
}
provider, err := schema.GetProvider(&store)
tassert.Nil(t, err, "the return err should be nil")
k8sClient := clientfake.NewClientBuilder().Build()
secretClient, err := provider.NewClient(context.Background(), &store, k8sClient, namespace)
if err != nil {
// On non Azure environment, MSI auth not available, so this error should be returned
tassert.EqualError(t, err, "failed to get oauth token from MSI: MSI not available")
} else {
// On Azure (where GitHub Actions are running) a secretClient is returned, as only an Authorizer is configured, but no token is requested for MI
tassert.NotNil(t, secretClient)
}
}
func TestNewClientNoCreds(t *testing.T) {
namespace := "internal"
vaultURL := "https://local.vault.url"
tenantID := "1234"
authType := esv1alpha1.ServicePrincipal
store := esv1alpha1.SecretStore{
ObjectMeta: metav1.ObjectMeta{
Namespace: namespace,
},
Spec: esv1alpha1.SecretStoreSpec{Provider: &esv1alpha1.SecretStoreProvider{AzureKV: &esv1alpha1.AzureKVProvider{
AuthType: &authType,
VaultURL: &vaultURL, VaultURL: &vaultURL,
TenantID: &tenantID, TenantID: &tenantID,
}}}, }}},
@ -55,32 +86,27 @@ func TestNewClientNoCreds(t *testing.T) {
provider, err := schema.GetProvider(&store) provider, err := schema.GetProvider(&store)
tassert.Nil(t, err, "the return err should be nil") tassert.Nil(t, err, "the return err should be nil")
k8sClient := clientfake.NewClientBuilder().Build() k8sClient := clientfake.NewClientBuilder().Build()
secretClient, err := provider.NewClient(context.Background(), &store, k8sClient, namespace) _, err = provider.NewClient(context.Background(), &store, k8sClient, namespace)
tassert.EqualError(t, err, "missing clientID/clientSecret in store config") tassert.EqualError(t, err, "missing clientID/clientSecret in store config")
tassert.Nil(t, secretClient)
store.Spec.Provider.AzureKV.AuthSecretRef = &esv1alpha1.AzureKVAuth{} store.Spec.Provider.AzureKV.AuthSecretRef = &esv1alpha1.AzureKVAuth{}
secretClient, err = provider.NewClient(context.Background(), &store, k8sClient, namespace) _, err = provider.NewClient(context.Background(), &store, k8sClient, namespace)
tassert.EqualError(t, err, "missing accessKeyID/secretAccessKey in store config") tassert.EqualError(t, err, "missing accessKeyID/secretAccessKey in store config")
tassert.Nil(t, secretClient)
store.Spec.Provider.AzureKV.AuthSecretRef.ClientID = &v1.SecretKeySelector{Name: "user"} store.Spec.Provider.AzureKV.AuthSecretRef.ClientID = &v1.SecretKeySelector{Name: "user"}
secretClient, err = provider.NewClient(context.Background(), &store, k8sClient, namespace) _, err = provider.NewClient(context.Background(), &store, k8sClient, namespace)
tassert.EqualError(t, err, "missing accessKeyID/secretAccessKey in store config") tassert.EqualError(t, err, "missing accessKeyID/secretAccessKey in store config")
tassert.Nil(t, secretClient)
store.Spec.Provider.AzureKV.AuthSecretRef.ClientSecret = &v1.SecretKeySelector{Name: "password"} store.Spec.Provider.AzureKV.AuthSecretRef.ClientSecret = &v1.SecretKeySelector{Name: "password"}
secretClient, err = provider.NewClient(context.Background(), &store, k8sClient, namespace) _, err = provider.NewClient(context.Background(), &store, k8sClient, namespace)
tassert.EqualError(t, err, "could not find secret internal/user: secrets \"user\" not found") tassert.EqualError(t, err, "could not find secret internal/user: secrets \"user\" not found")
tassert.Nil(t, secretClient)
store.TypeMeta.Kind = esv1alpha1.ClusterSecretStoreKind store.TypeMeta.Kind = esv1alpha1.ClusterSecretStoreKind
store.TypeMeta.APIVersion = esv1alpha1.ClusterSecretStoreKindAPIVersion store.TypeMeta.APIVersion = esv1alpha1.ClusterSecretStoreKindAPIVersion
ns := "default" ns := "default"
store.Spec.Provider.AzureKV.AuthSecretRef.ClientID.Namespace = &ns store.Spec.Provider.AzureKV.AuthSecretRef.ClientID.Namespace = &ns
store.Spec.Provider.AzureKV.AuthSecretRef.ClientSecret.Namespace = &ns store.Spec.Provider.AzureKV.AuthSecretRef.ClientSecret.Namespace = &ns
secretClient, err = provider.NewClient(context.Background(), &store, k8sClient, namespace) _, err = provider.NewClient(context.Background(), &store, k8sClient, namespace)
tassert.EqualError(t, err, "could not find secret default/user: secrets \"user\" not found") tassert.EqualError(t, err, "could not find secret default/user: secrets \"user\" not found")
tassert.Nil(t, secretClient)
} }
const ( const (

View file

@ -109,10 +109,37 @@ func (ibm *providerIBM) GetSecret(ctx context.Context, ref esv1alpha1.ExternalSe
switch secretType { switch secretType {
case sm.GetSecretOptionsSecretTypeArbitraryConst: case sm.GetSecretOptionsSecretTypeArbitraryConst:
return getArbitrarySecret(ibm, &secretName)
case sm.CreateSecretOptionsSecretTypeUsernamePasswordConst:
if ref.Property == "" {
return nil, fmt.Errorf("remoteRef.property required for secret type username_password")
}
return getUsernamePasswordSecret(ibm, &secretName, ref)
case sm.CreateSecretOptionsSecretTypeIamCredentialsConst:
return getIamCredentialsSecret(ibm, &secretName)
case sm.CreateSecretOptionsSecretTypeImportedCertConst:
if ref.Property == "" {
return nil, fmt.Errorf("remoteRef.property required for secret type imported_cert")
}
return getImportCertSecret(ibm, &secretName, ref)
default:
return nil, fmt.Errorf("unknown secret type %s", secretType)
}
}
func getArbitrarySecret(ibm *providerIBM, secretName *string) ([]byte, error) {
response, _, err := ibm.IBMClient.GetSecret( response, _, err := ibm.IBMClient.GetSecret(
&sm.GetSecretOptions{ &sm.GetSecretOptions{
SecretType: core.StringPtr(sm.GetSecretOptionsSecretTypeArbitraryConst), SecretType: core.StringPtr(sm.GetSecretOptionsSecretTypeArbitraryConst),
ID: &secretName, ID: secretName,
}) })
if err != nil { if err != nil {
return nil, err return nil, err
@ -122,15 +149,13 @@ func (ibm *providerIBM) GetSecret(ctx context.Context, ref esv1alpha1.ExternalSe
secretData := secret.SecretData.(map[string]interface{}) secretData := secret.SecretData.(map[string]interface{})
arbitrarySecretPayload := secretData["payload"].(string) arbitrarySecretPayload := secretData["payload"].(string)
return []byte(arbitrarySecretPayload), nil return []byte(arbitrarySecretPayload), nil
}
case sm.CreateSecretOptionsSecretTypeUsernamePasswordConst: func getImportCertSecret(ibm *providerIBM, secretName *string, ref esv1alpha1.ExternalSecretDataRemoteRef) ([]byte, error) {
if ref.Property == "" {
return nil, fmt.Errorf("remoteRef.property required for secret type username_password")
}
response, _, err := ibm.IBMClient.GetSecret( response, _, err := ibm.IBMClient.GetSecret(
&sm.GetSecretOptions{ &sm.GetSecretOptions{
SecretType: core.StringPtr(sm.CreateSecretOptionsSecretTypeUsernamePasswordConst), SecretType: core.StringPtr(sm.CreateSecretOptionsSecretTypeImportedCertConst),
ID: &secretName, ID: secretName,
}) })
if err != nil { if err != nil {
return nil, err return nil, err
@ -143,12 +168,13 @@ func (ibm *providerIBM) GetSecret(ctx context.Context, ref esv1alpha1.ExternalSe
return []byte(val.(string)), nil return []byte(val.(string)), nil
} }
return nil, fmt.Errorf("key %s does not exist in secret %s", ref.Property, ref.Key) return nil, fmt.Errorf("key %s does not exist in secret %s", ref.Property, ref.Key)
}
case sm.CreateSecretOptionsSecretTypeIamCredentialsConst: func getIamCredentialsSecret(ibm *providerIBM, secretName *string) ([]byte, error) {
response, _, err := ibm.IBMClient.GetSecret( response, _, err := ibm.IBMClient.GetSecret(
&sm.GetSecretOptions{ &sm.GetSecretOptions{
SecretType: core.StringPtr(sm.CreateSecretOptionsSecretTypeIamCredentialsConst), SecretType: core.StringPtr(sm.CreateSecretOptionsSecretTypeIamCredentialsConst),
ID: &secretName, ID: secretName,
}) })
if err != nil { if err != nil {
return nil, err return nil, err
@ -158,15 +184,13 @@ func (ibm *providerIBM) GetSecret(ctx context.Context, ref esv1alpha1.ExternalSe
secretData := *secret.APIKey secretData := *secret.APIKey
return []byte(secretData), nil return []byte(secretData), nil
}
case sm.CreateSecretOptionsSecretTypeImportedCertConst: func getUsernamePasswordSecret(ibm *providerIBM, secretName *string, ref esv1alpha1.ExternalSecretDataRemoteRef) ([]byte, error) {
if ref.Property == "" {
return nil, fmt.Errorf("remoteRef.property required for secret type imported_cert")
}
response, _, err := ibm.IBMClient.GetSecret( response, _, err := ibm.IBMClient.GetSecret(
&sm.GetSecretOptions{ &sm.GetSecretOptions{
SecretType: core.StringPtr(sm.CreateSecretOptionsSecretTypeImportedCertConst), SecretType: core.StringPtr(sm.CreateSecretOptionsSecretTypeUsernamePasswordConst),
ID: &secretName, ID: secretName,
}) })
if err != nil { if err != nil {
return nil, err return nil, err
@ -179,10 +203,6 @@ func (ibm *providerIBM) GetSecret(ctx context.Context, ref esv1alpha1.ExternalSe
return []byte(val.(string)), nil return []byte(val.(string)), nil
} }
return nil, fmt.Errorf("key %s does not exist in secret %s", ref.Property, ref.Key) return nil, fmt.Errorf("key %s does not exist in secret %s", ref.Property, ref.Key)
default:
return nil, fmt.Errorf("unknown secret type %s", secretType)
}
} }
func (ibm *providerIBM) GetSecretMap(ctx context.Context, ref esv1alpha1.ExternalSecretDataRemoteRef) (map[string][]byte, error) { func (ibm *providerIBM) GetSecretMap(ctx context.Context, ref esv1alpha1.ExternalSecretDataRemoteRef) (map[string][]byte, error) {
@ -214,16 +234,13 @@ func (ibm *providerIBM) GetSecretMap(ctx context.Context, ref esv1alpha1.Externa
secretData := secret.SecretData.(map[string]interface{}) secretData := secret.SecretData.(map[string]interface{})
arbitrarySecretPayload := secretData["payload"].(string) arbitrarySecretPayload := secretData["payload"].(string)
kv := make(map[string]string) kv := make(map[string]interface{})
err = json.Unmarshal([]byte(arbitrarySecretPayload), &kv) err = json.Unmarshal([]byte(arbitrarySecretPayload), &kv)
if err != nil { if err != nil {
return nil, fmt.Errorf(errJSONSecretUnmarshal, err) return nil, fmt.Errorf(errJSONSecretUnmarshal, err)
} }
secretMap := make(map[string][]byte) secretMap := byteArrayMap(kv)
for k, v := range kv {
secretMap[k] = []byte(v)
}
return secretMap, nil return secretMap, nil
@ -240,10 +257,7 @@ func (ibm *providerIBM) GetSecretMap(ctx context.Context, ref esv1alpha1.Externa
secret := response.Resources[0].(*sm.SecretResource) secret := response.Resources[0].(*sm.SecretResource)
secretData := secret.SecretData.(map[string]interface{}) secretData := secret.SecretData.(map[string]interface{})
secretMap := make(map[string][]byte) secretMap := byteArrayMap(secretData)
for k, v := range secretData {
secretMap[k] = []byte(v.(string))
}
return secretMap, nil return secretMap, nil
@ -278,10 +292,7 @@ func (ibm *providerIBM) GetSecretMap(ctx context.Context, ref esv1alpha1.Externa
secret := response.Resources[0].(*sm.SecretResource) secret := response.Resources[0].(*sm.SecretResource)
secretData := secret.SecretData.(map[string]interface{}) secretData := secret.SecretData.(map[string]interface{})
secretMap := make(map[string][]byte) secretMap := byteArrayMap(secretData)
for k, v := range secretData {
secretMap[k] = []byte(v.(string))
}
return secretMap, nil return secretMap, nil
@ -290,6 +301,14 @@ func (ibm *providerIBM) GetSecretMap(ctx context.Context, ref esv1alpha1.Externa
} }
} }
func byteArrayMap(secretData map[string]interface{}) map[string][]byte {
secretMap := make(map[string][]byte)
for k, v := range secretData {
secretMap[k] = []byte(v.(string))
}
return secretMap
}
func (ibm *providerIBM) Close(ctx context.Context) error { func (ibm *providerIBM) Close(ctx context.Context) error {
return nil return nil
} }

View file

@ -15,8 +15,9 @@ limitations under the License.
package register package register
// packages imported here are registered to the controller schema. // packages imported here are registered to the controller schema.
// nolint:golint // nolint:revive
import ( import (
_ "github.com/external-secrets/external-secrets/pkg/provider/akeyless"
_ "github.com/external-secrets/external-secrets/pkg/provider/alibaba" _ "github.com/external-secrets/external-secrets/pkg/provider/alibaba"
_ "github.com/external-secrets/external-secrets/pkg/provider/aws" _ "github.com/external-secrets/external-secrets/pkg/provider/aws"
_ "github.com/external-secrets/external-secrets/pkg/provider/azure/keyvault" _ "github.com/external-secrets/external-secrets/pkg/provider/azure/keyvault"

View file

@ -224,6 +224,8 @@ func (v *client) readSecret(ctx context.Context, path, version string) (map[stri
byteMap[k] = []byte(t) byteMap[k] = []byte(t)
case []byte: case []byte:
byteMap[k] = t byteMap[k] = t
case nil:
byteMap[k] = []byte(nil)
default: default:
return nil, errors.New(errSecretFormat) return nil, errors.New(errSecretFormat)
} }
@ -316,67 +318,115 @@ func getCertFromConfigMap(v *client) ([]byte, error) {
} }
func (v *client) setAuth(ctx context.Context, client Client, cfg *vault.Config) error { func (v *client) setAuth(ctx context.Context, client Client, cfg *vault.Config) error {
tokenExists, err := setSecretKeyToken(ctx, v, client)
if tokenExists {
return err
}
tokenExists, err = setAppRoleToken(ctx, v, client)
if tokenExists {
return err
}
tokenExists, err = setKubernetesAuthToken(ctx, v, client)
if tokenExists {
return err
}
tokenExists, err = setLdapAuthToken(ctx, v, client)
if tokenExists {
return err
}
tokenExists, err = setJwtAuthToken(ctx, v, client)
if tokenExists {
return err
}
tokenExists, err = setCertAuthToken(ctx, v, client, cfg)
if tokenExists {
return err
}
return errors.New(errAuthFormat)
}
func setAppRoleToken(ctx context.Context, v *client, client Client) (bool, error) {
tokenRef := v.store.Auth.TokenSecretRef tokenRef := v.store.Auth.TokenSecretRef
if tokenRef != nil { if tokenRef != nil {
token, err := v.secretKeyRef(ctx, tokenRef) token, err := v.secretKeyRef(ctx, tokenRef)
if err != nil { if err != nil {
return err return true, err
} }
client.SetToken(token) client.SetToken(token)
return nil return true, nil
} }
return false, nil
}
func setSecretKeyToken(ctx context.Context, v *client, client Client) (bool, error) {
appRole := v.store.Auth.AppRole appRole := v.store.Auth.AppRole
if appRole != nil { if appRole != nil {
token, err := v.requestTokenWithAppRoleRef(ctx, client, appRole) token, err := v.requestTokenWithAppRoleRef(ctx, client, appRole)
if err != nil { if err != nil {
return err return true, err
} }
client.SetToken(token) client.SetToken(token)
return nil return true, nil
} }
return false, nil
}
func setKubernetesAuthToken(ctx context.Context, v *client, client Client) (bool, error) {
kubernetesAuth := v.store.Auth.Kubernetes kubernetesAuth := v.store.Auth.Kubernetes
if kubernetesAuth != nil { if kubernetesAuth != nil {
token, err := v.requestTokenWithKubernetesAuth(ctx, client, kubernetesAuth) token, err := v.requestTokenWithKubernetesAuth(ctx, client, kubernetesAuth)
if err != nil { if err != nil {
return err return true, err
} }
client.SetToken(token) client.SetToken(token)
return nil return true, nil
} }
return false, nil
}
func setLdapAuthToken(ctx context.Context, v *client, client Client) (bool, error) {
ldapAuth := v.store.Auth.Ldap ldapAuth := v.store.Auth.Ldap
if ldapAuth != nil { if ldapAuth != nil {
token, err := v.requestTokenWithLdapAuth(ctx, client, ldapAuth) token, err := v.requestTokenWithLdapAuth(ctx, client, ldapAuth)
if err != nil { if err != nil {
return err return true, err
} }
client.SetToken(token) client.SetToken(token)
return nil return true, nil
} }
return false, nil
}
func setJwtAuthToken(ctx context.Context, v *client, client Client) (bool, error) {
jwtAuth := v.store.Auth.Jwt jwtAuth := v.store.Auth.Jwt
if jwtAuth != nil { if jwtAuth != nil {
token, err := v.requestTokenWithJwtAuth(ctx, client, jwtAuth) token, err := v.requestTokenWithJwtAuth(ctx, client, jwtAuth)
if err != nil { if err != nil {
return err return true, err
} }
client.SetToken(token) client.SetToken(token)
return nil return true, nil
} }
return false, nil
}
func setCertAuthToken(ctx context.Context, v *client, client Client, cfg *vault.Config) (bool, error) {
certAuth := v.store.Auth.Cert certAuth := v.store.Auth.Cert
if certAuth != nil { if certAuth != nil {
token, err := v.requestTokenWithCertAuth(ctx, client, certAuth, cfg) token, err := v.requestTokenWithCertAuth(ctx, client, certAuth, cfg)
if err != nil { if err != nil {
return err return true, err
} }
client.SetToken(token) client.SetToken(token)
return nil return true, nil
} }
return false, nil
return errors.New(errAuthFormat)
} }
func (v *client) secretKeyRefForServiceAccount(ctx context.Context, serviceAccountRef *esmeta.ServiceAccountSelector) (string, error) { func (v *client) secretKeyRefForServiceAccount(ctx context.Context, serviceAccountRef *esmeta.ServiceAccountSelector) (string, error) {
@ -493,43 +543,16 @@ func kubeParameters(role, jwt string) map[string]string {
} }
func (v *client) requestTokenWithKubernetesAuth(ctx context.Context, client Client, kubernetesAuth *esv1alpha1.VaultKubernetesAuth) (string, error) { func (v *client) requestTokenWithKubernetesAuth(ctx context.Context, client Client, kubernetesAuth *esv1alpha1.VaultKubernetesAuth) (string, error) {
jwtString := "" jwtString, err := getJwtString(ctx, v, kubernetesAuth)
if kubernetesAuth.ServiceAccountRef != nil {
jwt, err := v.secretKeyRefForServiceAccount(ctx, kubernetesAuth.ServiceAccountRef)
if err != nil { if err != nil {
return "", err return "", err
} }
jwtString = jwt
} else if kubernetesAuth.SecretRef != nil {
tokenRef := kubernetesAuth.SecretRef
if tokenRef.Key == "" {
tokenRef = kubernetesAuth.SecretRef.DeepCopy()
tokenRef.Key = "token"
}
jwt, err := v.secretKeyRef(ctx, tokenRef)
if err != nil {
return "", err
}
jwtString = jwt
} else {
// Kubernetes authentication is specified, but without a referenced
// Kubernetes secret. We check if the file path for in-cluster service account
// exists and attempt to use the token for Vault Kubernetes auth.
if _, err := os.Stat(serviceAccTokenPath); err != nil {
return "", fmt.Errorf(errServiceAccount, err)
}
jwtByte, err := ioutil.ReadFile(serviceAccTokenPath)
if err != nil {
return "", fmt.Errorf(errServiceAccount, err)
}
jwtString = string(jwtByte)
}
parameters := kubeParameters(kubernetesAuth.Role, jwtString) parameters := kubeParameters(kubernetesAuth.Role, jwtString)
url := strings.Join([]string{"/v1", "auth", kubernetesAuth.Path, "login"}, "/") url := strings.Join([]string{"/v1", "auth", kubernetesAuth.Path, "login"}, "/")
request := client.NewRequest("POST", url) request := client.NewRequest("POST", url)
err := request.SetJSONBody(parameters) err = request.SetJSONBody(parameters)
if err != nil { if err != nil {
return "", fmt.Errorf(errVaultReqParams, err) return "", fmt.Errorf(errVaultReqParams, err)
} }
@ -554,6 +577,39 @@ func (v *client) requestTokenWithKubernetesAuth(ctx context.Context, client Clie
return token, nil return token, nil
} }
func getJwtString(ctx context.Context, v *client, kubernetesAuth *esv1alpha1.VaultKubernetesAuth) (string, error) {
if kubernetesAuth.ServiceAccountRef != nil {
jwt, err := v.secretKeyRefForServiceAccount(ctx, kubernetesAuth.ServiceAccountRef)
if err != nil {
return "", err
}
return jwt, nil
} else if kubernetesAuth.SecretRef != nil {
tokenRef := kubernetesAuth.SecretRef
if tokenRef.Key == "" {
tokenRef = kubernetesAuth.SecretRef.DeepCopy()
tokenRef.Key = "token"
}
jwt, err := v.secretKeyRef(ctx, tokenRef)
if err != nil {
return "", err
}
return jwt, nil
} else {
// Kubernetes authentication is specified, but without a referenced
// Kubernetes secret. We check if the file path for in-cluster service account
// exists and attempt to use the token for Vault Kubernetes auth.
if _, err := os.Stat(serviceAccTokenPath); err != nil {
return "", fmt.Errorf(errServiceAccount, err)
}
jwtByte, err := ioutil.ReadFile(serviceAccTokenPath)
if err != nil {
return "", fmt.Errorf(errServiceAccount, err)
}
return string(jwtByte), nil
}
}
func (v *client) requestTokenWithLdapAuth(ctx context.Context, client Client, ldapAuth *esv1alpha1.VaultLdapAuth) (string, error) { func (v *client) requestTokenWithLdapAuth(ctx context.Context, client Client, ldapAuth *esv1alpha1.VaultLdapAuth) (string, error) {
username := strings.TrimSpace(ldapAuth.Username) username := strings.TrimSpace(ldapAuth.Username)

View file

@ -41,7 +41,7 @@ const (
secretDataString = "some-creds" secretDataString = "some-creds"
) )
func makeValidSecretStore() *esv1alpha1.SecretStore { func makeValidSecretStoreWithVersion(v esv1alpha1.VaultKVStoreVersion) *esv1alpha1.SecretStore {
return &esv1alpha1.SecretStore{ return &esv1alpha1.SecretStore{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: "vault-store", Name: "vault-store",
@ -52,7 +52,7 @@ func makeValidSecretStore() *esv1alpha1.SecretStore {
Vault: &esv1alpha1.VaultProvider{ Vault: &esv1alpha1.VaultProvider{
Server: "vault.example.com", Server: "vault.example.com",
Path: "secret", Path: "secret",
Version: esv1alpha1.VaultKVStoreV2, Version: v,
Auth: esv1alpha1.VaultAuth{ Auth: esv1alpha1.VaultAuth{
Kubernetes: &esv1alpha1.VaultKubernetesAuth{ Kubernetes: &esv1alpha1.VaultKubernetesAuth{
Path: "kubernetes", Path: "kubernetes",
@ -68,6 +68,10 @@ func makeValidSecretStore() *esv1alpha1.SecretStore {
} }
} }
func makeValidSecretStore() *esv1alpha1.SecretStore {
return makeValidSecretStoreWithVersion(esv1alpha1.VaultKVStoreV2)
}
func makeValidSecretStoreWithCerts() *esv1alpha1.SecretStore { func makeValidSecretStoreWithCerts() *esv1alpha1.SecretStore {
return &esv1alpha1.SecretStore{ return &esv1alpha1.SecretStore{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
@ -136,14 +140,35 @@ func newVaultResponse(data *vault.Secret) *vault.Response {
} }
} }
func newVaultTokenIDResponse(token string) *vault.Response { func newVaultResponseWithData(data map[string]interface{}) *vault.Response {
return newVaultResponse(&vault.Secret{ return newVaultResponse(&vault.Secret{
Data: map[string]interface{}{ Data: data,
"id": token,
},
}) })
} }
func newVaultTokenIDResponse(token string) *vault.Response {
return newVaultResponseWithData(map[string]interface{}{
"id": token,
})
}
type args struct {
newClientFunc func(c *vault.Config) (Client, error)
store esv1alpha1.GenericStore
kube kclient.Client
ns string
}
type want struct {
err error
}
type testCase struct {
reason string
args args
want want
}
func clientWithLoginMock(c *vault.Config) (Client, error) { func clientWithLoginMock(c *vault.Config) (Client, error) {
return &fake.VaultClient{ return &fake.VaultClient{
MockNewRequest: fake.NewMockNewRequestFn(&vault.Request{}), MockNewRequest: fake.NewMockNewRequestFn(&vault.Request{}),
@ -182,22 +207,7 @@ MIICsTCCAZkCFEJJ4daz5sxkFlzq9n1djLEuG7bmMA0GCSqGSIb3DQEBCwUAMBMxETAPBgNVBAMMCHZh
`) `)
secretData := []byte(secretDataString) secretData := []byte(secretDataString)
type args struct { cases := map[string]testCase{
newClientFunc func(c *vault.Config) (Client, error)
store esv1alpha1.GenericStore
kube kclient.Client
ns string
}
type want struct {
err error
}
cases := map[string]struct {
reason string
args args
want want
}{
"InvalidVaultStore": { "InvalidVaultStore": {
reason: "Should return error if given an invalid vault store.", reason: "Should return error if given an invalid vault store.",
args: args{ args: args{
@ -472,6 +482,12 @@ MIICsTCCAZkCFEJJ4daz5sxkFlzq9n1djLEuG7bmMA0GCSqGSIb3DQEBCwUAMBMxETAPBgNVBAMMCHZh
for name, tc := range cases { for name, tc := range cases {
t.Run(name, func(t *testing.T) { t.Run(name, func(t *testing.T) {
vaultTest(t, name, tc)
})
}
}
func vaultTest(t *testing.T, name string, tc testCase) {
conn := &connector{ conn := &connector{
newVaultClient: tc.args.newClientFunc, newVaultClient: tc.args.newClientFunc,
} }
@ -482,12 +498,19 @@ MIICsTCCAZkCFEJJ4daz5sxkFlzq9n1djLEuG7bmMA0GCSqGSIb3DQEBCwUAMBMxETAPBgNVBAMMCHZh
if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" { if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" {
t.Errorf("\n%s\nvault.New(...): -want error, +got error:\n%s", tc.reason, diff) t.Errorf("\n%s\nvault.New(...): -want error, +got error:\n%s", tc.reason, diff)
} }
})
}
} }
func TestGetSecretMap(t *testing.T) { func TestGetSecretMap(t *testing.T) {
errBoom := errors.New("boom") errBoom := errors.New("boom")
secret := map[string]interface{}{
"access_key": "access_key",
"access_secret": "access_secret",
}
secretWithNilVal := map[string]interface{}{
"access_key": "access_key",
"access_secret": "access_secret",
"token": nil,
}
type args struct { type args struct {
store *esv1alpha1.VaultProvider store *esv1alpha1.VaultProvider
@ -506,6 +529,74 @@ func TestGetSecretMap(t *testing.T) {
args args args args
want want want want
}{ }{
"ReadSecretKV1": {
reason: "Should map the secret even if it has a nil value",
args: args{
store: makeValidSecretStoreWithVersion(esv1alpha1.VaultKVStoreV1).Spec.Provider.Vault,
vClient: &fake.VaultClient{
MockNewRequest: fake.NewMockNewRequestFn(&vault.Request{}),
MockRawRequestWithContext: fake.NewMockRawRequestWithContextFn(
newVaultResponseWithData(secret), nil,
),
},
},
want: want{
err: nil,
},
},
"ReadSecretKV2": {
reason: "Should map the secret even if it has a nil value",
args: args{
store: makeValidSecretStoreWithVersion(esv1alpha1.VaultKVStoreV2).Spec.Provider.Vault,
vClient: &fake.VaultClient{
MockNewRequest: fake.NewMockNewRequestFn(&vault.Request{}),
MockRawRequestWithContext: fake.NewMockRawRequestWithContextFn(
newVaultResponseWithData(
map[string]interface{}{
"data": secret,
},
), nil,
),
},
},
want: want{
err: nil,
},
},
"ReadSecretWithNilValueKV1": {
reason: "Should map the secret even if it has a nil value",
args: args{
store: makeValidSecretStoreWithVersion(esv1alpha1.VaultKVStoreV1).Spec.Provider.Vault,
vClient: &fake.VaultClient{
MockNewRequest: fake.NewMockNewRequestFn(&vault.Request{}),
MockRawRequestWithContext: fake.NewMockRawRequestWithContextFn(
newVaultResponseWithData(secretWithNilVal), nil,
),
},
},
want: want{
err: nil,
},
},
"ReadSecretWithNilValueKV2": {
reason: "Should map the secret even if it has a nil value",
args: args{
store: makeValidSecretStoreWithVersion(esv1alpha1.VaultKVStoreV2).Spec.Provider.Vault,
vClient: &fake.VaultClient{
MockNewRequest: fake.NewMockNewRequestFn(&vault.Request{}),
MockRawRequestWithContext: fake.NewMockRawRequestWithContextFn(
newVaultResponseWithData(
map[string]interface{}{
"data": secretWithNilVal,
},
), nil,
),
},
},
want: want{
err: nil,
},
},
"ReadSecretError": { "ReadSecretError": {
reason: "Should return error if vault client fails to read secret.", reason: "Should return error if vault client fails to read secret.",
args: args{ args: args{

View file

@ -35,6 +35,12 @@ import (
"github.com/external-secrets/external-secrets/pkg/provider/yandex/lockbox/client/fake" "github.com/external-secrets/external-secrets/pkg/provider/yandex/lockbox/client/fake"
) )
const (
errMissingKey = "invalid Yandex Lockbox SecretStore resource: missing AuthorizedKey Name"
errSecretPayloadPermissionDenied = "unable to request secret payload to get secret: permission denied"
errSecretPayloadNotFound = "unable to request secret payload to get secret: secret not found"
)
func TestNewClient(t *testing.T) { func TestNewClient(t *testing.T) {
ctx := context.Background() ctx := context.Background()
const namespace = "namespace" const namespace = "namespace"
@ -54,17 +60,17 @@ func TestNewClient(t *testing.T) {
k8sClient := clientfake.NewClientBuilder().Build() k8sClient := clientfake.NewClientBuilder().Build()
secretClient, err := provider.NewClient(context.Background(), store, k8sClient, namespace) secretClient, err := provider.NewClient(context.Background(), store, k8sClient, namespace)
tassert.EqualError(t, err, "invalid Yandex Lockbox SecretStore resource: missing AuthorizedKey Name") tassert.EqualError(t, err, errMissingKey)
tassert.Nil(t, secretClient) tassert.Nil(t, secretClient)
store.Spec.Provider.YandexLockbox.Auth = esv1alpha1.YandexLockboxAuth{} store.Spec.Provider.YandexLockbox.Auth = esv1alpha1.YandexLockboxAuth{}
secretClient, err = provider.NewClient(context.Background(), store, k8sClient, namespace) secretClient, err = provider.NewClient(context.Background(), store, k8sClient, namespace)
tassert.EqualError(t, err, "invalid Yandex Lockbox SecretStore resource: missing AuthorizedKey Name") tassert.EqualError(t, err, errMissingKey)
tassert.Nil(t, secretClient) tassert.Nil(t, secretClient)
store.Spec.Provider.YandexLockbox.Auth.AuthorizedKey = esmeta.SecretKeySelector{} store.Spec.Provider.YandexLockbox.Auth.AuthorizedKey = esmeta.SecretKeySelector{}
secretClient, err = provider.NewClient(context.Background(), store, k8sClient, namespace) secretClient, err = provider.NewClient(context.Background(), store, k8sClient, namespace)
tassert.EqualError(t, err, "invalid Yandex Lockbox SecretStore resource: missing AuthorizedKey Name") tassert.EqualError(t, err, errMissingKey)
tassert.Nil(t, secretClient) tassert.Nil(t, secretClient)
const authorizedKeySecretName = "authorizedKeySecretName" const authorizedKeySecretName = "authorizedKeySecretName"
@ -248,7 +254,7 @@ func TestGetSecretUnauthorized(t *testing.T) {
secretsClient, err := provider.NewClient(ctx, store, k8sClient, namespace) secretsClient, err := provider.NewClient(ctx, store, k8sClient, namespace)
tassert.Nil(t, err) tassert.Nil(t, err)
_, err = secretsClient.GetSecret(ctx, esv1alpha1.ExternalSecretDataRemoteRef{Key: secretID}) _, err = secretsClient.GetSecret(ctx, esv1alpha1.ExternalSecretDataRemoteRef{Key: secretID})
tassert.EqualError(t, err, "unable to request secret payload to get secret: permission denied") tassert.EqualError(t, err, errSecretPayloadPermissionDenied)
} }
func TestGetSecretNotFound(t *testing.T) { func TestGetSecretNotFound(t *testing.T) {
@ -271,7 +277,7 @@ func TestGetSecretNotFound(t *testing.T) {
secretsClient, err := provider.NewClient(ctx, store, k8sClient, namespace) secretsClient, err := provider.NewClient(ctx, store, k8sClient, namespace)
tassert.Nil(t, err) tassert.Nil(t, err)
_, err = secretsClient.GetSecret(ctx, esv1alpha1.ExternalSecretDataRemoteRef{Key: "no-secret-with-this-id"}) _, err = secretsClient.GetSecret(ctx, esv1alpha1.ExternalSecretDataRemoteRef{Key: "no-secret-with-this-id"})
tassert.EqualError(t, err, "unable to request secret payload to get secret: secret not found") tassert.EqualError(t, err, errSecretPayloadNotFound)
secretID, _ := lockboxBackend.CreateSecret(authorizedKey, secretID, _ := lockboxBackend.CreateSecret(authorizedKey,
textEntry("k1", "v1"), textEntry("k1", "v1"),
@ -320,11 +326,11 @@ func TestGetSecretWithTwoNamespaces(t *testing.T) {
tassert.Nil(t, err) tassert.Nil(t, err)
data, err = secretsClient1.GetSecret(ctx, esv1alpha1.ExternalSecretDataRemoteRef{Key: secretID2, Property: k2}) data, err = secretsClient1.GetSecret(ctx, esv1alpha1.ExternalSecretDataRemoteRef{Key: secretID2, Property: k2})
tassert.Nil(t, data) tassert.Nil(t, data)
tassert.EqualError(t, err, "unable to request secret payload to get secret: permission denied") tassert.EqualError(t, err, errSecretPayloadPermissionDenied)
data, err = secretsClient2.GetSecret(ctx, esv1alpha1.ExternalSecretDataRemoteRef{Key: secretID1, Property: k1}) data, err = secretsClient2.GetSecret(ctx, esv1alpha1.ExternalSecretDataRemoteRef{Key: secretID1, Property: k1})
tassert.Nil(t, data) tassert.Nil(t, data)
tassert.EqualError(t, err, "unable to request secret payload to get secret: permission denied") tassert.EqualError(t, err, errSecretPayloadPermissionDenied)
data, err = secretsClient2.GetSecret(ctx, esv1alpha1.ExternalSecretDataRemoteRef{Key: secretID2, Property: k2}) data, err = secretsClient2.GetSecret(ctx, esv1alpha1.ExternalSecretDataRemoteRef{Key: secretID2, Property: k2})
tassert.Equal(t, v2, string(data)) tassert.Equal(t, v2, string(data))
tassert.Nil(t, err) tassert.Nil(t, err)
@ -381,11 +387,11 @@ func TestGetSecretWithTwoApiEndpoints(t *testing.T) {
tassert.Nil(t, err) tassert.Nil(t, err)
data, err = secretsClient1.GetSecret(ctx, esv1alpha1.ExternalSecretDataRemoteRef{Key: secretID2, Property: k2}) data, err = secretsClient1.GetSecret(ctx, esv1alpha1.ExternalSecretDataRemoteRef{Key: secretID2, Property: k2})
tassert.Nil(t, data) tassert.Nil(t, data)
tassert.EqualError(t, err, "unable to request secret payload to get secret: secret not found") tassert.EqualError(t, err, errSecretPayloadNotFound)
data, err = secretsClient2.GetSecret(ctx, esv1alpha1.ExternalSecretDataRemoteRef{Key: secretID1, Property: k1}) data, err = secretsClient2.GetSecret(ctx, esv1alpha1.ExternalSecretDataRemoteRef{Key: secretID1, Property: k1})
tassert.Nil(t, data) tassert.Nil(t, data)
tassert.EqualError(t, err, "unable to request secret payload to get secret: secret not found") tassert.EqualError(t, err, errSecretPayloadNotFound)
data, err = secretsClient2.GetSecret(ctx, esv1alpha1.ExternalSecretDataRemoteRef{Key: secretID2, Property: k2}) data, err = secretsClient2.GetSecret(ctx, esv1alpha1.ExternalSecretDataRemoteRef{Key: secretID2, Property: k2})
tassert.Equal(t, v2, string(data)) tassert.Equal(t, v2, string(data))
tassert.Nil(t, err) tassert.Nil(t, err)

View file

@ -39,7 +39,14 @@ func MergeStringMap(dest, src map[string]string) {
// IsNil checks if an Interface is nil. // IsNil checks if an Interface is nil.
func IsNil(i interface{}) bool { func IsNil(i interface{}) bool {
return i == nil || reflect.ValueOf(i).IsNil() if i == nil {
return true
}
value := reflect.ValueOf(i)
if value.Type().Kind() == reflect.Ptr {
return value.IsNil()
}
return false
} }
// ObjectHash calculates md5 sum of the data contained in the secret. // ObjectHash calculates md5 sum of the data contained in the secret.

View file

@ -17,6 +17,7 @@ package utils
import ( import (
"testing" "testing"
vault "github.com/oracle/oci-go-sdk/v45/vault"
v1 "k8s.io/api/core/v1" v1 "k8s.io/api/core/v1"
) )
@ -60,3 +61,88 @@ func TestObjectHash(t *testing.T) {
}) })
} }
} }
func TestIsNil(t *testing.T) {
tbl := []struct {
name string
val interface{}
exp bool
}{
{
name: "simple nil val",
val: nil,
exp: true,
},
{
name: "nil slice",
val: (*[]struct{})(nil),
exp: true,
},
{
name: "struct pointer",
val: &testing.T{},
exp: false,
},
{
name: "struct",
val: testing.T{},
exp: false,
},
{
name: "slice of struct",
val: []struct{}{{}},
exp: false,
},
{
name: "slice of ptr",
val: []*testing.T{nil},
exp: false,
},
{
name: "slice",
val: []struct{}(nil),
exp: false,
},
{
name: "int default value",
val: 0,
exp: false,
},
{
name: "empty str",
val: "",
exp: false,
},
{
name: "oracle vault",
val: vault.VaultsClient{},
exp: false,
},
{
name: "func",
val: func() {
// noop for testing and to make linter happy
},
exp: false,
},
{
name: "channel",
val: make(chan struct{}),
exp: false,
},
{
name: "map",
val: map[string]string{},
exp: false,
},
}
for _, row := range tbl {
t.Run(row.name, func(t *testing.T) {
res := IsNil(row.val)
if res != row.exp {
t.Errorf("IsNil(%#v)=%t, expected %t", row.val, res, row.exp)
}
})
}
}

View file

@ -4,5 +4,6 @@
package tools package tools
import ( import (
_ "github.com/onsi/ginkgo/ginkgo"
_ "sigs.k8s.io/controller-tools/cmd/controller-gen" _ "sigs.k8s.io/controller-tools/cmd/controller-gen"
) )