# Stella Ops CI Recipes — (2025‑08‑04) ## 0 · Key variables (export these once) | Variable | Meaning | Typical value | | ------------- | --------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------- | | `STELLA_URL` | Host that: ① stores the **CLI** & **SBOM‑builder** images under `/registry` **and** ② receives API calls at `https://$STELLA_URL` | `stella-ops.ci.acme.example` | | `DOCKER_HOST` | How containers reach your Docker daemon (because we no longer mount `/var/run/docker.sock`) | `tcp://docker:2375` | | `WORKSPACE` | Directory where the pipeline stores artefacts (SBOM file) | `$(pwd)` | | `IMAGE` | The image you are building & scanning | `acme/backend:sha-${COMMIT_SHA}` | | `SBOM_FILE` | Immutable SBOM name – `‑YYYYMMDDThhmmssZ.sbom.json` | `acme_backend_sha‑abc123‑20250804T153050Z.sbom.json` | ```bash export STELLA_URL="stella-ops.ci.acme.example" export DOCKER_HOST="tcp://docker:2375" # Jenkins/Circle often expose it like this export WORKSPACE="$(pwd)" export IMAGE="acme/backend:sha-${COMMIT_SHA}" export SBOM_FILE="$(echo "${IMAGE}" | tr '/:+' '__')-$(date -u +%Y%m%dT%H%M%SZ).sbom.json" ``` --- ## 1 · SBOM creation strategies ### Option A – **Buildx attested SBOM** (preferred if you can use BuildKit) You pass **two build args** so the Dockerfile can run the builder and copy the result out of the build context. ```bash docker buildx build \ --build-arg STELLA_SBOM_BUILDER="$STELLA_URL/registry/stella-sbom-builder:latest" \ --provenance=true --sbom=true \ --build-arg SBOM_FILE="$SBOM_FILE" \ -t "$IMAGE" . ``` **If you **cannot** use Buildx, use Option B below.** The older “run a builder stage inside the Dockerfile” pattern is unreliable for producing an SBOM of the final image. ```Dockerfile ARG STELLA_SBOM_BUILDER ARG SBOM_FILE FROM $STELLA_SBOM_BUILDER as sbom ARG IMAGE ARG SBOM_FILE RUN $STELLA_SBOM_BUILDER build --image $IMAGE --output /out/$SBOM_FILE # ---- actual build stages … ---- FROM alpine:3.20 COPY --from=sbom /out/$SBOM_FILE / # (optional) keep or discard # (rest of your Dockerfile) ``` ### Option B – **External builder step** (works everywhere; recommended baseline if Buildx isn’t available) *(keep this block if your pipeline already has an image‑build step that you can’t modify)* ```bash docker run --rm \ -e DOCKER_HOST="$DOCKER_HOST" \ # let builder reach the daemon remotely -v "$WORKSPACE:/workspace" \ # place SBOM beside the source code "$STELLA_URL/registry/stella-sbom-builder:latest" \ build --image "$IMAGE" --output "/workspace/${SBOM_FILE}" ``` --- ## 2 · Scan the image & upload results ```bash docker run --rm \ -e DOCKER_HOST="$DOCKER_HOST" \ # remote‑daemon pointer -v "$WORKSPACE/${SBOM_FILE}:/${SBOM_FILE}:ro" \ # mount SBOM under same name at container root -e STELLA_OPS_URL="https://${STELLA_URL}" \ # where the CLI posts findings "$STELLA_URL/registry/stella-cli:latest" \ scan --sbom "/${SBOM_FILE}" "$IMAGE" ``` The CLI returns **exit 0** if policies pass, **>0** if blocked — perfect for failing the job. --- ## 3 · CI templates Below are minimal, cut‑and‑paste snippets. **Feel free to delete Option B** if you adopt Option A. ### 3.1 Jenkins (Declarative Pipeline) ```groovy pipeline { agent { docker { image 'docker:25' args '--privileged' } } // gives us /usr/bin/docker environment { STELLA_URL = 'stella-ops.ci.acme.example' DOCKER_HOST = 'tcp://docker:2375' IMAGE = "acme/backend:${env.BUILD_NUMBER}" SBOM_FILE = "acme_backend_${env.BUILD_NUMBER}-${new Date().format('yyyyMMdd\'T\'HHmmss\'Z\'', TimeZone.getTimeZone('UTC'))}.sbom.json" } stages { stage('Build image + SBOM (Option A)') { steps { sh ''' docker build \ --build-arg STELLA_SBOM_BUILDER="$STELLA_URL/registry/stella-sbom-builder:latest" \ --build-arg SBOM_FILE="$SBOM_FILE" \ -t "$IMAGE" . ''' } } /* ---------- Option B fallback (when you must keep the existing build step as‑is) ---------- stage('SBOM builder (Option B)') { steps { sh ''' docker run --rm -e DOCKER_HOST="$DOCKER_HOST" \ -v "$WORKSPACE:/workspace" \ "$STELLA_URL/registry/stella-sbom-builder:latest" \ build --image "$IMAGE" --output "/workspace/${SBOM_FILE}" ''' } } ------------------------------------------------------------------------------------------ */ stage('Scan & upload') { steps { sh ''' docker run --rm -e DOCKER_HOST="$DOCKER_HOST" \ -v "$WORKSPACE/${SBOM_FILE}:/${SBOM_FILE}:ro" \ -e STELLA_OPS_URL="https://$STELLA_URL" \ "$STELLA_URL/registry/stella-cli:latest" \ scan --sbom "/${SBOM_FILE}" "$IMAGE" ''' } } } } ``` --- ### 3.2 CircleCI `.circleci/config.yml` ```yaml version: 2.1 jobs: stella_scan: docker: - image: cimg/base:stable # baremetal image with Docker CLI environment: STELLA_URL: stella-ops.ci.acme.example DOCKER_HOST: tcp://docker:2375 # Circle’s “remote Docker” socket steps: - checkout - run: name: Compute vars command: | echo 'export IMAGE="acme/backend:${CIRCLE_SHA1}"' >> $BASH_ENV echo 'export SBOM_FILE="$(echo acme/backend:${CIRCLE_SHA1} | tr "/:+" "__")-$(date -u +%Y%m%dT%H%M%SZ).sbom.json"' >> $BASH_ENV - run: name: Build image + SBOM (Option A) command: | docker build \ --build-arg STELLA_SBOM_BUILDER="$STELLA_URL/registry/stella-sbom-builder:latest" \ --build-arg SBOM_FILE="$SBOM_FILE" \ -t "$IMAGE" . # --- Option B fallback (when you must keep the existing build step as‑is) --- #- run: # name: SBOM builder (Option B) # command: | # docker run --rm -e DOCKER_HOST="$DOCKER_HOST" \ # -v "$PWD:/workspace" \ # "$STELLA_URL/registry/stella-sbom-builder:latest" \ # build --image "$IMAGE" --output "/workspace/${SBOM_FILE}" - run: name: Scan command: | docker run --rm -e DOCKER_HOST="$DOCKER_HOST" \ -v "$PWD/${SBOM_FILE}:/${SBOM_FILE}:ro" \ -e STELLA_OPS_URL="https://$STELLA_URL" \ "$STELLA_URL/registry/stella-cli:latest" \ scan --sbom "/${SBOM_FILE}" "$IMAGE" workflows: stella: jobs: [stella_scan] ``` --- ### 3.3 Gitea Actions `.gitea/workflows/stella.yml` *(Gitea 1.22+ ships native Actions compatible with GitHub syntax)* ```yaml name: Stella Scan on: [push] jobs: stella: runs-on: ubuntu-latest env: STELLA_URL: ${{ secrets.STELLA_URL }} DOCKER_HOST: tcp://docker:2375 # provided by the docker:dind service services: docker: image: docker:dind options: >- --privileged steps: - uses: actions/checkout@v4 - name: Compute vars id: vars run: | echo "IMAGE=ghcr.io/${{ gitea.repository }}:${{ gitea.sha }}" >> $GITEA_OUTPUT echo "SBOM_FILE=$(echo ghcr.io/${{ gitea.repository }}:${{ gitea.sha }} | tr '/:+' '__')-$(date -u +%Y%m%dT%H%M%SZ).sbom.json" >> $GITEA_OUTPUT - name: Build image + SBOM (Option A) run: | docker build \ --build-arg STELLA_SBOM_BUILDER="${STELLA_URL}/registry/stella-sbom-builder:latest" \ --build-arg SBOM_FILE="${{ steps.vars.outputs.SBOM_FILE }}" \ -t "${{ steps.vars.outputs.IMAGE }}" . # --- Option B fallback (when you must keep the existing build step as‑is) --- #- name: SBOM builder (Option B) # run: | # docker run --rm -e DOCKER_HOST="$DOCKER_HOST" \ # -v "$(pwd):/workspace" \ # "${STELLA_URL}/registry/stella-sbom-builder:latest" \ # build --image "${{ steps.vars.outputs.IMAGE }}" --output "/workspace/${{ steps.vars.outputs.SBOM_FILE }}" - name: Scan run: | docker run --rm -e DOCKER_HOST="$DOCKER_HOST" \ -v "$(pwd)/${{ steps.vars.outputs.SBOM_FILE }}:/${{ steps.vars.outputs.SBOM_FILE }}:ro" \ -e STELLA_OPS_URL="https://${STELLA_URL}" \ "${STELLA_URL}/registry/stella-cli:latest" \ scan --sbom "/${{ steps.vars.outputs.SBOM_FILE }}" "${{ steps.vars.outputs.IMAGE }}" ``` --- ## 4 · Troubleshooting cheat‑sheet | Symptom | Root cause | First things to try | | ------------------------------------- | --------------------------- | --------------------------------------------------------------- | | `no such host $STELLA_URL` | DNS typo or VPN outage | `ping $STELLA_URL` from runner | | `connection refused` when CLI uploads | Port 443 blocked | open firewall / check ingress | | `failed to stat /.json` | SBOM wasn’t produced | Did Option A actually run builder? If not, enable Option B | | `registry unauthorized` | Runner lacks registry creds | `docker login $STELLA_URL/registry` (store creds in CI secrets) | | Non‑zero scan exit | Blocking vuln/licence | Open project in Ops UI → triage or waive | --- ### Change log * **2025‑08‑04** – Variable clean‑up, removed Docker‑socket & cache mounts, added Jenkins / CircleCI / Gitea examples, clarified Option B comment.