ci: add pilot v1 hardening guards
This commit is contained in:
@@ -23,6 +23,12 @@ jobs:
|
|||||||
- name: Run production inventory placeholder guard self-test
|
- name: Run production inventory placeholder guard self-test
|
||||||
run: bash scripts/check_production_inventory_placeholders.sh --self-test
|
run: bash scripts/check_production_inventory_placeholders.sh --self-test
|
||||||
|
|
||||||
|
- name: Run private-config guard
|
||||||
|
run: bash scripts/check_private_config_guard.sh
|
||||||
|
|
||||||
|
- name: Run portal contract sync guard
|
||||||
|
run: node scripts/check_portal_contract_sync.mjs
|
||||||
|
|
||||||
rust-runtime-guard:
|
rust-runtime-guard:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
name: rust-workspace
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ "main" ]
|
||||||
|
pull_request:
|
||||||
|
branches: [ "main" ]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
rust-workspace:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Install Rust 1.85
|
||||||
|
uses: dtolnay/rust-toolchain@1.85.0
|
||||||
|
with:
|
||||||
|
components: rustfmt, clippy
|
||||||
|
|
||||||
|
- name: Format
|
||||||
|
run: cargo fmt --manifest-path adk-rust/Cargo.toml --all -- --check
|
||||||
|
|
||||||
|
- name: Test
|
||||||
|
run: cargo test --manifest-path adk-rust/Cargo.toml --workspace
|
||||||
|
|
||||||
|
- name: Clippy
|
||||||
|
run: cargo clippy --manifest-path adk-rust/Cargo.toml --workspace --all-targets -- -D warnings
|
||||||
|
|
||||||
|
- name: Release build
|
||||||
|
run: cargo build --manifest-path adk-rust/Cargo.toml --workspace --release
|
||||||
+6
-2
@@ -1,7 +1,11 @@
|
|||||||
# Local secrets
|
# Local secrets
|
||||||
/secrets/
|
/secrets/
|
||||||
/private-config/*.env
|
/private-config/*
|
||||||
/private-config/*.local
|
!/private-config/
|
||||||
|
!/private-config/README.md
|
||||||
|
!/private-config/.gitkeep
|
||||||
|
!/private-config/*.example
|
||||||
|
!/private-config/*.template
|
||||||
/ansible/inventory.ini
|
/ansible/inventory.ini
|
||||||
/codex_history.txt
|
/codex_history.txt
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +0,0 @@
|
|||||||
*.env
|
|
||||||
*.local
|
|
||||||
!.gitignore
|
|
||||||
!*.example
|
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
# private-config
|
||||||
|
|
||||||
|
This directory is reserved for local, host-specific, or secret configuration.
|
||||||
|
|
||||||
|
Do not commit real runtime values here. Git only allows:
|
||||||
|
|
||||||
|
- `private-config/README.md`
|
||||||
|
- `private-config/.gitkeep`
|
||||||
|
- `private-config/*.example`
|
||||||
|
- `private-config/*.template`
|
||||||
|
|
||||||
|
Use `scripts/check_private_config_guard.sh` before commits and in CI to verify
|
||||||
|
that no private config file has entered the git index.
|
||||||
Executable
+72
@@ -0,0 +1,72 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
import fs from "node:fs";
|
||||||
|
import path from "node:path";
|
||||||
|
import { fileURLToPath } from "node:url";
|
||||||
|
|
||||||
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||||
|
const root = path.resolve(__dirname, "..");
|
||||||
|
const contractPath = path.join(
|
||||||
|
root,
|
||||||
|
"adk-rust/crates/detmir-portal/src/contracts/openapi.json",
|
||||||
|
);
|
||||||
|
|
||||||
|
const requiredPublicPaths = [
|
||||||
|
"/api/contracts",
|
||||||
|
"/api/contracts/openapi.json",
|
||||||
|
"/api/contracts/typescript.d.ts",
|
||||||
|
"/api/reports",
|
||||||
|
"/api/executive",
|
||||||
|
"/api/workforce",
|
||||||
|
"/api/security",
|
||||||
|
"/api/forensics",
|
||||||
|
"/api/ueba",
|
||||||
|
"/api/pfsense",
|
||||||
|
"/api/incidents",
|
||||||
|
"/api/cases",
|
||||||
|
"/api/readiness/latest",
|
||||||
|
"/api/readiness/bundle",
|
||||||
|
"/api/readiness/verify",
|
||||||
|
];
|
||||||
|
|
||||||
|
function fail(message, details = []) {
|
||||||
|
console.error(message);
|
||||||
|
for (const detail of details) {
|
||||||
|
console.error(`- ${detail}`);
|
||||||
|
}
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
let contract;
|
||||||
|
try {
|
||||||
|
contract = JSON.parse(fs.readFileSync(contractPath, "utf8"));
|
||||||
|
} catch (error) {
|
||||||
|
fail(`failed to read OpenAPI contract: ${contractPath}`, [error.message]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!contract || typeof contract !== "object" || !contract.paths || typeof contract.paths !== "object") {
|
||||||
|
fail("OpenAPI contract has no object 'paths' section.");
|
||||||
|
}
|
||||||
|
|
||||||
|
const contractPaths = Object.keys(contract.paths);
|
||||||
|
const forbiddenPaths = contractPaths.filter((contractPathName) =>
|
||||||
|
/dioxus|prototype-mirror|mirror/i.test(contractPathName),
|
||||||
|
);
|
||||||
|
if (forbiddenPaths.length > 0) {
|
||||||
|
fail("OpenAPI contract contains legacy/prototype paths.", forbiddenPaths);
|
||||||
|
}
|
||||||
|
|
||||||
|
const effectivePublicPaths = new Set();
|
||||||
|
for (const contractPathName of contractPaths) {
|
||||||
|
effectivePublicPaths.add(contractPathName);
|
||||||
|
if (contractPathName.startsWith("/") && !contractPathName.startsWith("/api/")) {
|
||||||
|
effectivePublicPaths.add(`/api${contractPathName}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const missingPaths = requiredPublicPaths.filter((requiredPath) => !effectivePublicPaths.has(requiredPath));
|
||||||
|
if (missingPaths.length > 0) {
|
||||||
|
fail("OpenAPI contract is missing required public API paths.", missingPaths);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("portal contract sync guard: OK");
|
||||||
Executable
+25
@@ -0,0 +1,25 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
violations=()
|
||||||
|
|
||||||
|
while IFS= read -r -d '' path; do
|
||||||
|
rest="${path#private-config/}"
|
||||||
|
case "$path" in
|
||||||
|
private-config/README.md|private-config/.gitkeep)
|
||||||
|
continue
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
if [[ "$rest" != */* && ( "$rest" == *.example || "$rest" == *.template ) ]]; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
violations+=("$path")
|
||||||
|
done < <(git ls-files -z -- private-config)
|
||||||
|
|
||||||
|
if (( ${#violations[@]} > 0 )); then
|
||||||
|
printf 'private-config guard failed: tracked private files are forbidden. Allowed files are README.md, .gitkeep, *.example, *.template.\\n' >&2
|
||||||
|
printf '%s\\n' "${violations[@]}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "private-config guard: OK"
|
||||||
@@ -7,6 +7,16 @@ cd "$ROOT_DIR"
|
|||||||
TARGET_ROOT="${CARGO_TARGET_DIR:-$ROOT_DIR/adk-rust/target}"
|
TARGET_ROOT="${CARGO_TARGET_DIR:-$ROOT_DIR/adk-rust/target}"
|
||||||
RUST_BIN="${QUALITY_GATE_RUST:-}"
|
RUST_BIN="${QUALITY_GATE_RUST:-}"
|
||||||
|
|
||||||
|
echo "[preflight] Private-config guard"
|
||||||
|
bash scripts/check_private_config_guard.sh
|
||||||
|
|
||||||
|
echo "[preflight] Portal contract sync guard"
|
||||||
|
if command -v node >/dev/null 2>&1; then
|
||||||
|
node scripts/check_portal_contract_sync.mjs
|
||||||
|
else
|
||||||
|
echo "node not found, skipping portal contract sync guard."
|
||||||
|
fi
|
||||||
|
|
||||||
rust_candidates=()
|
rust_candidates=()
|
||||||
if [[ -n "$RUST_BIN" ]]; then
|
if [[ -n "$RUST_BIN" ]]; then
|
||||||
rust_candidates+=("$RUST_BIN")
|
rust_candidates+=("$RUST_BIN")
|
||||||
@@ -39,6 +49,7 @@ fi
|
|||||||
echo "[3/6] Node syntax check (if node available)"
|
echo "[3/6] Node syntax check (if node available)"
|
||||||
if command -v node >/dev/null 2>&1; then
|
if command -v node >/dev/null 2>&1; then
|
||||||
node --check scripts/aw-webui-browser-smoke.mjs >/dev/null
|
node --check scripts/aw-webui-browser-smoke.mjs >/dev/null
|
||||||
|
node --check scripts/check_portal_contract_sync.mjs >/dev/null
|
||||||
else
|
else
|
||||||
echo "node not found, skipping."
|
echo "node not found, skipping."
|
||||||
fi
|
fi
|
||||||
|
|||||||
Reference in New Issue
Block a user