Skip to content

feat: platform SecretStack with AWS and Vault backends - #28

Open
patrickleet wants to merge 1 commit into
mainfrom
feat/secretstack-platform-backends
Open

feat: platform SecretStack with AWS and Vault backends#28
patrickleet wants to merge 1 commit into
mainfrom
feat/secretstack-platform-backends

Conversation

@patrickleet

@patrickleet patrickleet commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Change API group to hops.ops.com.ai and package name to secret-stack
  • Add spec.backend: aws|vault with optional Vault Helm install
  • Gate dependents on sticky Helm revision existence (not Ready)
  • Vault/AWS examples, mocks, tests, README

Dogfood

Installed via hops config install --path on dory; claim with backend: vault produced Ready Helm Releases for external-secrets + vault and ClusterSecretStore Object.

Test plan

  • Local render/install on dory
  • CI render/validate/test
  • Confirm breaking API group change is intentional for consumers

Summary by CodeRabbit

  • New Features

    • Added platform-neutral SecretStack support for AWS Secrets Manager and HashiCorp Vault backends.
    • Added optional local Vault installation, external Vault configuration, authentication, and provider settings.
    • Added backend-aware status information, including SecretStore and Vault readiness details.
    • Updated examples and resource definitions with backend selection and Vault usage guidance.
  • Documentation

    • Renamed the project to Secret Stack and expanded the README with AWS, Vault, installation, and development instructions.
  • Tests

    • Expanded validation and end-to-end coverage for AWS, Vault, external Vault, and observed-resource scenarios.

Break the API group to hops.ops.com.ai and select secrets backend via
spec.backend (aws|vault). Gate PodIdentity and SM stores on aws; add optional
Vault Helm install plus Vault SecretStore. Gate dependents on sticky helm
revision existence rather than Ready. Dogfooded locally with hops config
install --path against dory (ESO + Vault Releases Ready).
@coderabbitai

coderabbitai Bot commented Aug 8, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The PR renames SecretStack to a platform-neutral API and adds AWS or Vault backend selection. Rendering now supports optional Vault installation, backend-specific SecretStores, expanded status data, and updated validation scenarios.

Changes

SecretStack backend expansion

Layer / File(s) Summary
Platform API and configuration contract
apis/secretstacks/*, examples/secretstacks/*, README.md, upbound.yaml, Makefile, .gitignore
The API changes from AWS-specific identifiers to hops.ops.com.ai. The CRD adds AWS or Vault configuration and backend-aware status fields. Examples, package metadata, documentation, and Rust build ignores are updated.
Backend state and resource rendering
functions/render/000-state-init.yaml.gotmpl, functions/render/200-helm-release-external-secrets.yaml.gotmpl, functions/render/201-helm-release-vault.yaml.gotmpl, functions/render/210-aws-pod-identity.yaml.gotmpl, functions/render/230-secret-store.yaml.gotmpl
Rendering initializes AWS or Vault state, optionally installs Vault, limits PodIdentity to AWS, and generates AWS or Vault SecretStore providers.
Observed state and lifecycle
functions/render/010-state-status.yaml.gotmpl, functions/render/999-status.yaml.gotmpl, examples/test/mocks/observed-resources/*
Observed resources now track existence, readiness, and Helm revisions. Status includes backend, SecretStore details, and optional Vault release details.
Examples, workflows, and render validation
.github/workflows/*, tests/test-render/main.k, tests/e2etest-secretstacks/main.k, examples/test/mocks/*
Tests and CI now cover generic API resources, AWS scenarios, Vault scenarios, and deployed Helm status data.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant SecretStack
  participant StateInit
  participant VaultRelease
  participant SecretStore
  SecretStack->>StateInit: provide backend configuration
  StateInit->>VaultRelease: render optional Vault Helm Release
  StateInit->>SecretStore: provide AWS or Vault provider state
  VaultRelease->>SecretStore: expose observed Vault installation state
  SecretStore->>SecretStack: render backend-specific store resources
Loading

Possibly related PRs

  • hops-ops/aws-secret-stack#16: Updates shared SecretStack validation matrices, observed-resource fixtures, and standard render/status logic.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: a platform-neutral SecretStack with selectable AWS and Vault backends.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/secretstack-platform-backends

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (1)
tests/test-render/main.k (1)

172-181: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Test revision-based gates when Helm is not Ready.

Both cases set revision = 1 and Ready=True. A readiness-based gate would also render the SecretStore. Set the Helm Ready condition to False while retaining revision = 1, then keep the SecretStore assertions. This validates the sticky revision contract.

Proposed test change
 conditions = [
-    {type = "Ready", status = "True"}
+    {type = "Ready", status = "False"}
     {type = "Synced", status = "True"}
 ]

Also applies to: 244-271

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test-render/main.k` around lines 172 - 181, Update the status fixtures
in the affected test cases around the status assignment so Helm retains revision
= 1 but its Ready condition is set to False. Keep the existing SecretStore
assertions unchanged, ensuring both cases validate revision-based rendering
independently of readiness.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apis/secretstacks/definition.yaml`:
- Around line 121-125: Add conditional x-kubernetes-validations to the
SecretStack schema so AWS requires spec.aws.region when backend=aws, Vault
requires spec.vault.server unless Vault installation is enabled, and external
Vault token authentication requires vault.auth.tokenSecretRef with an existing
reference rather than relying on the default name. Add negative admission/render
tests covering each incomplete configuration and preserve valid installed-Vault
and explicitly configured cases.

In `@functions/render/000-state-init.yaml.gotmpl`:
- Around line 75-83: Extend the vaultInstall branch in the state initialization
template to bootstrap the configured Kubernetes auth method before SecretStack
becomes Ready. Render the auth method, Kubernetes API configuration, and role
using vaultAuthMountPath, vaultAuthRole, and the configured service account, and
add readiness dependencies so SecretStack waits for these resources.

In `@functions/render/230-secret-store.yaml.gotmpl`:
- Around line 61-65: Update the conditional around tokenSecretRef namespace in
the Vault SecretStore template to add the namespace only when $ss.scope is
"Cluster". Remove the normalized namespace check and preserve the existing
defaulting and merge behavior within the ClusterSecretStore branch.

---

Nitpick comments:
In `@tests/test-render/main.k`:
- Around line 172-181: Update the status fixtures in the affected test cases
around the status assignment so Helm retains revision = 1 but its Ready
condition is set to False. Keep the existing SecretStore assertions unchanged,
ensuring both cases validate revision-based rendering independently of
readiness.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 1ec0a569-8263-470e-9b56-3e005d666171

📥 Commits

Reviewing files that changed from the base of the PR and between b5ad752 and 2fe4c04.

📒 Files selected for processing (27)
  • .github/workflows/on-pr.yaml
  • .github/workflows/on-push-main.yaml
  • .gitignore
  • Makefile
  • README.md
  • apis/secretstacks/composition.yaml
  • apis/secretstacks/definition.yaml
  • examples/secretstacks/minimal.yaml
  • examples/secretstacks/standard.yaml
  • examples/secretstacks/vault-external.yaml
  • examples/secretstacks/vault.yaml
  • examples/test/mocks/observed-resources/standard/steps/1/helm-external-secrets.yaml
  • examples/test/mocks/observed-resources/vault/steps/1/helm-external-secrets.yaml
  • examples/test/mocks/observed-resources/vault/steps/1/helm-vault.yaml
  • examples/test/mocks/observed-resources/vault/steps/2/helm-external-secrets.yaml
  • examples/test/mocks/observed-resources/vault/steps/2/helm-vault.yaml
  • examples/test/mocks/observed-resources/vault/steps/2/secret-store.yaml
  • functions/render/000-state-init.yaml.gotmpl
  • functions/render/010-state-status.yaml.gotmpl
  • functions/render/200-helm-release-external-secrets.yaml.gotmpl
  • functions/render/201-helm-release-vault.yaml.gotmpl
  • functions/render/210-aws-pod-identity.yaml.gotmpl
  • functions/render/230-secret-store.yaml.gotmpl
  • functions/render/999-status.yaml.gotmpl
  • tests/e2etest-secretstacks/main.k
  • tests/test-render/main.k
  • upbound.yaml

Comment on lines +121 to +125
description: AWS configuration. Required when backend=aws (region).
type: object
properties:
region:
description: AWS region for the EKS cluster.
description: AWS region for the EKS cluster and Secrets Manager.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect existing validation patterns and backend render guards.
rg -n -C 4 \
  'x-kubernetes-validations|backend:|region:|server:|tokenSecretRef:' \
  apis/secretstacks tests examples

rg -n -C 4 \
  'state\.aws\.region|state\.vault\.server|tokenSecretRef' \
  functions/render

Repository: hops-ops/aws-secret-stack

Length of output: 13382


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf 'definition.yaml relevant sections:\n'
sed -n '1,260p' apis/secretstacks/definition.yaml

printf '\nstate init template relevant sections:\n'
sed -n '1,160p' functions/render/000-state-init.yaml.gotmpl

printf '\nsecret store template lines:\n'
sed -n '1,90p' functions/render/230-secret-store.yaml.gotmpl

printf '\nGo template files mentioning validation errors or requirements:\n'
rg -n 'errorf|required|required value|x-kubernetes-validations|validation' .

Repository: hops-ops/aws-secret-stack

Length of output: 20048


Reject incomplete backend configuration at admission.

spec.aws.region is optional, and the AWS SecretStore is omitted when it is empty. spec.vault.server is optional, and the Vault SecretStore is omitted when it is empty while not installing Vault. For external Vault + token auth, spec.vault.auth.tokenSecretRef is optional and may default only to name: vault-token, even though the referenced Secret is missing. Add conditional x-kubernetes-validations and negative admission/render tests for these cases.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apis/secretstacks/definition.yaml` around lines 121 - 125, Add conditional
x-kubernetes-validations to the SecretStack schema so AWS requires
spec.aws.region when backend=aws, Vault requires spec.vault.server unless Vault
installation is enabled, and external Vault token authentication requires
vault.auth.tokenSecretRef with an existing reference rather than relying on the
default name. Add negative admission/render tests covering each incomplete
configuration and preserve valid installed-Vault and explicitly configured
cases.

Comment on lines +75 to +83
{{- if eq $vaultAuthMethod "" }}
{{- if $vaultInstall }}
{{- $vaultAuthMethod = "kubernetes" }}
{{- else }}
{{- $vaultAuthMethod = "token" }}
{{- end }}
{{- end }}
{{- $vaultAuthMountPath := $vaultAuth.mountPath | default "kubernetes" }}
{{- $vaultAuthRole := $vaultAuth.role | default "external-secrets" }}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Find declarative Vault Kubernetes-auth bootstrap resources.
rg -n -C 3 \
  'auth enable kubernetes|auth/kubernetes/config|auth/kubernetes/role|external-secrets' \
  functions examples tests

Repository: hops-ops/aws-secret-stack

Length of output: 25617


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate and inspect the relevant Vault templates and state defaults around auth installation and SecretStore rendering.
for f in functions/render/000-state-init.yaml.gotmpl functions/render/201-helm-release-vault.yaml.gotmpl functions/render/230-secret-store.yaml.gotmpl examples/secretstacks/vault.yaml examples/secretstacks/vault-external.yaml tests/test-render/main.k; do
  echo "===== $f ====="
  wc -l "$f"
  rg -n -C 5 'vault|auth|kubernetes|external-secrets|auth enable|auth/kubernetes/config|auth/kubernetes/role|role:|method:' "$f" || true
done

Repository: hops-ops/aws-secret-stack

Length of output: 23128


🌐 Web query:

HashiCorp Vault Kubernetes auth method Kubernetes API configuration role

💡 Result:

To configure the HashiCorp Vault Kubernetes authentication method, you must perform two primary steps: configuring the authentication backend to communicate with the Kubernetes API and defining roles that map Kubernetes service accounts to Vault policies. Configuration of the Kubernetes Auth Backend The backend configuration informs Vault how to reach the Kubernetes API and how to validate service account tokens via the TokenReview API [1][2]. 1. Mount the Kubernetes auth method (if not already done): vault auth enable kubernetes [3] 2. Configure the backend using the /auth/kubernetes/config endpoint [1][2]. - kubernetes_host: The URL of the Kubernetes API server (e.g., https://:) [1][4]. - kubernetes_ca_cert: The CA certificate used to verify the Kubernetes API server's TLS certificate. If Vault is running inside a Kubernetes pod, omitting this will cause Vault to default to the pod's service account CA certificate [1][2]. - token_reviewer_jwt: A service account token with permission to access the TokenReview API. If Vault is running in a pod, omitting this will default to the pod's service account token [1][2]. Important: Ensure the service account used by Vault (or specified as the token_reviewer_jwt) has the system:auth-delegator ClusterRole assigned to allow it to perform TokenReview requests [5]. Role Configuration Roles define the mapping between Kubernetes service accounts and Vault policies [5]. You can create a role using the /auth/kubernetes/role/:name endpoint [6]. Key parameters for role creation: - bound_service_account_names: A list of Kubernetes service account names permitted to use this role [6][7]. - bound_service_account_namespaces: A list of Kubernetes namespaces allowed to use this role [6][7]. - token_policies: The list of Vault policies to assign to the token generated upon successful authentication [6][7]. - audience: An optional audience to verify in the JWT [6][7]. Example command: vault write auth/kubernetes/role/my-role \ bound_service_account_names="my-service-account" \ bound_service_account_namespaces="default" \ token_policies="my-policy" \ ttl=1h [5] These configurations ensure that Vault can securely verify incoming Kubernetes tokens and authorize them based on your defined security policies [2][5].

Citations:


🌐 Web query:

HashiCorp Vault Helm chart kubernetes auth enable auto_unseal kubernetes auth backend role

💡 Result:

To manage HashiCorp Vault on Kubernetes using the Helm chart, you must configure three distinct areas: Auto-Unseal, the Kubernetes Auth Method, and the Kubernetes Auth Backend Role [1][2][3]. Auto-Unseal Configuration Auto-unseal is configured by providing a seal stanza in the Vault configuration block within your values.yaml file [4][3]. This requires you to specify the cloud provider KMS or HSM service (e.g., AWS KMS, Google Cloud KMS) [1][4]. Example for values.yaml: server: ha: enabled: true config: | seal "gcpckms" { project = "your-project" region = "global" key_ring = "your-key-ring" crypto_key = "your-key" } # Other config... Kubernetes Auth Method You must enable and configure the Kubernetes Auth Method within Vault after the cluster is running [5][6]. This is typically done via the Vault CLI or API [6]. 1. Enable the method: vault auth enable kubernetes 2. Configure the connection to Kubernetes: vault write auth/kubernetes/config \ kubernetes_host=https://$KUBERNETES_SERVICE_HOST:$KUBERNETES_SERVICE_PORT You can also use the Helm chart's server.authDelegator.enabled=true setting to automatically bind the necessary ClusterRole for the Vault service account to perform token reviews [7][8]. Kubernetes Auth Backend Role Once the auth method is enabled, you create roles that map Kubernetes service accounts to Vault policies [6][9]. This can be managed via the CLI or Terraform [6][9]. Example using the Vault CLI: vault write auth/kubernetes/role/my-app-role \ bound_service_account_names=my-app-sa \ bound_service_account_namespaces=default \ policies=my-app-policy \ ttl=1h For automated infrastructure management, you can use the vault_kubernetes_auth_backend_role resource in Terraform [9]. The role defines which Kubernetes service accounts and namespaces are authorized to authenticate with Vault and what policies they receive [6][9].

Citations:


Bootstrap the Kubernetes auth method when rendering the installed Vault.

vault.install=true renders Vault before 230-secret-store.yaml.gotmpl creates a Vault ClusterSecretStore with Kubernetes auth. Add the Vault Kubernetes auth method, Kubernetes API configuration, and role for the configured service account, and keep the SecretStack from becoming Ready until those resources exist.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@functions/render/000-state-init.yaml.gotmpl` around lines 75 - 83, Extend the
vaultInstall branch in the state initialization template to bootstrap the
configured Kubernetes auth method before SecretStack becomes Ready. Render the
auth method, Kubernetes API configuration, and role using vaultAuthMountPath,
vaultAuthRole, and the configured service account, and add readiness
dependencies so SecretStack waits for these resources.

Comment on lines +61 to +65
{{- /* ClusterSecretStore tokenSecretRef needs namespace */}}
{{- if or (eq $ss.scope "Cluster") $state.vault.auth.tokenSecretRef.namespace }}
{{- $tokenNs := $state.vault.auth.tokenSecretRef.namespace | default $state.namespace }}
{{- $tokenRef = merge $tokenRef (dict "namespace" $tokenNs) }}
{{- end }}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== locate file =="
fd -a '230-secret-store.yaml.gotmpl' . || true

echo "== file snippet =="
file="$(fd '230-secret-store.yaml.gotmpl' . | head -n1)"
if [ -n "${file:-}" ]; then
  wc -l "$file"
  sed -n '1,140p' "$file" | nl -ba
fi

echo "== state/init references to vault.auth.tokenSecretRef =="
rg -n "tokenSecretRef|vault\.auth|namespace" -S . --glob '!node_modules' --glob '!dist' --glob '!build' | head -n 200

Repository: hops-ops/aws-secret-stack

Length of output: 358


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== file snippet =="
sed -n '1,180p' functions/render/230-secret-store.yaml.gotmpl

echo "== state/init references to vault.auth.tokenSecretRef =="
rg -n "tokenSecretRef|vault\.auth|namespace" -S . --glob '!node_modules' --glob '!dist' --glob '!build' | sed -n '1,240p'

Repository: hops-ops/aws-secret-stack

Length of output: 12436


🌐 Web query:

External Secrets Operator Vault provider tokenSecretRef namespace ClusterSecretStore SecretStore tokenSecretRef schema

💡 Result:

In the External Secrets Operator (ESO), the tokenSecretRef field is used within the Vault provider configuration to authenticate using a static Vault token stored in a Kubernetes Secret [1][2]. The schema and requirements for this field differ depending on whether you are using a namespaced SecretStore or a cluster-wide ClusterSecretStore [2][3]. Schema for tokenSecretRef The tokenSecretRef block requires the following fields: - name: The name of the Kubernetes Secret containing the Vault token [1][4]. - key: The specific key within that Kubernetes Secret that holds the token value [1][4]. - namespace: (Mandatory for ClusterSecretStore) The namespace where the Kubernetes Secret resides [1][2]. For a namespaced SecretStore, this field is typically omitted because the operator looks for the secret in the same namespace as the SecretStore [2][3]. Usage Examples For a SecretStore (Namespaced): In a namespaced SecretStore, the operator expects the referenced secret to exist in the same namespace as the SecretStore [2][3]. spec: provider: vault:... auth: tokenSecretRef: name: "my-vault-token-secret" key: "token" For a ClusterSecretStore (Cluster-wide): In a ClusterSecretStore, you must explicitly define the namespace where the secret is located, as the store is cluster-scoped and cannot rely on implicit namespace matching [1][2]. spec: provider: vault:... auth: tokenSecretRef: name: "my-vault-token-secret" namespace: "secret-admin" key: "token" Key Considerations: - ClusterSecretStore: Because ClusterSecretStore is global, failing to specify the namespace for tokenSecretRef will cause the authentication to fail, as the operator will not know where to look for the secret [2][5]. - Security: Always ensure that the namespace containing the secret is appropriately restricted to maintain secure access to the Vault token [3].

Citations:


Omit tokenSecretRef.namespace for a namespaced SecretStore.

State initialization always normalizes vault.auth.tokenSecretRef.namespace to external-secrets. That causes this branch to add tokenSecretRef.namespace to every Vault SecretStore token reference. ESO uses tokenSecretRef.namespace for ClusterSecretStore; namespaced SecretStore references are scoped to the SecretStore namespace.

Only add namespace when $ss.scope is Cluster.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@functions/render/230-secret-store.yaml.gotmpl` around lines 61 - 65, Update
the conditional around tokenSecretRef namespace in the Vault SecretStore
template to add the namespace only when $ss.scope is "Cluster". Remove the
normalized namespace check and preserve the existing defaulting and merge
behavior within the ClusterSecretStore branch.

@github-actions

github-actions Bot commented Aug 8, 2026

Copy link
Copy Markdown

Published Crossplane Package

The following Crossplane package was published as part of this PR:

Package: ghcr.io/hops-ops/aws-secret-stack:pr-28-c66547c7731fe39477be6bb7b404f58bf50a51b9

View Package

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant