Skip to content

SPIRE-129: Add native UpstreamAuthority plugin support (cert-manager, Vault)#113

Open
anirudhAgniRedhat wants to merge 4 commits into
openshift:mainfrom
anirudhAgniRedhat:upstream-authority-support
Open

SPIRE-129: Add native UpstreamAuthority plugin support (cert-manager, Vault)#113
anirudhAgniRedhat wants to merge 4 commits into
openshift:mainfrom
anirudhAgniRedhat:upstream-authority-support

Conversation

@anirudhAgniRedhat
Copy link
Copy Markdown
Contributor

@anirudhAgniRedhat anirudhAgniRedhat commented Apr 29, 2026

Summary

Add optional upstreamAuthority field to SpireServer CR, allowing the SPIRE intermediate CA to be signed by an external PKI (cert-manager or Vault) instead of using a self-signed CA.

Supported plugins:

  • cert-manager -- in-cluster SA requests signed intermediate CA via CertificateRequest (Issuer/ClusterIssuer)
  • Vault -- PKI secrets engine with Kubernetes auth (k8s_auth), zero user-managed credentials

What the operator does on reconcile:

  • Generates SPIRE UpstreamAuthority plugin block in server ConfigMap
  • Injects projected SA token + CA cert volumes into StatefulSet (Vault)
  • Conditionally adds cert-manager.io/certificaterequests rule to spire-server ClusterRole
  • Validates config, sets ConfigurationValid=False on error
  • Rolling update via config hash on any change
  • Reverts to self-signed CA when upstreamAuthority is removed

No new controllers, webhooks, or CRDs.

Test plan

  • Unit tests: ConfigMap generation, StatefulSet volumes, validation, RBAC
  • Cluster test: cert-manager plugin loads, config generated correctly
  • Cluster test: Vault plugin signs intermediate CA, trust bundle contains Vault Root CA
  • Cluster test: removal reverts to self-signed CA, bundle accumulates both CAs
  • Cluster test: cross cluster vault plugin using k8s_auth validated

Summary by CodeRabbit

  • New Features

    • Optional spec.upstreamAuthority on SpireServer to use external PKI (cert-manager or Vault) for signing the intermediate CA; CRD validation enforces exactly one provider.
    • Operator adds RBAC for cert-manager CertificateRequest operations and mounts Vault token and optional upstream CA secret when Vault is selected.
    • CSV capability timestamp updated.
  • Tests

    • Added unit tests covering upstreamAuthority validation, manifest rendering, RBAC, and StatefulSet volume/mount behavior.

@openshift-ci-robot
Copy link
Copy Markdown

openshift-ci-robot commented Apr 29, 2026

@anirudhAgniRedhat: This pull request references SPIRE-129 which is a valid jira issue.

Warning: The referenced jira issue has an invalid target version for the target branch this PR targets: expected the story to target the "5.0.0" version, but no target version was set.

Details

In response to this:

Summary

Add optional upstreamAuthority field to SpireServer CR, allowing the SPIRE intermediate CA to be signed by an external PKI (cert-manager or Vault) instead of using a self-signed CA.

Supported plugins:

  • cert-manager -- in-cluster SA requests signed intermediate CA via CertificateRequest (Issuer/ClusterIssuer)
  • Vault -- PKI secrets engine with Kubernetes auth (k8s_auth), zero user-managed credentials

What the operator does on reconcile:

  • Generates SPIRE UpstreamAuthority plugin block in server ConfigMap
  • Injects projected SA token + CA cert volumes into StatefulSet (Vault)
  • Conditionally adds cert-manager.io/certificaterequests rule to spire-server ClusterRole
  • Validates config, sets ConfigurationValid=False on error
  • Rolling update via config hash on any change
  • Reverts to self-signed CA when upstreamAuthority is removed

No new controllers, webhooks, or CRDs.

Test plan

  • Unit tests: ConfigMap generation, StatefulSet volumes, validation, RBAC
  • Cluster test: cert-manager plugin loads, config generated correctly
  • Cluster test: Vault plugin signs intermediate CA, trust bundle contains Vault Root CA
  • Cluster test: removal reverts to self-signed CA, bundle accumulates both CAs
  • Cluster test: cross cluster vault plugin using k8s_auth validated

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the openshift-eng/jira-lifecycle-plugin repository.

@openshift-ci-robot openshift-ci-robot added the jira/valid-reference Indicates that this PR references a valid Jira ticket of any type. label Apr 29, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 29, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds an optional SpireServer.spec.upstreamAuthority to select exactly one external PKI backend (cert-manager or Vault); updates API types, CRD schema/validation, CSV/RBAC, ConfigMap/StatefulSet generation, reconcile validation, RBAC reconciliation, and unit tests covering the new behavior.

Changes

Upstream Authority feature

Layer / File(s) Summary
API Types / Data Shape
api/v1alpha1/spire_server_config_types.go
Adds SpireServerSpec.UpstreamAuthority *UpstreamAuthorityConfig and types: UpstreamAuthorityConfig, UpstreamAuthorityCertManager, UpstreamAuthorityVault, VaultK8sAuthConfig, SecretKeyReference.
CRD Schema / Validation
config/crd/bases/operator.openshift.io_spireservers.yaml, bundle/manifests/operator.openshift.io_spireservers.yaml
Adds spec.upstreamAuthority schema with certManager and vault branches and x-kubernetes-validations enforcing exactly one backend when present; backend fields and constraints added (vault URL pattern, defaults, required k8s auth fields).
CSV / Cluster Install RBAC
bundle/manifests/zero-trust-workload-identity-manager.clusterserviceversion.yaml, config/rbac/role.yaml
Updates CSV timestamp and adds clusterPermissions / ClusterRole rule allowing cert-manager.io certificaterequests (create,get,list,delete).
Controller validation & reconcile
pkg/controller/spire-server/controller.go, pkg/controller/zero-trust-workload-identity-manager/controller.go
Calls validateUpstreamAuthority during configuration validation; on validation error sets ConfigurationValid condition and aborts reconcile; extends generated RBAC for cert-manager access.
Validation logic & tests
pkg/controller/spire-server/validation.go, pkg/controller/spire-server/validation_test.go
Adds validateUpstreamAuthority, validateUpstreamAuthorityCertManager, validateUpstreamAuthorityVault enforcing mutual exclusivity and backend-specific checks (cert-manager namespace/issuer/kind; Vault URL scheme/host, k8sAuth presence and role). Adds table-driven tests covering expected error messages and success cases.
ConfigMap generation & tests
pkg/controller/spire-server/configmap.go, pkg/controller/spire-server/configmaps_test.go
When UpstreamAuthority set, emits plugins.UpstreamAuthority in server.conf; builds cert-manager or vault plugin_data with defaults and optionals (issuer kind/group defaults, vault pki mount default pki, optional ca_cert_path, insecure_skip_verify, optional vault namespace, k8s_auth with fixed token_path). Tests added for plugin content and omission.
StatefulSet wiring & tests
pkg/controller/spire-server/statefulset.go, pkg/controller/spire-server/statefulset_test.go
When Vault.k8sAuth configured, adds projected vault-token volume (serviceAccountToken projection with audience default vault, expiration 600s) and mounts read-only; when Vault.CACertSecretRef set, adds upstream-ca secret volume mapping configured key→ca.crt and mount. Tests validate volume/projection/items and absence for other modes.
RBAC generation & tests
pkg/controller/spire-server/rbac.go, pkg/controller/spire-server/rbac_test.go
Conditionally appends cert-manager.io certificaterequests PolicyRule when CertManager configured; unit test verifies rule present only in cert-manager case and absent otherwise.

Sequence Diagram(s)

sequenceDiagram
    participant CR as SpireServer CR
    participant Operator as Operator Controller
    participant K8s as Kubernetes API
    participant PKI as External PKI (Vault / cert-manager)

    CR->>Operator: Create/Update SpireServer with spec.upstreamAuthority
    Operator->>Operator: validateUpstreamAuthority()
    alt validation fails
        Operator->>K8s: update SpireServer.status (ConfigurationValid=false)
        Operator-->>CR: reconciliation abort (error)
    else validation passes
        Operator->>K8s: reconcile ClusterRole (add cert-manager rule if cert-manager configured)
        Operator->>K8s: create/update ConfigMap (server.conf with UpstreamAuthority plugin)
        Operator->>K8s: create/update StatefulSet (add token projection / CA secret if Vault)
        K8s->>PKI: runtime uses projected token or cert-manager CertificateRequest
        Operator-->>CR: reconciliation complete (resources applied)
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 10 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 30.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Test Structure And Quality ⚠️ Warning Tests violate single responsibility: ConfigMap tests assert 8 behaviors, StatefulSet tests assert 10 (volume config + mounting). Some assertions lack meaningful messages. Separate volume config from mounting tests. Add descriptive assertion messages to all Fatal/Error calls for better diagnostics.
✅ Passed checks (10 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: adding native UpstreamAuthority plugin support for cert-manager and Vault backends to the SPIRE operator, which aligns with the changeset's primary objective.
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.
Stable And Deterministic Test Names ✅ Passed All test names are stable and deterministic. No dynamic values (timestamps, UUIDs, pod names) found. Tests use descriptive static strings that clearly indicate what they validate.
Microshift Test Compatibility ✅ Passed PR adds only standard Go unit tests (testing.T), not Ginkgo e2e tests. No Ginkgo framework imports found in codebase. Check not applicable.
Single Node Openshift (Sno) Test Compatibility ✅ Passed No new Ginkgo e2e tests were added that make multi-node or HA cluster assumptions. Existing e2e tests work with DaemonSets and StatefulSets that function correctly on Single Node OpenShift.
Topology-Aware Scheduling Compatibility ✅ Passed No topology-breaking constraints. 1-replica StatefulSet with no hardcoded anti-affinity, spread, control-plane selectors, or topology assumptions. All scheduling is user-configurable.
Ote Binary Stdout Contract ✅ Passed PR does not violate OTE stdout contract. Main uses textlogger (stderr-safe), no fmt.Print/log.Print in init/main, Ginkgo suite setup clean, all test files standard with no TestMain.
Ipv6 And Disconnected Network Test Compatibility ✅ Passed No Ginkgo e2e tests were added in this PR. The custom check only applies to new e2e tests, which are absent. Unit tests use mock fixtures and don't make external network calls.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 golangci-lint (2.12.1)

Error: can't load config: unsupported version of the configuration: "" See https://golangci-lint.run/docs/product/migration-guide for migration instructions
The command is terminated due to an error: can't load config: unsupported version of the configuration: "" See https://golangci-lint.run/docs/product/migration-guide for migration instructions

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


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

@openshift-ci openshift-ci Bot requested review from TrilokGeer and iamP1 April 29, 2026 10:41
@openshift-ci openshift-ci Bot added the approved Indicates a PR has been approved by an approver from all required OWNERS files. label Apr 29, 2026
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🧹 Nitpick comments (2)
pkg/controller/spire-server/configmap.go (1)

381-383: Centralize upstream CA path constants across config and pod wiring.

Line 382 hardcodes /run/spire/upstream-ca/ca.crt, while StatefulSet mount path is separately hardcoded. Keeping these in one shared constant reduces drift risk.

♻️ Suggested direction
 const (
 	defaultCaKeyType = "rsa-2048"
 	vaultTokenPath   = "/var/run/secrets/tokens/vault"
+	upstreamCAMountPath = "/run/spire/upstream-ca"
+	upstreamCACertPath  = upstreamCAMountPath + "/ca.crt"
 )
...
-		pluginData["ca_cert_path"] = "/run/spire/upstream-ca/ca.crt"
+		pluginData["ca_cert_path"] = upstreamCACertPath

Then reuse upstreamCAMountPath in pkg/controller/spire-server/statefulset.go mount configuration.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/controller/spire-server/configmap.go` around lines 381 - 383, Introduce a
shared constant (e.g., upstreamCAMountPath) and replace the hardcoded string in
the configmap generation (where pluginData["ca_cert_path"] is assigned when
v.CACertSecretRef != nil) with that constant; then update the StatefulSet mount
configuration in pkg/controller/spire-server/statefulset.go to reuse
upstreamCAMountPath for the mount path instead of its own hardcoded value so
both places reference the same identifier.
pkg/controller/spire-server/statefulset_test.go (1)

1112-1116: Also assert absence of unexpected volume mounts in negative-path tests.

These checks currently validate only volumes. Adding mount assertions will catch dangling mount regressions too.

✅ Suggested addition
 func TestGenerateStatefulSet_CertManager(t *testing.T) {
 ...
 	for _, vol := range sts.Spec.Template.Spec.Volumes {
 		if vol.Name == "vault-token" || vol.Name == "upstream-ca" {
 			t.Errorf("Unexpected volume %q for cert-manager upstream authority", vol.Name)
 		}
 	}
+	spireContainer := findContainerByName(sts.Spec.Template.Spec.Containers, "spire-server")
+	for _, m := range spireContainer.VolumeMounts {
+		if m.Name == "vault-token" || m.Name == "upstream-ca" {
+			t.Errorf("Unexpected volume mount %q for cert-manager upstream authority", m.Name)
+		}
+	}
 }
 
 func TestGenerateStatefulSet_NoUpstreamAuthority(t *testing.T) {
 ...
 	for _, vol := range sts.Spec.Template.Spec.Volumes {
 		if vol.Name == "vault-token" || vol.Name == "upstream-ca" {
 			t.Errorf("Unexpected volume %q when no upstream authority configured", vol.Name)
 		}
 	}
+	spireContainer := findContainerByName(sts.Spec.Template.Spec.Containers, "spire-server")
+	for _, m := range spireContainer.VolumeMounts {
+		if m.Name == "vault-token" || m.Name == "upstream-ca" {
+			t.Errorf("Unexpected volume mount %q when no upstream authority configured", m.Name)
+		}
+	}
 }

Also applies to: 1129-1133

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/controller/spire-server/statefulset_test.go` around lines 1112 - 1116,
The test currently only checks for absence of volumes named "vault-token" and
"upstream-ca" (iterating sts.Spec.Template.Spec.Volumes); extend the
negative-path assertions to also iterate through
sts.Spec.Template.Spec.Containers and sts.Spec.Template.Spec.InitContainers and
verify none of their VolumeMounts have Name == "vault-token" or "upstream-ca",
using the same t.Errorf message format (e.g., "Unexpected volumeMount %q for
cert-manager upstream authority") so dangling mounts are caught as well.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@api/v1alpha1/spire_server_config_types.go`:
- Around line 398-403: The IssuerGroup field currently allows an explicit empty
string which bypasses the default; add a non-empty validation (e.g.
+kubebuilder:validation:MinLength=1 or a non-empty pattern) to IssuerGroup so
the default is honored when the field is omitted but empty strings are rejected,
and apply the same non-empty validation to the other defaultable path/group
fields referenced at lines 413-417 and 441-445 so none of those can be set to an
explicit empty string.
- Around line 408-411: The VaultAddr field currently allows both http and https;
tighten the kubebuilder validation Pattern on VaultAddr in
spire_server_config_types.go (field name: VaultAddr, struct tag
json:"vaultAddr") to require only HTTPS (e.g., change Pattern to ^https://.+)
and update the comment to state "HTTPS URL" so the CRD enforces secure transport
for token/cert flows.

In `@pkg/controller/spire-server/rbac_test.go`:
- Around line 2404-2410: The test is appending the cert-manager PolicyRule
manually which validates test code instead of production logic; instead call the
actual reconciler/helper used in production (reconcileClusterRole) with a Server
object whose Spec.UpstreamAuthority.CertManager is set, obtain the
returned/created ClusterRole (rbacv1.ClusterRole) and assert that its Rules
include APIGroups "cert-manager.io" and Resources "certificaterequests" with the
expected Verbs, removing the manual cr.Rules append; use the same approach for
the other two similar cases so all assertions exercise reconcileClusterRole and
inspect the real ClusterRole rules.

In `@pkg/controller/spire-server/validation.go`:
- Around line 196-197: The runtime check in validation.go currently accepts both
"http://" and "https://" for v.VaultAddr (v.VaultAddr) — change it to require
only "https://" (reject any "http://") and update the error message to say
"vault.vaultAddr must start with https://, got %s"; ensure any
documentation/comments reference using insecureSkipVerify for bypassing TLS
certificate validation when explicitly desired, and mirror this stricter
HTTPS-only rule in the CRD/API schema/validation so admission-time validation
matches the runtime check.

In `@pkg/controller/zero-trust-workload-identity-manager/controller.go`:
- Line 195: Remove the kubebuilder RBAC marker that grants cert-manager
CertificateRequest access (the line containing
"+kubebuilder:rbac:groups=cert-manager.io,resources=certificaterequests,verbs=create;get;list;delete"
in pkg/controller/zero-trust-workload-identity-manager/controller.go), since
CertificateRequest RBAC belongs to the SPIRE server path (see
pkg/controller/spire-server/rbac.go); after removing this marker, regenerate the
RBAC manifests and CSV from markers so the operator bundle reflects the
tightened least-privilege permissions.

---

Nitpick comments:
In `@pkg/controller/spire-server/configmap.go`:
- Around line 381-383: Introduce a shared constant (e.g., upstreamCAMountPath)
and replace the hardcoded string in the configmap generation (where
pluginData["ca_cert_path"] is assigned when v.CACertSecretRef != nil) with that
constant; then update the StatefulSet mount configuration in
pkg/controller/spire-server/statefulset.go to reuse upstreamCAMountPath for the
mount path instead of its own hardcoded value so both places reference the same
identifier.

In `@pkg/controller/spire-server/statefulset_test.go`:
- Around line 1112-1116: The test currently only checks for absence of volumes
named "vault-token" and "upstream-ca" (iterating
sts.Spec.Template.Spec.Volumes); extend the negative-path assertions to also
iterate through sts.Spec.Template.Spec.Containers and
sts.Spec.Template.Spec.InitContainers and verify none of their VolumeMounts have
Name == "vault-token" or "upstream-ca", using the same t.Errorf message format
(e.g., "Unexpected volumeMount %q for cert-manager upstream authority") so
dangling mounts are caught as well.
🪄 Autofix (Beta)

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: Repository: openshift/coderabbit/.coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 8f538eb3-8afd-480a-bcc8-0a67fe28ed31

📥 Commits

Reviewing files that changed from the base of the PR and between e391158 and a679e5e.

⛔ Files ignored due to path filters (1)
  • api/v1alpha1/zz_generated.deepcopy.go is excluded by !**/zz_generated*
📒 Files selected for processing (15)
  • api/v1alpha1/spire_server_config_types.go
  • bundle/manifests/operator.openshift.io_spireservers.yaml
  • bundle/manifests/zero-trust-workload-identity-manager.clusterserviceversion.yaml
  • config/crd/bases/operator.openshift.io_spireservers.yaml
  • config/rbac/role.yaml
  • pkg/controller/spire-server/configmap.go
  • pkg/controller/spire-server/configmaps_test.go
  • pkg/controller/spire-server/controller.go
  • pkg/controller/spire-server/rbac.go
  • pkg/controller/spire-server/rbac_test.go
  • pkg/controller/spire-server/statefulset.go
  • pkg/controller/spire-server/statefulset_test.go
  • pkg/controller/spire-server/validation.go
  • pkg/controller/spire-server/validation_test.go
  • pkg/controller/zero-trust-workload-identity-manager/controller.go

Comment thread api/v1alpha1/spire_server_config_types.go
Comment thread api/v1alpha1/spire_server_config_types.go
Comment thread pkg/controller/spire-server/rbac_test.go Outdated
Comment thread pkg/controller/spire-server/validation.go Outdated
Comment thread pkg/controller/zero-trust-workload-identity-manager/controller.go
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
pkg/controller/spire-server/statefulset_test.go (1)

1096-1134: Also assert the mounts are absent in the negative cases.

Lines 1112-1133 only check volumes. If a later change adds a stray vault-token or upstream-ca mount without the matching volume, these tests still pass even though the Pod spec would be invalid.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/controller/spire-server/statefulset_test.go` around lines 1096 - 1134,
The tests TestGenerateStatefulSet_CertManager and
TestGenerateStatefulSet_NoUpstreamAuthority currently only assert volumes are
absent; also iterate the PodSpec containers (and initContainers if used) from
the StatefulSet returned by GenerateSpireServerStatefulSet and assert that no
VolumeMounts exist with Name "vault-token" or "upstream-ca"; add these checks in
both tests (scan sts.Spec.Template.Spec.Containers and
sts.Spec.Template.Spec.InitContainers and fail the test if any mount with those
names is found) so mounts and volumes are both verified absent.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@config/crd/bases/operator.openshift.io_spireservers.yaml`:
- Around line 1541-1545: The CRD schema for vaultAddr currently allows http;
update the pattern for the vaultAddr property from ^https?://.+ to require HTTPS
only (e.g., ^https://.+) so the CRD enforces secure transport, and mirror the
same restriction in the validation logic in
pkg/controller/spire-server/validation.go for the vaultAddr validation routine
to keep runtime checks consistent with the CRD.
- Around line 1220-1223: The CEL validation expression for the
x-kubernetes-validations rule that checks bundleEndpointProfile uses a
curly/typographic quote for the empty-string literal, breaking CEL; locate the
rule referencing self.bundleEndpointProfile == ''https_spiffe'' ?
has(self.endpointSpiffeId) && self.endpointSpiffeId != ” : true and replace the
typographic double-quote character with two single quotes (''), ensuring the
expression reads self.bundleEndpointProfile == ''https_spiffe'' ?
has(self.endpointSpiffeId) && self.endpointSpiffeId != '' : true; apply the same
fix in both CRD occurrences where this validation appears (the rule referencing
bundleEndpointProfile and endpointSpiffeId).

---

Nitpick comments:
In `@pkg/controller/spire-server/statefulset_test.go`:
- Around line 1096-1134: The tests TestGenerateStatefulSet_CertManager and
TestGenerateStatefulSet_NoUpstreamAuthority currently only assert volumes are
absent; also iterate the PodSpec containers (and initContainers if used) from
the StatefulSet returned by GenerateSpireServerStatefulSet and assert that no
VolumeMounts exist with Name "vault-token" or "upstream-ca"; add these checks in
both tests (scan sts.Spec.Template.Spec.Containers and
sts.Spec.Template.Spec.InitContainers and fail the test if any mount with those
names is found) so mounts and volumes are both verified absent.
🪄 Autofix (Beta)

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: Repository: openshift/coderabbit/.coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 78b1c609-465c-4afa-869d-7fb68e5de8c0

📥 Commits

Reviewing files that changed from the base of the PR and between a679e5e and e617a9e.

📒 Files selected for processing (6)
  • api/v1alpha1/spire_server_config_types.go
  • bundle/manifests/operator.openshift.io_spireservers.yaml
  • bundle/manifests/zero-trust-workload-identity-manager.clusterserviceversion.yaml
  • config/crd/bases/operator.openshift.io_spireservers.yaml
  • pkg/controller/spire-server/rbac_test.go
  • pkg/controller/spire-server/statefulset_test.go
🚧 Files skipped from review as they are similar to previous changes (2)
  • pkg/controller/spire-server/rbac_test.go
  • api/v1alpha1/spire_server_config_types.go

Comment thread config/crd/bases/operator.openshift.io_spireservers.yaml Outdated
Comment thread config/crd/bases/operator.openshift.io_spireservers.yaml
@anirudhAgniRedhat anirudhAgniRedhat force-pushed the upstream-authority-support branch from e617a9e to a4b0887 Compare April 29, 2026 11:26
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (2)
pkg/controller/spire-server/statefulset.go (1)

272-303: Avoid hardcoded token path components in two places.

The token mount path (/var/run/secrets/tokens) and token filename (vault) are currently duplicated assumptions. Reusing the same canonical token path source used by config generation would reduce drift risk.

♻️ Suggested refactor
 import (
 	"context"
 	"fmt"
+	"path"
@@
-		expirationSeconds := int64(600)
+		expirationSeconds := int64(600)
@@
 								ServiceAccountToken: &corev1.ServiceAccountTokenProjection{
 									Audience:          audience,
 									ExpirationSeconds: &expirationSeconds,
-									Path:              "vault",
+									Path:              path.Base(vaultTokenPath),
 								},
@@
 			corev1.VolumeMount{
 				Name:      "vault-token",
-				MountPath: "/var/run/secrets/tokens",
+				MountPath: path.Dir(vaultTokenPath),
 				ReadOnly:  true,
 			},
 		)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/controller/spire-server/statefulset.go` around lines 272 - 303, Remove
the duplicated hardcoded token path and filename by using a single shared
source/constant for both the ServiceAccountTokenProjection.Path and the
VolumeMount.MountPath; e.g., obtain tokenFileName and tokenMountPath from the
same config-generation helper (or declare a package-level constant used by both
locations) and replace the literal "vault" and "/var/run/secrets/tokens" in the
block that builds the StatefulSet (the Projected
ServiceAccountTokenProjection.Path and the corev1.VolumeMount.MountPath for the
"vault-token" volume) so both use the same canonical values.
pkg/controller/spire-server/configmaps_test.go (1)

1831-1898: Add a Vault-defaults test case for mount-point fallback paths.

Current Vault tests set explicit pkiMountPoint and k8sAuthMountPoint, so default branches ("pki" and "kubernetes") stay untested.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/controller/spire-server/configmaps_test.go` around lines 1831 - 1898, Add
a new test (based on TestGenerateServerConfMap_WithVaultUpstreamAuthority) that
leaves PKIMountPoint and K8sAuth.K8sAuthMountPoint unset on the
v1alpha1.UpstreamAuthorityVault and v1alpha1.VaultK8sAuthConfig, call
generateServerConfMap, and assert the resulting plugin_data uses the fallback
mount names "pki" for pki_mount_point and "kubernetes" for
k8s_auth.k8s_auth_mount_point (also verify token_path and other existing
defaults remain unchanged); reference
TestGenerateServerConfMap_WithVaultUpstreamAuthority, generateServerConfMap,
UpstreamAuthorityVault.PKIMountPoint and VaultK8sAuthConfig.K8sAuthMountPoint to
locate where to add this new test case.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@pkg/controller/spire-server/configmap.go`:
- Around line 326-329: The code calls
buildUpstreamAuthorityPlugin(config.UpstreamAuthority) without ensuring the
Vault config is non-nil, causing a panic when upstreamAuthority is empty and
CertManager == nil; update the rendering path that handles
config.UpstreamAuthority to defensively check for a non-nil Vault config (or
that CertManager != nil) before treating the authority as Vault, and modify
buildUpstreamAuthorityPlugin (or its caller) to return a safe empty plugin or
skip setting plugins["UpstreamAuthority"] when both CertManager and Vault are
nil; ensure references to CertManager and Vault inside
buildUpstreamAuthorityPlugin are nil-guarded so an empty upstreamAuthority: {}
does not dereference Vault (applies to the callers around the
config.UpstreamAuthority usage and the buildUpstreamAuthorityPlugin function).

In `@pkg/controller/spire-server/rbac_test.go`:
- Around line 2473-2481: The test currently compares rule.Verbs to expectedVerbs
by index, making it order-sensitive; change the assertion in rbac_test.go to be
order-insensitive by treating verbs as sets: build a map[string]bool (or sort
both slices) from rule.Verbs and verify every entry in expectedVerbs exists and
the lengths match, and replace the t.Errorf messages to report
missing/unexpected verbs rather than index mismatches (operate on rule.Verbs and
expectedVerbs variables and update the failing messages sent via t.Errorf).

---

Nitpick comments:
In `@pkg/controller/spire-server/configmaps_test.go`:
- Around line 1831-1898: Add a new test (based on
TestGenerateServerConfMap_WithVaultUpstreamAuthority) that leaves PKIMountPoint
and K8sAuth.K8sAuthMountPoint unset on the v1alpha1.UpstreamAuthorityVault and
v1alpha1.VaultK8sAuthConfig, call generateServerConfMap, and assert the
resulting plugin_data uses the fallback mount names "pki" for pki_mount_point
and "kubernetes" for k8s_auth.k8s_auth_mount_point (also verify token_path and
other existing defaults remain unchanged); reference
TestGenerateServerConfMap_WithVaultUpstreamAuthority, generateServerConfMap,
UpstreamAuthorityVault.PKIMountPoint and VaultK8sAuthConfig.K8sAuthMountPoint to
locate where to add this new test case.

In `@pkg/controller/spire-server/statefulset.go`:
- Around line 272-303: Remove the duplicated hardcoded token path and filename
by using a single shared source/constant for both the
ServiceAccountTokenProjection.Path and the VolumeMount.MountPath; e.g., obtain
tokenFileName and tokenMountPath from the same config-generation helper (or
declare a package-level constant used by both locations) and replace the literal
"vault" and "/var/run/secrets/tokens" in the block that builds the StatefulSet
(the Projected ServiceAccountTokenProjection.Path and the
corev1.VolumeMount.MountPath for the "vault-token" volume) so both use the same
canonical values.
🪄 Autofix (Beta)

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: Repository: openshift/coderabbit/.coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 48427ed6-d1ad-490c-9528-1c9f42cda44e

📥 Commits

Reviewing files that changed from the base of the PR and between e617a9e and 651ad32.

⛔ Files ignored due to path filters (1)
  • api/v1alpha1/zz_generated.deepcopy.go is excluded by !**/zz_generated*
📒 Files selected for processing (15)
  • api/v1alpha1/spire_server_config_types.go
  • bundle/manifests/operator.openshift.io_spireservers.yaml
  • bundle/manifests/zero-trust-workload-identity-manager.clusterserviceversion.yaml
  • config/crd/bases/operator.openshift.io_spireservers.yaml
  • config/rbac/role.yaml
  • pkg/controller/spire-server/configmap.go
  • pkg/controller/spire-server/configmaps_test.go
  • pkg/controller/spire-server/controller.go
  • pkg/controller/spire-server/rbac.go
  • pkg/controller/spire-server/rbac_test.go
  • pkg/controller/spire-server/statefulset.go
  • pkg/controller/spire-server/statefulset_test.go
  • pkg/controller/spire-server/validation.go
  • pkg/controller/spire-server/validation_test.go
  • pkg/controller/zero-trust-workload-identity-manager/controller.go
✅ Files skipped from review due to trivial changes (4)
  • pkg/controller/zero-trust-workload-identity-manager/controller.go
  • config/rbac/role.yaml
  • pkg/controller/spire-server/statefulset_test.go
  • config/crd/bases/operator.openshift.io_spireservers.yaml

Comment thread pkg/controller/spire-server/configmap.go
Comment thread pkg/controller/spire-server/rbac_test.go
@anirudhAgniRedhat
Copy link
Copy Markdown
Contributor Author

/cc @rausingh-rh @PillaiManish

- certificaterequests
verbs:
- create
- delete
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

do we need delete permission as well?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

yes, delete is needed. The SPIRE plugin performs optimistic cleanup of stale CertificateRequests after each CA rotation. Without delete permission, those cleanup calls would fail (the plugin logs the error and continues, so it's non-fatal -- but stale CertificateRequest objects would accumulate in the namespace indefinitely).
The current RBAC (create, get, list, delete) is correct and complete for the cert-manager plugin's needs. No change needed.

Comment thread pkg/controller/spire-server/configmap.go Outdated
return nil
}

func buildCertManagerPluginData(cm *v1alpha1.UpstreamAuthorityCertManager) map[string]interface{} {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

map[string]interface{}, Why map of interface is being used? Isn't the value always going to be string?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

It's consistent with every other builder function in the file (buildDataStorePluginData, buildVaultPluginData, generateFederationConfig, etc.) -- they all return map[string]interface{}. Changing just this one would create an inconsistency for no functional benefit.

Comment thread pkg/controller/spire-server/validation.go Outdated
Name: "upstream-ca",
VolumeSource: corev1.VolumeSource{
Secret: &corev1.SecretVolumeSource{
SecretName: v.CACertSecretRef.Name,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

can we check if v.CACertSecretRef secret exists in validation.go?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

We can do it, but we have not done this in other places in the operator controllers.
Also, we need to increase the cache size as we use the custom client based on the cache.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Marking this as resolved as the behavior is consistent across the operator.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

If required, it can be tracked as a revamp in follow-up PR: add Secret existence validation across all controllers, along with the cache expansion needed to support it.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

In the meanwhile - let's update the ADR in the validation section.

Comment thread config/rbac/role.yaml
- get
- list
- watch
- apiGroups:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The ADR specified namespace-scoped RBAC, is the clusterrole approach intentional?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yes, intentional. The spire-server SA already has a ClusterRole with cluster-wide permissions (tokenreviews, nodes, pods). Adding CertificateRequest access there follows the same pattern. SPIRE only ever targets the namespace in its config the broader scope doesn't change runtime behavior, just avoids the complexity of managing cross-namespace RBAC lifecycle.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Ack, the ClusterRole approach is simpler and consistent with the existing spire-server SA permission. Can you update the ADR to reflect this design decision? Post that, it can be marked as resolved.

Comment thread pkg/controller/spire-server/configmap.go Outdated
Comment thread pkg/controller/spire-server/statefulset.go Outdated
- Extract all paths, plugin names, and defaults into named constants
  shared between configmap.go and statefulset.go
- Use net/url.Parse for Vault URL validation instead of string prefix
- Add nil guard in buildUpstreamAuthorityPlugin to prevent panic when
  both CertManager and Vault are nil
- Add security note to vaultAddr field description for HTTP usage

Co-authored-by: Cursor <cursoragent@cursor.com>
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
pkg/controller/spire-server/statefulset.go (1)

298-305: ⚡ Quick win

Avoid positional container indexing for volume mounts.

Using Containers[0] couples correctness to container order. Resolve the spire-server container by name before appending mounts.

Proposed refactor
+func getContainerByName(containers []corev1.Container, name string) *corev1.Container {
+	for i := range containers {
+		if containers[i].Name == name {
+			return &containers[i]
+		}
+	}
+	return nil
+}
+
 func addUpstreamAuthorityToStatefulSet(sts *appsv1.StatefulSet, ua *v1alpha1.UpstreamAuthorityConfig) {
@@
+	spireServerContainer := getContainerByName(sts.Spec.Template.Spec.Containers, "spire-server")
+	if spireServerContainer == nil {
+		return
+	}
+
 	if v.K8sAuth != nil {
@@
-		sts.Spec.Template.Spec.Containers[0].VolumeMounts = append(
-			sts.Spec.Template.Spec.Containers[0].VolumeMounts,
+		spireServerContainer.VolumeMounts = append(
+			spireServerContainer.VolumeMounts,
 			corev1.VolumeMount{
 				Name:      "vault-token",
 				MountPath: vaultTokenMountDir,
 				ReadOnly:  true,
 			},
 		)
 	}
@@
-		sts.Spec.Template.Spec.Containers[0].VolumeMounts = append(
-			sts.Spec.Template.Spec.Containers[0].VolumeMounts,
+		spireServerContainer.VolumeMounts = append(
+			spireServerContainer.VolumeMounts,
 			corev1.VolumeMount{
 				Name:      "upstream-ca",
 				MountPath: upstreamCAMountPath,
 				ReadOnly:  true,
 			},
 		)
 	}
 }

Also applies to: 326-333

🤖 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 `@pkg/controller/spire-server/statefulset.go` around lines 298 - 305, The code
appends a VolumeMount using positional indexing into
sts.Spec.Template.Spec.Containers[0]; instead, find the container by name
("spire-server") in sts.Spec.Template.Spec.Containers and append the
corev1.VolumeMount{Name:"vault-token", MountPath: vaultTokenMountDir,
ReadOnly:true} to that container's VolumeMounts slice (modify the container in
place or update the slice entry). Apply the same pattern for the other similar
block (the second occurrence that currently uses Containers[0], lines around the
326-333 region) so both mounts are added to the spire-server container by name
rather than by index.
🤖 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.

Nitpick comments:
In `@pkg/controller/spire-server/statefulset.go`:
- Around line 298-305: The code appends a VolumeMount using positional indexing
into sts.Spec.Template.Spec.Containers[0]; instead, find the container by name
("spire-server") in sts.Spec.Template.Spec.Containers and append the
corev1.VolumeMount{Name:"vault-token", MountPath: vaultTokenMountDir,
ReadOnly:true} to that container's VolumeMounts slice (modify the container in
place or update the slice entry). Apply the same pattern for the other similar
block (the second occurrence that currently uses Containers[0], lines around the
326-333 region) so both mounts are added to the spire-server container by name
rather than by index.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository: openshift/coderabbit/.coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 64254691-57ef-4313-9ca5-980217c7051b

📥 Commits

Reviewing files that changed from the base of the PR and between ce7a265 and 94918a7.

📒 Files selected for processing (4)
  • pkg/controller/spire-server/configmap.go
  • pkg/controller/spire-server/statefulset.go
  • pkg/controller/spire-server/validation.go
  • pkg/controller/spire-server/validation_test.go
🚧 Files skipped from review as they are similar to previous changes (2)
  • pkg/controller/spire-server/validation.go
  • pkg/controller/spire-server/validation_test.go

@openshift-ci
Copy link
Copy Markdown

openshift-ci Bot commented May 8, 2026

@anirudhAgniRedhat: all tests passed!

Full PR test history. Your PR dashboard.

Details

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository. I understand the commands that are listed here.

@rausingh-rh
Copy link
Copy Markdown
Contributor

/lgtm
/hold
please unhold when ready to merge

@openshift-ci openshift-ci Bot added the do-not-merge/hold Indicates that a PR should not merge because someone has issued a /hold command. label May 8, 2026
@openshift-ci openshift-ci Bot added the lgtm Indicates that a PR is ready to be merged. label May 8, 2026
@TrilokGeer
Copy link
Copy Markdown
Contributor

/lgtm
/approve

@openshift-ci
Copy link
Copy Markdown

openshift-ci Bot commented May 21, 2026

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: anirudhAgniRedhat, TrilokGeer

The full list of commands accepted by this bot can be found here.

The pull request process is described here

Details Needs approval from an approver in each of these files:
  • OWNERS [TrilokGeer,anirudhAgniRedhat]

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

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

Labels

approved Indicates a PR has been approved by an approver from all required OWNERS files. do-not-merge/hold Indicates that a PR should not merge because someone has issued a /hold command. jira/valid-reference Indicates that this PR references a valid Jira ticket of any type. lgtm Indicates that a PR is ready to be merged.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants