ci: add security scanning and SBOM to release pipeline

- ci.yml: add security job (gitleaks, cargo-audit, cargo-deny, trivy fs)
- release.yml: restructure into build-binary + build-docker matrix + release
  - build binary once on self-hosted, reuse across all Docker builds
  - trivy image scan per matrix variant, results to GitHub Security tab
  - SBOM generation in SPDX and CycloneDX formats attached to release
- deny.toml: cargo-deny policy (allowed licenses, banned openssl, crates.io only)
- Dockerfile: remove Rust build stage, use pre-built binary
- Dockerfile.astra, Dockerfile.redos: FROM scratch for Russian certified OS support
This commit is contained in:
2026-02-23 11:37:27 +00:00
parent 037204a3eb
commit 6ad710ff32
6 changed files with 197 additions and 141 deletions

View File

@@ -27,3 +27,59 @@ jobs:
- name: Run tests - name: Run tests
run: cargo test --package nora-registry run: cargo test --package nora-registry
security:
name: Security
runs-on: ubuntu-latest
permissions:
contents: read
security-events: write # for uploading SARIF to GitHub Security tab
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # full history required for gitleaks
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
- name: Cache cargo
uses: Swatinem/rust-cache@v2
# ── Secrets ────────────────────────────────────────────────────────────
- name: Gitleaks — scan for hardcoded secrets
uses: gitleaks/gitleaks-action@v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# ── CVE in Rust dependencies ────────────────────────────────────────────
- name: Install cargo-audit
run: cargo install cargo-audit --locked
- name: cargo audit — RustSec advisory database
run: cargo audit
# ── Licenses, banned crates, supply chain policy ────────────────────────
- name: cargo deny — licenses and banned crates
uses: EmbarkStudios/cargo-deny-action@v2
with:
command: check
arguments: --all-features
# ── CVE scan of source tree and Cargo.lock ──────────────────────────────
- name: Trivy — filesystem scan (Cargo.lock + source)
uses: aquasecurity/trivy-action@master
with:
scan-type: fs
scan-ref: .
format: sarif
output: trivy-fs.sarif
severity: HIGH,CRITICAL
exit-code: 0 # warn only; change to 1 to block the pipeline
- name: Upload Trivy fs results to GitHub Security tab
uses: github/codeql-action/upload-sarif@v3
if: always()
with:
sarif_file: trivy-fs.sarif
category: trivy-fs

View File

@@ -9,12 +9,36 @@ env:
IMAGE_NAME: ${{ github.repository }} IMAGE_NAME: ${{ github.repository }}
jobs: jobs:
build: build-binary:
name: Build Binary
runs-on: self-hosted
steps:
- uses: actions/checkout@v4
- name: Set up Rust
run: echo "/home/ai-user/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/bin" >> $GITHUB_PATH
- name: Build release binary
run: |
RUSTUP_HOME=/home/ai-user/.rustup cargo build --release --package nora-registry
cp target/release/nora ./nora
- name: Upload binary
uses: actions/upload-artifact@v4
with:
name: nora-binary
path: nora
retention-days: 1
build-docker:
name: Build & Push (${{ matrix.name }}) name: Build & Push (${{ matrix.name }})
runs-on: self-hosted runs-on: self-hosted
needs: build-binary
permissions: permissions:
contents: read contents: read
packages: write packages: write
security-events: write # for uploading SARIF to GitHub Security tab
strategy: strategy:
fail-fast: false fail-fast: false
@@ -33,6 +57,14 @@ jobs:
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Download binary
uses: actions/download-artifact@v4
with:
name: nora-binary
- name: Make binary executable
run: chmod +x nora
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3 uses: docker/setup-buildx-action@v3
@@ -68,20 +100,64 @@ jobs:
cache-from: type=gha,scope=${{ matrix.name }} cache-from: type=gha,scope=${{ matrix.name }}
cache-to: type=gha,mode=max,scope=${{ matrix.name }} cache-to: type=gha,mode=max,scope=${{ matrix.name }}
# ── CVE scan of the pushed image ────────────────────────────────────────
- name: Trivy — image scan
uses: aquasecurity/trivy-action@master
with:
scan-type: image
image-ref: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.meta.outputs.version }}${{ matrix.suffix }}
format: sarif
output: trivy-image-${{ matrix.name }}.sarif
severity: HIGH,CRITICAL
exit-code: 0 # warn only; change to 1 to block on vulnerabilities
- name: Upload Trivy image results to GitHub Security tab
uses: github/codeql-action/upload-sarif@v3
if: always()
with:
sarif_file: trivy-image-${{ matrix.name }}.sarif
category: trivy-image-${{ matrix.name }}
release: release:
name: GitHub Release name: GitHub Release
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: build needs: build-docker
permissions: permissions:
contents: write contents: write
packages: read # to pull image for SBOM generation
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Log in to Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
# ── SBOM — Software Bill of Materials ───────────────────────────────────
- name: Generate SBOM (SPDX)
uses: anchore/sbom-action@v0
with:
image: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }}
format: spdx-json
output-file: nora-${{ github.ref_name }}.sbom.spdx.json
- name: Generate SBOM (CycloneDX)
uses: anchore/sbom-action@v0
with:
image: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }}
format: cyclonedx-json
output-file: nora-${{ github.ref_name }}.sbom.cdx.json
- name: Create Release - name: Create Release
uses: softprops/action-gh-release@v1 uses: softprops/action-gh-release@v1
with: with:
generate_release_notes: true generate_release_notes: true
files: |
nora-${{ github.ref_name }}.sbom.spdx.json
nora-${{ github.ref_name }}.sbom.cdx.json
body: | body: |
## Docker ## Docker

View File

@@ -1,58 +1,11 @@
# syntax=docker/dockerfile:1.4 # syntax=docker/dockerfile:1.4
# Binary is pre-built by CI (cargo build --release) and passed via context
# Build stage
FROM rust:1.83-alpine AS builder
RUN apk add --no-cache musl-dev curl
WORKDIR /app
# Copy manifests
COPY Cargo.toml Cargo.lock ./
COPY nora-registry/Cargo.toml nora-registry/
COPY nora-storage/Cargo.toml nora-storage/
COPY nora-cli/Cargo.toml nora-cli/
# Create dummy sources for dependency caching
RUN mkdir -p nora-registry/src nora-storage/src nora-cli/src && \
echo "fn main() {}" > nora-registry/src/main.rs && \
echo "fn main() {}" > nora-storage/src/main.rs && \
echo "fn main() {}" > nora-cli/src/main.rs
# Build dependencies only (with cache)
RUN --mount=type=cache,target=/usr/local/cargo/registry \
--mount=type=cache,target=/usr/local/cargo/git \
--mount=type=cache,target=/app/target \
cargo build --release --package nora-registry && \
rm -rf nora-registry/src nora-storage/src nora-cli/src
# Copy real sources
COPY nora-registry/src nora-registry/src
COPY nora-storage/src nora-storage/src
COPY nora-cli/src nora-cli/src
# Build release binary (with cache)
RUN --mount=type=cache,target=/usr/local/cargo/registry \
--mount=type=cache,target=/usr/local/cargo/git \
--mount=type=cache,target=/app/target \
touch nora-registry/src/main.rs && \
cargo build --release --package nora-registry && \
cp /app/target/release/nora /usr/local/bin/nora
# Runtime stage
FROM alpine:3.20 FROM alpine:3.20
RUN apk add --no-cache ca-certificates RUN apk add --no-cache ca-certificates && mkdir -p /data
WORKDIR /app COPY nora /usr/local/bin/nora
# Copy binary
COPY --from=builder /usr/local/bin/nora /usr/local/bin/nora
# Create data directory
RUN mkdir -p /data
# Default environment
ENV RUST_LOG=info ENV RUST_LOG=info
ENV NORA_HOST=0.0.0.0 ENV NORA_HOST=0.0.0.0
ENV NORA_PORT=4000 ENV NORA_PORT=4000
@@ -64,5 +17,5 @@ EXPOSE 4000
VOLUME ["/data"] VOLUME ["/data"]
ENTRYPOINT ["nora"] ENTRYPOINT ["/usr/local/bin/nora"]
CMD ["serve"] CMD ["serve"]

View File

@@ -1,52 +1,17 @@
# syntax=docker/dockerfile:1.4 # syntax=docker/dockerfile:1.4
# Binary is pre-built by CI (cargo build --release) and passed via context
# Runtime: scratch — compatible with Astra Linux SE (FSTEC certified)
# To switch to official base: replace FROM scratch with
# FROM registry.astralinux.ru/library/alse:latest
# RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates && rm -rf /var/lib/apt/lists/*
# Build stage — static binary via musl (runs on any Linux) FROM alpine:3.20 AS certs
FROM rust:1.83-alpine AS builder RUN apk add --no-cache ca-certificates
RUN apk add --no-cache musl-dev curl
WORKDIR /app
# Copy manifests
COPY Cargo.toml Cargo.lock ./
COPY nora-registry/Cargo.toml nora-registry/
COPY nora-storage/Cargo.toml nora-storage/
COPY nora-cli/Cargo.toml nora-cli/
# Create dummy sources for dependency caching
RUN mkdir -p nora-registry/src nora-storage/src nora-cli/src && \
echo "fn main() {}" > nora-registry/src/main.rs && \
echo "fn main() {}" > nora-storage/src/main.rs && \
echo "fn main() {}" > nora-cli/src/main.rs
# Build dependencies only (with cache)
RUN --mount=type=cache,target=/usr/local/cargo/registry \
--mount=type=cache,target=/usr/local/cargo/git \
--mount=type=cache,target=/app/target \
cargo build --release --package nora-registry && \
rm -rf nora-registry/src nora-storage/src nora-cli/src
# Copy real sources
COPY nora-registry/src nora-registry/src
COPY nora-storage/src nora-storage/src
COPY nora-cli/src nora-cli/src
# Build release binary (with cache)
RUN --mount=type=cache,target=/usr/local/cargo/registry \
--mount=type=cache,target=/usr/local/cargo/git \
--mount=type=cache,target=/app/target \
touch nora-registry/src/main.rs && \
cargo build --release --package nora-registry && \
cp /app/target/release/nora /usr/local/bin/nora
# Runtime stage — scratch (compatible with Astra Linux SE, no foreign OS components)
# Switch FROM to registry.astralinux.ru/library/alse once registry access is configured
FROM scratch FROM scratch
# CA certificates for TLS COPY --from=certs /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt COPY nora /usr/local/bin/nora
COPY --from=builder /usr/local/bin/nora /usr/local/bin/nora
ENV RUST_LOG=info ENV RUST_LOG=info
ENV NORA_HOST=0.0.0.0 ENV NORA_HOST=0.0.0.0

View File

@@ -1,52 +1,17 @@
# syntax=docker/dockerfile:1.4 # syntax=docker/dockerfile:1.4
# Binary is pre-built by CI (cargo build --release) and passed via context
# Runtime: scratch — compatible with RED OS (FSTEC certified)
# To switch to official base: replace FROM scratch with
# FROM registry.red-soft.ru/redos/redos:8
# RUN dnf install -y ca-certificates && dnf clean all
# Build stage — static binary via musl (runs on any Linux) FROM alpine:3.20 AS certs
FROM rust:1.83-alpine AS builder RUN apk add --no-cache ca-certificates
RUN apk add --no-cache musl-dev curl
WORKDIR /app
# Copy manifests
COPY Cargo.toml Cargo.lock ./
COPY nora-registry/Cargo.toml nora-registry/
COPY nora-storage/Cargo.toml nora-storage/
COPY nora-cli/Cargo.toml nora-cli/
# Create dummy sources for dependency caching
RUN mkdir -p nora-registry/src nora-storage/src nora-cli/src && \
echo "fn main() {}" > nora-registry/src/main.rs && \
echo "fn main() {}" > nora-storage/src/main.rs && \
echo "fn main() {}" > nora-cli/src/main.rs
# Build dependencies only (with cache)
RUN --mount=type=cache,target=/usr/local/cargo/registry \
--mount=type=cache,target=/usr/local/cargo/git \
--mount=type=cache,target=/app/target \
cargo build --release --package nora-registry && \
rm -rf nora-registry/src nora-storage/src nora-cli/src
# Copy real sources
COPY nora-registry/src nora-registry/src
COPY nora-storage/src nora-storage/src
COPY nora-cli/src nora-cli/src
# Build release binary (with cache)
RUN --mount=type=cache,target=/usr/local/cargo/registry \
--mount=type=cache,target=/usr/local/cargo/git \
--mount=type=cache,target=/app/target \
touch nora-registry/src/main.rs && \
cargo build --release --package nora-registry && \
cp /app/target/release/nora /usr/local/bin/nora
# Runtime stage — scratch (compatible with RED OS, no foreign OS components)
# Switch FROM to registry.red-soft.ru/redos once registry access is configured
FROM scratch FROM scratch
# CA certificates for TLS COPY --from=certs /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt COPY nora /usr/local/bin/nora
COPY --from=builder /usr/local/bin/nora /usr/local/bin/nora
ENV RUST_LOG=info ENV RUST_LOG=info
ENV NORA_HOST=0.0.0.0 ENV NORA_HOST=0.0.0.0

41
deny.toml Normal file
View File

@@ -0,0 +1,41 @@
# cargo-deny configuration
# https://embarkstudios.github.io/cargo-deny/
[advisories]
# Vulnerability database (RustSec)
db-urls = ["https://github.com/rustsec/advisory-db"]
ignore = []
[licenses]
# Allowed open-source licenses
allow = [
"MIT",
"Apache-2.0",
"Apache-2.0 WITH LLVM-exception",
"BSD-2-Clause",
"BSD-3-Clause",
"ISC",
"Unicode-DFS-2016",
"Unicode-3.0",
"CC0-1.0",
"OpenSSL",
"Zlib",
"MPL-2.0", # Mozilla Public License — ok for binary linking
]
copyleft = "warn" # GPL etc — warn, don't block
unlicensed = "deny"
[bans]
multiple-versions = "warn"
deny = [
# Prefer rustls over openssl for static builds and supply chain cleanliness
{ name = "openssl-sys" },
{ name = "openssl" },
]
skip = []
[sources]
unknown-registry = "warn"
unknown-git = "warn"
# Allow only the official crates.io index
allow-registry = ["https://github.com/rust-lang/crates.io-index"]