Sub-Module 2.6: Keyless Signing with RHTAS (Optional)
Introduction
In Sub-Module 2.3, the signing step was left as a framework: cosign sign was demonstrated but not executed in-cluster because it requires either long-lived key files or an enterprise signing infrastructure. This sub-module fills that gap with Red Hat Trusted Artifact Signer (RHTAS).
With standalone cosign you manage long-lived private keys. With RHTAS you get keyless signing: Tekton tasks authenticate via their Kubernetes service account OIDC token, Fulcio issues a short-lived certificate for that identity, the signature goes into Rekor’s tamper-evident log, and TUF distributes the trust root. No key files to rotate or leak.
What RHTAS Actually Is
RHTAS is a production-ready deployment of the Sigstore project within an enterprise. Sigstore has become the de-facto signing system for containers — Kubernetes standardised on it, and multiple Red Hat products adopt it including Podman, Quay, and Red Hat Trusted Content.
The operator deploys four services, each a trusted root target in TUF:
-
Fulcio — the certificate authority (issues short-lived signing certs)
-
Rekor — the transparency log (append-only, tamper-evident)
-
CTlog — the certificate transparency log
-
TUF — The Update Framework (distributes trust root to verifiers)
Client software like cosign uses these trust root targets to sign and verify artifact signatures.
[Developer / Tekton SA] | | OIDC token (projected) v [Fulcio] -- issues short-lived certificate | v [cosign sign] -- keyless, cert-based | v [Rekor] -- records signature in transparency log | v [TUF] -- distributes trust root to verifiers
|
No key files anywhere in this workflow. The signing identity for a Tekton task is a URI — |
Prerequisites
-
Appendix B environment setup complete, which provides:
-
Completion of Sub-Module 2.3 (Security Pipeline) and Sub-Module 2.5 (chunkah Pipeline)
-
Red Hat OpenShift Container Platform 4.16 or later
-
cluster-adminprivileges
| Mode | Storage | RAM | CPU |
|---|---|---|---|
Dedicated (production) |
5 GB |
1 GB |
2 cores |
Managed (non-production) |
10 GB |
2 GB |
4 cores |
Exercise 1: Deploy the Securesign Stack
Verify the RHTAS operator is installed and ready before proceeding:
oc get csv -n trusted-artifact-signer | grep rhtas
NAME DISPLAY VERSION REPLACES PHASE rhtas-operator.v1.x.x Red Hat Trusted Artifact Signer 1.x.x Succeeded
|
If the RHTAS operator is not yet installed, complete Appendix B Step 24 first. The operator, Keycloak realm, and CLI tools are all provisioned as part of the platform environment setup. |
Step 1: Deploy the Securesign Stack
Deploy the full Sigstore stack by creating a Securesign custom resource. The Securesign CR configures Fulcio with two OIDC issuers: one for human users (Keycloak/RHBK) and one for Kubernetes service accounts (enabling keyless pipeline signing):
KEYCLOAK_URL=$(oc get route -n keycloak sso -o jsonpath='{.spec.host}')
cat << EOF | oc apply -f -
apiVersion: rhtas.redhat.com/v1alpha1
kind: Securesign
metadata:
name: securesign-sample
namespace: trusted-artifact-signer
spec:
rekor:
externalAccess:
enabled: true
monitoring:
enabled: false
fulcio:
externalAccess:
enabled: true
config:
OIDCIssuers:
- ClientID: "trusted-artifact-signer"
IssuerURL: "https://${KEYCLOAK_URL}/realms/trusted-artifact-signer"
Issuer: "https://${KEYCLOAK_URL}/realms/trusted-artifact-signer"
Type: "email"
- ClientID: "trusted-artifact-signer"
IssuerURL: "https://kubernetes.default.svc"
Issuer: "https://kubernetes.default.svc"
Type: "kubernetes"
certificate:
organizationName: Red Hat
organizationEmail: platform@redhat.com
commonName: fulcio
monitoring:
enabled: false
tuf:
externalAccess:
enabled: true
ctlog: {}
EOF
|
You can define several different OIDC providers in the same configuration. If you do not need an OIDC provider for human users, you can remove the Keycloak issuer and keep only the Kubernetes issuer for pipeline signing. If you prefer a different database rather than the default, set
|
Step 2: Watch Until All Components Are Ready
Monitor the deployment status of all Sigstore components:
oc get fulcio,rekor,tuf,ctlog \
-n trusted-artifact-signer
NAME STATUS fulcio.rhtas.redhat.com/securesign-sample Ready NAME STATUS rekor.rhtas.redhat.com/securesign-sample Ready NAME STATUS tuf.rhtas.redhat.com/securesign-sample Ready NAME STATUS ctlog.rhtas.redhat.com/securesign-sample Ready
|
The Securesign instance itself does not report a status. Watch for the individual component CRs (Fulcio, Rekor, TUF, CTlog) to reach |
Step 3: Configure Shell Environment
Set up the environment variables that cosign and other tools need. These use oc get to pull the live URLs from the operator-managed resources:
export TUF_URL=$(oc get tuf -o jsonpath='{.items[0].status.url}' -n trusted-artifact-signer)
export COSIGN_FULCIO_URL=$(oc get fulcio -o jsonpath='{.items[0].status.url}' -n trusted-artifact-signer)
export COSIGN_REKOR_URL=$(oc get rekor -o jsonpath='{.items[0].status.url}' -n trusted-artifact-signer)
export COSIGN_MIRROR=$TUF_URL
export COSIGN_ROOT=$TUF_URL/root.json
export COSIGN_OIDC_CLIENT_ID="trusted-artifact-signer"
export COSIGN_OIDC_ISSUER="https://kubernetes.default.svc"
export COSIGN_CERTIFICATE_OIDC_ISSUER="https://kubernetes.default.svc"
export COSIGN_YES="true"
export SIGSTORE_FULCIO_URL=$COSIGN_FULCIO_URL
export SIGSTORE_OIDC_ISSUER=$COSIGN_OIDC_ISSUER
export SIGSTORE_REKOR_URL=$COSIGN_REKOR_URL
export REKOR_REKOR_SERVER=$COSIGN_REKOR_URL
Initialise The Update Framework (TUF) system:
cosign initialize --mirror="$TUF_URL" --root="$TUF_URL/root.json"
|
Two OIDC issuers in the Fulcio config serve different purposes:
Both identities appear in the Fulcio-issued certificate’s Subject Alternative Name (SAN), providing full audit trail in Rekor. |
Exercise 2: Configure Tekton for Keyless Signing
Step 4: Create the Signing ServiceAccount
Tekton tasks need a service account whose projected OIDC token Fulcio will accept as an identity:
cat << 'EOF' | oc apply -f -
apiVersion: v1
kind: ServiceAccount
metadata:
name: rhtas-signer
namespace: hummingbird-builds-{user}
annotations:
rhtas.redhat.com/signing-identity: "true"
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: rhtas-signer-pipeline-binding
namespace: hummingbird-builds-{user}
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: tekton-aggregate-edit
subjects:
- kind: ServiceAccount
name: rhtas-signer
namespace: hummingbird-builds-{user}
EOF
serviceaccount/rhtas-signer created rolebinding.rbac.authorization.k8s.io/rhtas-signer-pipeline-binding created
The OIDC token gets projected into the task pod automatically by Kubernetes; no manual token handling is needed in your Task scripts. The signing identity URI — https://kubernetes.io/namespaces/hummingbird-builds-{user}/serviceaccounts/rhtas-signer — appears in the Fulcio-issued certificate’s SAN and is what you verify against later.
Step 5: Create the RHTAS Endpoints ConfigMap
This ConfigMap is created once and referenced by every signing Task via envFrom. Pull the URLs dynamically from the operator-managed resources:
NS=trusted-artifact-signer
FULCIO_URL=$(oc get fulcio -n $NS -o jsonpath='{.items[0].status.url}')
REKOR_URL=$(oc get rekor -n $NS -o jsonpath='{.items[0].status.url}')
TUF_URL=$(oc get tuf -n $NS -o jsonpath='{.items[0].status.url}')
QUAY_URL=$(oc get route -n quay quay-registry-quay -o jsonpath='{.spec.host}')
oc create configmap rhtas-config \
-n hummingbird-builds-{user} \
--from-literal=FULCIO_URL="$FULCIO_URL" \
--from-literal=REKOR_URL="$REKOR_URL" \
--from-literal=TUF_URL="$TUF_URL" \
--from-literal=QUAY_URL="$QUAY_URL" \
--dry-run=client -o yaml | oc apply -f -
configmap/rhtas-config created
|
Re-run this command any time you redeploy the Securesign stack. It always captures the live URLs from the operator-managed routes. |
Exercise 3: The Signing Tasks
This exercise creates three Tekton Tasks that form the signing portion of the pipeline: SBOM generation, keyless signing with attestation, and policy verification.
Step 6: Task 1 — Generate SBOM with Syft
cat << 'EOF' | oc apply -f -
apiVersion: tekton.dev/v1beta1
kind: Task
metadata:
name: generate-sbom
namespace: hummingbird-builds-{user}
spec:
params:
- name: IMAGE
description: Fully qualified image reference (registry/name:tag)
- name: OUTPUT_FORMAT
default: cyclonedx-json
results:
- name: SBOM_PATH
- name: SBOM_DIGEST
workspaces:
- name: output
steps:
- name: generate
image: ghcr.io/anchore/syft:latest
script: |
#!/bin/bash
set -euo pipefail
SBOM_PATH="$(workspaces.output.path)/sbom.cyclonedx.json"
syft scan \
"$(params.IMAGE)" \
--output "cyclonedx-json=${SBOM_PATH}"
if [ -d "$(workspaces.output.path)/source" ]; then
syft scan \
"dir:$(workspaces.output.path)/source" \
--output "cyclonedx-json=$(workspaces.output.path)/sbom-source.cyclonedx.json"
fi
DIGEST=$(sha256sum "$SBOM_PATH" | cut -d' ' -f1)
echo -n "$SBOM_PATH" > $(results.SBOM_PATH.path)
echo -n "sha256:$DIGEST" > $(results.SBOM_DIGEST.path)
echo "SBOM generated: $SBOM_PATH"
echo "Components: $(jq '.components | length' "$SBOM_PATH")"
EOF
task.tekton.dev/generate-sbom created
The Task scans the built image via OCI and also scans the source workspace (if present) to catch lockfile dependencies not visible in the final image layers. Both SBOM path and digest are exported as Task results.
Step 7: Task 2 — Sign Image and Attest SBOM (Keyless)
This is the core RHTAS Task. It initialises the enterprise TUF root, signs the image keylessly, attaches the SBOM as an OCI referrer, creates a signed in-toto attestation in Rekor, and captures the Fulcio certificate for audit:
cat << 'EOF' | oc apply -f -
apiVersion: tekton.dev/v1beta1
kind: Task
metadata:
name: rhtas-sign-and-attest
namespace: hummingbird-builds-{user}
spec:
params:
- name: IMAGE
description: Fully qualified image with digest (name@sha256:...)
- name: SBOM_PATH
description: Path to the CycloneDX SBOM JSON file
results:
- name: REKOR_UUID
description: UUID of the entry in the Rekor transparency log
- name: SIGNING_CERT
description: Base64-encoded short-lived cert issued by Fulcio
workspaces:
- name: artifacts
volumes:
- name: oidc-token
projected:
sources:
- serviceAccountToken:
path: oidc-token
expirationSeconds: 600
audience: "trusted-artifact-signer"
steps:
- name: initialize-tuf
image: registry.access.redhat.com/ubi9/ubi-minimal:latest
envFrom:
- configMapRef:
name: rhtas-config
script: |
#!/bin/bash
set -euo pipefail
CLI_SERVER=$(oc get route -n trusted-artifact-signer -l app.kubernetes.io/name=cli-server \
-o jsonpath='{.items[0].spec.host}' 2>/dev/null || echo "")
curl -sSL "https://${CLI_SERVER}/clients/linux/cosign-amd64.gz" \
| gunzip > /usr/local/bin/cosign && chmod +x /usr/local/bin/cosign
cosign initialize \
--mirror="${TUF_URL}" \
--root="${TUF_URL}/root.json"
echo "TUF initialized against ${TUF_URL}"
- name: sign-image
image: registry.access.redhat.com/ubi9/ubi-minimal:latest
envFrom:
- configMapRef:
name: rhtas-config
env:
- name: COSIGN_YES
value: "true"
volumeMounts:
- name: oidc-token
mountPath: /var/run/secrets/oidc
script: |
#!/bin/bash
set -euo pipefail
cosign initialize --mirror="${TUF_URL}" --root="${TUF_URL}/root.json"
cosign sign \
--fulcio-url="${FULCIO_URL}" \
--rekor-url="${REKOR_URL}" \
--identity-token="$(cat /var/run/secrets/oidc/oidc-token)" \
--yes \
"$(params.IMAGE)"
echo "Image signed: $(params.IMAGE)"
- name: attest-sbom
image: registry.access.redhat.com/ubi9/ubi-minimal:latest
envFrom:
- configMapRef:
name: rhtas-config
env:
- name: COSIGN_YES
value: "true"
volumeMounts:
- name: oidc-token
mountPath: /var/run/secrets/oidc
script: |
#!/bin/bash
set -euo pipefail
cosign initialize --mirror="${TUF_URL}" --root="${TUF_URL}/root.json"
SBOM_FILE="$(params.SBOM_PATH)"
cosign attach sbom \
--sbom "$SBOM_FILE" \
--type cyclonedx \
"$(params.IMAGE)"
REKOR_OUTPUT=$(cosign attest \
--fulcio-url="${FULCIO_URL}" \
--rekor-url="${REKOR_URL}" \
--identity-token="$(cat /var/run/secrets/oidc/oidc-token)" \
--predicate "$SBOM_FILE" \
--type cyclonedx \
--yes \
"$(params.IMAGE)" 2>&1)
echo "$REKOR_OUTPUT"
REKOR_UUID=$(echo "$REKOR_OUTPUT" \
| grep -oP 'tlog entry created with index: \K[0-9]+' \
|| echo "see-rekor-log")
echo -n "$REKOR_UUID" > $(results.REKOR_UUID.path)
- name: capture-cert
image: registry.access.redhat.com/ubi9/ubi-minimal:latest
envFrom:
- configMapRef:
name: rhtas-config
script: |
#!/bin/bash
set -euo pipefail
cosign initialize --mirror="${TUF_URL}" --root="${TUF_URL}/root.json"
CERT=$(cosign verify \
--rekor-url="${REKOR_URL}" \
--certificate-oidc-issuer="https://kubernetes.default.svc" \
--certificate-identity-regexp=".*rhtas-signer.*" \
"$(params.IMAGE)" 2>/dev/null \
| jq -r '.[0].optional.Bundle.Payload.body' \
| base64 -d \
| jq -r '.spec.signature.publicKey.content' \
|| echo "")
echo -n "${CERT:-not-captured}" > $(results.SIGNING_CERT.path)
EOF
task.tekton.dev/rhtas-sign-and-attest created
-
initialize-tuf — Points cosign at the enterprise TUF root instead of the public sigstore.dev
-
sign-image — Keyless sign using the projected OIDC token; Fulcio issues a short-lived certificate bound to the service account identity
-
attest-sbom — Attaches the SBOM as an OCI referrer, then creates a signed in-toto attestation linking the SBOM to the image by digest, recorded in Rekor
-
capture-cert — Retrieves the Fulcio certificate used for signing (useful for audit: proves which SA signed, at what time)
|
The OIDC token volume projection is the key mechanism. Kubernetes projects a short-lived service account token with the |
Step 8: Task 3 — Verify with Conforma (Enterprise Contract)
Conforma (formerly Enterprise Contract) is a tool for maintaining the security of software supply chains. You can use the ec binary to verify the attestation and signature of container images that use the RHTAS signing framework. It generates a pass-fail report with details on any security violations, and when you add the --info flag, the report includes more details and possible solutions for any violations:
cat << 'EOF' | oc apply -f -
apiVersion: tekton.dev/v1beta1
kind: Task
metadata:
name: conforma-verify
namespace: hummingbird-builds-{user}
spec:
params:
- name: IMAGE
- name: POLICY_SOURCE
default: "github.com/enterprise-contract/ec-policies//policy/release?ref=main"
results:
- name: EC_RESULT
- name: VIOLATIONS
workspaces:
- name: output
steps:
- name: verify
image: registry.access.redhat.com/ubi9/ubi-minimal:latest
envFrom:
- configMapRef:
name: rhtas-config
script: |
#!/bin/bash
set -euo pipefail
CLI_SERVER=$(oc get route -n trusted-artifact-signer -l app.kubernetes.io/name=cli-server \
-o jsonpath='{.items[0].spec.host}' 2>/dev/null || echo "")
curl -sSL "https://${CLI_SERVER}/clients/linux/ec-amd64.gz" \
| gunzip > /usr/local/bin/ec && chmod +x /usr/local/bin/ec
RESULT_FILE="$(workspaces.output.path)/ec-result.yaml"
ec validate image \
--image "$(params.IMAGE)" \
--policy "$(params.POLICY_SOURCE)" \
--certificate-oidc-issuer="https://kubernetes.default.svc" \
--certificate-identity-regexp=".*rhtas-signer.*" \
--rekor-url="${REKOR_URL}" \
--output yaml \
--show-successes \
> "$RESULT_FILE" 2>&1 || true
EC_SUCCESS=$(grep "^success:" "$RESULT_FILE" | awk '{print $2}' || echo "false")
echo -n "$EC_SUCCESS" > $(results.EC_RESULT.path)
if [ "$EC_SUCCESS" != "true" ]; then
VIOLATIONS=$(grep "msg:" "$RESULT_FILE" | head -10 || echo "check result file")
echo -n "$VIOLATIONS" > $(results.VIOLATIONS.path)
echo "Conforma verification result:"
cat "$RESULT_FILE"
else
echo "Conforma verification PASSED"
echo -n "" > $(results.VIOLATIONS.path)
fi
EOF
task.tekton.dev/conforma-verify created
success: true
successes:
- metadata:
code: builtin.attestation.signature_check
msg: Pass
- metadata:
code: builtin.attestation.syntax_check
msg: Pass
- metadata:
code: builtin.image.signature_check
msg: Pass
|
Conforma is the policy gate. It verifies that:
If Conforma fails, the pipeline fails — the image never gets promoted to production. |
Exercise 4: The Complete Pipeline
This pipeline ties together every stage: clone, build, chunkah layer splitting, SBOM generation, RHTAS keyless signing, and Conforma verification.
Step 10: Apply the Pipeline
cat << 'EOF' | oc apply -f -
apiVersion: tekton.dev/v1beta1
kind: Pipeline
metadata:
name: hummingbird-rhtas-pipeline
namespace: hummingbird-builds-{user}
spec:
params:
- name: IMAGE
- name: GIT_URL
- name: GIT_REVISION
default: main
workspaces:
- name: source
- name: artifacts
tasks:
- name: git-clone
taskRef:
resolver: cluster
params:
- name: kind
value: task
- name: name
value: git-clone
- name: namespace
value: openshift-pipelines
params:
- name: url
value: $(params.GIT_URL)
- name: revision
value: $(params.GIT_REVISION)
workspaces:
- name: output
workspace: source
- name: buildah-build
taskRef:
resolver: cluster
params:
- name: kind
value: task
- name: name
value: buildah
- name: namespace
value: openshift-pipelines
runAfter: [git-clone]
params:
- name: IMAGE
value: $(params.IMAGE)
- name: DOCKERFILE
value: ./Containerfile
- name: BUILD_EXTRA_ARGS
value: "--format oci --skip-unused-stages=false"
workspaces:
- name: source
workspace: source
- name: chunkah-split
taskRef:
name: chunkah-split-task
runAfter: [buildah-build]
params:
- name: SOURCE_IMAGE
value: $(params.IMAGE)
- name: OUTPUT_IMAGE
value: $(params.IMAGE)
- name: MAX_LAYERS
value: "48"
- name: generate-sbom
taskRef:
name: generate-sbom
runAfter: [chunkah-split]
params:
- name: IMAGE
value: $(params.IMAGE)
workspaces:
- name: output
workspace: artifacts
- name: rhtas-sign
taskRef:
name: rhtas-sign-and-attest
runAfter: [generate-sbom]
params:
- name: IMAGE
value: $(params.IMAGE)
- name: SBOM_PATH
value: $(tasks.generate-sbom.results.SBOM_PATH)
workspaces:
- name: artifacts
workspace: artifacts
- name: conforma-verify
taskRef:
name: conforma-verify
runAfter: [rhtas-sign]
params:
- name: IMAGE
value: $(params.IMAGE)
workspaces:
- name: output
workspace: artifacts
finally:
- name: report-summary
taskRef:
resolver: cluster
params:
- name: kind
value: task
- name: name
value: git-cli
- name: namespace
value: openshift-pipelines
params:
- name: GIT_SCRIPT
value: |
echo "=== RHTAS Pipeline Summary ==="
echo "Image: $(params.IMAGE)"
echo "Rekor UUID: $(tasks.rhtas-sign.results.REKOR_UUID)"
echo "Conforma: $(tasks.conforma-verify.results.EC_RESULT)"
workspaces:
- name: source
workspace: source
EOF
pipeline.tekton.dev/hummingbird-rhtas-pipeline created
Pipeline Flow
[Git Clone] -> [Buildah Build] -> [chunkah Split] | v [Generate SBOM] -> [RHTAS Sign + Attest] -> [Conforma Verify] | v [finally: Report Summary]
|
The |
Verifying a Signed Image
After a pipeline run, verification from any workstation that has TUF initialised:
Step 11: Initialise the Trust Root and Configure Environment
export TUF_URL=$(oc get tuf -o jsonpath='{.items[0].status.url}' -n trusted-artifact-signer)
export COSIGN_FULCIO_URL=$(oc get fulcio -o jsonpath='{.items[0].status.url}' -n trusted-artifact-signer)
export COSIGN_REKOR_URL=$(oc get rekor -o jsonpath='{.items[0].status.url}' -n trusted-artifact-signer)
export COSIGN_MIRROR=$TUF_URL
export COSIGN_ROOT=$TUF_URL/root.json
export COSIGN_OIDC_CLIENT_ID="trusted-artifact-signer"
export COSIGN_YES="true"
cosign initialize --mirror="$TUF_URL" --root="$TUF_URL/root.json"
Step 12: Verify the Image Signature
IMAGE=quay-registry-quay-quay.apps.cluster-{guid}.example.com/workshopuser/secure-nodejs:latest
cosign verify \
--certificate-identity-regexp=".*rhtas-signer.*" \
--certificate-oidc-issuer="https://kubernetes.default.svc" \
"$IMAGE" | jq .
[
{
"critical": {
"identity": { "docker-reference": "quay-registry-quay-quay.apps.cluster-{guid}.example.com/workshopuser/secure-nodejs" },
"image": { "docker-manifest-digest": "sha256:abc123..." }
},
"optional": {
"Bundle": { ... },
"Issuer": "https://kubernetes.default.svc",
"Subject": "https://kubernetes.io/namespaces/hummingbird-builds-{user}/serviceaccounts/rhtas-signer"
}
}
]
|
You can also use |
Step 13: View Supply Chain Artifacts
Use cosign tree to see the full supply chain artifact tree for an image — signatures and attestations:
cosign tree "$IMAGE"
📦 Supply Chain Security Related artifacts for an image: registry.internal.example.com/apps/my-app@sha256:abc123... └── 💾 Attestations for an image tag: ...sha256-abc123.att └── 🍒 sha256:def456... └── 🔐 Signatures for an image tag: ...sha256-abc123.sig └── 🍒 sha256:ghi789...
Step 14: Inspect the SBOM Attestation
cosign verify-attestation \
--certificate-oidc-issuer="https://kubernetes.default.svc" \
--certificate-identity-regexp=".*rhtas-signer.*" \
--type cyclonedx \
"$IMAGE" \
| jq '.payload | @base64d | fromjson | .predicate.components[].name'
"express" "accepts" "nodejs" ...
Step 15: Query Rekor Directly
rekor-cli search \
--sha "$(skopeo inspect --format '{{.Digest}}' docker://$IMAGE)" \
--rekor_server "$COSIGN_REKOR_URL" \
--format json | jq
This returns every transparency log entry related to the image — signatures, attestations, and timestamps — providing a complete audit trail.
Step 16: Verify with Conforma from the Command Line
ec validate image \
--image "$IMAGE" \
--certificate-identity-regexp=".*rhtas-signer.*" \
--certificate-oidc-issuer-regexp="kubernetes" \
--output yaml \
--show-successes
success: true
successes:
- metadata:
code: builtin.attestation.signature_check
msg: Pass
- metadata:
code: builtin.attestation.syntax_check
msg: Pass
- metadata:
code: builtin.image.signature_check
msg: Pass
Bare cosign vs RHTAS Operator
| Concern | Bare cosign | RHTAS operator |
|---|---|---|
Key management |
You manage |
No keys — Fulcio issues short-lived certs |
Identity |
Key-based (who has the key?) |
OIDC-based (which SA, which user?) |
Audit trail |
Your registry only |
Rekor transparency log — append-only, auditable |
Trust distribution |
Share |
TUF root — clients auto-update trust material |
Air-gap |
Works — self-contained |
Works — RHTAS runs fully on-cluster |
Policy enforcement |
Manual |
Conforma with OPA/Rego policies |
Certificate rotation |
Manual key rotation |
Automatic — each signing event gets a fresh cert |
Managing private keys is difficult, revoking them in a controlled fashion even more so, and integrating them into DevSecOps pipelines can become a pain. RHTAS resolves this by letting developers focus on development rather than key management.
Summary
✅ Deployed the Securesign stack (Fulcio, Rekor, TUF, CTlog) with Keycloak and Kubernetes OIDC issuers
✅ Configured shell environment with dynamically resolved RHTAS endpoints
✅ Created a Tekton ServiceAccount with OIDC token projection for Fulcio
✅ Created the rhtas-config ConfigMap from live operator state
✅ Created a Tekton Task to generate CycloneDX SBOMs with Syft
✅ Created a keyless signing Task that uses Fulcio certs and Rekor transparency logging
✅ Created a Conforma Task for supply chain policy enforcement
✅ Assembled the complete hummingbird-rhtas-pipeline with all stages
✅ Verified image signatures and SBOM attestations against Rekor
✅ Verified the supply chain artifact tree with cosign tree
✅ Understood the key differences between bare cosign and RHTAS
Keyless Signing: * No key files to generate, rotate, distribute, or revoke * Each signing event gets a fresh short-lived Fulcio certificate * The signing identity is a Kubernetes SA URI, not a cryptographic key * Every signature is recorded in Rekor’s tamper-evident transparency log
Conforma (Enterprise Contract): * Policy-as-code verification for your supply chain * Validates signatures, attestations, and provenance in one step * Blocks non-compliant images from reaching production * Integrates naturally into the Tekton pipeline as a gate
Production Benefits:
* Full audit trail: which SA signed which image, when, with what SBOM
* TUF auto-distributes trust material — no manual cosign.pub sharing
* Air-gap compatible — the entire RHTAS stack runs on-cluster
* Combines with chunkah (Sub-Module 2.5) for optimised, signed, attested images
Troubleshooting
Issue: Securesign components not reaching Ready
oc describe securesign securesign-sample -n trusted-artifact-signer
oc get events -n trusted-artifact-signer --sort-by=.metadata.creationTimestamp
Issue: Fulcio rejects the OIDC token
oc get sa rhtas-signer -n hummingbird-builds-{user} -o yaml
oc logs -l app.kubernetes.io/component=fulcio -n trusted-artifact-signer
Issue: cosign sign fails with certificate errors
cosign initialize --mirror="$TUF_URL" --root="$TUF_URL/root.json"
oc get configmap rhtas-config -n hummingbird-builds-{user} -o yaml
Issue: Conforma reports violations
ec validate image \
--image "$IMAGE" \
--policy "$POLICY_SOURCE" \
--rekor-url="$COSIGN_REKOR_URL" \
--output yaml \
--info