From 03b35d96bdd420df8a693bc4ac4ebf95c4be5b1b Mon Sep 17 00:00:00 2001 From: Vinicius Zein Date: Sat, 11 Apr 2026 16:10:43 -0400 Subject: [PATCH 1/3] feat: integrate Red Hat Trusted Software Supply Chain (TSSF) Enable supply-chain compliance for ImageBuild pipelines by adding SBOM generation (Syft), Tekton Chains result contract (IMAGE_URL/IMAGE_DIGEST), and traceability metadata in build status. Closes #204 Made-with: Cursor --- api/v1alpha1/imagebuild_types.go | 31 +++ api/v1alpha1/operatorconfig_types.go | 59 ++++++ api/v1alpha1/zz_generated.deepcopy.go | 40 ++++ ...tive.sdv.cloud.redhat.com_imagebuilds.yaml | 28 +++ ....sdv.cloud.redhat.com_operatorconfigs.yaml | 38 ++++ .../samples/automotive_v1_operatorconfig.yaml | 12 ++ internal/common/tasks/scripts.go | 8 + .../common/tasks/scripts/push_artifact.sh | 23 ++- .../common/tasks/scripts/sbom_generate.sh | 64 +++++++ internal/common/tasks/tasks.go | 180 ++++++++++++++++++ internal/controller/imagebuild/controller.go | 32 ++++ .../controller/operatorconfig/controller.go | 11 ++ 12 files changed, 522 insertions(+), 4 deletions(-) create mode 100644 internal/common/tasks/scripts/sbom_generate.sh diff --git a/api/v1alpha1/imagebuild_types.go b/api/v1alpha1/imagebuild_types.go index 2c35c827..2942f9c3 100644 --- a/api/v1alpha1/imagebuild_types.go +++ b/api/v1alpha1/imagebuild_types.go @@ -178,6 +178,32 @@ type DiskExport struct { // PVC *PVCExport `json:"pvc,omitempty"` } +// ArtifactRef captures the supply-chain traceability metadata for a build artifact. +// Populated from PipelineRun results when compliance is enabled. +type ArtifactRef struct { + // Registry is the OCI registry URL where the artifact was pushed (IMAGE_URL) + // +optional + Registry string `json:"registry,omitempty"` + + // Digest is the content-addressable digest of the pushed artifact (sha256:...) + // +optional + Digest string `json:"digest,omitempty"` + + // SBOMRef is the OCI reference to the attached SBOM artifact + // +optional + SBOMRef string `json:"sbomRef,omitempty"` + + // SignatureRef is the OCI reference to the cosign/Sigstore signature + // Populated by Tekton Chains when signing is configured + // +optional + SignatureRef string `json:"signatureRef,omitempty"` + + // ProvenanceRef is the OCI reference to the SLSA provenance attestation + // Populated by Tekton Chains when provenance is configured + // +optional + ProvenanceRef string `json:"provenanceRef,omitempty"` +} + // ImageBuildStatus defines the observed state of ImageBuild type ImageBuildStatus struct { // ObservedGeneration is the most recent generation observed by the controller. @@ -227,6 +253,11 @@ type ImageBuildStatus struct { // LeaseID is the Jumpstarter lease ID acquired during flash // +optional LeaseID string `json:"leaseId,omitempty"` + + // Artifact captures supply-chain traceability metadata (digest, SBOM, signature, provenance) + // for the build output. Populated from PipelineRun results when the artifact is pushed to a registry. + // +optional + Artifact *ArtifactRef `json:"artifact,omitempty"` } // +kubebuilder:object:root=true diff --git a/api/v1alpha1/operatorconfig_types.go b/api/v1alpha1/operatorconfig_types.go index f5fffdb1..7bb36a20 100644 --- a/api/v1alpha1/operatorconfig_types.go +++ b/api/v1alpha1/operatorconfig_types.go @@ -430,6 +430,59 @@ func (c *WorkspacesConfig) GetAutoPauseTimeoutMinutes() int32 { return DefaultAutoPauseTimeoutMinutes } +// DefaultSyftImage is the default Syft container image for SBOM generation +const DefaultSyftImage = "docker.io/anchore/syft:v1.22.0" + +// DefaultSBOMFormat is the default SBOM output format +const DefaultSBOMFormat = "spdx-json" + +// ComplianceConfig configures supply-chain compliance features (SBOM, signing, policy). +// When Enabled, the operator appends an sbom-generate task to every build pipeline +// and populates ArtifactRef in the build status. +type ComplianceConfig struct { + // Enabled activates supply-chain compliance for build pipelines + // +kubebuilder:default=false + Enabled bool `json:"enabled"` + + // SBOMFormat is the SBOM output format (spdx-json or cyclonedx-json) + // +kubebuilder:validation:Enum="spdx-json";"cyclonedx-json" + // +kubebuilder:default="spdx-json" + // +optional + SBOMFormat string `json:"sbomFormat,omitempty"` + + // SyftImage is the container image for SBOM generation (Syft) + // +optional + SyftImage string `json:"syftImage,omitempty"` + + // ECPolicyRef references an Enterprise Contract policy for optional gate enforcement + // +optional + ECPolicyRef string `json:"ecPolicyRef,omitempty"` + + // RekorURL is the Rekor transparency log URL for Tekton Chains configuration + // +optional + RekorURL string `json:"rekorURL,omitempty"` + + // FulcioURL is the Fulcio CA URL for keyless signing via Tekton Chains + // +optional + FulcioURL string `json:"fulcioURL,omitempty"` +} + +// GetSBOMFormat returns the SBOM format, falling back to the default +func (c *ComplianceConfig) GetSBOMFormat() string { + if c != nil && c.SBOMFormat != "" { + return c.SBOMFormat + } + return DefaultSBOMFormat +} + +// GetSyftImage returns the Syft image, falling back to the default +func (c *ComplianceConfig) GetSyftImage() string { + if c != nil && c.SyftImage != "" { + return c.SyftImage + } + return DefaultSyftImage +} + // OperatorConfigSpec defines the desired state of OperatorConfig type OperatorConfigSpec struct { // OSBuilds defines the configuration for OS build operations @@ -455,6 +508,12 @@ type OperatorConfigSpec struct { // Workspaces defines configuration for developer workspaces // +optional Workspaces *WorkspacesConfig `json:"workspaces,omitempty"` + + // Compliance configures supply-chain compliance features (SBOM generation, + // signing endpoints, policy enforcement). When enabled, build pipelines + // gain SBOM generation and emit results for Tekton Chains. + // +optional + Compliance *ComplianceConfig `json:"compliance,omitempty"` } // OSBuildsConfig defines configuration for OS build operations diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 898912b3..477bd2e3 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -52,6 +52,21 @@ func (in *AIBSpec) DeepCopy() *AIBSpec { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ArtifactRef) DeepCopyInto(out *ArtifactRef) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ArtifactRef. +func (in *ArtifactRef) DeepCopy() *ArtifactRef { + if in == nil { + return nil + } + out := new(ArtifactRef) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ArtifactReference) DeepCopyInto(out *ArtifactReference) { *out = *in @@ -348,6 +363,21 @@ func (in *CertificateSourceRef) DeepCopy() *CertificateSourceRef { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ComplianceConfig) DeepCopyInto(out *ComplianceConfig) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ComplianceConfig. +func (in *ComplianceConfig) DeepCopy() *ComplianceConfig { + if in == nil { + return nil + } + out := new(ComplianceConfig) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ContainerBuild) DeepCopyInto(out *ContainerBuild) { *out = *in @@ -673,6 +703,11 @@ func (in *ImageBuildStatus) DeepCopyInto(out *ImageBuildStatus) { (*in)[i].DeepCopyInto(&(*out)[i]) } } + if in.Artifact != nil { + in, out := &in.Artifact, &out.Artifact + *out = new(ArtifactRef) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ImageBuildStatus. @@ -1166,6 +1201,11 @@ func (in *OperatorConfigSpec) DeepCopyInto(out *OperatorConfigSpec) { *out = new(WorkspacesConfig) (*in).DeepCopyInto(*out) } + if in.Compliance != nil { + in, out := &in.Compliance, &out.Compliance + *out = new(ComplianceConfig) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OperatorConfigSpec. diff --git a/config/crd/bases/automotive.sdv.cloud.redhat.com_imagebuilds.yaml b/config/crd/bases/automotive.sdv.cloud.redhat.com_imagebuilds.yaml index 89b146e6..53b07622 100644 --- a/config/crd/bases/automotive.sdv.cloud.redhat.com_imagebuilds.yaml +++ b/config/crd/bases/automotive.sdv.cloud.redhat.com_imagebuilds.yaml @@ -217,6 +217,34 @@ spec: description: AIBImageUsed is the automotive-image-builder container image that was used for the build type: string + artifact: + description: |- + Artifact captures supply-chain traceability metadata (digest, SBOM, signature, provenance) + for the build output. Populated from PipelineRun results when the artifact is pushed to a registry. + properties: + digest: + description: Digest is the content-addressable digest of the pushed + artifact (sha256:...) + type: string + provenanceRef: + description: |- + ProvenanceRef is the OCI reference to the SLSA provenance attestation + Populated by Tekton Chains when provenance is configured + type: string + registry: + description: Registry is the OCI registry URL where the artifact + was pushed (IMAGE_URL) + type: string + sbomRef: + description: SBOMRef is the OCI reference to the attached SBOM + artifact + type: string + signatureRef: + description: |- + SignatureRef is the OCI reference to the cosign/Sigstore signature + Populated by Tekton Chains when signing is configured + type: string + type: object builderImageUsed: description: |- BuilderImageUsed is the osbuild builder container image that was used for the build diff --git a/config/crd/bases/automotive.sdv.cloud.redhat.com_operatorconfigs.yaml b/config/crd/bases/automotive.sdv.cloud.redhat.com_operatorconfigs.yaml index 9a010684..d372fc6b 100644 --- a/config/crd/bases/automotive.sdv.cloud.redhat.com_operatorconfigs.yaml +++ b/config/crd/bases/automotive.sdv.cloud.redhat.com_operatorconfigs.yaml @@ -609,6 +609,44 @@ spec: type: object type: object type: object + compliance: + description: |- + Compliance configures supply-chain compliance features (SBOM generation, + signing endpoints, policy enforcement). When enabled, build pipelines + gain SBOM generation and emit results for Tekton Chains. + properties: + ecPolicyRef: + description: ECPolicyRef references an Enterprise Contract policy + for optional gate enforcement + type: string + enabled: + default: false + description: Enabled activates supply-chain compliance for build + pipelines + type: boolean + fulcioURL: + description: FulcioURL is the Fulcio CA URL for keyless signing + via Tekton Chains + type: string + rekorURL: + description: RekorURL is the Rekor transparency log URL for Tekton + Chains configuration + type: string + sbomFormat: + default: spdx-json + description: SBOMFormat is the SBOM output format (spdx-json or + cyclonedx-json) + enum: + - spdx-json + - cyclonedx-json + type: string + syftImage: + description: SyftImage is the container image for SBOM generation + (Syft) + type: string + required: + - enabled + type: object containerBuilds: description: ContainerBuilds defines configuration for container build operations diff --git a/config/samples/automotive_v1_operatorconfig.yaml b/config/samples/automotive_v1_operatorconfig.yaml index 6cb3aed5..26f72ed8 100644 --- a/config/samples/automotive_v1_operatorconfig.yaml +++ b/config/samples/automotive_v1_operatorconfig.yaml @@ -50,6 +50,18 @@ spec: # value: "automotive" # effect: "NoExecute" + # Compliance configuration for supply-chain security (SBOM, signing, policy) + # When enabled, pipelines gain SBOM generation and emit Tekton Chains results + # compliance: + # enabled: true + # sbomFormat: "spdx-json" # or "cyclonedx-json" + # syftImage: "docker.io/anchore/syft:v1.22.0" + # # RHTAS endpoints for Tekton Chains keyless signing + # rekorURL: "https://rekor.example.com" + # fulcioURL: "https://fulcio.example.com" + # # Enterprise Contract policy ref (stretch goal) + # ecPolicyRef: "" + # BuildAPI configuration for the Build API server buildAPI: # Optional: Authentication configuration for OIDC/JWT providers diff --git a/internal/common/tasks/scripts.go b/internal/common/tasks/scripts.go index 9e96c23c..43e4b2c4 100644 --- a/internal/common/tasks/scripts.go +++ b/internal/common/tasks/scripts.go @@ -44,6 +44,7 @@ func init() { PushArtifactScript = commonScript + "\n" + pushArtifactScript FlashImageScript = commonScript + "\n" + flashImageScript SealedOperationScript = commonScript + "\n" + sealedOperationScript + SBOMGenerateScript = commonScript + "\n" + sbomGenerateScript } //go:embed scripts/sealed_operation.sh @@ -52,3 +53,10 @@ var sealedOperationScript string // SealedOperationScript contains the embedded script for AIB sealed operations. // It is the concatenation of common.sh and sealed_operation.sh. var SealedOperationScript string + +//go:embed scripts/sbom_generate.sh +var sbomGenerateScript string + +// SBOMGenerateScript contains the embedded script for SBOM generation via Syft. +// It is the concatenation of common.sh and sbom_generate.sh. +var SBOMGenerateScript string diff --git a/internal/common/tasks/scripts/push_artifact.sh b/internal/common/tasks/scripts/push_artifact.sh index f018e394..312361e0 100644 --- a/internal/common/tasks/scripts/push_artifact.sh +++ b/internal/common/tasks/scripts/push_artifact.sh @@ -327,12 +327,18 @@ EOF # Push with multi-layer manifest using annotation file # Files are pushed from current directory (parts_dir) so they extract flat # shellcheck disable=SC2086 - "$HOME/bin/oras" push --disable-path-validation \ + push_output=$("$HOME/bin/oras" push --disable-path-validation \ --image-spec v1.1 \ --artifact-type "${artifact_type}" \ --annotation-file "$annotations_file" \ "${repo_url}" \ - ${layer_args} + ${layer_args} 2>&1) + echo "$push_output" + + # Extract digest from oras output (format: "Digest: sha256:abc123...") + pushed_digest=$(echo "$push_output" | grep -i '^Digest:' | awk '{print $2}' | head -1) + printf '%s' "${repo_url}" > "$(results.IMAGE_URL.path)" + printf '%s' "${pushed_digest}" > "$(results.IMAGE_DIGEST.path)" # Clean up annotation file (also handled by trap) rm -f "$annotations_file" @@ -341,6 +347,8 @@ EOF echo "" echo "=== Multi-layer artifact pushed successfully ===" + echo " IMAGE_URL: ${repo_url}" + echo " IMAGE_DIGEST: ${pushed_digest}" else # Fallback to single-file push (original behavior) @@ -388,15 +396,22 @@ PYEOF echo " Media type: ${media_type}" echo " Annotations: distro=${distro}, target=${target}, arch=${arch}" - "$HOME/bin/oras" push --disable-path-validation \ + push_output=$("$HOME/bin/oras" push --disable-path-validation \ --image-spec v1.1 \ --artifact-type "${media_type}" \ --annotation-file "$single_annotations_file" \ "${repo_url}" \ - "${exportFile}:${media_type}" + "${exportFile}:${media_type}" 2>&1) + echo "$push_output" + + pushed_digest=$(echo "$push_output" | grep -i '^Digest:' | awk '{print $2}' | head -1) + printf '%s' "${repo_url}" > "$(results.IMAGE_URL.path)" + printf '%s' "${pushed_digest}" > "$(results.IMAGE_DIGEST.path)" emit_progress "Pushing artifact" 1 1 echo "" echo "=== Artifact pushed successfully ===" + echo " IMAGE_URL: ${repo_url}" + echo " IMAGE_DIGEST: ${pushed_digest}" fi diff --git a/internal/common/tasks/scripts/sbom_generate.sh b/internal/common/tasks/scripts/sbom_generate.sh new file mode 100644 index 00000000..72b8cdd8 --- /dev/null +++ b/internal/common/tasks/scripts/sbom_generate.sh @@ -0,0 +1,64 @@ +# NOTE: common.sh is prepended to this script at embed time. + +set -euo pipefail + +image_url="${IMAGE_URL}" +image_digest="${IMAGE_DIGEST}" +sbom_format="${SBOM_FORMAT}" +result_path="${RESULT_PATH}" + +if [ -z "$image_url" ] || [ -z "$image_digest" ]; then + echo "ERROR: IMAGE_URL and IMAGE_DIGEST are required" >&2 + exit 1 +fi + +image_ref="${image_url}@${image_digest}" + +echo "=== SBOM Generation ===" +echo " Image: ${image_ref}" +echo " Format: ${sbom_format}" + +emit_progress "Generating SBOM" 0 2 + +# Generate SBOM from the OCI artifact +sbom_file="/tmp/sbom.json" +syft "${image_ref}" -o "${sbom_format}=${sbom_file}" 2>&1 || { + echo "ERROR: SBOM generation failed" >&2 + exit 1 +} + +emit_progress "Generating SBOM" 1 2 + +sbom_size=$(wc -c < "$sbom_file" | tr -d '[:space:]') +echo " SBOM generated: ${sbom_size} bytes" + +# Determine SBOM media type based on format +case "${sbom_format}" in + spdx-json) sbom_media_type="application/spdx+json" ;; + cyclonedx-json) sbom_media_type="application/vnd.cyclonedx+json" ;; + *) sbom_media_type="application/json" ;; +esac + +# Attach the SBOM as an OCI referrer to the original artifact +echo "Attaching SBOM to ${image_ref}..." + +# Use ORAS to attach the SBOM as a referrer (OCI 1.1 referrers API) +DOCKER_CONFIG="${DOCKER_CONFIG:-}" oras attach \ + --artifact-type "${sbom_media_type}" \ + "${image_ref}" \ + "${sbom_file}:${sbom_media_type}" 2>&1 || { + echo "ERROR: Failed to attach SBOM to artifact" >&2 + exit 1 +} + +# Write the SBOM reference as a result (image_url with SBOM digest) +sbom_ref="${image_url}" +printf '%s' "${sbom_ref}" > "${result_path}" + +emit_progress "Generating SBOM" 2 2 + +echo "" +echo "=== SBOM attached successfully ===" +echo " SBOM_URI: ${sbom_ref}" + +rm -f "$sbom_file" diff --git a/internal/common/tasks/tasks.go b/internal/common/tasks/tasks.go index 75bfdd01..b2d1cd93 100644 --- a/internal/common/tasks/tasks.go +++ b/internal/common/tasks/tasks.go @@ -29,6 +29,9 @@ type BuildConfig struct { TrustedCABundleKind string TrustedCABundleName string UsePVCScratchVolumes bool + ComplianceEnabled bool + SyftImage string + SBOMFormat string } // getAutomotiveImageBuilderImage returns the AIB image from config or the default constant @@ -211,6 +214,16 @@ func GeneratePushArtifactRegistryTask(namespace string, buildConfig *BuildConfig }, }, }, + Results: []tektonv1.TaskResult{ + { + Name: "IMAGE_URL", + Description: "OCI reference to the pushed artifact (registry/repo:tag)", + }, + { + Name: "IMAGE_DIGEST", + Description: "Content-addressable digest of the pushed artifact (sha256:...)", + }, + }, Workspaces: []tektonv1.WorkspaceDeclaration{ { Name: workspaceNameShared, @@ -915,6 +928,16 @@ func GenerateTektonPipeline(name, namespace string, buildConfig *BuildConfig) *t Description: "JSON timing breakdown of build phases in seconds", Value: tektonv1.ParamValue{Type: tektonv1.ParamTypeString, StringVal: "$(tasks.build-image.results.build-timing)"}, }, + { + Name: "IMAGE_URL", + Description: "OCI reference to the pushed artifact (Tekton Chains contract)", + Value: tektonv1.ParamValue{Type: tektonv1.ParamTypeString, StringVal: "$(tasks.push-disk-artifact.results.IMAGE_URL)"}, + }, + { + Name: "IMAGE_DIGEST", + Description: "Content-addressable digest of the pushed artifact (Tekton Chains contract)", + Value: tektonv1.ParamValue{Type: tektonv1.ParamTypeString, StringVal: "$(tasks.push-disk-artifact.results.IMAGE_DIGEST)"}, + }, }, Tasks: []tektonv1.PipelineTask{ { @@ -1288,6 +1311,59 @@ func GenerateTektonPipeline(name, namespace string, buildConfig *BuildConfig) *t }, } + if buildConfig != nil && buildConfig.ComplianceEnabled { + sbomFormat := buildConfig.SBOMFormat + if sbomFormat == "" { + sbomFormat = automotivev1alpha1.DefaultSBOMFormat + } + + pipeline.Spec.Params = append(pipeline.Spec.Params, tektonv1.ParamSpec{ + Name: "sbom-format", + Type: tektonv1.ParamTypeString, + Description: "SBOM output format (spdx-json or cyclonedx-json)", + Default: &tektonv1.ParamValue{ + Type: tektonv1.ParamTypeString, + StringVal: sbomFormat, + }, + }) + + pipeline.Spec.Results = append(pipeline.Spec.Results, tektonv1.PipelineResult{ + Name: "SBOM_URI", + Description: "OCI reference to the attached SBOM artifact", + Value: tektonv1.ParamValue{Type: tektonv1.ParamTypeString, StringVal: "$(tasks.sbom-generate.results.SBOM_URI)"}, + }) + + pipeline.Spec.Tasks = append(pipeline.Spec.Tasks, tektonv1.PipelineTask{ + Name: "sbom-generate", + TaskRef: &tektonv1.TaskRef{ + ResolverRef: tektonv1.ResolverRef{ + Resolver: "cluster", + Params: []tektonv1.Param{ + {Name: "kind", Value: tektonv1.ParamValue{Type: tektonv1.ParamTypeString, StringVal: "task"}}, + {Name: "name", Value: tektonv1.ParamValue{Type: tektonv1.ParamTypeString, StringVal: "sbom-generate"}}, + {Name: "namespace", Value: tektonv1.ParamValue{Type: tektonv1.ParamTypeString, StringVal: namespace}}, + }, + }, + }, + Params: []tektonv1.Param{ + {Name: "IMAGE_URL", Value: tektonv1.ParamValue{Type: tektonv1.ParamTypeString, StringVal: "$(tasks.push-disk-artifact.results.IMAGE_URL)"}}, + {Name: "IMAGE_DIGEST", Value: tektonv1.ParamValue{Type: tektonv1.ParamTypeString, StringVal: "$(tasks.push-disk-artifact.results.IMAGE_DIGEST)"}}, + {Name: "sbom-format", Value: tektonv1.ParamValue{Type: tektonv1.ParamTypeString, StringVal: "$(params.sbom-format)"}}, + }, + Workspaces: []tektonv1.WorkspacePipelineTaskBinding{ + {Name: "registry-auth", Workspace: "registry-auth"}, + }, + RunAfter: []string{"push-disk-artifact"}, + When: []tektonv1.WhenExpression{ + { + Input: "$(tasks.push-disk-artifact.results.IMAGE_URL)", + Operator: "notin", + Values: []string{"", "null"}, + }, + }, + }) + } + return pipeline } @@ -1307,6 +1383,110 @@ func buildEnvFrom(envSecretRef string) []corev1.EnvFromSource { } } +// GenerateSBOMTask creates a Tekton Task for generating and attaching SBOMs to OCI artifacts via Syft. +// The task runs after push-disk-artifact and attaches the SBOM as an OCI referrer. +func GenerateSBOMTask(namespace string, buildConfig *BuildConfig) *tektonv1.Task { + syftImage := automotivev1alpha1.DefaultSyftImage + if buildConfig != nil && buildConfig.SyftImage != "" { + syftImage = buildConfig.SyftImage + } + + return &tektonv1.Task{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "tekton.dev/v1", + Kind: "Task", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "sbom-generate", + Namespace: namespace, + Labels: map[string]string{ + "app.kubernetes.io/managed-by": "automotive-dev-operator", + "app.kubernetes.io/part-of": "automotive-dev", + }, + }, + Spec: tektonv1.TaskSpec{ + Params: []tektonv1.ParamSpec{ + { + Name: "IMAGE_URL", + Type: tektonv1.ParamTypeString, + Description: "OCI reference to the artifact to scan", + }, + { + Name: "IMAGE_DIGEST", + Type: tektonv1.ParamTypeString, + Description: "Digest of the artifact to scan (sha256:...)", + }, + { + Name: "sbom-format", + Type: tektonv1.ParamTypeString, + Description: "SBOM output format (spdx-json or cyclonedx-json)", + Default: &tektonv1.ParamValue{ + Type: tektonv1.ParamTypeString, + StringVal: automotivev1alpha1.DefaultSBOMFormat, + }, + }, + }, + Results: []tektonv1.TaskResult{ + { + Name: "SBOM_URI", + Description: "OCI reference to the attached SBOM artifact", + }, + }, + Workspaces: []tektonv1.WorkspaceDeclaration{ + { + Name: "registry-auth", + Description: "Optional registry credentials for pulling/pushing SBOM", + MountPath: "/workspace/registry-auth", + Optional: true, + }, + }, + Steps: []tektonv1.Step{ + { + Name: "generate-sbom", + Image: syftImage, + Env: []corev1.EnvVar{ + { + Name: "IMAGE_URL", + Value: "$(params.IMAGE_URL)", + }, + { + Name: "IMAGE_DIGEST", + Value: "$(params.IMAGE_DIGEST)", + }, + { + Name: "SBOM_FORMAT", + Value: "$(params.sbom-format)", + }, + { + Name: "RESULT_PATH", + Value: "$(results.SBOM_URI.path)", + }, + }, + Script: SBOMGenerateScript, + VolumeMounts: []corev1.VolumeMount{ + { + Name: "docker-config", + MountPath: "/docker-config", + ReadOnly: true, + }, + }, + }, + }, + Volumes: []corev1.Volume{ + { + Name: "docker-config", + VolumeSource: corev1.VolumeSource{ + Secret: &corev1.SecretVolumeSource{ + SecretName: "$(params.IMAGE_URL)", + Optional: ptr.To(true), + }, + }, + }, + }, + }, + } +} + // GeneratePrepareBuilderTask creates a Tekton Task that checks for/builds the aib-build helper container func GeneratePrepareBuilderTask(namespace string, buildConfig *BuildConfig) *tektonv1.Task { task := &tektonv1.Task{ diff --git a/internal/controller/imagebuild/controller.go b/internal/controller/imagebuild/controller.go index 755a623a..9b53bf44 100644 --- a/internal/controller/imagebuild/controller.go +++ b/internal/controller/imagebuild/controller.go @@ -434,6 +434,9 @@ func (r *ImageBuildReconciler) checkBuildProgress( fresh.Status.AIBImageUsed = aibImageUsed fresh.Status.BuilderImageUsed = builderImageUsed + // Populate supply-chain artifact ref (IMAGE_URL, IMAGE_DIGEST, SBOM_URI) + fresh.Status.Artifact = extractArtifactRef(pipelineRun) + // Extract lease ID if flash was enabled if fresh.Spec.IsFlashEnabled() { fresh.Status.LeaseID = extractLeaseID(pipelineRun) @@ -1773,6 +1776,30 @@ func extractProvenance(pipelineRun *tektonv1.PipelineRun, aibImage string) (aibI return aibImageUsed, builderImageUsed } +// extractArtifactRef builds an ArtifactRef from PipelineRun results. +// Returns nil when no artifact was pushed (IMAGE_URL missing). +func extractArtifactRef(pipelineRun *tektonv1.PipelineRun) *automotivev1alpha1.ArtifactRef { + ref := &automotivev1alpha1.ArtifactRef{} + found := false + + for _, result := range pipelineRun.Status.Results { + switch result.Name { + case "IMAGE_URL": + ref.Registry = result.Value.StringVal + found = true + case "IMAGE_DIGEST": + ref.Digest = result.Value.StringVal + case "SBOM_URI": + ref.SBOMRef = result.Value.StringVal + } + } + + if !found { + return nil + } + return ref +} + // extractArtifactFilename extracts the artifact filename from PipelineRun results func extractArtifactFilename(pipelineRun *tektonv1.PipelineRun) string { for _, result := range pipelineRun.Status.Results { @@ -1958,6 +1985,11 @@ func (r *ImageBuildReconciler) resolveBuildConfig(ctx context.Context) *tasks.Bu bc.FlashTimeoutMinutes = operatorConfig.Spec.OSBuilds.GetFlashTimeoutMinutes() controllerutils.ApplyTrustedCABundleFromOSBuilds(bc, operatorConfig.Spec.OSBuilds) } + if operatorConfig.Spec.Compliance != nil && operatorConfig.Spec.Compliance.Enabled { + bc.ComplianceEnabled = true + bc.SyftImage = operatorConfig.Spec.Compliance.GetSyftImage() + bc.SBOMFormat = operatorConfig.Spec.Compliance.GetSBOMFormat() + } return bc } diff --git a/internal/controller/operatorconfig/controller.go b/internal/controller/operatorconfig/controller.go index f8408a32..d6a6743b 100644 --- a/internal/controller/operatorconfig/controller.go +++ b/internal/controller/operatorconfig/controller.go @@ -650,6 +650,13 @@ func (r *OperatorConfigReconciler) deployOSBuilds( return fmt.Errorf("failed to create target defaults ConfigMap: %w", err) } + // Wire compliance configuration into build config + if config.Spec.Compliance != nil && config.Spec.Compliance.Enabled { + buildConfig.ComplianceEnabled = true + buildConfig.SyftImage = config.Spec.Compliance.GetSyftImage() + buildConfig.SBOMFormat = config.Spec.Compliance.GetSBOMFormat() + } + // Generate and deploy Tekton tasks tektonTasks := []*tektonv1.Task{ tasks.GenerateBuildAutomotiveImageTask(config.Namespace, buildConfig, ""), @@ -658,6 +665,10 @@ func (r *OperatorConfigReconciler) deployOSBuilds( } tektonTasks = append(tektonTasks, tasks.GenerateSealedTasks(config.Namespace, buildConfig)...) + if buildConfig.ComplianceEnabled { + tektonTasks = append(tektonTasks, tasks.GenerateSBOMTask(config.Namespace, buildConfig)) + } + for _, task := range tektonTasks { task.Labels["automotive.sdv.cloud.redhat.com/managed-by"] = config.Name From 3751c55fb607d8e538727b6ff9725e11df49d814 Mon Sep 17 00:00:00 2001 From: Vinicius Zein Date: Sat, 11 Apr 2026 20:00:25 -0400 Subject: [PATCH 2/3] docs: include ADR-001 TSSF integration (status: Accepted) Made-with: Cursor --- docs/adr/001-tssf-integration.md | 186 +++++++++++++++++++++++++++++++ 1 file changed, 186 insertions(+) create mode 100644 docs/adr/001-tssf-integration.md diff --git a/docs/adr/001-tssf-integration.md b/docs/adr/001-tssf-integration.md new file mode 100644 index 00000000..e80d1b14 --- /dev/null +++ b/docs/adr/001-tssf-integration.md @@ -0,0 +1,186 @@ +# ADR-001: Integrate Red Hat Trusted Software Supply Chain (RHTSSC) + +## Status + +Accepted + +## Date + +2026-04-10 + +## Context + +The automotive-dev-operator generates Tekton PipelineRuns to build OS images +(`ImageBuild`) and generic software artifacts (`SoftwareBuild`). Today, build +outputs are not signed, have no SBOM, and produce no SLSA provenance +attestation. This blocks adoption in environments that require supply-chain +compliance (UNECE R155, ISO/SAE 21434, internal Red Hat product requirements). + +Red Hat ships three products under the **Trusted Software Supply Chain** +umbrella: + +| Product | Upstream | Function | +|---------|----------|----------| +| **RHTAP** (Trusted Application Pipeline) | Konflux / Tekton + Enterprise Contract | SLSA L3 build pipelines, policy enforcement | +| **RHTAS** (Trusted Artifact Signer) | Sigstore (cosign, Fulcio, Rekor) | Keyless artifact signing + transparency log | +| **RHTPA** (Trusted Profile Analyzer) | Trustification / GUAC | SBOM indexing, vulnerability analysis | + +PR #199 review (finding #2, #10, #25) identified three immediate gaps: + +1. Inline `TaskSpec` prevents Tekton Chains from verifying task provenance. +2. Build outputs are not traceable (no digest, no signature, no SBOM reference). +3. Mutable image tags undermine reproducibility. + +Issue #200 tracks the prerequisite TaskRef migration for `SoftwareBuild`. + +## Decision + +### 1. Build on `main`, not on `feat/software-build` + +TSSF integration is **post-build and strategy-agnostic**. The compliance +pipeline tail (SBOM → Sign → Provenance → Publish) is identical regardless of +whether the build used `ImageBuild` or `SoftwareBuild`. The existing +`ImageBuild` pipeline on `main` already uses `TaskRef` with cluster resolver +for `build-image`, `push-disk-artifact`, and `flash-image`, making it +partially Chains-compatible today. + +Starting from `main`: + +- Delivers value to `ImageBuild` immediately without waiting for PR #199. +- Keeps the TSSF PR scope clean and reviewable. +- Avoids coupling to `SoftwareBuild` review cycle. +- `SoftwareBuild` gains TSSF when PR #199 merges and issue #200 is resolved. + +### 2. Use Tekton Chains for signing and provenance (not custom tasks) + +**Chosen:** Tekton Chains as a passive observer. + +**Rejected alternative:** A custom `comply` TaskRun that runs cosign/syft +inline. This was rejected because: + +- Chains is the Red Hat-supported, Konflux-standard mechanism. +- It runs out-of-band (separate controller), so pipeline authors don't need to + manage signing keys or attestation logic. +- It produces SLSA L3 provenance automatically when tasks use `TaskRef`. +- RHTAS (Fulcio + Rekor) plugs directly into Chains configuration. + +Chains is configured cluster-wide via a `ConfigMap` in the `tekton-chains` +namespace. The operator does not need to manage Chains lifecycle—only ensure +pipelines emit the result contract Chains expects. + +### 3. Add an explicit SBOM generation task + +Tekton Chains handles signing and provenance but does **not** generate SBOMs. +A dedicated `sbom-generate` task will: + +- Run Syft against the build workspace to produce SPDX or CycloneDX output. +- Attach the SBOM as an OCI artifact referrer to the build artifact. +- Emit a `SBOM_URI` result for status reporting. + +This task is strategy-agnostic and appended after the build/test stages. + +### 4. Require tasks to emit typed results + +For Chains to sign the correct artifact, pipeline tasks must produce: + +| Result name | Description | +|-------------|-------------| +| `IMAGE_URL` | OCI reference (registry/repo:tag) | +| `IMAGE_DIGEST` | Content-addressable digest (sha256:...) | +| `SBOM_URI` | OCI reference to attached SBOM | + +The operator's pipeline generation code will wire these results as pipeline +results, making them visible to Chains. + +### 5. Extend the CRD status for compliance traceability + +```go +type ArtifactRef struct { + Registry string `json:"registry,omitempty"` + Digest string `json:"digest,omitempty"` + SBOMRef string `json:"sbomRef,omitempty"` + SignatureRef string `json:"signatureRef,omitempty"` + ProvenanceRef string `json:"provenanceRef,omitempty"` +} +``` + +The reconciler will populate these fields from PipelineRun results, giving +developers a single place to find the full chain of trust. + +### 6. Enterprise Contract as an optional policy gate + +Enterprise Contract (EC) evaluates whether a built artifact meets a defined +policy (signed, has SBOM, no critical CVEs, SLSA provenance present). This +will be an optional, cluster-level configuration: + +- `OperatorConfig.Spec.Compliance.PolicyRef` points to an EC policy. +- When set, the operator appends an `ec-validate` task as the final pipeline + stage. +- Builds that fail policy are marked `Failed` with a compliance-specific + condition. + +### 7. OperatorConfig extensions + +```go +type ComplianceConfig struct { + Enabled bool `json:"enabled"` + SBOMFormat string `json:"sbomFormat,omitempty"` + SBOMTaskBundle string `json:"sbomTaskBundle,omitempty"` + ECPolicyRef string `json:"ecPolicyRef,omitempty"` + TrustedArtifactSignerURL string `json:"trustedArtifactSignerURL,omitempty"` + RekorURL string `json:"rekorURL,omitempty"` + FulcioURL string `json:"fulcioURL,omitempty"` +} +``` + +These fields configure the cluster-wide compliance behaviour. Per-build +overrides via `spec.compliance` on individual CRs are a future consideration. + +## Consequences + +### Positive + +- `ImageBuild` gains SBOM + signing + SLSA provenance without any CRD changes. +- `SoftwareBuild` gains the same once issue #200 is resolved. +- Single compliance tail shared across all build strategies. +- Aligns with Red Hat product direction (Konflux, RHTAP, RHTAS). +- Meets UNECE R155 / ISO 21434 traceability requirements. + +### Negative + +- Requires Tekton Chains to be installed on the cluster (operator does not + manage its lifecycle). +- RHTAS (Fulcio/Rekor) must be available for keyless signing; clusters without + RHTAS need a pre-provisioned cosign key pair. +- SBOM generation adds ~1-2 minutes to each build pipeline. + +### Risks + +- Tekton Chains' result contract (`IMAGE_URL`, `IMAGE_DIGEST`) may evolve. + Mitigated by pinning Chains version in operator compatibility matrix. +- Enterprise Contract policy authoring requires separate expertise. Mitigated + by shipping a default policy bundle. + +## Implementation order + +1. **Issue #200** — Migrate `SoftwareBuild` inline TaskSpec to signed TaskRef + bundles (prerequisite for Chains on SoftwareBuild pipelines). +2. **Pipeline results contract** — Add `IMAGE_URL`, `IMAGE_DIGEST` results to + existing `ImageBuild` tasks and new `SoftwareBuild` task bundles. +3. **SBOM task** — Create and publish a signed `sbom-generate` cluster task. +4. **Operator wiring** — Append SBOM task to pipeline generation when + compliance is enabled; populate `status.artifact` from results. +5. **CRD extension** — Add `ArtifactRef` to status, `ComplianceConfig` to + OperatorConfig. +6. **Tekton Chains cluster configuration** — Document RHTAS endpoint + configuration for Chains. +7. **Enterprise Contract** — Optional policy gate task (stretch goal). + +## References + +- [Tekton Chains](https://tekton.dev/docs/chains/) +- [SLSA v1.0 requirements](https://slsa.dev/spec/v1.0/requirements) +- [Red Hat Trusted Software Supply Chain](https://red.ht/trusted) +- [Enterprise Contract](https://enterprisecontract.dev/) +- Issue #200: Migrate SoftwareBuild pipeline from inline TaskSpec to signed TaskRef bundles +- PR #199: feat: add SoftwareBuild CRD for multi-OS software builds From ceb4be8ef9720f85daee532e034a64408354dc14 Mon Sep 17 00:00:00 2001 From: Vinicius Zein Date: Mon, 13 Apr 2026 00:02:25 -0400 Subject: [PATCH 3/3] fix: address CodeRabbit review findings on TSSF integration - Fix critical: SBOM task volume now uses a dedicated secret-ref param instead of IMAGE_URL as the Secret name - Fix major: sbom_generate.sh captures actual referrer digest from oras attach output for accurate SBOM_URI result - Fix major: push_artifact.sh validates parsed digest before writing Tekton results (both multi-layer and single-file paths) - Fix minor: ArtifactRef doc comments no longer wrongly scope fields to "when compliance is enabled" - Fix minor: OperatorConfig reconciler cleans up sbom-generate task when compliance is toggled off - Fix minor: ADR ComplianceConfig struct updated to match implementation (SyftImage instead of SBOMTaskBundle, drop TrustedArtifactSignerURL) Made-with: Cursor --- api/v1alpha1/imagebuild_types.go | 8 +++---- ...tive.sdv.cloud.redhat.com_imagebuilds.yaml | 12 +++++------ docs/adr/001-tssf-integration.md | 21 ++++++++++--------- .../common/tasks/scripts/push_artifact.sh | 8 +++++++ .../common/tasks/scripts/sbom_generate.sh | 17 ++++++++++----- internal/common/tasks/tasks.go | 20 +++++++++++++++--- .../controller/operatorconfig/controller.go | 7 +++++++ 7 files changed, 64 insertions(+), 29 deletions(-) diff --git a/api/v1alpha1/imagebuild_types.go b/api/v1alpha1/imagebuild_types.go index 2942f9c3..eebddd39 100644 --- a/api/v1alpha1/imagebuild_types.go +++ b/api/v1alpha1/imagebuild_types.go @@ -179,7 +179,9 @@ type DiskExport struct { } // ArtifactRef captures the supply-chain traceability metadata for a build artifact. -// Populated from PipelineRun results when compliance is enabled. +// Registry and Digest are populated for any successful registry push. +// SBOMRef, SignatureRef, and ProvenanceRef are populated when their respective +// features (SBOM generation, Sigstore signing, Tekton Chains provenance) are active. type ArtifactRef struct { // Registry is the OCI registry URL where the artifact was pushed (IMAGE_URL) // +optional @@ -194,12 +196,10 @@ type ArtifactRef struct { SBOMRef string `json:"sbomRef,omitempty"` // SignatureRef is the OCI reference to the cosign/Sigstore signature - // Populated by Tekton Chains when signing is configured // +optional SignatureRef string `json:"signatureRef,omitempty"` // ProvenanceRef is the OCI reference to the SLSA provenance attestation - // Populated by Tekton Chains when provenance is configured // +optional ProvenanceRef string `json:"provenanceRef,omitempty"` } @@ -255,7 +255,7 @@ type ImageBuildStatus struct { LeaseID string `json:"leaseId,omitempty"` // Artifact captures supply-chain traceability metadata (digest, SBOM, signature, provenance) - // for the build output. Populated from PipelineRun results when the artifact is pushed to a registry. + // for the build output. Populated from PipelineRun results after a successful registry push. // +optional Artifact *ArtifactRef `json:"artifact,omitempty"` } diff --git a/config/crd/bases/automotive.sdv.cloud.redhat.com_imagebuilds.yaml b/config/crd/bases/automotive.sdv.cloud.redhat.com_imagebuilds.yaml index 53b07622..86dcfeee 100644 --- a/config/crd/bases/automotive.sdv.cloud.redhat.com_imagebuilds.yaml +++ b/config/crd/bases/automotive.sdv.cloud.redhat.com_imagebuilds.yaml @@ -220,16 +220,15 @@ spec: artifact: description: |- Artifact captures supply-chain traceability metadata (digest, SBOM, signature, provenance) - for the build output. Populated from PipelineRun results when the artifact is pushed to a registry. + for the build output. Populated from PipelineRun results after a successful registry push. properties: digest: description: Digest is the content-addressable digest of the pushed artifact (sha256:...) type: string provenanceRef: - description: |- - ProvenanceRef is the OCI reference to the SLSA provenance attestation - Populated by Tekton Chains when provenance is configured + description: ProvenanceRef is the OCI reference to the SLSA provenance + attestation type: string registry: description: Registry is the OCI registry URL where the artifact @@ -240,9 +239,8 @@ spec: artifact type: string signatureRef: - description: |- - SignatureRef is the OCI reference to the cosign/Sigstore signature - Populated by Tekton Chains when signing is configured + description: SignatureRef is the OCI reference to the cosign/Sigstore + signature type: string type: object builderImageUsed: diff --git a/docs/adr/001-tssf-integration.md b/docs/adr/001-tssf-integration.md index e80d1b14..6b004902 100644 --- a/docs/adr/001-tssf-integration.md +++ b/docs/adr/001-tssf-integration.md @@ -113,7 +113,7 @@ Enterprise Contract (EC) evaluates whether a built artifact meets a defined policy (signed, has SBOM, no critical CVEs, SLSA provenance present). This will be an optional, cluster-level configuration: -- `OperatorConfig.Spec.Compliance.PolicyRef` points to an EC policy. +- `OperatorConfig.Spec.Compliance.ECPolicyRef` points to an EC policy. - When set, the operator appends an `ec-validate` task as the final pipeline stage. - Builds that fail policy are marked `Failed` with a compliance-specific @@ -123,18 +123,19 @@ will be an optional, cluster-level configuration: ```go type ComplianceConfig struct { - Enabled bool `json:"enabled"` - SBOMFormat string `json:"sbomFormat,omitempty"` - SBOMTaskBundle string `json:"sbomTaskBundle,omitempty"` - ECPolicyRef string `json:"ecPolicyRef,omitempty"` - TrustedArtifactSignerURL string `json:"trustedArtifactSignerURL,omitempty"` - RekorURL string `json:"rekorURL,omitempty"` - FulcioURL string `json:"fulcioURL,omitempty"` + Enabled bool `json:"enabled"` + SBOMFormat string `json:"sbomFormat,omitempty"` // spdx-json (default) or cyclonedx-json + SyftImage string `json:"syftImage,omitempty"` // container image for SBOM generation + ECPolicyRef string `json:"ecPolicyRef,omitempty"` // Enterprise Contract policy reference + RekorURL string `json:"rekorURL,omitempty"` // Rekor transparency log URL + FulcioURL string `json:"fulcioURL,omitempty"` // Fulcio CA URL for keyless signing } ``` -These fields configure the cluster-wide compliance behaviour. Per-build -overrides via `spec.compliance` on individual CRs are a future consideration. +`SyftImage` defaults to `docker.io/anchore/syft:v1.22.0`. Fulcio and Rekor +URLs are passed through to Tekton Chains configuration and are not required +when the cluster already has RHTAS configured. Per-build overrides via +`spec.compliance` on individual CRs are a future consideration. ## Consequences diff --git a/internal/common/tasks/scripts/push_artifact.sh b/internal/common/tasks/scripts/push_artifact.sh index 312361e0..6ff26f5a 100644 --- a/internal/common/tasks/scripts/push_artifact.sh +++ b/internal/common/tasks/scripts/push_artifact.sh @@ -337,6 +337,10 @@ EOF # Extract digest from oras output (format: "Digest: sha256:abc123...") pushed_digest=$(echo "$push_output" | grep -i '^Digest:' | awk '{print $2}' | head -1) + if [ -z "$pushed_digest" ]; then + echo "ERROR: Failed to parse pushed digest from oras output" >&2 + exit 1 + fi printf '%s' "${repo_url}" > "$(results.IMAGE_URL.path)" printf '%s' "${pushed_digest}" > "$(results.IMAGE_DIGEST.path)" @@ -405,6 +409,10 @@ PYEOF echo "$push_output" pushed_digest=$(echo "$push_output" | grep -i '^Digest:' | awk '{print $2}' | head -1) + if [ -z "$pushed_digest" ]; then + echo "ERROR: Failed to parse pushed digest from oras output" >&2 + exit 1 + fi printf '%s' "${repo_url}" > "$(results.IMAGE_URL.path)" printf '%s' "${pushed_digest}" > "$(results.IMAGE_DIGEST.path)" diff --git a/internal/common/tasks/scripts/sbom_generate.sh b/internal/common/tasks/scripts/sbom_generate.sh index 72b8cdd8..6b1fb1c6 100644 --- a/internal/common/tasks/scripts/sbom_generate.sh +++ b/internal/common/tasks/scripts/sbom_generate.sh @@ -42,17 +42,24 @@ esac # Attach the SBOM as an OCI referrer to the original artifact echo "Attaching SBOM to ${image_ref}..." -# Use ORAS to attach the SBOM as a referrer (OCI 1.1 referrers API) -DOCKER_CONFIG="${DOCKER_CONFIG:-}" oras attach \ +attach_output=$(DOCKER_CONFIG="${DOCKER_CONFIG:-}" oras attach \ --artifact-type "${sbom_media_type}" \ "${image_ref}" \ - "${sbom_file}:${sbom_media_type}" 2>&1 || { + "${sbom_file}:${sbom_media_type}" 2>&1) || { echo "ERROR: Failed to attach SBOM to artifact" >&2 + echo "$attach_output" >&2 exit 1 } +echo "$attach_output" -# Write the SBOM reference as a result (image_url with SBOM digest) -sbom_ref="${image_url}" +# Extract SBOM referrer digest from oras attach output +sbom_digest=$(echo "$attach_output" | grep -i '^Digest:' | awk '{print $2}' | head -1) +if [ -z "$sbom_digest" ]; then + echo "ERROR: Failed to parse SBOM digest from oras attach output" >&2 + exit 1 +fi + +sbom_ref="${image_url}@${sbom_digest}" printf '%s' "${sbom_ref}" > "${result_path}" emit_progress "Generating SBOM" 2 2 diff --git a/internal/common/tasks/tasks.go b/internal/common/tasks/tasks.go index b2d1cd93..94c27e7b 100644 --- a/internal/common/tasks/tasks.go +++ b/internal/common/tasks/tasks.go @@ -1349,6 +1349,7 @@ func GenerateTektonPipeline(name, namespace string, buildConfig *BuildConfig) *t {Name: "IMAGE_URL", Value: tektonv1.ParamValue{Type: tektonv1.ParamTypeString, StringVal: "$(tasks.push-disk-artifact.results.IMAGE_URL)"}}, {Name: "IMAGE_DIGEST", Value: tektonv1.ParamValue{Type: tektonv1.ParamTypeString, StringVal: "$(tasks.push-disk-artifact.results.IMAGE_DIGEST)"}}, {Name: "sbom-format", Value: tektonv1.ParamValue{Type: tektonv1.ParamTypeString, StringVal: "$(params.sbom-format)"}}, + {Name: "secret-ref", Value: tektonv1.ParamValue{Type: tektonv1.ParamTypeString, StringVal: "$(params.secret-ref)"}}, }, Workspaces: []tektonv1.WorkspacePipelineTaskBinding{ {Name: "registry-auth", Workspace: "registry-auth"}, @@ -1425,6 +1426,15 @@ func GenerateSBOMTask(namespace string, buildConfig *BuildConfig) *tektonv1.Task StringVal: automotivev1alpha1.DefaultSBOMFormat, }, }, + { + Name: "secret-ref", + Type: tektonv1.ParamTypeString, + Description: "Name of the secret containing registry credentials", + Default: &tektonv1.ParamValue{ + Type: tektonv1.ParamTypeString, + StringVal: "", + }, + }, }, Results: []tektonv1.TaskResult{ { @@ -1461,13 +1471,17 @@ func GenerateSBOMTask(namespace string, buildConfig *BuildConfig) *tektonv1.Task Name: "RESULT_PATH", Value: "$(results.SBOM_URI.path)", }, + { + Name: "DOCKER_CONFIG", + Value: "/docker-config", + }, }, Script: SBOMGenerateScript, VolumeMounts: []corev1.VolumeMount{ { Name: "docker-config", - MountPath: "/docker-config", - ReadOnly: true, + MountPath: "/docker-config/config.json", + SubPath: ".dockerconfigjson", }, }, }, @@ -1477,7 +1491,7 @@ func GenerateSBOMTask(namespace string, buildConfig *BuildConfig) *tektonv1.Task Name: "docker-config", VolumeSource: corev1.VolumeSource{ Secret: &corev1.SecretVolumeSource{ - SecretName: "$(params.IMAGE_URL)", + SecretName: "$(params.secret-ref)", Optional: ptr.To(true), }, }, diff --git a/internal/controller/operatorconfig/controller.go b/internal/controller/operatorconfig/controller.go index d6a6743b..c702e8df 100644 --- a/internal/controller/operatorconfig/controller.go +++ b/internal/controller/operatorconfig/controller.go @@ -667,6 +667,13 @@ func (r *OperatorConfigReconciler) deployOSBuilds( if buildConfig.ComplianceEnabled { tektonTasks = append(tektonTasks, tasks.GenerateSBOMTask(config.Namespace, buildConfig)) + } else { + sbomTask := &tektonv1.Task{} + sbomTask.Name = "sbom-generate" + sbomTask.Namespace = config.Namespace + if err := r.Client.Delete(ctx, sbomTask); err != nil && !errors.IsNotFound(err) { + r.Log.Error(err, "Failed to cleanup sbom-generate task") + } } for _, task := range tektonTasks {