Explorar el Código

Merge branch 'main' into ibm-enable-retries

Daniel Hix hace 4 años
padre
commit
082cee230f
Se han modificado 59 ficheros con 2463 adiciones y 405 borrados
  1. 5 0
      .github/PAUL.yaml
  2. 15 11
      .github/workflows/ci.yml
  3. 9 9
      .github/workflows/e2e.yml
  4. 1 8
      .golangci.yaml
  5. 1 1
      Dockerfile
  6. 20 27
      Makefile
  7. 7 5
      README.md
  8. 42 0
      apis/externalsecrets/v1alpha1/secretstore_akeyless_types.go
  9. 31 4
      apis/externalsecrets/v1alpha1/secretstore_azurekv_types.go
  10. 4 0
      apis/externalsecrets/v1alpha1/secretstore_types.go
  11. 74 0
      apis/externalsecrets/v1alpha1/zz_generated.deepcopy.go
  12. 2 2
      deploy/charts/external-secrets/Chart.yaml
  13. 1 1
      deploy/charts/external-secrets/README.md
  14. 105 4
      deploy/crds/external-secrets.io_clustersecretstores.yaml
  15. 105 4
      deploy/crds/external-secrets.io_secretstores.yaml
  16. 68 0
      docs/provider-akeyless.md
  17. 16 2
      docs/provider-azure-key-vault.md
  18. 12 0
      docs/snippets/akeyless-credentials-secret.yaml
  19. 18 0
      docs/snippets/akeyless-external-secret-json.yaml
  20. 19 0
      docs/snippets/akeyless-external-secret.yaml
  21. 20 0
      docs/snippets/akeyless-secret-store.yaml
  22. 13 0
      docs/snippets/azkv-secret-store-mi.yaml
  23. 1 1
      docs/snippets/template-from-secret.yaml
  24. 3 3
      e2e/Dockerfile
  25. 4 3
      e2e/Makefile
  26. 5 5
      e2e/entrypoint.sh
  27. 2 1
      e2e/framework/addon/vault.go
  28. 4 3
      e2e/run.sh
  29. 49 0
      e2e/suite/akeyless/akeyless.go
  30. 227 0
      e2e/suite/akeyless/provider.go
  31. 5 1
      e2e/suite/alibaba/alibaba.go
  32. 5 1
      e2e/suite/azure/azure.go
  33. 19 11
      e2e/suite/common/common.go
  34. 5 1
      e2e/suite/gcp/gcp.go
  35. 6 1
      e2e/suite/gitlab/gitlab.go
  36. 39 30
      e2e/suite/vault/vault.go
  37. 13 9
      go.mod
  38. 94 25
      go.sum
  39. 1 0
      hack/api-docs/mkdocs.yml
  40. 6 1
      pkg/controllers/externalsecret/externalsecret_controller.go
  41. 25 23
      pkg/controllers/externalsecret/externalsecret_controller_test.go
  42. 155 0
      pkg/provider/akeyless/akeyless.go
  43. 250 0
      pkg/provider/akeyless/akeyless_api.go
  44. 168 0
      pkg/provider/akeyless/akeyless_test.go
  45. 103 0
      pkg/provider/akeyless/auth.go
  46. 49 0
      pkg/provider/akeyless/fake/fake.go
  47. 99 0
      pkg/provider/akeyless/utils.go
  48. 3 0
      pkg/provider/aws/auth/auth.go
  49. 18 21
      pkg/provider/aws/auth/auth_test.go
  50. 57 20
      pkg/provider/azure/keyvault/keyvault.go
  51. 36 10
      pkg/provider/azure/keyvault/keyvault_test.go
  52. 88 69
      pkg/provider/ibm/provider.go
  53. 2 1
      pkg/provider/register/register.go
  54. 101 45
      pkg/provider/vault/vault.go
  55. 123 32
      pkg/provider/vault/vault_test.go
  56. 15 9
      pkg/provider/yandex/lockbox/lockbox_test.go
  57. 8 1
      pkg/utils/utils.go
  58. 86 0
      pkg/utils/utils_test.go
  59. 1 0
      tools.go

+ 5 - 0
.github/PAUL.yaml

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

+ 15 - 11
.github/workflows/ci.yml

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

+ 9 - 9
.github/workflows/e2e.yml

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

+ 1 - 8
.golangci.yaml

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

+ 1 - 1
Dockerfile

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

+ 20 - 27
Makefile

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

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

+ 42 - 0
apis/externalsecrets/v1alpha1/secretstore_akeyless_types.go

@@ -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"`
+}

+ 31 - 4
apis/externalsecrets/v1alpha1/secretstore_azurekv_types.go

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

+ 4 - 0
apis/externalsecrets/v1alpha1/secretstore_types.go

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

+ 74 - 0
apis/externalsecrets/v1alpha1/zz_generated.deepcopy.go

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

+ 2 - 2
deploy/charts/external-secrets/Chart.yaml

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

+ 1 - 1
deploy/charts/external-secrets/README.md

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

+ 105 - 4
deploy/crds/external-secrets.io_clustersecretstores.yaml

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

+ 105 - 4
deploy/crds/external-secrets.io_secretstores.yaml

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

+ 68 - 0
docs/provider-akeyless.md

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

+ 16 - 2
docs/provider-azure-key-vault.md

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

+ 12 - 0
docs/snippets/akeyless-credentials-secret.yaml

@@ -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 - 0
docs/snippets/akeyless-external-secret-json.yaml

@@ -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 - 0
docs/snippets/akeyless-external-secret.yaml

@@ -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 - 0
docs/snippets/akeyless-secret-store.yaml

@@ -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 - 0
docs/snippets/azkv-secret-store-mi.yaml

@@ -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 - 1
docs/snippets/template-from-secret.yaml

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

+ 3 - 3
e2e/Dockerfile

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

+ 4 - 3
e2e/Makefile

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

+ 5 - 5
e2e/entrypoint.sh

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

+ 2 - 1
e2e/framework/addon/vault.go

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

+ 4 - 3
e2e/run.sh

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

+ 49 - 0
e2e/suite/akeyless/akeyless.go

@@ -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 - 0
e2e/suite/akeyless/provider.go

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

+ 5 - 1
e2e/suite/alibaba/alibaba.go

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

+ 5 - 1
e2e/suite/azure/azure.go

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

+ 19 - 11
e2e/suite/common/common.go

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

+ 5 - 1
e2e/suite/gcp/gcp.go

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

+ 6 - 1
e2e/suite/gitlab/gitlab.go

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

+ 39 - 30
e2e/suite/vault/vault.go

@@ -23,6 +23,15 @@ import (
 	"github.com/external-secrets/external-secrets/e2e/suite/common"
 	"github.com/external-secrets/external-secrets/e2e/suite/common"
 )
 )
 
 
+const (
+	withTokenAuth = "with token auth"
+	withCertAuth  = "with cert auth"
+	withApprole   = "with approle auth"
+	withV1        = "with v1 provider"
+	withJWT       = "with jwt provider"
+	withK8s       = "with kubernetes provider"
+)
+
 var _ = Describe("[vault] ", func() {
 var _ = Describe("[vault] ", func() {
 	f := framework.New("eso-vault")
 	f := framework.New("eso-vault")
 
 
@@ -30,41 +39,41 @@ var _ = Describe("[vault] ", func() {
 		framework.TableFunc(f,
 		framework.TableFunc(f,
 			newVaultProvider(f)),
 			newVaultProvider(f)),
 		// uses token auth
 		// uses token auth
-		compose("with token auth", f, common.JSONDataFromSync, useTokenAuth),
-		compose("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
 		// 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
 		// 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
 		// 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
 		// 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
 		// 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),
 	)
 	)
 })
 })
 
 

+ 13 - 9
go.mod

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

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

+ 1 - 0
hack/api-docs/mkdocs.yml

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

+ 6 - 1
pkg/controllers/externalsecret/externalsecret_controller.go

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

+ 25 - 23
pkg/controllers/externalsecret/externalsecret_controller_test.go

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

+ 155 - 0
pkg/provider/akeyless/akeyless.go

@@ -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 - 0
pkg/provider/akeyless/akeyless_api.go

@@ -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 - 0
pkg/provider/akeyless/akeyless_test.go

@@ -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 - 0
pkg/provider/akeyless/auth.go

@@ -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 - 0
pkg/provider/akeyless/fake/fake.go

@@ -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 - 0
pkg/provider/akeyless/utils.go

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

+ 3 - 0
pkg/provider/aws/auth/auth.go

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

+ 18 - 21
pkg/provider/aws/auth/auth_test.go

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

+ 57 - 20
pkg/provider/azure/keyvault/keyvault.go

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

+ 36 - 10
pkg/provider/azure/keyvault/keyvault_test.go

@@ -39,15 +39,46 @@ func newAzure() (Azure, *fake.AzureMock) {
 	return testAzure, azureMock
 	return testAzure, azureMock
 }
 }
 
 
+func TestNewClientManagedIdentityNoNeedForCredentials(t *testing.T) {
+	namespace := "internal"
+	vaultURL := "https://local.vault.url"
+	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) {
 func TestNewClientNoCreds(t *testing.T) {
 	namespace := "internal"
 	namespace := "internal"
 	vaultURL := "https://local.vault.url"
 	vaultURL := "https://local.vault.url"
 	tenantID := "1234"
 	tenantID := "1234"
+	authType := esv1alpha1.ServicePrincipal
 	store := esv1alpha1.SecretStore{
 	store := esv1alpha1.SecretStore{
 		ObjectMeta: metav1.ObjectMeta{
 		ObjectMeta: metav1.ObjectMeta{
 			Namespace: namespace,
 			Namespace: namespace,
 		},
 		},
 		Spec: esv1alpha1.SecretStoreSpec{Provider: &esv1alpha1.SecretStoreProvider{AzureKV: &esv1alpha1.AzureKVProvider{
 		Spec: esv1alpha1.SecretStoreSpec{Provider: &esv1alpha1.SecretStoreProvider{AzureKV: &esv1alpha1.AzureKVProvider{
+			AuthType: &authType,
 			VaultURL: &vaultURL,
 			VaultURL: &vaultURL,
 			TenantID: &tenantID,
 			TenantID: &tenantID,
 		}}},
 		}}},
@@ -55,32 +86,27 @@ func TestNewClientNoCreds(t *testing.T) {
 	provider, err := schema.GetProvider(&store)
 	provider, err := schema.GetProvider(&store)
 	tassert.Nil(t, err, "the return err should be nil")
 	tassert.Nil(t, err, "the return err should be nil")
 	k8sClient := clientfake.NewClientBuilder().Build()
 	k8sClient := clientfake.NewClientBuilder().Build()
-	secretClient, err := provider.NewClient(context.Background(), &store, k8sClient, namespace)
+	_, err = provider.NewClient(context.Background(), &store, k8sClient, namespace)
 	tassert.EqualError(t, err, "missing clientID/clientSecret in store config")
 	tassert.EqualError(t, err, "missing clientID/clientSecret in store config")
-	tassert.Nil(t, secretClient)
 
 
 	store.Spec.Provider.AzureKV.AuthSecretRef = &esv1alpha1.AzureKVAuth{}
 	store.Spec.Provider.AzureKV.AuthSecretRef = &esv1alpha1.AzureKVAuth{}
-	secretClient, err = provider.NewClient(context.Background(), &store, k8sClient, namespace)
+	_, err = provider.NewClient(context.Background(), &store, k8sClient, namespace)
 	tassert.EqualError(t, err, "missing accessKeyID/secretAccessKey in store config")
 	tassert.EqualError(t, err, "missing accessKeyID/secretAccessKey in store config")
-	tassert.Nil(t, secretClient)
 
 
 	store.Spec.Provider.AzureKV.AuthSecretRef.ClientID = &v1.SecretKeySelector{Name: "user"}
 	store.Spec.Provider.AzureKV.AuthSecretRef.ClientID = &v1.SecretKeySelector{Name: "user"}
-	secretClient, err = provider.NewClient(context.Background(), &store, k8sClient, namespace)
+	_, err = provider.NewClient(context.Background(), &store, k8sClient, namespace)
 	tassert.EqualError(t, err, "missing accessKeyID/secretAccessKey in store config")
 	tassert.EqualError(t, err, "missing accessKeyID/secretAccessKey in store config")
-	tassert.Nil(t, secretClient)
 
 
 	store.Spec.Provider.AzureKV.AuthSecretRef.ClientSecret = &v1.SecretKeySelector{Name: "password"}
 	store.Spec.Provider.AzureKV.AuthSecretRef.ClientSecret = &v1.SecretKeySelector{Name: "password"}
-	secretClient, err = provider.NewClient(context.Background(), &store, k8sClient, namespace)
+	_, err = provider.NewClient(context.Background(), &store, k8sClient, namespace)
 	tassert.EqualError(t, err, "could not find secret internal/user: secrets \"user\" not found")
 	tassert.EqualError(t, err, "could not find secret internal/user: secrets \"user\" not found")
-	tassert.Nil(t, secretClient)
 	store.TypeMeta.Kind = esv1alpha1.ClusterSecretStoreKind
 	store.TypeMeta.Kind = esv1alpha1.ClusterSecretStoreKind
 	store.TypeMeta.APIVersion = esv1alpha1.ClusterSecretStoreKindAPIVersion
 	store.TypeMeta.APIVersion = esv1alpha1.ClusterSecretStoreKindAPIVersion
 	ns := "default"
 	ns := "default"
 	store.Spec.Provider.AzureKV.AuthSecretRef.ClientID.Namespace = &ns
 	store.Spec.Provider.AzureKV.AuthSecretRef.ClientID.Namespace = &ns
 	store.Spec.Provider.AzureKV.AuthSecretRef.ClientSecret.Namespace = &ns
 	store.Spec.Provider.AzureKV.AuthSecretRef.ClientSecret.Namespace = &ns
-	secretClient, err = provider.NewClient(context.Background(), &store, k8sClient, namespace)
+	_, err = provider.NewClient(context.Background(), &store, k8sClient, namespace)
 	tassert.EqualError(t, err, "could not find secret default/user: secrets \"user\" not found")
 	tassert.EqualError(t, err, "could not find secret default/user: secrets \"user\" not found")
-	tassert.Nil(t, secretClient)
 }
 }
 
 
 const (
 const (

+ 88 - 69
pkg/provider/ibm/provider.go

@@ -109,82 +109,102 @@ func (ibm *providerIBM) GetSecret(ctx context.Context, ref esv1alpha1.ExternalSe
 
 
 	switch secretType {
 	switch secretType {
 	case sm.GetSecretOptionsSecretTypeArbitraryConst:
 	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:
 	case sm.CreateSecretOptionsSecretTypeUsernamePasswordConst:
+
 		if ref.Property == "" {
 		if ref.Property == "" {
 			return nil, fmt.Errorf("remoteRef.property required for secret type username_password")
 			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:
 	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:
 	case sm.CreateSecretOptionsSecretTypeImportedCertConst:
+
 		if ref.Property == "" {
 		if ref.Property == "" {
 			return nil, fmt.Errorf("remoteRef.property required for secret type imported_cert")
 			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:
 	default:
 		return nil, fmt.Errorf("unknown secret type %s", secretType)
 		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) {
 func (ibm *providerIBM) GetSecretMap(ctx context.Context, ref esv1alpha1.ExternalSecretDataRemoteRef) (map[string][]byte, error) {
 	if utils.IsNil(ibm.IBMClient) {
 	if utils.IsNil(ibm.IBMClient) {
 		return nil, fmt.Errorf(errUninitalizedIBMProvider)
 		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{})
 		secretData := secret.SecretData.(map[string]interface{})
 		arbitrarySecretPayload := secretData["payload"].(string)
 		arbitrarySecretPayload := secretData["payload"].(string)
 
 
-		kv := make(map[string]string)
+		kv := make(map[string]interface{})
 		err = json.Unmarshal([]byte(arbitrarySecretPayload), &kv)
 		err = json.Unmarshal([]byte(arbitrarySecretPayload), &kv)
 		if err != nil {
 		if err != nil {
 			return nil, fmt.Errorf(errJSONSecretUnmarshal, err)
 			return nil, fmt.Errorf(errJSONSecretUnmarshal, err)
 		}
 		}
 
 
-		secretMap := make(map[string][]byte)
-		for k, v := range kv {
-			secretMap[k] = []byte(v)
-		}
+		secretMap := byteArrayMap(kv)
 
 
 		return secretMap, nil
 		return secretMap, nil
 
 
@@ -240,10 +257,7 @@ func (ibm *providerIBM) GetSecretMap(ctx context.Context, ref esv1alpha1.Externa
 		secret := response.Resources[0].(*sm.SecretResource)
 		secret := response.Resources[0].(*sm.SecretResource)
 		secretData := secret.SecretData.(map[string]interface{})
 		secretData := secret.SecretData.(map[string]interface{})
 
 
-		secretMap := make(map[string][]byte)
-		for k, v := range secretData {
-			secretMap[k] = []byte(v.(string))
-		}
+		secretMap := byteArrayMap(secretData)
 
 
 		return secretMap, nil
 		return secretMap, nil
 
 
@@ -278,10 +292,7 @@ func (ibm *providerIBM) GetSecretMap(ctx context.Context, ref esv1alpha1.Externa
 		secret := response.Resources[0].(*sm.SecretResource)
 		secret := response.Resources[0].(*sm.SecretResource)
 		secretData := secret.SecretData.(map[string]interface{})
 		secretData := secret.SecretData.(map[string]interface{})
 
 
-		secretMap := make(map[string][]byte)
-		for k, v := range secretData {
-			secretMap[k] = []byte(v.(string))
-		}
+		secretMap := byteArrayMap(secretData)
 
 
 		return secretMap, nil
 		return secretMap, nil
 
 
@@ -290,6 +301,14 @@ func (ibm *providerIBM) GetSecretMap(ctx context.Context, ref esv1alpha1.Externa
 	}
 	}
 }
 }
 
 
+func byteArrayMap(secretData map[string]interface{}) map[string][]byte {
+	secretMap := make(map[string][]byte)
+	for k, v := range secretData {
+		secretMap[k] = []byte(v.(string))
+	}
+	return secretMap
+}
+
 func (ibm *providerIBM) Close(ctx context.Context) error {
 func (ibm *providerIBM) Close(ctx context.Context) error {
 	return nil
 	return nil
 }
 }

+ 2 - 1
pkg/provider/register/register.go

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

+ 101 - 45
pkg/provider/vault/vault.go

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

+ 123 - 32
pkg/provider/vault/vault_test.go

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

+ 15 - 9
pkg/provider/yandex/lockbox/lockbox_test.go

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

+ 8 - 1
pkg/utils/utils.go

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

+ 86 - 0
pkg/utils/utils_test.go

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

+ 1 - 0
tools.go

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