# Diagram-matching DevSecOps bundle for offline RHEL 9.6

Updated 2026-09-12, Linux x86_64. This is a standalone copy of the existing
DevSecOps bundle, extended with **Syft 1.51.1 and Grype 0.118.0**. Existing Jira,
Gitea, Drone, SonarQube and openQA remain your running services.

| Diagram stage | Implementation |
|---|---|
| Plan / feedback | Jira security requirements, threat model, owners, findings and deadlines |
| Source | Gitea protected branches + Gitleaks 8.30.1 history/files scan |
| Build | Drone + Syft SBOM + Grype vulnerability gate + Trivy 0.74.0 vulnerability/IaC gate |
| Static test | Existing SonarQube server and supported security analyzers; wait for quality gate |
| System test | Existing openQA against the same candidate build |
| DAST | Bundled ZAP 2.17.0 container against authorized internal staging |
| Release | Protected promotion after every gate; SHA256 artifact integrity checks |

## Install and test

1. Follow [INSTALL-OFFLINE.md](INSTALL-OFFLINE.md) sections 1–3 for trusted transfer,
   genuine RHEL 9.6 prerequisites and installation. The installer now copies both
   Trivy and Grype caches. No RHEL RPMs are bundled; use your authorized RHEL DVD
   or the included RPM collector on a subscribed RHEL 9.6 staging host.
2. On the RHEL 9.6 test host, verify the trusted manifest, then run:

   ```bash
   cd /path/to/my-devsecops
   sha256sum --check SHA256SUMS
   sudo bash scripts/install-offline.sh /opt/devsecops
   sudo unshare --net bash /opt/devsecops/scripts/test-offline.sh
   ```

   The test uses synthetic fixtures: secrets detection, vulnerable dependency
   rejection by Trivy and Grype, SBOM parsing, clean scan acceptance, missing DB
   rejection. Nonzero means acceptance failed.
   Temporary evidence paths are printed; no real credentials are needed.
3. As the dedicated CI account, prepare per-job caches outside your checkout:

   ```bash
   mkdir -p /srv/ci-cache/build-001/{trivy,grype}
   cp -a /opt/devsecops/cache/trivy/. /srv/ci-cache/build-001/trivy/
   cp -a /opt/devsecops/cache/grype/. /srv/ci-cache/build-001/grype/
   export TRIVY_CACHE_DIR=/srv/ci-cache/build-001/trivy
   export GRYPE_DB_CACHE_DIR=/srv/ci-cache/build-001/grype
   /opt/devsecops/scripts/scan-source.sh /srv/source/app /srv/reports/source-001
   /opt/devsecops/scripts/scan-supply-chain.sh dir /srv/source/app /srv/reports/sbom-001
   ```

   Both commands must succeed. Grype returns 2 when the severity gate finds a
   match; any nonzero exit blocks promotion, including scanner/database errors.
   The Grype database age limit is 168 hours; Trivy defaults to 7 days.
4. Scan the built image as well as source. The name
   `internal.example/app:build-001` in the earlier example was a **placeholder**,
   not a bundled image. `podman save` exports an image already in your local
   store; it does not build or download it. Run `podman images` and select your
   application's image ID. Use the same account that built or loaded it:
   rootless Podman and `sudo podman` have separate image stores.

   This interactive Bash example stops if inspection or export fails and uses a
   new artifact directory, so a previous build's archive cannot be scanned by mistake:

   ```bash
   (
     set -euo pipefail
     podman images
     read -r -p 'Application image ID from the list above: ' APP_IMAGE
     test -n "$APP_IMAGE"
     podman image inspect "$APP_IMAGE" >/dev/null
     RUN_DIR="$(mktemp -d "$HOME/devsecops-image.XXXXXXXX")"
     echo "Archive and reports: $RUN_DIR"
     podman save --format docker-archive -o "$RUN_DIR/app.tar" "$APP_IMAGE"
     test -s "$RUN_DIR/app.tar"
         # Run both scanners for evidence; any nonzero result blocks promotion.
     status=0
     /opt/devsecops/scripts/scan-image.sh "$RUN_DIR/app.tar" "$RUN_DIR/trivy" || status=1
     /opt/devsecops/scripts/scan-supply-chain.sh docker-archive "$RUN_DIR/app.tar" "$RUN_DIR/sbom" || status=1
     exit "$status"
   )
   ```

   If your application is not listed, build it using your existing offline build
   process or transfer its image archive from the build host and load it with
   `podman load -i /actual/path/application-image.tar`. Retagging an unrelated
   image does not create your application.

   To test the scanners immediately using an included image, run the following
   from the **my-devsecops bundle directory** (no Podman export or load needed):

   ```bash
   (
     set -euo pipefail
     test -s images/zap-stable-amd64.tar
     RUN_DIR="$(mktemp -d "$HOME/devsecops-zap-scan.XXXXXXXX")"
     echo "Reports: $RUN_DIR"
     status=0
     /opt/devsecops/scripts/scan-image.sh images/zap-stable-amd64.tar "$RUN_DIR/trivy" || status=1
     /opt/devsecops/scripts/scan-supply-chain.sh docker-archive images/zap-stable-amd64.tar "$RUN_DIR/sbom" || status=1
     exit "$status"
   )
   ```

   This scans ZAP itself, not your application. Vulnerability findings may fail
   the gate. Database-age information is not the cause of a missing-archive error;
   stale databases will independently fail the freshness policy.

   Keep `sbom.syft.json`, `sbom.cdx.json`, `grype.json`, `grype-db.json` and Trivy
   reports with commit ID and artifact SHA256. An empty SBOM can produce a clean
   result: review whether your actual application's components are catalogued.

## Drone integration and feedback

`examples/drone-exec.yml` includes both source and Syft/Grype steps for an **exec
runner**. Adapt this into your existing build, retaining SonarQube/openQA jobs.
Required sequence: source checks → build → artifact checks → staging → openQA
and authenticated ZAP → promotion. Preserve reports in an always-run step, even
when a gate fails. Require the resulting Drone status in Gitea branch protection.
Use unique workspace/cache/report paths per build; restrict exec runners to
trusted repositories because they execute directly on the host.

The default Drone templates now use separate Gitleaks, Trivy, Syft, Grype and
ZAP images. Syft passes its SBOM to Grype through the shared evidence volume.
See [PER-TOOL-CONTAINERS.md](PER-TOOL-CONTAINERS.md) for all five archives,
load commands and offline tests. The combined scanner archive remains available
for compatibility. Mirror Drone clone/build/plugin images and application
dependencies internally.

For Jira, record CVE/rule, component/version, severity, commit/build, evidence
link, owner, due date and any reviewed exception expiry. Deduplicate using
project + component + CVE/rule. No Jira tickets or service configuration are
changed automatically by these scripts. Choose your deployment's Jira API/auth
before adding an automated ticket publisher; reports are ready for that adapter.

## ZAP, host hardening and offline refresh

Follow installation guide sections 5–7 for ZAP, optional OpenSCAP and release integrity.
ZAP baseline is passive analysis plus crawling; authenticated active/API testing
requires your application context and test credentials. ZAP is DAST, not ongoing
host intrusion detection. Keep your host audit/monitoring controls too.

Refresh on a connected approved transfer station:

```bash
python3 scripts/download-anchore.py  # pinned binary versions; no silent upgrades
bash scripts/refresh-grype-db.sh /path/to/new-grype-cache
# Transfer the complete new cache as cache/grype in a NEW bundle snapshot.
# Refresh Trivy DB, Java DB and rules per INSTALL-OFFLINE.md section 9.
python3 scripts/make-manifest.py
sha256sum --check SHA256SUMS
```

Authenticate the new manifest independently before import. Keep previous snapshots
for rollback; never replace a database during a running scan. Syft needs no CVE
DB; remote enrichment is not enabled. Grype has its own database; it cannot use
Trivy's database. Preserve tool/cache versions together. Allow at least 35 GB
for transfer files, installed caches and loaded images, plus application storage.

## Primary references

- [Syft local inputs and SBOM outputs](https://oss.anchore.com/docs/guides/sbom/getting-started/)
- [Grype database management and freshness](https://oss.anchore.com/docs/guides/vulnerability/database/)
- [Trivy offline operation](https://trivy.dev/docs/latest/guide/advanced/air-gap/)
- [ZAP baseline behavior and exit codes](https://www.zaproxy.org/docs/docker/baseline-scan/)

See `metadata/MY-VALIDATION.md` for checks performed in this session. Earlier
validation logs are retained as provenance for reused assets, not fresh RHEL tests.
