354 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			Markdown
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			354 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			Markdown
		
	
	
		
			Executable File
		
	
	
	
	
# 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 – `<image-ref>‑YYYYMMDDThhmmssZ.sbom.json`                                                                    | `acme_backend_sha‑abc123‑20250804T153050Z.sbom.json` |
 | 
						||
 | 
						||
> **Authority graph scopes note (2025-10-27):** CI stages that spin up the Authority compose profile now rely on the checked-in `etc/authority.yaml`. Before running integration smoke jobs, inject real secrets for every `etc/secrets/*.secret` file (Cartographer, Graph API, Policy Engine, Concelier, Excititor). The repository defaults contain `*-change-me` placeholders and Authority will reject tokens if those secrets are not overridden. Reissue CI tokens that previously used `policy:write`/`policy:submit`/`policy:edit` scopes—new bundles must request `policy:read`, `policy:author`, `policy:review`, `policy:simulate`, and (`policy:approve`/`policy:operate`/`policy:activate` when pipelines promote policies).
 | 
						||
 | 
						||
```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 · Docs CI (Gitea Actions & Offline Mirror)
 | 
						||
 | 
						||
StellaOps ships a dedicated Docs workflow at `.gitea/workflows/docs.yml`. When mirroring the pipeline offline or running it locally, install the same toolchain so markdown linting, schema validation, and HTML preview stay deterministic.
 | 
						||
 | 
						||
### 4.1 Toolchain bootstrap
 | 
						||
 | 
						||
```bash
 | 
						||
# Node.js 20.x is required; install once per runner
 | 
						||
npm install --no-save \
 | 
						||
  markdown-link-check \
 | 
						||
  remark-cli \
 | 
						||
  remark-preset-lint-recommended \
 | 
						||
  ajv \
 | 
						||
  ajv-cli \
 | 
						||
  ajv-formats
 | 
						||
 | 
						||
# Python 3.11+ powers the preview renderer
 | 
						||
python -m pip install --upgrade pip
 | 
						||
python -m pip install markdown pygments
 | 
						||
```
 | 
						||
 | 
						||
> **No `pip` available?** Some hardened Python builds (including the repo’s `tmp/docenv`
 | 
						||
> interpreter) ship without `pip`/`ensurepip`. In that case download the pure‑Python
 | 
						||
> sdists (e.g. `Markdown-3.x.tar.gz`, `pygments-2.x.tar.gz`) and extract their
 | 
						||
> packages directly into the virtualenv’s `lib/python*/site-packages/` folder.
 | 
						||
> This keeps the renderer working even when package managers are disabled.
 | 
						||
 | 
						||
**Offline tip.** Add the packages above to your artifact mirror (for example `ops/devops/offline-kit.json`) so runners can install them via `npm --offline` / `pip --no-index`.
 | 
						||
 | 
						||
### 4.2 Schema validation step
 | 
						||
 | 
						||
Ajv compiles every event schema to guard against syntax or format regressions. The workflow uses `ajv-formats` for UUID/date-time support.
 | 
						||
 | 
						||
```bash
 | 
						||
for schema in docs/events/*.json; do
 | 
						||
  npx ajv compile -c ajv-formats -s "$schema"
 | 
						||
done
 | 
						||
```
 | 
						||
 | 
						||
Run this loop before committing schema changes. For new references, append `-r additional-file.json` so CI and local runs stay aligned.
 | 
						||
 | 
						||
### 4.3 Preview build
 | 
						||
 | 
						||
```bash
 | 
						||
python scripts/render_docs.py --source docs --output artifacts/docs-preview --clean
 | 
						||
```
 | 
						||
 | 
						||
Host the resulting bundle via any static file server for review (for example `python -m http.server`).
 | 
						||
 | 
						||
### 4.4 Publishing checklist
 | 
						||
 | 
						||
- [ ] Toolchain installs succeed without hitting the public internet (mirror or cached tarballs).
 | 
						||
- [ ] Ajv validation passes for `scanner.report.ready@1`, `scheduler.rescan.delta@1`, `attestor.logged@1`.
 | 
						||
- [ ] Markdown link check (`npx markdown-link-check`) reports no broken references.
 | 
						||
- [ ] Preview bundle archived (or attached) for stakeholders.
 | 
						||
 | 
						||
### 4.5 Policy DSL lint stage
 | 
						||
 | 
						||
Policy Engine v2 pipelines now fail fast if policy documents are malformed. After checkout and dotnet restore, run:
 | 
						||
 | 
						||
```bash
 | 
						||
dotnet run \
 | 
						||
  --project src/Tools/PolicyDslValidator/PolicyDslValidator.csproj \
 | 
						||
  -- \
 | 
						||
  --strict docs/examples/policies/*.yaml
 | 
						||
```
 | 
						||
 | 
						||
- `--strict` treats warnings as errors so missing metadata doesn’t slip through.
 | 
						||
- The validator accepts globs, so you can point it at tenant policy directories later (`policies/**/*.yaml`).
 | 
						||
- Exit codes follow UNIX conventions: `0` success, `1` parse/errors, `2` warnings when `--strict` is set, `64` usage mistakes.
 | 
						||
 | 
						||
Capture the validator output as part of your build logs; Support uses it when triaging policy rollout issues.
 | 
						||
 | 
						||
### 4.6 Policy simulation smoke
 | 
						||
 | 
						||
Catch unexpected policy regressions by exercising a small set of golden SBOM findings via the simulation smoke tool:
 | 
						||
 | 
						||
```bash
 | 
						||
dotnet run \
 | 
						||
  --project src/Tools/PolicySimulationSmoke/PolicySimulationSmoke.csproj \
 | 
						||
  -- \
 | 
						||
  --scenario-root samples/policy/simulations \
 | 
						||
  --output artifacts/policy-simulations
 | 
						||
```
 | 
						||
 | 
						||
- The tool loads each `scenario.json` under `samples/policy/simulations`, evaluates the referenced policy, and fails the build if projected verdicts change.
 | 
						||
- In CI the command runs twice (to `run1/` and `run2/`) and `diff -u` compares the summaries—any mismatch signals a determinism regression.
 | 
						||
- Artifacts land in `artifacts/policy-simulations/policy-simulation-summary.json`; upload them for later inspection (see CI workflow).
 | 
						||
- Expand scenarios by copying real-world findings into the samples directory—ensure expected statuses are recorded so regressions trip the pipeline.
 | 
						||
 | 
						||
---
 | 
						||
 | 
						||
## 5 · 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 /<sbom>.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‑10‑18** – Documented Docs CI toolchain (Ajv validation, static preview) and offline checklist.
 | 
						||
* **2025‑08‑04** – Variable clean‑up, removed Docker‑socket & cache mounts, added Jenkins / CircleCI / Gitea examples, clarified Option B comment.
 |