mirror of
https://github.com/getnora-io/nora.git
synced 2026-04-12 13:50:31 +00:00
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:
56
.github/workflows/ci.yml
vendored
56
.github/workflows/ci.yml
vendored
@@ -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
|
||||||
|
|||||||
80
.github/workflows/release.yml
vendored
80
.github/workflows/release.yml
vendored
@@ -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
|
||||||
|
|
||||||
|
|||||||
55
Dockerfile
55
Dockerfile
@@ -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"]
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
41
deny.toml
Normal 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"]
|
||||||
Reference in New Issue
Block a user