ソースを参照

feat: add v2 provider runtime plumbing

Signed-off-by: Moritz Johner <beller.moritz@googlemail.com>
Signed-off-by: Gergely Brautigam <182850+Skarlso@users.noreply.github.com>
Moritz Johner 2 ヶ月 前
コミット
862bc2d0b3
100 ファイル変更6988 行追加275 行削除
  1. 4 0
      .dockerignore
  2. 30 0
      .fossa.yml
  3. 17 1
      .github/actions/e2e/action.yml
  4. 4 0
      .github/workflows/dependency-review.yml
  5. 54 8
      .github/workflows/dlc.yml
  6. 37 3
      .github/workflows/e2e.yml
  7. 2 0
      .github/workflows/publish.yml
  8. 16 4
      .sonarcloud.properties
  9. 332 0
      E2E_V2_PLAN.md
  10. 154 20
      Makefile
  11. 134 0
      PR_WALKTHROUGH.md
  12. 28 0
      apis/externalsecrets/v1/authscope_types.go
  13. 1 1
      apis/externalsecrets/v1/externalsecret_types.go
  14. 2 2
      apis/externalsecrets/v1/provider.go
  15. 6 6
      apis/externalsecrets/v1/provider_schema.go
  16. 1 0
      apis/externalsecrets/v1/secretstore_aws_types.go
  17. 2 0
      apis/externalsecrets/v1/secretstore_github_types.go
  18. 59 0
      apis/externalsecrets/v1/secretstore_runtime_ref_test.go
  19. 60 1
      apis/externalsecrets/v1/secretstore_types.go
  20. 60 0
      apis/externalsecrets/v1/secretstore_validator.go
  21. 191 3
      apis/externalsecrets/v1/secretstore_validator_test.go
  22. 1 0
      apis/externalsecrets/v1/secretstore_vault_types.go
  23. 22 0
      apis/externalsecrets/v1/secretstore_webhook.go
  24. 82 0
      apis/externalsecrets/v1/secretstore_webhook_test.go
  25. 40 0
      apis/externalsecrets/v1/zz_generated.deepcopy.go
  26. 53 0
      apis/externalsecrets/v1alpha1/clusterproviderclass_types.go
  27. 53 0
      apis/externalsecrets/v1alpha1/providerclass_types.go
  28. 71 0
      apis/externalsecrets/v1alpha1/providerclass_types_test.go
  29. 6 2
      apis/externalsecrets/v1alpha1/pushsecret_types.go
  30. 2 0
      apis/externalsecrets/v1alpha1/register.go
  31. 192 0
      apis/externalsecrets/v1alpha1/zz_generated.deepcopy.go
  32. 2 2
      apis/externalsecrets/v1beta1/externalsecret_types.go
  33. 2 2
      apis/externalsecrets/v1beta1/provider.go
  34. 6 6
      apis/externalsecrets/v1beta1/provider_schema.go
  35. 1 0
      apis/externalsecrets/v1beta1/secretstore_aws_types.go
  36. 2 0
      apis/externalsecrets/v1beta1/secretstore_github_types.go
  37. 59 0
      apis/externalsecrets/v1beta1/secretstore_runtime_ref_test.go
  38. 59 1
      apis/externalsecrets/v1beta1/secretstore_types.go
  39. 60 0
      apis/externalsecrets/v1beta1/secretstore_validator.go
  40. 170 3
      apis/externalsecrets/v1beta1/secretstore_validator_test.go
  41. 1 0
      apis/externalsecrets/v1beta1/secretstore_vault_types.go
  42. 22 0
      apis/externalsecrets/v1beta1/secretstore_webhook.go
  43. 82 0
      apis/externalsecrets/v1beta1/secretstore_webhook_test.go
  44. 40 0
      apis/externalsecrets/v1beta1/zz_generated.deepcopy.go
  45. 5 3
      apis/generators/v1alpha1/types_grafana.go
  46. 20 0
      apis/provider/aws/v2alpha1/doc.go
  47. 39 0
      apis/provider/aws/v2alpha1/groupversion_info.go
  48. 93 0
      apis/provider/aws/v2alpha1/parameterstore_types.go
  49. 97 0
      apis/provider/aws/v2alpha1/secretsmanager_types.go
  50. 268 0
      apis/provider/aws/v2alpha1/zz_generated.deepcopy.go
  51. 21 0
      apis/provider/fake/v2alpha1/doc.go
  52. 36 0
      apis/provider/fake/v2alpha1/groupversion_info.go
  53. 75 0
      apis/provider/fake/v2alpha1/types.go
  54. 123 0
      apis/provider/fake/v2alpha1/zz_generated.deepcopy.go
  55. 21 0
      apis/provider/kubernetes/v2alpha1/doc.go
  56. 36 0
      apis/provider/kubernetes/v2alpha1/groupversion_info.go
  57. 51 0
      apis/provider/kubernetes/v2alpha1/types.go
  58. 83 0
      apis/provider/kubernetes/v2alpha1/zz_generated.deepcopy.go
  59. BIN
      assets/eso-out-of-tree.png
  60. 37 0
      cmd/controller/certcontroller.go
  61. 52 1
      cmd/controller/root.go
  62. 30 0
      cmd/controller/root_test.go
  63. 6 6
      config/crds/bases/external-secrets.io_clusterexternalsecrets.yaml
  64. 122 0
      config/crds/bases/external-secrets.io_clusterproviderclasses.yaml
  65. 14 6
      config/crds/bases/external-secrets.io_clusterpushsecrets.yaml
  66. 122 13
      config/crds/bases/external-secrets.io_clustersecretstores.yaml
  67. 6 6
      config/crds/bases/external-secrets.io_externalsecrets.yaml
  68. 121 0
      config/crds/bases/external-secrets.io_providerclasses.yaml
  69. 13 5
      config/crds/bases/external-secrets.io_pushsecrets.yaml
  70. 122 13
      config/crds/bases/external-secrets.io_secretstores.yaml
  71. 0 2
      config/crds/bases/generators.external-secrets.io_clustergenerators.yaml
  72. 0 2
      config/crds/bases/generators.external-secrets.io_vaultdynamicsecrets.yaml
  73. 6 0
      config/crds/bases/kustomization.yaml
  74. 75 0
      config/crds/bases/provider.external-secrets.io_fakes.yaml
  75. 265 0
      config/crds/bases/provider.external-secrets.io_kubernetes.yaml
  76. 294 0
      config/crds/bases/provider.external-secrets.io_parameterstores.yaml
  77. 315 0
      config/crds/bases/provider.external-secrets.io_secretsmanagers.yaml
  78. 207 0
      deploy/charts/README.md
  79. 472 0
      deploy/charts/external-secrets/PROVIDERS.md
  80. 6 0
      deploy/charts/external-secrets/README.md
  81. 62 0
      deploy/charts/external-secrets/templates/_helpers.tpl
  82. 8 0
      deploy/charts/external-secrets/templates/cert-controller-deployment.yaml
  83. 1 0
      deploy/charts/external-secrets/templates/cert-controller-rbac.yaml
  84. 0 4
      deploy/charts/external-secrets/templates/crds/README.md
  85. 18 0
      deploy/charts/external-secrets/templates/provider-class.yaml
  86. 155 0
      deploy/charts/external-secrets/templates/provider-deployment.yaml
  87. 42 0
      deploy/charts/external-secrets/templates/provider-hpa.yaml
  88. 28 0
      deploy/charts/external-secrets/templates/provider-poddisruptionbudget.yaml
  89. 103 0
      deploy/charts/external-secrets/templates/provider-rbac.yaml
  90. 35 0
      deploy/charts/external-secrets/templates/provider-service.yaml
  91. 25 0
      deploy/charts/external-secrets/templates/provider-serviceaccount.yaml
  92. 30 0
      deploy/charts/external-secrets/templates/provider-servicemonitor.yaml
  93. 30 0
      deploy/charts/external-secrets/templates/rbac.yaml
  94. 586 145
      deploy/charts/external-secrets/tests/__snapshot__/crds_test.yaml.snap
  95. 1 1
      deploy/charts/external-secrets/tests/crds_test.yaml
  96. 21 0
      deploy/charts/external-secrets/tests/provider_class_test.yaml
  97. 40 0
      deploy/charts/external-secrets/tests/provider_rbac_test.yaml
  98. 3 3
      deploy/charts/external-secrets/tests/webhook_test.yaml
  99. 89 0
      deploy/charts/external-secrets/values-test.yaml
  100. 236 0
      deploy/charts/external-secrets/values-with-providers-example.yaml

+ 4 - 0
.dockerignore

@@ -1,6 +1,10 @@
 .git
 .github
 .gitignore
+.cache
+**/.cache
+.tmp
+**/.tmp
 .golangci.yaml
 ADOPTERS.md
 CNAME

+ 30 - 0
.fossa.yml

@@ -0,0 +1,30 @@
+version: 3
+
+paths:
+  exclude:
+    - providers/v2
+
+targets:
+  exclude:
+    # v2 provider runtime modules are provider-owned deliverables.
+    # Keep the core repo FOSSA scan scoped to the existing monorepo targets.
+    - type: gomod
+      path: providers/v2/adapter
+    - type: gomod
+      path: providers/v2/adapter/generator
+    - type: gomod
+      path: providers/v2/adapter/store
+    - type: gomod
+      path: providers/v2/aws
+    - type: gomod
+      path: providers/v2/common
+    - type: gomod
+      path: providers/v2/common/grpc/server
+    - type: gomod
+      path: providers/v2/common/proto
+    - type: gomod
+      path: providers/v2/fake
+    - type: gomod
+      path: providers/v2/hack
+    - type: gomod
+      path: providers/v2/kubernetes

+ 17 - 1
.github/actions/e2e/action.yml

@@ -1,6 +1,12 @@
 name: "e2e"
 description: "runs our e2e test suite"
 
+inputs:
+  make-target:
+    description: "Make target to execute (for example: test.e2e or test.e2e.v2)"
+    required: false
+    default: "test.e2e"
+
 runs:
   using: composite
   steps:
@@ -56,4 +62,14 @@ runs:
       shell: bash
       env:
         DOCKER_BUILD_ARGS: --load
-      run: make test.e2e
+        MAKE_TARGET: ${{ inputs.make-target }}
+      run: |
+        case "$MAKE_TARGET" in
+          test.e2e|test.e2e.v2|test.e2e.v2.operational)
+            make "$MAKE_TARGET"
+            ;;
+          *)
+            echo "unsupported make target: $MAKE_TARGET" >&2
+            exit 1
+            ;;
+        esac

+ 4 - 0
.github/workflows/dependency-review.yml

@@ -27,3 +27,7 @@ jobs:
           persist-credentials: false
       - name: 'Dependency Review'
         uses: actions/dependency-review-action@a1d282b36b6f3519aa1f3fc636f609c47dddb294 # v4
+        with:
+          allow-ghsas: |
+            GHSA-7f33-f4f5-xwgw
+            GHSA-f5pg-7wfw-84q9

+ 54 - 8
.github/workflows/dlc.yml

@@ -26,19 +26,65 @@ jobs:
         with:
           persist-credentials: false
 
+      - name: "Install FOSSA CLI"
+        if: ${{ env.HAS_FOSSA_KEY == 'true' }}
+        run: |
+          curl -H 'Cache-Control: no-cache' https://raw.githubusercontent.com/fossas/fossa-cli/master/install-latest.sh | bash -s -- -b "$RUNNER_TEMP/bin" v3.17.1
+          echo "$RUNNER_TEMP/bin" >> "$GITHUB_PATH"
+          "$RUNNER_TEMP/bin/fossa" --version
+
       - name: "Run FOSSA Scan"
-        uses: fossas/fossa-action@ff70fe9fe17cbd2040648f1c45e8ec4e4884dcf3 # main
+        id: fossa_scan
         if: ${{ env.HAS_FOSSA_KEY == 'true' }}
+        continue-on-error: true
         env:
           FOSSA_API_KEY: ${{ secrets.FOSSA_API_KEY }}
-        with:
-          api-key: ${{ env.FOSSA_API_KEY }}
+          FOSSA_BRANCH: ${{ github.head_ref || github.ref_name }}
+          FOSSA_REVISION: ${{ github.event.pull_request.head.sha || github.sha }}
+        run: |
+          fossa analyze --debug --branch "$FOSSA_BRANCH" --revision "$FOSSA_REVISION" >"$RUNNER_TEMP/fossa-analyze.stdout" 2>"$RUNNER_TEMP/fossa-analyze.stderr"
+
+      - name: "Report FOSSA Scan Failure"
+        if: ${{ env.HAS_FOSSA_KEY == 'true' && steps.fossa_scan.outcome == 'failure' }}
+        run: |
+          if [ -f /tmp/fossa-analyze-scan-summary.txt ]; then
+            echo "FOSSA analyze summary:"
+            cat /tmp/fossa-analyze-scan-summary.txt
+            summary=$(tail -n 20 /tmp/fossa-analyze-scan-summary.txt | tr '\n' ' ' | sed 's/%/%25/g; s/\r/%0D/g')
+            echo "::error::${summary}"
+          elif [ -f "$RUNNER_TEMP/fossa-analyze.stderr" ]; then
+            echo "FOSSA analyze stderr:"
+            cat "$RUNNER_TEMP/fossa-analyze.stderr"
+            if grep -q "Invalid project permission" "$RUNNER_TEMP/fossa-analyze.stderr"; then
+              echo "::warning::FOSSA scan skipped because the configured API key does not have project edit permission in the FOSSA organization."
+              exit 0
+            fi
+            summary=$(tail -n 20 "$RUNNER_TEMP/fossa-analyze.stderr" | tr '\n' ' ' | sed 's/%/%25/g; s/\r/%0D/g')
+            echo "::error::${summary}"
+          else
+            echo "::error::FOSSA scan failed before writing /tmp/fossa-analyze-scan-summary.txt"
+          fi
+          exit 1
 
       - name: "Run FOSSA Test"
-        uses: fossas/fossa-action@ff70fe9fe17cbd2040648f1c45e8ec4e4884dcf3 # main
-        if: ${{ env.HAS_FOSSA_KEY == 'true' }}
+        id: fossa_test
+        if: ${{ env.HAS_FOSSA_KEY == 'true' && steps.fossa_scan.outcome == 'success' }}
+        continue-on-error: true
         env:
           FOSSA_API_KEY: ${{ secrets.FOSSA_API_KEY }}
-        with:
-          api-key: ${{ env.FOSSA_API_KEY }}
-          run-tests: true
+          FOSSA_REVISION: ${{ github.event.pull_request.head.sha || github.sha }}
+        run: |
+          fossa test --debug --revision "$FOSSA_REVISION" >"$RUNNER_TEMP/fossa-test.stdout" 2>"$RUNNER_TEMP/fossa-test.stderr"
+
+      - name: "Report FOSSA Test Failure"
+        if: ${{ env.HAS_FOSSA_KEY == 'true' && steps.fossa_test.outcome == 'failure' }}
+        run: |
+          if [ -f "$RUNNER_TEMP/fossa-test.stderr" ]; then
+            echo "FOSSA test stderr:"
+            cat "$RUNNER_TEMP/fossa-test.stderr"
+            summary=$(tail -n 20 "$RUNNER_TEMP/fossa-test.stderr" | tr '\n' ' ' | sed 's/%/%25/g; s/\r/%0D/g')
+            echo "::error::${summary}"
+          else
+            echo "::error::FOSSA test failed. No stderr file was captured."
+          fi
+          exit 1

+ 37 - 3
.github/workflows/e2e.yml

@@ -18,7 +18,22 @@ env:
 jobs:
 
   integration-trusted:
+    name: integration-trusted (${{ matrix.suite.name }})
     runs-on: ubuntu-latest
+    strategy:
+      fail-fast: false
+      matrix:
+        suite:
+        - name: classic
+          make_target: test.e2e
+          allow_failure: false
+        - name: v2
+          make_target: test.e2e.v2
+          allow_failure: true
+        - name: v2-operational
+          make_target: test.e2e.v2.operational
+          allow_failure: true
+    continue-on-error: ${{ matrix.suite.allow_failure }}
     permissions:
       id-token: write #for oidc auth with aws/gcp/azure
       contents: read  #for checkout
@@ -66,10 +81,27 @@ jobs:
       run: git fetch --prune --unshallow
 
     - uses: ./.github/actions/e2e
+      with:
+        make-target: ${{ matrix.suite.make_target }}
 
   # Repo owner has commented /ok-to-test on a (fork-based) pull request
   integration-fork:
+    name: integration-fork (${{ matrix.suite.name }})
     runs-on: ubuntu-latest
+    strategy:
+      fail-fast: false
+      matrix:
+        suite:
+        - name: classic
+          make_target: test.e2e
+          allow_failure: false
+        - name: v2
+          make_target: test.e2e.v2
+          allow_failure: true
+        - name: v2-operational
+          make_target: test.e2e.v2.operational
+          allow_failure: true
+    continue-on-error: ${{ matrix.suite.allow_failure }}
     permissions:
       id-token: write      #for oidc auth with aws/gcp/azure
       contents: read       #for checkout
@@ -121,8 +153,10 @@ jobs:
 
     - id: e2e
       uses: ./.github/actions/e2e
+      with:
+        make-target: ${{ matrix.suite.make_target }}
     - id: create_token
-      if: always()
+      if: always() && matrix.suite.name == 'classic'
       uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
       env:
         APP_ID: ${{ secrets.APP_ID }}
@@ -132,7 +166,7 @@ jobs:
         owner: ${{ github.repository_owner }}
 
     - name: Update on Succeess
-      if: always() && steps.e2e.conclusion == 'success'
+      if: always() && matrix.suite.name == 'classic' && steps.e2e.conclusion == 'success'
       uses: peter-evans/create-or-update-comment@e8674b075228eee787fea43ef493e45ece1004c9 # v5.0.0
       with:
         token: ${{ steps.create_token.outputs.token }}
@@ -140,7 +174,7 @@ jobs:
         body: |
             [Bot] - :white_check_mark: [e2e for ${{ env.TARGET_SHA }} passed](https://github.com/external-secrets/external-secrets/actions/runs/${{ github.run_id }})
     - name: Update on Failure
-      if: always() &&  steps.e2e.conclusion != 'success'
+      if: always() && matrix.suite.name == 'classic' && steps.e2e.conclusion != 'success'
       uses: peter-evans/create-or-update-comment@e8674b075228eee787fea43ef493e45ece1004c9 # v5.0.0
       with:
         token: ${{ steps.create_token.outputs.token }}

+ 2 - 0
.github/workflows/publish.yml

@@ -116,6 +116,7 @@ jobs:
         if: env.IS_FORK != ''
         shell: bash
         env:
+          DOCKER: docker buildx
           IMAGE_TAG: ${{ steps.container_info.outputs.image-tag }}
           BUILD_ARGS: ${{ inputs.build-args }}
           DOCKER_BUILD_ARGS: >-
@@ -128,6 +129,7 @@ jobs:
         if: env.IS_FORK == ''
         shell: bash
         env:
+          DOCKER: docker buildx
           IMAGE_TAG: ${{ steps.container_info.outputs.image-tag }}
           BUILD_ARGS: ${{ inputs.build-args }}
           DOCKER_BUILD_ARGS: --no-cache --load

+ 16 - 4
.sonarcloud.properties

@@ -3,14 +3,14 @@ sonar.projectKey=external-secrets_external-secrets
 
 # Path to sources
 sonar.sources=.
-sonar.exclusions=**/*_test.go, **/zz_generated.deepcopy.go, e2e/**
+sonar.exclusions=**/*_test.go, **/*.pb.go, **/zz_generated.deepcopy.go, e2e/**, providers/v2/hack/**, docs/api/spec.md, deploy/crds/bundle.yaml, deploy/charts/external-secrets/PROVIDERS.md, tests/__snapshot__/**, deploy/charts/external-secrets/tests/__snapshot__/**
 
 # Path to tests
 sonar.tests=.
 sonar.test.inclusions=**/*_test.go, e2e/**
 
 # Issues to ignore
-sonar.issue.ignore.multicriteria=g1,g2,g3
+sonar.issue.ignore.multicriteria=g1,g2,g3,g4,g5,g6
 
 # Ignore "Define a constant instead of duplicating this literal" in tests
 sonar.issue.ignore.multicriteria.g1.ruleKey=go:S1192
@@ -24,5 +24,17 @@ sonar.issue.ignore.multicriteria.g2.resourceKey=apis/externalsecrets/v1beta1/**
 sonar.issue.ignore.multicriteria.g3.ruleKey=go:S1066
 sonar.issue.ignore.multicriteria.g3.resourceKey=apis/externalsecrets/v1/**
 
-# Exclude API directories from duplication detection altogether because duplication is expected between versions.
-sonar.cpd.exclusions=apis/externalsecrets/v1/**,apis/externalsecrets/v1beta1/**
+# Ignore cognitive-complexity findings in tests.
+sonar.issue.ignore.multicriteria.g4.ruleKey=go:S3776
+sonar.issue.ignore.multicriteria.g4.resourceKey=**/*_test.go, e2e/**
+
+# Ignore orchestration hotspots that are intentionally dense while the v2 runtime split settles.
+sonar.issue.ignore.multicriteria.g5.ruleKey=go:S3776
+sonar.issue.ignore.multicriteria.g5.resourceKey=pkg/controllers/pushsecret/pushsecret_controller.go, pkg/controllers/pushsecret/pushsecret_controller_v2.go, providers/v2/aws/store/store.go
+
+# Ignore the compatibility-focused generator session helper signature for now.
+sonar.issue.ignore.multicriteria.g6.ruleKey=go:S107
+sonar.issue.ignore.multicriteria.g6.resourceKey=providers/v2/aws/store/auth/auth.go
+
+# Exclude generated artifacts and the specific runtime-split mirror hotspots from duplication detection until shared abstractions land.
+sonar.cpd.exclusions=apis/externalsecrets/v1/**,apis/externalsecrets/v1beta1/**,providers/v2/hack/**,docs/api/spec.md,deploy/crds/bundle.yaml,deploy/charts/external-secrets/PROVIDERS.md,tests/__snapshot__/**,deploy/charts/external-secrets/tests/__snapshot__/**,providers/v2/aws/store/auth/auth.go,providers/v2/aws/store/parameterstore/parameterstore.go,providers/v2/aws/store/parameterstore/fake/fake.go,providers/v2/aws/store/secretsmanager/secretsmanager.go,providers/v2/aws/store/secretsmanager/fake/fake.go,providers/v2/common/grpc/client.go,providers/v2/adapter/store/client.go,providers/v2/adapter/store/server.go,providers/v2/fake/store/store.go,runtime/clientmanager/manager.go,runtime/webhook/webhook/webhook.go,pkg/clientmanager/manager.go,pkg/controllers/providercerts/certs.go,pkg/controllers/providerclass/controller.go,pkg/controllers/clusterproviderclass/controller.go

+ 332 - 0
E2E_V2_PLAN.md

@@ -0,0 +1,332 @@
+# E2E V2 Migration Plan
+
+## Goal
+
+Reuse the existing provider e2e suite for the new V2 architecture, starting with the Kubernetes provider, and delete `e2e/suites/v2` after its reusable logic has been migrated.
+
+The target outcome is:
+
+- `provider.test` remains the single suite binary for provider e2e tests.
+- legacy and V2 runs are selected by configuration, not by separate suites.
+- existing table-driven provider assertions are reused.
+- V2-only Kubernetes tests for push, capabilities, and metrics live under `e2e/suites/provider`.
+- only the Kubernetes V2 provider is enabled initially.
+
+## Agreed Decisions
+
+- Use `provider.test` for both legacy and V2 runs.
+- Replace the current `test.e2e.v2` flow so it runs `provider.test` in V2 mode instead of `v2.test`.
+- Reuse the existing table-driven tests in `e2e/suites/provider/cases/kubernetes`.
+- Also migrate the V2 push, capabilities, and metrics tests into `e2e/suites/provider`.
+- For referent-auth equivalence in V2, use `ClusterProvider` with `AuthenticationScopeManifestNamespace`.
+- In the first migration step, enable only the Kubernetes provider deployment in V2 mode.
+
+## Proposed Design
+
+### 1. Introduce a provider suite mode switch
+
+Add an environment-driven mode switch for the provider suite.
+
+Suggested env var:
+
+- `E2E_PROVIDER_MODE=legacy|v2`
+
+Behavior:
+
+- default: `legacy`
+- `v2`: install ESO with V2 enabled, create Provider/ClusterProvider CRDs, and enable the Kubernetes provider deployment
+
+This keeps the suite layout stable and avoids carrying `e2e/suites/v2` as a parallel test hierarchy.
+
+### 2. Move reusable V2 bootstrap into framework/addon
+
+The V2 install logic currently proven in `e2e/suites/v2/suite_test.go` should be promoted into reusable addon mutators instead of staying in suite code.
+
+Recommended shape:
+
+- add V2 mutators in `e2e/framework/addon`
+- use `addon.NewESO(...)` for both legacy and V2
+- do **not** rely on the bespoke installer in `e2e/framework/addon/eso_v2.go`; either remove it later or repurpose it so there is only one supported installation path
+
+Suggested mutators:
+
+- `addon.WithV2Mode()`
+- `addon.WithProviderNamespace("external-secrets-system")`
+- `addon.WithV2Providers(...providers)` or a narrower `addon.WithKubernetesV2Provider()`
+
+Minimum V2 Helm settings:
+
+- `v2.enabled=true`
+- `crds.createProvider=true`
+- `crds.createClusterProvider=true`
+- `providers.enabled=true`
+- one provider entry for Kubernetes
+- release namespace `external-secrets-system`
+- release name `external-secrets`
+
+### 3. Make framework defaults mode-aware
+
+The main framework coupling today is that the default testcase builder assumes a namespaced `SecretStore` reference and no ref kind.
+
+Add mode-aware defaults to `framework.Framework` so existing test tables can continue to build their manifests the same way.
+
+Suggested new fields on `framework.Framework`:
+
+- `DefaultSecretStoreRefKind string`
+- `DefaultPushSecretStoreRefKind string`
+- optionally `DefaultPushSecretStoreRefAPIVersion string`
+- optionally `ProviderMode string`
+
+Set them in `framework.New(...)` based on `E2E_PROVIDER_MODE`:
+
+- legacy:
+  - `DefaultSecretStoreRefKind = ""`
+  - `DefaultPushSecretStoreRefKind = ""`
+- v2:
+  - `DefaultSecretStoreRefKind = esv1.ProviderKindStr`
+  - `DefaultPushSecretStoreRefKind = esv1.ProviderKindStr`
+
+Update default testcase creation in `e2e/framework/testcase.go` so:
+
+- `makeDefaultExternalSecretTestCase()` sets `Spec.SecretStoreRef.Kind` from framework defaults
+- `makeDefaultPushSecretTestCase()` sets `Spec.SecretStoreRefs[0].Kind` from framework defaults
+
+This allows existing table-driven tests to reuse the same setup with no broad manifest rewrite.
+
+### 4. Move V2 resource helpers out of `e2e/suites/v2`
+
+Anything that is reusable test infrastructure should move out of the `v2` suite package before that package is deleted.
+
+Recommended destination:
+
+- new package under `e2e/framework/v2` or similar
+
+Helpers to move first:
+
+- cluster CA bundle lookup
+- Kubernetes provider CR creation
+- Provider creation
+- ClusterProvider creation
+- provider readiness waits
+- RBAC helper for Kubernetes provider access
+- metrics scraping helpers
+
+Likely source files:
+
+- `e2e/suites/v2/helpers.go`
+- `e2e/suites/v2/metrics_helpers.go`
+
+After migration, provider cases should import only framework packages, never `e2e/suites/v2`.
+
+## Kubernetes Provider Migration
+
+### 5. Add a V2 setup path to the Kubernetes provider case
+
+Extend `e2e/suites/provider/cases/kubernetes/provider.go` so the provider setup can operate in both modes.
+
+Recommended approach:
+
+- keep the current `Provider` as the abstraction used by the table tests
+- branch internally on provider mode
+- legacy mode keeps creating `SecretStore` and `ClusterSecretStore`
+- V2 mode creates:
+  - namespaced `Kind=Kubernetes`
+  - namespaced `Kind=Provider`
+  - `ClusterProvider` for the referent-auth equivalent path
+
+Recommended structure:
+
+- `BeforeEach()` decides `setupLegacy()` vs `setupV2()`
+- `CreateStore()` keeps legacy behavior
+- add `CreateStoreV2()`
+- `CreateReferentStore()` keeps legacy behavior
+- add `CreateReferentStoreV2()`
+
+### 6. Preserve existing testcase naming semantics
+
+To minimize rewiring, keep the resource names aligned with what the table tests already expect.
+
+For the default case in V2 mode:
+
+- `Provider.Name = f.Namespace.Name`
+- `Provider.Namespace = f.Namespace.Name`
+- `ExternalSecret.Spec.SecretStoreRef.Name` remains untouched by tests and still resolves correctly
+
+For referent-auth cases in V2 mode:
+
+- create a `ClusterProvider` using the existing referent naming pattern
+- wire it with `AuthenticationScopeManifestNamespace`
+
+### 7. Map referent-auth behavior explicitly
+
+Today the Kubernetes suite expresses referent auth by switching to `ClusterSecretStore`.
+
+In V2, the semantic equivalent should be:
+
+- `SecretStoreRef.Kind = ClusterProvider`
+- `SecretStoreRef.Name = <referent-name>`
+- `ClusterProvider.Spec.AuthenticationScope = ManifestNamespace`
+
+Update `withReferentStore(...)` so it branches by mode:
+
+- legacy: `ClusterSecretStore`
+- V2: `ClusterProvider`
+
+Use API constants where available:
+
+- `esv1.ProviderKindStr`
+- `esv1.ClusterProviderKindStr`
+- `esv1.AuthenticationScopeManifestNamespace`
+
+### 8. RBAC model for Kubernetes V2 tests
+
+The V2 Kubernetes provider needs explicit RBAC to read or write secrets in the target namespace.
+
+For namespaced provider tests:
+
+- create role + rolebinding in the remote namespace
+- bind to the manifest namespace service account used by the provider auth configuration
+
+For referent/cluster-provider cases:
+
+- ensure RBAC is granted in the namespace the provider should access
+- when `AuthenticationScopeManifestNamespace` is used, bind the service account identity from the ExternalSecret/PushSecret namespace
+
+The plan should prefer one RBAC helper with explicit parameters over many ad-hoc copies.
+
+## Test Migration Scope
+
+### 9. Reuse the existing table-driven Kubernetes tests
+
+Keep the existing table-driven assertions in:
+
+- `e2e/suites/provider/cases/kubernetes/kubernetes.go`
+
+These should run in both modes, but V2 execution should be label-gated so we can migrate incrementally.
+
+Recommended labeling:
+
+- retain current `kubernetes` label
+- add V2-specific coverage behind `v2`
+- optionally add `legacy` label to the old suite bootstrap only if needed later
+
+Recommended strategy:
+
+- do not duplicate the common table entries
+- instantiate the same tables with a V2-aware provider setup
+
+### 10. Migrate V2-only Kubernetes tests into provider suite
+
+Move the following test coverage into `e2e/suites/provider/cases/kubernetes` or a sibling under `e2e/suites/provider`:
+
+- capabilities tests
+- push tests
+- metrics tests
+- any cluster-provider tests that express Kubernetes V2 behavior not already covered by the legacy tables
+
+Suggested placement:
+
+- `e2e/suites/provider/cases/kubernetes/capabilities_v2_test.go`
+- `e2e/suites/provider/cases/kubernetes/push_v2_test.go`
+- `e2e/suites/provider/cases/kubernetes/metrics_v2_test.go`
+- `e2e/suites/provider/cases/kubernetes/cluster_provider_v2_test.go`
+
+Guideline:
+
+- keep table-driven sync assertions in the existing `kubernetes.go`
+- keep new V2-only behavior in separate files so the legacy flow stays readable
+
+### 11. Metrics migration approach
+
+The metrics coverage in `e2e/suites/v2/metrics_test.go` is useful and should remain, but it should be narrowed to Kubernetes-only assumptions for this first step.
+
+First migration scope:
+
+- provider readiness metrics for `Provider`
+- readiness metrics for `ClusterProvider`
+- gRPC client/server request metrics after an `ExternalSecret` sync
+- any Kubernetes-specific cache metrics that remain stable enough for CI
+
+Avoid in the first pass:
+
+- fake-provider metrics dependencies
+- broad provider-agnostic abstractions that are not needed yet
+
+## CI / Build Changes
+
+### 12. Switch `test.e2e.v2` to run the provider suite
+
+Update the E2E execution path so V2 uses the provider suite binary instead of `v2.test`.
+
+Planned changes:
+
+- `e2e/Makefile`
+  - keep loading the controller image
+  - load only `provider-kubernetes` for V2 initially
+  - run `TEST_SUITES="provider"`
+  - run with `E2E_PROVIDER_MODE=v2`
+  - keep `GINKGO_LABELS="v2"`
+- `e2e/Dockerfile`
+  - remove `ADD e2e/suites/v2/v2.test /v2.test` once the old suite is gone
+
+Target command shape:
+
+```bash
+GINKGO_LABELS="v2" E2E_PROVIDER_MODE="v2" TEST_SUITES="provider" ./run.sh
+```
+
+### 13. Delete `e2e/suites/v2` only after parity is reached
+
+Deletion should happen only after:
+
+- V2 bootstrap moved out of `e2e/suites/v2/suite_test.go`
+- helpers moved out of `e2e/suites/v2/helpers.go`
+- metrics helpers moved out of `e2e/suites/v2/metrics_helpers.go`
+- Kubernetes V2 tests live under `e2e/suites/provider`
+- `test.e2e.v2` no longer depends on `v2.test`
+
+At that point remove:
+
+- `e2e/suites/v2/`
+- `v2.test` build/copy path
+- any stale docs or make targets referencing the old suite layout
+
+## Suggested Implementation Sequence
+
+1. Add provider-mode env handling to the provider suite bootstrap.
+2. Extract V2 Helm mutators from `e2e/suites/v2/suite_test.go` into `e2e/framework/addon`.
+3. Make framework testcase defaults mode-aware for `Provider` refs.
+4. Move reusable V2 helpers into a framework package.
+5. Extend Kubernetes provider setup to support legacy and V2.
+6. Reuse the existing table-driven Kubernetes tests in V2 mode.
+7. Migrate V2 capabilities tests into the provider suite.
+8. Migrate V2 push tests into the provider suite.
+9. Migrate V2 metrics tests into the provider suite.
+10. Update `test.e2e.v2` to run `provider.test`.
+11. Delete `e2e/suites/v2` and remove `v2.test` packaging.
+
+## Acceptance Criteria
+
+The migration is complete when all of the following are true:
+
+- `make test.e2e` still runs the legacy provider suite unchanged.
+- `make test.e2e.v2` runs `provider.test` with `E2E_PROVIDER_MODE=v2`.
+- the Kubernetes table-driven tests pass in V2 mode using `Provider` / `ClusterProvider` resources.
+- V2 push, capabilities, and metrics tests pass from within `e2e/suites/provider`.
+- only the Kubernetes V2 provider deployment is enabled in the initial V2 run.
+- `e2e/suites/v2` is deleted.
+- the e2e image no longer bundles `v2.test`.
+
+## Risks / Watchouts
+
+- The existing `e2e/framework/addon/eso_v2.go` appears to describe a second installation approach; keeping both active will create drift. Consolidate on the Helm-mutation path.
+- Metrics tests may be brittle if they assert on counters that can vary with retries or background reconciliation. Prefer existence and lower-bound assertions over exact counts.
+- `ClusterProvider` resources are cluster-scoped and must be cleaned up carefully to avoid cross-test leakage.
+- The default testcase ref-kind switch must not affect non-V2 suites; it should be entirely gated by `E2E_PROVIDER_MODE`.
+- Referent-auth semantics should be validated carefully: for V2 this migration assumes `AuthenticationScopeManifestNamespace` is the intended equivalent.
+
+## Out of Scope for This First Step
+
+- Migrating all other providers to V2.
+- Enabling AWS, Fake, or other provider deployments in the V2 provider-suite bootstrap.
+- Building a generic V2 abstraction for every provider before the Kubernetes migration proves the pattern.

+ 154 - 20
Makefile

@@ -6,6 +6,21 @@ MAKEFLAGS     += --warn-undefined-variables
 .SHELLFLAGS   := -euo pipefail -c
 
 ARCH ?= amd64 arm64 ppc64le
+
+# Detect local architecture for e2e testing
+LOCAL_ARCH := $(shell uname -m)
+ifeq ($(LOCAL_ARCH),x86_64)
+	LOCAL_GOARCH := amd64
+else ifeq ($(LOCAL_ARCH),aarch64)
+	LOCAL_GOARCH := arm64
+else ifeq ($(LOCAL_ARCH),arm64)
+	LOCAL_GOARCH := arm64
+else ifeq ($(LOCAL_ARCH),ppc64le)
+	LOCAL_GOARCH := ppc64le
+else
+	LOCAL_GOARCH := amd64
+endif
+
 BUILD_ARGS ?= CGO_ENABLED=0
 DOCKER_BUILD_ARGS ?=
 DOCKERFILE ?= Dockerfile
@@ -69,16 +84,38 @@ ERR		= echo ${TIME} ${RED}[FAIL]${CNone}
 OK		= echo ${TIME} ${GREEN}[ OK ]${CNone}
 FAIL	= (echo ${TIME} ${RED}[FAIL]${CNone} && false)
 
+# ====================================================================================
+# Protobuf
+
+.PHONY: proto
+proto: ## Generate protobuf code
+	@$(INFO) generating protobuf code
+	@protoc --go_out=. --go_opt=paths=source_relative \
+		--go-grpc_out=. --go-grpc_opt=paths=source_relative \
+		-I. \
+		providers/v2/common/proto/provider/secretstore.proto
+	@protoc --go_out=. --go_opt=paths=source_relative \
+		--go-grpc_out=. --go-grpc_opt=paths=source_relative \
+		-I. \
+		providers/v2/common/proto/generator/generator.proto
+	@for file in \
+		providers/v2/common/proto/provider/secretstore.pb.go \
+		providers/v2/common/proto/provider/secretstore_grpc.pb.go \
+		providers/v2/common/proto/generator/generator.pb.go \
+		providers/v2/common/proto/generator/generator_grpc.pb.go; do \
+		tmp=$$(mktemp); \
+		cat hack/boilerplate.go.txt "$$file" > "$$tmp"; \
+		mv "$$tmp" "$$file"; \
+	done
+	@$(OK) protobuf code generated
+
 # ====================================================================================
 # Conformance
 
-reviewable: generate docs manifests helm.generate helm.schema.update helm.docs lint license.check helm.test.update test.crds.update tf.fmt ## Ensure a PR is ready for review.
-	@go mod tidy
-	@cd e2e/ && go mod tidy
-	@cd apis/ && go mod tidy
-	@cd runtime/ && go mod tidy
-	@for provider in providers/v1/*/; do (cd $$provider && go mod tidy); done
-	@for generator in generators/v1/*/; do (cd $$generator && go mod tidy); done
+reviewable: generate docs manifests helm.generate helm.schema.update helm.docs lint license.check helm.test.update test.crds.update tf.fmt generate-providers verify-providers ## Ensure a PR is ready for review.
+	@for module in . e2e apis runtime $$(find providers/v1 generators/v1 providers/v2 -name go.mod -not -path '*/vendor/*' -exec dirname {} \; | sort); do \
+		(cd "$$module" && GOWORK=off go mod tidy); \
+	done
 
 check-diff: reviewable ## Ensure branch is clean.
 	@$(INFO) checking that branch is clean
@@ -99,10 +136,10 @@ license.check:
 go-work:
 	@$(INFO) creating go workspace
 	@rm -rf go.work go.work.sum
-	@go work init
-	@go work use -r .
-	@go work edit -dropuse ./e2e
-	@go work sync
+	@GOWORK=off go work init
+	@GOWORK="$(shell pwd)/go.work" go work use -r .
+	@GOWORK="$(shell pwd)/go.work" go work edit -dropuse ./e2e
+	@GOWORK="$(shell pwd)/go.work" go work sync
 	@$(OK) created go workspace
 
 .PHONY: test
@@ -129,6 +166,18 @@ test.e2e.managed: generate ## Run e2e tests managed
 	$(MAKE) -C ./e2e test.managed
 	@$(OK) go test e2e-tests-managed
 
+.PHONY: test.e2e.v2
+test.e2e.v2: generate ## Run V2 E2E tests
+	@$(INFO) go test v2 e2e-tests
+	$(MAKE) -C ./e2e test.v2
+	@$(OK) go test v2 e2e-tests
+
+.PHONY: test.e2e.v2.operational
+test.e2e.v2.operational: generate ## Run focused V2 operational E2E tests
+	@$(INFO) go test v2 operational e2e-tests
+	$(MAKE) -C ./e2e test.v2.operational
+	@$(OK) go test v2 operational e2e-tests
+
 .PHONY: test.crds
 test.crds: cty crds.generate.tests ## Test CRDs for modification and backwards compatibility
 	@$(INFO) $(CTY) test tests
@@ -192,6 +241,21 @@ generate: ## Generate code and crds
 	@./hack/crd.generate.sh $(BUNDLE_DIR) $(CRD_DIR)
 	@$(OK) Finished generating deepcopy and crds
 
+generate-providers: ## Generate provider main.go and Dockerfile files from provider.yaml configs
+	@$(INFO) Generating provider files
+	@cd providers/v2/hack && go run generate-provider-main.go -providers-dir=..
+	@$(OK) Generated provider files
+
+verify-providers: ## Verify that provider files are up to date
+	@$(INFO) Verifying provider files are up to date
+	@cd providers/v2/hack && go run generate-provider-main.go -providers-dir=.. -dry-run
+	@if ! git diff --quiet providers/v2/*/main.go providers/v2/*/Dockerfile 2>/dev/null; then \
+		echo "Provider files are out of date. Run 'make generate-providers' to update them."; \
+		git diff providers/v2/*/main.go providers/v2/*/Dockerfile; \
+		exit 1; \
+	fi
+	@$(OK) Provider files are up to date
+
 # ====================================================================================
 # Local Utility
 
@@ -206,7 +270,7 @@ manifests: helm.generate ## Generate manifests from helm chart
 	helm template external-secrets $(HELM_DIR) -f deploy/manifests/helm-values.yaml > $(OUTPUT_DIR)/deploy/manifests/external-secrets.yaml
 
 crds.install: generate ## Install CRDs into a cluster. This is for convenience
-	kubectl apply -f $(BUNDLE_DIR) --server-side
+	kubectl apply -f $(BUNDLE_DIR) --server-side --force-conflicts
 
 crds.uninstall: ## Uninstall CRDs from a cluster. This is for convenience
 	kubectl delete -f $(BUNDLE_DIR)
@@ -329,17 +393,87 @@ docker.tag:  ## Emit IMAGE_TAG
 	@echo $(IMAGE_TAG)
 
 .PHONY: docker.build
-docker.build: $(addprefix build-,$(ARCH)) ## Build the docker image
-	@$(INFO) $(DOCKER) build
-	echo $(DOCKER) buildx build -f $(DOCKERFILE) . $(DOCKER_BUILD_ARGS) -t $(IMAGE_NAME):$(IMAGE_TAG)
-	$(DOCKER) buildx build -f $(DOCKERFILE) . $(DOCKER_BUILD_ARGS) -t $(IMAGE_NAME):$(IMAGE_TAG)
-	@$(OK) $(DOCKER) build
+docker.build: docker.build.controller docker.build.providers ## Build all docker images (controller + providers)
+
+.PHONY: docker.build.e2e
+docker.build.e2e: docker.build.controller.e2e ## Build docker images for local e2e testing (local arch only)
+
+.PHONY: docker.build.controller
+docker.build.controller: $(addprefix build-,$(ARCH)) ## Build the controller docker image
+	@$(INFO) $(DOCKER) build controller
+	@echo $(DOCKER) build -f $(DOCKERFILE) . $(DOCKER_BUILD_ARGS) -t $(IMAGE_NAME):$(IMAGE_TAG)
+	@DOCKER_BUILDKIT=1 $(DOCKER) build -f $(DOCKERFILE) . $(DOCKER_BUILD_ARGS) -t $(IMAGE_NAME):$(IMAGE_TAG)
+	@$(OK) $(DOCKER) build controller
+
+.PHONY: docker.build.controller.e2e
+docker.build.controller.e2e: build-$(LOCAL_GOARCH) ## Build the controller docker image for local arch only
+	@$(INFO) $(DOCKER) build controller for $(LOCAL_GOARCH)
+	@echo $(DOCKER) build -f $(DOCKERFILE) . $(DOCKER_BUILD_ARGS) -t $(IMAGE_NAME):$(IMAGE_TAG)
+	@DOCKER_BUILDKIT=1 $(DOCKER) build -f $(DOCKERFILE) . $(DOCKER_BUILD_ARGS) -t $(IMAGE_NAME):$(IMAGE_TAG)
+	@$(OK) $(DOCKER) build controller for $(LOCAL_GOARCH)
+
+.PHONY: docker.build.providers
+docker.build.providers: docker.build.provider.kubernetes docker.build.provider.aws docker.build.provider.fake ## Build all provider images
+
+.PHONY: docker.build.provider.kubernetes
+docker.build.provider.kubernetes: ## Build Kubernetes provider image
+	@$(INFO) $(DOCKER) build kubernetes provider
+	@DOCKER_BUILDKIT=1 $(DOCKER) build \
+		-f providers/v2/kubernetes/Dockerfile \
+		. \
+		$(DOCKER_BUILD_ARGS) \
+		-t $(IMAGE_REGISTRY)/external-secrets/provider-kubernetes:$(IMAGE_TAG)
+	@$(OK) $(DOCKER) build kubernetes provider
+
+.PHONY: docker.build.provider.aws
+docker.build.provider.aws: ## Build AWS provider image
+	@$(INFO) $(DOCKER) build AWS provider
+	@DOCKER_BUILDKIT=1 $(DOCKER) build \
+		-f providers/v2/aws/Dockerfile \
+		. \
+		$(DOCKER_BUILD_ARGS) \
+		-t $(IMAGE_REGISTRY)/external-secrets/provider-aws:$(IMAGE_TAG)
+	@$(OK) $(DOCKER) build AWS provider
+
+.PHONY: docker.build.provider.fake
+docker.build.provider.fake: ## Build Fake provider image
+	@$(INFO) $(DOCKER) build Fake provider
+	@DOCKER_BUILDKIT=1 $(DOCKER) build \
+		-f providers/v2/fake/Dockerfile \
+		. \
+		$(DOCKER_BUILD_ARGS) \
+		-t $(IMAGE_REGISTRY)/external-secrets/provider-fake:$(IMAGE_TAG)
+	@$(OK) $(DOCKER) build Fake provider
 
 .PHONY: docker.push
-docker.push: ## Push the docker image to the registry
-	@$(INFO) $(DOCKER) push
+docker.push: docker.push.controller docker.push.providers ## Push all docker images to the registry
+
+.PHONY: docker.push.controller
+docker.push.controller: ## Push the controller docker image to the registry
+	@$(INFO) $(DOCKER) push controller
 	@$(DOCKER) push $(IMAGE_NAME):$(IMAGE_TAG)
-	@$(OK) $(DOCKER) push
+	@$(OK) $(DOCKER) push controller
+
+.PHONY: docker.push.providers
+docker.push.providers: docker.push.provider.kubernetes docker.push.provider.aws docker.push.provider.fake ## Push all provider images
+
+.PHONY: docker.push.provider.kubernetes
+docker.push.provider.kubernetes: ## Push Kubernetes provider image
+	@$(INFO) $(DOCKER) push kubernetes provider
+	@$(DOCKER) push $(IMAGE_REGISTRY)/external-secrets/provider-kubernetes:$(IMAGE_TAG)
+	@$(OK) $(DOCKER) push kubernetes provider
+
+.PHONY: docker.push.provider.aws
+docker.push.provider.aws: ## Push AWS provider image
+	@$(INFO) $(DOCKER) push AWS provider
+	@$(DOCKER) push $(IMAGE_REGISTRY)/external-secrets/provider-aws:$(IMAGE_TAG)
+	@$(OK) $(DOCKER) push AWS provider
+
+.PHONY: docker.push.provider.fake
+docker.push.provider.fake: ## Push Fake provider image
+	@$(INFO) $(DOCKER) push Fake provider
+	@$(DOCKER) push $(IMAGE_REGISTRY)/external-secrets/provider-fake:$(IMAGE_TAG)
+	@$(OK) $(DOCKER) push Fake provider
 
 # RELEASE_TAG is tag to promote. Default is promoting to main branch, but can be overriden
 # to promote a tag to a specific version.

+ 134 - 0
PR_WALKTHROUGH.md

@@ -0,0 +1,134 @@
+# PR Walkthrough
+
+This PR is large, but the core change is narrow: runtime selection moved from the old v2 `ProviderStore` model to `ProviderClass`, with a new namespaced `ProviderClass` as the default.
+
+If you want a guided tour, review it in this order. The goal is to confirm the semantic contract first, then check the controller/runtime enforcement, then validate that the e2es actually prove the contract.
+
+## 20-Minute Review Plan
+
+### 1. API contract and invariants (5 min)
+
+Start here:
+
+- `apis/externalsecrets/v1/secretstore_types.go`
+- `apis/externalsecrets/v1/externalsecret_types.go`
+- `apis/externalsecrets/v1beta1/externalsecret_types.go`
+- `apis/externalsecrets/v1alpha1/pushsecret_types.go`
+
+Focus on these questions:
+
+- Is the default runtime class kind now clearly namespaced `ProviderClass`?
+- Is it explicit that namespaced `ProviderClass` resolution uses the `SecretStore` namespace?
+- Is it explicit that `ClusterSecretStore` may only target `ClusterProviderClass`?
+- Are kind defaults and validation rules obvious from the types, not just from controller behavior?
+
+What matters:
+
+- If this layer is ambiguous, the rest of the PR is hard to reason about.
+- Most regression risk comes from mismatched assumptions between API shape and controller resolution.
+
+### 2. Runtime resolution and controller wiring (6 min)
+
+Read next:
+
+- `pkg/controllers/externalsecret/externalsecret_controller.go`
+- `pkg/controllers/pushsecret/pushsecret_controller.go`
+- `pkg/controllers/providerclass/controller.go`
+- `cmd/controller/root.go`
+
+Focus on these questions:
+
+- Where is the default class kind applied when the kind is omitted?
+- Does a namespaced `ProviderClass` always resolve in the `SecretStore` namespace, not the workload namespace?
+- Is the `ClusterSecretStore -> ProviderClass` path rejected early and clearly?
+- Did controller setup and watches change in a way that could miss reconciles or break ownership assumptions?
+
+What matters:
+
+- This is the highest-value review area.
+- The main failure mode is “valid YAML, wrong runtime object selected”.
+
+### 3. Migration away from ProviderStore (3 min)
+
+Skim the removals as one story:
+
+- deleted `apis/externalsecrets/v2alpha1/*`
+- deleted `pkg/controllers/providerstore/*`
+- deleted `runtime/clientmanager/providerstore.go`
+
+Focus on these questions:
+
+- Is anything still conceptually depending on the old v2 providerstore layer?
+- Were there any compatibility shims that silently carried behavior and are now gone?
+- Does the new `ProviderClass` path fully replace the removed stack, or is there a hidden gap?
+
+What matters:
+
+- The diff is deletion-heavy, so this is less about line review and more about architecture sanity.
+
+### 4. PushSecret edge cases (3 min)
+
+Give this a separate pass:
+
+- `pkg/controllers/pushsecret/pushsecret_controller.go`
+- `pkg/controllers/pushsecret/pushsecret_controller_test.go`
+
+Focus on these questions:
+
+- Does omitted kind now normalize consistently to `SecretStore`?
+- Does PushSecret resolve runtime refs the same way ExternalSecret does?
+- Are tests covering the defaulting path, not only explicit kind declarations?
+
+What matters:
+
+- There was a follow-up fix here, so this is a known sharp edge.
+
+### 5. E2E proof, not just migration churn (3 min)
+
+Finish in the e2es:
+
+- `e2e/framework/v2/helpers.go`
+- `e2e/suites/provider/cases/fake/runtime_ref_v2.go`
+- `e2e/suites/provider/cases/fake/operational_v2.go`
+- `e2e/suites/provider/cases/common/*`
+
+Focus on these questions:
+
+- Do the tests cover both namespaced `ProviderClass` and `ClusterProviderClass` resolution?
+- Is there a negative case for `ClusterSecretStore -> ProviderClass`?
+- Do the fake-provider tests exercise the real runtime-ref path, or do helpers hide too much?
+- Did the migration keep the tests behavior-focused, or did it drift into fixture churn?
+
+What matters:
+
+- This tells you whether the new contract is enforced end-to-end, not just unit-tested.
+
+## What To Skim Last
+
+Only after the semantic review above:
+
+- `config/crds/*`
+- `deploy/crds/bundle.yaml`
+- `docs/api/spec.md`
+- `tests/__snapshot__/*`
+
+Use these as confirmation that the public surface matches the implementation. Do not start here.
+
+## Things Worth Calling Out In The Tour
+
+If you are presenting this PR live, I would center the walkthrough on four claims:
+
+1. The default is now namespaced `ProviderClass`.
+2. Namespaced `ProviderClass` resolution is anchored to the `SecretStore` namespace.
+3. `ClusterSecretStore` is intentionally restricted to `ClusterProviderClass`.
+4. The old v2 `ProviderStore` stack was intentionally removed rather than kept as a compatibility layer.
+
+## Suggested Tour Script
+
+If you want a compact live narrative, use this:
+
+1. Start at the API types and state the new invariants.
+2. Jump to controller resolution and show where those invariants are enforced.
+3. Call out the ProviderStore removal as deliberate simplification, not incidental cleanup.
+4. Show the PushSecret defaulting fix as a concrete edge case.
+5. End with the e2e runtime-ref coverage to show the new model is exercised end-to-end.

+ 28 - 0
apis/externalsecrets/v1/authscope_types.go

@@ -0,0 +1,28 @@
+/*
+Copyright © The ESO Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    https://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 v1
+
+// AuthenticationScope defines which namespace should be used when resolving
+// provider-owned backend configuration for out-of-process test scenarios.
+type AuthenticationScope string
+
+const (
+	// AuthenticationScopeProviderNamespace uses the provider/backend namespace.
+	AuthenticationScopeProviderNamespace AuthenticationScope = "ProviderNamespace"
+	// AuthenticationScopeManifestNamespace uses the ExternalSecret/PushSecret namespace.
+	AuthenticationScopeManifestNamespace AuthenticationScope = "ManifestNamespace"
+)

+ 1 - 1
apis/externalsecrets/v1/externalsecret_types.go

@@ -29,7 +29,7 @@ type SecretStoreRef struct {
 	// +kubebuilder:validation:Pattern:=^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
 	Name string `json:"name,omitempty"`
 
-	// Kind of the SecretStore resource (SecretStore or ClusterSecretStore)
+	// Kind of the SecretStore resource (SecretStore, ClusterSecretStore)
 	// Defaults to `SecretStore`
 	// +optional
 	// +kubebuilder:validation:Enum=SecretStore;ClusterSecretStore

+ 2 - 2
apis/externalsecrets/v1/provider.go

@@ -49,8 +49,8 @@ func (v ValidationResult) String() string {
 // +k8s:deepcopy-gen:interfaces=nil
 // +k8s:deepcopy-gen=nil
 
-// Provider is a common interface for interacting with secret backends.
-type Provider interface {
+// ProviderInterface is a common interface for interacting with secret backends.
+type ProviderInterface interface {
 	// NewClient constructs a SecretsManager Provider
 	NewClient(ctx context.Context, store GenericStore, kube client.Client, namespace string) (SecretsClient, error)
 

+ 6 - 6
apis/externalsecrets/v1/provider_schema.go

@@ -23,16 +23,16 @@ import (
 	"sync"
 )
 
-var builder map[string]Provider
+var builder map[string]ProviderInterface
 var buildlock sync.RWMutex
 
 func init() {
-	builder = make(map[string]Provider)
+	builder = make(map[string]ProviderInterface)
 }
 
 // Register a store backend type. Register panics if a
 // backend with the same store is already registered.
-func Register(s Provider, storeSpec *SecretStoreProvider, maintenanceStatus MaintenanceStatus) {
+func Register(s ProviderInterface, storeSpec *SecretStoreProvider, maintenanceStatus MaintenanceStatus) {
 	storeName, err := getProviderName(storeSpec)
 	if err != nil {
 		panic(fmt.Sprintf("store error registering schema: %s", err.Error()))
@@ -51,7 +51,7 @@ func Register(s Provider, storeSpec *SecretStoreProvider, maintenanceStatus Main
 
 // ForceRegister adds to store schema, overwriting a store if
 // already registered. Should only be used for testing.
-func ForceRegister(s Provider, storeSpec *SecretStoreProvider, maintenanceStatus MaintenanceStatus) {
+func ForceRegister(s ProviderInterface, storeSpec *SecretStoreProvider, maintenanceStatus MaintenanceStatus) {
 	storeName, err := getProviderName(storeSpec)
 	if err != nil {
 		panic(fmt.Sprintf("store error registering schema: %s", err.Error()))
@@ -64,7 +64,7 @@ func ForceRegister(s Provider, storeSpec *SecretStoreProvider, maintenanceStatus
 }
 
 // GetProviderByName returns the provider implementation by name.
-func GetProviderByName(name string) (Provider, bool) {
+func GetProviderByName(name string) (ProviderInterface, bool) {
 	buildlock.RLock()
 	f, ok := builder[name]
 	buildlock.RUnlock()
@@ -72,7 +72,7 @@ func GetProviderByName(name string) (Provider, bool) {
 }
 
 // GetProvider returns the provider from the generic store.
-func GetProvider(s GenericStore) (Provider, error) {
+func GetProvider(s GenericStore) (ProviderInterface, error) {
 	if s == nil {
 		return nil, nil
 	}

+ 1 - 0
apis/externalsecrets/v1/secretstore_aws_types.go

@@ -82,6 +82,7 @@ type SecretsManager struct {
 	// then by default Secrets Manager uses a 30-day recovery window.
 	// see: https://docs.aws.amazon.com/secretsmanager/latest/apireference/API_DeleteSecret.html#SecretsManager-DeleteSecret-request-RecoveryWindowInDays
 	// +optional
+	// +kubebuilder:validation:Format=""
 	RecoveryWindowInDays int64 `json:"recoveryWindowInDays,omitempty"`
 }
 

+ 2 - 0
apis/externalsecrets/v1/secretstore_github_types.go

@@ -32,9 +32,11 @@ type GithubProvider struct {
 	Auth GithubAppAuth `json:"auth"`
 
 	// appID specifies the Github APP that will be used to authenticate the client
+	// +kubebuilder:validation:Format=""
 	AppID int64 `json:"appID"`
 
 	// installationID specifies the Github APP installation that will be used to authenticate the client
+	// +kubebuilder:validation:Format=""
 	InstallationID int64 `json:"installationID"`
 
 	// organization will be used to fetch secrets from the Github organization

+ 59 - 0
apis/externalsecrets/v1/secretstore_runtime_ref_test.go

@@ -0,0 +1,59 @@
+/*
+Copyright © The ESO Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    https://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 v1
+
+import "testing"
+
+func TestSecretStoreSpecDeepCopyPreservesRuntimeRef(t *testing.T) {
+	spec := &SecretStoreSpec{
+		RuntimeRef: &StoreRuntimeRef{
+			Kind: "ClusterProviderClass",
+			Name: "aws",
+		},
+	}
+
+	out := spec.DeepCopy()
+	if out.RuntimeRef == nil {
+		t.Fatalf("expected RuntimeRef to be copied")
+	}
+	if out.RuntimeRef.Name != "aws" || out.RuntimeRef.Kind != "ClusterProviderClass" {
+		t.Fatalf("unexpected RuntimeRef copy: %#v", out.RuntimeRef)
+	}
+	if out.RuntimeRef == spec.RuntimeRef {
+		t.Fatalf("expected RuntimeRef to be deep-copied")
+	}
+}
+
+func TestSecretStoreSpecDeepCopyPreservesProviderClassRuntimeRef(t *testing.T) {
+	spec := &SecretStoreSpec{
+		RuntimeRef: &StoreRuntimeRef{
+			Kind: "ProviderClass",
+			Name: "aws",
+		},
+	}
+
+	out := spec.DeepCopy()
+	if out.RuntimeRef == nil {
+		t.Fatalf("expected RuntimeRef to be copied")
+	}
+	if out.RuntimeRef.Name != "aws" || out.RuntimeRef.Kind != "ProviderClass" {
+		t.Fatalf("unexpected RuntimeRef copy: %#v", out.RuntimeRef)
+	}
+	if out.RuntimeRef == spec.RuntimeRef {
+		t.Fatalf("expected RuntimeRef to be deep-copied")
+	}
+}

+ 60 - 1
apis/externalsecrets/v1/secretstore_types.go

@@ -21,7 +21,57 @@ import (
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 )
 
+// StoreRuntimeRefKind constants define the supported runtime reference kinds.
+const (
+	StoreRuntimeRefKindProviderClass        = "ProviderClass"
+	StoreRuntimeRefKindClusterProviderClass = "ClusterProviderClass"
+)
+
+// StoreRuntimeRef identifies the runtime configuration used by a store.
+type StoreRuntimeRef struct {
+	// Kind identifies the runtime resource type referenced by this store.
+	// +kubebuilder:validation:Enum=ProviderClass;ClusterProviderClass
+	// +optional
+	Kind string `json:"kind,omitempty"`
+
+	// Name is the runtime resource name referenced by this store.
+	// +kubebuilder:validation:MinLength:=1
+	// +kubebuilder:validation:MaxLength:=253
+	// +kubebuilder:validation:Pattern:=^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
+	Name string `json:"name"`
+}
+
+// StoreProviderRef identifies the provider configuration used by a store.
+type StoreProviderRef struct {
+	// APIVersion identifies the API schema version for the provider resource.
+	// +kubebuilder:validation:Required
+	// +kubebuilder:validation:MinLength:=1
+	APIVersion string `json:"apiVersion"`
+
+	// Kind identifies the provider resource type referenced by this store.
+	// +kubebuilder:validation:Required
+	// +kubebuilder:validation:MinLength:=1
+	Kind string `json:"kind"`
+
+	// Name is the provider resource name referenced by this store.
+	// +kubebuilder:validation:Required
+	// +kubebuilder:validation:MinLength:=1
+	// +kubebuilder:validation:MaxLength:=253
+	// +kubebuilder:validation:Pattern:=^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
+	Name string `json:"name"`
+
+	// Namespace is the provider resource namespace referenced by this store.
+	// +optional
+	// +kubebuilder:validation:MinLength:=1
+	// +kubebuilder:validation:MaxLength:=63
+	// +kubebuilder:validation:Pattern:=^[a-z0-9]([-a-z0-9]*[a-z0-9])?$
+	Namespace string `json:"namespace,omitempty"`
+}
+
 // SecretStoreSpec defines the desired state of SecretStore.
+// +kubebuilder:validation:XValidation:rule="(has(self.provider) && !has(self.providerRef)) || (!has(self.provider) && has(self.providerRef))",message="exactly one of spec.provider or spec.providerRef must be set"
+// +kubebuilder:validation:XValidation:rule="!(has(self.provider) && has(self.runtimeRef))",message="spec.runtimeRef must be empty when spec.provider is set"
+// +kubebuilder:validation:XValidation:rule="!has(self.providerRef) || has(self.runtimeRef)",message="spec.runtimeRef is required when spec.providerRef is set"
 type SecretStoreSpec struct {
 	// Used to select the correct ESO controller (think: ingress.ingressClassName)
 	// The ESO controller is instantiated with a specific controller name and filters ES based on this property
@@ -29,7 +79,11 @@ type SecretStoreSpec struct {
 	Controller string `json:"controller,omitempty"`
 
 	// Used to configure the provider. Only one provider may be set
-	Provider *SecretStoreProvider `json:"provider"`
+	Provider *SecretStoreProvider `json:"provider,omitempty"`
+
+	// ProviderRef references a provider configuration managed externally.
+	// +optional
+	ProviderRef *StoreProviderRef `json:"providerRef,omitempty"`
 
 	// Used to configure HTTP retries on failures.
 	// +optional
@@ -42,6 +96,10 @@ type SecretStoreSpec struct {
 	// Used to constrain a ClusterSecretStore to specific namespaces. Relevant only to ClusterSecretStore.
 	// +optional
 	Conditions []ClusterSecretStoreCondition `json:"conditions,omitempty"`
+
+	// RuntimeRef points to runtime configuration for this store.
+	// +optional
+	RuntimeRef *StoreRuntimeRef `json:"runtimeRef,omitempty"`
 }
 
 // ClusterSecretStoreCondition describes a condition by which to choose namespaces to process ExternalSecrets in
@@ -275,6 +333,7 @@ type CAProvider struct {
 
 // SecretStoreRetrySettings defines the retry settings for accessing external secrets manager stores.
 type SecretStoreRetrySettings struct {
+	// +kubebuilder:validation:Format=""
 	MaxRetries    *int32  `json:"maxRetries,omitempty"`
 	RetryInterval *string `json:"retryInterval,omitempty"`
 }

+ 60 - 0
apis/externalsecrets/v1/secretstore_validator.go

@@ -74,6 +74,15 @@ func validateStore(store GenericStore) (admission.Warnings, error) {
 	if err := validateConditions(store); err != nil {
 		return nil, err
 	}
+	if err := validateProviderMode(store); err != nil {
+		return nil, err
+	}
+	if err := validateRuntimeRef(store); err != nil {
+		return nil, err
+	}
+	if store.GetSpec() != nil && store.GetSpec().ProviderRef != nil {
+		return nil, nil
+	}
 
 	provider, err := GetProvider(store)
 	if err != nil {
@@ -108,3 +117,54 @@ func validateConditions(store GenericStore) error {
 
 	return errs
 }
+
+func validateProviderMode(store GenericStore) error {
+	if store == nil || store.GetSpec() == nil {
+		return nil
+	}
+	spec := store.GetSpec()
+	hasProvider := spec.Provider != nil
+	hasProviderRef := spec.ProviderRef != nil
+	if hasProvider == hasProviderRef {
+		return fmt.Errorf("exactly one of spec.provider or spec.providerRef must be set")
+	}
+	if hasProvider {
+		if spec.RuntimeRef != nil {
+			return fmt.Errorf("spec.runtimeRef must be empty when spec.provider is set")
+		}
+		return nil
+	}
+	if spec.ProviderRef.APIVersion == "" {
+		return fmt.Errorf("spec.providerRef.apiVersion is required")
+	}
+	if spec.ProviderRef.Kind == "" {
+		return fmt.Errorf("spec.providerRef.kind is required")
+	}
+	if spec.ProviderRef.Name == "" {
+		return fmt.Errorf("spec.providerRef.name is required")
+	}
+	if spec.RuntimeRef == nil {
+		return fmt.Errorf("spec.runtimeRef is required when spec.providerRef is set")
+	}
+	if store.GetKind() == SecretStoreKind {
+		namespace := spec.ProviderRef.Namespace
+		if namespace != "" && namespace != store.GetObjectMeta().Namespace {
+			return fmt.Errorf("spec.providerRef.namespace must be empty or match metadata.namespace")
+		}
+	}
+	return nil
+}
+
+func validateRuntimeRef(store GenericStore) error {
+	if store == nil || store.GetSpec() == nil {
+		return nil
+	}
+	runtimeRef := store.GetSpec().RuntimeRef
+	if runtimeRef == nil {
+		return nil
+	}
+	if store.GetKind() == ClusterSecretStoreKind && runtimeRef.Kind == StoreRuntimeRefKindProviderClass {
+		return fmt.Errorf("%s runtimeRef.kind must not be %q", store.GetKind(), runtimeRef.Kind)
+	}
+	return nil
+}

+ 191 - 3
apis/externalsecrets/v1/secretstore_validator_test.go

@@ -22,12 +22,13 @@ import (
 
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
 )
 
 // ValidationProvider is a simple provider that we can use without cyclic import.
 type ValidationProvider struct {
-	Provider
+	ProviderInterface
 }
 
 func (v *ValidationProvider) ValidateStore(_ GenericStore) (admission.Warnings, error) {
@@ -143,7 +144,7 @@ func TestValidateSecretStore(t *testing.T) {
 			},
 		},
 		{
-			name: "no registered store backend",
+			name: "requires provider or providerRef",
 			obj: &SecretStore{
 				Spec: SecretStoreSpec{
 					Conditions: []ClusterSecretStoreCondition{
@@ -154,7 +155,135 @@ func TestValidateSecretStore(t *testing.T) {
 				},
 			},
 			assertErr: func(t *testing.T, err error) {
-				assert.EqualError(t, err, "store error for : secret stores must only have exactly one backend specified, found 0")
+				assert.EqualError(t, err, "exactly one of spec.provider or spec.providerRef must be set")
+			},
+			assertWarns: func(t *testing.T, warns admission.Warnings) {
+				require.Equal(t, 0, len(warns))
+			},
+		},
+		{
+			name: "rejects provider and providerRef together",
+			obj: &SecretStore{
+				Spec: SecretStoreSpec{
+					Provider: &SecretStoreProvider{
+						AWS: &AWSProvider{},
+					},
+					ProviderRef: &StoreProviderRef{
+						Name: "aws",
+					},
+				},
+			},
+			assertErr: func(t *testing.T, err error) {
+				assert.EqualError(t, err, "exactly one of spec.provider or spec.providerRef must be set")
+			},
+			assertWarns: func(t *testing.T, warns admission.Warnings) {
+				require.Equal(t, 0, len(warns))
+			},
+		},
+		{
+			name: "rejects provider with runtimeRef",
+			obj: &SecretStore{
+				Spec: SecretStoreSpec{
+					Provider: &SecretStoreProvider{
+						AWS: &AWSProvider{},
+					},
+					RuntimeRef: &StoreRuntimeRef{
+						Kind: "ProviderClass",
+						Name: "aws",
+					},
+				},
+			},
+			assertErr: func(t *testing.T, err error) {
+				assert.EqualError(t, err, "spec.runtimeRef must be empty when spec.provider is set")
+			},
+			assertWarns: func(t *testing.T, warns admission.Warnings) {
+				require.Equal(t, 0, len(warns))
+			},
+		},
+		{
+			name: "rejects providerRef without runtimeRef",
+			obj: &SecretStore{
+				Spec: SecretStoreSpec{
+					ProviderRef: &StoreProviderRef{
+						APIVersion: "external-secrets.io/v1",
+						Kind:       "Provider",
+						Name:       "aws",
+					},
+				},
+			},
+			assertErr: func(t *testing.T, err error) {
+				assert.EqualError(t, err, "spec.runtimeRef is required when spec.providerRef is set")
+			},
+			assertWarns: func(t *testing.T, warns admission.Warnings) {
+				require.Equal(t, 0, len(warns))
+			},
+		},
+		{
+			name: "rejects providerRef namespace mismatch",
+			obj: &SecretStore{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      "store",
+					Namespace: "default",
+				},
+				Spec: SecretStoreSpec{
+					ProviderRef: &StoreProviderRef{
+						APIVersion: "external-secrets.io/v1",
+						Kind:       "Provider",
+						Name:       "aws",
+						Namespace:  "other",
+					},
+					RuntimeRef: &StoreRuntimeRef{
+						Kind: "ProviderClass",
+						Name: "aws",
+					},
+				},
+			},
+			assertErr: func(t *testing.T, err error) {
+				assert.EqualError(t, err, "spec.providerRef.namespace must be empty or match metadata.namespace")
+			},
+			assertWarns: func(t *testing.T, warns admission.Warnings) {
+				require.Equal(t, 0, len(warns))
+			},
+		},
+		{
+			name: "allows providerRef with runtimeRef",
+			obj: &SecretStore{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      "store",
+					Namespace: "default",
+				},
+				Spec: SecretStoreSpec{
+					ProviderRef: &StoreProviderRef{
+						APIVersion: "external-secrets.io/v1",
+						Kind:       "Provider",
+						Name:       "aws",
+					},
+					RuntimeRef: &StoreRuntimeRef{
+						Kind: "ProviderClass",
+						Name: "aws",
+					},
+				},
+			},
+			assertErr: func(t *testing.T, err error) {
+				require.NoError(t, err)
+			},
+			assertWarns: func(t *testing.T, warns admission.Warnings) {
+				require.Equal(t, 0, len(warns))
+			},
+		},
+		{
+			name: "rejects empty providerRef",
+			obj: &SecretStore{
+				Spec: SecretStoreSpec{
+					ProviderRef: &StoreProviderRef{},
+					RuntimeRef: &StoreRuntimeRef{
+						Kind: "ProviderClass",
+						Name: "aws",
+					},
+				},
+			},
+			assertErr: func(t *testing.T, err error) {
+				assert.EqualError(t, err, "spec.providerRef.apiVersion is required")
 			},
 			assertWarns: func(t *testing.T, warns admission.Warnings) {
 				require.Equal(t, 0, len(warns))
@@ -200,3 +329,62 @@ func TestValidateSecretStore(t *testing.T) {
 		})
 	}
 }
+
+func TestValidateStoreRejectsProviderClassForClusterSecretStore(t *testing.T) {
+	store := &ClusterSecretStore{
+		Spec: SecretStoreSpec{
+			ProviderRef: &StoreProviderRef{
+				APIVersion: "external-secrets.io/v1",
+				Kind:       "Provider",
+				Name:       "aws",
+			},
+			RuntimeRef: &StoreRuntimeRef{
+				Kind: "ProviderClass",
+				Name: "aws",
+			},
+		},
+	}
+
+	warns, err := validateStore(store)
+	require.Error(t, err)
+	assert.EqualError(t, err, "ClusterSecretStore runtimeRef.kind must not be \"ProviderClass\"")
+	require.Len(t, warns, 0)
+}
+
+func TestValidateClusterSecretStoreAllowsProviderRefRuntimeRef(t *testing.T) {
+	store := &ClusterSecretStore{
+		Spec: SecretStoreSpec{
+			ProviderRef: &StoreProviderRef{
+				APIVersion: "external-secrets.io/v1",
+				Kind:       "Provider",
+				Name:       "aws",
+			},
+			RuntimeRef: &StoreRuntimeRef{
+				Kind: "ClusterProviderClass",
+				Name: "aws",
+			},
+		},
+	}
+
+	warns, err := validateStore(store)
+	require.NoError(t, err)
+	require.Len(t, warns, 0)
+}
+
+func TestValidateStoreSkipsInlineProviderValidationForProviderRefMode(t *testing.T) {
+	store := &SecretStore{
+		ObjectMeta: metav1.ObjectMeta{Namespace: "team-a"},
+		Spec: SecretStoreSpec{
+			RuntimeRef: &StoreRuntimeRef{Name: "fake-runtime"},
+			ProviderRef: &StoreProviderRef{
+				APIVersion: "provider.external-secrets.io/v2alpha1",
+				Kind:       "Fake",
+				Name:       "fake-config",
+			},
+		},
+	}
+
+	warns, err := validateStore(store)
+	require.NoError(t, err)
+	require.Empty(t, warns)
+}

+ 1 - 0
apis/externalsecrets/v1/secretstore_vault_types.go

@@ -301,6 +301,7 @@ type VaultKubernetesServiceAccountTokenAuth struct {
 	// Deprecated: this will be removed in the future.
 	// Defaults to 10 minutes.
 	// +optional
+	// +kubebuilder:validation:Format=""
 	ExpirationSeconds *int64 `json:"expirationSeconds,omitempty"`
 }
 

+ 22 - 0
apis/externalsecrets/v1/secretstore_webhook.go

@@ -17,12 +17,15 @@ limitations under the License.
 package v1
 
 import (
+	"context"
+
 	ctrl "sigs.k8s.io/controller-runtime"
 )
 
 // SetupWebhookWithManager registers the SecretStore webhook with the controller manager.
 func (c *SecretStore) SetupWebhookWithManager(mgr ctrl.Manager) error {
 	return ctrl.NewWebhookManagedBy(mgr, c).
+		WithDefaulter(&secretStoreDefaulter{}).
 		WithValidator(&GenericStoreValidator{}).
 		Complete()
 }
@@ -30,6 +33,25 @@ func (c *SecretStore) SetupWebhookWithManager(mgr ctrl.Manager) error {
 // SetupWebhookWithManager registers the ClusterSecretStore webhook with the controller manager.
 func (c *ClusterSecretStore) SetupWebhookWithManager(mgr ctrl.Manager) error {
 	return ctrl.NewWebhookManagedBy(mgr, c).
+		WithDefaulter(&clusterSecretStoreDefaulter{}).
 		WithValidator(&GenericClusterStoreValidator{}).
 		Complete()
 }
+
+type secretStoreDefaulter struct{}
+
+func (d *secretStoreDefaulter) Default(_ context.Context, store *SecretStore) error {
+	if store.Spec.RuntimeRef != nil && store.Spec.RuntimeRef.Kind == "" {
+		store.Spec.RuntimeRef.Kind = StoreRuntimeRefKindProviderClass
+	}
+	return nil
+}
+
+type clusterSecretStoreDefaulter struct{}
+
+func (d *clusterSecretStoreDefaulter) Default(_ context.Context, store *ClusterSecretStore) error {
+	if store.Spec.RuntimeRef != nil && store.Spec.RuntimeRef.Kind == "" {
+		store.Spec.RuntimeRef.Kind = StoreRuntimeRefKindClusterProviderClass
+	}
+	return nil
+}

+ 82 - 0
apis/externalsecrets/v1/secretstore_webhook_test.go

@@ -0,0 +1,82 @@
+/*
+Copyright © The ESO Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    https://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 v1
+
+import (
+	"context"
+	"testing"
+
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+func TestSecretStoreDefaulterDefaultsRuntimeRefKindToProviderClass(t *testing.T) {
+	store := &SecretStore{
+		Spec: SecretStoreSpec{
+			RuntimeRef: &StoreRuntimeRef{
+				Name: "aws",
+			},
+		},
+	}
+
+	if err := (&secretStoreDefaulter{}).Default(context.Background(), store); err != nil {
+		t.Fatalf("Default() error = %v", err)
+	}
+	if store.Spec.RuntimeRef.Kind != "ProviderClass" {
+		t.Fatalf("expected runtimeRef.kind to default to ProviderClass, got %q", store.Spec.RuntimeRef.Kind)
+	}
+}
+
+func TestClusterSecretStoreDefaulterDefaultsRuntimeRefKindToClusterProviderClass(t *testing.T) {
+	store := &ClusterSecretStore{
+		Spec: SecretStoreSpec{
+			RuntimeRef: &StoreRuntimeRef{
+				Name: "aws",
+			},
+		},
+	}
+
+	if err := (&clusterSecretStoreDefaulter{}).Default(context.Background(), store); err != nil {
+		t.Fatalf("Default() error = %v", err)
+	}
+	if store.Spec.RuntimeRef.Kind != "ClusterProviderClass" {
+		t.Fatalf("expected runtimeRef.kind to default to ClusterProviderClass, got %q", store.Spec.RuntimeRef.Kind)
+	}
+}
+
+func TestSecretStoreDefaulterDoesNotDefaultProviderRefNamespace(t *testing.T) {
+	store := &SecretStore{
+		ObjectMeta: metav1.ObjectMeta{
+			Namespace: "default",
+		},
+		Spec: SecretStoreSpec{
+			ProviderRef: &StoreProviderRef{
+				Name:      "aws",
+				Namespace: "other",
+			},
+			RuntimeRef: &StoreRuntimeRef{
+				Name: "aws",
+			},
+		},
+	}
+
+	if err := (&secretStoreDefaulter{}).Default(context.Background(), store); err != nil {
+		t.Fatalf("Default() error = %v", err)
+	}
+	if store.Spec.ProviderRef.Namespace != "other" {
+		t.Fatalf("expected providerRef.namespace to remain %q, got %q", "other", store.Spec.ProviderRef.Namespace)
+	}
+}

+ 40 - 0
apis/externalsecrets/v1/zz_generated.deepcopy.go

@@ -3829,6 +3829,11 @@ func (in *SecretStoreSpec) DeepCopyInto(out *SecretStoreSpec) {
 		*out = new(SecretStoreProvider)
 		(*in).DeepCopyInto(*out)
 	}
+	if in.ProviderRef != nil {
+		in, out := &in.ProviderRef, &out.ProviderRef
+		*out = new(StoreProviderRef)
+		**out = **in
+	}
 	if in.RetrySettings != nil {
 		in, out := &in.RetrySettings, &out.RetrySettings
 		*out = new(SecretStoreRetrySettings)
@@ -3841,6 +3846,11 @@ func (in *SecretStoreSpec) DeepCopyInto(out *SecretStoreSpec) {
 			(*in)[i].DeepCopyInto(&(*out)[i])
 		}
 	}
+	if in.RuntimeRef != nil {
+		in, out := &in.RuntimeRef, &out.RuntimeRef
+		*out = new(StoreRuntimeRef)
+		**out = **in
+	}
 }
 
 // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SecretStoreSpec.
@@ -3963,6 +3973,36 @@ func (in *StoreGeneratorSourceRef) DeepCopy() *StoreGeneratorSourceRef {
 	return out
 }
 
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *StoreProviderRef) DeepCopyInto(out *StoreProviderRef) {
+	*out = *in
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StoreProviderRef.
+func (in *StoreProviderRef) DeepCopy() *StoreProviderRef {
+	if in == nil {
+		return nil
+	}
+	out := new(StoreProviderRef)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *StoreRuntimeRef) DeepCopyInto(out *StoreRuntimeRef) {
+	*out = *in
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StoreRuntimeRef.
+func (in *StoreRuntimeRef) DeepCopy() *StoreRuntimeRef {
+	if in == nil {
+		return nil
+	}
+	out := new(StoreRuntimeRef)
+	in.DeepCopyInto(out)
+	return out
+}
+
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *StoreSourceRef) DeepCopyInto(out *StoreSourceRef) {
 	*out = *in

+ 53 - 0
apis/externalsecrets/v1alpha1/clusterproviderclass_types.go

@@ -0,0 +1,53 @@
+/*
+Copyright © The ESO Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    https://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package v1alpha1
+
+import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+
+// ClusterProviderClassSpec defines the desired state of ClusterProviderClass.
+type ClusterProviderClassSpec struct {
+	// +kubebuilder:validation:MinLength:=1
+	Address string `json:"address"`
+}
+
+// ClusterProviderClassStatus defines the observed state of ClusterProviderClass.
+type ClusterProviderClassStatus struct {
+	Conditions []metav1.Condition `json:"conditions,omitempty"`
+}
+
+// +kubebuilder:object:root=true
+// +kubebuilder:subresource:status
+// +kubebuilder:resource:scope=Cluster,categories={externalsecrets},shortName=cpc
+// +kubebuilder:printcolumn:name="Address",type=string,JSONPath=`.spec.address`
+
+// ClusterProviderClass is a cluster-scoped store runtime class.
+type ClusterProviderClass struct {
+	metav1.TypeMeta   `json:",inline"`
+	metav1.ObjectMeta `json:"metadata,omitempty"`
+
+	Spec   ClusterProviderClassSpec   `json:"spec"`
+	Status ClusterProviderClassStatus `json:"status,omitempty"`
+}
+
+// +kubebuilder:object:root=true
+
+// ClusterProviderClassList contains a list of ClusterProviderClass.
+type ClusterProviderClassList struct {
+	metav1.TypeMeta `json:",inline"`
+	metav1.ListMeta `json:"metadata,omitempty"`
+	Items           []ClusterProviderClass `json:"items"`
+}

+ 53 - 0
apis/externalsecrets/v1alpha1/providerclass_types.go

@@ -0,0 +1,53 @@
+/*
+Copyright © The ESO Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    https://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package v1alpha1
+
+import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+
+// ProviderClassSpec defines the desired state of ProviderClass.
+type ProviderClassSpec struct {
+	// +kubebuilder:validation:MinLength:=1
+	Address string `json:"address"`
+}
+
+// ProviderClassStatus defines the observed state of ProviderClass.
+type ProviderClassStatus struct {
+	Conditions []metav1.Condition `json:"conditions,omitempty"`
+}
+
+// +kubebuilder:object:root=true
+// +kubebuilder:subresource:status
+// +kubebuilder:resource:categories={externalsecrets},shortName=pc
+// +kubebuilder:printcolumn:name="Address",type=string,JSONPath=`.spec.address`
+
+// ProviderClass is a namespaced store runtime class.
+type ProviderClass struct {
+	metav1.TypeMeta   `json:",inline"`
+	metav1.ObjectMeta `json:"metadata,omitempty"`
+
+	Spec   ProviderClassSpec   `json:"spec"`
+	Status ProviderClassStatus `json:"status,omitempty"`
+}
+
+// +kubebuilder:object:root=true
+
+// ProviderClassList contains a list of ProviderClass.
+type ProviderClassList struct {
+	metav1.TypeMeta `json:",inline"`
+	metav1.ListMeta `json:"metadata,omitempty"`
+	Items           []ProviderClass `json:"items"`
+}

+ 71 - 0
apis/externalsecrets/v1alpha1/providerclass_types_test.go

@@ -0,0 +1,71 @@
+/*
+Copyright © The ESO Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    https://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 (
+	"slices"
+	"testing"
+
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/runtime"
+)
+
+func TestAddToSchemeRegistersProviderClass(t *testing.T) {
+	scheme := runtime.NewScheme()
+	if err := AddToScheme(scheme); err != nil {
+		t.Fatalf("AddToScheme: %v", err)
+	}
+
+	gvks, _, err := scheme.ObjectKinds(&ProviderClass{})
+	if err != nil {
+		t.Fatalf("ObjectKinds: %v", err)
+	}
+	expected := SchemeGroupVersion.WithKind("ProviderClass")
+	if !slices.Contains(gvks, expected) {
+		t.Fatalf("expected scheme to register %s, got %v", expected, gvks)
+	}
+}
+
+func TestProviderClassDeepCopyCopiesStatus(t *testing.T) {
+	original := &ProviderClass{
+		Status: ProviderClassStatus{
+			Conditions: []metav1.Condition{
+				{
+					Type:   "Ready",
+					Status: metav1.ConditionTrue,
+					Reason: "Configured",
+				},
+			},
+		},
+	}
+
+	copied := original.DeepCopy()
+	if copied == nil {
+		t.Fatalf("expected DeepCopy to return a value")
+	}
+	if len(copied.Status.Conditions) != 1 {
+		t.Fatalf("expected copied conditions, got %v", copied.Status.Conditions)
+	}
+	if copied.Status.Conditions[0].Reason != "Configured" {
+		t.Fatalf("unexpected copied condition: %#v", copied.Status.Conditions[0])
+	}
+
+	original.Status.Conditions[0].Reason = "Updated"
+	if copied.Status.Conditions[0].Reason == "Updated" {
+		t.Fatalf("expected conditions to be deep-copied")
+	}
+}

+ 6 - 2
apis/externalsecrets/v1alpha1/pushsecret_types.go

@@ -47,11 +47,15 @@ type PushSecretStoreRef struct {
 	// +optional
 	LabelSelector *metav1.LabelSelector `json:"labelSelector,omitempty"`
 
-	// Kind of the SecretStore resource (SecretStore or ClusterSecretStore)
+	// Kind of the SecretStore resource (SecretStore, ClusterSecretStore)
 	// +optional
-	// +kubebuilder:default="SecretStore"
 	// +kubebuilder:validation:Enum=SecretStore;ClusterSecretStore
 	Kind string `json:"kind,omitempty"`
+
+	// APIVersion of the referenced store resource.
+	// This field is optional and depends on the selected store kind.
+	// +optional
+	APIVersion string `json:"apiVersion,omitempty"`
 }
 
 // PushSecretUpdatePolicy defines how push secrets are updated in the provider.

+ 2 - 0
apis/externalsecrets/v1alpha1/register.go

@@ -62,6 +62,8 @@ var (
 )
 
 func init() {
+	SchemeBuilder.Register(&ClusterProviderClass{}, &ClusterProviderClassList{})
+	SchemeBuilder.Register(&ProviderClass{}, &ProviderClassList{})
 	SchemeBuilder.Register(&PushSecret{}, &PushSecretList{})
 	SchemeBuilder.Register(&ClusterPushSecret{}, &ClusterPushSecretList{})
 }

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

@@ -27,6 +27,102 @@ import (
 	runtime "k8s.io/apimachinery/pkg/runtime"
 )
 
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *ClusterProviderClass) DeepCopyInto(out *ClusterProviderClass) {
+	*out = *in
+	out.TypeMeta = in.TypeMeta
+	in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
+	out.Spec = in.Spec
+	in.Status.DeepCopyInto(&out.Status)
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterProviderClass.
+func (in *ClusterProviderClass) DeepCopy() *ClusterProviderClass {
+	if in == nil {
+		return nil
+	}
+	out := new(ClusterProviderClass)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *ClusterProviderClass) DeepCopyObject() runtime.Object {
+	if c := in.DeepCopy(); c != nil {
+		return c
+	}
+	return nil
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *ClusterProviderClassList) DeepCopyInto(out *ClusterProviderClassList) {
+	*out = *in
+	out.TypeMeta = in.TypeMeta
+	in.ListMeta.DeepCopyInto(&out.ListMeta)
+	if in.Items != nil {
+		in, out := &in.Items, &out.Items
+		*out = make([]ClusterProviderClass, len(*in))
+		for i := range *in {
+			(*in)[i].DeepCopyInto(&(*out)[i])
+		}
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterProviderClassList.
+func (in *ClusterProviderClassList) DeepCopy() *ClusterProviderClassList {
+	if in == nil {
+		return nil
+	}
+	out := new(ClusterProviderClassList)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *ClusterProviderClassList) DeepCopyObject() runtime.Object {
+	if c := in.DeepCopy(); c != nil {
+		return c
+	}
+	return nil
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *ClusterProviderClassSpec) DeepCopyInto(out *ClusterProviderClassSpec) {
+	*out = *in
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterProviderClassSpec.
+func (in *ClusterProviderClassSpec) DeepCopy() *ClusterProviderClassSpec {
+	if in == nil {
+		return nil
+	}
+	out := new(ClusterProviderClassSpec)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *ClusterProviderClassStatus) DeepCopyInto(out *ClusterProviderClassStatus) {
+	*out = *in
+	if in.Conditions != nil {
+		in, out := &in.Conditions, &out.Conditions
+		*out = make([]v1.Condition, len(*in))
+		for i := range *in {
+			(*in)[i].DeepCopyInto(&(*out)[i])
+		}
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterProviderClassStatus.
+func (in *ClusterProviderClassStatus) DeepCopy() *ClusterProviderClassStatus {
+	if in == nil {
+		return nil
+	}
+	out := new(ClusterProviderClassStatus)
+	in.DeepCopyInto(out)
+	return out
+}
+
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *ClusterPushSecret) DeepCopyInto(out *ClusterPushSecret) {
 	*out = *in
@@ -191,6 +287,102 @@ func (in *ClusterPushSecretStatus) DeepCopy() *ClusterPushSecretStatus {
 	return out
 }
 
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *ProviderClass) DeepCopyInto(out *ProviderClass) {
+	*out = *in
+	out.TypeMeta = in.TypeMeta
+	in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
+	out.Spec = in.Spec
+	in.Status.DeepCopyInto(&out.Status)
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ProviderClass.
+func (in *ProviderClass) DeepCopy() *ProviderClass {
+	if in == nil {
+		return nil
+	}
+	out := new(ProviderClass)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *ProviderClass) DeepCopyObject() runtime.Object {
+	if c := in.DeepCopy(); c != nil {
+		return c
+	}
+	return nil
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *ProviderClassList) DeepCopyInto(out *ProviderClassList) {
+	*out = *in
+	out.TypeMeta = in.TypeMeta
+	in.ListMeta.DeepCopyInto(&out.ListMeta)
+	if in.Items != nil {
+		in, out := &in.Items, &out.Items
+		*out = make([]ProviderClass, len(*in))
+		for i := range *in {
+			(*in)[i].DeepCopyInto(&(*out)[i])
+		}
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ProviderClassList.
+func (in *ProviderClassList) DeepCopy() *ProviderClassList {
+	if in == nil {
+		return nil
+	}
+	out := new(ProviderClassList)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *ProviderClassList) DeepCopyObject() runtime.Object {
+	if c := in.DeepCopy(); c != nil {
+		return c
+	}
+	return nil
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *ProviderClassSpec) DeepCopyInto(out *ProviderClassSpec) {
+	*out = *in
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ProviderClassSpec.
+func (in *ProviderClassSpec) DeepCopy() *ProviderClassSpec {
+	if in == nil {
+		return nil
+	}
+	out := new(ProviderClassSpec)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *ProviderClassStatus) DeepCopyInto(out *ProviderClassStatus) {
+	*out = *in
+	if in.Conditions != nil {
+		in, out := &in.Conditions, &out.Conditions
+		*out = make([]v1.Condition, len(*in))
+		for i := range *in {
+			(*in)[i].DeepCopyInto(&(*out)[i])
+		}
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ProviderClassStatus.
+func (in *ProviderClassStatus) DeepCopy() *ProviderClassStatus {
+	if in == nil {
+		return nil
+	}
+	out := new(ProviderClassStatus)
+	in.DeepCopyInto(out)
+	return out
+}
+
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *PushSecret) DeepCopyInto(out *PushSecret) {
 	*out = *in

+ 2 - 2
apis/externalsecrets/v1beta1/externalsecret_types.go

@@ -29,10 +29,10 @@ type SecretStoreRef struct {
 	// +kubebuilder:validation:Pattern:=^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
 	Name string `json:"name,omitempty"`
 
-	// Kind of the SecretStore resource (SecretStore or ClusterSecretStore)
+	// Kind of the SecretStore resource (SecretStore, ClusterSecretStore)
 	// Defaults to `SecretStore`
 	// +optional
-	// +kubebuilder:validation:Enum=SecretStore;ClusterSecretStore
+	// +kubebuilder:validation:Enum=SecretStore;ClusterSecretStore;
 	Kind string `json:"kind,omitempty"`
 }
 

+ 2 - 2
apis/externalsecrets/v1beta1/provider.go

@@ -47,8 +47,8 @@ func (v ValidationResult) String() string {
 // +k8s:deepcopy-gen:interfaces=nil
 // +k8s:deepcopy-gen=nil
 
-// Provider is a common interface for interacting with secret backends.
-type Provider interface {
+// ProviderInterface is a common interface for interacting with secret backends.
+type ProviderInterface interface {
 	// NewClient constructs a SecretsManager Provider
 	NewClient(ctx context.Context, store GenericStore, kube client.Client, namespace string) (SecretsClient, error)
 

+ 6 - 6
apis/externalsecrets/v1beta1/provider_schema.go

@@ -23,16 +23,16 @@ import (
 	"sync"
 )
 
-var builder map[string]Provider
+var builder map[string]ProviderInterface
 var buildlock sync.RWMutex
 
 func init() {
-	builder = make(map[string]Provider)
+	builder = make(map[string]ProviderInterface)
 }
 
 // Register a store backend type. Register panics if a
 // backend with the same store is already registered.
-func Register(s Provider, storeSpec *SecretStoreProvider) {
+func Register(s ProviderInterface, storeSpec *SecretStoreProvider) {
 	storeName, err := getProviderName(storeSpec)
 	if err != nil {
 		panic(fmt.Sprintf("store error registering schema: %s", err.Error()))
@@ -50,7 +50,7 @@ func Register(s Provider, storeSpec *SecretStoreProvider) {
 
 // ForceRegister adds to store schema, overwriting a store if
 // already registered. Should only be used for testing.
-func ForceRegister(s Provider, storeSpec *SecretStoreProvider) {
+func ForceRegister(s ProviderInterface, storeSpec *SecretStoreProvider) {
 	storeName, err := getProviderName(storeSpec)
 	if err != nil {
 		panic(fmt.Sprintf("store error registering schema: %s", err.Error()))
@@ -62,7 +62,7 @@ func ForceRegister(s Provider, storeSpec *SecretStoreProvider) {
 }
 
 // GetProviderByName returns the provider implementation by name.
-func GetProviderByName(name string) (Provider, bool) {
+func GetProviderByName(name string) (ProviderInterface, bool) {
 	buildlock.RLock()
 	f, ok := builder[name]
 	buildlock.RUnlock()
@@ -70,7 +70,7 @@ func GetProviderByName(name string) (Provider, bool) {
 }
 
 // GetProvider returns the provider from the generic store.
-func GetProvider(s GenericStore) (Provider, error) {
+func GetProvider(s GenericStore) (ProviderInterface, error) {
 	if s == nil {
 		return nil, nil
 	}

+ 1 - 0
apis/externalsecrets/v1beta1/secretstore_aws_types.go

@@ -82,6 +82,7 @@ type SecretsManager struct {
 	// then by default Secrets Manager uses a 30 day recovery window.
 	// see: https://docs.aws.amazon.com/secretsmanager/latest/apireference/API_DeleteSecret.html#SecretsManager-DeleteSecret-request-RecoveryWindowInDays
 	// +optional
+	// +kubebuilder:validation:Format=""
 	RecoveryWindowInDays int64 `json:"recoveryWindowInDays,omitempty"`
 }
 

+ 2 - 0
apis/externalsecrets/v1beta1/secretstore_github_types.go

@@ -32,9 +32,11 @@ type GithubProvider struct {
 	Auth GithubAppAuth `json:"auth"`
 
 	// appID specifies the Github APP that will be used to authenticate the client
+	// +kubebuilder:validation:Format=""
 	AppID int64 `json:"appID"`
 
 	// installationID specifies the Github APP installation that will be used to authenticate the client
+	// +kubebuilder:validation:Format=""
 	InstallationID int64 `json:"installationID"`
 
 	// organization will be used to fetch secrets from the Github organization

+ 59 - 0
apis/externalsecrets/v1beta1/secretstore_runtime_ref_test.go

@@ -0,0 +1,59 @@
+/*
+Copyright © The ESO Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    https://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package v1beta1
+
+import "testing"
+
+func TestSecretStoreSpecDeepCopyPreservesProviderClassRuntimeRef(t *testing.T) {
+	spec := &SecretStoreSpec{
+		RuntimeRef: &StoreRuntimeRef{
+			Kind: "ProviderClass",
+			Name: "aws",
+		},
+	}
+
+	out := spec.DeepCopy()
+	if out.RuntimeRef == nil {
+		t.Fatalf("expected RuntimeRef to be copied")
+	}
+	if out.RuntimeRef.Name != "aws" || out.RuntimeRef.Kind != "ProviderClass" {
+		t.Fatalf("unexpected RuntimeRef copy: %#v", out.RuntimeRef)
+	}
+	if out.RuntimeRef == spec.RuntimeRef {
+		t.Fatalf("expected RuntimeRef to be deep-copied")
+	}
+}
+
+func TestSecretStoreSpecDeepCopyPreservesClusterProviderClassRuntimeRef(t *testing.T) {
+	spec := &SecretStoreSpec{
+		RuntimeRef: &StoreRuntimeRef{
+			Kind: "ClusterProviderClass",
+			Name: "aws",
+		},
+	}
+
+	out := spec.DeepCopy()
+	if out.RuntimeRef == nil {
+		t.Fatalf("expected RuntimeRef to be copied")
+	}
+	if out.RuntimeRef.Name != "aws" || out.RuntimeRef.Kind != "ClusterProviderClass" {
+		t.Fatalf("unexpected RuntimeRef copy: %#v", out.RuntimeRef)
+	}
+	if out.RuntimeRef == spec.RuntimeRef {
+		t.Fatalf("expected RuntimeRef to be deep-copied")
+	}
+}

+ 59 - 1
apis/externalsecrets/v1beta1/secretstore_types.go

@@ -21,7 +21,57 @@ import (
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 )
 
+// StoreRuntimeRefKind constants define the supported runtime reference kinds.
+const (
+	StoreRuntimeRefKindProviderClass        = "ProviderClass"
+	StoreRuntimeRefKindClusterProviderClass = "ClusterProviderClass"
+)
+
+// StoreRuntimeRef identifies the runtime configuration used by a store.
+type StoreRuntimeRef struct {
+	// Kind identifies the runtime resource type referenced by this store.
+	// +kubebuilder:validation:Enum=ProviderClass;ClusterProviderClass
+	// +optional
+	Kind string `json:"kind,omitempty"`
+
+	// Name is the runtime resource name referenced by this store.
+	// +kubebuilder:validation:MinLength:=1
+	// +kubebuilder:validation:MaxLength:=253
+	// +kubebuilder:validation:Pattern:=^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
+	Name string `json:"name"`
+}
+
+// StoreProviderRef identifies the provider configuration used by a store.
+type StoreProviderRef struct {
+	// APIVersion identifies the API schema version for the provider resource.
+	// +kubebuilder:validation:Required
+	// +kubebuilder:validation:MinLength:=1
+	APIVersion string `json:"apiVersion"`
+
+	// Kind identifies the provider resource type referenced by this store.
+	// +kubebuilder:validation:Required
+	// +kubebuilder:validation:MinLength:=1
+	Kind string `json:"kind"`
+
+	// Name is the provider resource name referenced by this store.
+	// +kubebuilder:validation:Required
+	// +kubebuilder:validation:MinLength:=1
+	// +kubebuilder:validation:MaxLength:=253
+	// +kubebuilder:validation:Pattern:=^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
+	Name string `json:"name"`
+
+	// Namespace is the provider resource namespace referenced by this store.
+	// +optional
+	// +kubebuilder:validation:MinLength:=1
+	// +kubebuilder:validation:MaxLength:=63
+	// +kubebuilder:validation:Pattern:=^[a-z0-9]([-a-z0-9]*[a-z0-9])?$
+	Namespace string `json:"namespace,omitempty"`
+}
+
 // SecretStoreSpec defines the desired state of SecretStore.
+// +kubebuilder:validation:XValidation:rule="(has(self.provider) && !has(self.providerRef)) || (!has(self.provider) && has(self.providerRef))",message="exactly one of spec.provider or spec.providerRef must be set"
+// +kubebuilder:validation:XValidation:rule="!(has(self.provider) && has(self.runtimeRef))",message="spec.runtimeRef must be empty when spec.provider is set"
+// +kubebuilder:validation:XValidation:rule="!has(self.providerRef) || has(self.runtimeRef)",message="spec.runtimeRef is required when spec.providerRef is set"
 type SecretStoreSpec struct {
 	// Used to select the correct ESO controller (think: ingress.ingressClassName)
 	// The ESO controller is instantiated with a specific controller name and filters ES based on this property
@@ -29,7 +79,11 @@ type SecretStoreSpec struct {
 	Controller string `json:"controller,omitempty"`
 
 	// Used to configure the provider. Only one provider may be set
-	Provider *SecretStoreProvider `json:"provider"`
+	Provider *SecretStoreProvider `json:"provider,omitempty"`
+
+	// ProviderRef references a provider configuration managed externally.
+	// +optional
+	ProviderRef *StoreProviderRef `json:"providerRef,omitempty"`
 
 	// Used to configure HTTP retries on failures.
 	// +optional
@@ -42,6 +96,10 @@ type SecretStoreSpec struct {
 	// Used to constrain a ClusterSecretStore to specific namespaces. Relevant only to ClusterSecretStore.
 	// +optional
 	Conditions []ClusterSecretStoreCondition `json:"conditions,omitempty"`
+
+	// RuntimeRef points to runtime configuration for this store.
+	// +optional
+	RuntimeRef *StoreRuntimeRef `json:"runtimeRef,omitempty"`
 }
 
 // ClusterSecretStoreCondition describes a condition by which to choose namespaces to process ExternalSecrets in

+ 60 - 0
apis/externalsecrets/v1beta1/secretstore_validator.go

@@ -68,6 +68,15 @@ func validateStore(store GenericStore) (admission.Warnings, error) {
 	if err := validateConditions(store); err != nil {
 		return nil, err
 	}
+	if err := validateProviderMode(store); err != nil {
+		return nil, err
+	}
+	if err := validateRuntimeRef(store); err != nil {
+		return nil, err
+	}
+	if store.GetSpec() != nil && store.GetSpec().ProviderRef != nil {
+		return nil, nil
+	}
 
 	provider, err := GetProvider(store)
 	if err != nil {
@@ -89,3 +98,54 @@ func validateConditions(store GenericStore) error {
 
 	return errs
 }
+
+func validateProviderMode(store GenericStore) error {
+	if store == nil || store.GetSpec() == nil {
+		return nil
+	}
+	spec := store.GetSpec()
+	hasProvider := spec.Provider != nil
+	hasProviderRef := spec.ProviderRef != nil
+	if hasProvider == hasProviderRef {
+		return fmt.Errorf("exactly one of spec.provider or spec.providerRef must be set")
+	}
+	if hasProvider {
+		if spec.RuntimeRef != nil {
+			return fmt.Errorf("spec.runtimeRef must be empty when spec.provider is set")
+		}
+		return nil
+	}
+	if spec.ProviderRef.APIVersion == "" {
+		return fmt.Errorf("spec.providerRef.apiVersion is required")
+	}
+	if spec.ProviderRef.Kind == "" {
+		return fmt.Errorf("spec.providerRef.kind is required")
+	}
+	if spec.ProviderRef.Name == "" {
+		return fmt.Errorf("spec.providerRef.name is required")
+	}
+	if spec.RuntimeRef == nil {
+		return fmt.Errorf("spec.runtimeRef is required when spec.providerRef is set")
+	}
+	if store.GetKind() == SecretStoreKind {
+		namespace := spec.ProviderRef.Namespace
+		if namespace != "" && namespace != store.GetObjectMeta().Namespace {
+			return fmt.Errorf("spec.providerRef.namespace must be empty or match metadata.namespace")
+		}
+	}
+	return nil
+}
+
+func validateRuntimeRef(store GenericStore) error {
+	if store == nil || store.GetSpec() == nil {
+		return nil
+	}
+	runtimeRef := store.GetSpec().RuntimeRef
+	if runtimeRef == nil {
+		return nil
+	}
+	if store.GetKind() == ClusterSecretStoreKind && runtimeRef.Kind == StoreRuntimeRefKindProviderClass {
+		return fmt.Errorf("%s runtimeRef.kind must not be %q", store.GetKind(), runtimeRef.Kind)
+	}
+	return nil
+}

+ 170 - 3
apis/externalsecrets/v1beta1/secretstore_validator_test.go

@@ -21,12 +21,13 @@ import (
 
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
 )
 
 // ValidationProvider is a simple provider that we can use without cyclic import.
 type ValidationProvider struct {
-	Provider
+	ProviderInterface
 }
 
 func (v *ValidationProvider) ValidateStore(_ GenericStore) (admission.Warnings, error) {
@@ -128,7 +129,7 @@ func TestValidateSecretStore(t *testing.T) {
 			},
 		},
 		{
-			name: "no registered store backend",
+			name: "requires provider or providerRef",
 			obj: &SecretStore{
 				Spec: SecretStoreSpec{
 					Conditions: []ClusterSecretStoreCondition{
@@ -139,7 +140,117 @@ func TestValidateSecretStore(t *testing.T) {
 				},
 			},
 			assertErr: func(t *testing.T, err error) {
-				assert.EqualError(t, err, "store error for : secret stores must only have exactly one backend specified, found 0")
+				assert.EqualError(t, err, "exactly one of spec.provider or spec.providerRef must be set")
+			},
+		},
+		{
+			name: "rejects provider and providerRef together",
+			obj: &SecretStore{
+				Spec: SecretStoreSpec{
+					Provider: &SecretStoreProvider{
+						AWS: &AWSProvider{},
+					},
+					ProviderRef: &StoreProviderRef{
+						Name: "aws",
+					},
+				},
+			},
+			assertErr: func(t *testing.T, err error) {
+				assert.EqualError(t, err, "exactly one of spec.provider or spec.providerRef must be set")
+			},
+		},
+		{
+			name: "rejects provider with runtimeRef",
+			obj: &SecretStore{
+				Spec: SecretStoreSpec{
+					Provider: &SecretStoreProvider{
+						AWS: &AWSProvider{},
+					},
+					RuntimeRef: &StoreRuntimeRef{
+						Kind: "ProviderClass",
+						Name: "aws",
+					},
+				},
+			},
+			assertErr: func(t *testing.T, err error) {
+				assert.EqualError(t, err, "spec.runtimeRef must be empty when spec.provider is set")
+			},
+		},
+		{
+			name: "rejects providerRef without runtimeRef",
+			obj: &SecretStore{
+				Spec: SecretStoreSpec{
+					ProviderRef: &StoreProviderRef{
+						APIVersion: "external-secrets.io/v1beta1",
+						Kind:       "Provider",
+						Name:       "aws",
+					},
+				},
+			},
+			assertErr: func(t *testing.T, err error) {
+				assert.EqualError(t, err, "spec.runtimeRef is required when spec.providerRef is set")
+			},
+		},
+		{
+			name: "rejects providerRef namespace mismatch",
+			obj: &SecretStore{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      "store",
+					Namespace: "default",
+				},
+				Spec: SecretStoreSpec{
+					ProviderRef: &StoreProviderRef{
+						APIVersion: "external-secrets.io/v1beta1",
+						Kind:       "Provider",
+						Name:       "aws",
+						Namespace:  "other",
+					},
+					RuntimeRef: &StoreRuntimeRef{
+						Kind: "ProviderClass",
+						Name: "aws",
+					},
+				},
+			},
+			assertErr: func(t *testing.T, err error) {
+				assert.EqualError(t, err, "spec.providerRef.namespace must be empty or match metadata.namespace")
+			},
+		},
+		{
+			name: "allows providerRef with runtimeRef",
+			obj: &SecretStore{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      "store",
+					Namespace: "default",
+				},
+				Spec: SecretStoreSpec{
+					ProviderRef: &StoreProviderRef{
+						APIVersion: "external-secrets.io/v1beta1",
+						Kind:       "Provider",
+						Name:       "aws",
+					},
+					RuntimeRef: &StoreRuntimeRef{
+						Kind: "ProviderClass",
+						Name: "aws",
+					},
+				},
+			},
+			assertErr: func(t *testing.T, err error) {
+				require.NoError(t, err)
+			},
+		},
+		{
+			name: "rejects empty providerRef",
+			obj: &SecretStore{
+				Spec: SecretStoreSpec{
+					ProviderRef: &StoreProviderRef{},
+					RuntimeRef: &StoreRuntimeRef{
+						Kind: "ProviderClass",
+						Name: "aws",
+					},
+				},
+			},
+			assertErr: func(t *testing.T, err error) {
+				assert.EqualError(t, err, "spec.providerRef.apiVersion is required")
 			},
 		},
 	}
@@ -154,3 +265,59 @@ func TestValidateSecretStore(t *testing.T) {
 		})
 	}
 }
+
+func TestValidateStoreRejectsProviderClassForClusterSecretStore(t *testing.T) {
+	store := &ClusterSecretStore{
+		Spec: SecretStoreSpec{
+			ProviderRef: &StoreProviderRef{
+				APIVersion: "external-secrets.io/v1beta1",
+				Kind:       "Provider",
+				Name:       "aws",
+			},
+			RuntimeRef: &StoreRuntimeRef{
+				Kind: "ProviderClass",
+				Name: "aws",
+			},
+		},
+	}
+
+	_, err := validateStore(store)
+	require.Error(t, err)
+	assert.EqualError(t, err, "ClusterSecretStore runtimeRef.kind must not be \"ProviderClass\"")
+}
+
+func TestValidateClusterSecretStoreAllowsProviderRefRuntimeRef(t *testing.T) {
+	store := &ClusterSecretStore{
+		Spec: SecretStoreSpec{
+			ProviderRef: &StoreProviderRef{
+				APIVersion: "external-secrets.io/v1beta1",
+				Kind:       "Provider",
+				Name:       "aws",
+			},
+			RuntimeRef: &StoreRuntimeRef{
+				Kind: "ClusterProviderClass",
+				Name: "aws",
+			},
+		},
+	}
+
+	_, err := validateStore(store)
+	require.NoError(t, err)
+}
+
+func TestValidateStoreSkipsInlineProviderValidationForProviderRefMode(t *testing.T) {
+	store := &SecretStore{
+		ObjectMeta: metav1.ObjectMeta{Namespace: "team-a"},
+		Spec: SecretStoreSpec{
+			RuntimeRef: &StoreRuntimeRef{Name: "fake-runtime"},
+			ProviderRef: &StoreProviderRef{
+				APIVersion: "provider.external-secrets.io/v2alpha1",
+				Kind:       "Fake",
+				Name:       "fake-config",
+			},
+		},
+	}
+
+	_, err := validateStore(store)
+	require.NoError(t, err)
+}

+ 1 - 0
apis/externalsecrets/v1beta1/secretstore_vault_types.go

@@ -290,6 +290,7 @@ type VaultKubernetesServiceAccountTokenAuth struct {
 	// Deprecated: this will be removed in the future.
 	// Defaults to 10 minutes.
 	// +optional
+	// +kubebuilder:validation:Format=""
 	ExpirationSeconds *int64 `json:"expirationSeconds,omitempty"`
 }
 

+ 22 - 0
apis/externalsecrets/v1beta1/secretstore_webhook.go

@@ -17,12 +17,15 @@ limitations under the License.
 package v1beta1
 
 import (
+	"context"
+
 	ctrl "sigs.k8s.io/controller-runtime"
 )
 
 // SetupWebhookWithManager configures the webhook manager for the SecretStore.
 func (c *SecretStore) SetupWebhookWithManager(mgr ctrl.Manager) error {
 	return ctrl.NewWebhookManagedBy(mgr, c).
+		WithDefaulter(&secretStoreDefaulter{}).
 		WithValidator(&GenericStoreValidator{}).
 		Complete()
 }
@@ -30,6 +33,25 @@ func (c *SecretStore) SetupWebhookWithManager(mgr ctrl.Manager) error {
 // SetupWebhookWithManager configures the webhook manager for the ClusterSecretStore.
 func (c *ClusterSecretStore) SetupWebhookWithManager(mgr ctrl.Manager) error {
 	return ctrl.NewWebhookManagedBy(mgr, c).
+		WithDefaulter(&clusterSecretStoreDefaulter{}).
 		WithValidator(&GenericClusterStoreValidator{}).
 		Complete()
 }
+
+type secretStoreDefaulter struct{}
+
+func (d *secretStoreDefaulter) Default(_ context.Context, store *SecretStore) error {
+	if store.Spec.RuntimeRef != nil && store.Spec.RuntimeRef.Kind == "" {
+		store.Spec.RuntimeRef.Kind = StoreRuntimeRefKindProviderClass
+	}
+	return nil
+}
+
+type clusterSecretStoreDefaulter struct{}
+
+func (d *clusterSecretStoreDefaulter) Default(_ context.Context, store *ClusterSecretStore) error {
+	if store.Spec.RuntimeRef != nil && store.Spec.RuntimeRef.Kind == "" {
+		store.Spec.RuntimeRef.Kind = StoreRuntimeRefKindClusterProviderClass
+	}
+	return nil
+}

+ 82 - 0
apis/externalsecrets/v1beta1/secretstore_webhook_test.go

@@ -0,0 +1,82 @@
+/*
+Copyright © The ESO Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    https://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package v1beta1
+
+import (
+	"context"
+	"testing"
+
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+func TestSecretStoreDefaulterDefaultsRuntimeRefKindToProviderClass(t *testing.T) {
+	store := &SecretStore{
+		Spec: SecretStoreSpec{
+			RuntimeRef: &StoreRuntimeRef{
+				Name: "aws",
+			},
+		},
+	}
+
+	if err := (&secretStoreDefaulter{}).Default(context.Background(), store); err != nil {
+		t.Fatalf("Default() error = %v", err)
+	}
+	if store.Spec.RuntimeRef.Kind != "ProviderClass" {
+		t.Fatalf("expected runtimeRef.kind to default to ProviderClass, got %q", store.Spec.RuntimeRef.Kind)
+	}
+}
+
+func TestClusterSecretStoreDefaulterDefaultsRuntimeRefKindToClusterProviderClass(t *testing.T) {
+	store := &ClusterSecretStore{
+		Spec: SecretStoreSpec{
+			RuntimeRef: &StoreRuntimeRef{
+				Name: "aws",
+			},
+		},
+	}
+
+	if err := (&clusterSecretStoreDefaulter{}).Default(context.Background(), store); err != nil {
+		t.Fatalf("Default() error = %v", err)
+	}
+	if store.Spec.RuntimeRef.Kind != "ClusterProviderClass" {
+		t.Fatalf("expected runtimeRef.kind to default to ClusterProviderClass, got %q", store.Spec.RuntimeRef.Kind)
+	}
+}
+
+func TestSecretStoreDefaulterDoesNotDefaultProviderRefNamespace(t *testing.T) {
+	store := &SecretStore{
+		ObjectMeta: metav1.ObjectMeta{
+			Namespace: "default",
+		},
+		Spec: SecretStoreSpec{
+			ProviderRef: &StoreProviderRef{
+				Name:      "aws",
+				Namespace: "other",
+			},
+			RuntimeRef: &StoreRuntimeRef{
+				Name: "aws",
+			},
+		},
+	}
+
+	if err := (&secretStoreDefaulter{}).Default(context.Background(), store); err != nil {
+		t.Fatalf("Default() error = %v", err)
+	}
+	if store.Spec.ProviderRef.Namespace != "other" {
+		t.Fatalf("expected providerRef.namespace to remain %q, got %q", "other", store.Spec.ProviderRef.Namespace)
+	}
+}

+ 40 - 0
apis/externalsecrets/v1beta1/zz_generated.deepcopy.go

@@ -2920,6 +2920,11 @@ func (in *SecretStoreSpec) DeepCopyInto(out *SecretStoreSpec) {
 		*out = new(SecretStoreProvider)
 		(*in).DeepCopyInto(*out)
 	}
+	if in.ProviderRef != nil {
+		in, out := &in.ProviderRef, &out.ProviderRef
+		*out = new(StoreProviderRef)
+		**out = **in
+	}
 	if in.RetrySettings != nil {
 		in, out := &in.RetrySettings, &out.RetrySettings
 		*out = new(SecretStoreRetrySettings)
@@ -2932,6 +2937,11 @@ func (in *SecretStoreSpec) DeepCopyInto(out *SecretStoreSpec) {
 			(*in)[i].DeepCopyInto(&(*out)[i])
 		}
 	}
+	if in.RuntimeRef != nil {
+		in, out := &in.RuntimeRef, &out.RuntimeRef
+		*out = new(StoreRuntimeRef)
+		**out = **in
+	}
 }
 
 // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SecretStoreSpec.
@@ -3054,6 +3064,36 @@ func (in *StoreGeneratorSourceRef) DeepCopy() *StoreGeneratorSourceRef {
 	return out
 }
 
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *StoreProviderRef) DeepCopyInto(out *StoreProviderRef) {
+	*out = *in
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StoreProviderRef.
+func (in *StoreProviderRef) DeepCopy() *StoreProviderRef {
+	if in == nil {
+		return nil
+	}
+	out := new(StoreProviderRef)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *StoreRuntimeRef) DeepCopyInto(out *StoreRuntimeRef) {
+	*out = *in
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StoreRuntimeRef.
+func (in *StoreRuntimeRef) DeepCopy() *StoreRuntimeRef {
+	if in == nil {
+		return nil
+	}
+	out := new(StoreRuntimeRef)
+	in.DeepCopyInto(out)
+	return out
+}
+
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *StoreSourceRef) DeepCopyInto(out *StoreSourceRef) {
 	*out = *in

+ 5 - 3
apis/generators/v1alpha1/types_grafana.go

@@ -75,9 +75,11 @@ type GrafanaServiceAccountTokenState struct {
 
 // GrafanaStateServiceAccount contains the service account ID, login and token ID.
 type GrafanaStateServiceAccount struct {
-	ServiceAccountID      *int64  `json:"id"`
-	ServiceAccountLogin   *string `json:"login"`
-	ServiceAccountTokenID *int64  `json:"tokenID"`
+	// +kubebuilder:validation:Format=""
+	ServiceAccountID    *int64  `json:"id"`
+	ServiceAccountLogin *string `json:"login"`
+	// +kubebuilder:validation:Format=""
+	ServiceAccountTokenID *int64 `json:"tokenID"`
 }
 
 // Grafana represents a generator for Grafana service account tokens.

+ 20 - 0
apis/provider/aws/v2alpha1/doc.go

@@ -0,0 +1,20 @@
+/*
+Copyright © The ESO Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    https://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 v2alpha1 contains API Schema definitions for the AWS provider v2alpha1 API group.
+// +kubebuilder:object:generate=true
+// +groupName=provider.external-secrets.io
+package v2alpha1

+ 39 - 0
apis/provider/aws/v2alpha1/groupversion_info.go

@@ -0,0 +1,39 @@
+/*
+Copyright © The ESO Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    https://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 v2alpha1
+
+import (
+	"k8s.io/apimachinery/pkg/runtime/schema"
+	"sigs.k8s.io/controller-runtime/pkg/scheme"
+)
+
+var (
+	// GroupVersion is group version used to register these objects.
+	GroupVersion = schema.GroupVersion{Group: "provider.external-secrets.io", Version: "v2alpha1"}
+
+	// SecretsManagerKind is the kind name used for SecretsManager resources.
+	SecretsManagerKind = "SecretsManager"
+
+	// ParameterStoreKind is the kind name used for ParameterStore resources.
+	ParameterStoreKind = "ParameterStore"
+
+	// SchemeBuilder is used to add go types to the GroupVersionKind scheme.
+	SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion}
+
+	// AddToScheme adds the types in this group-version to the given scheme.
+	AddToScheme = SchemeBuilder.AddToScheme
+)

+ 93 - 0
apis/provider/aws/v2alpha1/parameterstore_types.go

@@ -0,0 +1,93 @@
+/*
+Copyright © The ESO Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    https://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 v2alpha1
+
+import (
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+
+	v1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
+)
+
+// ParameterStoreSpec defines the desired state of ParameterStore.
+type ParameterStoreSpec struct {
+	// Auth defines the information necessary to authenticate against AWS
+	// if not set aws sdk will infer credentials from your environment
+	// see: https://docs.aws.amazon.com/sdk-for-go/v1/developer-guide/configuring-sdk.html#specifying-credentials
+	// +optional
+	Auth v1.AWSAuth `json:"auth,omitempty"`
+
+	// Role is a Role ARN which the provider will assume
+	// +optional
+	Role string `json:"role,omitempty"`
+
+	// AWS Region to be used for the provider
+	Region string `json:"region"`
+
+	// AdditionalRoles is a chained list of Role ARNs which the provider will sequentially assume before assuming the Role
+	// +optional
+	AdditionalRoles []string `json:"additionalRoles,omitempty"`
+
+	// AWS External ID set on assumed IAM roles
+	ExternalID string `json:"externalID,omitempty"`
+
+	// AWS STS assume role session tags
+	// +optional
+	SessionTags []*v1.Tag `json:"sessionTags,omitempty"`
+
+	// AWS STS assume role transitive session tags. Required when multiple rules are used with the provider
+	// +optional
+	TransitiveTagKeys []string `json:"transitiveTagKeys,omitempty"`
+
+	// Prefix adds a prefix to all retrieved values.
+	// +optional
+	Prefix string `json:"prefix,omitempty"`
+}
+
+// ParameterStoreStatus defines the observed state of ParameterStore.
+type ParameterStoreStatus struct {
+	// Conditions represent the latest available observations of the resource's state.
+	// +optional
+	Conditions []metav1.Condition `json:"conditions,omitempty"`
+}
+
+// +kubebuilder:object:root=true
+// +kubebuilder:subresource:status
+// +kubebuilder:resource:scope=Namespaced,categories={externalsecrets},shortName=ssm
+// +kubebuilder:printcolumn:name="Region",type=string,JSONPath=`.spec.region`
+// +kubebuilder:printcolumn:name="Age",type=date,JSONPath=`.metadata.creationTimestamp`
+
+// ParameterStore is the Schema for AWS Parameter Store provider configuration.
+type ParameterStore struct {
+	metav1.TypeMeta   `json:",inline"`
+	metav1.ObjectMeta `json:"metadata,omitempty"`
+
+	Spec   ParameterStoreSpec   `json:"spec,omitempty"`
+	Status ParameterStoreStatus `json:"status,omitempty"`
+}
+
+// +kubebuilder:object:root=true
+
+// ParameterStoreList contains a list of ParameterStore.
+type ParameterStoreList struct {
+	metav1.TypeMeta `json:",inline"`
+	metav1.ListMeta `json:"metadata,omitempty"`
+	Items           []ParameterStore `json:"items"`
+}
+
+func init() {
+	SchemeBuilder.Register(&ParameterStore{}, &ParameterStoreList{})
+}

+ 97 - 0
apis/provider/aws/v2alpha1/secretsmanager_types.go

@@ -0,0 +1,97 @@
+/*
+Copyright © The ESO Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    https://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 v2alpha1
+
+import (
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+
+	v1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
+)
+
+// SecretsManagerSpec defines the desired state of SecretsManager.
+type SecretsManagerSpec struct {
+	// Auth defines the information necessary to authenticate against AWS
+	// if not set aws sdk will infer credentials from your environment
+	// see: https://docs.aws.amazon.com/sdk-for-go/v1/developer-guide/configuring-sdk.html#specifying-credentials
+	// +optional
+	Auth v1.AWSAuth `json:"auth,omitempty"`
+
+	// Role is a Role ARN which the provider will assume
+	// +optional
+	Role string `json:"role,omitempty"`
+
+	// AWS Region to be used for the provider
+	Region string `json:"region"`
+
+	// AdditionalRoles is a chained list of Role ARNs which the provider will sequentially assume before assuming the Role
+	// +optional
+	AdditionalRoles []string `json:"additionalRoles,omitempty"`
+
+	// AWS External ID set on assumed IAM roles
+	ExternalID string `json:"externalID,omitempty"`
+
+	// AWS STS assume role session tags
+	// +optional
+	SessionTags []*v1.Tag `json:"sessionTags,omitempty"`
+
+	// SecretsManager defines how the provider behaves when interacting with AWS SecretsManager
+	// +optional
+	SecretsManager *v1.SecretsManager `json:"secretsManager,omitempty"`
+
+	// AWS STS assume role transitive session tags. Required when multiple rules are used with the provider
+	// +optional
+	TransitiveTagKeys []string `json:"transitiveTagKeys,omitempty"`
+
+	// Prefix adds a prefix to all retrieved values.
+	// +optional
+	Prefix string `json:"prefix,omitempty"`
+}
+
+// SecretsManagerStatus defines the observed state of SecretsManager.
+type SecretsManagerStatus struct {
+	// Conditions represent the latest available observations of the resource's state.
+	// +optional
+	Conditions []metav1.Condition `json:"conditions,omitempty"`
+}
+
+// +kubebuilder:object:root=true
+// +kubebuilder:subresource:status
+// +kubebuilder:resource:scope=Namespaced,categories={externalsecrets},shortName=sm
+// +kubebuilder:printcolumn:name="Region",type=string,JSONPath=`.spec.region`
+// +kubebuilder:printcolumn:name="Age",type=date,JSONPath=`.metadata.creationTimestamp`
+
+// SecretsManager is the Schema for AWS Secrets Manager provider configuration.
+type SecretsManager struct {
+	metav1.TypeMeta   `json:",inline"`
+	metav1.ObjectMeta `json:"metadata,omitempty"`
+
+	Spec   SecretsManagerSpec   `json:"spec,omitempty"`
+	Status SecretsManagerStatus `json:"status,omitempty"`
+}
+
+// +kubebuilder:object:root=true
+
+// SecretsManagerList contains a list of SecretsManager.
+type SecretsManagerList struct {
+	metav1.TypeMeta `json:",inline"`
+	metav1.ListMeta `json:"metadata,omitempty"`
+	Items           []SecretsManager `json:"items"`
+}
+
+func init() {
+	SchemeBuilder.Register(&SecretsManager{}, &SecretsManagerList{})
+}

+ 268 - 0
apis/provider/aws/v2alpha1/zz_generated.deepcopy.go

@@ -0,0 +1,268 @@
+//go:build !ignore_autogenerated
+
+/*
+Copyright © The ESO Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    https://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+// Code generated by controller-gen. DO NOT EDIT.
+
+package v2alpha1
+
+import (
+	"github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	runtime "k8s.io/apimachinery/pkg/runtime"
+)
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *ParameterStore) DeepCopyInto(out *ParameterStore) {
+	*out = *in
+	out.TypeMeta = in.TypeMeta
+	in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
+	in.Spec.DeepCopyInto(&out.Spec)
+	in.Status.DeepCopyInto(&out.Status)
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ParameterStore.
+func (in *ParameterStore) DeepCopy() *ParameterStore {
+	if in == nil {
+		return nil
+	}
+	out := new(ParameterStore)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *ParameterStore) DeepCopyObject() runtime.Object {
+	if c := in.DeepCopy(); c != nil {
+		return c
+	}
+	return nil
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *ParameterStoreList) DeepCopyInto(out *ParameterStoreList) {
+	*out = *in
+	out.TypeMeta = in.TypeMeta
+	in.ListMeta.DeepCopyInto(&out.ListMeta)
+	if in.Items != nil {
+		in, out := &in.Items, &out.Items
+		*out = make([]ParameterStore, len(*in))
+		for i := range *in {
+			(*in)[i].DeepCopyInto(&(*out)[i])
+		}
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ParameterStoreList.
+func (in *ParameterStoreList) DeepCopy() *ParameterStoreList {
+	if in == nil {
+		return nil
+	}
+	out := new(ParameterStoreList)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *ParameterStoreList) DeepCopyObject() runtime.Object {
+	if c := in.DeepCopy(); c != nil {
+		return c
+	}
+	return nil
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *ParameterStoreSpec) DeepCopyInto(out *ParameterStoreSpec) {
+	*out = *in
+	in.Auth.DeepCopyInto(&out.Auth)
+	if in.AdditionalRoles != nil {
+		in, out := &in.AdditionalRoles, &out.AdditionalRoles
+		*out = make([]string, len(*in))
+		copy(*out, *in)
+	}
+	if in.SessionTags != nil {
+		in, out := &in.SessionTags, &out.SessionTags
+		*out = make([]*v1.Tag, len(*in))
+		for i := range *in {
+			if (*in)[i] != nil {
+				in, out := &(*in)[i], &(*out)[i]
+				*out = new(v1.Tag)
+				**out = **in
+			}
+		}
+	}
+	if in.TransitiveTagKeys != nil {
+		in, out := &in.TransitiveTagKeys, &out.TransitiveTagKeys
+		*out = make([]string, len(*in))
+		copy(*out, *in)
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ParameterStoreSpec.
+func (in *ParameterStoreSpec) DeepCopy() *ParameterStoreSpec {
+	if in == nil {
+		return nil
+	}
+	out := new(ParameterStoreSpec)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *ParameterStoreStatus) DeepCopyInto(out *ParameterStoreStatus) {
+	*out = *in
+	if in.Conditions != nil {
+		in, out := &in.Conditions, &out.Conditions
+		*out = make([]metav1.Condition, len(*in))
+		for i := range *in {
+			(*in)[i].DeepCopyInto(&(*out)[i])
+		}
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ParameterStoreStatus.
+func (in *ParameterStoreStatus) DeepCopy() *ParameterStoreStatus {
+	if in == nil {
+		return nil
+	}
+	out := new(ParameterStoreStatus)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *SecretsManager) DeepCopyInto(out *SecretsManager) {
+	*out = *in
+	out.TypeMeta = in.TypeMeta
+	in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
+	in.Spec.DeepCopyInto(&out.Spec)
+	in.Status.DeepCopyInto(&out.Status)
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SecretsManager.
+func (in *SecretsManager) DeepCopy() *SecretsManager {
+	if in == nil {
+		return nil
+	}
+	out := new(SecretsManager)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *SecretsManager) DeepCopyObject() runtime.Object {
+	if c := in.DeepCopy(); c != nil {
+		return c
+	}
+	return nil
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *SecretsManagerList) DeepCopyInto(out *SecretsManagerList) {
+	*out = *in
+	out.TypeMeta = in.TypeMeta
+	in.ListMeta.DeepCopyInto(&out.ListMeta)
+	if in.Items != nil {
+		in, out := &in.Items, &out.Items
+		*out = make([]SecretsManager, len(*in))
+		for i := range *in {
+			(*in)[i].DeepCopyInto(&(*out)[i])
+		}
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SecretsManagerList.
+func (in *SecretsManagerList) DeepCopy() *SecretsManagerList {
+	if in == nil {
+		return nil
+	}
+	out := new(SecretsManagerList)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *SecretsManagerList) DeepCopyObject() runtime.Object {
+	if c := in.DeepCopy(); c != nil {
+		return c
+	}
+	return nil
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *SecretsManagerSpec) DeepCopyInto(out *SecretsManagerSpec) {
+	*out = *in
+	in.Auth.DeepCopyInto(&out.Auth)
+	if in.AdditionalRoles != nil {
+		in, out := &in.AdditionalRoles, &out.AdditionalRoles
+		*out = make([]string, len(*in))
+		copy(*out, *in)
+	}
+	if in.SessionTags != nil {
+		in, out := &in.SessionTags, &out.SessionTags
+		*out = make([]*v1.Tag, len(*in))
+		for i := range *in {
+			if (*in)[i] != nil {
+				in, out := &(*in)[i], &(*out)[i]
+				*out = new(v1.Tag)
+				**out = **in
+			}
+		}
+	}
+	if in.SecretsManager != nil {
+		in, out := &in.SecretsManager, &out.SecretsManager
+		*out = new(v1.SecretsManager)
+		**out = **in
+	}
+	if in.TransitiveTagKeys != nil {
+		in, out := &in.TransitiveTagKeys, &out.TransitiveTagKeys
+		*out = make([]string, len(*in))
+		copy(*out, *in)
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SecretsManagerSpec.
+func (in *SecretsManagerSpec) DeepCopy() *SecretsManagerSpec {
+	if in == nil {
+		return nil
+	}
+	out := new(SecretsManagerSpec)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *SecretsManagerStatus) DeepCopyInto(out *SecretsManagerStatus) {
+	*out = *in
+	if in.Conditions != nil {
+		in, out := &in.Conditions, &out.Conditions
+		*out = make([]metav1.Condition, len(*in))
+		for i := range *in {
+			(*in)[i].DeepCopyInto(&(*out)[i])
+		}
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SecretsManagerStatus.
+func (in *SecretsManagerStatus) DeepCopy() *SecretsManagerStatus {
+	if in == nil {
+		return nil
+	}
+	out := new(SecretsManagerStatus)
+	in.DeepCopyInto(out)
+	return out
+}

+ 21 - 0
apis/provider/fake/v2alpha1/doc.go

@@ -0,0 +1,21 @@
+/*
+Copyright © The ESO Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    https://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 v2alpha1 contains v2alpha1 API schema definitions for the Fake provider.
+// +kubebuilder:object:generate=true
+// +groupName=provider.external-secrets.io
+// +versionName=v2alpha1
+package v2alpha1

+ 36 - 0
apis/provider/fake/v2alpha1/groupversion_info.go

@@ -0,0 +1,36 @@
+/*
+Copyright © The ESO Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    https://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 v2alpha1
+
+import (
+	"k8s.io/apimachinery/pkg/runtime/schema"
+	"sigs.k8s.io/controller-runtime/pkg/scheme"
+)
+
+var (
+	// GroupVersion is group version used to register these objects.
+	GroupVersion = schema.GroupVersion{Group: "provider.external-secrets.io", Version: "v2alpha1"}
+
+	// Kind is the kind name used for Fake resources.
+	Kind = "Fake"
+
+	// SchemeBuilder is used to add go types to the GroupVersionKind scheme.
+	SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion}
+
+	// AddToScheme adds the types in this group-version to the given scheme.
+	AddToScheme = SchemeBuilder.AddToScheme
+)

+ 75 - 0
apis/provider/fake/v2alpha1/types.go

@@ -0,0 +1,75 @@
+/*
+Copyright © The ESO Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    https://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 v2alpha1
+
+import (
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+
+	esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
+)
+
+// Fake defines the configuration for the Fake provider.
+// This provider returns static key-value pairs for testing purposes.
+// +kubebuilder:object:root=true
+// +kubebuilder:storageversion
+// +kubebuilder:subresource:status
+// +kubebuilder:resource:scope=Namespaced,categories={external-secrets},shortName=fake
+// +genclient.
+type Fake struct {
+	metav1.TypeMeta   `json:",inline"`
+	metav1.ObjectMeta `json:"metadata,omitempty"`
+
+	Spec esv1.FakeProvider `json:"spec,omitempty"`
+}
+
+// +kubebuilder:object:root=true
+// FakeList contains a list of Fake resources.
+type FakeList struct {
+	metav1.TypeMeta `json:",inline"`
+	metav1.ListMeta `json:"metadata,omitempty"`
+	Items           []Fake `json:"items"`
+}
+
+// FakeProviderSpec defines the desired state of Fake provider.
+// It matches the structure of v1.FakeProvider for easy conversion.
+// +kubebuilder:object:generate=true
+type FakeProviderSpec struct {
+	// Data defines the static key-value pairs to return.
+	Data []FakeProviderData `json:"data"`
+
+	// ValidationResult optionally specifies the validation result for testing.
+	// +optional
+	ValidationResult *string `json:"validationResult,omitempty"`
+}
+
+// FakeProviderData defines a key-value pair with optional version.
+// +kubebuilder:object:generate=true
+type FakeProviderData struct {
+	// Key is the secret key.
+	Key string `json:"key"`
+
+	// Value is the secret value.
+	Value string `json:"value"`
+
+	// Version is an optional version identifier.
+	// +optional
+	Version string `json:"version,omitempty"`
+}
+
+func init() {
+	SchemeBuilder.Register(&Fake{}, &FakeList{})
+}

+ 123 - 0
apis/provider/fake/v2alpha1/zz_generated.deepcopy.go

@@ -0,0 +1,123 @@
+//go:build !ignore_autogenerated
+
+/*
+Copyright © The ESO Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    https://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+// Code generated by controller-gen. DO NOT EDIT.
+
+package v2alpha1
+
+import (
+	runtime "k8s.io/apimachinery/pkg/runtime"
+)
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *Fake) DeepCopyInto(out *Fake) {
+	*out = *in
+	out.TypeMeta = in.TypeMeta
+	in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
+	in.Spec.DeepCopyInto(&out.Spec)
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Fake.
+func (in *Fake) DeepCopy() *Fake {
+	if in == nil {
+		return nil
+	}
+	out := new(Fake)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *Fake) DeepCopyObject() runtime.Object {
+	if c := in.DeepCopy(); c != nil {
+		return c
+	}
+	return nil
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *FakeList) DeepCopyInto(out *FakeList) {
+	*out = *in
+	out.TypeMeta = in.TypeMeta
+	in.ListMeta.DeepCopyInto(&out.ListMeta)
+	if in.Items != nil {
+		in, out := &in.Items, &out.Items
+		*out = make([]Fake, len(*in))
+		for i := range *in {
+			(*in)[i].DeepCopyInto(&(*out)[i])
+		}
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FakeList.
+func (in *FakeList) DeepCopy() *FakeList {
+	if in == nil {
+		return nil
+	}
+	out := new(FakeList)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *FakeList) DeepCopyObject() runtime.Object {
+	if c := in.DeepCopy(); c != nil {
+		return c
+	}
+	return nil
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *FakeProviderData) DeepCopyInto(out *FakeProviderData) {
+	*out = *in
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FakeProviderData.
+func (in *FakeProviderData) DeepCopy() *FakeProviderData {
+	if in == nil {
+		return nil
+	}
+	out := new(FakeProviderData)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *FakeProviderSpec) DeepCopyInto(out *FakeProviderSpec) {
+	*out = *in
+	if in.Data != nil {
+		in, out := &in.Data, &out.Data
+		*out = make([]FakeProviderData, len(*in))
+		copy(*out, *in)
+	}
+	if in.ValidationResult != nil {
+		in, out := &in.ValidationResult, &out.ValidationResult
+		*out = new(string)
+		**out = **in
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FakeProviderSpec.
+func (in *FakeProviderSpec) DeepCopy() *FakeProviderSpec {
+	if in == nil {
+		return nil
+	}
+	out := new(FakeProviderSpec)
+	in.DeepCopyInto(out)
+	return out
+}

+ 21 - 0
apis/provider/kubernetes/v2alpha1/doc.go

@@ -0,0 +1,21 @@
+/*
+Copyright © The ESO Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    https://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 v2alpha1 contains resources for external-secrets
+// +kubebuilder:object:generate=true
+// +groupName=provider.external-secrets.io
+// +versionName=v2alpha1
+package v2alpha1

+ 36 - 0
apis/provider/kubernetes/v2alpha1/groupversion_info.go

@@ -0,0 +1,36 @@
+/*
+Copyright © The ESO Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    https://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 v2alpha1
+
+import (
+	"k8s.io/apimachinery/pkg/runtime/schema"
+	"sigs.k8s.io/controller-runtime/pkg/scheme"
+)
+
+var (
+	// GroupVersion is group version used to register these objects.
+	GroupVersion = schema.GroupVersion{Group: "provider.external-secrets.io", Version: "v2alpha1"}
+
+	// Kind is the kind name used for Kubernetes resources.
+	Kind = "Kubernetes"
+
+	// SchemeBuilder is used to add go types to the GroupVersionKind scheme.
+	SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion}
+
+	// AddToScheme adds the types in this group-version to the given scheme.
+	AddToScheme = SchemeBuilder.AddToScheme
+)

+ 51 - 0
apis/provider/kubernetes/v2alpha1/types.go

@@ -0,0 +1,51 @@
+/*
+Copyright © The ESO Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    https://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 v2alpha1 contains the v2alpha1 API definitions for provider resources.
+package v2alpha1
+
+import (
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+
+	v1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
+)
+
+// Kubernetes defines the configuration for the Kubernetes Secret provider.
+// This provider fetches secrets from Kubernetes Secrets in the same cluster.
+// It's primarily useful for testing and migration scenarios.
+// +kubebuilder:object:root=true
+// +kubebuilder:storageversion
+// +kubebuilder:subresource:status
+// +kubebuilder:resource:scope=Namespaced,categories={external-secrets}
+type Kubernetes struct {
+	metav1.TypeMeta   `json:",inline"`
+	metav1.ObjectMeta `json:"metadata,omitempty"`
+
+	Spec v1.KubernetesProvider `json:"spec,omitempty"`
+}
+
+// +kubebuilder:object:root=true
+
+// KubernetesList contains a list of Kubernetes resources.
+type KubernetesList struct {
+	metav1.TypeMeta `json:",inline"`
+	metav1.ListMeta `json:"metadata,omitempty"`
+	Items           []Kubernetes `json:"items"`
+}
+
+func init() {
+	SchemeBuilder.Register(&Kubernetes{}, &KubernetesList{})
+}

+ 83 - 0
apis/provider/kubernetes/v2alpha1/zz_generated.deepcopy.go

@@ -0,0 +1,83 @@
+//go:build !ignore_autogenerated
+
+/*
+Copyright © The ESO Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    https://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+// Code generated by controller-gen. DO NOT EDIT.
+
+package v2alpha1
+
+import (
+	runtime "k8s.io/apimachinery/pkg/runtime"
+)
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *Kubernetes) DeepCopyInto(out *Kubernetes) {
+	*out = *in
+	out.TypeMeta = in.TypeMeta
+	in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
+	in.Spec.DeepCopyInto(&out.Spec)
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Kubernetes.
+func (in *Kubernetes) DeepCopy() *Kubernetes {
+	if in == nil {
+		return nil
+	}
+	out := new(Kubernetes)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *Kubernetes) DeepCopyObject() runtime.Object {
+	if c := in.DeepCopy(); c != nil {
+		return c
+	}
+	return nil
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *KubernetesList) DeepCopyInto(out *KubernetesList) {
+	*out = *in
+	out.TypeMeta = in.TypeMeta
+	in.ListMeta.DeepCopyInto(&out.ListMeta)
+	if in.Items != nil {
+		in, out := &in.Items, &out.Items
+		*out = make([]Kubernetes, len(*in))
+		for i := range *in {
+			(*in)[i].DeepCopyInto(&(*out)[i])
+		}
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KubernetesList.
+func (in *KubernetesList) DeepCopy() *KubernetesList {
+	if in == nil {
+		return nil
+	}
+	out := new(KubernetesList)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *KubernetesList) DeepCopyObject() runtime.Object {
+	if c := in.DeepCopy(); c != nil {
+		return c
+	}
+	return nil
+}

BIN
assets/eso-out-of-tree.png


+ 37 - 0
cmd/controller/certcontroller.go

@@ -40,6 +40,7 @@ import (
 
 	ctrlcommon "github.com/external-secrets/external-secrets/pkg/controllers/common"
 	"github.com/external-secrets/external-secrets/pkg/controllers/crds"
+	"github.com/external-secrets/external-secrets/pkg/controllers/providercerts"
 	"github.com/external-secrets/external-secrets/pkg/controllers/webhookconfig"
 	"github.com/external-secrets/external-secrets/runtime/constants"
 )
@@ -138,6 +139,21 @@ var certcontrollerCmd = &cobra.Command{
 			os.Exit(1)
 		}
 
+		// Setup provider certificate reconciler if configured
+		if providerConfig := buildProviderConfig(); providerConfig != nil {
+			providerCertReconciler := providercerts.New(
+				mgr.GetClient(),
+				ctrl.Log.WithName("provider-certs"),
+				providerConfig,
+				crdRequeueInterval,
+				mgr.Elected(),
+			)
+			if err := mgr.Add(providerCertReconciler); err != nil {
+				setupLog.Error(err, "unable to add provider cert reconciler")
+				os.Exit(1)
+			}
+		}
+
 		whc := webhookconfig.New(mgr.GetClient(), mgr.GetScheme(), mgr.Elected(),
 			ctrl.Log.WithName("controllers").WithName("webhook-certs-updater"),
 			webhookconfig.Opts{
@@ -175,6 +191,25 @@ var certcontrollerCmd = &cobra.Command{
 	},
 }
 
+func buildProviderConfig() *providercerts.ProviderCertConfig {
+	if providerNamespace == "" {
+		return nil
+	}
+	if len(providerServiceNames) == 0 {
+		return nil
+	}
+	serviceNames := make([]string, 0, len(providerServiceNames))
+	for _, serviceName := range providerServiceNames {
+		if serviceName == "" {
+			continue
+		}
+		serviceNames = append(serviceNames, serviceName)
+	}
+	return &providercerts.ProviderCertConfig{
+		Namespace:    providerNamespace,
+		ServiceNames: serviceNames,
+	}
+}
 func setupLogger() {
 	var lvl zapcore.Level
 	var enc zapcore.TimeEncoder
@@ -223,4 +258,6 @@ func init() {
 	certcontrollerCmd.Flags().DurationVar(&crdRequeueInterval, "crd-requeue-interval", time.Minute*5, "Time duration between reconciling CRDs for new certs")
 	certcontrollerCmd.Flags().BoolVar(&enableHTTP2, "enable-http2", false,
 		"If set, HTTP/2 will be enabled for the metrics server")
+	certcontrollerCmd.Flags().StringVar(&providerNamespace, "provider-namespace", "", "Provider namespace")
+	certcontrollerCmd.Flags().StringSliceVar(&providerServiceNames, "provider-service-names", []string{}, "Provider service names for DNS SANs")
 }

+ 52 - 1
cmd/controller/root.go

@@ -30,7 +30,9 @@ import (
 	ctrl "sigs.k8s.io/controller-runtime"
 	"sigs.k8s.io/controller-runtime/pkg/cache"
 	"sigs.k8s.io/controller-runtime/pkg/client"
+	"sigs.k8s.io/controller-runtime/pkg/controller"
 	"sigs.k8s.io/controller-runtime/pkg/healthz"
+	crmetrics "sigs.k8s.io/controller-runtime/pkg/metrics"
 	"sigs.k8s.io/controller-runtime/pkg/metrics/filters"
 	"sigs.k8s.io/controller-runtime/pkg/metrics/server"
 	"sigs.k8s.io/controller-runtime/pkg/webhook"
@@ -38,8 +40,12 @@ import (
 	esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
 	esv1alpha1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1alpha1"
 	genv1alpha1 "github.com/external-secrets/external-secrets/apis/generators/v1alpha1"
+	awsv2 "github.com/external-secrets/external-secrets/apis/provider/aws/v2alpha1"
+	fakev2alpha1 "github.com/external-secrets/external-secrets/apis/provider/fake/v2alpha1"
+	k8sv2alpha1 "github.com/external-secrets/external-secrets/apis/provider/kubernetes/v2alpha1"
 	"github.com/external-secrets/external-secrets/pkg/controllers/clusterexternalsecret"
 	"github.com/external-secrets/external-secrets/pkg/controllers/clusterexternalsecret/cesmetrics"
+	"github.com/external-secrets/external-secrets/pkg/controllers/clusterproviderclass"
 	"github.com/external-secrets/external-secrets/pkg/controllers/clusterpushsecret"
 	"github.com/external-secrets/external-secrets/pkg/controllers/clusterpushsecret/cpsmetrics"
 	ctrlcommon "github.com/external-secrets/external-secrets/pkg/controllers/common"
@@ -47,11 +53,14 @@ import (
 	"github.com/external-secrets/external-secrets/pkg/controllers/externalsecret/esmetrics"
 	"github.com/external-secrets/external-secrets/pkg/controllers/generatorstate"
 	ctrlmetrics "github.com/external-secrets/external-secrets/pkg/controllers/metrics"
+	"github.com/external-secrets/external-secrets/pkg/controllers/providerclass"
 	"github.com/external-secrets/external-secrets/pkg/controllers/pushsecret"
 	"github.com/external-secrets/external-secrets/pkg/controllers/pushsecret/psmetrics"
 	"github.com/external-secrets/external-secrets/pkg/controllers/secretstore"
 	"github.com/external-secrets/external-secrets/pkg/controllers/secretstore/cssmetrics"
 	"github.com/external-secrets/external-secrets/pkg/controllers/secretstore/ssmetrics"
+	grpccommon "github.com/external-secrets/external-secrets/providers/v2/common/grpc"
+	"github.com/external-secrets/external-secrets/runtime/clientmanager"
 	"github.com/external-secrets/external-secrets/runtime/feature"
 
 	// To allow using gcp auth.
@@ -105,6 +114,8 @@ var (
 	tlsMinVersion                         string
 	enableHTTP2                           bool
 	allowGenericTargets                   bool
+	providerNamespace                     string
+	providerServiceNames                  []string
 )
 
 const (
@@ -120,6 +131,11 @@ func init() {
 	utilruntime.Must(esv1.AddToScheme(scheme))
 	utilruntime.Must(esv1alpha1.AddToScheme(scheme))
 	utilruntime.Must(genv1alpha1.AddToScheme(scheme))
+
+	// v2 provider schemes
+	utilruntime.Must(awsv2.AddToScheme(scheme))
+	utilruntime.Must(fakev2alpha1.AddToScheme(scheme))
+	utilruntime.Must(k8sv2alpha1.AddToScheme(scheme))
 }
 
 var rootCmd = &cobra.Command{
@@ -131,6 +147,14 @@ var rootCmd = &cobra.Command{
 
 		ctrlmetrics.SetUpLabelNames(enableExtendedMetricLabels)
 		esmetrics.SetUpMetrics()
+		if err := clientmanager.RegisterMetrics(); err != nil {
+			setupLog.Error(err, "unable to register clientmanager metrics")
+			os.Exit(1)
+		}
+		if err := grpccommon.RegisterMetrics(crmetrics.Registry); err != nil {
+			setupLog.Error(err, "unable to register grpc metrics")
+			os.Exit(1)
+		}
 		config := ctrl.GetConfigOrDie()
 		config.QPS = clientQPS
 		config.Burst = clientBurst
@@ -236,6 +260,33 @@ var rootCmd = &cobra.Command{
 				os.Exit(1)
 			}
 		}
+
+		if err = (&clusterproviderclass.Reconciler{
+			Client:          mgr.GetClient(),
+			Log:             ctrl.Log.WithName("controllers").WithName("ClusterProviderClass"),
+			Scheme:          mgr.GetScheme(),
+			RequeueInterval: storeRequeueInterval,
+		}).SetupWithManager(mgr, controller.Options{
+			MaxConcurrentReconciles: concurrent,
+			RateLimiter:             ctrlcommon.BuildRateLimiter(),
+		}); err != nil {
+			setupLog.Error(err, errCreateController, "controller", "ClusterProviderClass")
+			os.Exit(1)
+		}
+
+		if err = (&providerclass.Reconciler{
+			Client:          mgr.GetClient(),
+			Log:             ctrl.Log.WithName("controllers").WithName("ProviderClass"),
+			Scheme:          mgr.GetScheme(),
+			RequeueInterval: storeRequeueInterval,
+		}).SetupWithManager(mgr, controller.Options{
+			MaxConcurrentReconciles: concurrent,
+			RateLimiter:             ctrlcommon.BuildRateLimiter(),
+		}); err != nil {
+			setupLog.Error(err, errCreateController, "controller", "ProviderClass")
+			os.Exit(1)
+		}
+
 		if err = (&generatorstate.Reconciler{
 			Client:     mgr.GetClient(),
 			Log:        ctrl.Log.WithName("controllers").WithName("GeneratorState"),
@@ -369,7 +420,7 @@ func init() {
 		true,
 		"Enable a direct API read when the partial Secret cache and managed Secret cache disagree. Disable to rely on cache retry only.",
 	)
-	rootCmd.Flags().DurationVar(&storeRequeueInterval, "store-requeue-interval", time.Minute*5, "Default Time duration between reconciling (Cluster)SecretStores")
+	rootCmd.Flags().DurationVar(&storeRequeueInterval, "store-requeue-interval", 30*time.Second, "Default Time duration between reconciling (Cluster)SecretStores")
 	rootCmd.Flags().BoolVar(&enableFloodGate, "enable-flood-gate", true, "Enable flood gate. External secret will be reconciled only if the ClusterStore or Store have an healthy or unknown state.")
 	rootCmd.Flags().BoolVar(&enableGeneratorState, "enable-generator-state", true, "Whether the Controller should manage GeneratorState")
 	rootCmd.Flags().BoolVar(&enableExtendedMetricLabels, "enable-extended-metric-labels", false, "Enable recommended kubernetes annotations as labels in metrics.")

+ 30 - 0
cmd/controller/root_test.go

@@ -0,0 +1,30 @@
+/*
+Copyright © The ESO Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    https://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 controller
+
+import "testing"
+
+func TestStoreRequeueIntervalDefault(t *testing.T) {
+	flag := rootCmd.Flags().Lookup("store-requeue-interval")
+	if flag == nil {
+		t.Fatal("store-requeue-interval flag not found")
+	}
+
+	if flag.DefValue != "30s" {
+		t.Fatalf("expected store-requeue-interval default 30s, got %q", flag.DefValue)
+	}
+}

+ 6 - 6
config/crds/bases/external-secrets.io_clusterexternalsecrets.yaml

@@ -200,7 +200,7 @@ spec:
                               properties:
                                 kind:
                                   description: |-
-                                    Kind of the SecretStore resource (SecretStore or ClusterSecretStore)
+                                    Kind of the SecretStore resource (SecretStore, ClusterSecretStore)
                                     Defaults to `SecretStore`
                                   enum:
                                   - SecretStore
@@ -466,7 +466,7 @@ spec:
                               properties:
                                 kind:
                                   description: |-
-                                    Kind of the SecretStore resource (SecretStore or ClusterSecretStore)
+                                    Kind of the SecretStore resource (SecretStore, ClusterSecretStore)
                                     Defaults to `SecretStore`
                                   enum:
                                   - SecretStore
@@ -509,7 +509,7 @@ spec:
                     properties:
                       kind:
                         description: |-
-                          Kind of the SecretStore resource (SecretStore or ClusterSecretStore)
+                          Kind of the SecretStore resource (SecretStore, ClusterSecretStore)
                           Defaults to `SecretStore`
                         enum:
                         - SecretStore
@@ -1081,7 +1081,7 @@ spec:
                               properties:
                                 kind:
                                   description: |-
-                                    Kind of the SecretStore resource (SecretStore or ClusterSecretStore)
+                                    Kind of the SecretStore resource (SecretStore, ClusterSecretStore)
                                     Defaults to `SecretStore`
                                   enum:
                                   - SecretStore
@@ -1285,7 +1285,7 @@ spec:
                               properties:
                                 kind:
                                   description: |-
-                                    Kind of the SecretStore resource (SecretStore or ClusterSecretStore)
+                                    Kind of the SecretStore resource (SecretStore, ClusterSecretStore)
                                     Defaults to `SecretStore`
                                   enum:
                                   - SecretStore
@@ -1328,7 +1328,7 @@ spec:
                     properties:
                       kind:
                         description: |-
-                          Kind of the SecretStore resource (SecretStore or ClusterSecretStore)
+                          Kind of the SecretStore resource (SecretStore, ClusterSecretStore)
                           Defaults to `SecretStore`
                         enum:
                         - SecretStore

+ 122 - 0
config/crds/bases/external-secrets.io_clusterproviderclasses.yaml

@@ -0,0 +1,122 @@
+apiVersion: apiextensions.k8s.io/v1
+kind: CustomResourceDefinition
+metadata:
+  annotations:
+    controller-gen.kubebuilder.io/version: v0.19.0
+  name: clusterproviderclasses.external-secrets.io
+spec:
+  group: external-secrets.io
+  names:
+    categories:
+    - externalsecrets
+    kind: ClusterProviderClass
+    listKind: ClusterProviderClassList
+    plural: clusterproviderclasses
+    shortNames:
+    - cpc
+    singular: clusterproviderclass
+  scope: Cluster
+  versions:
+  - additionalPrinterColumns:
+    - jsonPath: .spec.address
+      name: Address
+      type: string
+    name: v1alpha1
+    schema:
+      openAPIV3Schema:
+        description: ClusterProviderClass is a cluster-scoped store runtime class.
+        properties:
+          apiVersion:
+            description: |-
+              APIVersion defines the versioned schema of this representation of an object.
+              Servers should convert recognized schemas to the latest internal value, and
+              may reject unrecognized values.
+              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
+            type: string
+          kind:
+            description: |-
+              Kind is a string value representing the REST resource this object represents.
+              Servers may infer this from the endpoint the client submits requests to.
+              Cannot be updated.
+              In CamelCase.
+              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
+            type: string
+          metadata:
+            type: object
+          spec:
+            description: ClusterProviderClassSpec defines the desired state of ClusterProviderClass.
+            properties:
+              address:
+                minLength: 1
+                type: string
+            required:
+            - address
+            type: object
+          status:
+            description: ClusterProviderClassStatus defines the observed state of
+              ClusterProviderClass.
+            properties:
+              conditions:
+                items:
+                  description: Condition contains details for one aspect of the current
+                    state of this API Resource.
+                  properties:
+                    lastTransitionTime:
+                      description: |-
+                        lastTransitionTime is the last time the condition transitioned from one status to another.
+                        This should be when the underlying condition changed.  If that is not known, then using the time when the API field changed is acceptable.
+                      format: date-time
+                      type: string
+                    message:
+                      description: |-
+                        message is a human readable message indicating details about the transition.
+                        This may be an empty string.
+                      maxLength: 32768
+                      type: string
+                    observedGeneration:
+                      description: |-
+                        observedGeneration represents the .metadata.generation that the condition was set based upon.
+                        For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date
+                        with respect to the current state of the instance.
+                      format: int64
+                      minimum: 0
+                      type: integer
+                    reason:
+                      description: |-
+                        reason contains a programmatic identifier indicating the reason for the condition's last transition.
+                        Producers of specific condition types may define expected values and meanings for this field,
+                        and whether the values are considered a guaranteed API.
+                        The value should be a CamelCase string.
+                        This field may not be empty.
+                      maxLength: 1024
+                      minLength: 1
+                      pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$
+                      type: string
+                    status:
+                      description: status of the condition, one of True, False, Unknown.
+                      enum:
+                      - "True"
+                      - "False"
+                      - Unknown
+                      type: string
+                    type:
+                      description: type of condition in CamelCase or in foo.example.com/CamelCase.
+                      maxLength: 316
+                      pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$
+                      type: string
+                  required:
+                  - lastTransitionTime
+                  - message
+                  - reason
+                  - status
+                  - type
+                  type: object
+                type: array
+            type: object
+        required:
+        - spec
+        type: object
+    served: true
+    storage: true
+    subresources:
+      status: {}

+ 14 - 6
config/crds/bases/external-secrets.io_clusterpushsecrets.yaml

@@ -259,10 +259,14 @@ spec:
                           description: StoreRef specifies which SecretStore to push
                             to. Required.
                           properties:
+                            apiVersion:
+                              description: |-
+                                APIVersion of the referenced store resource.
+                                This field is optional and depends on the selected store kind.
+                              type: string
                             kind:
-                              default: SecretStore
-                              description: Kind of the SecretStore resource (SecretStore
-                                or ClusterSecretStore)
+                              description: Kind of the SecretStore resource (SecretStore,
+                                ClusterSecretStore)
                               enum:
                               - SecretStore
                               - ClusterSecretStore
@@ -348,10 +352,14 @@ spec:
                       description: PushSecretStoreRef contains a reference on how
                         to sync to a SecretStore.
                       properties:
+                        apiVersion:
+                          description: |-
+                            APIVersion of the referenced store resource.
+                            This field is optional and depends on the selected store kind.
+                          type: string
                         kind:
-                          default: SecretStore
-                          description: Kind of the SecretStore resource (SecretStore
-                            or ClusterSecretStore)
+                          description: Kind of the SecretStore resource (SecretStore,
+                            ClusterSecretStore)
                           enum:
                           - SecretStore
                           - ClusterSecretStore

+ 122 - 13
config/crds/bases/external-secrets.io_clustersecretstores.yaml

@@ -556,7 +556,6 @@ spec:
                               ForceDeleteWithoutRecovery in the same call. If you don't use either,
                               then by default Secrets Manager uses a 30-day recovery window.
                               see: https://docs.aws.amazon.com/secretsmanager/latest/apireference/API_DeleteSecret.html#SecretsManager-DeleteSecret-request-RecoveryWindowInDays
-                            format: int64
                             type: integer
                         type: object
                       service:
@@ -2251,7 +2250,6 @@ spec:
                       appID:
                         description: appID specifies the Github APP that will be used
                           to authenticate the client
-                        format: int64
                         type: integer
                       auth:
                         description: auth configures how secret-manager authenticates
@@ -2296,7 +2294,6 @@ spec:
                       installationID:
                         description: installationID specifies the Github APP installation
                           that will be used to authenticate the client
-                        format: int64
                         type: integer
                       orgSecretVisibility:
                         description: |-
@@ -5574,7 +5571,6 @@ spec:
 
                                       Deprecated: this will be removed in the future.
                                       Defaults to 10 minutes.
-                                    format: int64
                                     type: integer
                                   serviceAccountRef:
                                     description: Service account field containing
@@ -6547,6 +6543,39 @@ spec:
                     - auth
                     type: object
                 type: object
+              providerRef:
+                description: ProviderRef references a provider configuration managed
+                  externally.
+                properties:
+                  apiVersion:
+                    description: APIVersion identifies the API schema version for
+                      the provider resource.
+                    minLength: 1
+                    type: string
+                  kind:
+                    description: Kind identifies the provider resource type referenced
+                      by this store.
+                    minLength: 1
+                    type: string
+                  name:
+                    description: Name is the provider resource name referenced by
+                      this store.
+                    maxLength: 253
+                    minLength: 1
+                    pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
+                    type: string
+                  namespace:
+                    description: Namespace is the provider resource namespace referenced
+                      by this store.
+                    maxLength: 63
+                    minLength: 1
+                    pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$
+                    type: string
+                required:
+                - apiVersion
+                - kind
+                - name
+                type: object
               refreshInterval:
                 description: Used to configure store refresh interval in seconds.
                   Empty or 0 will default to the controller config.
@@ -6555,14 +6584,39 @@ spec:
                 description: Used to configure HTTP retries on failures.
                 properties:
                   maxRetries:
-                    format: int32
                     type: integer
                   retryInterval:
                     type: string
                 type: object
-            required:
-            - provider
+              runtimeRef:
+                description: RuntimeRef points to runtime configuration for this store.
+                properties:
+                  kind:
+                    description: Kind identifies the runtime resource type referenced
+                      by this store.
+                    enum:
+                    - ProviderClass
+                    - ClusterProviderClass
+                    type: string
+                  name:
+                    description: Name is the runtime resource name referenced by this
+                      store.
+                    maxLength: 253
+                    minLength: 1
+                    pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
+                    type: string
+                required:
+                - name
+                type: object
             type: object
+            x-kubernetes-validations:
+            - message: exactly one of spec.provider or spec.providerRef must be set
+              rule: (has(self.provider) && !has(self.providerRef)) || (!has(self.provider)
+                && has(self.providerRef))
+            - message: spec.runtimeRef must be empty when spec.provider is set
+              rule: '!(has(self.provider) && has(self.runtimeRef))'
+            - message: spec.runtimeRef is required when spec.providerRef is set
+              rule: '!has(self.providerRef) || has(self.runtimeRef)'
           status:
             description: SecretStoreStatus defines the observed state of the SecretStore.
             properties:
@@ -7220,7 +7274,6 @@ spec:
                               ForceDeleteWithoutRecovery in the same call. If you don't use either,
                               then by default Secrets Manager uses a 30 day recovery window.
                               see: https://docs.aws.amazon.com/secretsmanager/latest/apireference/API_DeleteSecret.html#SecretsManager-DeleteSecret-request-RecoveryWindowInDays
-                            format: int64
                             type: integer
                         type: object
                       service:
@@ -8525,7 +8578,6 @@ spec:
                       appID:
                         description: appID specifies the Github APP that will be used
                           to authenticate the client
-                        format: int64
                         type: integer
                       auth:
                         description: auth configures how secret-manager authenticates
@@ -8570,7 +8622,6 @@ spec:
                       installationID:
                         description: installationID specifies the Github APP installation
                           that will be used to authenticate the client
-                        format: int64
                         type: integer
                       organization:
                         description: organization will be used to fetch secrets from
@@ -10263,7 +10314,6 @@ spec:
 
                                       Deprecated: this will be removed in the future.
                                       Defaults to 10 minutes.
-                                    format: int64
                                     type: integer
                                   serviceAccountRef:
                                     description: Service account field containing
@@ -11067,6 +11117,39 @@ spec:
                     - auth
                     type: object
                 type: object
+              providerRef:
+                description: ProviderRef references a provider configuration managed
+                  externally.
+                properties:
+                  apiVersion:
+                    description: APIVersion identifies the API schema version for
+                      the provider resource.
+                    minLength: 1
+                    type: string
+                  kind:
+                    description: Kind identifies the provider resource type referenced
+                      by this store.
+                    minLength: 1
+                    type: string
+                  name:
+                    description: Name is the provider resource name referenced by
+                      this store.
+                    maxLength: 253
+                    minLength: 1
+                    pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
+                    type: string
+                  namespace:
+                    description: Namespace is the provider resource namespace referenced
+                      by this store.
+                    maxLength: 63
+                    minLength: 1
+                    pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$
+                    type: string
+                required:
+                - apiVersion
+                - kind
+                - name
+                type: object
               refreshInterval:
                 description: Used to configure store refresh interval in seconds.
                   Empty or 0 will default to the controller config.
@@ -11082,9 +11165,35 @@ spec:
                     description: RetryInterval is the interval between retry attempts.
                     type: string
                 type: object
-            required:
-            - provider
+              runtimeRef:
+                description: RuntimeRef points to runtime configuration for this store.
+                properties:
+                  kind:
+                    description: Kind identifies the runtime resource type referenced
+                      by this store.
+                    enum:
+                    - ProviderClass
+                    - ClusterProviderClass
+                    type: string
+                  name:
+                    description: Name is the runtime resource name referenced by this
+                      store.
+                    maxLength: 253
+                    minLength: 1
+                    pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
+                    type: string
+                required:
+                - name
+                type: object
             type: object
+            x-kubernetes-validations:
+            - message: exactly one of spec.provider or spec.providerRef must be set
+              rule: (has(self.provider) && !has(self.providerRef)) || (!has(self.provider)
+                && has(self.providerRef))
+            - message: spec.runtimeRef must be empty when spec.provider is set
+              rule: '!(has(self.provider) && has(self.runtimeRef))'
+            - message: spec.runtimeRef is required when spec.providerRef is set
+              rule: '!has(self.providerRef) || has(self.runtimeRef)'
           status:
             description: SecretStoreStatus defines the observed state of the SecretStore.
             properties:

+ 6 - 6
config/crds/bases/external-secrets.io_externalsecrets.yaml

@@ -185,7 +185,7 @@ spec:
                           properties:
                             kind:
                               description: |-
-                                Kind of the SecretStore resource (SecretStore or ClusterSecretStore)
+                                Kind of the SecretStore resource (SecretStore, ClusterSecretStore)
                                 Defaults to `SecretStore`
                               enum:
                               - SecretStore
@@ -449,7 +449,7 @@ spec:
                           properties:
                             kind:
                               description: |-
-                                Kind of the SecretStore resource (SecretStore or ClusterSecretStore)
+                                Kind of the SecretStore resource (SecretStore, ClusterSecretStore)
                                 Defaults to `SecretStore`
                               enum:
                               - SecretStore
@@ -492,7 +492,7 @@ spec:
                 properties:
                   kind:
                     description: |-
-                      Kind of the SecretStore resource (SecretStore or ClusterSecretStore)
+                      Kind of the SecretStore resource (SecretStore, ClusterSecretStore)
                       Defaults to `SecretStore`
                     enum:
                     - SecretStore
@@ -932,7 +932,7 @@ spec:
                           properties:
                             kind:
                               description: |-
-                                Kind of the SecretStore resource (SecretStore or ClusterSecretStore)
+                                Kind of the SecretStore resource (SecretStore, ClusterSecretStore)
                                 Defaults to `SecretStore`
                               enum:
                               - SecretStore
@@ -1135,7 +1135,7 @@ spec:
                           properties:
                             kind:
                               description: |-
-                                Kind of the SecretStore resource (SecretStore or ClusterSecretStore)
+                                Kind of the SecretStore resource (SecretStore, ClusterSecretStore)
                                 Defaults to `SecretStore`
                               enum:
                               - SecretStore
@@ -1178,7 +1178,7 @@ spec:
                 properties:
                   kind:
                     description: |-
-                      Kind of the SecretStore resource (SecretStore or ClusterSecretStore)
+                      Kind of the SecretStore resource (SecretStore, ClusterSecretStore)
                       Defaults to `SecretStore`
                     enum:
                     - SecretStore

+ 121 - 0
config/crds/bases/external-secrets.io_providerclasses.yaml

@@ -0,0 +1,121 @@
+apiVersion: apiextensions.k8s.io/v1
+kind: CustomResourceDefinition
+metadata:
+  annotations:
+    controller-gen.kubebuilder.io/version: v0.19.0
+  name: providerclasses.external-secrets.io
+spec:
+  group: external-secrets.io
+  names:
+    categories:
+    - externalsecrets
+    kind: ProviderClass
+    listKind: ProviderClassList
+    plural: providerclasses
+    shortNames:
+    - pc
+    singular: providerclass
+  scope: Namespaced
+  versions:
+  - additionalPrinterColumns:
+    - jsonPath: .spec.address
+      name: Address
+      type: string
+    name: v1alpha1
+    schema:
+      openAPIV3Schema:
+        description: ProviderClass is a namespaced store runtime class.
+        properties:
+          apiVersion:
+            description: |-
+              APIVersion defines the versioned schema of this representation of an object.
+              Servers should convert recognized schemas to the latest internal value, and
+              may reject unrecognized values.
+              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
+            type: string
+          kind:
+            description: |-
+              Kind is a string value representing the REST resource this object represents.
+              Servers may infer this from the endpoint the client submits requests to.
+              Cannot be updated.
+              In CamelCase.
+              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
+            type: string
+          metadata:
+            type: object
+          spec:
+            description: ProviderClassSpec defines the desired state of ProviderClass.
+            properties:
+              address:
+                minLength: 1
+                type: string
+            required:
+            - address
+            type: object
+          status:
+            description: ProviderClassStatus defines the observed state of ProviderClass.
+            properties:
+              conditions:
+                items:
+                  description: Condition contains details for one aspect of the current
+                    state of this API Resource.
+                  properties:
+                    lastTransitionTime:
+                      description: |-
+                        lastTransitionTime is the last time the condition transitioned from one status to another.
+                        This should be when the underlying condition changed.  If that is not known, then using the time when the API field changed is acceptable.
+                      format: date-time
+                      type: string
+                    message:
+                      description: |-
+                        message is a human readable message indicating details about the transition.
+                        This may be an empty string.
+                      maxLength: 32768
+                      type: string
+                    observedGeneration:
+                      description: |-
+                        observedGeneration represents the .metadata.generation that the condition was set based upon.
+                        For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date
+                        with respect to the current state of the instance.
+                      format: int64
+                      minimum: 0
+                      type: integer
+                    reason:
+                      description: |-
+                        reason contains a programmatic identifier indicating the reason for the condition's last transition.
+                        Producers of specific condition types may define expected values and meanings for this field,
+                        and whether the values are considered a guaranteed API.
+                        The value should be a CamelCase string.
+                        This field may not be empty.
+                      maxLength: 1024
+                      minLength: 1
+                      pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$
+                      type: string
+                    status:
+                      description: status of the condition, one of True, False, Unknown.
+                      enum:
+                      - "True"
+                      - "False"
+                      - Unknown
+                      type: string
+                    type:
+                      description: type of condition in CamelCase or in foo.example.com/CamelCase.
+                      maxLength: 316
+                      pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$
+                      type: string
+                  required:
+                  - lastTransitionTime
+                  - message
+                  - reason
+                  - status
+                  - type
+                  type: object
+                type: array
+            type: object
+        required:
+        - spec
+        type: object
+    served: true
+    storage: true
+    subresources:
+      status: {}

+ 13 - 5
config/crds/bases/external-secrets.io_pushsecrets.yaml

@@ -183,10 +183,14 @@ spec:
                       description: StoreRef specifies which SecretStore to push to.
                         Required.
                       properties:
+                        apiVersion:
+                          description: |-
+                            APIVersion of the referenced store resource.
+                            This field is optional and depends on the selected store kind.
+                          type: string
                         kind:
-                          default: SecretStore
-                          description: Kind of the SecretStore resource (SecretStore
-                            or ClusterSecretStore)
+                          description: Kind of the SecretStore resource (SecretStore,
+                            ClusterSecretStore)
                           enum:
                           - SecretStore
                           - ClusterSecretStore
@@ -272,9 +276,13 @@ spec:
                   description: PushSecretStoreRef contains a reference on how to sync
                     to a SecretStore.
                   properties:
+                    apiVersion:
+                      description: |-
+                        APIVersion of the referenced store resource.
+                        This field is optional and depends on the selected store kind.
+                      type: string
                     kind:
-                      default: SecretStore
-                      description: Kind of the SecretStore resource (SecretStore or
+                      description: Kind of the SecretStore resource (SecretStore,
                         ClusterSecretStore)
                       enum:
                       - SecretStore

+ 122 - 13
config/crds/bases/external-secrets.io_secretstores.yaml

@@ -556,7 +556,6 @@ spec:
                               ForceDeleteWithoutRecovery in the same call. If you don't use either,
                               then by default Secrets Manager uses a 30-day recovery window.
                               see: https://docs.aws.amazon.com/secretsmanager/latest/apireference/API_DeleteSecret.html#SecretsManager-DeleteSecret-request-RecoveryWindowInDays
-                            format: int64
                             type: integer
                         type: object
                       service:
@@ -2251,7 +2250,6 @@ spec:
                       appID:
                         description: appID specifies the Github APP that will be used
                           to authenticate the client
-                        format: int64
                         type: integer
                       auth:
                         description: auth configures how secret-manager authenticates
@@ -2296,7 +2294,6 @@ spec:
                       installationID:
                         description: installationID specifies the Github APP installation
                           that will be used to authenticate the client
-                        format: int64
                         type: integer
                       orgSecretVisibility:
                         description: |-
@@ -5574,7 +5571,6 @@ spec:
 
                                       Deprecated: this will be removed in the future.
                                       Defaults to 10 minutes.
-                                    format: int64
                                     type: integer
                                   serviceAccountRef:
                                     description: Service account field containing
@@ -6547,6 +6543,39 @@ spec:
                     - auth
                     type: object
                 type: object
+              providerRef:
+                description: ProviderRef references a provider configuration managed
+                  externally.
+                properties:
+                  apiVersion:
+                    description: APIVersion identifies the API schema version for
+                      the provider resource.
+                    minLength: 1
+                    type: string
+                  kind:
+                    description: Kind identifies the provider resource type referenced
+                      by this store.
+                    minLength: 1
+                    type: string
+                  name:
+                    description: Name is the provider resource name referenced by
+                      this store.
+                    maxLength: 253
+                    minLength: 1
+                    pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
+                    type: string
+                  namespace:
+                    description: Namespace is the provider resource namespace referenced
+                      by this store.
+                    maxLength: 63
+                    minLength: 1
+                    pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$
+                    type: string
+                required:
+                - apiVersion
+                - kind
+                - name
+                type: object
               refreshInterval:
                 description: Used to configure store refresh interval in seconds.
                   Empty or 0 will default to the controller config.
@@ -6555,14 +6584,39 @@ spec:
                 description: Used to configure HTTP retries on failures.
                 properties:
                   maxRetries:
-                    format: int32
                     type: integer
                   retryInterval:
                     type: string
                 type: object
-            required:
-            - provider
+              runtimeRef:
+                description: RuntimeRef points to runtime configuration for this store.
+                properties:
+                  kind:
+                    description: Kind identifies the runtime resource type referenced
+                      by this store.
+                    enum:
+                    - ProviderClass
+                    - ClusterProviderClass
+                    type: string
+                  name:
+                    description: Name is the runtime resource name referenced by this
+                      store.
+                    maxLength: 253
+                    minLength: 1
+                    pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
+                    type: string
+                required:
+                - name
+                type: object
             type: object
+            x-kubernetes-validations:
+            - message: exactly one of spec.provider or spec.providerRef must be set
+              rule: (has(self.provider) && !has(self.providerRef)) || (!has(self.provider)
+                && has(self.providerRef))
+            - message: spec.runtimeRef must be empty when spec.provider is set
+              rule: '!(has(self.provider) && has(self.runtimeRef))'
+            - message: spec.runtimeRef is required when spec.providerRef is set
+              rule: '!has(self.providerRef) || has(self.runtimeRef)'
           status:
             description: SecretStoreStatus defines the observed state of the SecretStore.
             properties:
@@ -7220,7 +7274,6 @@ spec:
                               ForceDeleteWithoutRecovery in the same call. If you don't use either,
                               then by default Secrets Manager uses a 30 day recovery window.
                               see: https://docs.aws.amazon.com/secretsmanager/latest/apireference/API_DeleteSecret.html#SecretsManager-DeleteSecret-request-RecoveryWindowInDays
-                            format: int64
                             type: integer
                         type: object
                       service:
@@ -8525,7 +8578,6 @@ spec:
                       appID:
                         description: appID specifies the Github APP that will be used
                           to authenticate the client
-                        format: int64
                         type: integer
                       auth:
                         description: auth configures how secret-manager authenticates
@@ -8570,7 +8622,6 @@ spec:
                       installationID:
                         description: installationID specifies the Github APP installation
                           that will be used to authenticate the client
-                        format: int64
                         type: integer
                       organization:
                         description: organization will be used to fetch secrets from
@@ -10263,7 +10314,6 @@ spec:
 
                                       Deprecated: this will be removed in the future.
                                       Defaults to 10 minutes.
-                                    format: int64
                                     type: integer
                                   serviceAccountRef:
                                     description: Service account field containing
@@ -11067,6 +11117,39 @@ spec:
                     - auth
                     type: object
                 type: object
+              providerRef:
+                description: ProviderRef references a provider configuration managed
+                  externally.
+                properties:
+                  apiVersion:
+                    description: APIVersion identifies the API schema version for
+                      the provider resource.
+                    minLength: 1
+                    type: string
+                  kind:
+                    description: Kind identifies the provider resource type referenced
+                      by this store.
+                    minLength: 1
+                    type: string
+                  name:
+                    description: Name is the provider resource name referenced by
+                      this store.
+                    maxLength: 253
+                    minLength: 1
+                    pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
+                    type: string
+                  namespace:
+                    description: Namespace is the provider resource namespace referenced
+                      by this store.
+                    maxLength: 63
+                    minLength: 1
+                    pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$
+                    type: string
+                required:
+                - apiVersion
+                - kind
+                - name
+                type: object
               refreshInterval:
                 description: Used to configure store refresh interval in seconds.
                   Empty or 0 will default to the controller config.
@@ -11082,9 +11165,35 @@ spec:
                     description: RetryInterval is the interval between retry attempts.
                     type: string
                 type: object
-            required:
-            - provider
+              runtimeRef:
+                description: RuntimeRef points to runtime configuration for this store.
+                properties:
+                  kind:
+                    description: Kind identifies the runtime resource type referenced
+                      by this store.
+                    enum:
+                    - ProviderClass
+                    - ClusterProviderClass
+                    type: string
+                  name:
+                    description: Name is the runtime resource name referenced by this
+                      store.
+                    maxLength: 253
+                    minLength: 1
+                    pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
+                    type: string
+                required:
+                - name
+                type: object
             type: object
+            x-kubernetes-validations:
+            - message: exactly one of spec.provider or spec.providerRef must be set
+              rule: (has(self.provider) && !has(self.providerRef)) || (!has(self.provider)
+                && has(self.providerRef))
+            - message: spec.runtimeRef must be empty when spec.provider is set
+              rule: '!(has(self.provider) && has(self.runtimeRef))'
+            - message: spec.runtimeRef is required when spec.providerRef is set
+              rule: '!has(self.providerRef) || has(self.runtimeRef)'
           status:
             description: SecretStoreStatus defines the observed state of the SecretStore.
             properties:

+ 0 - 2
config/crds/bases/generators.external-secrets.io_clustergenerators.yaml

@@ -1697,7 +1697,6 @@ spec:
 
                                           Deprecated: this will be removed in the future.
                                           Defaults to 10 minutes.
-                                        format: int64
                                         type: integer
                                       serviceAccountRef:
                                         description: Service account field containing
@@ -2187,7 +2186,6 @@ spec:
                         description: Used to configure http retries if failed
                         properties:
                           maxRetries:
-                            format: int32
                             type: integer
                           retryInterval:
                             type: string

+ 0 - 2
config/crds/bases/generators.external-secrets.io_vaultdynamicsecrets.yaml

@@ -556,7 +556,6 @@ spec:
 
                                   Deprecated: this will be removed in the future.
                                   Defaults to 10 minutes.
-                                format: int64
                                 type: integer
                               serviceAccountRef:
                                 description: Service account field containing the
@@ -1046,7 +1045,6 @@ spec:
                 description: Used to configure http retries if failed
                 properties:
                   maxRetries:
-                    format: int32
                     type: integer
                   retryInterval:
                     type: string

+ 6 - 0
config/crds/bases/kustomization.yaml

@@ -3,9 +3,11 @@ apiVersion: kustomize.config.k8s.io/v1beta1
 kind: Kustomization
 resources:
   - external-secrets.io_clusterexternalsecrets.yaml
+  - external-secrets.io_clusterproviderclasses.yaml
   - external-secrets.io_clusterpushsecrets.yaml
   - external-secrets.io_clustersecretstores.yaml
   - external-secrets.io_externalsecrets.yaml
+  - external-secrets.io_providerclasses.yaml
   - external-secrets.io_pushsecrets.yaml
   - external-secrets.io_secretstores.yaml
   - generators.external-secrets.io_acraccesstokens.yaml
@@ -25,3 +27,7 @@ resources:
   - generators.external-secrets.io_uuids.yaml
   - generators.external-secrets.io_vaultdynamicsecrets.yaml
   - generators.external-secrets.io_webhooks.yaml
+  - provider.external-secrets.io_fakes.yaml
+  - provider.external-secrets.io_kubernetes.yaml
+  - provider.external-secrets.io_parameterstores.yaml
+  - provider.external-secrets.io_secretsmanagers.yaml

+ 75 - 0
config/crds/bases/provider.external-secrets.io_fakes.yaml

@@ -0,0 +1,75 @@
+apiVersion: apiextensions.k8s.io/v1
+kind: CustomResourceDefinition
+metadata:
+  annotations:
+    controller-gen.kubebuilder.io/version: v0.19.0
+  name: fakes.provider.external-secrets.io
+spec:
+  group: provider.external-secrets.io
+  names:
+    categories:
+    - external-secrets
+    kind: Fake
+    listKind: FakeList
+    plural: fakes
+    shortNames:
+    - fake
+    singular: fake
+  scope: Namespaced
+  versions:
+  - name: v2alpha1
+    schema:
+      openAPIV3Schema:
+        description: |-
+          Fake defines the configuration for the Fake provider.
+          This provider returns static key-value pairs for testing purposes.
+        properties:
+          apiVersion:
+            description: |-
+              APIVersion defines the versioned schema of this representation of an object.
+              Servers should convert recognized schemas to the latest internal value, and
+              may reject unrecognized values.
+              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
+            type: string
+          kind:
+            description: |-
+              Kind is a string value representing the REST resource this object represents.
+              Servers may infer this from the endpoint the client submits requests to.
+              Cannot be updated.
+              In CamelCase.
+              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
+            type: string
+          metadata:
+            type: object
+          spec:
+            description: FakeProvider configures a fake provider that returns static
+              values.
+            properties:
+              data:
+                items:
+                  description: FakeProviderData defines a key-value pair with optional
+                    version for the fake provider.
+                  properties:
+                    key:
+                      type: string
+                    value:
+                      type: string
+                    version:
+                      type: string
+                  required:
+                  - key
+                  - value
+                  type: object
+                type: array
+              validationResult:
+                description: ValidationResult is defined type for the number of validation
+                  results.
+                type: integer
+            required:
+            - data
+            type: object
+        type: object
+    served: true
+    storage: true
+    subresources:
+      status: {}

+ 265 - 0
config/crds/bases/provider.external-secrets.io_kubernetes.yaml

@@ -0,0 +1,265 @@
+apiVersion: apiextensions.k8s.io/v1
+kind: CustomResourceDefinition
+metadata:
+  annotations:
+    controller-gen.kubebuilder.io/version: v0.19.0
+  name: kubernetes.provider.external-secrets.io
+spec:
+  group: provider.external-secrets.io
+  names:
+    categories:
+    - external-secrets
+    kind: Kubernetes
+    listKind: KubernetesList
+    plural: kubernetes
+    singular: kubernetes
+  scope: Namespaced
+  versions:
+  - name: v2alpha1
+    schema:
+      openAPIV3Schema:
+        description: |-
+          Kubernetes defines the configuration for the Kubernetes Secret provider.
+          This provider fetches secrets from Kubernetes Secrets in the same cluster.
+          It's primarily useful for testing and migration scenarios.
+        properties:
+          apiVersion:
+            description: |-
+              APIVersion defines the versioned schema of this representation of an object.
+              Servers should convert recognized schemas to the latest internal value, and
+              may reject unrecognized values.
+              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
+            type: string
+          kind:
+            description: |-
+              Kind is a string value representing the REST resource this object represents.
+              Servers may infer this from the endpoint the client submits requests to.
+              Cannot be updated.
+              In CamelCase.
+              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
+            type: string
+          metadata:
+            type: object
+          spec:
+            description: KubernetesProvider configures a store to sync secrets with
+              a Kubernetes instance.
+            properties:
+              auth:
+                description: Auth configures how secret-manager authenticates with
+                  a Kubernetes instance.
+                maxProperties: 1
+                minProperties: 1
+                properties:
+                  cert:
+                    description: has both clientCert and clientKey as secretKeySelector
+                    properties:
+                      clientCert:
+                        description: |-
+                          SecretKeySelector is a reference to a specific 'key' within a Secret resource.
+                          In some instances, `key` is a required field.
+                        properties:
+                          key:
+                            description: |-
+                              A key in the referenced Secret.
+                              Some instances of this field may be defaulted, in others it may be required.
+                            maxLength: 253
+                            minLength: 1
+                            pattern: ^[-._a-zA-Z0-9]+$
+                            type: string
+                          name:
+                            description: The name of the Secret resource being referred
+                              to.
+                            maxLength: 253
+                            minLength: 1
+                            pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
+                            type: string
+                          namespace:
+                            description: |-
+                              The namespace of the Secret resource being referred to.
+                              Ignored if referent is not cluster-scoped, otherwise defaults to the namespace of the referent.
+                            maxLength: 63
+                            minLength: 1
+                            pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$
+                            type: string
+                        type: object
+                      clientKey:
+                        description: |-
+                          SecretKeySelector is a reference to a specific 'key' within a Secret resource.
+                          In some instances, `key` is a required field.
+                        properties:
+                          key:
+                            description: |-
+                              A key in the referenced Secret.
+                              Some instances of this field may be defaulted, in others it may be required.
+                            maxLength: 253
+                            minLength: 1
+                            pattern: ^[-._a-zA-Z0-9]+$
+                            type: string
+                          name:
+                            description: The name of the Secret resource being referred
+                              to.
+                            maxLength: 253
+                            minLength: 1
+                            pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
+                            type: string
+                          namespace:
+                            description: |-
+                              The namespace of the Secret resource being referred to.
+                              Ignored if referent is not cluster-scoped, otherwise defaults to the namespace of the referent.
+                            maxLength: 63
+                            minLength: 1
+                            pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$
+                            type: string
+                        type: object
+                    type: object
+                  serviceAccount:
+                    description: points to a service account that should be used for
+                      authentication
+                    properties:
+                      audiences:
+                        description: |-
+                          Audience specifies the `aud` claim for the service account token
+                          If the service account uses a well-known annotation for e.g. IRSA or GCP Workload Identity
+                          then this audiences will be appended to the list
+                        items:
+                          type: string
+                        type: array
+                      name:
+                        description: The name of the ServiceAccount resource being
+                          referred to.
+                        maxLength: 253
+                        minLength: 1
+                        pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
+                        type: string
+                      namespace:
+                        description: |-
+                          Namespace of the resource being referred to.
+                          Ignored if referent is not cluster-scoped, otherwise defaults to the namespace of the referent.
+                        maxLength: 63
+                        minLength: 1
+                        pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$
+                        type: string
+                    required:
+                    - name
+                    type: object
+                  token:
+                    description: use static token to authenticate with
+                    properties:
+                      bearerToken:
+                        description: |-
+                          SecretKeySelector is a reference to a specific 'key' within a Secret resource.
+                          In some instances, `key` is a required field.
+                        properties:
+                          key:
+                            description: |-
+                              A key in the referenced Secret.
+                              Some instances of this field may be defaulted, in others it may be required.
+                            maxLength: 253
+                            minLength: 1
+                            pattern: ^[-._a-zA-Z0-9]+$
+                            type: string
+                          name:
+                            description: The name of the Secret resource being referred
+                              to.
+                            maxLength: 253
+                            minLength: 1
+                            pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
+                            type: string
+                          namespace:
+                            description: |-
+                              The namespace of the Secret resource being referred to.
+                              Ignored if referent is not cluster-scoped, otherwise defaults to the namespace of the referent.
+                            maxLength: 63
+                            minLength: 1
+                            pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$
+                            type: string
+                        type: object
+                    type: object
+                type: object
+              authRef:
+                description: A reference to a secret that contains the auth information.
+                properties:
+                  key:
+                    description: |-
+                      A key in the referenced Secret.
+                      Some instances of this field may be defaulted, in others it may be required.
+                    maxLength: 253
+                    minLength: 1
+                    pattern: ^[-._a-zA-Z0-9]+$
+                    type: string
+                  name:
+                    description: The name of the Secret resource being referred to.
+                    maxLength: 253
+                    minLength: 1
+                    pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
+                    type: string
+                  namespace:
+                    description: |-
+                      The namespace of the Secret resource being referred to.
+                      Ignored if referent is not cluster-scoped, otherwise defaults to the namespace of the referent.
+                    maxLength: 63
+                    minLength: 1
+                    pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$
+                    type: string
+                type: object
+              remoteNamespace:
+                default: default
+                description: Remote namespace to fetch the secrets from
+                maxLength: 63
+                minLength: 1
+                pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$
+                type: string
+              server:
+                description: configures the Kubernetes server Address.
+                properties:
+                  caBundle:
+                    description: CABundle is a base64-encoded CA certificate
+                    format: byte
+                    type: string
+                  caProvider:
+                    description: 'see: https://external-secrets.io/v0.4.1/spec/#external-secrets.io/v1alpha1.CAProvider'
+                    properties:
+                      key:
+                        description: The key where the CA certificate can be found
+                          in the Secret or ConfigMap.
+                        maxLength: 253
+                        minLength: 1
+                        pattern: ^[-._a-zA-Z0-9]+$
+                        type: string
+                      name:
+                        description: The name of the object located at the provider
+                          type.
+                        maxLength: 253
+                        minLength: 1
+                        pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
+                        type: string
+                      namespace:
+                        description: |-
+                          The namespace the Provider type is in.
+                          Can only be defined when used in a ClusterSecretStore.
+                        maxLength: 63
+                        minLength: 1
+                        pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$
+                        type: string
+                      type:
+                        description: The type of provider to use such as "Secret",
+                          or "ConfigMap".
+                        enum:
+                        - Secret
+                        - ConfigMap
+                        type: string
+                    required:
+                    - name
+                    - type
+                    type: object
+                  url:
+                    default: kubernetes.default
+                    description: configures the Kubernetes server Address.
+                    type: string
+                type: object
+            type: object
+        type: object
+    served: true
+    storage: true
+    subresources:
+      status: {}

+ 294 - 0
config/crds/bases/provider.external-secrets.io_parameterstores.yaml

@@ -0,0 +1,294 @@
+apiVersion: apiextensions.k8s.io/v1
+kind: CustomResourceDefinition
+metadata:
+  annotations:
+    controller-gen.kubebuilder.io/version: v0.19.0
+  name: parameterstores.provider.external-secrets.io
+spec:
+  group: provider.external-secrets.io
+  names:
+    categories:
+    - externalsecrets
+    kind: ParameterStore
+    listKind: ParameterStoreList
+    plural: parameterstores
+    shortNames:
+    - ssm
+    singular: parameterstore
+  scope: Namespaced
+  versions:
+  - additionalPrinterColumns:
+    - jsonPath: .spec.region
+      name: Region
+      type: string
+    - jsonPath: .metadata.creationTimestamp
+      name: Age
+      type: date
+    name: v2alpha1
+    schema:
+      openAPIV3Schema:
+        description: ParameterStore is the Schema for AWS Parameter Store provider
+          configuration.
+        properties:
+          apiVersion:
+            description: |-
+              APIVersion defines the versioned schema of this representation of an object.
+              Servers should convert recognized schemas to the latest internal value, and
+              may reject unrecognized values.
+              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
+            type: string
+          kind:
+            description: |-
+              Kind is a string value representing the REST resource this object represents.
+              Servers may infer this from the endpoint the client submits requests to.
+              Cannot be updated.
+              In CamelCase.
+              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
+            type: string
+          metadata:
+            type: object
+          spec:
+            description: ParameterStoreSpec defines the desired state of ParameterStore.
+            properties:
+              additionalRoles:
+                description: AdditionalRoles is a chained list of Role ARNs which
+                  the provider will sequentially assume before assuming the Role
+                items:
+                  type: string
+                type: array
+              auth:
+                description: |-
+                  Auth defines the information necessary to authenticate against AWS
+                  if not set aws sdk will infer credentials from your environment
+                  see: https://docs.aws.amazon.com/sdk-for-go/v1/developer-guide/configuring-sdk.html#specifying-credentials
+                properties:
+                  jwt:
+                    description: AWSJWTAuth stores reference to Authenticate against
+                      AWS using service account tokens.
+                    properties:
+                      serviceAccountRef:
+                        description: ServiceAccountSelector is a reference to a ServiceAccount
+                          resource.
+                        properties:
+                          audiences:
+                            description: |-
+                              Audience specifies the `aud` claim for the service account token
+                              If the service account uses a well-known annotation for e.g. IRSA or GCP Workload Identity
+                              then this audiences will be appended to the list
+                            items:
+                              type: string
+                            type: array
+                          name:
+                            description: The name of the ServiceAccount resource being
+                              referred to.
+                            maxLength: 253
+                            minLength: 1
+                            pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
+                            type: string
+                          namespace:
+                            description: |-
+                              Namespace of the resource being referred to.
+                              Ignored if referent is not cluster-scoped, otherwise defaults to the namespace of the referent.
+                            maxLength: 63
+                            minLength: 1
+                            pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$
+                            type: string
+                        required:
+                        - name
+                        type: object
+                    type: object
+                  secretRef:
+                    description: |-
+                      AWSAuthSecretRef holds secret references for AWS credentials
+                      both AccessKeyID and SecretAccessKey must be defined in order to properly authenticate.
+                    properties:
+                      accessKeyIDSecretRef:
+                        description: The AccessKeyID is used for authentication
+                        properties:
+                          key:
+                            description: |-
+                              A key in the referenced Secret.
+                              Some instances of this field may be defaulted, in others it may be required.
+                            maxLength: 253
+                            minLength: 1
+                            pattern: ^[-._a-zA-Z0-9]+$
+                            type: string
+                          name:
+                            description: The name of the Secret resource being referred
+                              to.
+                            maxLength: 253
+                            minLength: 1
+                            pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
+                            type: string
+                          namespace:
+                            description: |-
+                              The namespace of the Secret resource being referred to.
+                              Ignored if referent is not cluster-scoped, otherwise defaults to the namespace of the referent.
+                            maxLength: 63
+                            minLength: 1
+                            pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$
+                            type: string
+                        type: object
+                      secretAccessKeySecretRef:
+                        description: The SecretAccessKey is used for authentication
+                        properties:
+                          key:
+                            description: |-
+                              A key in the referenced Secret.
+                              Some instances of this field may be defaulted, in others it may be required.
+                            maxLength: 253
+                            minLength: 1
+                            pattern: ^[-._a-zA-Z0-9]+$
+                            type: string
+                          name:
+                            description: The name of the Secret resource being referred
+                              to.
+                            maxLength: 253
+                            minLength: 1
+                            pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
+                            type: string
+                          namespace:
+                            description: |-
+                              The namespace of the Secret resource being referred to.
+                              Ignored if referent is not cluster-scoped, otherwise defaults to the namespace of the referent.
+                            maxLength: 63
+                            minLength: 1
+                            pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$
+                            type: string
+                        type: object
+                      sessionTokenSecretRef:
+                        description: |-
+                          The SessionToken used for authentication
+                          This must be defined if AccessKeyID and SecretAccessKey are temporary credentials
+                          see: https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_temp_use-resources.html
+                        properties:
+                          key:
+                            description: |-
+                              A key in the referenced Secret.
+                              Some instances of this field may be defaulted, in others it may be required.
+                            maxLength: 253
+                            minLength: 1
+                            pattern: ^[-._a-zA-Z0-9]+$
+                            type: string
+                          name:
+                            description: The name of the Secret resource being referred
+                              to.
+                            maxLength: 253
+                            minLength: 1
+                            pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
+                            type: string
+                          namespace:
+                            description: |-
+                              The namespace of the Secret resource being referred to.
+                              Ignored if referent is not cluster-scoped, otherwise defaults to the namespace of the referent.
+                            maxLength: 63
+                            minLength: 1
+                            pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$
+                            type: string
+                        type: object
+                    type: object
+                type: object
+              externalID:
+                description: AWS External ID set on assumed IAM roles
+                type: string
+              prefix:
+                description: Prefix adds a prefix to all retrieved values.
+                type: string
+              region:
+                description: AWS Region to be used for the provider
+                type: string
+              role:
+                description: Role is a Role ARN which the provider will assume
+                type: string
+              sessionTags:
+                description: AWS STS assume role session tags
+                items:
+                  description: |-
+                    Tag is a key-value pair that can be attached to an AWS resource.
+                    see: https://docs.aws.amazon.com/general/latest/gr/aws_tagging.html
+                  properties:
+                    key:
+                      type: string
+                    value:
+                      type: string
+                  required:
+                  - key
+                  - value
+                  type: object
+                type: array
+              transitiveTagKeys:
+                description: AWS STS assume role transitive session tags. Required
+                  when multiple rules are used with the provider
+                items:
+                  type: string
+                type: array
+            required:
+            - region
+            type: object
+          status:
+            description: ParameterStoreStatus defines the observed state of ParameterStore.
+            properties:
+              conditions:
+                description: Conditions represent the latest available observations
+                  of the resource's state.
+                items:
+                  description: Condition contains details for one aspect of the current
+                    state of this API Resource.
+                  properties:
+                    lastTransitionTime:
+                      description: |-
+                        lastTransitionTime is the last time the condition transitioned from one status to another.
+                        This should be when the underlying condition changed.  If that is not known, then using the time when the API field changed is acceptable.
+                      format: date-time
+                      type: string
+                    message:
+                      description: |-
+                        message is a human readable message indicating details about the transition.
+                        This may be an empty string.
+                      maxLength: 32768
+                      type: string
+                    observedGeneration:
+                      description: |-
+                        observedGeneration represents the .metadata.generation that the condition was set based upon.
+                        For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date
+                        with respect to the current state of the instance.
+                      format: int64
+                      minimum: 0
+                      type: integer
+                    reason:
+                      description: |-
+                        reason contains a programmatic identifier indicating the reason for the condition's last transition.
+                        Producers of specific condition types may define expected values and meanings for this field,
+                        and whether the values are considered a guaranteed API.
+                        The value should be a CamelCase string.
+                        This field may not be empty.
+                      maxLength: 1024
+                      minLength: 1
+                      pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$
+                      type: string
+                    status:
+                      description: status of the condition, one of True, False, Unknown.
+                      enum:
+                      - "True"
+                      - "False"
+                      - Unknown
+                      type: string
+                    type:
+                      description: type of condition in CamelCase or in foo.example.com/CamelCase.
+                      maxLength: 316
+                      pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$
+                      type: string
+                  required:
+                  - lastTransitionTime
+                  - message
+                  - reason
+                  - status
+                  - type
+                  type: object
+                type: array
+            type: object
+        type: object
+    served: true
+    storage: true
+    subresources:
+      status: {}

+ 315 - 0
config/crds/bases/provider.external-secrets.io_secretsmanagers.yaml

@@ -0,0 +1,315 @@
+apiVersion: apiextensions.k8s.io/v1
+kind: CustomResourceDefinition
+metadata:
+  annotations:
+    controller-gen.kubebuilder.io/version: v0.19.0
+  name: secretsmanagers.provider.external-secrets.io
+spec:
+  group: provider.external-secrets.io
+  names:
+    categories:
+    - externalsecrets
+    kind: SecretsManager
+    listKind: SecretsManagerList
+    plural: secretsmanagers
+    shortNames:
+    - sm
+    singular: secretsmanager
+  scope: Namespaced
+  versions:
+  - additionalPrinterColumns:
+    - jsonPath: .spec.region
+      name: Region
+      type: string
+    - jsonPath: .metadata.creationTimestamp
+      name: Age
+      type: date
+    name: v2alpha1
+    schema:
+      openAPIV3Schema:
+        description: SecretsManager is the Schema for AWS Secrets Manager provider
+          configuration.
+        properties:
+          apiVersion:
+            description: |-
+              APIVersion defines the versioned schema of this representation of an object.
+              Servers should convert recognized schemas to the latest internal value, and
+              may reject unrecognized values.
+              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
+            type: string
+          kind:
+            description: |-
+              Kind is a string value representing the REST resource this object represents.
+              Servers may infer this from the endpoint the client submits requests to.
+              Cannot be updated.
+              In CamelCase.
+              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
+            type: string
+          metadata:
+            type: object
+          spec:
+            description: SecretsManagerSpec defines the desired state of SecretsManager.
+            properties:
+              additionalRoles:
+                description: AdditionalRoles is a chained list of Role ARNs which
+                  the provider will sequentially assume before assuming the Role
+                items:
+                  type: string
+                type: array
+              auth:
+                description: |-
+                  Auth defines the information necessary to authenticate against AWS
+                  if not set aws sdk will infer credentials from your environment
+                  see: https://docs.aws.amazon.com/sdk-for-go/v1/developer-guide/configuring-sdk.html#specifying-credentials
+                properties:
+                  jwt:
+                    description: AWSJWTAuth stores reference to Authenticate against
+                      AWS using service account tokens.
+                    properties:
+                      serviceAccountRef:
+                        description: ServiceAccountSelector is a reference to a ServiceAccount
+                          resource.
+                        properties:
+                          audiences:
+                            description: |-
+                              Audience specifies the `aud` claim for the service account token
+                              If the service account uses a well-known annotation for e.g. IRSA or GCP Workload Identity
+                              then this audiences will be appended to the list
+                            items:
+                              type: string
+                            type: array
+                          name:
+                            description: The name of the ServiceAccount resource being
+                              referred to.
+                            maxLength: 253
+                            minLength: 1
+                            pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
+                            type: string
+                          namespace:
+                            description: |-
+                              Namespace of the resource being referred to.
+                              Ignored if referent is not cluster-scoped, otherwise defaults to the namespace of the referent.
+                            maxLength: 63
+                            minLength: 1
+                            pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$
+                            type: string
+                        required:
+                        - name
+                        type: object
+                    type: object
+                  secretRef:
+                    description: |-
+                      AWSAuthSecretRef holds secret references for AWS credentials
+                      both AccessKeyID and SecretAccessKey must be defined in order to properly authenticate.
+                    properties:
+                      accessKeyIDSecretRef:
+                        description: The AccessKeyID is used for authentication
+                        properties:
+                          key:
+                            description: |-
+                              A key in the referenced Secret.
+                              Some instances of this field may be defaulted, in others it may be required.
+                            maxLength: 253
+                            minLength: 1
+                            pattern: ^[-._a-zA-Z0-9]+$
+                            type: string
+                          name:
+                            description: The name of the Secret resource being referred
+                              to.
+                            maxLength: 253
+                            minLength: 1
+                            pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
+                            type: string
+                          namespace:
+                            description: |-
+                              The namespace of the Secret resource being referred to.
+                              Ignored if referent is not cluster-scoped, otherwise defaults to the namespace of the referent.
+                            maxLength: 63
+                            minLength: 1
+                            pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$
+                            type: string
+                        type: object
+                      secretAccessKeySecretRef:
+                        description: The SecretAccessKey is used for authentication
+                        properties:
+                          key:
+                            description: |-
+                              A key in the referenced Secret.
+                              Some instances of this field may be defaulted, in others it may be required.
+                            maxLength: 253
+                            minLength: 1
+                            pattern: ^[-._a-zA-Z0-9]+$
+                            type: string
+                          name:
+                            description: The name of the Secret resource being referred
+                              to.
+                            maxLength: 253
+                            minLength: 1
+                            pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
+                            type: string
+                          namespace:
+                            description: |-
+                              The namespace of the Secret resource being referred to.
+                              Ignored if referent is not cluster-scoped, otherwise defaults to the namespace of the referent.
+                            maxLength: 63
+                            minLength: 1
+                            pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$
+                            type: string
+                        type: object
+                      sessionTokenSecretRef:
+                        description: |-
+                          The SessionToken used for authentication
+                          This must be defined if AccessKeyID and SecretAccessKey are temporary credentials
+                          see: https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_temp_use-resources.html
+                        properties:
+                          key:
+                            description: |-
+                              A key in the referenced Secret.
+                              Some instances of this field may be defaulted, in others it may be required.
+                            maxLength: 253
+                            minLength: 1
+                            pattern: ^[-._a-zA-Z0-9]+$
+                            type: string
+                          name:
+                            description: The name of the Secret resource being referred
+                              to.
+                            maxLength: 253
+                            minLength: 1
+                            pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
+                            type: string
+                          namespace:
+                            description: |-
+                              The namespace of the Secret resource being referred to.
+                              Ignored if referent is not cluster-scoped, otherwise defaults to the namespace of the referent.
+                            maxLength: 63
+                            minLength: 1
+                            pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$
+                            type: string
+                        type: object
+                    type: object
+                type: object
+              externalID:
+                description: AWS External ID set on assumed IAM roles
+                type: string
+              prefix:
+                description: Prefix adds a prefix to all retrieved values.
+                type: string
+              region:
+                description: AWS Region to be used for the provider
+                type: string
+              role:
+                description: Role is a Role ARN which the provider will assume
+                type: string
+              secretsManager:
+                description: SecretsManager defines how the provider behaves when
+                  interacting with AWS SecretsManager
+                properties:
+                  forceDeleteWithoutRecovery:
+                    description: |-
+                      Specifies whether to delete the secret without any recovery window. You
+                      can't use both this parameter and RecoveryWindowInDays in the same call.
+                      If you don't use either, then by default Secrets Manager uses a 30 day
+                      recovery window.
+                      see: https://docs.aws.amazon.com/secretsmanager/latest/apireference/API_DeleteSecret.html#SecretsManager-DeleteSecret-request-ForceDeleteWithoutRecovery
+                    type: boolean
+                  recoveryWindowInDays:
+                    description: |-
+                      The number of days from 7 to 30 that Secrets Manager waits before
+                      permanently deleting the secret. You can't use both this parameter and
+                      ForceDeleteWithoutRecovery in the same call. If you don't use either,
+                      then by default Secrets Manager uses a 30-day recovery window.
+                      see: https://docs.aws.amazon.com/secretsmanager/latest/apireference/API_DeleteSecret.html#SecretsManager-DeleteSecret-request-RecoveryWindowInDays
+                    type: integer
+                type: object
+              sessionTags:
+                description: AWS STS assume role session tags
+                items:
+                  description: |-
+                    Tag is a key-value pair that can be attached to an AWS resource.
+                    see: https://docs.aws.amazon.com/general/latest/gr/aws_tagging.html
+                  properties:
+                    key:
+                      type: string
+                    value:
+                      type: string
+                  required:
+                  - key
+                  - value
+                  type: object
+                type: array
+              transitiveTagKeys:
+                description: AWS STS assume role transitive session tags. Required
+                  when multiple rules are used with the provider
+                items:
+                  type: string
+                type: array
+            required:
+            - region
+            type: object
+          status:
+            description: SecretsManagerStatus defines the observed state of SecretsManager.
+            properties:
+              conditions:
+                description: Conditions represent the latest available observations
+                  of the resource's state.
+                items:
+                  description: Condition contains details for one aspect of the current
+                    state of this API Resource.
+                  properties:
+                    lastTransitionTime:
+                      description: |-
+                        lastTransitionTime is the last time the condition transitioned from one status to another.
+                        This should be when the underlying condition changed.  If that is not known, then using the time when the API field changed is acceptable.
+                      format: date-time
+                      type: string
+                    message:
+                      description: |-
+                        message is a human readable message indicating details about the transition.
+                        This may be an empty string.
+                      maxLength: 32768
+                      type: string
+                    observedGeneration:
+                      description: |-
+                        observedGeneration represents the .metadata.generation that the condition was set based upon.
+                        For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date
+                        with respect to the current state of the instance.
+                      format: int64
+                      minimum: 0
+                      type: integer
+                    reason:
+                      description: |-
+                        reason contains a programmatic identifier indicating the reason for the condition's last transition.
+                        Producers of specific condition types may define expected values and meanings for this field,
+                        and whether the values are considered a guaranteed API.
+                        The value should be a CamelCase string.
+                        This field may not be empty.
+                      maxLength: 1024
+                      minLength: 1
+                      pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$
+                      type: string
+                    status:
+                      description: status of the condition, one of True, False, Unknown.
+                      enum:
+                      - "True"
+                      - "False"
+                      - Unknown
+                      type: string
+                    type:
+                      description: type of condition in CamelCase or in foo.example.com/CamelCase.
+                      maxLength: 316
+                      pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$
+                      type: string
+                  required:
+                  - lastTransitionTime
+                  - message
+                  - reason
+                  - status
+                  - type
+                  type: object
+                type: array
+            type: object
+        type: object
+    served: true
+    storage: true
+    subresources:
+      status: {}

+ 207 - 0
deploy/charts/README.md

@@ -0,0 +1,207 @@
+# External Secrets Operator V2 - Helm Charts
+
+This directory contains production-ready Helm charts for External Secrets Operator V2.
+
+## Available Charts
+
+### [external-secrets-v2](./external-secrets-v2/)
+
+Main controller chart for External Secrets Operator V2.
+
+**Install**:
+```bash
+helm install external-secrets-v2 ./external-secrets-v2 \
+  --namespace external-secrets-system \
+  --create-namespace
+```
+
+**Features**:
+- Automatic TLS certificate management
+- Leader election for HA
+- Prometheus metrics
+- Security hardening
+- Flexible RBAC
+
+[📖 Chart Documentation](./external-secrets-v2/README.md)
+
+### [external-secrets-v2-provider-aws](./external-secrets-v2-provider-aws/)
+
+AWS Secrets Manager provider for External Secrets Operator V2.
+
+**Install**:
+```bash
+helm install aws-provider ./external-secrets-v2-provider-aws \
+  --namespace external-secrets-system \
+  --set aws.region=us-east-1 \
+  --set serviceAccount.annotations."eks\.amazonaws\.com/role-arn"="arn:aws:iam::ACCOUNT:role/ROLE"
+```
+
+**Features**:
+- IRSA (IAM Roles for Service Accounts) support
+- Connection pooling (50x faster)
+- Auto-scaling support
+- High availability
+
+[📖 Chart Documentation](./external-secrets-v2-provider-aws/README.md)
+
+## Quick Start
+
+### 1. Install Controller
+
+```bash
+helm install external-secrets-v2 ./external-secrets-v2 \
+  --namespace external-secrets-system \
+  --create-namespace \
+  --wait
+```
+
+### 2. Install Provider
+
+```bash
+helm install aws-provider ./external-secrets-v2-provider-aws \
+  --namespace external-secrets-system \
+  --set aws.region=us-east-1 \
+  --set serviceAccount.annotations."eks\.amazonaws\.com/role-arn"="arn:aws:iam::123456789012:role/eso-aws" \
+  --wait
+```
+
+### 3. Verify
+
+```bash
+kubectl get pods -n external-secrets-system
+```
+
+## Documentation
+
+- 📘 [Quick Start Guide](../../examples/v2/helm-quick-start.md)
+- 📗 [Installation Guide](../../docs/guides/helm-v2-installation.md)
+- 📙 [Design Document](../../design/014-helm-charts-implementation.md)
+
+## Testing
+
+Run automated tests:
+
+```bash
+../../hack/test-helm-charts.sh all
+```
+
+## Development
+
+### Lint Charts
+
+```bash
+helm lint ./external-secrets-v2
+helm lint ./external-secrets-v2-provider-aws
+```
+
+### Template Rendering
+
+```bash
+helm template test ./external-secrets-v2 > rendered-controller.yaml
+helm template test ./external-secrets-v2-provider-aws > rendered-provider.yaml
+```
+
+### Dry Run
+
+```bash
+helm install --dry-run test ./external-secrets-v2
+helm install --dry-run test ./external-secrets-v2-provider-aws
+```
+
+## Production Deployment
+
+### High Availability
+
+```yaml
+# values-ha.yaml
+replicaCount: 3
+
+podDisruptionBudget:
+  enabled: true
+  minAvailable: 2
+
+metrics:
+  enabled: true
+  serviceMonitor:
+    enabled: true
+
+affinity:
+  podAntiAffinity:
+    requiredDuringSchedulingIgnoredDuringExecution:
+    - labelSelector:
+        matchLabels:
+          app.kubernetes.io/name: external-secrets-v2
+      topologyKey: kubernetes.io/hostname
+```
+
+```bash
+helm install external-secrets-v2 ./external-secrets-v2 \
+  --namespace external-secrets-system \
+  --create-namespace \
+  -f values-ha.yaml
+```
+
+## GitOps
+
+### ArgoCD
+
+```yaml
+apiVersion: argoproj.io/v1alpha1
+kind: Application
+metadata:
+  name: external-secrets-v2
+spec:
+  project: default
+  source:
+    repoURL: https://charts.external-secrets.io
+    chart: external-secrets-v2
+    targetRevision: 0.1.0-alpha.1
+  destination:
+    server: https://kubernetes.default.svc
+    namespace: external-secrets-system
+  syncPolicy:
+    automated:
+      prune: true
+      selfHeal: true
+    syncOptions:
+    - CreateNamespace=true
+```
+
+### Flux
+
+```yaml
+apiVersion: helm.toolkit.fluxcd.io/v2beta1
+kind: HelmRelease
+metadata:
+  name: external-secrets-v2
+  namespace: flux-system
+spec:
+  interval: 10m
+  chart:
+    spec:
+      chart: external-secrets-v2
+      version: 0.1.0-alpha.1
+      sourceRef:
+        kind: HelmRepository
+        name: external-secrets
+  targetNamespace: external-secrets-system
+  install:
+    createNamespace: true
+```
+
+## Chart Versions
+
+| Chart | Version | App Version | Status |
+|-------|---------|-------------|--------|
+| external-secrets-v2 | 0.1.0-alpha.1 | v0.1.0-alpha.1 | Alpha |
+| external-secrets-v2-provider-aws | 0.1.0-alpha.1 | v0.1.0-alpha.1 | Alpha |
+
+## Support
+
+- 🐛 [Report Issues](https://github.com/external-secrets/external-secrets/issues)
+- 💬 [Slack](https://kubernetes.slack.com/messages/external-secrets)
+- 📚 [Documentation](https://external-secrets.io)
+
+## License
+
+Apache 2.0 - See [LICENSE](../../LICENSE)

+ 472 - 0
deploy/charts/external-secrets/PROVIDERS.md

@@ -0,0 +1,472 @@
+# Providers Reference
+
+This guide explains how to deploy External Secrets with integrated provider deployments using the monolithic Helm chart.
+
+## Overview
+
+The External Secrets Helm chart now supports deploying one or multiple secret providers alongside the controller in a single installation. Each provider runs as an independent deployment with its own configuration, allowing you to:
+
+- Deploy multiple providers simultaneously (AWS, GCP, Azure, Vault, etc.)
+- Configure each provider independently with specific resource limits, security contexts, and authentication
+- Scale providers independently based on workload requirements
+- Enable high availability with pod disruption budgets and anti-affinity rules
+
+## Basic Configuration
+
+### Enable Provider Deployments
+
+Set `providers.enabled` to `true` and define your providers in the `providers.list` array:
+
+```yaml
+providers:
+  enabled: true
+  list:
+    - name: aws
+      type: aws
+      enabled: true
+```
+
+### Provider Structure
+
+Each provider in the list supports the following configuration:
+
+| Field | Type | Description | Required |
+|-------|------|-------------|----------|
+| `name` | string | Unique name for this provider instance | Yes |
+| `type` | string | Provider type (aws, gcp, azure, vault, etc.) | Yes |
+| `enabled` | boolean | Enable/disable this provider | Yes |
+| `replicaCount` | int | Number of replicas (default: 2) | No |
+
+## Configuration Sections
+
+### Image Configuration
+
+```yaml
+image:
+  repository: oci.external-secrets.io/external-secrets/provider-aws
+  pullPolicy: IfNotPresent
+  tag: ""  # Defaults to chart appVersion
+```
+
+### Service Account
+
+```yaml
+serviceAccount:
+  create: true
+  annotations:
+    # Example: AWS IRSA
+    eks.amazonaws.com/role-arn: arn:aws:iam::123456789012:role/eso-provider-aws
+    # Example: GCP Workload Identity
+    # iam.gke.io/gcp-service-account: eso-provider@project.iam.gserviceaccount.com
+    # Example: Azure Workload Identity
+    # azure.workload.identity/client-id: "00000000-0000-0000-0000-000000000000"
+  name: ""  # Auto-generated if empty
+  automount: true
+```
+
+### Security Contexts
+
+```yaml
+podSecurityContext:
+  enabled: true
+  runAsNonRoot: true
+  runAsUser: 65532
+  fsGroup: 65532
+  seccompProfile:
+    type: RuntimeDefault
+
+securityContext:
+  enabled: true
+  allowPrivilegeEscalation: false
+  readOnlyRootFilesystem: true
+  runAsNonRoot: true
+  runAsUser: 65532
+  capabilities:
+    drop:
+    - ALL
+```
+
+### Resources
+
+```yaml
+resources:
+  limits:
+    cpu: 200m
+    memory: 256Mi
+  requests:
+    cpu: 50m
+    memory: 64Mi
+```
+
+### Service Configuration
+
+```yaml
+service:
+  type: ClusterIP
+  port: 8080
+  annotations: {}
+```
+
+### High Availability
+
+```yaml
+# Pod Disruption Budget
+podDisruptionBudget:
+  enabled: true
+  minAvailable: 1
+  # maxUnavailable: 1
+
+# Affinity rules for spreading pods
+affinity:
+  podAntiAffinity:
+    preferredDuringSchedulingIgnoredDuringExecution:
+    - weight: 100
+      podAffinityTerm:
+        labelSelector:
+          matchLabels:
+            app.kubernetes.io/component: provider
+            external-secrets.io/provider: aws
+        topologyKey: kubernetes.io/hostname
+
+# Topology spread constraints
+topologySpreadConstraints:
+- maxSkew: 1
+  topologyKey: topology.kubernetes.io/zone
+  whenUnsatisfiable: ScheduleAnyway
+  labelSelector:
+    matchLabels:
+      external-secrets.io/provider: aws
+```
+
+### Auto-scaling
+
+```yaml
+autoscaling:
+  enabled: true
+  minReplicas: 2
+  maxReplicas: 10
+  targetCPUUtilizationPercentage: 80
+  targetMemoryUtilizationPercentage: 80
+```
+
+### TLS Configuration
+
+```yaml
+tls:
+  enabled: true
+  certPath: /etc/provider/certs
+  caSecretName: external-secrets-v2-ca
+  mountCA: true
+```
+
+### Provider-Specific Configuration
+
+Use the `config` section to pass provider-specific settings:
+
+```yaml
+config:
+  # AWS provider example
+  region: us-east-1
+  authMethod: irsa
+  assumeRoleARN: ""
+  externalID: ""
+  
+  # GCP provider example
+  # projectID: my-project
+  
+  # Azure provider example
+  # vaultURL: https://my-vault.vault.azure.net
+  # tenantID: "00000000-0000-0000-0000-000000000000"
+  
+  # Vault provider example
+  # vaultAddr: https://vault.example.com
+  # authMethod: kubernetes
+```
+
+### Logging
+
+```yaml
+logging:
+  level: info  # debug, info, warn, error
+  format: json  # json, console
+  development: false
+```
+
+### Metrics
+
+```yaml
+metrics:
+  enabled: true
+  port: 8081
+  serviceMonitor:
+    enabled: true
+    namespace: ""  # Defaults to release namespace
+    interval: 30s
+    scrapeTimeout: 10s
+    labels: {}
+```
+
+### Health Checks
+
+```yaml
+health:
+  port: 8082
+  livenessProbe:
+    enabled: true
+    initialDelaySeconds: 10
+    periodSeconds: 20
+    timeoutSeconds: 5
+    failureThreshold: 3
+  readinessProbe:
+    enabled: true
+    initialDelaySeconds: 5
+    periodSeconds: 10
+    timeoutSeconds: 5
+    failureThreshold: 3
+```
+
+### Extra Configuration
+
+```yaml
+# Additional environment variables
+extraEnv:
+- name: CUSTOM_VAR
+  value: "custom-value"
+- name: SECRET_VAR
+  valueFrom:
+    secretKeyRef:
+      name: my-secret
+      key: password
+
+# Additional volumes
+extraVolumes:
+- name: custom-config
+  configMap:
+    name: provider-config
+
+# Additional volume mounts
+extraVolumeMounts:
+- name: custom-config
+  mountPath: /etc/config
+  readOnly: true
+
+# Pod annotations
+podAnnotations:
+  prometheus.io/scrape: "true"
+
+# Pod labels
+podLabels:
+  environment: production
+
+# Node selector
+nodeSelector:
+  cloud.provider/instance-type: standard
+
+# Tolerations
+tolerations:
+- key: "provider-workload"
+  operator: "Equal"
+  value: "true"
+  effect: "NoSchedule"
+
+# Priority class
+priorityClassName: high-priority
+```
+
+## Examples
+
+### AWS Provider with IRSA
+
+```yaml
+providers:
+  enabled: true
+  list:
+    - name: aws-us-east-1
+      type: aws
+      enabled: true
+      replicaCount: 3
+      
+      image:
+        repository: oci.external-secrets.io/external-secrets/provider-aws
+      
+      serviceAccount:
+        create: true
+        annotations:
+          eks.amazonaws.com/role-arn: arn:aws:iam::123456789012:role/eso-provider-aws
+      
+      resources:
+        limits:
+          cpu: 300m
+          memory: 512Mi
+        requests:
+          cpu: 100m
+          memory: 128Mi
+      
+      config:
+        region: us-east-1
+        authMethod: irsa
+      
+      podDisruptionBudget:
+        enabled: true
+        minAvailable: 2
+      
+      metrics:
+        enabled: true
+        serviceMonitor:
+          enabled: true
+```
+
+### Multiple Providers
+
+```yaml
+providers:
+  enabled: true
+  list:
+    # AWS Provider
+    - name: aws
+      type: aws
+      enabled: true
+      replicaCount: 2
+      image:
+        repository: oci.external-secrets.io/external-secrets/provider-aws
+      serviceAccount:
+        annotations:
+          eks.amazonaws.com/role-arn: arn:aws:iam::123456789012:role/eso-aws
+      config:
+        region: us-east-1
+        authMethod: irsa
+    
+    # GCP Provider
+    - name: gcp
+      type: gcp
+      enabled: true
+      replicaCount: 2
+      image:
+        repository: oci.external-secrets.io/external-secrets/provider-gcp
+      serviceAccount:
+        annotations:
+          iam.gke.io/gcp-service-account: eso@project.iam.gserviceaccount.com
+      config:
+        projectID: my-project
+    
+    # Azure Provider
+    - name: azure
+      type: azure
+      enabled: true
+      replicaCount: 2
+      image:
+        repository: oci.external-secrets.io/external-secrets/provider-azure
+      serviceAccount:
+        annotations:
+          azure.workload.identity/client-id: "00000000-0000-0000-0000-000000000000"
+      podLabels:
+        azure.workload.identity/use: "true"
+      config:
+        vaultURL: https://my-vault.vault.azure.net
+        tenantID: "00000000-0000-0000-0000-000000000000"
+```
+
+### Provider with Custom Authentication Secret
+
+```yaml
+providers:
+  enabled: true
+  list:
+    - name: vault
+      type: vault
+      enabled: true
+      
+      image:
+        repository: oci.external-secrets.io/external-secrets/provider-vault
+      
+      config:
+        vaultAddr: https://vault.example.com
+        authMethod: token
+      
+      extraEnv:
+      - name: VAULT_TOKEN
+        valueFrom:
+          secretKeyRef:
+            name: vault-token
+            key: token
+      
+      extraVolumes:
+      - name: vault-ca
+        secret:
+          secretName: vault-ca-cert
+      
+      extraVolumeMounts:
+      - name: vault-ca
+        mountPath: /etc/vault/ca
+        readOnly: true
+```
+
+## Installation
+
+### Install with providers
+
+```bash
+helm install external-secrets external-secrets/external-secrets \
+  -f values-with-providers.yaml
+```
+
+### Upgrade existing installation
+
+```bash
+helm upgrade external-secrets external-secrets/external-secrets \
+  -f values-with-providers.yaml
+```
+
+### Install with specific provider enabled
+
+```bash
+helm install external-secrets external-secrets/external-secrets \
+  --set providers.enabled=true \
+  --set providers.list[0].name=aws \
+  --set providers.list[0].type=aws \
+  --set providers.list[0].enabled=true \
+  --set providers.list[0].replicaCount=2 \
+  --set providers.list[0].image.repository=oci.external-secrets.io/external-secrets/provider-aws
+```
+
+## Troubleshooting
+
+### Check provider deployment status
+
+```bash
+kubectl get deployments -l app.kubernetes.io/component=provider
+```
+
+### View provider logs
+
+```bash
+kubectl logs -l external-secrets.io/provider=aws -f
+```
+
+### Check provider metrics
+
+```bash
+kubectl port-forward svc/external-secrets-provider-aws 8081:8081
+curl http://localhost:8081/metrics
+```
+
+### Verify TLS connectivity
+
+```bash
+kubectl exec -it deployment/external-secrets-provider-aws -- sh
+# Check if certificates are mounted
+ls -la /etc/provider/certs
+```
+
+## Best Practices
+
+1. **High Availability**: Always use `replicaCount >= 2` for production
+2. **Resource Limits**: Set appropriate resource limits based on your workload
+3. **Pod Disruption Budgets**: Enable PDBs to prevent all replicas from being evicted
+4. **Anti-Affinity**: Use pod anti-affinity to spread replicas across nodes/zones
+5. **Monitoring**: Enable metrics and ServiceMonitor for observability
+6. **Authentication**: Use workload identity (IRSA, Workload Identity) instead of static credentials
+7. **TLS**: Keep TLS enabled for secure provider-controller communication
+8. **Auto-scaling**: Use HPA for dynamic scaling based on load
+9. **Health Checks**: Enable liveness and readiness probes for better reliability
+10. **Security Context**: Use restrictive security contexts (non-root, read-only filesystem)

+ 6 - 0
deploy/charts/external-secrets/README.md

@@ -114,6 +114,7 @@ The command removes all the Kubernetes components associated with the chart and
 | crds.conversion.enabled | bool | `false` | Conversion is disabled by default as we stopped supporting v1alpha1. |
 | crds.createClusterExternalSecret | bool | `true` | If true, create CRDs for Cluster External Secret. If set to false you must also set processClusterExternalSecret: false. |
 | crds.createClusterGenerator | bool | `true` | If true, create CRDs for Cluster Generator. If set to false you must also set processClusterGenerator: false. |
+| crds.createClusterProviderClass | bool | `true` | If true, create CRDs for Cluster Provider Class. |
 | crds.createClusterPushSecret | bool | `true` | If true, create CRDs for Cluster Push Secret. If set to false you must also set processClusterPushSecret: false. |
 | crds.createClusterSecretStore | bool | `true` | If true, create CRDs for Cluster Secret Store. If set to false you must also set processClusterStore: false. |
 | crds.createPushSecret | bool | `true` | If true, create CRDs for Push Secret. If set to false you must also set processPushSecret: false. |
@@ -200,6 +201,10 @@ The command removes all the Kubernetes components associated with the chart and
 | processClusterStore | bool | `true` | if true, the operator will process cluster store. Else, it will ignore them. |
 | processPushSecret | bool | `true` | if true, the operator will process push secret. Else, it will ignore them. |
 | processSecretStore | bool | `true` | if true, the operator will process secret store. Else, it will ignore them. |
+| providerDefaults | object | `{"affinity":{},"autoscaling":{"enabled":false,"maxReplicas":10,"minReplicas":2,"targetCPUUtilizationPercentage":80,"targetMemoryUtilizationPercentage":80},"health":{"livenessProbe":{"enabled":false,"failureThreshold":3,"initialDelaySeconds":10,"periodSeconds":20,"timeoutSeconds":5},"port":8082,"readinessProbe":{"enabled":false,"failureThreshold":3,"initialDelaySeconds":5,"periodSeconds":10,"timeoutSeconds":5}},"metrics":{"enabled":true,"port":8081,"serviceMonitor":{"enabled":false,"interval":"30s","labels":{},"namespace":"","scrapeTimeout":"10s"}},"nodeSelector":{},"podAnnotations":{},"podDisruptionBudget":{"enabled":true,"minAvailable":1},"podLabels":{},"podSecurityContext":{"enabled":true,"fsGroup":65532,"runAsNonRoot":true,"runAsUser":65532,"seccompProfile":{"type":"RuntimeDefault"}},"priorityClassName":"","replicaCount":2,"resources":{"limits":{"cpu":"200m","memory":"256Mi"},"requests":{"cpu":"50m","memory":"64Mi"}},"securityContext":{"allowPrivilegeEscalation":false,"capabilities":{"drop":["ALL"]},"enabled":true,"readOnlyRootFilesystem":true,"runAsNonRoot":true,"runAsUser":65532},"service":{"annotations":{},"port":8080,"type":"ClusterIP"},"serviceAccount":{"annotations":{},"automount":true,"create":true,"name":""},"tls":{"caSecretName":"external-secrets-v2-ca","certPath":"/etc/provider/certs","enabled":true,"mountCA":true},"tolerations":[],"topologySpreadConstraints":[]}` | Provider defaults configuration Common configuration that is automatically merged with each provider's configuration Individual providers can override any of these defaults by specifying the same keys |
+| providers | object | `{"enabled":false,"list":[]}` | Provider deployment configuration Deploy one or more external secret providers alongside the controller Each provider runs as a separate deployment with its own configuration |
+| providers.enabled | bool | `false` | Enable provider deployments |
+| providers.list | list | `[]` | List of providers to deploy Each provider automatically inherits defaults from providerDefaults above You only need to specify what you want to override |
 | rbac.aggregateToEdit | bool | `true` | Specifies whether permissions are aggregated to the edit ClusterRole |
 | rbac.aggregateToView | bool | `true` | Specifies whether permissions are aggregated to the view ClusterRole |
 | rbac.create | bool | `true` | Specifies whether role and rolebinding resources should be created. |
@@ -247,6 +252,7 @@ The command removes all the Kubernetes components associated with the chart and
 | systemAuthDelegator | bool | `false` | If true the system:auth-delegator ClusterRole will be added to RBAC |
 | tolerations | list | `[]` |  |
 | topologySpreadConstraints | list | `[]` |  |
+| v2 | object | `{"enabled":true}` | Experimental v2 out-of-process provider runtime support. Enables SecretStore runtimeRef compatibility flows. |
 | vault | object | `{"enableTokenCache":false,"tokenCacheSize":262144}` | Vault token cache configuration |
 | vault.enableTokenCache | bool | `false` | Enable Vault token cache. External secrets will reuse the Vault token without creating a new one on each request. |
 | vault.tokenCacheSize | int | `262144` | Maximum size of Vault token cache. Only used if enableTokenCache is true. |

+ 62 - 0
deploy/charts/external-secrets/templates/_helpers.tpl

@@ -248,6 +248,7 @@ Create the name of the pod disruption budget to use in the webhook
 {{- define "external-secrets.webhookPdbName" -}}
 {{- .Values.webhook.podDisruptionBudget.nameOverride | default (printf "%s-webhook-pdb" (include "external-secrets.fullname" .)) }}
 {{- end }}
+{{/*
 Fail the install if a cluster scoped reconciler is enabled while its namespace scoped counterpart is disabled
 */}}
 {{- define "external-secrets.reconciler-sanity-test" -}}
@@ -278,3 +279,64 @@ Decide whether to render the ServiceMonitor resource.
     {{- fail (printf "Invalid renderMode '%s'. Must be one of: skipIfMissing, failIfMissing, alwaysRender." $mode) -}}
   {{- end -}}
 {{- end -}}
+
+{{/*
+Provider helpers
+*/}}
+{{- define "external-secrets.provider.fullname" -}}
+{{- $providerName := .provider.name | default .provider.type -}}
+{{- printf "%s-provider-%s" (include "external-secrets.fullname" .root) $providerName | trunc 63 | trimSuffix "-" -}}
+{{- end -}}
+
+{{- define "external-secrets.provider.servicename" -}}
+{{- $providerName := .provider.name | default .provider.type -}}
+{{- printf "provider-%s" $providerName | trunc 63 | trimSuffix "-" -}}
+{{- end -}}
+
+{{- define "external-secrets.provider.labels" -}}
+helm.sh/chart: {{ include "external-secrets.chart" .root }}
+{{ include "external-secrets.provider.selectorLabels" . }}
+{{- if .root.Chart.AppVersion }}
+app.kubernetes.io/version: {{ .root.Chart.AppVersion | quote }}
+{{- end }}
+app.kubernetes.io/managed-by: {{ .root.Release.Service }}
+app.kubernetes.io/component: provider
+external-secrets.io/provider: {{ .provider.type }}
+{{- with .root.Values.commonLabels }}
+{{ toYaml . }}
+{{- end }}
+{{- with .provider.podLabels }}
+{{ toYaml . }}
+{{- end }}
+{{- end -}}
+
+{{- define "external-secrets.provider.selectorLabels" -}}
+{{- $providerName := .provider.name | default .provider.type -}}
+app.kubernetes.io/name: {{ include "external-secrets.name" .root }}-provider-{{ $providerName }}
+app.kubernetes.io/instance: {{ .root.Release.Name }}
+{{- end -}}
+
+{{- define "external-secrets.provider.serviceAccountName" -}}
+{{- if .provider.serviceAccount.create -}}
+{{- default (include "external-secrets.provider.fullname" .) .provider.serviceAccount.name -}}
+{{- else -}}
+{{- default "default" .provider.serviceAccount.name -}}
+{{- end -}}
+{{- end -}}
+
+{{- define "external-secrets.provider.image" -}}
+{{- $tag := .provider.image.tag | default .root.Chart.AppVersion -}}
+{{- printf "%s:%s" .provider.image.repository $tag -}}
+{{- end -}}
+
+{{/*
+Merge provider defaults with provider-specific configuration.
+Provider-specific values take precedence over defaults.
+Usage: {{- $provider := include "external-secrets.provider.mergeDefaults" (dict "provider" $provider "root" $root) | fromYaml -}}
+*/}}
+{{- define "external-secrets.provider.mergeDefaults" -}}
+{{- $defaults := .root.Values.providerDefaults | default dict -}}
+{{- $provider := .provider -}}
+{{- $merged := deepCopy $defaults | mustMergeOverwrite (deepCopy $provider) -}}
+{{- $merged | toYaml -}}
+{{- end -}}

+ 8 - 0
deploy/charts/external-secrets/templates/cert-controller-deployment.yaml

@@ -92,6 +92,14 @@ spec:
           {{- if .Values.leaderElect }}
           - --enable-leader-election=true
           {{- end }}
+          {{- if and .Values.v2.enabled .Values.providers.enabled }}
+          - --provider-namespace={{ template "external-secrets.namespace" . }}
+            {{- range .Values.providers.list }}
+            {{- if .enabled }}
+          - --provider-service-names={{ include "external-secrets.provider.servicename" (dict "provider" . "root" $) }}
+            {{- end }}
+            {{- end }}
+          {{- end }}
           {{- range $key, $value := .Values.certController.extraArgs }}
             {{- if $value }}
           - --{{ $key }}={{ $value }}

+ 1 - 0
deploy/charts/external-secrets/templates/cert-controller-rbac.yaml

@@ -66,6 +66,7 @@ rules:
     - "list"
     - "watch"
     - "update"
+    - "create"
     - "patch"
   - apiGroups:
     - "coordination.k8s.io"

+ 0 - 4
deploy/charts/external-secrets/templates/crds/README.md

@@ -1,4 +0,0 @@
-# CRD Template Directory
-CRDs are autogenerated during helm packaging. To install the CRDs set `installCRDS: true` during helm install or upgrade.
-
-The latest CRDs in the repository are located [here](../../../../crds).

+ 18 - 0
deploy/charts/external-secrets/templates/provider-class.yaml

@@ -0,0 +1,18 @@
+{{- if .Values.providers.enabled }}
+{{- range .Values.providers.list }}
+{{- if .enabled }}
+{{- $root := $ }}
+{{- $provider := include "external-secrets.provider.mergeDefaults" (dict "provider" . "root" $root) | fromYaml }}
+{{- $providerName := $provider.name | default $provider.type }}
+---
+apiVersion: external-secrets.io/v1alpha1
+kind: ClusterProviderClass
+metadata:
+  name: {{ $providerName }}
+  labels:
+    {{- include "external-secrets.provider.labels" (dict "provider" $provider "root" $root) | nindent 4 }}
+spec:
+  address: {{ include "external-secrets.provider.servicename" (dict "provider" $provider "root" $root) }}.{{ include "external-secrets.namespace" $root }}.svc:{{ $provider.service.port | default 8080 }}
+{{- end }}
+{{- end }}
+{{- end }}

+ 155 - 0
deploy/charts/external-secrets/templates/provider-deployment.yaml

@@ -0,0 +1,155 @@
+{{- if .Values.providers.enabled }}
+{{- range .Values.providers.list }}
+{{- if .enabled }}
+{{- $root := $ }}
+{{- $provider := include "external-secrets.provider.mergeDefaults" (dict "provider" . "root" $root) | fromYaml }}
+---
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: {{ include "external-secrets.provider.fullname" (dict "provider" $provider "root" $root) }}
+  namespace: {{ include "external-secrets.namespace" $root }}
+  labels:
+    {{- include "external-secrets.provider.labels" (dict "provider" $provider "root" $root) | nindent 4 }}
+  {{- with $root.Values.commonAnnotations }}
+  annotations:
+    {{- toYaml . | nindent 4 }}
+  {{- end }}
+spec:
+  {{- if not (and $provider.autoscaling $provider.autoscaling.enabled) }}
+  replicas: {{ $provider.replicaCount | default 2 }}
+  {{- end }}
+  selector:
+    matchLabels:
+      {{- include "external-secrets.provider.selectorLabels" (dict "provider" $provider "root" $root) | nindent 6 }}
+  template:
+    metadata:
+      annotations:
+        {{- with $provider.podAnnotations }}
+        {{- toYaml . | nindent 8 }}
+        {{- end }}
+      labels:
+        {{- include "external-secrets.provider.selectorLabels" (dict "provider" $provider "root" $root) | nindent 8 }}
+        {{- with $provider.podLabels }}
+        {{- toYaml . | nindent 8 }}
+        {{- end }}
+    spec:
+      {{- with $provider.imagePullSecrets }}
+      imagePullSecrets:
+        {{- toYaml . | nindent 8 }}
+      {{- end }}
+      serviceAccountName: {{ include "external-secrets.provider.serviceAccountName" (dict "provider" $provider "root" $root) }}
+      {{- if $provider.priorityClassName }}
+      priorityClassName: {{ $provider.priorityClassName }}
+      {{- end }}
+      {{- if and $provider.podSecurityContext $provider.podSecurityContext.enabled }}
+      securityContext:
+        {{- include "external-secrets.renderSecurityContext" (dict "securityContext" $provider.podSecurityContext "context" $root) | nindent 8 }}
+      {{- end }}
+      containers:
+      - name: provider
+        image: {{ include "external-secrets.provider.image" (dict "provider" $provider "root" $root) }}
+        imagePullPolicy: {{ $provider.image.pullPolicy | default "IfNotPresent" }}
+        {{- if and $provider.securityContext $provider.securityContext.enabled }}
+        securityContext:
+          {{- include "external-secrets.renderSecurityContext" (dict "securityContext" $provider.securityContext "context" $root) | nindent 10 }}
+        {{- end }}
+        args: []
+        env:
+        {{- if and $provider.tls $provider.tls.enabled }}
+        - name: TLS_ENABLED
+          value: "true"
+        - name: TLS_CERT_DIR
+          value: {{ $provider.tls.certPath | default "/etc/provider/certs" }}
+        {{- end }}
+        {{- if $provider.config }}
+        {{- range $key, $value := $provider.config }}
+        - name: {{ $key | upper | replace "." "_" }}
+          value: {{ $value | quote }}
+        {{- end }}
+        {{- end }}
+        {{- with $provider.extraEnv }}
+        {{- toYaml . | nindent 8 }}
+        {{- end }}
+        ports:
+        - name: grpc
+          containerPort: {{ $provider.service.port | default 8080 }}
+          protocol: TCP
+        {{- if and $provider.metrics $provider.metrics.enabled }}
+        - name: metrics
+          containerPort: {{ $provider.metrics.port | default 8081 }}
+          protocol: TCP
+        {{- end }}
+        {{- if $provider.health }}
+        - name: health
+          containerPort: {{ $provider.health.port | default 8082 }}
+          protocol: TCP
+        {{- end }}
+        {{- if and $provider.health $provider.health.livenessProbe $provider.health.livenessProbe.enabled }}
+        livenessProbe:
+          httpGet:
+            path: /healthz
+            port: health
+          initialDelaySeconds: {{ $provider.health.livenessProbe.initialDelaySeconds | default 10 }}
+          periodSeconds: {{ $provider.health.livenessProbe.periodSeconds | default 20 }}
+          timeoutSeconds: {{ $provider.health.livenessProbe.timeoutSeconds | default 5 }}
+          failureThreshold: {{ $provider.health.livenessProbe.failureThreshold | default 3 }}
+        {{- end }}
+        {{- if and $provider.health $provider.health.readinessProbe $provider.health.readinessProbe.enabled }}
+        readinessProbe:
+          httpGet:
+            path: /readyz
+            port: health
+          initialDelaySeconds: {{ $provider.health.readinessProbe.initialDelaySeconds | default 5 }}
+          periodSeconds: {{ $provider.health.readinessProbe.periodSeconds | default 10 }}
+          timeoutSeconds: {{ $provider.health.readinessProbe.timeoutSeconds | default 5 }}
+          failureThreshold: {{ $provider.health.readinessProbe.failureThreshold | default 3 }}
+        {{- end }}
+        {{- with $provider.resources }}
+        resources:
+          {{- toYaml . | nindent 10 }}
+        {{- end }}
+        volumeMounts:
+        {{- if and $provider.tls $provider.tls.enabled }}
+        - name: provider-certs
+          mountPath: {{ $provider.tls.certPath | default "/etc/provider/certs" }}
+          readOnly: true
+        {{- end }}
+        {{- with $provider.extraVolumeMounts }}
+        {{- toYaml . | nindent 8 }}
+        {{- end }}
+      volumes:
+      {{- if and $provider.tls $provider.tls.enabled }}
+      - name: provider-certs
+        secret:
+          secretName: "external-secrets-provider-tls"
+          items:
+          - key: tls.crt
+            path: tls.crt
+          - key: tls.key
+            path: tls.key
+          - key: ca.crt
+            path: ca.crt
+      {{- end }}
+      {{- with $provider.extraVolumes }}
+      {{- toYaml . | nindent 6 }}
+      {{- end }}
+      {{- with $provider.nodeSelector }}
+      nodeSelector:
+        {{- toYaml . | nindent 8 }}
+      {{- end }}
+      {{- with $provider.affinity }}
+      affinity:
+        {{- toYaml . | nindent 8 }}
+      {{- end }}
+      {{- with $provider.tolerations }}
+      tolerations:
+        {{- toYaml . | nindent 8 }}
+      {{- end }}
+      {{- with $provider.topologySpreadConstraints }}
+      topologySpreadConstraints:
+        {{- toYaml . | nindent 8 }}
+      {{- end }}
+{{- end }}
+{{- end }}
+{{- end }}

+ 42 - 0
deploy/charts/external-secrets/templates/provider-hpa.yaml

@@ -0,0 +1,42 @@
+{{- if .Values.providers.enabled }}
+{{- range .Values.providers.list }}
+{{- if .enabled }}
+{{- $root := $ }}
+{{- $provider := include "external-secrets.provider.mergeDefaults" (dict "provider" . "root" $root) | fromYaml }}
+{{- if and $provider.autoscaling $provider.autoscaling.enabled }}
+---
+apiVersion: autoscaling/v2
+kind: HorizontalPodAutoscaler
+metadata:
+  name: {{ include "external-secrets.provider.fullname" (dict "provider" $provider "root" $root) }}
+  namespace: {{ include "external-secrets.namespace" $root }}
+  labels:
+    {{- include "external-secrets.provider.labels" (dict "provider" $provider "root" $root) | nindent 4 }}
+spec:
+  scaleTargetRef:
+    apiVersion: apps/v1
+    kind: Deployment
+    name: {{ include "external-secrets.provider.fullname" (dict "provider" $provider "root" $root) }}
+  minReplicas: {{ $provider.autoscaling.minReplicas | default 2 }}
+  maxReplicas: {{ $provider.autoscaling.maxReplicas | default 10 }}
+  metrics:
+  {{- if $provider.autoscaling.targetCPUUtilizationPercentage }}
+  - type: Resource
+    resource:
+      name: cpu
+      target:
+        type: Utilization
+        averageUtilization: {{ $provider.autoscaling.targetCPUUtilizationPercentage }}
+  {{- end }}
+  {{- if $provider.autoscaling.targetMemoryUtilizationPercentage }}
+  - type: Resource
+    resource:
+      name: memory
+      target:
+        type: Utilization
+        averageUtilization: {{ $provider.autoscaling.targetMemoryUtilizationPercentage }}
+  {{- end }}
+{{- end }}
+{{- end }}
+{{- end }}
+{{- end }}

+ 28 - 0
deploy/charts/external-secrets/templates/provider-poddisruptionbudget.yaml

@@ -0,0 +1,28 @@
+{{- if .Values.providers.enabled }}
+{{- range .Values.providers.list }}
+{{- if .enabled }}
+{{- $root := $ }}
+{{- $provider := include "external-secrets.provider.mergeDefaults" (dict "provider" . "root" $root) | fromYaml }}
+{{- if and $provider.podDisruptionBudget $provider.podDisruptionBudget.enabled }}
+---
+apiVersion: policy/v1
+kind: PodDisruptionBudget
+metadata:
+  name: {{ include "external-secrets.provider.fullname" (dict "provider" $provider "root" $root) }}
+  namespace: {{ include "external-secrets.namespace" $root }}
+  labels:
+    {{- include "external-secrets.provider.labels" (dict "provider" $provider "root" $root) | nindent 4 }}
+spec:
+  {{- if $provider.podDisruptionBudget.minAvailable }}
+  minAvailable: {{ $provider.podDisruptionBudget.minAvailable }}
+  {{- end }}
+  {{- if $provider.podDisruptionBudget.maxUnavailable }}
+  maxUnavailable: {{ $provider.podDisruptionBudget.maxUnavailable }}
+  {{- end }}
+  selector:
+    matchLabels:
+      {{- include "external-secrets.provider.selectorLabels" (dict "provider" $provider "root" $root) | nindent 6 }}
+{{- end }}
+{{- end }}
+{{- end }}
+{{- end }}

+ 103 - 0
deploy/charts/external-secrets/templates/provider-rbac.yaml

@@ -0,0 +1,103 @@
+{{- if and .Values.providers.enabled .Values.rbac.create }}
+{{- range .Values.providers.list }}
+{{- if .enabled }}
+{{- $root := $ }}
+{{- $provider := include "external-secrets.provider.mergeDefaults" (dict "provider" . "root" $root) | fromYaml }}
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRole
+metadata:
+  name: {{ include "external-secrets.provider.fullname" (dict "provider" $provider "root" $root) }}
+  labels:
+    {{- include "external-secrets.provider.labels" (dict "provider" $provider "root" $root) | nindent 4 }}
+rules:
+  # All providers need to read their own provider configuration CRDs
+  - apiGroups:
+    - "provider.external-secrets.io"
+    resources:
+    - "fakes"
+    - "kubernetes"
+    - "secretmanagers"
+    - "secretsmanagers"
+    - "parameterstores"
+    verbs:
+    - "get"
+    - "list"
+    - "watch"
+  # Providers that support generators need to read generator CRDs
+  - apiGroups:
+    - "generators.external-secrets.io"
+    resources:
+    - "fakes"
+    - "passwords"
+    - "ecrauthorizationtokens"
+    - "stssessiontokens"
+    - "gcraccesstokens"
+    - "uuids"
+    - "vaultdynamicsecrets"
+    - "acraccesstokens"
+    verbs:
+    - "get"
+    - "list"
+    - "watch"
+  - apiGroups:
+    - ""
+    resources:
+    - "secrets"
+    verbs:
+    - "get"
+    - "list"
+    - "watch"
+{{- if eq $provider.type "kubernetes" }}
+  # Kubernetes provider needs to read service accounts for auth
+  - apiGroups:
+    - ""
+    resources:
+    - "serviceaccounts"
+    verbs:
+    - "get"
+  # Kubernetes provider needs to get service account tokens
+  - apiGroups:
+    - ""
+    resources:
+    - "serviceaccounts/token"
+    verbs:
+    - "create"
+  - apiGroups:
+    - authorization.k8s.io
+    resources:
+    - selfsubjectrulesreviews
+    verbs:
+    - create
+{{- end }}
+{{- if eq $provider.type "aws" }}
+  # AWS provider may need access to AWS credentials stored in secrets
+  - apiGroups:
+    - ""
+    resources:
+    - "secrets"
+    - "configmaps"
+    verbs:
+    - "get"
+    - "list"
+{{- end }}
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRoleBinding
+metadata:
+  name: {{ include "external-secrets.provider.fullname" (dict "provider" $provider "root" $root) }}
+  labels:
+    {{- include "external-secrets.provider.labels" (dict "provider" $provider "root" $root) | nindent 4 }}
+roleRef:
+  apiGroup: rbac.authorization.k8s.io
+  kind: ClusterRole
+  name: {{ include "external-secrets.provider.fullname" (dict "provider" $provider "root" $root) }}
+subjects:
+  - kind: ServiceAccount
+    name: {{ include "external-secrets.provider.serviceAccountName" (dict "provider" $provider "root" $root) }}
+    namespace: {{ include "external-secrets.namespace" $root }}
+{{- end }}
+{{- end }}
+{{- end }}
+
+

+ 35 - 0
deploy/charts/external-secrets/templates/provider-service.yaml

@@ -0,0 +1,35 @@
+{{- if .Values.providers.enabled }}
+{{- range .Values.providers.list }}
+{{- if .enabled }}
+{{- $root := $ }}
+{{- $provider := include "external-secrets.provider.mergeDefaults" (dict "provider" . "root" $root) | fromYaml }}
+---
+apiVersion: v1
+kind: Service
+metadata:
+  name: {{ include "external-secrets.provider.servicename" (dict "provider" $provider "root" $root) }}
+  namespace: {{ include "external-secrets.namespace" $root }}
+  labels:
+    {{- include "external-secrets.provider.labels" (dict "provider" $provider "root" $root) | nindent 4 }}
+  {{- with $provider.service.annotations }}
+  annotations:
+    {{- toYaml . | nindent 4 }}
+  {{- end }}
+spec:
+  type: {{ $provider.service.type | default "ClusterIP" }}
+  ports:
+  - port: {{ $provider.service.port | default 8080 }}
+    targetPort: grpc
+    protocol: TCP
+    name: grpc
+  {{- if and $provider.metrics $provider.metrics.enabled }}
+  - port: {{ $provider.metrics.port | default 8081 }}
+    targetPort: metrics
+    protocol: TCP
+    name: metrics
+  {{- end }}
+  selector:
+    {{- include "external-secrets.provider.selectorLabels" (dict "provider" $provider "root" $root) | nindent 4 }}
+{{- end }}
+{{- end }}
+{{- end }}

+ 25 - 0
deploy/charts/external-secrets/templates/provider-serviceaccount.yaml

@@ -0,0 +1,25 @@
+{{- if .Values.providers.enabled }}
+{{- range .Values.providers.list }}
+{{- if .enabled }}
+{{- $root := $ }}
+{{- $provider := include "external-secrets.provider.mergeDefaults" (dict "provider" . "root" $root) | fromYaml }}
+{{- if $provider.serviceAccount.create }}
+---
+apiVersion: v1
+kind: ServiceAccount
+metadata:
+  name: {{ include "external-secrets.provider.serviceAccountName" (dict "provider" $provider "root" $root) }}
+  namespace: {{ include "external-secrets.namespace" $root }}
+  labels:
+    {{- include "external-secrets.provider.labels" (dict "provider" $provider "root" $root) | nindent 4 }}
+  {{- with $provider.serviceAccount.annotations }}
+  annotations:
+    {{- toYaml . | nindent 4 }}
+  {{- end }}
+{{- if $provider.serviceAccount.automount }}
+automountServiceAccountToken: true
+{{- end }}
+{{- end }}
+{{- end }}
+{{- end }}
+{{- end }}

+ 30 - 0
deploy/charts/external-secrets/templates/provider-servicemonitor.yaml

@@ -0,0 +1,30 @@
+{{- if .Values.providers.enabled }}
+{{- range .Values.providers.list }}
+{{- if .enabled }}
+{{- $root := $ }}
+{{- $provider := include "external-secrets.provider.mergeDefaults" (dict "provider" . "root" $root) | fromYaml }}
+{{- if and $provider.metrics $provider.metrics.enabled $provider.metrics.serviceMonitor $provider.metrics.serviceMonitor.enabled }}
+---
+apiVersion: monitoring.coreos.com/v1
+kind: ServiceMonitor
+metadata:
+  name: {{ include "external-secrets.provider.fullname" (dict "provider" $provider "root" $root) }}
+  namespace: {{ $provider.metrics.serviceMonitor.namespace | default (include "external-secrets.namespace" $root) }}
+  labels:
+    {{- include "external-secrets.provider.labels" (dict "provider" $provider "root" $root) | nindent 4 }}
+    {{- with $provider.metrics.serviceMonitor.labels }}
+    {{- toYaml . | nindent 4 }}
+    {{- end }}
+spec:
+  selector:
+    matchLabels:
+      {{- include "external-secrets.provider.selectorLabels" (dict "provider" $provider "root" $root) | nindent 6 }}
+  endpoints:
+  - port: metrics
+    interval: {{ $provider.metrics.serviceMonitor.interval | default "30s" }}
+    scrapeTimeout: {{ $provider.metrics.serviceMonitor.scrapeTimeout | default "10s" }}
+    path: /metrics
+{{- end }}
+{{- end }}
+{{- end }}
+{{- end }}

+ 30 - 0
deploy/charts/external-secrets/templates/rbac.yaml

@@ -13,10 +13,26 @@ metadata:
   labels:
     {{- include "external-secrets.labels" . | nindent 4 }}
 rules:
+  {{- if .Values.v2.enabled }}
+  - apiGroups:
+    - "provider.external-secrets.io"
+    resources:
+    - "kubernetes"
+    - "awssecretsmanagers"
+    - "fakes"
+    verbs:
+    - "get"
+    - "list"
+    - "watch"
+  {{- end }}
   - apiGroups:
     - "external-secrets.io"
     resources:
     - "secretstores"
+    {{- if .Values.v2.enabled }}
+    - "clusterproviderclasses"
+    - "providerclasses"
+    {{- end }}
     {{- if .Values.processClusterStore }}
     - "clustersecretstores"
     {{- end }}
@@ -37,6 +53,12 @@ rules:
   - apiGroups:
     - "external-secrets.io"
     resources:
+    {{- if .Values.v2.enabled }}
+    - "clusterproviderclasses"
+    - "clusterproviderclasses/status"
+    - "providerclasses"
+    - "providerclasses/status"
+    {{- end }}
     - "externalsecrets"
     - "externalsecrets/status"
     {{- if .Values.openshiftFinalizers }}
@@ -256,6 +278,10 @@ rules:
     resources:
       - "externalsecrets"
       - "secretstores"
+      {{- if .Values.v2.enabled }}
+      - "providers"
+      - "clusterproviders"
+      {{- end }}
       {{- if .Values.processClusterStore }}
       - "clustersecretstores"
       {{- end }}
@@ -318,6 +344,10 @@ rules:
     resources:
       - "externalsecrets"
       - "secretstores"
+      {{- if .Values.v2.enabled }}
+      - "providers"
+      - "clusterproviders"
+      {{- end }}
       {{- if .Values.processClusterStore }}
       - "clustersecretstores"
       {{- end }}

+ 586 - 145
deploy/charts/external-secrets/tests/__snapshot__/crds_test.yaml.snap

@@ -60,7 +60,7 @@ should match snapshot of default values:
                   description: SecretStoreSpec defines the desired state of SecretStore.
                   properties:
                     conditions:
-                      description: Used to constraint a ClusterSecretStore to specific namespaces. Relevant only to ClusterSecretStore
+                      description: Used to constrain a ClusterSecretStore to specific namespaces. Relevant only to ClusterSecretStore.
                       items:
                         description: |-
                           ClusterSecretStoreCondition describes a condition by which to choose namespaces to process ExternalSecrets in
@@ -354,96 +354,6 @@ should match snapshot of default values:
                             - akeylessGWApiURL
                             - authSecretRef
                           type: object
-                        alibaba:
-                          description: Alibaba configures this store to sync secrets using Alibaba Cloud provider
-                          properties:
-                            auth:
-                              description: AlibabaAuth contains a secretRef for credentials.
-                              properties:
-                                rrsa:
-                                  description: AlibabaRRSAAuth authenticates against Alibaba using RRSA.
-                                  properties:
-                                    oidcProviderArn:
-                                      type: string
-                                    oidcTokenFilePath:
-                                      type: string
-                                    roleArn:
-                                      type: string
-                                    sessionName:
-                                      type: string
-                                  required:
-                                    - oidcProviderArn
-                                    - oidcTokenFilePath
-                                    - roleArn
-                                    - sessionName
-                                  type: object
-                                secretRef:
-                                  description: AlibabaAuthSecretRef holds secret references for Alibaba credentials.
-                                  properties:
-                                    accessKeyIDSecretRef:
-                                      description: The AccessKeyID is used for authentication
-                                      properties:
-                                        key:
-                                          description: |-
-                                            A key in the referenced Secret.
-                                            Some instances of this field may be defaulted, in others it may be required.
-                                          maxLength: 253
-                                          minLength: 1
-                                          pattern: ^[-._a-zA-Z0-9]+$
-                                          type: string
-                                        name:
-                                          description: The name of the Secret resource being referred to.
-                                          maxLength: 253
-                                          minLength: 1
-                                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
-                                          type: string
-                                        namespace:
-                                          description: |-
-                                            The namespace of the Secret resource being referred to.
-                                            Ignored if referent is not cluster-scoped, otherwise defaults to the namespace of the referent.
-                                          maxLength: 63
-                                          minLength: 1
-                                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$
-                                          type: string
-                                      type: object
-                                    accessKeySecretSecretRef:
-                                      description: The AccessKeySecret is used for authentication
-                                      properties:
-                                        key:
-                                          description: |-
-                                            A key in the referenced Secret.
-                                            Some instances of this field may be defaulted, in others it may be required.
-                                          maxLength: 253
-                                          minLength: 1
-                                          pattern: ^[-._a-zA-Z0-9]+$
-                                          type: string
-                                        name:
-                                          description: The name of the Secret resource being referred to.
-                                          maxLength: 253
-                                          minLength: 1
-                                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
-                                          type: string
-                                        namespace:
-                                          description: |-
-                                            The namespace of the Secret resource being referred to.
-                                            Ignored if referent is not cluster-scoped, otherwise defaults to the namespace of the referent.
-                                          maxLength: 63
-                                          minLength: 1
-                                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$
-                                          type: string
-                                      type: object
-                                  required:
-                                    - accessKeyIDSecretRef
-                                    - accessKeySecretSecretRef
-                                  type: object
-                              type: object
-                            regionID:
-                              description: Alibaba Region to be used for the provider
-                              type: string
-                          required:
-                            - auth
-                            - regionID
-                          type: object
                         aws:
                           description: AWS configures this store to sync secrets using AWS Secret Manager provider
                           properties:
@@ -608,7 +518,6 @@ should match snapshot of default values:
                                     ForceDeleteWithoutRecovery in the same call. If you don't use either,
                                     then by default Secrets Manager uses a 30-day recovery window.
                                     see: https://docs.aws.amazon.com/secretsmanager/latest/apireference/API_DeleteSecret.html#SecretsManager-DeleteSecret-request-RecoveryWindowInDays
-                                  format: int64
                                   type: integer
                               type: object
                             service:
@@ -767,8 +676,11 @@ should match snapshot of default values:
                               type: string
                             customCloudConfig:
                               description: |-
-                                CustomCloudConfig defines custom Azure Stack Hub or Azure Stack Edge endpoints.
+                                CustomCloudConfig defines custom Azure endpoints for non-standard clouds.
                                 Required when EnvironmentType is AzureStackCloud.
+                                Optional for other environment types - useful for Azure China when using Workload Identity
+                                with AKS, where the OIDC issuer (login.partner.microsoftonline.cn) differs from the
+                                standard China Cloud endpoint (login.chinacloudapi.cn).
                                 IMPORTANT: This feature REQUIRES UseAzureSDK to be set to true. Custom cloud
                                 configuration is not supported with the legacy go-autorest SDK.
                               properties:
@@ -852,6 +764,97 @@ should match snapshot of default values:
                           required:
                             - vaultUrl
                           type: object
+                        barbican:
+                          description: Barbican configures this store to sync secrets using the OpenStack Barbican provider
+                          properties:
+                            auth:
+                              description: BarbicanAuth contains the authentication information for Barbican.
+                              properties:
+                                password:
+                                  description: BarbicanProviderPasswordRef defines a reference to a secret containing password for the Barbican provider.
+                                  properties:
+                                    secretRef:
+                                      description: |-
+                                        SecretKeySelector is a reference to a specific 'key' within a Secret resource.
+                                        In some instances, `key` is a required field.
+                                      properties:
+                                        key:
+                                          description: |-
+                                            A key in the referenced Secret.
+                                            Some instances of this field may be defaulted, in others it may be required.
+                                          maxLength: 253
+                                          minLength: 1
+                                          pattern: ^[-._a-zA-Z0-9]+$
+                                          type: string
+                                        name:
+                                          description: The name of the Secret resource being referred to.
+                                          maxLength: 253
+                                          minLength: 1
+                                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
+                                          type: string
+                                        namespace:
+                                          description: |-
+                                            The namespace of the Secret resource being referred to.
+                                            Ignored if referent is not cluster-scoped, otherwise defaults to the namespace of the referent.
+                                          maxLength: 63
+                                          minLength: 1
+                                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$
+                                          type: string
+                                      type: object
+                                  required:
+                                    - secretRef
+                                  type: object
+                                username:
+                                  description: BarbicanProviderUsernameRef defines a reference to a secret containing username for the Barbican provider.
+                                  maxProperties: 1
+                                  minProperties: 1
+                                  properties:
+                                    secretRef:
+                                      description: |-
+                                        SecretKeySelector is a reference to a specific 'key' within a Secret resource.
+                                        In some instances, `key` is a required field.
+                                      properties:
+                                        key:
+                                          description: |-
+                                            A key in the referenced Secret.
+                                            Some instances of this field may be defaulted, in others it may be required.
+                                          maxLength: 253
+                                          minLength: 1
+                                          pattern: ^[-._a-zA-Z0-9]+$
+                                          type: string
+                                        name:
+                                          description: The name of the Secret resource being referred to.
+                                          maxLength: 253
+                                          minLength: 1
+                                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
+                                          type: string
+                                        namespace:
+                                          description: |-
+                                            The namespace of the Secret resource being referred to.
+                                            Ignored if referent is not cluster-scoped, otherwise defaults to the namespace of the referent.
+                                          maxLength: 63
+                                          minLength: 1
+                                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$
+                                          type: string
+                                      type: object
+                                    value:
+                                      type: string
+                                  type: object
+                              required:
+                                - password
+                                - username
+                              type: object
+                            authURL:
+                              type: string
+                            domainName:
+                              type: string
+                            region:
+                              type: string
+                            tenantName:
+                              type: string
+                          required:
+                            - auth
+                          type: object
                         beyondtrust:
                           description: Beyondtrust configures this store to sync secrets using Password Safe provider.
                           properties:
@@ -1034,6 +1037,10 @@ should match snapshot of default values:
                                 clientTimeOutSeconds:
                                   description: Timeout specifies a time limit for requests made by this Client. The timeout includes connection time, any redirects, and reading the response body. Defaults to 45 seconds.
                                   type: integer
+                                decrypt:
+                                  default: true
+                                  description: 'When true, the response includes the decrypted password. When false, the password field is omitted. This option only applies to the SECRET retrieval type. Default: true.'
+                                  type: boolean
                                 retrievalType:
                                   description: The secret retrieval type. SECRET = Secrets Safe (credential, text, file). MANAGED_ACCOUNT = Password Safe account associated with a system.
                                   type: string
@@ -1557,60 +1564,59 @@ should match snapshot of default values:
                             - clientSecret
                             - tenant
                           type: object
-                        device42:
-                          description: Device42 configures this store to sync secrets using the Device42 provider
+                        doppler:
+                          description: Doppler configures this store to sync secrets using the Doppler provider
                           properties:
                             auth:
-                              description: Auth configures how secret-manager authenticates with a Device42 instance.
+                              description: Auth configures how the Operator authenticates with the Doppler API
                               properties:
-                                secretRef:
-                                  description: Device42SecretRef contains the secret reference for accessing the Device42 instance.
+                                oidcConfig:
+                                  description: OIDCConfig authenticates using Kubernetes ServiceAccount tokens via OIDC.
                                   properties:
-                                    credentials:
-                                      description: Username / Password is used for authentication.
+                                    expirationSeconds:
+                                      default: 600
+                                      description: |-
+                                        ExpirationSeconds sets the ServiceAccount token validity duration.
+                                        Defaults to 10 minutes.
+                                      format: int64
+                                      type: integer
+                                    identity:
+                                      description: Identity is the Doppler Service Account Identity ID configured for OIDC authentication.
+                                      type: string
+                                    serviceAccountRef:
+                                      description: ServiceAccountRef specifies the Kubernetes ServiceAccount to use for authentication.
                                       properties:
-                                        key:
+                                        audiences:
                                           description: |-
-                                            A key in the referenced Secret.
-                                            Some instances of this field may be defaulted, in others it may be required.
-                                          maxLength: 253
-                                          minLength: 1
-                                          pattern: ^[-._a-zA-Z0-9]+$
-                                          type: string
+                                            Audience specifies the `aud` claim for the service account token
+                                            If the service account uses a well-known annotation for e.g. IRSA or GCP Workload Identity
+                                            then this audiences will be appended to the list
+                                          items:
+                                            type: string
+                                          type: array
                                         name:
-                                          description: The name of the Secret resource being referred to.
+                                          description: The name of the ServiceAccount resource being referred to.
                                           maxLength: 253
                                           minLength: 1
                                           pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
                                           type: string
                                         namespace:
                                           description: |-
-                                            The namespace of the Secret resource being referred to.
+                                            Namespace of the resource being referred to.
                                             Ignored if referent is not cluster-scoped, otherwise defaults to the namespace of the referent.
                                           maxLength: 63
                                           minLength: 1
                                           pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$
                                           type: string
+                                      required:
+                                        - name
                                       type: object
+                                  required:
+                                    - identity
+                                    - serviceAccountRef
                                   type: object
-                              required:
-                                - secretRef
-                              type: object
-                            host:
-                              description: URL configures the Device42 instance URL.
-                              type: string
-                          required:
-                            - auth
-                            - host
-                          type: object
-                        doppler:
-                          description: Doppler configures this store to sync secrets using the Doppler provider
-                          properties:
-                            auth:
-                              description: Auth configures how the Operator authenticates with the Doppler API
-                              properties:
                                 secretRef:
-                                  description: DopplerAuthSecretRef contains the secret reference for accessing the Doppler API.
+                                  description: SecretRef authenticates using a Doppler service token stored in a Kubernetes Secret.
                                   properties:
                                     dopplerToken:
                                       description: |-
@@ -1644,9 +1650,10 @@ should match snapshot of default values:
                                   required:
                                     - dopplerToken
                                   type: object
-                              required:
-                                - secretRef
                               type: object
+                              x-kubernetes-validations:
+                                - message: Exactly one of 'secretRef' or 'oidcConfig' must be specified
+                                  rule: (has(self.secretRef) && !has(self.oidcConfig)) || (!has(self.secretRef) && has(self.oidcConfig))
                             config:
                               description: Doppler config (required if not using a Service Token)
                               type: string
@@ -1675,6 +1682,87 @@ should match snapshot of default values:
                           required:
                             - auth
                           type: object
+                        dvls:
+                          description: DVLS configures this store to sync secrets using Devolutions Server provider
+                          properties:
+                            auth:
+                              description: Auth defines the authentication method to use.
+                              properties:
+                                secretRef:
+                                  description: SecretRef contains the Application ID and Application Secret for authentication.
+                                  properties:
+                                    appId:
+                                      description: AppID is the reference to the secret containing the Application ID.
+                                      properties:
+                                        key:
+                                          description: |-
+                                            A key in the referenced Secret.
+                                            Some instances of this field may be defaulted, in others it may be required.
+                                          maxLength: 253
+                                          minLength: 1
+                                          pattern: ^[-._a-zA-Z0-9]+$
+                                          type: string
+                                        name:
+                                          description: The name of the Secret resource being referred to.
+                                          maxLength: 253
+                                          minLength: 1
+                                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
+                                          type: string
+                                        namespace:
+                                          description: |-
+                                            The namespace of the Secret resource being referred to.
+                                            Ignored if referent is not cluster-scoped, otherwise defaults to the namespace of the referent.
+                                          maxLength: 63
+                                          minLength: 1
+                                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$
+                                          type: string
+                                      type: object
+                                    appSecret:
+                                      description: AppSecret is the reference to the secret containing the Application Secret.
+                                      properties:
+                                        key:
+                                          description: |-
+                                            A key in the referenced Secret.
+                                            Some instances of this field may be defaulted, in others it may be required.
+                                          maxLength: 253
+                                          minLength: 1
+                                          pattern: ^[-._a-zA-Z0-9]+$
+                                          type: string
+                                        name:
+                                          description: The name of the Secret resource being referred to.
+                                          maxLength: 253
+                                          minLength: 1
+                                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
+                                          type: string
+                                        namespace:
+                                          description: |-
+                                            The namespace of the Secret resource being referred to.
+                                            Ignored if referent is not cluster-scoped, otherwise defaults to the namespace of the referent.
+                                          maxLength: 63
+                                          minLength: 1
+                                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$
+                                          type: string
+                                      type: object
+                                  required:
+                                    - appId
+                                    - appSecret
+                                  type: object
+                              required:
+                                - secretRef
+                              type: object
+                            insecure:
+                              description: |-
+                                Insecure allows connecting to DVLS over plain HTTP.
+                                This is NOT RECOMMENDED for production use.
+                                Set to true only if you understand the security implications.
+                              type: boolean
+                            serverUrl:
+                              description: ServerURL is the DVLS instance URL (e.g., https://dvls.example.com).
+                              type: string
+                          required:
+                            - auth
+                            - serverUrl
+                          type: object
                         fake:
                           description: Fake configures a store with static key/value pairs
                           properties:
@@ -1953,12 +2041,11 @@ should match snapshot of default values:
                           type: object
                         github:
                           description: |-
-                            Github configures this store to push GitHub Action secrets using GitHub API provider.
+                            Github configures this store to push GitHub Actions secrets using the GitHub API provider.
                             Note: This provider only supports write operations (PushSecret) and cannot fetch secrets from GitHub
                           properties:
                             appID:
                               description: appID specifies the Github APP that will be used to authenticate the client
-                              format: int64
                               type: integer
                             auth:
                               description: auth configures how secret-manager authenticates with a Github instance.
@@ -1999,8 +2086,17 @@ should match snapshot of default values:
                               type: string
                             installationID:
                               description: installationID specifies the Github APP installation that will be used to authenticate the client
-                              format: int64
                               type: integer
+                            orgSecretVisibility:
+                              description: |-
+                                orgSecretVisibility controls the visibility of organization secrets pushed via PushSecret.
+                                Valid values are "all" or "private".
+                                When unset, new secrets are created with visibility "all" and existing secrets preserve
+                                whatever visibility they already have in GitHub.
+                              enum:
+                                - all
+                                - private
+                              type: string
                             organization:
                               description: organization will be used to fetch secrets from the Github organization
                               type: string
@@ -2901,6 +2997,48 @@ should match snapshot of default values:
                                     - clientSecret
                                   type: object
                               type: object
+                            caBundle:
+                              description: |-
+                                CABundle is a PEM-encoded CA certificate bundle used to validate
+                                the Infisical server's TLS certificate. Mutually exclusive with CAProvider.
+                              format: byte
+                              type: string
+                            caProvider:
+                              description: |-
+                                CAProvider is a reference to a Secret or ConfigMap that contains a CA certificate.
+                                The certificate is used to validate the Infisical server's TLS certificate.
+                                Mutually exclusive with CABundle.
+                              properties:
+                                key:
+                                  description: The key where the CA certificate can be found in the Secret or ConfigMap.
+                                  maxLength: 253
+                                  minLength: 1
+                                  pattern: ^[-._a-zA-Z0-9]+$
+                                  type: string
+                                name:
+                                  description: The name of the object located at the provider type.
+                                  maxLength: 253
+                                  minLength: 1
+                                  pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
+                                  type: string
+                                namespace:
+                                  description: |-
+                                    The namespace the Provider type is in.
+                                    Can only be defined when used in a ClusterSecretStore.
+                                  maxLength: 63
+                                  minLength: 1
+                                  pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$
+                                  type: string
+                                type:
+                                  description: The type of provider to use such as "Secret", or "ConfigMap".
+                                  enum:
+                                    - Secret
+                                    - ConfigMap
+                                  type: string
+                              required:
+                                - name
+                                - type
+                              type: object
                             hostAPI:
                               default: https://app.infisical.com/api
                               description: HostAPI specifies the base URL of the Infisical API. If not provided, it defaults to "https://app.infisical.com/api".
@@ -3179,6 +3317,120 @@ should match snapshot of default values:
                                   type: string
                               type: object
                           type: object
+                        nebiusmysterybox:
+                          description: NebiusMysterybox configures this store to sync secrets using NebiusMysterybox provider
+                          properties:
+                            apiDomain:
+                              description: NebiusMysterybox API endpoint
+                              type: string
+                            auth:
+                              description: Auth defines parameters to authenticate in MysteryBox
+                              properties:
+                                serviceAccountCredsSecretRef:
+                                  description: |-
+                                    ServiceAccountCreds references a Kubernetes Secret key that contains a JSON
+                                    document with service account credentials used to get an IAM token.
+
+                                    Expected JSON structure:
+                                    {
+                                      "subject-credentials": {
+                                        "alg": "RS256",
+                                        "private-key": "-----BEGIN PRIVATE KEY-----\n<private-key>\n-----END PRIVATE KEY-----\n",
+                                        "kid": "<public-key-id>",
+                                        "iss": "<issuer-service-account-id>",
+                                        "sub": "<subject-service-account-id>"
+                                      }
+                                    }
+                                  properties:
+                                    key:
+                                      description: |-
+                                        A key in the referenced Secret.
+                                        Some instances of this field may be defaulted, in others it may be required.
+                                      maxLength: 253
+                                      minLength: 1
+                                      pattern: ^[-._a-zA-Z0-9]+$
+                                      type: string
+                                    name:
+                                      description: The name of the Secret resource being referred to.
+                                      maxLength: 253
+                                      minLength: 1
+                                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
+                                      type: string
+                                    namespace:
+                                      description: |-
+                                        The namespace of the Secret resource being referred to.
+                                        Ignored if referent is not cluster-scoped, otherwise defaults to the namespace of the referent.
+                                      maxLength: 63
+                                      minLength: 1
+                                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$
+                                      type: string
+                                  type: object
+                                tokenSecretRef:
+                                  description: Token authenticates with Nebius Mysterybox by presenting a token.
+                                  properties:
+                                    key:
+                                      description: |-
+                                        A key in the referenced Secret.
+                                        Some instances of this field may be defaulted, in others it may be required.
+                                      maxLength: 253
+                                      minLength: 1
+                                      pattern: ^[-._a-zA-Z0-9]+$
+                                      type: string
+                                    name:
+                                      description: The name of the Secret resource being referred to.
+                                      maxLength: 253
+                                      minLength: 1
+                                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
+                                      type: string
+                                    namespace:
+                                      description: |-
+                                        The namespace of the Secret resource being referred to.
+                                        Ignored if referent is not cluster-scoped, otherwise defaults to the namespace of the referent.
+                                      maxLength: 63
+                                      minLength: 1
+                                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$
+                                      type: string
+                                  type: object
+                              type: object
+                              x-kubernetes-validations:
+                                - message: either serviceAccountCredsSecretRef or tokenSecretRef must be set
+                                  rule: has(self.serviceAccountCredsSecretRef) || has(self.tokenSecretRef)
+                            caProvider:
+                              description: The provider for the CA bundle to use to validate NebiusMysterybox server certificate.
+                              properties:
+                                certSecretRef:
+                                  description: |-
+                                    SecretKeySelector is a reference to a specific 'key' within a Secret resource.
+                                    In some instances, `key` is a required field.
+                                  properties:
+                                    key:
+                                      description: |-
+                                        A key in the referenced Secret.
+                                        Some instances of this field may be defaulted, in others it may be required.
+                                      maxLength: 253
+                                      minLength: 1
+                                      pattern: ^[-._a-zA-Z0-9]+$
+                                      type: string
+                                    name:
+                                      description: The name of the Secret resource being referred to.
+                                      maxLength: 253
+                                      minLength: 1
+                                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
+                                      type: string
+                                    namespace:
+                                      description: |-
+                                        The namespace of the Secret resource being referred to.
+                                        Ignored if referent is not cluster-scoped, otherwise defaults to the namespace of the referent.
+                                      maxLength: 63
+                                      minLength: 1
+                                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$
+                                      type: string
+                                  type: object
+                              type: object
+                          required:
+                            - apiDomain
+                            - auth
+                          type: object
                         ngrok:
                           description: Ngrok configures this store to sync secrets using the ngrok provider.
                           properties:
@@ -3406,6 +3658,28 @@ should match snapshot of default values:
                               required:
                                 - serviceAccountSecretRef
                               type: object
+                            cache:
+                              description: |-
+                                Cache configures client-side caching for read operations (GetSecret, GetSecretMap).
+                                When enabled, secrets are cached with the specified TTL.
+                                Write operations (PushSecret, DeleteSecret) automatically invalidate relevant cache entries.
+                                If omitted, caching is disabled (default).
+                                cache: {} is a valid option to set.
+                              properties:
+                                maxSize:
+                                  default: 100
+                                  description: |-
+                                    MaxSize is the maximum number of secrets to cache.
+                                    When the cache is full, least-recently-used entries are evicted.
+                                  minimum: 1
+                                  type: integer
+                                ttl:
+                                  default: 5m
+                                  description: |-
+                                    TTL is the time-to-live for cached secrets.
+                                    Format: duration string (e.g., "5m", "1h", "30s")
+                                  type: string
+                              type: object
                             integrationInfo:
                               description: |-
                                 IntegrationInfo specifies the name and version of the integration built using the 1Password Go SDK.
@@ -3566,6 +3840,168 @@ should match snapshot of default values:
                             - region
                             - vault
                           type: object
+                        ovh:
+                          description: OVHcloud configures this store to sync secrets using the OVHcloud provider.
+                          properties:
+                            auth:
+                              description: Authentication method (mtls or token).
+                              properties:
+                                mtls:
+                                  description: OvhClientMTLS defines the configuration required to authenticate to OVHcloud's Secret Manager using mTLS.
+                                  properties:
+                                    caBundle:
+                                      format: byte
+                                      type: string
+                                    caProvider:
+                                      description: |-
+                                        CAProvider provides a custom certificate authority for accessing the provider's store.
+                                        The CAProvider points to a Secret or ConfigMap resource that contains a PEM-encoded certificate.
+                                      properties:
+                                        key:
+                                          description: The key where the CA certificate can be found in the Secret or ConfigMap.
+                                          maxLength: 253
+                                          minLength: 1
+                                          pattern: ^[-._a-zA-Z0-9]+$
+                                          type: string
+                                        name:
+                                          description: The name of the object located at the provider type.
+                                          maxLength: 253
+                                          minLength: 1
+                                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
+                                          type: string
+                                        namespace:
+                                          description: |-
+                                            The namespace the Provider type is in.
+                                            Can only be defined when used in a ClusterSecretStore.
+                                          maxLength: 63
+                                          minLength: 1
+                                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$
+                                          type: string
+                                        type:
+                                          description: The type of provider to use such as "Secret", or "ConfigMap".
+                                          enum:
+                                            - Secret
+                                            - ConfigMap
+                                          type: string
+                                      required:
+                                        - name
+                                        - type
+                                      type: object
+                                    certSecretRef:
+                                      description: |-
+                                        SecretKeySelector is a reference to a specific 'key' within a Secret resource.
+                                        In some instances, `key` is a required field.
+                                      properties:
+                                        key:
+                                          description: |-
+                                            A key in the referenced Secret.
+                                            Some instances of this field may be defaulted, in others it may be required.
+                                          maxLength: 253
+                                          minLength: 1
+                                          pattern: ^[-._a-zA-Z0-9]+$
+                                          type: string
+                                        name:
+                                          description: The name of the Secret resource being referred to.
+                                          maxLength: 253
+                                          minLength: 1
+                                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
+                                          type: string
+                                        namespace:
+                                          description: |-
+                                            The namespace of the Secret resource being referred to.
+                                            Ignored if referent is not cluster-scoped, otherwise defaults to the namespace of the referent.
+                                          maxLength: 63
+                                          minLength: 1
+                                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$
+                                          type: string
+                                      type: object
+                                    keySecretRef:
+                                      description: |-
+                                        SecretKeySelector is a reference to a specific 'key' within a Secret resource.
+                                        In some instances, `key` is a required field.
+                                      properties:
+                                        key:
+                                          description: |-
+                                            A key in the referenced Secret.
+                                            Some instances of this field may be defaulted, in others it may be required.
+                                          maxLength: 253
+                                          minLength: 1
+                                          pattern: ^[-._a-zA-Z0-9]+$
+                                          type: string
+                                        name:
+                                          description: The name of the Secret resource being referred to.
+                                          maxLength: 253
+                                          minLength: 1
+                                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
+                                          type: string
+                                        namespace:
+                                          description: |-
+                                            The namespace of the Secret resource being referred to.
+                                            Ignored if referent is not cluster-scoped, otherwise defaults to the namespace of the referent.
+                                          maxLength: 63
+                                          minLength: 1
+                                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$
+                                          type: string
+                                      type: object
+                                  required:
+                                    - certSecretRef
+                                    - keySecretRef
+                                  type: object
+                                token:
+                                  description: OvhClientToken defines the configuration required to authenticate to OVHcloud's Secret Manager using a token.
+                                  properties:
+                                    tokenSecretRef:
+                                      description: |-
+                                        SecretKeySelector is a reference to a specific 'key' within a Secret resource.
+                                        In some instances, `key` is a required field.
+                                      properties:
+                                        key:
+                                          description: |-
+                                            A key in the referenced Secret.
+                                            Some instances of this field may be defaulted, in others it may be required.
+                                          maxLength: 253
+                                          minLength: 1
+                                          pattern: ^[-._a-zA-Z0-9]+$
+                                          type: string
+                                        name:
+                                          description: The name of the Secret resource being referred to.
+                                          maxLength: 253
+                                          minLength: 1
+                                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
+                                          type: string
+                                        namespace:
+                                          description: |-
+                                            The namespace of the Secret resource being referred to.
+                                            Ignored if referent is not cluster-scoped, otherwise defaults to the namespace of the referent.
+                                          maxLength: 63
+                                          minLength: 1
+                                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$
+                                          type: string
+                                      type: object
+                                  required:
+                                    - tokenSecretRef
+                                  type: object
+                              type: object
+                            casRequired:
+                              description: 'Enables or disables check-and-set (CAS) (default: false).'
+                              type: boolean
+                            okmsTimeout:
+                              default: 30
+                              description: 'Setup a timeout in seconds when requests to the KMS are made (default: 30).'
+                              format: int32
+                              minimum: 1
+                              type: integer
+                            okmsid:
+                              description: specifies the OKMS ID.
+                              type: string
+                            server:
+                              description: specifies the OKMS server endpoint.
+                              type: string
+                          required:
+                            - auth
+                            - okmsid
+                            - server
+                          type: object
                         passbolt:
                           description: |-
                             PassboltProvider provides access to Passbolt secrets manager.
@@ -3794,7 +4230,7 @@ should match snapshot of default values:
                             - project
                           type: object
                         scaleway:
-                          description: Scaleway
+                          description: Scaleway configures this store to sync secrets using the Scaleway provider.
                           properties:
                             accessKey:
                               description: AccessKey is the non-secret part of the api key.
@@ -4057,7 +4493,7 @@ should match snapshot of default values:
                             - url
                           type: object
                         vault:
-                          description: Vault configures this store to sync secrets using Hashi provider
+                          description: Vault configures this store to sync secrets using the HashiCorp Vault provider.
                           properties:
                             auth:
                               description: Auth configures how secret-manager authenticates with the Vault server.
@@ -4209,6 +4645,9 @@ should match snapshot of default values:
                                           pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$
                                           type: string
                                       type: object
+                                    vaultRole:
+                                      description: VaultRole specifies the Vault role to use for TLS certificate authentication.
+                                      type: string
                                   type: object
                                 gcp:
                                   description: |-
@@ -4496,6 +4935,7 @@ should match snapshot of default values:
                                             Optional audiences field that will be used to request a temporary Kubernetes service
                                             account token for the service account referenced by `serviceAccountRef`.
                                             Defaults to a single audience `vault` it not specified.
+
                                             Deprecated: use serviceAccountRef.Audiences instead
                                           items:
                                             type: string
@@ -4505,9 +4945,9 @@ should match snapshot of default values:
                                             Optional expiration time in seconds that will be used to request a temporary
                                             Kubernetes service account token for the service account referenced by
                                             `serviceAccountRef`.
+
                                             Deprecated: this will be removed in the future.
                                             Defaults to 10 minutes.
-                                          format: int64
                                           type: integer
                                         serviceAccountRef:
                                           description: Service account field containing the name of a kubernetes ServiceAccount.
@@ -5426,10 +5866,9 @@ should match snapshot of default values:
                       description: Used to configure store refresh interval in seconds. Empty or 0 will default to the controller config.
                       type: integer
                     retrySettings:
-                      description: Used to configure http retries if failed
+                      description: Used to configure HTTP retries on failures.
                       properties:
                         maxRetries:
-                          format: int32
                           type: integer
                         retryInterval:
                           type: string
@@ -5510,7 +5949,7 @@ should match snapshot of default values:
                   description: SecretStoreSpec defines the desired state of SecretStore.
                   properties:
                     conditions:
-                      description: Used to constraint a ClusterSecretStore to specific namespaces. Relevant only to ClusterSecretStore
+                      description: Used to constrain a ClusterSecretStore to specific namespaces. Relevant only to ClusterSecretStore.
                       items:
                         description: |-
                           ClusterSecretStoreCondition describes a condition by which to choose namespaces to process ExternalSecrets in
@@ -6058,7 +6497,6 @@ should match snapshot of default values:
                                     ForceDeleteWithoutRecovery in the same call. If you don't use either,
                                     then by default Secrets Manager uses a 30 day recovery window.
                                     see: https://docs.aws.amazon.com/secretsmanager/latest/apireference/API_DeleteSecret.html#SecretsManager-DeleteSecret-request-RecoveryWindowInDays
-                                  format: int64
                                   type: integer
                               type: object
                             service:
@@ -6450,6 +6888,10 @@ should match snapshot of default values:
                                 clientTimeOutSeconds:
                                   description: Timeout specifies a time limit for requests made by this Client. The timeout includes connection time, any redirects, and reading the response body. Defaults to 45 seconds.
                                   type: integer
+                                decrypt:
+                                  default: true
+                                  description: 'When true, the response includes the decrypted password. When false, the password field is omitted. This option only applies to the SECRET retrieval type. Default: true.'
+                                  type: boolean
                                 retrievalType:
                                   description: The secret retrieval type. SECRET = Secrets Safe (credential, text, file). MANAGED_ACCOUNT = Password Safe account associated with a system.
                                   type: string
@@ -7243,11 +7685,10 @@ should match snapshot of default values:
                               type: string
                           type: object
                         github:
-                          description: Github configures this store to push Github Action secrets using Github API provider
+                          description: Github configures this store to push GitHub Actions secrets using the GitHub API provider.
                           properties:
                             appID:
                               description: appID specifies the Github APP that will be used to authenticate the client
-                              format: int64
                               type: integer
                             auth:
                               description: auth configures how secret-manager authenticates with a Github instance.
@@ -7288,7 +7729,6 @@ should match snapshot of default values:
                               type: string
                             installationID:
                               description: installationID specifies the Github APP installation that will be used to authenticate the client
-                              format: int64
                               type: integer
                             organization:
                               description: organization will be used to fetch secrets from the Github organization
@@ -8311,7 +8751,7 @@ should match snapshot of default values:
                             - project
                           type: object
                         scaleway:
-                          description: Scaleway
+                          description: Scaleway configures this store to sync secrets using the Scaleway provider.
                           properties:
                             accessKey:
                               description: AccessKey is the non-secret part of the api key.
@@ -8531,7 +8971,7 @@ should match snapshot of default values:
                             - url
                           type: object
                         vault:
-                          description: Vault configures this store to sync secrets using Hashi provider
+                          description: Vault configures this store to sync secrets using the HashiCorp Vault provider.
                           properties:
                             auth:
                               description: Auth configures how secret-manager authenticates with the Vault server.
@@ -8836,6 +9276,7 @@ should match snapshot of default values:
                                             Optional audiences field that will be used to request a temporary Kubernetes service
                                             account token for the service account referenced by `serviceAccountRef`.
                                             Defaults to a single audience `vault` it not specified.
+
                                             Deprecated: use serviceAccountRef.Audiences instead
                                           items:
                                             type: string
@@ -8845,9 +9286,9 @@ should match snapshot of default values:
                                             Optional expiration time in seconds that will be used to request a temporary
                                             Kubernetes service account token for the service account referenced by
                                             `serviceAccountRef`.
+
                                             Deprecated: this will be removed in the future.
                                             Defaults to 10 minutes.
-                                          format: int64
                                           type: integer
                                         serviceAccountRef:
                                           description: Service account field containing the name of a kubernetes ServiceAccount.
@@ -9617,7 +10058,7 @@ should match snapshot of default values:
                       description: Used to configure store refresh interval in seconds. Empty or 0 will default to the controller config.
                       type: integer
                     retrySettings:
-                      description: Used to configure http retries if failed
+                      description: Used to configure HTTP retries on failures.
                       properties:
                         maxRetries:
                           description: MaxRetries is the maximum number of retry attempts.

+ 1 - 1
deploy/charts/external-secrets/tests/crds_test.yaml

@@ -1,6 +1,6 @@
 suite: test crds
 templates:
-  - crds/secretstore.yaml
+  - crds/external-secrets.io_secretstore.yaml
 tests:
   - it: should match snapshot of default values
     asserts:

+ 21 - 0
deploy/charts/external-secrets/tests/provider_class_test.yaml

@@ -0,0 +1,21 @@
+suite: provider class
+templates:
+  - provider-class.yaml
+tests:
+  - it: renders a ClusterProviderClass for each enabled provider
+    set:
+      namespaceOverride: default
+      providers:
+        enabled: true
+        list:
+          - name: aws
+            type: aws
+            enabled: true
+    asserts:
+      - hasDocuments:
+          count: 1
+      - isKind:
+          of: ClusterProviderClass
+      - equal:
+          path: spec.address
+          value: provider-aws.default.svc:8080

+ 40 - 0
deploy/charts/external-secrets/tests/provider_rbac_test.yaml

@@ -0,0 +1,40 @@
+suite: test provider rbac
+templates:
+  - provider-rbac.yaml
+tests:
+  - it: should grant gcp providers access to secretmanager configs
+    set:
+      providers:
+        enabled: true
+        list:
+          - name: gcp
+            type: gcp
+            enabled: true
+            image:
+              repository: ghcr.io/external-secrets/provider-gcp
+              tag: test
+    documentIndex: 0
+    asserts:
+      - isKind:
+          of: ClusterRole
+      - contains:
+          path: rules[0].resources
+          content: secretmanagers
+  - it: should grant aws providers access to secretsmanager configs
+    set:
+      providers:
+        enabled: true
+        list:
+          - name: aws
+            type: aws
+            enabled: true
+            image:
+              repository: ghcr.io/external-secrets/provider-aws
+              tag: test
+    documentIndex: 0
+    asserts:
+      - isKind:
+          of: ClusterRole
+      - contains:
+          path: rules[0].resources
+          content: secretsmanagers

+ 3 - 3
deploy/charts/external-secrets/tests/webhook_test.yaml

@@ -5,7 +5,7 @@ templates:
   - webhook-service.yaml
   - webhook-certificate.yaml
   - validatingwebhook.yaml
-  - crds/externalsecret.yaml
+  - crds/external-secrets.io_externalsecret.yaml
 tests:
   - it: should match snapshot of default values
     asserts:
@@ -182,7 +182,7 @@ tests:
           value: "NAMESPACE/RELEASE-NAME-external-secrets-webhook"
     templates:
       - validatingwebhook.yaml
-      - crds/externalsecret.yaml
+      - crds/external-secrets.io_externalsecret.yaml
   - it: should not add annotations to the webhook
     set:
       webhook.create: true
@@ -194,7 +194,7 @@ tests:
           # value: "NAMESPACE/RELEASE-NAME-external-secrets-webhook"
     templates:
       - validatingwebhook.yaml
-      - crds/externalsecret.yaml
+      - crds/external-secrets.io_externalsecret.yaml
   - it: should have the correct labels
     set:
       webhook.create: true

+ 89 - 0
deploy/charts/external-secrets/values-test.yaml

@@ -0,0 +1,89 @@
+# Minimal test configuration for provider deployment
+replicaCount: 1
+
+image:
+  repository: oci.external-secrets.io/external-secrets/external-secrets
+  pullPolicy: IfNotPresent
+
+installCRDs: true
+v2:
+  enabled: true
+crds:
+  createClusterProviderClass: true
+
+providers:
+  enabled: true
+  list:
+    - name: aws-test
+      type: aws
+      enabled: true
+      replicaCount: 1
+      
+      image:
+        repository: oci.external-secrets.io/external-secrets/provider-aws
+        pullPolicy: IfNotPresent
+      
+      serviceAccount:
+        create: true
+        automount: true
+      
+      podSecurityContext:
+        enabled: true
+        runAsNonRoot: true
+        runAsUser: 65532
+      
+      securityContext:
+        enabled: true
+        allowPrivilegeEscalation: false
+        readOnlyRootFilesystem: true
+        runAsNonRoot: true
+        runAsUser: 65532
+        capabilities:
+          drop:
+          - ALL
+      
+      service:
+        type: ClusterIP
+        port: 8080
+      
+      resources:
+        limits:
+          cpu: 100m
+          memory: 128Mi
+        requests:
+          cpu: 25m
+          memory: 32Mi
+      
+      tls:
+        enabled: false
+      
+      config:
+        region: us-east-1
+      
+      logging:
+        level: info
+        format: json
+      
+      metrics:
+        enabled: true
+        port: 8081
+      
+      health:
+        port: 8082
+      
+      podDisruptionBudget:
+        enabled: false
+      
+      autoscaling:
+        enabled: false
+
+serviceAccount:
+  create: true
+
+resources:
+  limits:
+    cpu: 100m
+    memory: 128Mi
+  requests:
+    cpu: 25m
+    memory: 32Mi

+ 236 - 0
deploy/charts/external-secrets/values-with-providers-example.yaml

@@ -0,0 +1,236 @@
+# Example values.yaml demonstrating provider deployment
+# This shows how to deploy External Secrets with multiple providers
+
+# Deploy the External Secrets controller
+replicaCount: 1
+
+image:
+  repository: oci.external-secrets.io/external-secrets/external-secrets
+  pullPolicy: IfNotPresent
+  tag: ""
+
+# Install CRDs
+installCRDs: true
+v2:
+  enabled: true
+crds:
+  createClusterProviderClass: true
+
+# Enable provider deployments
+providers:
+  enabled: true
+  
+  list:
+    # AWS Provider Example
+    - name: aws-primary
+      type: aws
+      enabled: true
+      replicaCount: 2
+      
+      image:
+        repository: oci.external-secrets.io/external-secrets/provider-aws
+        pullPolicy: IfNotPresent
+        tag: ""
+      
+      serviceAccount:
+        create: true
+        annotations:
+          # Example: Use IRSA for AWS authentication
+          eks.amazonaws.com/role-arn: arn:aws:iam::123456789012:role/eso-provider-aws
+        automount: true
+      
+      podSecurityContext:
+        enabled: true
+        runAsNonRoot: true
+        runAsUser: 65532
+        fsGroup: 65532
+        seccompProfile:
+          type: RuntimeDefault
+      
+      securityContext:
+        enabled: true
+        allowPrivilegeEscalation: false
+        readOnlyRootFilesystem: true
+        runAsNonRoot: true
+        runAsUser: 65532
+        capabilities:
+          drop:
+          - ALL
+      
+      service:
+        type: ClusterIP
+        port: 8080
+      
+      resources:
+        limits:
+          cpu: 200m
+          memory: 256Mi
+        requests:
+          cpu: 50m
+          memory: 64Mi
+      
+      affinity:
+        podAntiAffinity:
+          preferredDuringSchedulingIgnoredDuringExecution:
+          - weight: 100
+            podAffinityTerm:
+              labelSelector:
+                matchLabels:
+                  app.kubernetes.io/component: provider
+                  external-secrets.io/provider: aws
+              topologyKey: kubernetes.io/hostname
+      
+      podDisruptionBudget:
+        enabled: true
+        minAvailable: 1
+      
+      tls:
+        enabled: true
+        certPath: /etc/provider/certs
+        caSecretName: external-secrets-v2-ca
+        mountCA: true
+      
+      config:
+        region: us-east-1
+        authMethod: irsa
+      
+      logging:
+        level: info
+        format: json
+      
+      metrics:
+        enabled: true
+        port: 8081
+        serviceMonitor:
+          enabled: true
+          interval: 30s
+          scrapeTimeout: 10s
+      
+      health:
+        port: 8082
+        livenessProbe:
+          enabled: true
+          initialDelaySeconds: 10
+          periodSeconds: 20
+        readinessProbe:
+          enabled: true
+          initialDelaySeconds: 5
+          periodSeconds: 10
+    
+    # GCP Provider Example (disabled by default)
+    - name: gcp
+      type: gcp
+      enabled: false
+      replicaCount: 2
+      
+      image:
+        repository: oci.external-secrets.io/external-secrets/provider-gcp
+        pullPolicy: IfNotPresent
+      
+      serviceAccount:
+        create: true
+        annotations:
+          # Example: Use Workload Identity for GCP authentication
+          iam.gke.io/gcp-service-account: eso-provider@project-id.iam.gserviceaccount.com
+      
+      resources:
+        limits:
+          cpu: 200m
+          memory: 256Mi
+        requests:
+          cpu: 50m
+          memory: 64Mi
+      
+      config:
+        projectID: my-project-id
+      
+      logging:
+        level: info
+      
+      metrics:
+        enabled: true
+    
+    # Azure Provider Example (disabled by default)
+    - name: azure
+      type: azure
+      enabled: false
+      replicaCount: 2
+      
+      image:
+        repository: oci.external-secrets.io/external-secrets/provider-azure
+        pullPolicy: IfNotPresent
+      
+      serviceAccount:
+        create: true
+        annotations:
+          # Example: Use Azure Workload Identity
+          azure.workload.identity/client-id: "00000000-0000-0000-0000-000000000000"
+      
+      podLabels:
+        azure.workload.identity/use: "true"
+      
+      resources:
+        limits:
+          cpu: 200m
+          memory: 256Mi
+        requests:
+          cpu: 50m
+          memory: 64Mi
+      
+      config:
+        vaultURL: https://my-keyvault.vault.azure.net
+        tenantID: "00000000-0000-0000-0000-000000000000"
+      
+      logging:
+        level: info
+      
+      metrics:
+        enabled: true
+    
+    # Vault Provider Example (disabled by default)
+    - name: vault
+      type: vault
+      enabled: false
+      replicaCount: 2
+      
+      image:
+        repository: oci.external-secrets.io/external-secrets/provider-vault
+        pullPolicy: IfNotPresent
+      
+      serviceAccount:
+        create: true
+      
+      resources:
+        limits:
+          cpu: 200m
+          memory: 256Mi
+        requests:
+          cpu: 50m
+          memory: 64Mi
+      
+      config:
+        vaultAddr: https://vault.example.com
+        authMethod: kubernetes
+      
+      extraEnv:
+      - name: VAULT_SKIP_VERIFY
+        value: "false"
+      
+      logging:
+        level: info
+      
+      metrics:
+        enabled: true
+
+# Standard controller configuration continues...
+serviceAccount:
+  create: true
+  annotations: {}
+
+resources:
+  limits:
+    cpu: 500m
+    memory: 512Mi
+  requests:
+    cpu: 100m
+    memory: 128Mi

この差分においてかなりの量のファイルが変更されているため、一部のファイルを表示していません