Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/holger

server/lib/src/export_profile.rs

//! ★ **The export's decision profile** (lane HOL-NCSC, NCSC P2): what crosses from the online
//! side to the airgap side.
//!
//! An export (`holger export`, `BundleService.Export`, the diode push of `export:`, all through
//! [`crate::export_door::ExportDoor`]) carries what the secure side decides by: the bytes and
//! their digests, the signed scan evidence, the SBOM, the approvals, the provenance material,
//! the bundle's sequence and validity window, and the sync manifest's packages and
//! invalidations. It never carries what only the online box knows about itself: the
//! importer's or an operator's name (the approvers are the policy's, and cross), free-text
//! reasons and policy words, timings, its repository names, source names, addresses or paths.
//!
//! [`PROFILE`] classifies every field of every structure that crosses; the guard
//! `server/lib/tests/the_export_carries_only_what_decides.rs` reads the structures from their
//! source and fails on a field with no class. There is one profile and no switch: nothing on
//! the secure side needs the full record, and a switch would be a way to send it.

use crate::promote::intake::{ArtifactState, Evidence, IntakeEntry};

/// How a field of an exported structure crosses the boundary.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Class {
    /// Carried as it is: the secure side decides by it.
    Kept,
    /// Carried only on the transition that needs it: the approver on the APPROVED line.
    KeptOnApproval,
    /// Carried inside a signature made on the online side (the scan evidence, the sync
    /// manifest): what a scanner said, verified on the secure side; it holds no operator text.
    Signed,
    /// Carried as its SHA-256 only: free text whose binding matters, never its words. A
    /// value that already is a digest reference (`sha256:`, `blake3:`, `scan-evidence `)
    /// is kept.
    Digested,
    /// Never carried (left empty, zero or absent).
    Stripped,
}

use Class::*;

/// One exported structure: `(file, struct, [(field, class)])`.
pub type Structure = (&'static str, &'static str, &'static [(&'static str, Class)]);

/// Every exported structure, every field.
pub const PROFILE: &[Structure] = &[
    (
        "server/lib/src/promote/airgap.rs",
        "ExportIndex",
        &[
            ("schema", Kept),
            ("exported_at", Stripped),
            ("exported_by", Stripped),
            ("production", Stripped),
            ("verdict", Kept),
            ("computed_from", Kept),
            ("artifacts", Kept),
        ],
    ),
    (
        "server/lib/src/promote/airgap.rs",
        "ExportedArtifact",
        &[
            ("namespace", Kept),
            ("name", Kept),
            ("version", Kept),
            ("payload", Kept),
            ("sha256", Kept),
            ("blake3", Kept),
            ("size", Kept),
            ("verdict", Kept),
            // Computed from the evidence by the fold, never typed.
            ("reasons", Kept),
            ("ran", Kept),
            ("not_run", Kept),
            ("evidence", Kept),
            ("attestations", Kept),
            ("signer_cert", Kept),
            ("sbom", Kept),
            ("intake_record", Kept),
            ("approvers", Kept),
            ("provenance", Kept),
            // What the package says about itself for its index (lane HOL-PERF-2): read
            // out of the bytes by the online side's scan worker, no operator text.
            ("index_row", Kept),
        ],
    ),
    (
        "server/lib/src/promote/intake.rs",
        "IntakeEntry",
        &[
            ("from", Kept),
            ("to", Kept),
            ("artifact", Kept),
            ("sha256", Kept),
            ("repo", Stripped),
            ("actor", KeptOnApproval),
            ("at", Stripped),
            ("evidence", Kept),
            ("policy", Stripped),
            ("reason", Stripped),
            ("origin", Stripped),
            ("effective_policy", Stripped),
        ],
    ),
    (
        "server/lib/src/promote/intake.rs",
        "Evidence",
        &[
            ("kind", Kept),
            ("source", Kept),
            ("verdict", Kept),
            ("reference", Digested),
        ],
    ),
    (
        "update-bundle/src/core.rs",
        "BundleCore",
        &[
            ("schema_version", Kept),
            ("bundle_id", Kept),
            ("sequence", Kept),
            // The validity window the secure side holds the bundle to.
            ("created_at", Kept),
            ("expires_at", Kept),
            // The program and its version (`holger <v> BundleService.Export`).
            ("producer", Kept),
            ("supplier", Kept),
            ("product", Kept),
            ("release_version", Kept),
            ("supported_hardware_models", Kept),
            ("supported_hardware_revisions", Kept),
            ("source_provenance", Kept),
            ("payloads", Kept),
            ("evidence", Kept),
            ("policy_result", Kept),
            ("supersedes", Kept),
            ("withdraws", Kept),
        ],
    ),
    (
        "update-bundle/src/core.rs",
        "SourceProvenance",
        &[
            ("source_kind", Kept),
            ("source_uri", Stripped),
            ("source_repository", Stripped),
            ("source_asset_path", Stripped),
            ("fetched_at", Stripped),
            ("reported_size", Stripped),
            ("vendor_checksum", Stripped),
            ("vendor_signature_status", Kept),
            ("tls_origin", Stripped),
            ("transfer_receipt_digest", Stripped),
        ],
    ),
    (
        "update-bundle/src/core.rs",
        "PolicyResult",
        &[
            ("policy_digest", Kept),
            ("advisory_snapshot_digest", Kept),
            ("verdict", Kept),
            ("non_production", Kept),
        ],
    ),
    (
        "update-bundle/src/core.rs",
        "PayloadEntry",
        &[
            ("relative_path", Kept),
            ("media_type", Kept),
            ("size_bytes", Kept),
            ("sha256", Kept),
            ("blake3", Kept),
        ],
    ),
    (
        "update-bundle/src/core.rs",
        "EvidenceEntry",
        &[
            ("relative_path", Kept),
            ("media_type", Kept),
            ("size_bytes", Kept),
            ("sha256", Kept),
            ("blake3", Kept),
            ("evidence_kind", Kept),
        ],
    ),
    (
        "server/lib/src/scanner/mod.rs",
        "Evidence",
        &[
            ("schema", Signed),
            ("subject", Signed),
            ("verdict", Signed),
            ("reasons", Signed),
            ("records", Signed),
            ("scanned_at", Signed),
        ],
    ),
    (
        "server/lib/src/scanner/mod.rs",
        "EvidenceSubject",
        &[
            ("file_name", Signed),
            ("ecosystem", Signed),
            ("name", Signed),
            ("version", Signed),
            ("size", Signed),
            ("inputs", Signed),
        ],
    ),
    (
        "server/lib/src/scanner/mod.rs",
        "ScanEvidence",
        &[
            ("scanner_id", Signed),
            ("scanner_version", Signed),
            ("kind", Signed),
            ("verdict", Signed),
            ("findings", Signed),
            ("inputs", Signed),
            ("document", Signed),
            ("detail", Signed),
            ("required", Signed),
            ("intel_sequence", Signed),
            ("hold", Signed),
        ],
    ),
    (
        "sync/src/lib.rs",
        "SyncManifest",
        &[
            ("schema", Signed),
            ("upstream", Signed),
            ("generation", Signed),
            ("issued_at", Signed),
            ("carrier", Signed),
            ("added", Signed),
            ("invalidations", Signed),
        ],
    ),
    (
        "sync/src/lib.rs",
        "Invalidation",
        &[
            ("id", Signed),
            ("coordinate", Signed),
            ("sha256", Signed),
            ("reason", Signed),
            // One sentence a downstream shows: the CVE, the scanner hit, the notice.
            ("detail", Signed),
            ("generation", Signed),
            ("issued_at", Signed),
            ("issued_by", Stripped),
        ],
    ),
    (
        "sync/src/lib.rs",
        "PackageRef",
        &[("coordinate", Signed), ("sha256", Signed)],
    ),
];

/// What the importer, the admin and the other non-approving actors are called on the
/// airgap side.
pub const ONLINE_ACTOR: &str = "online";

/// Pure: a reference that is already a digest, kept as it is.
fn is_digest_reference(r: &str) -> bool {
    r.starts_with("sha256:") || r.starts_with("blake3:") || r.starts_with("scan-evidence ")
}

/// ★ **The line of an intake record the export carries** ([`PROFILE`]'s `IntakeEntry` and
/// `Evidence` rows): the transition, its coordinate and bytes, its evidence slots (free-text
/// references as their SHA-256), the approver on the APPROVED line; no repository, timing,
/// policy words, reason, source or effective policy, and no other actor's name.
pub fn decision_entry(e: &IntakeEntry) -> IntakeEntry {
    IntakeEntry {
        from: e.from,
        to: e.to,
        artifact: e.artifact.clone(),
        sha256: e.sha256.clone(),
        repo: String::new(),
        actor: if e.to == ArtifactState::Approved {
            e.actor.clone()
        } else {
            ONLINE_ACTOR.to_string()
        },
        at: 0,
        evidence: e.evidence.iter().map(decision_evidence).collect(),
        policy: String::new(),
        reason: String::new(),
        origin: String::new(),
        effective_policy: None,
    }
}

fn decision_evidence(ev: &Evidence) -> Evidence {
    let mut out = ev.clone();
    if !out.reference.is_empty() && !is_digest_reference(&out.reference) {
        out.reference = format!(
            "sha256:{}",
            update_bundle::digest::sha256_hex(out.reference.as_bytes())
        );
    }
    out
}