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:
commit
082cee230f
59 changed files with 2477 additions and 419 deletions
5
.github/PAUL.yaml
vendored
5
.github/PAUL.yaml
vendored
|
@ -47,6 +47,11 @@ pull_requests:
|
|||
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.
|
||||
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
|
||||
cats_enabled: true
|
||||
# enables the /dog command
|
||||
|
|
26
.github/workflows/ci.yml
vendored
26
.github/workflows/ci.yml
vendored
|
@ -11,7 +11,7 @@ on:
|
|||
env:
|
||||
# Common versions
|
||||
GO_VERSION: '1.16'
|
||||
GOLANGCI_VERSION: 'v1.33'
|
||||
GOLANGCI_VERSION: 'v1.42.1'
|
||||
# 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
|
||||
KUBEBUILDER_TOOLS_VERSION: '1.20.2'
|
||||
|
@ -61,14 +61,14 @@ jobs:
|
|||
echo "::set-output name=mod-cache::$(go env GOMODCACHE)"
|
||||
|
||||
- name: Cache the Go Build Cache
|
||||
uses: actions/cache@v2.1.6
|
||||
uses: actions/cache@v2.1.7
|
||||
with:
|
||||
path: ${{ steps.go.outputs.build-cache }}
|
||||
key: ${{ runner.os }}-build-lint-${{ hashFiles('**/go.sum') }}
|
||||
restore-keys: ${{ runner.os }}-build-lint-
|
||||
|
||||
- name: Cache Go Dependencies
|
||||
uses: actions/cache@v2.1.6
|
||||
uses: actions/cache@v2.1.7
|
||||
with:
|
||||
path: ${{ steps.go.outputs.mod-cache }}
|
||||
key: ${{ runner.os }}-pkg-${{ hashFiles('**/go.sum') }}
|
||||
|
@ -107,21 +107,25 @@ jobs:
|
|||
echo "::set-output name=mod-cache::$(go env GOMODCACHE)"
|
||||
|
||||
- name: Cache the Go Build Cache
|
||||
uses: actions/cache@v2.1.6
|
||||
uses: actions/cache@v2.1.7
|
||||
with:
|
||||
path: ${{ steps.go.outputs.build-cache }}
|
||||
key: ${{ runner.os }}-build-check-diff-${{ hashFiles('**/go.sum') }}
|
||||
restore-keys: ${{ runner.os }}-build-check-diff-
|
||||
|
||||
- name: Cache Go Dependencies
|
||||
uses: actions/cache@v2.1.6
|
||||
uses: actions/cache@v2.1.7
|
||||
with:
|
||||
path: ${{ steps.go.outputs.mod-cache }}
|
||||
key: ${{ runner.os }}-pkg-${{ hashFiles('**/go.sum') }}
|
||||
restore-keys: ${{ runner.os }}-pkg-
|
||||
|
||||
# Check DIff also runs Reviewable which needs golangci-lint installed
|
||||
- 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:
|
||||
runs-on: ubuntu-18.04
|
||||
|
@ -147,14 +151,14 @@ jobs:
|
|||
echo "::set-output name=mod-cache::$(go env GOMODCACHE)"
|
||||
|
||||
- name: Cache the Go Build Cache
|
||||
uses: actions/cache@v2.1.6
|
||||
uses: actions/cache@v2.1.7
|
||||
with:
|
||||
path: ${{ steps.go.outputs.build-cache }}
|
||||
key: ${{ runner.os }}-build-unit-tests-${{ hashFiles('**/go.sum') }}
|
||||
restore-keys: ${{ runner.os }}-build-unit-tests-
|
||||
|
||||
- name: Cache Go Dependencies
|
||||
uses: actions/cache@v2.1.6
|
||||
uses: actions/cache@v2.1.7
|
||||
with:
|
||||
path: ${{ steps.go.outputs.mod-cache }}
|
||||
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
|
||||
|
||||
- name: Cache envtest binaries
|
||||
uses: actions/cache@v2.1.6
|
||||
uses: actions/cache@v2.1.7
|
||||
with:
|
||||
path: /usr/local/kubebuilder
|
||||
key: ${{ runner.os }}-kubebuilder-${{env.KUBEBUILDER_TOOLS_VERSION}}
|
||||
|
@ -214,14 +218,14 @@ jobs:
|
|||
echo "::set-output name=mod-cache::$(go env GOMODCACHE)"
|
||||
|
||||
- name: Cache the Go Build Cache
|
||||
uses: actions/cache@v2.1.6
|
||||
uses: actions/cache@v2.1.7
|
||||
with:
|
||||
path: ${{ steps.go.outputs.build-cache }}
|
||||
key: ${{ runner.os }}-build-publish-artifacts-${{ hashFiles('**/go.sum') }}
|
||||
restore-keys: ${{ runner.os }}-build-publish-artifacts-
|
||||
|
||||
- name: Cache Go Dependencies
|
||||
uses: actions/cache@v2.1.6
|
||||
uses: actions/cache@v2.1.7
|
||||
with:
|
||||
path: ${{ steps.go.outputs.mod-cache }}
|
||||
key: ${{ runner.os }}-pkg-${{ hashFiles('**/go.sum') }}
|
||||
|
|
18
.github/workflows/e2e.yml
vendored
18
.github/workflows/e2e.yml
vendored
|
@ -50,14 +50,14 @@ jobs:
|
|||
echo "::set-output name=mod-cache::$(go env GOMODCACHE)"
|
||||
|
||||
- name: Cache the Go Build Cache
|
||||
uses: actions/cache@v2.1.6
|
||||
uses: actions/cache@v2.1.7
|
||||
with:
|
||||
path: ${{ steps.go.outputs.build-cache }}
|
||||
key: ${{ runner.os }}-build-unit-tests-${{ hashFiles('**/go.sum') }}
|
||||
restore-keys: ${{ runner.os }}-build-unit-tests-
|
||||
|
||||
- name: Cache Go Dependencies
|
||||
uses: actions/cache@v2.1.6
|
||||
uses: actions/cache@v2.1.7
|
||||
with:
|
||||
path: ${{ steps.go.outputs.mod-cache }}
|
||||
key: ${{ runner.os }}-pkg-${{ hashFiles('**/go.sum') }}
|
||||
|
@ -113,14 +113,14 @@ jobs:
|
|||
echo "::set-output name=mod-cache::$(go env GOMODCACHE)"
|
||||
|
||||
- name: Cache the Go Build Cache
|
||||
uses: actions/cache@v2.1.6
|
||||
uses: actions/cache@v2.1.7
|
||||
with:
|
||||
path: ${{ steps.go.outputs.build-cache }}
|
||||
key: ${{ runner.os }}-build-unit-tests-${{ hashFiles('**/go.sum') }}
|
||||
restore-keys: ${{ runner.os }}-build-unit-tests-
|
||||
|
||||
- name: Cache Go Dependencies
|
||||
uses: actions/cache@v2.1.6
|
||||
uses: actions/cache@v2.1.7
|
||||
with:
|
||||
path: ${{ steps.go.outputs.mod-cache }}
|
||||
key: ${{ runner.os }}-pkg-${{ hashFiles('**/go.sum') }}
|
||||
|
@ -149,7 +149,7 @@ jobs:
|
|||
make test.e2e
|
||||
|
||||
# Update check run called "integration-fork"
|
||||
- uses: actions/github-script@v5
|
||||
- uses: actions/github-script@v1
|
||||
id: update-check-run
|
||||
if: ${{ always() }}
|
||||
env:
|
||||
|
@ -165,19 +165,19 @@ jobs:
|
|||
pull_number: process.env.number
|
||||
});
|
||||
const ref = pull.head.sha;
|
||||
|
||||
console.log("\n\nPR sha: " + ref)
|
||||
const { data: checks } = await github.checks.listForRef({
|
||||
...context.repo,
|
||||
ref
|
||||
});
|
||||
|
||||
console.log("\n\nPR CHECKS: " + checks)
|
||||
const check = checks.check_runs.filter(c => c.name === process.env.job);
|
||||
|
||||
console.log("\n\nPR Filtered CHECK: " + check)
|
||||
console.log(check)
|
||||
const { data: result } = await github.checks.update({
|
||||
...context.repo,
|
||||
check_run_id: check[0].id,
|
||||
status: 'completed',
|
||||
conclusion: process.env.conclusion
|
||||
});
|
||||
|
||||
return result;
|
||||
|
|
|
@ -32,14 +32,10 @@ linters-settings:
|
|||
min-complexity: 16
|
||||
goheader:
|
||||
template-path: ./hack/boilerplate.go.txt
|
||||
golint:
|
||||
min-confidence: 0
|
||||
govet:
|
||||
check-shadowing: false
|
||||
lll:
|
||||
line-length: 300
|
||||
maligned:
|
||||
suggest-new: true
|
||||
misspell:
|
||||
locale: US
|
||||
|
||||
|
@ -62,7 +58,6 @@ linters:
|
|||
- gocritic
|
||||
- godot
|
||||
- gofmt
|
||||
- golint
|
||||
- goprintffuncname
|
||||
- gosec
|
||||
- gosimple
|
||||
|
@ -70,13 +65,12 @@ linters:
|
|||
- ineffassign
|
||||
- interfacer
|
||||
- lll
|
||||
- maligned
|
||||
- misspell
|
||||
- nakedret
|
||||
- nolintlint
|
||||
- prealloc
|
||||
- revive
|
||||
- rowserrcheck
|
||||
- scopelint
|
||||
- sqlclosecheck
|
||||
- staticcheck
|
||||
- structcheck
|
||||
|
@ -102,7 +96,6 @@ issues:
|
|||
- errcheck
|
||||
- dupl
|
||||
- gosec
|
||||
- scopelint
|
||||
- unparam
|
||||
- lll
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
FROM alpine:3.14.2
|
||||
FROM gcr.io/distroless/static
|
||||
ARG TARGETOS
|
||||
ARG TARGETARCH
|
||||
COPY bin/external-secrets-${TARGETOS}-${TARGETARCH} /bin/external-secrets
|
||||
|
|
47
Makefile
47
Makefile
|
@ -65,12 +65,10 @@ FAIL = (echo ${TIME} ${RED}[FAIL]${CNone} && false)
|
|||
# ====================================================================================
|
||||
# Conformance
|
||||
|
||||
# Ensure a PR is ready for review.
|
||||
reviewable: generate helm.generate
|
||||
reviewable: generate helm.generate lint ## Ensure a PR is ready for review.
|
||||
@go mod tidy
|
||||
|
||||
# Ensure branch is clean.
|
||||
check-diff: reviewable
|
||||
check-diff: reviewable ## Ensure branch is clean.
|
||||
@$(INFO) checking that branch is clean
|
||||
@test -z "$$(git status --porcelain)" || (echo "$$(git status --porcelain)" && $(FAIL))
|
||||
@$(OK) branch is clean
|
||||
|
@ -91,7 +89,7 @@ test.e2e: generate ## Run e2e tests
|
|||
@$(OK) go test unit-tests
|
||||
|
||||
.PHONY: build
|
||||
build: $(addprefix build-,$(ARCH))
|
||||
build: $(addprefix build-,$(ARCH)) ## Build binary
|
||||
|
||||
.PHONY: build-%
|
||||
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
|
||||
@$(OK) go build $*
|
||||
|
||||
# Check install of golanci-lint
|
||||
lint.check:
|
||||
lint.check: ## Check install of golanci-lint
|
||||
@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"; \
|
||||
exit 1; \
|
||||
fi
|
||||
|
||||
# installs golangci-lint to the go bin dir
|
||||
lint.install:
|
||||
lint.install: ## Install golangci-lint to the go bin dir
|
||||
@if ! golangci-lint --version > /dev/null 2>&1; then \
|
||||
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
|
||||
|
||||
lint: lint.check ## run golangci-lint
|
||||
lint: lint.check ## Run golangci-lint
|
||||
@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"; \
|
||||
exit 1; \
|
||||
fi
|
||||
@$(OK) Finished linting
|
||||
|
||||
fmt: lint.check ## ensure consistent code style
|
||||
fmt: lint.check ## Ensure consistent code style
|
||||
@go mod tidy
|
||||
@go fmt ./...
|
||||
@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.
|
||||
# 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
|
||||
|
||||
# Generate manifests from helm chart
|
||||
manifests: helm.generate
|
||||
manifests: helm.generate ## Generate manifests from helm chart
|
||||
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
|
||||
|
||||
# Install CRDs into a cluster. This is for convenience.
|
||||
crds.install: generate
|
||||
crds.install: generate ## Install CRDs into a cluster. This is for convenience
|
||||
kubectl apply -f $(CRD_DIR)
|
||||
|
||||
# Uninstall CRDs from a cluster. This is for convenience.
|
||||
crds.uninstall:
|
||||
crds.uninstall: ## Uninstall CRDs from a cluster. This is for convenience
|
||||
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
|
||||
@$(OK) helm package
|
||||
|
||||
# Copy crds to helm chart directory
|
||||
helm.generate: helm.docs
|
||||
helm.generate: helm.docs ## Copy crds to helm chart directory
|
||||
@cp $(CRD_DIR)/*.yaml $(HELM_DIR)/templates/crds/
|
||||
# Add helm if statement for controlling the install of CRDs
|
||||
@for i in $(HELM_DIR)/templates/crds/*.yaml; do \
|
||||
|
@ -189,24 +182,24 @@ helm.generate: helm.docs
|
|||
# ====================================================================================
|
||||
# Documentation
|
||||
.PHONY: docs
|
||||
docs: generate
|
||||
docs: generate ## Generate docs
|
||||
$(MAKE) -C ./hack/api-docs build
|
||||
|
||||
.PHONY: serve-docs
|
||||
serve-docs:
|
||||
serve-docs: ## Serve docs
|
||||
$(MAKE) -C ./hack/api-docs serve
|
||||
|
||||
# ====================================================================================
|
||||
# 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
|
||||
@$(INFO) docker build
|
||||
@docker build . $(BUILD_ARGS) -t $(IMAGE_REGISTRY):$(VERSION)
|
||||
@$(OK) docker build
|
||||
|
||||
docker.push:
|
||||
docker.push: ## Push the docker image to the registry
|
||||
@$(INFO) docker push
|
||||
@docker push $(IMAGE_REGISTRY):$(VERSION)
|
||||
@$(OK) docker push
|
||||
|
@ -216,7 +209,7 @@ docker.push:
|
|||
RELEASE_TAG ?= main
|
||||
SOURCE_TAG ?= $(VERSION)
|
||||
|
||||
docker.promote:
|
||||
docker.promote: ## Promote the docker image to the registry
|
||||
@$(INFO) promoting $(SOURCE_TAG) to $(RELEASE_TAG)
|
||||
docker manifest inspect $(IMAGE_REGISTRY):$(SOURCE_TAG) > .tagmanifest
|
||||
for digest in $$(jq -r '.manifests[].digest' < .tagmanifest); do \
|
||||
|
@ -231,5 +224,5 @@ docker.promote:
|
|||
# Help
|
||||
|
||||
# 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)"
|
||||
|
|
12
README.md
12
README.md
|
@ -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 Parameter Store](https://external-secrets.io/provider-aws-parameter-store/)
|
||||
- [Akeyless](https://www.akeyless.io/)
|
||||
- [Hashicorp Vault](https://www.vaultproject.io/)
|
||||
- [Google Cloud Secrets Manager](https://external-secrets.io/provider-google-secrets-manager/)
|
||||
- [Azure Key Vault](https://external-secrets.io/provider-azure-key-vault/)
|
||||
|
@ -20,7 +21,7 @@ Multiple people and organizations are joining efforts to create a single Externa
|
|||
- [Yandex Lockbox](https://external-secrets.io/provider-yandex-lockbox/)
|
||||
- [Gitlab Project Variables](https://external-secrets.io/provider-gitlab-project-variables/)
|
||||
- [Alibaba Cloud KMS](https://www.alibabacloud.com/product/kms) (Docs still missing, PRs welcomed!)
|
||||
- [Oracle Vault]( https://external-secrets.io/provider-oracle-vault)
|
||||
- [Oracle Vault]( https://external-secrets.io/provider-oracle-vault)
|
||||
|
||||
## Stability and Support Level
|
||||
|
||||
|
@ -28,10 +29,10 @@ Multiple people and organizations are joining efforts to create a single Externa
|
|||
|
||||
| Provider | Stability | Contact |
|
||||
| ------------------------------------------------------------------------ | :-------: | ---------------------------------------------: |
|
||||
| [AWS SM](https://external-secrets.io/provider-aws-secrets-manager/) | alpha | [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) |
|
||||
| [Hashicorp Vault](https://external-secrets.io/provider-hashicorp-vault/) | alpha | [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) |
|
||||
| [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/) | beta | [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/) | beta | [ESO Org](https://github.com/external-secrets) |
|
||||
|
||||
### 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) |
|
||||
| Alibaba Cloud KMS | alpha | [@ElsaChelala](https://github.com/ElsaChelala) |
|
||||
| [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
|
||||
|
|
42
apis/externalsecrets/v1alpha1/secretstore_akeyless_types.go
Normal file
42
apis/externalsecrets/v1alpha1/secretstore_akeyless_types.go
Normal 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"`
|
||||
}
|
|
@ -16,14 +16,41 @@ package v1alpha1
|
|||
|
||||
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.
|
||||
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.
|
||||
VaultURL *string `json:"vaultUrl"`
|
||||
// TenantID configures the Azure Tenant to send requests to.
|
||||
TenantID *string `json:"tenantId"`
|
||||
// Auth configures how the operator authenticates with Azure.
|
||||
AuthSecretRef *AzureKVAuth `json:"authSecretRef"`
|
||||
// TenantID configures the Azure Tenant to send requests to. Required for ServicePrincipal auth type.
|
||||
// +optional
|
||||
TenantID *string `json:"tenantId,omitempty"`
|
||||
// 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.
|
||||
|
|
|
@ -46,6 +46,10 @@ type SecretStoreProvider struct {
|
|||
// +optional
|
||||
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
|
||||
// +optional
|
||||
Vault *VaultProvider `json:"vault,omitempty"`
|
||||
|
|
|
@ -102,6 +102,65 @@ func (in *AWSProvider) DeepCopy() *AWSProvider {
|
|||
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.
|
||||
func (in *AlibabaAuth) DeepCopyInto(out *AlibabaAuth) {
|
||||
*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.
|
||||
func (in *AzureKVProvider) DeepCopyInto(out *AzureKVProvider) {
|
||||
*out = *in
|
||||
if in.AuthType != nil {
|
||||
in, out := &in.AuthType, &out.AuthType
|
||||
*out = new(AuthType)
|
||||
**out = **in
|
||||
}
|
||||
if in.VaultURL != nil {
|
||||
in, out := &in.VaultURL, &out.VaultURL
|
||||
*out = new(string)
|
||||
|
@ -198,6 +262,11 @@ func (in *AzureKVProvider) DeepCopyInto(out *AzureKVProvider) {
|
|||
*out = new(AzureKVAuth)
|
||||
(*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.
|
||||
|
@ -794,6 +863,11 @@ func (in *SecretStoreProvider) DeepCopyInto(out *SecretStoreProvider) {
|
|||
*out = new(AzureKVProvider)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.Akeyless != nil {
|
||||
in, out := &in.Akeyless, &out.Akeyless
|
||||
*out = new(AkeylessProvider)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.Vault != nil {
|
||||
in, out := &in.Vault, &out.Vault
|
||||
*out = new(VaultProvider)
|
||||
|
|
|
@ -2,8 +2,8 @@ apiVersion: v2
|
|||
name: external-secrets
|
||||
description: External secret management for Kubernetes
|
||||
type: application
|
||||
version: "0.3.6"
|
||||
appVersion: "v0.3.6"
|
||||
version: "0.3.9"
|
||||
appVersion: "v0.3.9"
|
||||
kubeVersion: ">= 1.11.0-0"
|
||||
keywords:
|
||||
- kubernetes-external-secrets
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
[//]: # (README.md generated by gotmpl. DO NOT EDIT.)
|
||||
|
||||
 
|
||||
 
|
||||
|
||||
External secret management for Kubernetes
|
||||
|
||||
|
|
|
@ -54,6 +54,94 @@ spec:
|
|||
maxProperties: 1
|
||||
minProperties: 1
|
||||
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:
|
||||
description: Alibaba configures this store to sync secrets using
|
||||
Alibaba Cloud provider
|
||||
|
@ -222,7 +310,7 @@ spec:
|
|||
properties:
|
||||
authSecretRef:
|
||||
description: Auth configures how the operator authenticates
|
||||
with Azure.
|
||||
with Azure. Required for ServicePrincipal auth type.
|
||||
properties:
|
||||
clientId:
|
||||
description: The Azure clientId of the service principle
|
||||
|
@ -266,17 +354,30 @@ spec:
|
|||
- clientId
|
||||
- clientSecret
|
||||
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:
|
||||
description: TenantID configures the Azure Tenant to send
|
||||
requests to.
|
||||
requests to. Required for ServicePrincipal auth type.
|
||||
type: string
|
||||
vaultUrl:
|
||||
description: Vault Url from which the secrets to be fetched
|
||||
from.
|
||||
type: string
|
||||
required:
|
||||
- authSecretRef
|
||||
- tenantId
|
||||
- vaultUrl
|
||||
type: object
|
||||
gcpsm:
|
||||
|
|
|
@ -54,6 +54,94 @@ spec:
|
|||
maxProperties: 1
|
||||
minProperties: 1
|
||||
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:
|
||||
description: Alibaba configures this store to sync secrets using
|
||||
Alibaba Cloud provider
|
||||
|
@ -222,7 +310,7 @@ spec:
|
|||
properties:
|
||||
authSecretRef:
|
||||
description: Auth configures how the operator authenticates
|
||||
with Azure.
|
||||
with Azure. Required for ServicePrincipal auth type.
|
||||
properties:
|
||||
clientId:
|
||||
description: The Azure clientId of the service principle
|
||||
|
@ -266,17 +354,30 @@ spec:
|
|||
- clientId
|
||||
- clientSecret
|
||||
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:
|
||||
description: TenantID configures the Azure Tenant to send
|
||||
requests to.
|
||||
requests to. Required for ServicePrincipal auth type.
|
||||
type: string
|
||||
vaultUrl:
|
||||
description: Vault Url from which the secrets to be fetched
|
||||
from.
|
||||
type: string
|
||||
required:
|
||||
- authSecretRef
|
||||
- tenantId
|
||||
- vaultUrl
|
||||
type: object
|
||||
gcpsm:
|
||||
|
|
68
docs/provider-akeyless.md
Normal file
68
docs/provider-akeyless.md
Normal 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}'
|
||||
```
|
|
@ -7,23 +7,37 @@ External Secrets Operator integrates with [Azure Key vault](https://azure.micros
|
|||
|
||||
### 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
|
||||
|
||||
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
|
||||
{% include 'azkv-credentials-secret.yaml' %}
|
||||
```
|
||||
|
||||
### 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
|
||||
{% include 'azkv-secret-store.yaml' %}
|
||||
```
|
||||
|
||||
Or in case of Managed Idenetity authentication:
|
||||
|
||||
```yaml
|
||||
{% include 'azkv-secret-store-mi.yaml' %}
|
||||
```
|
||||
|
||||
### 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).
|
||||
|
|
12
docs/snippets/akeyless-credentials-secret.yaml
Normal file
12
docs/snippets/akeyless-credentials-secret.yaml
Normal 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
|
||||
|
||||
|
||||
|
18
docs/snippets/akeyless-external-secret-json.yaml
Normal file
18
docs/snippets/akeyless-external-secret-json.yaml
Normal 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
|
19
docs/snippets/akeyless-external-secret.yaml
Normal file
19
docs/snippets/akeyless-external-secret.yaml
Normal 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
|
20
docs/snippets/akeyless-secret-store.yaml
Normal file
20
docs/snippets/akeyless-secret-store.yaml
Normal 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
|
13
docs/snippets/azkv-secret-store-mi.yaml
Normal file
13
docs/snippets/azkv-secret-store-mi.yaml
Normal 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"
|
|
@ -1,5 +1,5 @@
|
|||
{% raw %}
|
||||
# define your tempalte in a config map
|
||||
# define your template in a config map
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
ARG GO_VERSION=1.16
|
||||
FROM golang:$GO_VERSION-buster as builder
|
||||
|
||||
ENV KUBECTL_VERSION="v1.19.2"
|
||||
ENV HELM_VERSION="v3.3.4"
|
||||
ENV KUBECTL_VERSION="v1.21.2"
|
||||
ENV HELM_VERSION="v3.7.1"
|
||||
|
||||
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 && \
|
||||
|
@ -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 && \
|
||||
chmod +x /usr/local/bin/helm
|
||||
|
||||
FROM alpine:3.12
|
||||
FROM alpine:3.15.0
|
||||
RUN apk add -U --no-cache \
|
||||
ca-certificates \
|
||||
bash \
|
||||
|
|
|
@ -4,7 +4,7 @@ SHELL := /bin/bash
|
|||
|
||||
IMG_TAG = test
|
||||
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 ?=
|
||||
export FOCUS := $(FOCUS)
|
||||
|
||||
|
@ -19,13 +19,14 @@ test: e2e-image ## Run e2e tests against current kube context
|
|||
$(MAKE) -C ../ docker.build \
|
||||
IMAGE_REGISTRY=local/external-secrets \
|
||||
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" $(IMG)
|
||||
./run.sh
|
||||
|
||||
e2e-bin:
|
||||
CGO_ENABLED=0 ginkgo build .
|
||||
CGO_ENABLED=0 go run github.com/onsi/ginkgo/ginkgo build .
|
||||
|
||||
e2e-image: e2e-bin
|
||||
-rm -rf ./k8s/deploy
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
set -e
|
||||
set -euo pipefail
|
||||
|
||||
NC='\e[0m'
|
||||
BGREEN='\e[32m'
|
||||
|
@ -46,8 +46,8 @@ ginkgo_args=(
|
|||
kubectl apply -f /k8s/deploy/crds
|
||||
|
||||
echo -e "${BGREEN}Running e2e test suite (FOCUS=${FOCUS})...${NC}"
|
||||
ginkgo "${ginkgo_args[@]}" \
|
||||
-focus="${FOCUS}" \
|
||||
-skip="\[Serial\]|\[MemoryLeak\]" \
|
||||
-nodes="${E2E_NODES}" \
|
||||
ACK_GINKGO_RC=true ginkgo "${ginkgo_args[@]}" \
|
||||
-focus="${FOCUS}" \
|
||||
-skip="\[Serial\]|\[MemoryLeak\]" \
|
||||
-nodes="${E2E_NODES}" \
|
||||
/e2e.test
|
||||
|
|
|
@ -28,7 +28,7 @@ import (
|
|||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/golang-jwt/jwt"
|
||||
"github.com/golang-jwt/jwt/v4"
|
||||
vault "github.com/hashicorp/vault/api"
|
||||
|
||||
// nolint
|
||||
|
@ -286,6 +286,7 @@ func (l *Vault) Setup(cfg *Config) error {
|
|||
return l.chart.Setup(cfg)
|
||||
}
|
||||
|
||||
// nolint:gocritic
|
||||
func genVaultCertificates(namespace string) ([]byte, []byte, []byte, []byte, []byte, []byte, error) {
|
||||
// gen server ca + certs
|
||||
serverRootCert, serverRootPem, serverRootKey, err := genCARoot()
|
||||
|
|
|
@ -13,9 +13,7 @@
|
|||
# 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.
|
||||
set -o errexit
|
||||
set -o nounset
|
||||
set -o pipefail
|
||||
set -euo pipefail
|
||||
|
||||
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/"
|
||||
|
@ -56,6 +54,9 @@ kubectl run --rm \
|
|||
--env="GCP_PROJECT_ID=${GCP_PROJECT_ID:-}" \
|
||||
--env="AZURE_CLIENT_ID=${AZURE_CLIENT_ID:-}" \
|
||||
--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="VAULT_URL=${VAULT_URL:-}" \
|
||||
--env="GITLAB_TOKEN=${GITLAB_TOKEN:-}" \
|
||||
|
|
49
e2e/suite/akeyless/akeyless.go
Normal file
49
e2e/suite/akeyless/akeyless.go
Normal 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)),
|
||||
)
|
||||
})
|
227
e2e/suite/akeyless/provider.go
Normal file
227
e2e/suite/akeyless/provider.go
Normal 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
|
||||
}
|
|
@ -31,7 +31,11 @@ var _ = Describe("[alibaba] ", func() {
|
|||
accessKeyID := os.Getenv("ACCESS_KEY_ID")
|
||||
accessKeySecret := os.Getenv("ACCESS_KEY_SECRET")
|
||||
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),
|
||||
Entry(common.SimpleDataSync(f)),
|
||||
|
|
|
@ -30,7 +30,11 @@ var _ = Describe("[azure] ", func() {
|
|||
tenantID := os.Getenv("TENANT_ID")
|
||||
clientID := os.Getenv("AZURE_CLIENT_ID")
|
||||
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),
|
||||
Entry(common.SimpleDataSync(f)),
|
||||
|
|
|
@ -22,6 +22,14 @@ import (
|
|||
"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.
|
||||
// Not supported by: vault.
|
||||
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.
|
||||
func DockerJSONConfig(f *framework.Framework) (string, func(*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"}}}`
|
||||
cloudSecretValue := fmt.Sprintf(`{"dockerconfig": %s}`, dockerconfig)
|
||||
tc.Secrets = map[string]string{
|
||||
|
@ -299,7 +307,7 @@ func DockerJSONConfig(f *framework.Framework) (string, func(*framework.TestCase)
|
|||
tc.ExpectedSecret = &v1.Secret{
|
||||
Type: v1.SecretTypeOpaque,
|
||||
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{
|
||||
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.
|
||||
func DataPropertyDockerconfigJSON(f *framework.Framework) (string, func(*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\"}}}"`
|
||||
dockerconfig := `{"auths":{"https://index.docker.io/v1/": {"auth": "c3R...zE2"}}}`
|
||||
cloudSecretValue := fmt.Sprintf(`{"dockerconfig": %s}`, dockerconfigString)
|
||||
|
@ -337,7 +345,7 @@ func DataPropertyDockerconfigJSON(f *framework.Framework) (string, func(*framewo
|
|||
tc.ExpectedSecret = &v1.Secret{
|
||||
Type: v1.SecretTypeDockerConfigJson,
|
||||
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{
|
||||
Type: v1.SecretTypeDockerConfigJson,
|
||||
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{
|
||||
Type: v1.SecretTypeSSHAuth,
|
||||
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{
|
||||
Type: v1.SecretTypeSSHAuth,
|
||||
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.
|
||||
func SSHKeySyncDataProperty(f *framework.Framework) (string, func(*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-----
|
||||
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABlwAAAAdzc2gtcn
|
||||
NhAAAAAwEAAQAAAYEAsARoZUqo6L5dd0WRjZ2QPq/kKlbjtUY1njzJ01UtdC1u1eSJFUnV
|
||||
|
@ -483,7 +491,7 @@ func SSHKeySyncDataProperty(f *framework.Framework) (string, func(*framework.Tes
|
|||
tc.ExpectedSecret = &v1.Secret{
|
||||
Type: v1.SecretTypeSSHAuth,
|
||||
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{
|
||||
Type: v1.SecretTypeSSHAuth,
|
||||
Data: map[string]string{
|
||||
"ssh-privatekey": "{{ .mysecret | toString }}",
|
||||
sshPrivateKey: mysecretToStringTemplating,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -36,7 +36,11 @@ var _ = Describe("[gcp] ", func() {
|
|||
f := framework.New("eso-gcp")
|
||||
credentials := os.Getenv("GCP_SM_SA_JSON")
|
||||
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.
|
||||
// It uses templating to generate a k8s secret of type tls with pem values
|
||||
|
|
|
@ -33,7 +33,12 @@ var _ = Describe("[gitlab] ", func() {
|
|||
f := framework.New("esogitlab")
|
||||
credentials := os.Getenv("GITLAB_TOKEN")
|
||||
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),
|
||||
Entry(common.SimpleDataSync(f)),
|
||||
|
|
|
@ -23,6 +23,15 @@ import (
|
|||
"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() {
|
||||
f := framework.New("eso-vault")
|
||||
|
||||
|
@ -30,41 +39,41 @@ var _ = Describe("[vault] ", func() {
|
|||
framework.TableFunc(f,
|
||||
newVaultProvider(f)),
|
||||
// uses token auth
|
||||
compose("with token auth", f, common.JSONDataFromSync, useTokenAuth),
|
||||
compose("with token auth", f, common.JSONDataWithProperty, useTokenAuth),
|
||||
compose("with token auth", f, common.JSONDataWithTemplate, useTokenAuth),
|
||||
compose("with token auth", f, common.DataPropertyDockerconfigJSON, useTokenAuth),
|
||||
compose("with token auth", f, common.JSONDataWithoutTargetName, useTokenAuth),
|
||||
compose(withTokenAuth, f, common.JSONDataFromSync, useTokenAuth),
|
||||
compose(withTokenAuth, f, common.JSONDataWithProperty, useTokenAuth),
|
||||
compose(withTokenAuth, f, common.JSONDataWithTemplate, useTokenAuth),
|
||||
compose(withTokenAuth, f, common.DataPropertyDockerconfigJSON, useTokenAuth),
|
||||
compose(withTokenAuth, f, common.JSONDataWithoutTargetName, useTokenAuth),
|
||||
// use cert auth
|
||||
compose("with cert auth", f, common.JSONDataFromSync, useCertAuth),
|
||||
compose("with cert auth", f, common.JSONDataWithProperty, useCertAuth),
|
||||
compose("with cert auth", f, common.JSONDataWithTemplate, useCertAuth),
|
||||
compose("with cert auth", f, common.DataPropertyDockerconfigJSON, useCertAuth),
|
||||
compose("with cert auth", f, common.JSONDataWithoutTargetName, useTokenAuth),
|
||||
compose(withCertAuth, f, common.JSONDataFromSync, useCertAuth),
|
||||
compose(withCertAuth, f, common.JSONDataWithProperty, useCertAuth),
|
||||
compose(withCertAuth, f, common.JSONDataWithTemplate, useCertAuth),
|
||||
compose(withCertAuth, f, common.DataPropertyDockerconfigJSON, useCertAuth),
|
||||
compose(withCertAuth, f, common.JSONDataWithoutTargetName, useTokenAuth),
|
||||
// use approle auth
|
||||
compose("with appRole auth", f, common.JSONDataFromSync, useApproleAuth),
|
||||
compose("with appRole auth", f, common.JSONDataWithProperty, useApproleAuth),
|
||||
compose("with appRole auth", f, common.JSONDataWithTemplate, useApproleAuth),
|
||||
compose("with appRole auth", f, common.DataPropertyDockerconfigJSON, useApproleAuth),
|
||||
compose("with appRole auth", f, common.JSONDataWithoutTargetName, useTokenAuth),
|
||||
compose(withApprole, f, common.JSONDataFromSync, useApproleAuth),
|
||||
compose(withApprole, f, common.JSONDataWithProperty, useApproleAuth),
|
||||
compose(withApprole, f, common.JSONDataWithTemplate, useApproleAuth),
|
||||
compose(withApprole, f, common.DataPropertyDockerconfigJSON, useApproleAuth),
|
||||
compose(withApprole, f, common.JSONDataWithoutTargetName, useTokenAuth),
|
||||
// use v1 provider
|
||||
compose("with v1 kv provider", f, common.JSONDataFromSync, useV1Provider),
|
||||
compose("with v1 kv provider", f, common.JSONDataWithProperty, useV1Provider),
|
||||
compose("with v1 kv provider", f, common.JSONDataWithTemplate, useV1Provider),
|
||||
compose("with v1 kv provider", f, common.DataPropertyDockerconfigJSON, useV1Provider),
|
||||
compose("with v1 kv provider", f, common.JSONDataWithoutTargetName, useTokenAuth),
|
||||
compose(withV1, f, common.JSONDataFromSync, useV1Provider),
|
||||
compose(withV1, f, common.JSONDataWithProperty, useV1Provider),
|
||||
compose(withV1, f, common.JSONDataWithTemplate, useV1Provider),
|
||||
compose(withV1, f, common.DataPropertyDockerconfigJSON, useV1Provider),
|
||||
compose(withV1, f, common.JSONDataWithoutTargetName, useTokenAuth),
|
||||
// use jwt provider
|
||||
compose("with jwt provider", f, common.JSONDataFromSync, useJWTProvider),
|
||||
compose("with jwt provider", f, common.JSONDataWithProperty, useJWTProvider),
|
||||
compose("with jwt provider", f, common.JSONDataWithTemplate, useJWTProvider),
|
||||
compose("with jwt provider", f, common.DataPropertyDockerconfigJSON, useJWTProvider),
|
||||
compose("with jwt provider", f, common.JSONDataWithoutTargetName, useTokenAuth),
|
||||
compose(withJWT, f, common.JSONDataFromSync, useJWTProvider),
|
||||
compose(withJWT, f, common.JSONDataWithProperty, useJWTProvider),
|
||||
compose(withJWT, f, common.JSONDataWithTemplate, useJWTProvider),
|
||||
compose(withJWT, f, common.DataPropertyDockerconfigJSON, useJWTProvider),
|
||||
compose(withJWT, f, common.JSONDataWithoutTargetName, useTokenAuth),
|
||||
// use kubernetes provider
|
||||
compose("with kubernetes provider", f, common.JSONDataFromSync, useKubernetesProvider),
|
||||
compose("with kubernetes provider", f, common.JSONDataWithProperty, useKubernetesProvider),
|
||||
compose("with kubernetes provider", f, common.JSONDataWithTemplate, useKubernetesProvider),
|
||||
compose("with kubernetes provider", f, common.DataPropertyDockerconfigJSON, useKubernetesProvider),
|
||||
compose("with kubernetes provider", f, common.JSONDataWithoutTargetName, useTokenAuth),
|
||||
compose(withK8s, f, common.JSONDataFromSync, useKubernetesProvider),
|
||||
compose(withK8s, f, common.JSONDataWithProperty, useKubernetesProvider),
|
||||
compose(withK8s, f, common.JSONDataWithTemplate, useKubernetesProvider),
|
||||
compose(withK8s, f, common.DataPropertyDockerconfigJSON, useKubernetesProvider),
|
||||
compose(withK8s, f, common.JSONDataWithoutTargetName, useTokenAuth),
|
||||
)
|
||||
})
|
||||
|
||||
|
|
22
go.mod
22
go.mod
|
@ -33,20 +33,23 @@ replace (
|
|||
)
|
||||
|
||||
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/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/validation v0.3.1 // indirect
|
||||
github.com/IBM/go-sdk-core/v5 v5.5.0
|
||||
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/aws/aws-sdk-go v1.38.6
|
||||
github.com/crossplane/crossplane-runtime v0.13.0
|
||||
github.com/fatih/color 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/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/gofuzz v1.2.0 // indirect
|
||||
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/kr/pretty v0.2.1 // indirect
|
||||
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/oracle/oci-go-sdk/v45 v45.2.0
|
||||
github.com/pierrec/lz4 v2.5.2+incompatible // indirect
|
||||
|
@ -65,7 +68,7 @@ require (
|
|||
github.com/prometheus/client_model v0.2.0
|
||||
github.com/spf13/cobra v1.1.3 // indirect
|
||||
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/yandex-cloud/go-genproto v0.0.0-20210809082946-a97da516c588
|
||||
github.com/yandex-cloud/go-sdk v0.0.0-20210809100642-c13c40a429fa
|
||||
|
@ -73,11 +76,12 @@ require (
|
|||
go.uber.org/zap v1.17.0
|
||||
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83
|
||||
golang.org/x/exp v0.0.0-20200331195152-e8c3332aa8e5 // indirect
|
||||
golang.org/x/oauth2 v0.0.0-20210201163806-010130855d6c
|
||||
golang.org/x/tools v0.1.2-0.20210512205948-8287d5da45e4 // indirect
|
||||
google.golang.org/api v0.30.0
|
||||
google.golang.org/genproto v0.0.0-20201110150050-8816d57aaa9a
|
||||
google.golang.org/grpc v1.31.0
|
||||
golang.org/x/oauth2 v0.0.0-20210413134643-5e61552d6c78
|
||||
golang.org/x/sys v0.0.0-20211102061401-a2f17f7b995c // indirect
|
||||
golang.org/x/tools v0.1.7 // indirect
|
||||
google.golang.org/api v0.45.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
|
||||
k8s.io/api v0.21.3
|
||||
k8s.io/apimachinery v0.21.3
|
||||
|
|
119
go.sum
119
go.sum
|
@ -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.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.65.0 h1:Dg9iHVQfrhq82rUNu9ZxUDrJLaxFUe/HlCVaLyRruq8=
|
||||
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.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
|
||||
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/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/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-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
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.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-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 v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||
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/go.mod h1:ui7WezCLWMWxVWr1GETZY3smRy0G4KWq9vcPtJmFl7Y=
|
||||
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.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/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
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-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-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-test/deep v1.0.2-0.20181118220953-042da051cf31/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
|
||||
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/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptGaCkuDUx6wNykzzlUixGxvkme+H/lnzb+A=
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
|
||||
github.com/golang-jwt/jwt/v4 v4.2.0 h1:besgBTC8w8HjP6NzQdxwKH9Z5oQMZ24ThTrHp3cZ8eU=
|
||||
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/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
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.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.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8=
|
||||
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.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.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
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/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
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.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.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.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
|
||||
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/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.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-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
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-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-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/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=
|
||||
|
@ -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/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-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.10/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
|
||||
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.2/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
|
||||
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.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 v1.5.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/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
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.7.5/go.mod h1:5/xDoumyyDNerp2U36lyolv46b3uF/9Bu6OfyQ9GImk=
|
||||
github.com/tidwall/match v1.0.3 h1:FQUVvBImDutD8wJLN6c5eMzWtjgONK9MwIBCOrUJKeE=
|
||||
github.com/tidwall/match v1.0.3/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
||||
github.com/tidwall/gjson v1.12.1 h1:ikuZsLdhr8Ws0IdROXUS1Gi4v9Z4pGqpX/CvJkxvfpo=
|
||||
github.com/tidwall/gjson v1.12.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
|
||||
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.1.0 h1:K3hMW5epkdAVwibsQEfR/7Zj0Qgt4DxtNumTq/VloO8=
|
||||
github.com/tidwall/pretty v1.1.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
|
||||
github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
|
||||
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-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||
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.32/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.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||
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.2/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.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.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
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-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-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-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-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
||||
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.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.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.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo=
|
||||
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-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-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-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-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781 h1:DzZ89McO9/gWPsQXS/FVKAlG02ZjaQ6AlZRBimEYOd0=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
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-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-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-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-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-20210201163806-010130855d6c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/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-20181108010431-42b317875d0f/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-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-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-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-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-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-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-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-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
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-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-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-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-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-20210114065538-d78b04bdf963/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
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.2-0.20210512205948-8287d5da45e4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.7 h1:6j8CgantCy3yc8JGBqkDLMKWqZ0RDU2g1HVgacojGWQ=
|
||||
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-20191011141410-1b5146add898/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.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.30.0 h1:yfrXXP61wVuLb0vBcG6qaOoIoqYEzOQS8jum51jkv2w=
|
||||
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.4.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-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-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-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-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/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
|
|
|
@ -45,6 +45,7 @@ nav:
|
|||
- Secrets Manager: provider-google-secrets-manager.md
|
||||
- IBM:
|
||||
- Secrets Manager: provider-ibm-secrets-manager.md
|
||||
- Akeyless: provider-akeyless.md
|
||||
- HashiCorp Vault: provider-hashicorp-vault.md
|
||||
- Yandex:
|
||||
- Lockbox: provider-yandex-lockbox.md
|
||||
|
|
|
@ -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")
|
||||
currCond := GetExternalSecretCondition(externalSecret.Status, esv1alpha1.ExternalSecretReady)
|
||||
SetExternalSecretCondition(&externalSecret, *conditionSynced)
|
||||
externalSecret.Status.RefreshTime = metav1.NewTime(time.Now())
|
||||
externalSecret.Status.SyncedResourceVersion = getResourceVersion(externalSecret)
|
||||
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{
|
||||
RequeueAfter: refreshInt,
|
||||
|
|
|
@ -134,6 +134,11 @@ var _ = Describe("ExternalSecret controller", func() {
|
|||
ExternalSecretName = "test-es"
|
||||
ExternalSecretStore = "test-store"
|
||||
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
|
||||
|
@ -283,13 +288,13 @@ var _ = Describe("ExternalSecret controller", func() {
|
|||
// create secret beforehand
|
||||
Expect(k8sClient.Create(context.Background(), &v1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-secret",
|
||||
Name: ExternalSecretTargetSecretName,
|
||||
Namespace: ExternalSecretNamespace,
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
existingKey: []byte(existingVal),
|
||||
},
|
||||
}, client.FieldOwner("fake.manager"))).To(Succeed())
|
||||
}, client.FieldOwner(FakeManager))).To(Succeed())
|
||||
|
||||
fakeProvider.WithGetSecret([]byte(secretVal), nil)
|
||||
tc.checkSecret = func(es *esv1alpha1.ExternalSecret, secret *v1.Secret) {
|
||||
|
@ -309,7 +314,7 @@ var _ = Describe("ExternalSecret controller", func() {
|
|||
"external-secrets",
|
||||
fmt.Sprintf("{\"f:data\":{\"f:targetProperty\":{}},\"f:immutable\":{},\"f:metadata\":{\"f:annotations\":{\"f:%s\":{}}}}", esv1alpha1.AnnotationDataHash)),
|
||||
).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
|
||||
Expect(k8sClient.Create(context.Background(), &v1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-secret",
|
||||
Name: ExternalSecretTargetSecretName,
|
||||
Namespace: ExternalSecretNamespace,
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
existingKey: []byte(existingVal),
|
||||
},
|
||||
}, client.FieldOwner("fake.manager"))).To(Succeed())
|
||||
}, client.FieldOwner(FakeManager))).To(Succeed())
|
||||
fakeProvider.WithGetSecret([]byte(secretVal), nil)
|
||||
|
||||
tc.checkCondition = func(es *esv1alpha1.ExternalSecret) bool {
|
||||
|
@ -373,7 +378,7 @@ var _ = Describe("ExternalSecret controller", func() {
|
|||
// check owner/managedFields
|
||||
Expect(hasOwnerRef(secret.ObjectMeta, "ExternalSecret", ExternalSecretName)).To(BeFalse())
|
||||
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
|
||||
syncWithTemplate := func(tc *testCase) {
|
||||
const secretVal = "someValue"
|
||||
const expectedSecretVal = "SOMEVALUE was templated"
|
||||
const tplStaticKey = "tplstatickey"
|
||||
const tplStaticVal = "tplstaticvalue"
|
||||
tc.externalSecret.ObjectMeta.Labels = map[string]string{
|
||||
|
@ -401,7 +405,7 @@ var _ = Describe("ExternalSecret controller", func() {
|
|||
},
|
||||
Type: v1.SecretTypeOpaque,
|
||||
Data: map[string]string{
|
||||
targetProp: "{{ .targetProperty | toString | upper }} was templated",
|
||||
targetProp: targetPropObj,
|
||||
tplStaticKey: tplStaticVal,
|
||||
},
|
||||
}
|
||||
|
@ -426,7 +430,6 @@ var _ = Describe("ExternalSecret controller", func() {
|
|||
// * dataFrom
|
||||
syncWithTemplatePrecedence := func(tc *testCase) {
|
||||
const secretVal = "someValue"
|
||||
const expectedSecretVal = "SOMEVALUE was templated"
|
||||
const tplStaticKey = "tplstatickey"
|
||||
const tplStaticVal = "tplstaticvalue"
|
||||
const tplFromCMName = "template-cm"
|
||||
|
@ -480,7 +483,7 @@ var _ = Describe("ExternalSecret controller", func() {
|
|||
},
|
||||
Data: map[string]string{
|
||||
// this should be the data value, not dataFrom
|
||||
targetProp: "{{ .targetProperty | toString | upper }} was templated",
|
||||
targetProp: targetPropObj,
|
||||
// this should use the value from the map
|
||||
"bar": "value from map: {{ .bar | toString }}",
|
||||
// just a static value
|
||||
|
@ -494,8 +497,8 @@ var _ = Describe("ExternalSecret controller", func() {
|
|||
}
|
||||
fakeProvider.WithGetSecret([]byte(secretVal), nil)
|
||||
fakeProvider.WithGetSecretMap(map[string][]byte{
|
||||
"targetProperty": []byte("map-foo-value"),
|
||||
"bar": []byte("map-bar-value"),
|
||||
"targetProperty": []byte(FooValue),
|
||||
"bar": []byte(BarValue),
|
||||
}, nil)
|
||||
tc.checkSecret = func(es *esv1alpha1.ExternalSecret, secret *v1.Secret) {
|
||||
// check values
|
||||
|
@ -509,7 +512,6 @@ var _ = Describe("ExternalSecret controller", func() {
|
|||
|
||||
refreshWithTemplate := func(tc *testCase) {
|
||||
const secretVal = "someValue"
|
||||
const expectedSecretVal = "SOMEVALUE was templated"
|
||||
const tplStaticKey = "tplstatickey"
|
||||
const tplStaticVal = "tplstaticvalue"
|
||||
tc.externalSecret.Spec.RefreshInterval = &metav1.Duration{Duration: time.Second}
|
||||
|
@ -520,7 +522,7 @@ var _ = Describe("ExternalSecret controller", func() {
|
|||
},
|
||||
Type: v1.SecretTypeOpaque,
|
||||
Data: map[string]string{
|
||||
targetProp: "{{ .targetProperty | toString | upper }} was templated",
|
||||
targetProp: targetPropObj,
|
||||
tplStaticKey: tplStaticVal,
|
||||
},
|
||||
}
|
||||
|
@ -660,13 +662,13 @@ var _ = Describe("ExternalSecret controller", func() {
|
|||
},
|
||||
}
|
||||
fakeProvider.WithGetSecretMap(map[string][]byte{
|
||||
"foo": []byte("map-foo-value"),
|
||||
"bar": []byte("map-bar-value"),
|
||||
"foo": []byte(FooValue),
|
||||
"bar": []byte(BarValue),
|
||||
}, nil)
|
||||
tc.checkSecret = func(es *esv1alpha1.ExternalSecret, secret *v1.Secret) {
|
||||
// check values
|
||||
Expect(string(secret.Data["foo"])).To(Equal("map-foo-value"))
|
||||
Expect(string(secret.Data["bar"])).To(Equal("map-bar-value"))
|
||||
Expect(string(secret.Data["foo"])).To(Equal(FooValue))
|
||||
Expect(string(secret.Data["bar"])).To(Equal(BarValue))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -687,14 +689,14 @@ var _ = Describe("ExternalSecret controller", func() {
|
|||
},
|
||||
}
|
||||
fakeProvider.WithGetSecretMap(map[string][]byte{
|
||||
"tls.crt": []byte("map-foo-value"),
|
||||
"tls.key": []byte("map-bar-value"),
|
||||
"tls.crt": []byte(FooValue),
|
||||
"tls.key": []byte(BarValue),
|
||||
}, nil)
|
||||
tc.checkSecret = func(es *esv1alpha1.ExternalSecret, secret *v1.Secret) {
|
||||
Expect(secret.Type).To(Equal(v1.SecretTypeTLS))
|
||||
// check values
|
||||
Expect(string(secret.Data["tls.crt"])).To(Equal("map-foo-value"))
|
||||
Expect(string(secret.Data["tls.key"])).To(Equal("map-bar-value"))
|
||||
Expect(string(secret.Data["tls.crt"])).To(Equal(FooValue))
|
||||
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
|
||||
checkSecretDataHashAnnotationChange := func(tc *testCase) {
|
||||
fakeData := map[string][]byte{
|
||||
"targetProperty": []byte("map-foo-value"),
|
||||
"targetProperty": []byte(FooValue),
|
||||
}
|
||||
fakeProvider.WithGetSecretMap(fakeData, nil)
|
||||
tc.externalSecret.Spec.RefreshInterval = &metav1.Duration{Duration: time.Minute * 10}
|
||||
|
|
155
pkg/provider/akeyless/akeyless.go
Normal file
155
pkg/provider/akeyless/akeyless.go
Normal 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
|
||||
}
|
250
pkg/provider/akeyless/akeyless_api.go
Normal file
250
pkg/provider/akeyless/akeyless_api.go
Normal 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
|
||||
}
|
168
pkg/provider/akeyless/akeyless_test.go
Normal file
168
pkg/provider/akeyless/akeyless_test.go
Normal 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)
|
||||
}
|
103
pkg/provider/akeyless/auth.go
Normal file
103
pkg/provider/akeyless/auth.go
Normal 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)
|
||||
}
|
49
pkg/provider/akeyless/fake/fake.go
Normal file
49
pkg/provider/akeyless/fake/fake.go
Normal 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
|
||||
}
|
99
pkg/provider/akeyless/utils.go
Normal file
99
pkg/provider/akeyless/utils.go
Normal 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)
|
||||
}
|
|
@ -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) {
|
||||
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
|
||||
}
|
||||
name := prov.Auth.JWTAuth.ServiceAccountRef.Name
|
||||
|
|
|
@ -38,16 +38,13 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
myServiceAcc = "my-service-account"
|
||||
otherNs = "other-ns"
|
||||
esNamespaceKey = "es-namespace"
|
||||
platformTeamNsKey = "platform-team-ns"
|
||||
myServiceAccountKey = "my-service-account"
|
||||
otherNsName = "other-ns"
|
||||
)
|
||||
|
||||
func TestNewSession(t *testing.T) {
|
||||
const (
|
||||
esNamespace = "es-namespace"
|
||||
platformTeamNs = "platform-team-ns"
|
||||
)
|
||||
|
||||
rows := []TestSessionRow{
|
||||
{
|
||||
name: "nil store",
|
||||
|
@ -271,7 +268,7 @@ func TestNewSession(t *testing.T) {
|
|||
},
|
||||
{
|
||||
name: "ClusterStore should use credentials from a specific namespace",
|
||||
namespace: esNamespace,
|
||||
namespace: esNamespaceKey,
|
||||
store: &esv1alpha1.ClusterSecretStore{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
APIVersion: esv1alpha1.ClusterSecretStoreKindAPIVersion,
|
||||
|
@ -284,12 +281,12 @@ func TestNewSession(t *testing.T) {
|
|||
SecretRef: &esv1alpha1.AWSAuthSecretRef{
|
||||
AccessKeyID: esmeta.SecretKeySelector{
|
||||
Name: "onesecret",
|
||||
Namespace: aws.String(platformTeamNs),
|
||||
Namespace: aws.String(platformTeamNsKey),
|
||||
Key: "one",
|
||||
},
|
||||
SecretAccessKey: esmeta.SecretKeySelector{
|
||||
Name: "onesecret",
|
||||
Namespace: aws.String(platformTeamNs),
|
||||
Namespace: aws.String(platformTeamNsKey),
|
||||
Key: "two",
|
||||
},
|
||||
},
|
||||
|
@ -302,7 +299,7 @@ func TestNewSession(t *testing.T) {
|
|||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "onesecret",
|
||||
Namespace: platformTeamNs,
|
||||
Namespace: platformTeamNsKey,
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
"one": []byte("1111"),
|
||||
|
@ -316,7 +313,7 @@ func TestNewSession(t *testing.T) {
|
|||
},
|
||||
{
|
||||
name: "namespace is mandatory when using ClusterStore with SecretKeySelector",
|
||||
namespace: esNamespace,
|
||||
namespace: esNamespaceKey,
|
||||
store: &esv1alpha1.ClusterSecretStore{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
APIVersion: esv1alpha1.ClusterSecretStoreKindAPIVersion,
|
||||
|
@ -345,19 +342,19 @@ func TestNewSession(t *testing.T) {
|
|||
},
|
||||
{
|
||||
name: "jwt auth via cluster secret store",
|
||||
namespace: esNamespace,
|
||||
namespace: esNamespaceKey,
|
||||
sa: &v1.ServiceAccount{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: myServiceAcc,
|
||||
Namespace: otherNs,
|
||||
Name: myServiceAccountKey,
|
||||
Namespace: otherNsName,
|
||||
Annotations: map[string]string{
|
||||
roleARNAnnotation: "my-sa-role",
|
||||
},
|
||||
},
|
||||
},
|
||||
jwtProvider: func(name, namespace, roleArn, region string) (credentials.Provider, error) {
|
||||
assert.Equal(t, myServiceAcc, name)
|
||||
assert.Equal(t, otherNs, namespace)
|
||||
assert.Equal(t, myServiceAccountKey, name)
|
||||
assert.Equal(t, otherNsName, namespace)
|
||||
assert.Equal(t, "my-sa-role", roleArn)
|
||||
return fakesess.CredentialsProvider{
|
||||
RetrieveFunc: func() (credentials.Value, error) {
|
||||
|
@ -382,8 +379,8 @@ func TestNewSession(t *testing.T) {
|
|||
Auth: esv1alpha1.AWSAuth{
|
||||
JWTAuth: &esv1alpha1.AWSJWTAuth{
|
||||
ServiceAccountRef: &esmeta.ServiceAccountSelector{
|
||||
Name: myServiceAcc,
|
||||
Namespace: aws.String(otherNs),
|
||||
Name: myServiceAccountKey,
|
||||
Namespace: aws.String(otherNsName),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -434,8 +431,8 @@ func testRow(t *testing.T, row TestSessionRow) {
|
|||
}
|
||||
err := kc.Create(context.Background(), &authv1.TokenRequest{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: myServiceAcc,
|
||||
Namespace: otherNs,
|
||||
Name: myServiceAccountKey,
|
||||
Namespace: otherNsName,
|
||||
},
|
||||
})
|
||||
assert.Nil(t, err)
|
||||
|
|
|
@ -35,6 +35,7 @@ import (
|
|||
|
||||
const (
|
||||
defaultObjType = "secret"
|
||||
vaultResource = "https://vault.azure.net"
|
||||
)
|
||||
|
||||
// Provider satisfies the provider interface.
|
||||
|
@ -74,15 +75,18 @@ func newClient(ctx context.Context, store esv1alpha1.GenericStore, kube client.C
|
|||
store: store,
|
||||
namespace: namespace,
|
||||
}
|
||||
azClient, vaultURL, err := anAzure.newAzureClient(ctx)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
clientSet, err := anAzure.setAzureClientWithManagedIdentity()
|
||||
if clientSet {
|
||||
return anAzure, err
|
||||
}
|
||||
|
||||
anAzure.baseClient = azClient
|
||||
anAzure.vaultURL = vaultURL
|
||||
return anAzure, nil
|
||||
clientSet, err = anAzure.setAzureClientWithServicePrincipal(ctx)
|
||||
if clientSet {
|
||||
return anAzure, err
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("cannot initialize Azure Client: no valid authType was specified")
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
func (a *Azure) newAzureClient(ctx context.Context) (*keyvault.BaseClient, string, error) {
|
||||
func (a *Azure) setAzureClientWithManagedIdentity() (bool, error) {
|
||||
spec := *a.store.GetSpec().Provider.AzureKV
|
||||
tenantID := *spec.TenantID
|
||||
vaultURL := *spec.VaultURL
|
||||
|
||||
if spec.AuthSecretRef == nil {
|
||||
return nil, "", fmt.Errorf("missing clientID/clientSecret in store config")
|
||||
}
|
||||
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
|
||||
if *spec.AuthType != esv1alpha1.ManagedIdentity {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
clientCredentialsConfig := kvauth.NewClientCredentialsConfig(cid, csec, tenantID)
|
||||
// the default resource api is the management URL and not the vault URL which we need for keyvault operations
|
||||
clientCredentialsConfig.Resource = "https://vault.azure.net"
|
||||
authorizer, err := clientCredentialsConfig.Authorizer()
|
||||
msiConfig := kvauth.NewMSIConfig()
|
||||
msiConfig.Resource = vaultResource
|
||||
if spec.IdentityID != nil {
|
||||
msiConfig.ClientID = *spec.IdentityID
|
||||
}
|
||||
authorizer, err := msiConfig.Authorizer()
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
return true, err
|
||||
}
|
||||
|
||||
basicClient := keyvault.New()
|
||||
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) {
|
||||
|
|
|
@ -39,15 +39,46 @@ func newAzure() (Azure, *fake.AzureMock) {
|
|||
return testAzure, azureMock
|
||||
}
|
||||
|
||||
func TestNewClientNoCreds(t *testing.T) {
|
||||
func TestNewClientManagedIdentityNoNeedForCredentials(t *testing.T) {
|
||||
namespace := "internal"
|
||||
vaultURL := "https://local.vault.url"
|
||||
tenantID := "1234"
|
||||
identityID := "1234"
|
||||
authType := esv1alpha1.ManagedIdentity
|
||||
store := esv1alpha1.SecretStore{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Namespace: namespace,
|
||||
},
|
||||
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,
|
||||
TenantID: &tenantID,
|
||||
}}},
|
||||
|
@ -55,32 +86,27 @@ func TestNewClientNoCreds(t *testing.T) {
|
|||
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)
|
||||
_, err = provider.NewClient(context.Background(), &store, k8sClient, namespace)
|
||||
tassert.EqualError(t, err, "missing clientID/clientSecret in store config")
|
||||
tassert.Nil(t, secretClient)
|
||||
|
||||
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.Nil(t, secretClient)
|
||||
|
||||
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.Nil(t, secretClient)
|
||||
|
||||
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.Nil(t, secretClient)
|
||||
store.TypeMeta.Kind = esv1alpha1.ClusterSecretStoreKind
|
||||
store.TypeMeta.APIVersion = esv1alpha1.ClusterSecretStoreKindAPIVersion
|
||||
ns := "default"
|
||||
store.Spec.Provider.AzureKV.AuthSecretRef.ClientID.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.Nil(t, secretClient)
|
||||
}
|
||||
|
||||
const (
|
||||
|
|
|
@ -109,82 +109,102 @@ func (ibm *providerIBM) GetSecret(ctx context.Context, ref esv1alpha1.ExternalSe
|
|||
|
||||
switch secretType {
|
||||
case sm.GetSecretOptionsSecretTypeArbitraryConst:
|
||||
response, _, err := ibm.IBMClient.GetSecret(
|
||||
&sm.GetSecretOptions{
|
||||
SecretType: core.StringPtr(sm.GetSecretOptionsSecretTypeArbitraryConst),
|
||||
ID: &secretName,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
secret := response.Resources[0].(*sm.SecretResource)
|
||||
secretData := secret.SecretData.(map[string]interface{})
|
||||
arbitrarySecretPayload := secretData["payload"].(string)
|
||||
return []byte(arbitrarySecretPayload), nil
|
||||
return getArbitrarySecret(ibm, &secretName)
|
||||
|
||||
case sm.CreateSecretOptionsSecretTypeUsernamePasswordConst:
|
||||
|
||||
if ref.Property == "" {
|
||||
return nil, fmt.Errorf("remoteRef.property required for secret type username_password")
|
||||
}
|
||||
response, _, err := ibm.IBMClient.GetSecret(
|
||||
&sm.GetSecretOptions{
|
||||
SecretType: core.StringPtr(sm.CreateSecretOptionsSecretTypeUsernamePasswordConst),
|
||||
ID: &secretName,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
secret := response.Resources[0].(*sm.SecretResource)
|
||||
secretData := secret.SecretData.(map[string]interface{})
|
||||
|
||||
if val, ok := secretData[ref.Property]; ok {
|
||||
return []byte(val.(string)), nil
|
||||
}
|
||||
return nil, fmt.Errorf("key %s does not exist in secret %s", ref.Property, ref.Key)
|
||||
return getUsernamePasswordSecret(ibm, &secretName, ref)
|
||||
|
||||
case sm.CreateSecretOptionsSecretTypeIamCredentialsConst:
|
||||
response, _, err := ibm.IBMClient.GetSecret(
|
||||
&sm.GetSecretOptions{
|
||||
SecretType: core.StringPtr(sm.CreateSecretOptionsSecretTypeIamCredentialsConst),
|
||||
ID: &secretName,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
secret := response.Resources[0].(*sm.SecretResource)
|
||||
secretData := *secret.APIKey
|
||||
|
||||
return []byte(secretData), nil
|
||||
return getIamCredentialsSecret(ibm, &secretName)
|
||||
|
||||
case sm.CreateSecretOptionsSecretTypeImportedCertConst:
|
||||
|
||||
if ref.Property == "" {
|
||||
return nil, fmt.Errorf("remoteRef.property required for secret type imported_cert")
|
||||
}
|
||||
response, _, err := ibm.IBMClient.GetSecret(
|
||||
&sm.GetSecretOptions{
|
||||
SecretType: core.StringPtr(sm.CreateSecretOptionsSecretTypeImportedCertConst),
|
||||
ID: &secretName,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
secret := response.Resources[0].(*sm.SecretResource)
|
||||
secretData := secret.SecretData.(map[string]interface{})
|
||||
|
||||
if val, ok := secretData[ref.Property]; ok {
|
||||
return []byte(val.(string)), nil
|
||||
}
|
||||
return nil, fmt.Errorf("key %s does not exist in secret %s", ref.Property, ref.Key)
|
||||
|
||||
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(
|
||||
&sm.GetSecretOptions{
|
||||
SecretType: core.StringPtr(sm.GetSecretOptionsSecretTypeArbitraryConst),
|
||||
ID: secretName,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
secret := response.Resources[0].(*sm.SecretResource)
|
||||
secretData := secret.SecretData.(map[string]interface{})
|
||||
arbitrarySecretPayload := secretData["payload"].(string)
|
||||
return []byte(arbitrarySecretPayload), nil
|
||||
}
|
||||
|
||||
func getImportCertSecret(ibm *providerIBM, secretName *string, ref esv1alpha1.ExternalSecretDataRemoteRef) ([]byte, error) {
|
||||
response, _, err := ibm.IBMClient.GetSecret(
|
||||
&sm.GetSecretOptions{
|
||||
SecretType: core.StringPtr(sm.CreateSecretOptionsSecretTypeImportedCertConst),
|
||||
ID: secretName,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
secret := response.Resources[0].(*sm.SecretResource)
|
||||
secretData := secret.SecretData.(map[string]interface{})
|
||||
|
||||
if val, ok := secretData[ref.Property]; ok {
|
||||
return []byte(val.(string)), nil
|
||||
}
|
||||
return nil, fmt.Errorf("key %s does not exist in secret %s", ref.Property, ref.Key)
|
||||
}
|
||||
|
||||
func getIamCredentialsSecret(ibm *providerIBM, secretName *string) ([]byte, error) {
|
||||
response, _, err := ibm.IBMClient.GetSecret(
|
||||
&sm.GetSecretOptions{
|
||||
SecretType: core.StringPtr(sm.CreateSecretOptionsSecretTypeIamCredentialsConst),
|
||||
ID: secretName,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
secret := response.Resources[0].(*sm.SecretResource)
|
||||
secretData := *secret.APIKey
|
||||
|
||||
return []byte(secretData), nil
|
||||
}
|
||||
|
||||
func getUsernamePasswordSecret(ibm *providerIBM, secretName *string, ref esv1alpha1.ExternalSecretDataRemoteRef) ([]byte, error) {
|
||||
response, _, err := ibm.IBMClient.GetSecret(
|
||||
&sm.GetSecretOptions{
|
||||
SecretType: core.StringPtr(sm.CreateSecretOptionsSecretTypeUsernamePasswordConst),
|
||||
ID: secretName,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
secret := response.Resources[0].(*sm.SecretResource)
|
||||
secretData := secret.SecretData.(map[string]interface{})
|
||||
|
||||
if val, ok := secretData[ref.Property]; ok {
|
||||
return []byte(val.(string)), nil
|
||||
}
|
||||
return nil, fmt.Errorf("key %s does not exist in secret %s", ref.Property, ref.Key)
|
||||
}
|
||||
|
||||
func (ibm *providerIBM) GetSecretMap(ctx context.Context, ref esv1alpha1.ExternalSecretDataRemoteRef) (map[string][]byte, error) {
|
||||
if utils.IsNil(ibm.IBMClient) {
|
||||
return nil, fmt.Errorf(errUninitalizedIBMProvider)
|
||||
|
@ -214,16 +234,13 @@ func (ibm *providerIBM) GetSecretMap(ctx context.Context, ref esv1alpha1.Externa
|
|||
secretData := secret.SecretData.(map[string]interface{})
|
||||
arbitrarySecretPayload := secretData["payload"].(string)
|
||||
|
||||
kv := make(map[string]string)
|
||||
kv := make(map[string]interface{})
|
||||
err = json.Unmarshal([]byte(arbitrarySecretPayload), &kv)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(errJSONSecretUnmarshal, err)
|
||||
}
|
||||
|
||||
secretMap := make(map[string][]byte)
|
||||
for k, v := range kv {
|
||||
secretMap[k] = []byte(v)
|
||||
}
|
||||
secretMap := byteArrayMap(kv)
|
||||
|
||||
return secretMap, nil
|
||||
|
||||
|
@ -240,10 +257,7 @@ func (ibm *providerIBM) GetSecretMap(ctx context.Context, ref esv1alpha1.Externa
|
|||
secret := response.Resources[0].(*sm.SecretResource)
|
||||
secretData := secret.SecretData.(map[string]interface{})
|
||||
|
||||
secretMap := make(map[string][]byte)
|
||||
for k, v := range secretData {
|
||||
secretMap[k] = []byte(v.(string))
|
||||
}
|
||||
secretMap := byteArrayMap(secretData)
|
||||
|
||||
return secretMap, nil
|
||||
|
||||
|
@ -278,10 +292,7 @@ func (ibm *providerIBM) GetSecretMap(ctx context.Context, ref esv1alpha1.Externa
|
|||
secret := response.Resources[0].(*sm.SecretResource)
|
||||
secretData := secret.SecretData.(map[string]interface{})
|
||||
|
||||
secretMap := make(map[string][]byte)
|
||||
for k, v := range secretData {
|
||||
secretMap[k] = []byte(v.(string))
|
||||
}
|
||||
secretMap := byteArrayMap(secretData)
|
||||
|
||||
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 {
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -15,8 +15,9 @@ limitations under the License.
|
|||
package register
|
||||
|
||||
// packages imported here are registered to the controller schema.
|
||||
// nolint:golint
|
||||
// nolint:revive
|
||||
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/aws"
|
||||
_ "github.com/external-secrets/external-secrets/pkg/provider/azure/keyvault"
|
||||
|
|
|
@ -224,6 +224,8 @@ func (v *client) readSecret(ctx context.Context, path, version string) (map[stri
|
|||
byteMap[k] = []byte(t)
|
||||
case []byte:
|
||||
byteMap[k] = t
|
||||
case nil:
|
||||
byteMap[k] = []byte(nil)
|
||||
default:
|
||||
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 {
|
||||
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
|
||||
if tokenRef != nil {
|
||||
token, err := v.secretKeyRef(ctx, tokenRef)
|
||||
if err != nil {
|
||||
return err
|
||||
return true, err
|
||||
}
|
||||
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
|
||||
if appRole != nil {
|
||||
token, err := v.requestTokenWithAppRoleRef(ctx, client, appRole)
|
||||
if err != nil {
|
||||
return err
|
||||
return true, err
|
||||
}
|
||||
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
|
||||
if kubernetesAuth != nil {
|
||||
token, err := v.requestTokenWithKubernetesAuth(ctx, client, kubernetesAuth)
|
||||
if err != nil {
|
||||
return err
|
||||
return true, err
|
||||
}
|
||||
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
|
||||
if ldapAuth != nil {
|
||||
token, err := v.requestTokenWithLdapAuth(ctx, client, ldapAuth)
|
||||
if err != nil {
|
||||
return err
|
||||
return true, err
|
||||
}
|
||||
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
|
||||
if jwtAuth != nil {
|
||||
token, err := v.requestTokenWithJwtAuth(ctx, client, jwtAuth)
|
||||
if err != nil {
|
||||
return err
|
||||
return true, err
|
||||
}
|
||||
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
|
||||
if certAuth != nil {
|
||||
token, err := v.requestTokenWithCertAuth(ctx, client, certAuth, cfg)
|
||||
if err != nil {
|
||||
return err
|
||||
return true, err
|
||||
}
|
||||
client.SetToken(token)
|
||||
return nil
|
||||
return true, nil
|
||||
}
|
||||
|
||||
return errors.New(errAuthFormat)
|
||||
return false, nil
|
||||
}
|
||||
|
||||
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) {
|
||||
jwtString := ""
|
||||
if kubernetesAuth.ServiceAccountRef != nil {
|
||||
jwt, err := v.secretKeyRefForServiceAccount(ctx, kubernetesAuth.ServiceAccountRef)
|
||||
if err != nil {
|
||||
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)
|
||||
jwtString, err := getJwtString(ctx, v, kubernetesAuth)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
parameters := kubeParameters(kubernetesAuth.Role, jwtString)
|
||||
url := strings.Join([]string{"/v1", "auth", kubernetesAuth.Path, "login"}, "/")
|
||||
request := client.NewRequest("POST", url)
|
||||
|
||||
err := request.SetJSONBody(parameters)
|
||||
err = request.SetJSONBody(parameters)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf(errVaultReqParams, err)
|
||||
}
|
||||
|
@ -554,6 +577,39 @@ func (v *client) requestTokenWithKubernetesAuth(ctx context.Context, client Clie
|
|||
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) {
|
||||
username := strings.TrimSpace(ldapAuth.Username)
|
||||
|
||||
|
|
|
@ -41,7 +41,7 @@ const (
|
|||
secretDataString = "some-creds"
|
||||
)
|
||||
|
||||
func makeValidSecretStore() *esv1alpha1.SecretStore {
|
||||
func makeValidSecretStoreWithVersion(v esv1alpha1.VaultKVStoreVersion) *esv1alpha1.SecretStore {
|
||||
return &esv1alpha1.SecretStore{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "vault-store",
|
||||
|
@ -52,7 +52,7 @@ func makeValidSecretStore() *esv1alpha1.SecretStore {
|
|||
Vault: &esv1alpha1.VaultProvider{
|
||||
Server: "vault.example.com",
|
||||
Path: "secret",
|
||||
Version: esv1alpha1.VaultKVStoreV2,
|
||||
Version: v,
|
||||
Auth: esv1alpha1.VaultAuth{
|
||||
Kubernetes: &esv1alpha1.VaultKubernetesAuth{
|
||||
Path: "kubernetes",
|
||||
|
@ -68,6 +68,10 @@ func makeValidSecretStore() *esv1alpha1.SecretStore {
|
|||
}
|
||||
}
|
||||
|
||||
func makeValidSecretStore() *esv1alpha1.SecretStore {
|
||||
return makeValidSecretStoreWithVersion(esv1alpha1.VaultKVStoreV2)
|
||||
}
|
||||
|
||||
func makeValidSecretStoreWithCerts() *esv1alpha1.SecretStore {
|
||||
return &esv1alpha1.SecretStore{
|
||||
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{
|
||||
Data: map[string]interface{}{
|
||||
"id": token,
|
||||
},
|
||||
Data: data,
|
||||
})
|
||||
}
|
||||
|
||||
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) {
|
||||
return &fake.VaultClient{
|
||||
MockNewRequest: fake.NewMockNewRequestFn(&vault.Request{}),
|
||||
|
@ -182,22 +207,7 @@ MIICsTCCAZkCFEJJ4daz5sxkFlzq9n1djLEuG7bmMA0GCSqGSIb3DQEBCwUAMBMxETAPBgNVBAMMCHZh
|
|||
`)
|
||||
secretData := []byte(secretDataString)
|
||||
|
||||
type args struct {
|
||||
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
|
||||
}{
|
||||
cases := map[string]testCase{
|
||||
"InvalidVaultStore": {
|
||||
reason: "Should return error if given an invalid vault store.",
|
||||
args: args{
|
||||
|
@ -472,22 +482,35 @@ MIICsTCCAZkCFEJJ4daz5sxkFlzq9n1djLEuG7bmMA0GCSqGSIb3DQEBCwUAMBMxETAPBgNVBAMMCHZh
|
|||
|
||||
for name, tc := range cases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
conn := &connector{
|
||||
newVaultClient: tc.args.newClientFunc,
|
||||
}
|
||||
if tc.args.newClientFunc == nil {
|
||||
conn.newVaultClient = newVaultClient
|
||||
}
|
||||
_, err := conn.NewClient(context.Background(), tc.args.store, tc.args.kube, tc.args.ns)
|
||||
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)
|
||||
}
|
||||
vaultTest(t, name, tc)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func vaultTest(t *testing.T, name string, tc testCase) {
|
||||
conn := &connector{
|
||||
newVaultClient: tc.args.newClientFunc,
|
||||
}
|
||||
if tc.args.newClientFunc == nil {
|
||||
conn.newVaultClient = newVaultClient
|
||||
}
|
||||
_, err := conn.NewClient(context.Background(), tc.args.store, tc.args.kube, tc.args.ns)
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetSecretMap(t *testing.T) {
|
||||
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 {
|
||||
store *esv1alpha1.VaultProvider
|
||||
|
@ -506,6 +529,74 @@ func TestGetSecretMap(t *testing.T) {
|
|||
args args
|
||||
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": {
|
||||
reason: "Should return error if vault client fails to read secret.",
|
||||
args: args{
|
||||
|
|
|
@ -35,6 +35,12 @@ import (
|
|||
"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) {
|
||||
ctx := context.Background()
|
||||
const namespace = "namespace"
|
||||
|
@ -54,17 +60,17 @@ func TestNewClient(t *testing.T) {
|
|||
|
||||
k8sClient := clientfake.NewClientBuilder().Build()
|
||||
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)
|
||||
|
||||
store.Spec.Provider.YandexLockbox.Auth = esv1alpha1.YandexLockboxAuth{}
|
||||
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)
|
||||
|
||||
store.Spec.Provider.YandexLockbox.Auth.AuthorizedKey = esmeta.SecretKeySelector{}
|
||||
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)
|
||||
|
||||
const authorizedKeySecretName = "authorizedKeySecretName"
|
||||
|
@ -248,7 +254,7 @@ func TestGetSecretUnauthorized(t *testing.T) {
|
|||
secretsClient, err := provider.NewClient(ctx, store, k8sClient, namespace)
|
||||
tassert.Nil(t, err)
|
||||
_, 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) {
|
||||
|
@ -271,7 +277,7 @@ func TestGetSecretNotFound(t *testing.T) {
|
|||
secretsClient, err := provider.NewClient(ctx, store, k8sClient, namespace)
|
||||
tassert.Nil(t, err)
|
||||
_, 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,
|
||||
textEntry("k1", "v1"),
|
||||
|
@ -320,11 +326,11 @@ func TestGetSecretWithTwoNamespaces(t *testing.T) {
|
|||
tassert.Nil(t, err)
|
||||
data, err = secretsClient1.GetSecret(ctx, esv1alpha1.ExternalSecretDataRemoteRef{Key: secretID2, Property: k2})
|
||||
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})
|
||||
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})
|
||||
tassert.Equal(t, v2, string(data))
|
||||
tassert.Nil(t, err)
|
||||
|
@ -381,11 +387,11 @@ func TestGetSecretWithTwoApiEndpoints(t *testing.T) {
|
|||
tassert.Nil(t, err)
|
||||
data, err = secretsClient1.GetSecret(ctx, esv1alpha1.ExternalSecretDataRemoteRef{Key: secretID2, Property: k2})
|
||||
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})
|
||||
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})
|
||||
tassert.Equal(t, v2, string(data))
|
||||
tassert.Nil(t, err)
|
||||
|
|
|
@ -39,7 +39,14 @@ func MergeStringMap(dest, src map[string]string) {
|
|||
|
||||
// IsNil checks if an Interface is nil.
|
||||
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.
|
||||
|
|
|
@ -17,6 +17,7 @@ package utils
|
|||
import (
|
||||
"testing"
|
||||
|
||||
vault "github.com/oracle/oci-go-sdk/v45/vault"
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
1
tools.go
1
tools.go
|
@ -4,5 +4,6 @@
|
|||
package tools
|
||||
|
||||
import (
|
||||
_ "github.com/onsi/ginkgo/ginkgo"
|
||||
_ "sigs.k8s.io/controller-tools/cmd/controller-gen"
|
||||
)
|
||||
|
|
Loading…
Reference in a new issue