Skip to content

Conversation

@knrc
Copy link
Contributor

@knrc knrc commented Nov 6, 2025

This pull request adds support for image pull secrets, with the ability to specify them at the top level (inherited by all) or within individual components.

Summary by Sourcery

Add support for image pull secrets across all operator components

New Features:

  • Introduce ImagePullSecrets field in CRD API types and schemas for top-level and individual components (CTlog, Fulcio, Rekor, Trillian, TimestampAuthority, TUF, Securesign)
  • Add WithImagePullSecrets option in RBAC actions to propagate imagePullSecrets into ServiceAccount resources

Enhancements:

  • Implement MergeImagePullSecrets utility to combine and deduplicate global and component-specific secret lists
  • Update controllers to merge global and per-component ImagePullSecrets when ensuring sub-resources

Tests:

  • Add unit tests for MergeImagePullSecrets covering edge and deduplication cases
  • Add RBAC action tests to validate ServiceAccount imagePullSecrets behavior on create and update

@knrc knrc requested a review from osmman November 6, 2025 00:32
@sourcery-ai
Copy link

sourcery-ai bot commented Nov 6, 2025

Reviewer's Guide

This PR extends the operator to support configurable image pull secrets by augmenting CRD schemas and Spec types, adding deep copy logic and a merge utility, and integrating the secrets into RBAC ServiceAccounts and subresource ensure logic.

ER diagram for imagePullSecrets in CRDs

erDiagram
    SECURESIGN_SPEC ||--o{ LOCAL_OBJECT_REFERENCE : "imagePullSecrets"
    CTLOG_SPEC ||--o{ LOCAL_OBJECT_REFERENCE : "imagePullSecrets"
    FULCIO_SPEC ||--o{ LOCAL_OBJECT_REFERENCE : "imagePullSecrets"
    REKOR_SPEC ||--o{ LOCAL_OBJECT_REFERENCE : "imagePullSecrets"
    TRILLIAN_SPEC ||--o{ LOCAL_OBJECT_REFERENCE : "imagePullSecrets"
    TIMESTAMPAUTHORITY_SPEC ||--o{ LOCAL_OBJECT_REFERENCE : "imagePullSecrets"
    TUF_SPEC ||--o{ LOCAL_OBJECT_REFERENCE : "imagePullSecrets"
    LOCAL_OBJECT_REFERENCE {
        string name
    }
Loading

Class diagram for updated Spec types with imagePullSecrets

classDiagram
    class SecuresignSpec {
        Tuf: TufSpec
        Ctlog: CTlogSpec
        TimestampAuthority: TimestampAuthoritySpec
        ImagePullSecrets: LocalObjectReference[]
    }
    class TufSpec {
        Pvc: TufPvc
        ImagePullSecrets: LocalObjectReference[]
    }
    class CTlogSpec {
        MaxCertChainSize: int64
        ImagePullSecrets: LocalObjectReference[]
    }
    class FulcioSpec {
        TrustedCA: LocalObjectReference
        ImagePullSecrets: LocalObjectReference[]
    }
    class RekorSpec {
        MaxRequestBodySize: int64
        ImagePullSecrets: LocalObjectReference[]
    }
    class TrillianSpec {
        MaxRecvMessageSize: int64
        ImagePullSecrets: LocalObjectReference[]
    }
    class TimestampAuthoritySpec {
        MaxRequestBodySize: int64
        ImagePullSecrets: LocalObjectReference[]
    }
    SecuresignSpec --> TufSpec
    SecuresignSpec --> CTlogSpec
    SecuresignSpec --> TimestampAuthoritySpec
    TufSpec --> TufPvc
    FulcioSpec --> LocalObjectReference
    CTlogSpec --> LocalObjectReference
    RekorSpec --> LocalObjectReference
    TrillianSpec --> LocalObjectReference
    TimestampAuthoritySpec --> LocalObjectReference
    SecuresignSpec --> LocalObjectReference
    TufSpec --> LocalObjectReference
Loading

Class diagram for rbacAction and WithImagePullSecrets

classDiagram
    class rbacAction {
        componentName: string
        rbacName: string
        rules: PolicyRule[]
        canHandle: func(context.Context, T) bool
        imagePullSecrets: func(context.Context, T) []LocalObjectReference
    }
    class WithImagePullSecrets {
        <<function>>
    }
    rbacAction --> PolicyRule
    rbacAction --> LocalObjectReference
    rbacAction --> WithImagePullSecrets
Loading

Flow diagram for merging imagePullSecrets in ensure logic

flowchart TD
    A["SecuresignSpec.ImagePullSecrets"] --> C["MergeImagePullSecrets"]
    B["ComponentSpec.ImagePullSecrets"] --> C
    C --> D["ComponentSpec.ImagePullSecrets (merged)"]
Loading

File-Level Changes

Change Details Files
Extend CRD schemas with optional imagePullSecrets field
  • Add imagePullSecrets array definitions to all component CRD YAMLs
config/crd/bases/rhtas.redhat.com_securesigns.yaml
config/crd/bases/rhtas.redhat.com_ctlogs.yaml
config/crd/bases/rhtas.redhat.com_fulcios.yaml
config/crd/bases/rhtas.redhat.com_rekors.yaml
config/crd/bases/rhtas.redhat.com_timestampauthorities.yaml
config/crd/bases/rhtas.redhat.com_trillians.yaml
config/crd/bases/rhtas.redhat.com_tufs.yaml
Add ImagePullSecrets field to API Spec types
  • Define []LocalObjectReference ImagePullSecrets in each component Spec
api/v1alpha1/ctlog_types.go
api/v1alpha1/fulcio_types.go
api/v1alpha1/rekor_types.go
api/v1alpha1/timestampauthority_types.go
api/v1alpha1/trillian_types.go
api/v1alpha1/tuf_types.go
api/v1alpha1/securesign_types.go
Implement deep copy support for ImagePullSecrets
  • Copy ImagePullSecrets slices in generated deepcopy functions
api/v1alpha1/zz_generated.deepcopy.go
Introduce RBAC action option and integrate pull secrets
  • Add imagePullSecrets field and WithImagePullSecrets setter to rbacAction
  • Extend handleServiceAccount to apply secrets on create/update
  • Add service account tests covering secret scenarios
internal/action/rbac/action.go
internal/action/rbac/action_test.go
Update controllers to pass ImagePullSecrets to RBAC actions
  • Invoke WithImagePullSecrets in NewRBACAction for all components
internal/controller/tuf/actions/rbac.go
internal/controller/ctlog/actions/rbac.go
internal/controller/fulcio/actions/rbac.go
internal/controller/rekor/actions/server/rbac.go
internal/controller/trillian/actions/logserver/rbac.go
internal/controller/tsa/actions/rbac.go
internal/controller/trillian/actions/db/rbac.go
internal/controller/trillian/actions/logsigner/rbac.go
Merge top-level and component-level secrets in ensure controllers
  • Use utils.MergeImagePullSecrets in ensure_* handlers to combine global and component secrets
internal/controller/securesign/actions/ensure_ctlog.go
internal/controller/securesign/actions/ensure_fulcio.go
internal/controller/securesign/actions/ensure_rekor.go
internal/controller/securesign/actions/ensure_trillian.go
internal/controller/securesign/actions/ensure_tsa.go
internal/controller/securesign/actions/ensure_tuf.go
Add MergeImagePullSecrets utility and unit tests
  • Implement MergeImagePullSecrets for deduplication and filtering
  • Add unit tests covering merge logic edge cases
internal/utils/collections.go
internal/utils/collections_test.go

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@qodo-merge-pro
Copy link

qodo-merge-pro bot commented Nov 6, 2025

PR Compliance Guide 🔍

Below is a summary of compliance checks for this PR:

Security Compliance
🟢
No security concerns identified No security vulnerabilities detected by AI analysis. Human verification advised for critical code.
Ticket Compliance
🎫 No ticket provided
  • Create ticket/issue
Codebase Duplication Compliance
Codebase context is not defined

Follow the guide to enable codebase context checks.

Custom Compliance
🟢
Generic: Meaningful Naming and Self-Documenting Code

Objective: Ensure all identifiers clearly express their purpose and intent, making code
self-documenting

Status: Passed

Generic: Robust Error Handling and Edge Case Management

Objective: Ensure comprehensive error handling that provides meaningful context and graceful
degradation

Status: Passed

Generic: Secure Error Handling

Objective: To prevent the leakage of sensitive system information through error messages while
providing sufficient detail for internal debugging.

Status: Passed

Generic: Secure Logging Practices

Objective: To ensure logs are useful for debugging and auditing without exposing sensitive
information like PII, PHI, or cardholder data.

Status: Passed

Generic: Security-First Input Validation and Data Handling

Objective: Ensure all data inputs are validated, sanitized, and handled securely to prevent
vulnerabilities

Status: Passed

Generic: Comprehensive Audit Trails

Objective: To create a detailed and reliable record of critical system actions for security analysis
and compliance.

Status:
No auditing: The new logic for setting ServiceAccount image pull secrets and merging configurations
adds critical state changes without any added auditing or logging of actions taken or
outcomes.

Referred Code
func (i rbacAction[T]) handleServiceAccount(ctx context.Context, instance T) *action.Result {
	var err error
	l := labels.For(i.componentName, i.rbacName, instance.GetName())

	opts := []func(*v1.ServiceAccount) error{
		ensure.ControllerReference[*v1.ServiceAccount](instance, i.Client),
		ensure.Labels[*v1.ServiceAccount](slices.Collect(maps.Keys(l)), l),
	}

	var pullSecrets []v1.LocalObjectReference
	if i.imagePullSecrets != nil {
		pullSecrets = i.imagePullSecrets(ctx, instance)
		if len(pullSecrets) > 0 {
			opts = append(opts, func(sa *v1.ServiceAccount) error {
				sa.ImagePullSecrets = pullSecrets
				return nil
			})
		}
	}

	if _, err = kubernetes.CreateOrUpdate(ctx, i.Client, &v1.ServiceAccount{


 ... (clipped 6 lines)
  • Update
Compliance status legend 🟢 - Fully Compliant
🟡 - Partial Compliant
🔴 - Not Compliant
⚪ - Requires Further Human Verification
🏷️ - Compliance label

Copy link

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey there - I've reviewed your changes - here's some feedback:

  • handleServiceAccount does not clear existing ImagePullSecrets when the function returns an empty slice or nil on updates—consider explicitly setting or clearing the field to avoid stale secrets.
  • MergeImagePullSecrets builds the result using a map, causing non-deterministic ordering; consider preserving a stable order (e.g. base entries first, then overrides) for predictable output.
  • The CRD schema additions for ImagePullSecrets are copy-pasted across all component specs—consider factoring out common definitions or using schema templates to reduce repetition.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- handleServiceAccount does not clear existing ImagePullSecrets when the function returns an empty slice or nil on updates—consider explicitly setting or clearing the field to avoid stale secrets.
- MergeImagePullSecrets builds the result using a map, causing non-deterministic ordering; consider preserving a stable order (e.g. base entries first, then overrides) for predictable output.
- The CRD schema additions for ImagePullSecrets are copy-pasted across all component specs—consider factoring out common definitions or using schema templates to reduce repetition.

## Individual Comments

### Comment 1
<location> `api/v1alpha1/securesign_types.go:39-40` </location>
<code_context>
 	//+optional
 	MaxCertChainSize *int64 `json:"maxCertChainSize,omitempty"`
+
+	// ImagePullSecrets is an optional list of references to secrets for pulling container images.
+	//+optional
+	ImagePullSecrets []core.LocalObjectReference `json:"imagePullSecrets,omitempty"`
</code_context>

<issue_to_address>
**suggestion:** ImagePullSecrets field in SecuresignSpec is not marked as optional.

Adding '+optional' to ImagePullSecrets will ensure consistent CRD generation and validation, matching other spec fields.

```suggestion
	// ImagePullSecrets is an optional list of references to secrets for pulling container images.
	//+optional
	ImagePullSecrets []core.LocalObjectReference `json:"imagePullSecrets,omitempty"`
```
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@qodo-merge-pro
Copy link

qodo-merge-pro bot commented Nov 6, 2025

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
High-level
Consolidate ImagePullSecrets into a shared struct

To reduce code duplication, move the ImagePullSecrets field from the various
component Spec structs into the shared PodRequirements struct, which is already
embedded in most of them.

Examples:

api/v1alpha1/fulcio_types.go [37-39]
	// ImagePullSecrets is an optional list of references to secrets for pulling container images.
	//+optional
	ImagePullSecrets []core.LocalObjectReference `json:"imagePullSecrets,omitempty"`
api/v1alpha1/rekor_types.go [63-65]
	// ImagePullSecrets is an optional list of references to secrets for pulling container images.
	//+optional
	ImagePullSecrets []core.LocalObjectReference `json:"imagePullSecrets,omitempty"`

Solution Walkthrough:

Before:

// In api/v1alpha1/fulcio_types.go
type FulcioSpec struct {
  PodRequirements `json:",inline"`
  // ... other fields
  ImagePullSecrets []core.LocalObjectReference `json:"imagePullSecrets,omitempty"`
}

// In api/v1alpha1/rekor_types.go
type RekorSpec struct {
  PodRequirements `json:",inline"`
  // ... other fields
  ImagePullSecrets []core.LocalObjectReference `json:"imagePullSecrets,omitempty"`
}

// ... and so on for other specs

After:

// In a shared types file (e.g., podrequirements_types.go)
type PodRequirements struct {
  // ... existing fields like Affinity, Resources etc.
  ImagePullSecrets []core.LocalObjectReference `json:"imagePullSecrets,omitempty"`
}

// In api/v1alpha1/fulcio_types.go
type FulcioSpec struct {
  PodRequirements `json:",inline"`
  // ... other fields
  // ImagePullSecrets is now in PodRequirements
}

// ... and so on for other specs
Suggestion importance[1-10]: 8

__

Why: The suggestion correctly identifies significant code duplication and proposes a valid refactoring that improves the design by centralizing the ImagePullSecrets field into the PodRequirements struct, which is a logical location for it.

Medium
Possible issue
Sort merged secrets for deterministic order

To prevent unnecessary reconciliations, sort the merged ImagePullSecrets slice
by name to ensure a deterministic order before returning it.

internal/utils/collections.go [16-41]

 // MergeImagePullSecrets merges two lists of ImagePullSecrets
 func MergeImagePullSecrets(base, override []v1.LocalObjectReference) []v1.LocalObjectReference {
 	if len(base) == 0 && len(override) == 0 {
 		return nil
 	}
 
 	secrets := make(map[string]v1.LocalObjectReference)
 
 	addSecrets := func(list []v1.LocalObjectReference) {
 		for _, secret := range list {
 			if secret.Name != "" {
 				secrets[secret.Name] = secret
 			}
 		}
 	}
 
 	addSecrets(base)
 	addSecrets(override)
 
 	result := make([]v1.LocalObjectReference, 0, len(secrets))
 	for _, secret := range secrets {
 		result = append(result, secret)
 	}
 
+	// Sort for deterministic order
+	slices.SortFunc(result, func(a, b v1.LocalObjectReference) int {
+		return cmp.Compare(a.Name, b.Name)
+	})
+
 	return result
 }
  • Apply / Chat
Suggestion importance[1-10]: 6

__

Why: The suggestion correctly identifies that iterating over a map in Go is non-deterministic, which could cause unnecessary reconciliations. Sorting the result ensures stable output, improving controller performance and predictability.

Low
General
Return nil for empty merged secrets

Standardize the MergeImagePullSecrets function to always return nil when there
are no secrets to return, resolving the inconsistent behavior of returning
either nil or an empty slice.

internal/utils/collections.go [16-41]

 // MergeImagePullSecrets merges two lists of ImagePullSecrets
 func MergeImagePullSecrets(base, override []v1.LocalObjectReference) []v1.LocalObjectReference {
 	if len(base) == 0 && len(override) == 0 {
 		return nil
 	}
 
 	secrets := make(map[string]v1.LocalObjectReference)
-...
+
+	addSecrets := func(list []v1.LocalObjectReference) {
+		for _, secret := range list {
+			if secret.Name != "" {
+				secrets[secret.Name] = secret
+			}
+		}
+	}
+
 	addSecrets(base)
 	addSecrets(override)
+
+	if len(secrets) == 0 {
+		return nil
+	}
 
 	result := make([]v1.LocalObjectReference, 0, len(secrets))
 	for _, secret := range secrets {
 		result = append(result, secret)
 	}
 
 	return result
 }

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 5

__

Why: The suggestion correctly points out an inconsistency in return values (nil vs. empty slice), which can matter in Kubernetes. Standardizing the return value for an empty set of secrets improves code consistency and predictability.

Low
  • Update

@osmman osmman added the enhancement New feature or request label Nov 6, 2025
Ctlog CTlogSpec `json:"ctlog,omitempty"`
TimestampAuthority *TimestampAuthoritySpec `json:"tsa,omitempty"`

ServiceAccountRequirements `json:",inline"`
Copy link
Collaborator

Choose a reason for hiding this comment

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

ImagePullSecrets for this CRD behave differently compare to other CRDs. It will require document that behavior and it will be good to provide some tests to not broke it in feature changes.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes to documentation, I've already reached out to Aron about creating the docs issues. Or are you referring to having a comment?

I'll add some tests

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I add a comment to SecuresignSpec and some higher level tests to complement the lower level ones

Copy link
Collaborator

@osmman osmman Nov 12, 2025

Choose a reason for hiding this comment

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

Unfortunately adding comment like you dud will not modify CRD's OpenAPI which is main source for documentation of CRDs.

For example:

oc explain securesign.spec

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah okay, now I understand what you are after. I'll take a look

Copy link
Collaborator

Choose a reason for hiding this comment

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

One option is to not use ServiceAccountRquirements for Secureign resource and simply add directly imagePullSecrets to that resource. Other CRD can use shared struct.

Copy link
Collaborator

Choose a reason for hiding this comment

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

or have two versions of struct. We already have specific version for TufPvc.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Adding the comment to the type is ignored, because of the inlining, so I've added something to the SecureSignSpec instead

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@osmman take a look and see if that works

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@osmman crdify is complaining about the extra text, so I've reverted that

@knrc knrc force-pushed the securesign-3185 branch 4 times, most recently from cb36dd4 to 9cf907a Compare November 13, 2025 16:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request Review effort 3/5

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants