Skip to content

feat: add KMS signer support for Fulcio (SECURESIGN-4462) - #2184

Draft
kdacosta0 wants to merge 6 commits into
mainfrom
fulcio-kms-conversion-wip
Draft

feat: add KMS signer support for Fulcio (SECURESIGN-4462)#2184
kdacosta0 wants to merge 6 commits into
mainfrom
fulcio-kms-conversion-wip

Conversation

@kdacosta0

@kdacosta0 kdacosta0 commented Aug 5, 2026

Copy link
Copy Markdown
Member

What changed

Adds KMS signer support for Fulcio — operators can now configure Fulcio to use a remote KMS key (AWS, GCP, Azure, HashiCorp Vault) instead of a local file-based ECDSA key.

API

  • FulcioSigner.Kms field (type: kms + kms.keyResource URI)
  • 4 XValidation rules enforcing mutual exclusion between file and kms
  • certificateChainRef required for KMS (Fulcio server hard-requires --kms-cert-chain-path)

Controller

  • resolve_kms_signer action — resolves cert chain secret, sets CertCondition
  • generate_signer skipped for KMS (IsEnabled guard: type == file || type == "")
  • Deployment: --ca=kmsca + --kms-resource + --kms-cert-chain-path args, cert volume with CA only (no private key)
  • FIPS validation skips private key check for KMS

Conversion (v1 ↔ v1alpha1)

  • Mutual exclusion guard: KMS branch nils File, file branch nils Kms
  • Auth restored unconditionally (orthogonal to signer type)
  • Fuzzer constrained to valid states (file XOR kms, auth on both)

Why

SECURESIGN-4462 — customers need remote key management for Fulcio CA signing keys.

Tested

  • Unit tests: conversion roundtrip fuzz, deployment args, resolve_kms_signer, generate_signer guard, XValidation CEL
  • Cluster: 12 manual tests (XValidation rejection, KMS reconcile, conversion roundtrip read+write, type switching, file signer regression)
  • AWS KMS: cosign sign with real RSA_2048 key (eu-north-1) on OpenShift — tlog entry created with index: 0

Stack

Depends on: #2181 (Auth support)
Parallel with: #2182 (PKCS#11) — no code dependency, either can merge first.

sampras343 and others added 5 commits August 5, 2026 13:40
…ervedGeneration

Two bugs in the Trillian logsigner controller actions:

1. logsigner/service.go and logsigner/monitoring.go use
   actions.ServerCondition ("LogServerAvailable") instead of
   actions.SignerCondition ("LogSignerAvailable"). When the logsigner
   service is created, it overwrites the logserver's condition to
   "Creating", causing LogServerAvailable to oscillate between
   Creating and Ready on every reconcile cycle.

2. All four deployment/service actions in logserver and logsigner
   set conditions without ObservedGeneration, which is inconsistent
   with the rollout check actions that do set it.

The combined effect prevents Trillian from reliably reaching Ready
state, which blocks the entire SecureSign dependency chain
(CTLog → TUF → SecureSign).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add signer-agnostic pod customization fields to FulcioSpec and CTlogSpec,
enabling users to inject init containers, volumes, and volume mounts
regardless of which signer backend is active.

API changes:
- InitContainerSpec in common.go — curated corev1.Container subset
- InitContainers, Volumes, VolumeMounts on FulcioSpec and CTlogSpec

Controller changes:
- User-defined resources applied in shared deployment path before signer
  type branching
- CTLog operator-managed "keys" volume set after user volumes so operator
  always wins on reserved names
- Shared helpers: HasVolume, EnsureVolumeDefaultMode, ReconcileInitContainers
- Fulcio ensureCommonDeployment extracted for shared scaffolding

Housekeeping:
- Remove dead HasMountPath function
- v1alpha1 conversion preserves new fields via MarshalData annotations
- Roundtrip fuzzer coverage for new fields

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…tion

Move user pod-resource reconciliation (InitContainers, Volumes,
VolumeMounts) from inside ensureCommonDeployment/ensureDeployment
into a standalone deployment.PodResources() ensure function in
the CreateOrUpdate chain.

This makes the user pod-resource logic signer-agnostic and
composable — the same PodResources ensure function can be reused
by Rekor, TSA, and TUF without reimplementation per component.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Sachin Sampras M <sampras343@gmail.com>
Add Auth field on FulcioSigner and CTlogSigner for injecting
authentication credentials (env vars and secret mounts) into the
main server container, regardless of signer type.

- Auth *Auth on FulcioSigner and CTlogSigner
- ContainerAuth called unconditionally — cleans up stale auth
  volumes/mounts when auth is removed
- Auth volume renamed to "signer-auth" with legacy "auth" volume
  cleanup on every reconcile for seamless upgrade
- EnsureVolumeDefaultMode prevents infinite reconciliation loop
- SecureSign.SetDefaults() calls Signer.SetDefaults() for explicit
  type defaulting

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add Auth field on FulcioSigner and CTlogSigner for injecting
authentication credentials (env vars and secret mounts) into the
main server container, regardless of signer type.

- Auth *Auth on FulcioSigner and CTlogSigner
- ContainerAuth called unconditionally — cleans up stale auth
  volumes/mounts when auth is removed
- Auth volume renamed to "signer-auth" with legacy "auth" volume
  cleanup on every reconcile for seamless upgrade
- EnsureVolumeDefaultMode prevents infinite reconciliation loop
- SecureSign.SetDefaults() calls Signer.SetDefaults() for explicit
  type defaulting

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@qodo-for-securesign

Copy link
Copy Markdown

PR Summary by Qodo

Add KMS-backed Fulcio signer support (plus pod customization + auth fixes)

✨ Enhancement 🐞 Bug fix 🧪 Tests ⚙️ Configuration changes 🕐 40+ Minutes

Grey Divider

AI Description

• Add Fulcio KMS signer mode with strict CRD validation and deployment wiring.
• Support user initContainers/volumes/mounts and signer auth injection for Fulcio/CTlog.
• Fix Trillian logsigner condition handling and expand unit/fuzz coverage for
 conversions/controllers.
Diagram

graph TD
  CR["Fulcio & Securesign CRs"] --> VAL["CRD XValidation"] --> CTRL["Fulcio controller"] --> MODE{"Signer type?"} --> FILE["File signer"] --> DEP["Deployment builder"] --> POD["Fulcio server pod"]
  MODE --> KMS["KMS signer"] --> DEP
  FILE --> SFILE[("Key+CA secrets")]
  KMS --> SKMS[("CA chain secret")]
  DEP --> ENS["ensure helpers"]
  POD --> EKMS["External KMS"]
  subgraph Legend
    direction LR
    _proc["Component"] ~~~ _dec{"Decision"} ~~~ _sec[("K8s Secret")]
  end
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Schema union via oneOf (no explicit type field)
  • ➕ CRD schema can enforce mutual exclusivity structurally (stronger than CEL)
  • ➕ Reduces ambiguous states like empty type + kms/file objects
  • ➖ Harder to evolve without breaking existing clients
  • ➖ Conversion/defaulting may become more complex across versions
2. Separate subresources for signer backends (FulcioFileSigner / FulcioKMSSigner)
  • ➕ Clear separation of concerns; backend-specific status/conditions
  • ➕ Cleaner reconciliation: each controller/action targets one backend
  • ➖ More CRDs and operational overhead for users
  • ➖ Non-trivial migration path from existing Fulcio CRs
3. Implicit mode selection (presence-based) instead of type discriminator
  • ➕ Fewer fields for users to set (e.g., kms present => KMS mode)
  • ➕ Avoids needing to keep Type, File, and Kms in sync
  • ➖ Ambiguity when both branches are present; needs strict validation anyway
  • ➖ Harder to keep backward-compatible default behavior ('file' when unset)

Recommendation: Keep the current discriminator-based approach (type=file|kms) with explicit CEL mutual-exclusion and targeted conversion guards. It preserves backward compatibility (defaulting to file when type is empty), keeps v1alpha1 compatibility via MarshalData/UnmarshalData, and makes reconcile branching straightforward while still supporting strict validation.

Files changed (35) +15063 / -1756

Enhancement (9) +482 / -126
common.goAdd InitContainerSpec for curated init container configuration +36/-0

Add InitContainerSpec for curated init container configuration

• Introduces InitContainerSpec as a controlled subset of corev1.Container for user-provided init containers. This enables vendor-specific initialization without exposing the full pod spec surface area.

api/v1/common.go

ctlog_types.goExtend CTlogSpec with pod customization fields and signer Auth +13/-0

Extend CTlogSpec with pod customization fields and signer Auth

• Adds InitContainers, Volumes, and VolumeMounts to CTlogSpec for deployment customization. Extends CTlogSigner with an Auth block to support env/secret-based credentials injection.

api/v1/ctlog_types.go

fulcio_types.goAdd KMS Fulcio signer API and pod customization fields +22/-1

Add KMS Fulcio signer API and pod customization fields

• Adds signer type 'kms', a Kms config block (keyResource), and Auth on FulcioSigner. Introduces CEL XValidation rules to enforce file↔kms mutual exclusion and require certificateChainRef for KMS; also adds InitContainers/Volumes/VolumeMounts to FulcioSpec.

api/v1/fulcio_types.go

securesign_defaults.goDefault nested signer specs for Securesign umbrella CR +2/-1

Default nested signer specs for Securesign umbrella CR

• Calls SetDefaults on Fulcio and CTlog signer specs at the aggregate Securesign level. This ensures nested defaulting is applied consistently before reconcile logic relies on it.

api/v1/securesign_defaults.go

deployment.goReconcile user pod resources and signer auth for CTlog deployment +21/-6

Reconcile user pod resources and signer auth for CTlog deployment

• Applies user-defined initContainers/volumes/mounts to CTlog pods, injects signer auth env/secret mounts, and ensures operator-managed volumes win on name conflicts. Also sets full VolumeSource and DefaultMode to avoid reconcile drift.

internal/controller/ctlog/actions/deployment.go

deployment.goAdd KMS deployment path and refactor shared Fulcio deployment setup +183/-118

Add KMS deployment path and refactor shared Fulcio deployment setup

• Refactors common deployment scaffolding into ensureCommonDeployment (ports, probes, SA, user pod resources). Adds KMS signer branch that sets --ca=kmsca, --kms-resource, mounts only the CA chain, and requires --kms-cert-chain-path; file branch retains key+cert projected secret behavior.

internal/controller/fulcio/actions/deployment.go

resolve_kms_signer.goAdd resolveKMSSigner action to set cert status from chain secret +112/-0

Add resolveKMSSigner action to set cert status from chain secret

• Introduces a new reconcile action that (for type=kms) requires the certificate chain secret, labels it as the Fulcio CA, sets status.certificate.CARef, and updates CertCondition with ObservedGeneration handling.

internal/controller/fulcio/actions/resolve_kms_signer.go

fulcio_controller.goWire resolveKMSSigner action into Fulcio reconcile pipeline +1/-0

Wire resolveKMSSigner action into Fulcio reconcile pipeline

• Adds the new ResolveKMSSignerAction into the Fulcio controller action list after signer generation/FIPS validation, enabling KMS mode reconciliation to populate status and proceed.

internal/controller/fulcio/fulcio_controller.go

pod_spec.goAdd shared reconciliation helpers for user pod resources +92/-0

Add shared reconciliation helpers for user pod resources

• Adds HasVolume, EnsureVolumeDefaultMode (to prevent infinite reconcile loops due to defaulting), and shared ReconcileUserPodResources/ReconcileInitContainers helpers used by Fulcio and CTlog deployment actions.

internal/utils/kubernetes/ensure/pod_spec.go

Bug fix (11) +99 / -52
ctlog_conversion.goPreserve CTlog pod customization and signer Auth on ConvertTo +4/-0

Preserve CTlog pod customization and signer Auth on ConvertTo

• Restores v1-only InitContainers/Volumes/VolumeMounts and signer Auth when converting v1alpha1 CTlog back to v1. This aligns conversion behavior with MarshalData/UnmarshalData preservation expectations.

api/v1alpha1/ctlog_conversion.go

fulcio_conversion.goAdd KMS/file mutual exclusion guard and preserve Auth + pod fields +15/-5

Add KMS/file mutual exclusion guard and preserve Auth + pod fields

• Updates ConvertTo logic to explicitly nil the opposite signer branch based on Type (kms clears File; file clears Kms) and continues preserving empty File struct semantics when applicable. Restores signer Auth and v1-only pod customization fields during conversion.

api/v1alpha1/fulcio_conversion.go

securesign_conversion.goPreserve Fulcio KMS and CTlog Auth/pod extension fields on ConvertTo +19/-5

Preserve Fulcio KMS and CTlog Auth/pod extension fields on ConvertTo

• Extends Securesign conversion to apply the same Fulcio signer mutual exclusion rules and restore Auth and pod customization fields for Fulcio and CTlog when converting back to v1.

api/v1alpha1/securesign_conversion.go

fips_validation.goSkip private key validation in FIPS checks for KMS signer +9/-7

Skip private key validation in FIPS checks for KMS signer

• Adjusts FIPS crypto material collection to only validate the file private key when not using KMS. Certificate chain validation remains required in all cases.

internal/controller/fulcio/actions/fips_validation.go

generate_signer.goDisable signer secret generation for KMS signer type +3/-0

Disable signer secret generation for KMS signer type

• Adds an IsEnabled guard so GenerateSigner runs only for file signers (or default empty type). This prevents generating/expecting private key material when using remote KMS.

internal/controller/fulcio/actions/generate_signer.go

deployment.goSet ObservedGeneration when updating Trillian logserver conditions +5/-4

Set ObservedGeneration when updating Trillian logserver conditions

• Ensures the Creating condition set on deployment creation includes ObservedGeneration for consistent rollout/ready detection.

internal/controller/trillian/actions/logserver/deployment.go

service.goSet ObservedGeneration when updating Trillian logserver service condition +5/-4

Set ObservedGeneration when updating Trillian logserver service condition

• Adds ObservedGeneration to the Creating condition when the service is created, preventing stale condition evaluation across generations.

internal/controller/trillian/actions/logserver/service.go

deployment.goSet ObservedGeneration when updating Trillian logsigner conditions +5/-4

Set ObservedGeneration when updating Trillian logsigner conditions

• Adds ObservedGeneration to logsigner deployment Creating condition updates to align with rollout checks.

internal/controller/trillian/actions/logsigner/deployment.go

monitoring.goFix logsigner monitoring to use LogSigner condition type +1/-1

Fix logsigner monitoring to use LogSigner condition type

• Corrects monitoring action wiring to use SignerCondition instead of ServerCondition, preventing logsigner from clobbering logserver condition state.

internal/controller/trillian/actions/logsigner/monitoring.go

service.goFix logsigner service condition type and add ObservedGeneration +5/-4

Fix logsigner service condition type and add ObservedGeneration

• Stops overwriting logserver availability by switching to SignerCondition and adds ObservedGeneration when setting Creating condition during service creation.

internal/controller/trillian/actions/logsigner/service.go

auth.goHarden auth injection: rename volume and clean up legacy mounts +28/-18

Harden auth injection: rename volume and clean up legacy mounts

• Renames the auth projected volume to signer-auth, removes legacy 'auth' volumes/mounts, and makes ContainerAuth idempotent including cleanup when auth is nil/empty. Ensures DefaultMode is set to avoid reconcile drift.

internal/utils/kubernetes/ensure/auth.go

Tests (10) +1870 / -3
fulcio_types_test.goAdd extensive API validation tests for KMS signer +310/-0

Add extensive API validation tests for KMS signer

• Adds positive/negative tests for KMS signer configuration (required fields, URI validation, mutual exclusion) and Auth combinations. Includes update-path tests switching between file and KMS signers and helper generateKMSSigner().

api/v1/fulcio_types_test.go

securesign_types_test.goValidate Securesign accepts Fulcio KMS signer configuration +35/-0

Validate Securesign accepts Fulcio KMS signer configuration

• Adds tests ensuring a Securesign aggregate CR can include a KMS Fulcio signer and that missing certificateChainRef is rejected by validation.

api/v1/securesign_types_test.go

conversion_roundtrip_test.goConstrain fuzzers for new Auth/pod resource and KMS signer states +151/-0

Constrain fuzzers for new Auth/pod resource and KMS signer states

• Adds fuzzer functions that generate conversion-roundtrip-safe values for v1-only pod extension fields and signer Auth. Ensures FulcioSigner fuzzing respects file XOR kms while allowing Auth on both, and extends CTlog fuzzing similarly.

api/v1alpha1/conversion_roundtrip_test.go

conversion_unit_test.goAdd Fulcio KMS conversion unit test (Kms + Auth preserved) +44/-0

Add Fulcio KMS conversion unit test (Kms + Auth preserved)

• Adds a focused unit test verifying Fulcio KMS signer configuration (including Auth env/secretMount) survives v1↔v1alpha1 roundtrip without losing fields.

api/v1alpha1/conversion_unit_test.go

deployment_test.goAdd CTlog deployment tests for pod resources and auth precedence +256/-0

Add CTlog deployment tests for pod resources and auth precedence

• Adds unit tests verifying user volumes/mounts and initContainers are applied, signer auth injection works, and operator-managed volumes take precedence over user volumes with the same name.

internal/controller/ctlog/actions/deployment_test.go

fulcio_deployment_test.goAdd Fulcio deployment tests for KMS args, auth, and volume behavior +343/-3

Add Fulcio deployment tests for KMS args, auth, and volume behavior

• Adds tests asserting KMS-specific args and mounts, absence of file-only PASSWORD/key settings, projected auth volume creation, and that the KMS cert volume contains only the CA chain secret.

internal/controller/fulcio/actions/fulcio_deployment_test.go

generate_signer_test.goTest GenerateSigner disabled for KMS and FIPS behavior in KMS mode +70/-0

Test GenerateSigner disabled for KMS and FIPS behavior in KMS mode

• Adds a CanHandle test proving GenerateSigner is disabled when type=kms. Adds a FIPS validation test verifying KMS mode can pass without validating a private key secret.

internal/controller/fulcio/actions/generate_signer_test.go

resolve_kms_signer_test.goAdd unit tests for resolveKMSSigner action and condition gating +218/-0

Add unit tests for resolveKMSSigner action and condition gating

• Covers happy path, missing secret error/conditions, generation bump behavior, Ready-condition gating, and CA label application to the chain secret.

internal/controller/fulcio/actions/resolve_kms_signer_test.go

auth_test.goAdd unit tests for ContainerAuth behavior and legacy cleanup +222/-0

Add unit tests for ContainerAuth behavior and legacy cleanup

• Adds test coverage for nil/empty auth, env-only, secret-only, combined auth, idempotency, legacy volume cleanup, and Auth() by container name.

internal/utils/kubernetes/ensure/auth_test.go

pod_spec_test.goAdd tests for volume defaulting and init container reconciliation +221/-0

Add tests for volume defaulting and init container reconciliation

• Adds unit tests for HasVolume, EnsureVolumeDefaultMode across supported volume sources, and ReconcileInitContainers behavior (add/update/remove and volume mount preservation).

internal/utils/kubernetes/ensure/pod_spec_test.go

Other (5) +12612 / -1575
zz_generated.deepcopy.goRegenerate deepcopy for new API fields (KMS/Auth/pod resources) +113/-0

Regenerate deepcopy for new API fields (KMS/Auth/pod resources)

• Updates autogenerated DeepCopy logic for new fields on Fulcio/CTlog specs and signers, and adds DeepCopy for InitContainerSpec.

api/v1/zz_generated.deepcopy.go

zz_generated.conversion.goAnnotate manual conversion requirements for new v1-only fields +6/-0

Annotate manual conversion requirements for new v1-only fields

• Updates autogenerated conversion warnings to reflect new v1-only InitContainers/Volumes/VolumeMounts fields that must be handled manually during v1↔v1alpha1 conversion.

api/v1alpha1/zz_generated.conversion.go

rhtas.redhat.com_ctlogs.yamlRegenerate CTlog CRD schema (pod customization/Auth additions) +2712/-0

Regenerate CTlog CRD schema (pod customization/Auth additions)

• Updates the generated CTlog CRD to include new spec fields introduced in the API (initContainers, volumes, volumeMounts, and signer auth).

config/crd/bases/rhtas.redhat.com_ctlogs.yaml

rhtas.redhat.com_fulcios.yamlAdd Fulcio KMS signer schema + CEL validations to CRD +2736/-0

Add Fulcio KMS signer schema + CEL validations to CRD

• Extends the Fulcio CRD with kms.keyResource and validation for acceptable URI schemes. Adds XValidation rules to require kms and certificateChainRef for type=kms and enforce file↔kms mutual exclusion.

config/crd/bases/rhtas.redhat.com_fulcios.yaml

rhtas.redhat.com_securesigns.yamlPropagate Fulcio KMS signer schema to Securesign aggregate CRD +7045/-1575

Propagate Fulcio KMS signer schema to Securesign aggregate CRD

• Mirrors Fulcio signer CRD changes into the embedded Fulcio spec inside the Securesign CRD, including kms schema and mutual exclusion validations.

config/crd/bases/rhtas.redhat.com_securesigns.yaml

v1 API types (KMS, Auth structs), v1alpha1 conversion with mutual
exclusion guard, generate_signer skip for KMS, resolve_kms_signer
action, deployment args for kmsca, FIPS validation for KMS.

Cluster-tested: conversion roundtrip verified on OpenShift.
Not yet PR-ready — needs cleanup and mutual exclusion cluster test.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@kdacosta0
kdacosta0 force-pushed the fulcio-kms-conversion-wip branch from 0d4f8a6 to fc9e208 Compare August 5, 2026 13:59
@qodo-for-securesign

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (6) 📘 Rule violations (0) 📜 Skill insights (0)

Grey Divider


Action required

1. Fulcio-cert source not reset 🐞 Bug ≡ Correctness
Description
Fulcio deployment reconciliation applies user-defined Volumes first, then mutates only
cert.Projected for the operator-managed "fulcio-cert" volume without resetting VolumeSource. If a
user supplies a "fulcio-cert" volume with another source (e.g., EmptyDir), this can produce an
invalid volume spec and block Deployment creation.
Code

internal/controller/fulcio/actions/deployment.go[R240-243]

+			cert := kubernetes.FindVolumeByNameOrCreate(&template.Spec, "fulcio-cert")
+			if cert.Projected == nil {
+				cert.Projected = &core.ProjectedVolumeSource{}
+			}
Relevance

●●● Strong

Resetting VolumeSource for operator-owned volume avoids invalid mixed sources when user overrides
same name.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
User pod resources are reconciled before operator-managed volumes, and ReconcileUserPodResources
overwrites VolumeSource by name. Fulcio later sets only cert.Projected instead of replacing
VolumeSource, unlike fulcio-config/oidc-info where VolumeSource is replaced wholesale.

internal/controller/fulcio/actions/deployment.go[115-149]
internal/controller/fulcio/actions/deployment.go[230-260]
internal/controller/fulcio/actions/deployment.go[337-350]
internal/utils/kubernetes/ensure/pod_spec.go[107-115]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`fulcio-cert` is operator-managed but user volumes are reconciled first; later code only initializes/edits `cert.Projected` and `cert.Projected.Sources` without clearing any pre-existing `VolumeSource` fields.

### Issue Context
`ReconcileUserPodResources` overwrites `VolumeSource` for user-supplied volumes by name, so a user can unintentionally (or intentionally) provide a conflicting source under the reserved name `fulcio-cert`.

### Fix Focus Areas
- internal/controller/fulcio/actions/deployment.go[146-149]
- internal/controller/fulcio/actions/deployment.go[230-260]
- internal/controller/fulcio/actions/deployment.go[284-321]
- internal/utils/kubernetes/ensure/pod_spec.go[107-115]

### Suggested fix
- In both KMS and file signer branches, replace:
 - `if cert.Projected == nil { cert.Projected = ... }` and direct `cert.Projected.Sources = ...`
- With:
 - `cert.VolumeSource = core.VolumeSource{Projected: &core.ProjectedVolumeSource{Sources: ...}}`
- Then call `ensure.EnsureVolumeDefaultMode(cert)`.
- Add/extend a test similar to CTlog’s operator precedence test to cover `fulcio-cert` collisions.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

2. Auth env vars persist 🐞 Bug ⛨ Security
Description
When auth is removed or env entries are deleted, ensure.ContainerAuth removes auth volumes/mounts
but never removes previously injected environment variables from the container. This leaves stale
auth configuration/credential references in the pod template after CR updates.
Code

internal/utils/kubernetes/ensure/auth.go[R28-31]

+		if auth == nil || (len(auth.Env) == 0 && len(auth.SecretMount) == 0) {
+			kubernetes.RemoveVolumeByName(templateSpec, authVolumeName)
+			kubernetes.RemoveVolumeMountByName(container, authVolumeName)
+			return nil
Relevance

●●● Strong

Stale auth env vars after CR update is a real correctness/security footgun; cleanup logic is
straightforward.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The early-return path only removes volumes/mounts and exits, and the non-nil path only
Find-or-Creates env vars by name without any deletion logic.

internal/utils/kubernetes/ensure/auth.go[25-39]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`ensure.ContainerAuth` upserts env vars from `auth.Env` but does not remove env vars that were previously injected when `auth` is removed or when the desired env list shrinks.

### Issue Context
This causes stale credential references to remain in the Deployment/Pod template even after users remove them from the CR.

### Fix Focus Areas
- internal/utils/kubernetes/ensure/auth.go[25-39]
- internal/utils/kubernetes/deployment.go[217-235]

### Suggested fix
- Implement env var cleanup for auth-managed env vars:
 - Compute `desired := set(auth.Env[].Name)`.
 - Remove env vars previously managed by auth but not in `desired`.
- To avoid deleting env vars managed by other operator logic, track auth-managed env var names (e.g., in a pod-template annotation) and use that list for cleanup on subsequent reconciles.
- Add a unit test that:
 - Applies auth with env vars A,B;
 - Updates auth to only A (or nil);
 - Verifies B is removed.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Init pull policy clobbered 🐞 Bug ⚙ Maintainability
Description
ReconcileInitContainers unconditionally assigns ImagePullPolicy from InitContainerSpec, even when
it’s omitted and empty, contradicting the stated intent to preserve Kubernetes-defaulted fields.
This can cause reconcile drift and unnecessary updates.
Code

internal/utils/kubernetes/ensure/pod_spec.go[R142-145]

+			c.Resources = core.ResourceRequirements{}
+		}
+		c.ImagePullPolicy = spec.ImagePullPolicy
+		c.Env = append([]core.EnvVar{}, spec.Env...)
Relevance

●●● Strong

Only setting ImagePullPolicy when explicitly provided matches “preserve defaults” intent; small
deterministic fix.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The function comment says it preserves defaulted fields, but the implementation assigns
ImagePullPolicy unconditionally from the (optional) spec field.

internal/utils/kubernetes/ensure/pod_spec.go[125-147]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`ReconcileInitContainers` claims to preserve defaulted fields, but it overwrites `ImagePullPolicy` even when the CR did not specify it (zero value).

### Issue Context
When the API server defaults `ImagePullPolicy`, writing an empty value during reconciliation can cause a constant desired/observed mismatch or churn.

### Fix Focus Areas
- internal/utils/kubernetes/ensure/pod_spec.go[125-147]

### Suggested fix
- Only assign `c.ImagePullPolicy = spec.ImagePullPolicy` when `spec.ImagePullPolicy != ""`.
- Otherwise, leave the existing value in place.
- Add a unit test ensuring an existing non-empty ImagePullPolicy is preserved when InitContainerSpec omits it.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


4. KMS deref without guard 🐞 Bug ☼ Reliability
Description
Fulcio deployment’s KMS branch dereferences instance.Spec.Signer.Kms.KeyResource without checking
Kms for nil. While CRD validation requires kms when type=="kms", a malformed object bypassing
validation would panic the reconciler during deployment rendering.
Code

internal/controller/fulcio/actions/deployment.go[R231-234]

+			args = append(args,
+				"--ca=kmsca",
+				fmt.Sprintf("--kms-resource=%s", instance.Spec.Signer.Kms.KeyResource),
+			)
Relevance

●●● Strong

Defensive nil-guard prevents reconciler panic on malformed CRs; low-risk reliability improvement.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The KMS path is selected only by Signer.Type and then immediately dereferences Signer.Kms,
despite Kms being a pointer field; validation exists but is not a runtime invariant.

internal/controller/fulcio/actions/deployment.go[191-235]
api/v1/fulcio_types.go[53-76]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
The controller assumes `spec.signer.kms` is non-nil when `spec.signer.type == 'kms'` and dereferences it.

### Issue Context
Admission validation reduces likelihood, but controllers should still be robust against malformed objects (e.g., older clusters, partially-upgraded CRDs, manual edits, tests).

### Fix Focus Areas
- internal/controller/fulcio/actions/deployment.go[191-235]
- api/v1/fulcio_types.go[53-76]

### Suggested fix
- In the KMS branch, add a defensive check:
 - if `instance.Spec.Signer.Kms == nil` (or `KeyResource == ""`), return a clear error and/or set a failure condition.
- Consider similar guards for `certificateChainRef` before dereferencing in KMS-related logic.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View more (2)
5. Auth volume has no sources 🐞 Bug ☼ Reliability
Description
ensure.ContainerAuth always creates the "signer-auth" projected volume even when Auth.SecretMount is
empty, leaving ProjectedVolumeSource.Sources empty. This adds an unnecessary mount and can lead to
validation/runtime failures for env-only auth usage paths (e.g., Trillian DB auth uses env-only
secrets).
Code

internal/utils/kubernetes/ensure/auth.go[R41-44]

+		authProjected := kubernetes.FindVolumeByNameOrCreate(templateSpec, authVolumeName)
+		authProjected.VolumeSource = core.VolumeSource{
+			Projected: &core.ProjectedVolumeSource{},
+		}
Relevance

●● Moderate

Empty Projected sources for env-only auth might be intended/validated elsewhere; change could be
behavior-altering.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
ContainerAuth overwrites the volume to an empty ProjectedVolumeSource and only appends projections
inside the SecretMount loop, so env-only auth yields an empty projection list. Trillian’s
dbSecretToAuth constructs Auth with only Env (no SecretMount), exercising this path in real
reconciles.

internal/utils/kubernetes/ensure/auth.go[23-53]
internal/controller/trillian/utils/db_support.go[45-66]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`ensure.ContainerAuth` creates a `Projected` volume (`signer-auth`) even when `auth.SecretMount` is empty, resulting in a projected volume with no `sources`.

### Issue Context
Env-only auth is a supported/common path (e.g., Trillian DB secret auth builds only `Auth.Env`). A projected volume with no projections is unnecessary at best and may be rejected or behave unexpectedly.

### Fix Focus Areas
- internal/utils/kubernetes/ensure/auth.go[28-53]
- internal/controller/trillian/utils/db_support.go[45-66]

### Suggested fix
- Change `ContainerAuth` to:
 - Always reconcile `auth.Env`.
 - Only create/mount the `signer-auth` projected volume when `len(auth.SecretMount) > 0`.
 - If `len(auth.SecretMount) == 0`, remove `signer-auth` volume and mount (and do not create it).
- Update/adjust unit tests that currently expect the volume for env-only auth.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


6. Stale user volumes linger 🐞 Bug ⚙ Maintainability
Description
ReconcileUserPodResources upserts spec.Volumes and spec.VolumeMounts but never removes ones deleted
from the CR, unlike init containers which are explicitly pruned. This leaves stale volumes/mounts in
the pod template after spec changes.
Code

internal/utils/kubernetes/ensure/pod_spec.go[R111-115]

+	for _, vol := range volumes {
+		v := kubernetes.FindVolumeByNameOrCreate(podSpec, vol.Name)
+		v.VolumeSource = vol.VolumeSource
+		EnsureVolumeDefaultMode(v)
+	}
Relevance

●● Moderate

Pruning removed volumes/mounts is potentially breaking for users relying on additive semantics;
unclear team intent.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The function contains only upsert loops for volumes/mounts and no filtering step, while init
containers have explicit stale removal logic.

internal/utils/kubernetes/ensure/pod_spec.go[104-123]
internal/utils/kubernetes/ensure/pod_spec.go[149-161]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
User-defined `Volumes` and `VolumeMounts` are only added/updated; removals from the CR are not reflected in the Deployment template.

### Issue Context
This violates declarative reconciliation semantics and can keep unintended mounts/volumes around after the user removes them from the spec.

### Fix Focus Areas
- internal/utils/kubernetes/ensure/pod_spec.go[104-123]
- internal/utils/kubernetes/ensure/pod_spec.go[149-161]

### Suggested fix
- Introduce cleanup for user-managed volumes and mounts.
- Avoid deleting operator-owned volumes/mounts by establishing ownership (e.g., store the last-applied user volume+mount names in a pod-template annotation, then prune only those names when no longer present).
- Add a unit test that applies volumes/mounts, then reconciles with an empty list and verifies they are removed.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


ⓘ  1 issues published inline · 6 in summary

To customize comments, go to the Qodo configuration screen, or learn more in the docs.

Qodo Logo

Comment on lines +240 to +243
cert := kubernetes.FindVolumeByNameOrCreate(&template.Spec, "fulcio-cert")
if cert.Projected == nil {
cert.Projected = &core.ProjectedVolumeSource{}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

3. Fulcio-cert source not reset 🐞 Bug ≡ Correctness

Fulcio deployment reconciliation applies user-defined Volumes first, then mutates only
cert.Projected for the operator-managed "fulcio-cert" volume without resetting VolumeSource. If a
user supplies a "fulcio-cert" volume with another source (e.g., EmptyDir), this can produce an
invalid volume spec and block Deployment creation.
Agent Prompt
### Issue description
`fulcio-cert` is operator-managed but user volumes are reconciled first; later code only initializes/edits `cert.Projected` and `cert.Projected.Sources` without clearing any pre-existing `VolumeSource` fields.

### Issue Context
`ReconcileUserPodResources` overwrites `VolumeSource` for user-supplied volumes by name, so a user can unintentionally (or intentionally) provide a conflicting source under the reserved name `fulcio-cert`.

### Fix Focus Areas
- internal/controller/fulcio/actions/deployment.go[146-149]
- internal/controller/fulcio/actions/deployment.go[230-260]
- internal/controller/fulcio/actions/deployment.go[284-321]
- internal/utils/kubernetes/ensure/pod_spec.go[107-115]

### Suggested fix
- In both KMS and file signer branches, replace:
  - `if cert.Projected == nil { cert.Projected = ... }` and direct `cert.Projected.Sources = ...`
- With:
  - `cert.VolumeSource = core.VolumeSource{Projected: &core.ProjectedVolumeSource{Sources: ...}}`
- Then call `ensure.EnsureVolumeDefaultMode(cert)`.
- Add/extend a test similar to CTlog’s operator precedence test to cover `fulcio-cert` collisions.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

@kdacosta0
kdacosta0 marked this pull request as draft August 5, 2026 14:24
@kdacosta0

Copy link
Copy Markdown
Member Author

Waiting for #2181 to merge

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants