Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b83cb45ccb | ||
|
|
9ec07ff7a1 | ||
|
|
0e57c2d7a7 |
+13
-4
@@ -29,10 +29,13 @@ env:
|
||||
CARGO_NET_RETRY: "10"
|
||||
CARGO_HTTP_MULTIPLEXING: "false"
|
||||
|
||||
# Cancel in-progress runs for the same branch/PR
|
||||
# Cancel superseded PR runs, but NEVER cancel main-branch runs — those build and
|
||||
# deploy per-service images, and cancelling one merge's deploy when the next
|
||||
# merge lands leaves a service un-deployed (as happened between two back-to-back
|
||||
# merges). So cancel-in-progress only for pull_request events.
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
||||
|
||||
jobs:
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -217,13 +220,16 @@ jobs:
|
||||
image: docker:27-cli
|
||||
steps:
|
||||
- name: Build, push and trigger orca redeploy
|
||||
env:
|
||||
TRAMITON_FETCH_TOKEN: ${{ secrets.TRAMITON_FETCH_TOKEN }}
|
||||
run: |
|
||||
apk add --no-cache git curl openssl
|
||||
git init && git remote add origin "${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}.git"
|
||||
git fetch --depth=1 origin "${GITHUB_SHA}" && git checkout FETCH_HEAD
|
||||
IMAGE=registry.meghsakha.com/compliance-dashboard
|
||||
echo "${{ secrets.REGISTRY_PASSWORD }}" | docker login registry.meghsakha.com -u "${{ secrets.REGISTRY_USERNAME }}" --password-stdin
|
||||
docker build -f Dockerfile.dashboard -t "$IMAGE:latest" -t "$IMAGE:${GITHUB_SHA}" .
|
||||
DOCKER_BUILDKIT=1 docker build --secret id=tramiton_token,env=TRAMITON_FETCH_TOKEN \
|
||||
-f Dockerfile.dashboard -t "$IMAGE:latest" -t "$IMAGE:${GITHUB_SHA}" .
|
||||
docker push "$IMAGE:latest" && docker push "$IMAGE:${GITHUB_SHA}"
|
||||
PAYLOAD=$(printf '{"ref":"refs/heads/main","repository":{"full_name":"sharang/compliance-scanner-agent"},"head_commit":{"id":"%s","message":"deploy dashboard"}}' "${GITHUB_SHA}")
|
||||
SIG=$(printf '%s' "$PAYLOAD" | openssl dgst -sha256 -hmac "${{ secrets.ORCA_WEBHOOK_SECRET }}" | awk '{print $2}')
|
||||
@@ -259,13 +265,16 @@ jobs:
|
||||
image: docker:27-cli
|
||||
steps:
|
||||
- name: Build, push and trigger orca redeploy
|
||||
env:
|
||||
TRAMITON_FETCH_TOKEN: ${{ secrets.TRAMITON_FETCH_TOKEN }}
|
||||
run: |
|
||||
apk add --no-cache git curl openssl
|
||||
git init && git remote add origin "${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}.git"
|
||||
git fetch --depth=1 origin "${GITHUB_SHA}" && git checkout FETCH_HEAD
|
||||
IMAGE=registry.meghsakha.com/compliance-mcp
|
||||
echo "${{ secrets.REGISTRY_PASSWORD }}" | docker login registry.meghsakha.com -u "${{ secrets.REGISTRY_USERNAME }}" --password-stdin
|
||||
docker build -f Dockerfile.mcp -t "$IMAGE:latest" -t "$IMAGE:${GITHUB_SHA}" .
|
||||
DOCKER_BUILDKIT=1 docker build --secret id=tramiton_token,env=TRAMITON_FETCH_TOKEN \
|
||||
-f Dockerfile.mcp -t "$IMAGE:latest" -t "$IMAGE:${GITHUB_SHA}" .
|
||||
docker push "$IMAGE:latest" && docker push "$IMAGE:${GITHUB_SHA}"
|
||||
PAYLOAD=$(printf '{"ref":"refs/heads/main","repository":{"full_name":"sharang/compliance-scanner-agent"},"head_commit":{"id":"%s","message":"deploy mcp"}}' "${GITHUB_SHA}")
|
||||
SIG=$(printf '%s' "$PAYLOAD" | openssl dgst -sha256 -hmac "${{ secrets.ORCA_WEBHOOK_SECRET }}" | awk '{print $2}')
|
||||
|
||||
+10
-1
@@ -7,7 +7,16 @@ ARG DOCS_URL=/docs
|
||||
WORKDIR /app
|
||||
COPY . .
|
||||
ENV DOCS_URL=${DOCS_URL}
|
||||
RUN dx build --release --package compliance-dashboard
|
||||
# compliance-agent (a workspace member) depends on the private tramiton-core git
|
||||
# repo, so the workspace resolve needs it even to build the dashboard.
|
||||
# Authenticate the fetch with a PAT passed as a BuildKit secret.
|
||||
RUN --mount=type=secret,id=tramiton_token \
|
||||
if [ -s /run/secrets/tramiton_token ]; then \
|
||||
git config --global \
|
||||
url."https://sharang:$(cat /run/secrets/tramiton_token)@gitea.meghsakha.com/".insteadOf \
|
||||
"ssh://git@gitea.meghsakha.com:22222/"; \
|
||||
fi && \
|
||||
CARGO_NET_GIT_FETCH_WITH_CLI=true dx build --release --package compliance-dashboard
|
||||
|
||||
FROM debian:bookworm-slim
|
||||
RUN apt-get update && apt-get install -y ca-certificates libssl3 && rm -rf /var/lib/apt/lists/*
|
||||
|
||||
+10
-1
@@ -2,7 +2,16 @@ FROM rust:1.94-bookworm AS builder
|
||||
|
||||
WORKDIR /app
|
||||
COPY . .
|
||||
RUN cargo build --release -p compliance-mcp
|
||||
# compliance-agent (a workspace member) depends on the private tramiton-core git
|
||||
# repo, so the workspace resolve needs it even to build the mcp binary.
|
||||
# Authenticate the fetch with a PAT passed as a BuildKit secret.
|
||||
RUN --mount=type=secret,id=tramiton_token \
|
||||
if [ -s /run/secrets/tramiton_token ]; then \
|
||||
git config --global \
|
||||
url."https://sharang:$(cat /run/secrets/tramiton_token)@gitea.meghsakha.com/".insteadOf \
|
||||
"ssh://git@gitea.meghsakha.com:22222/"; \
|
||||
fi && \
|
||||
CARGO_NET_GIT_FETCH_WITH_CLI=true cargo build --release -p compliance-mcp
|
||||
|
||||
FROM debian:bookworm-slim
|
||||
RUN apt-get update && apt-get install -y ca-certificates libssl3 && rm -rf /var/lib/apt/lists/*
|
||||
|
||||
@@ -498,6 +498,13 @@ impl PipelineOrchestrator {
|
||||
"Unified pipeline: scan plan built"
|
||||
);
|
||||
|
||||
// Ingest + classify (tramiton for firmware) and store the detected type.
|
||||
self.classify_and_store(target, &target_id, scan_run_id)
|
||||
.await;
|
||||
// Provision a DAST target from a LiveUrl artifact so DAST fires for
|
||||
// wizard-created targets, not just migrated ones.
|
||||
self.ensure_dast_target(target, &plan).await;
|
||||
|
||||
match target.code_artifact() {
|
||||
Some(code) if code.kind == ArtifactKind::GitRepo => {
|
||||
let repo = repo_view_from_target(target, code);
|
||||
@@ -527,6 +534,110 @@ impl PipelineOrchestrator {
|
||||
}
|
||||
}
|
||||
|
||||
/// Ingest the target's artifacts, classify (tramiton for firmware/RTOS/Yocto,
|
||||
/// heuristics otherwise), and store the detected classification on the target.
|
||||
/// Best-effort — never fails the scan.
|
||||
async fn classify_and_store(
|
||||
&self,
|
||||
target: &OnboardedTarget,
|
||||
target_id: &str,
|
||||
scan_run_id: &str,
|
||||
) {
|
||||
self.update_phase(scan_run_id, "classification").await;
|
||||
let ctx = crate::ingest::IngestContext::from_config(&self.config, target_id);
|
||||
let ingest_set = match crate::ingest::ingest_all(target, &ctx) {
|
||||
Ok(set) => set,
|
||||
Err(e) => {
|
||||
tracing::warn!(target_id, error = %e, "Unified pipeline: ingest for classification failed");
|
||||
return;
|
||||
}
|
||||
};
|
||||
let working_paths = ingest_set.working_paths();
|
||||
match crate::classify::classify_target(
|
||||
target,
|
||||
&working_paths,
|
||||
&crate::classify::TramitonNative,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(classification) => {
|
||||
tracing::info!(
|
||||
target_id,
|
||||
suggested = %classification.suggested,
|
||||
"Unified pipeline: classified target"
|
||||
);
|
||||
if let (Some(oid), Ok(bson)) = (target.id, mongodb::bson::to_bson(&classification))
|
||||
{
|
||||
let _ = self
|
||||
.db
|
||||
.onboarded_targets()
|
||||
.update_one(
|
||||
doc! { "_id": oid },
|
||||
doc! { "$set": { "classification": bson } },
|
||||
)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::warn!(target_id, error = %e, "Unified pipeline: classification failed")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// If the target has a `LiveUrl` artifact and DAST is planned, provision a
|
||||
/// `DastTarget` (keyed by `repo_id` = target id) so the existing DAST trigger
|
||||
/// fires for wizard-created targets. Idempotent.
|
||||
async fn ensure_dast_target(
|
||||
&self,
|
||||
target: &OnboardedTarget,
|
||||
plan: &crate::pipeline::plan::ScanPlan,
|
||||
) {
|
||||
if !plan.has(ScanType::Dast) {
|
||||
return;
|
||||
}
|
||||
let (Some(url), Some(oid)) = (target.live_url(), target.id) else {
|
||||
return;
|
||||
};
|
||||
let target_id = oid.to_hex();
|
||||
if self
|
||||
.db
|
||||
.dast_targets()
|
||||
.find_one(doc! { "repo_id": &target_id })
|
||||
.await
|
||||
.ok()
|
||||
.flatten()
|
||||
.is_some()
|
||||
{
|
||||
return; // already provisioned
|
||||
}
|
||||
let kind = url
|
||||
.web
|
||||
.as_ref()
|
||||
.map(|w| w.target_kind.clone())
|
||||
.unwrap_or(DastTargetType::WebApp);
|
||||
let mut dast = DastTarget::new(target.name.clone(), url.source_ref.clone(), kind);
|
||||
dast.repo_id = Some(target_id);
|
||||
if let Some(web) = &url.web {
|
||||
dast.excluded_paths = web.excluded_paths.clone();
|
||||
dast.max_crawl_depth = web.max_crawl_depth;
|
||||
dast.rate_limit = web.rate_limit;
|
||||
dast.allow_destructive = web.allow_destructive;
|
||||
}
|
||||
if let Some(auth) = &url.auth {
|
||||
dast.auth_config = Some(DastAuthConfig {
|
||||
method: auth.method.clone(),
|
||||
login_url: auth.login_url.clone(),
|
||||
username: auth.username.clone(),
|
||||
password: None,
|
||||
token: auth.secret.clone(),
|
||||
headers: auth.headers.clone(),
|
||||
});
|
||||
}
|
||||
if let Err(e) = self.db.dast_targets().insert_one(&dast).await {
|
||||
tracing::warn!(error = %e, "Unified pipeline: failed to provision DAST target");
|
||||
}
|
||||
}
|
||||
|
||||
/// Sync the onboarded-target document after a scan: bump `findings_count`
|
||||
/// and advance the git artifact's `last_scanned_commit` watermark.
|
||||
async fn finalize_target(
|
||||
|
||||
Reference in New Issue
Block a user