Shift Left – CI/CD Scanning & Signing Pipeline

Duration: ~25 minutes

Overview

Integrate security scanning and policy checks into pipelines.

Build a Tekton pipeline that builds, scans, gates, and signs an image, then deploy by digest from the signed result.

Why it matters

Unsigned, unscanned images reach production when security sits after deploy. Pipelines that build, scan, gate, and sign shrink the window where attackers swap in a poisoned tag.

What does it solve

  • Silent promotion of unscanned images

  • Deployments that float on mutable tags instead of digests

  • Missing signature evidence for admission and audit

Your Mission

Build a Tekton path that refuses unscanned or unsigned images—then deploy only by digest so a supply-chain swap cannot silently replace what you ran.

Prerequisites

  • OpenShift Pipelines (Tekton) Operator installed

  • Cosign installed locally, or simulate signing in the pipeline tasks

Click each step only if you need a hint.

Namespace & Service Account

Tekton CRDs require YAML; only typos fixed and kept minimal.

oc new-project 201-05-s-pipeline
och create sa pipeline -n 201-05-s-pipeline
oc adm policy add-scc-to-user anyuid -z pipeline -n 201-05-s-pipeline  # only if build strategy requires
Build Task (S2I or Buildah Simplified)
oc apply -n 201-05-s-pipeline -f - <<'EOF'
apiVersion: tekton.dev/v1
kind: Task
metadata:
  name: build-image
spec:
  results:
  - name: image-digest
  params:
  - name: IMAGE
  steps:
  - name: build
    image: registry.access.redhat.com/ubi9/buildah
    script: |
      #!/usr/bin/env bash
      set -e
      touch index.html; echo Hello > index.html
      buildah bud -t $(params.IMAGE) .
      buildah push $(params.IMAGE)
      DIGEST=$(skopeo inspect docker://$(params.IMAGE) | jq -r .Digest)
      echo -n "$DIGEST" > $(results.image-digest.path)
EOF
Scan Task (Simulated)
oc apply -n 201-05-s-pipeline -f - <<'EOF'
apiVersion: tekton.dev/v1
kind: Task
metadata:
  name: scan-image
spec:
  params:
  - name: IMAGE
  results:
  - name: scan-status
  steps:
  - name: scan
    image: registry.access.redhat.com/ubi9/ubi
    script: |
      #!/usr/bin/env bash
      echo "Simulating vulnerability scan"
      # set FAIL=1 to simulate failure
      if [ "$FAIL" = "1" ]; then echo -n FAIL > $(results.scan-status.path); exit 1; fi
      echo -n PASS > $(results.scan-status.path)
EOF
Sign Task (Cosign Simulated)
oc apply -n 201-05-s-pipeline -f - <<'EOF'
apiVersion: tekton.dev/v1
kind: Task
metadata:
  name: sign-image
spec:
  params:
  - name: IMAGE
  - name: DIGEST
  steps:
  - name: sign
    image: registry.access.redhat.com/ubi9/ubi
    script: |
      #!/usr/bin/env bash
      echo "Simulating cosign sign $(params.IMAGE)@$(params.DIGEST)"
      echo "signature-ok" > /tekton/home/signature.txt
EOF
Pipeline Definition
oc apply -n 201-05-s-pipeline -f - <<'EOF'
apiVersion: tekton.dev/v1
kind: Pipeline
metadata:
  name: secure-build
spec:
  params:
  - name: IMAGE
  tasks:
  - name: build
    taskRef:
      name: build-image
    params:
    - name: IMAGE
      value: $(params.IMAGE)
  - name: scan
    runAfter: [build]
    taskRef:
      name: scan-image
    params:
    - name: IMAGE
      value: $(params.IMAGE)
  - name: sign
    runAfter: [scan]
    when:
    - input: "$(tasks.scan.results.scan-status)"
      operator: In
      values: ["PASS"]
    taskRef:
      name: sign-image
    params:
    - name: IMAGE
      value: $(params.IMAGE)
    - name: DIGEST
      value: $(tasks.build.results.image-digest)
EOF
Run Pipeline
oc apply -n 201-05-s-pipeline -f - <<'EOF'
apiVersion: tekton.dev/v1
kind: PipelineRun
metadata:
  name: secure-build-run
spec:
  serviceAccountName: pipeline
  pipelineRef:
    name: secure-build
  params:
  - name: IMAGE
    value: image-registry.openshift-image-registry.svc:5000/201-05-s-pipeline/demo:latest
EOF

Monitor:

oc get pipelineruns -n 201-05-s-pipeline
och describe pipelinerun secure-build-run -n 201-05-s-pipeline | grep -i status

Get digest:

oc get pipelinerun secure-build-run -n 201-05-s-pipeline -o jsonpath='{.status.pipelineResults}'
Deployment Using Digest (Gate Simulation)

Replace <DIGEST> with extracted digest:

oc apply -n 201-05-s-pipeline -f - <<'EOF'
apiVersion: apps/v1
kind: Deployment
metadata:
  name: app
spec:
  replicas: 1
  selector:
    matchLabels: {app: demo}
  template:
    metadata:
      labels: {app: demo}
    spec:
      containers:
      - name: web
        image: image-registry.openshift-image-registry.svc:5000/201-05-s-pipeline/demo@sha256:<DIGEST>
EOF

Failure Simulation

Re-run with FAIL=1 env (edit scan task to export) and confirm sign task skipped.

Debrief

Your pipeline enforced scan-before-sign, skipped signing on failure, and deployed by digest so mutable tags cannot sneak into production.

What breaks without this:

  • Unscanned or unsigned images → known-bad artifacts reach the cluster

  • Tag-based deploys → non-reproducible, retaggable runtime

Controls that matter: Tekton (or equivalent) gates, conditional sign tasks, digest pins, and admission that extends trust (signatures/SBOM) at deploy time—see 201-11 for Gatekeeper / cosign enforcement on tag-based images.

Quick facts: the when-clause is the gate; admission and SBOM checks are the natural next layers.

giphy

Cleanup

Before moving to the next module, run the lab cleanup script to reset transient resources from this module.

cd ~/openshift-security-roadshow
bash setup/lab-cleanup.sh --module 201-05