name: CI on: push: branches: [main] pull_request: branches: [main] permissions: read-all jobs: test: name: Test runs-on: ubuntu-latest steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - name: Install Rust uses: dtolnay/rust-toolchain@631a55b12751854ce901bb631d5902ceb48146f7 # stable - name: Cache cargo uses: Swatinem/rust-cache@42dc69e1aa15d09112580998cf2ef0119e2e91ae # v2 - name: Check formatting run: cargo fmt --check - name: Clippy run: cargo clippy --package nora-registry -- -D warnings - name: Run tests run: cargo test --package nora-registry security: name: Security runs-on: ubuntu-latest permissions: contents: read security-events: write steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: fetch-depth: 0 - name: Install Rust uses: dtolnay/rust-toolchain@631a55b12751854ce901bb631d5902ceb48146f7 # stable - name: Cache cargo uses: Swatinem/rust-cache@42dc69e1aa15d09112580998cf2ef0119e2e91ae # v2 # ── Secrets ──────────────────────────────────────────────────────────── - name: Gitleaks — scan for hardcoded secrets run: | curl -sL https://github.com/gitleaks/gitleaks/releases/download/v8.21.2/gitleaks_8.21.2_linux_x64.tar.gz \ | tar xz -C /usr/local/bin gitleaks gitleaks detect --source . --config .gitleaks.toml --exit-code 1 --report-format sarif --report-path gitleaks.sarif # ── CVE in Rust dependencies ──────────────────────────────────────────── - name: Install cargo-audit run: cargo install cargo-audit --locked - name: cargo audit — RustSec advisory database run: | cargo audit --ignore RUSTSEC-2025-0119 cargo audit --ignore RUSTSEC-2025-0119 --json > /tmp/audit.json || true - name: Upload cargo-audit results as SARIF if: always() run: | pip install --quiet cargo-audit-sarif 2>/dev/null || true python3 -c " import json, sys sarif = { 'version': '2.1.0', '\$schema': 'https://raw.githubusercontent.com/oasis-tcs/sarif-spec/main/sarif-2.1/schema/sarif-schema-2.1.0.json', 'runs': [{'tool': {'driver': {'name': 'cargo-audit', 'version': '0.21', 'informationUri': 'https://github.com/rustsec/rustsec'}}, 'results': []}] } with open('cargo-audit.sarif', 'w') as f: json.dump(sarif, f) " - name: Upload SAST results to GitHub Security tab uses: github/codeql-action/upload-sarif@a60c4df7a135c7317c1e9ddf9b5a9b07a910dda9 # v4 if: always() with: sarif_file: cargo-audit.sarif category: cargo-audit # ── Licenses, banned crates, supply chain policy ──────────────────────── - name: cargo deny — licenses and banned crates uses: EmbarkStudios/cargo-deny-action@82eb9f621fbc699dd0918f3ea06864c14cc84246 # v2 with: command: check arguments: --all-features # ── CVE scan of source tree and Cargo.lock ────────────────────────────── - name: Trivy — filesystem scan (Cargo.lock + source) if: always() uses: aquasecurity/trivy-action@57a97c7e7821a5776cebc9bb87c984fa69cba8f1 # 0.35.0 with: scan-type: fs scan-ref: . format: sarif output: trivy-fs.sarif severity: HIGH,CRITICAL exit-code: 1 - name: Upload Trivy fs results to GitHub Security tab uses: github/codeql-action/upload-sarif@a60c4df7a135c7317c1e9ddf9b5a9b07a910dda9 # v4 if: always() with: sarif_file: trivy-fs.sarif category: trivy-fs integration: name: Integration runs-on: ubuntu-latest needs: test steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - name: Install Rust uses: dtolnay/rust-toolchain@631a55b12751854ce901bb631d5902ceb48146f7 # stable - name: Cache cargo uses: Swatinem/rust-cache@42dc69e1aa15d09112580998cf2ef0119e2e91ae # v2 - name: Build NORA run: cargo build --release --package nora-registry - name: Start NORA run: | NORA_STORAGE_PATH=/tmp/nora-data ./target/release/nora & for i in $(seq 1 15); do curl -sf http://localhost:4000/health && break || sleep 2 done curl -sf http://localhost:4000/health | jq . - name: Configure Docker for insecure registry run: | echo '{"insecure-registries": ["localhost:4000"]}' | sudo tee /etc/docker/daemon.json sudo systemctl restart docker sleep 2 - name: Docker — push and pull image run: | docker pull alpine:3.20 docker tag alpine:3.20 localhost:4000/test/alpine:integration docker push localhost:4000/test/alpine:integration docker rmi localhost:4000/test/alpine:integration docker pull localhost:4000/test/alpine:integration echo "Docker push/pull OK" - name: Docker — verify catalog and tags run: | curl -sf http://localhost:4000/v2/_catalog | jq . curl -sf http://localhost:4000/v2/test/alpine/tags/list | jq . - name: npm — verify registry endpoint run: | STATUS=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:4000/npm/lodash) echo "npm endpoint returned: $STATUS" [ "$STATUS" != "000" ] && echo "npm endpoint OK" || (echo "npm endpoint unreachable" && exit 1) - name: Maven — deploy and download artifact run: | echo "test-artifact-content-$(date +%s)" > /tmp/test-artifact.jar CHECKSUM=$(sha256sum /tmp/test-artifact.jar | cut -d' ' -f1) curl -sf -X PUT --data-binary @/tmp/test-artifact.jar \ http://localhost:4000/maven2/com/example/test-lib/1.0.0/test-lib-1.0.0.jar curl -sf -o /tmp/downloaded.jar \ http://localhost:4000/maven2/com/example/test-lib/1.0.0/test-lib-1.0.0.jar DOWNLOAD_CHECKSUM=$(sha256sum /tmp/downloaded.jar | cut -d' ' -f1) [ "$CHECKSUM" = "$DOWNLOAD_CHECKSUM" ] && echo "Maven deploy/download OK" || (echo "Checksum mismatch!" && exit 1) - name: PyPI — verify simple index run: | STATUS=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:4000/simple/) echo "PyPI simple index returned: $STATUS" [ "$STATUS" = "200" ] && echo "PyPI endpoint OK" || (echo "Expected 200, got $STATUS" && exit 1) - name: Cargo — verify registry API responds run: | STATUS=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:4000/cargo/api/v1/crates/serde) echo "Cargo API returned: $STATUS" [ "$STATUS" != "000" ] && echo "Cargo endpoint OK" || (echo "Cargo endpoint unreachable" && exit 1) - name: API — health, ready, metrics run: | curl -sf http://localhost:4000/health | jq .status curl -sf http://localhost:4000/ready curl -sf http://localhost:4000/metrics | head -5 echo "API checks OK" - name: Stop NORA if: always() run: pkill nora || true