Test Coverage and Compliance#
This flake includes an SCF-driven compliance layer that lets you:
- Apply Secure Controls Framework (SCF) controls as NixOS modules
- Attest each control against typed assessment objectives (the SCF PPTDF model)
- Run the runtime-verifiable assessment objectives inside a NixOS VM using the NixOS test framework
- Emit a structured evidence trail and render it to a Markdown attestation report
This document explains:
- What the compliance layer provides
- How to run it
- How it is wired together in the flake
- How to add or extend controls
The module lives under modules/compliance/:
default.nix– flake wiring: control registry, flake-level exports, the NixOS test, the attestation-report package, and thecomplianceNixOS moduleoptions.nix– thetsunaminoai.compliance.*option surface and the read-onlycompliance.evidenceoutputlib.nix– helpers (mkControl,mkAssessmentObjective,filterControlsByFrameworks)scf-data.nix– the authoritative SCF crosswalk:authoritativeSources(FDIs) andcontrols(per-control framework references and assessment objectives)controls/<SCF-ID>.nix– one file per implemented control
1. What You Get#
At a high level, this flake provides:
- Compliance data at flake level
Exposed under flake.compliance:
scfData– the importedscf-data.nixcrosswalk (authoritativeSources+controls)controls– all 8 implemented control attribute sets (allControlImpls)testFrameworks– the FDIs the flake-level NixOS test attests against-
testApplicable– the subset ofcontrolsrequired bytestFrameworks -
A compliance NixOS module
Exposed as flake.nixosModules.compliance, which:
- Merges every control’s
configToApplyinto the host configuration (gated behindtsunaminoai.compliance.enable) - Declares the
tsunaminoai.compliance.*options -
Exposes a read-only
compliance.evidenceattestation trail -
A compliance VM test
Exposed via perSystem.checks.compliance, which:
- Boots a NixOS VM with the compliance module enabled
- Runs the Technology-tier assessment-objective
checksnippets from the framework-applicable controls -
Is runnable with
nix flake checkornix build .#checks.<system>.compliance -
An attestation report package
Exposed via perSystem.packages.attestation-report, which renders the evidence trail
to Markdown via modules/compliance/scripts/render-report.py.
2. Quick Start#
2.1. Run all flake checks#
From the flake root:
# Runs every check under perSystem.checks.*, including the compliance VM test
nix flake check
The compliance check is the only compliance-related VM test wired into nix flake check.
(The cache and graphical checks are currently commented out in
modules/flake/checks/default.nix, pending issues #165 and #162.)
2.2. Run only the compliance VM test#
# Replace x86_64-linux with your system if different
nix build '.#checks.x86_64-linux.compliance' -L
This:
- Builds a NixOS VM with
tsunaminoai.compliance.enable = true - Applies the merged controls from the
complianceNixOS module - Runs the Technology-tier assessment-objective
checksnippets for the framework-applicable controls - Fails if any assertion fails
2.3. Render the attestation report#
nix build .#attestation-report && cat result
This renders the SCF evidence trail (control IDs, framework crosswalks, per-objective status and evidence prose) to Markdown.
2.4. Inspect a host’s evidence trail#
For any NixOS host that imports the compliance module with enable = true:
nix eval .#nixosConfigurations.<host>.config.compliance.evidence --json | jq .
3. Architecture#
3.1. Controls live one-per-file under controls/#
Each control is a separate file under modules/compliance/controls/, built with the
mkControl helper from lib.nix. The implemented controls are:
| File | scf_id |
Name |
|---|---|---|
CFG-02.nix |
CFG-02 |
Secure Baseline Configurations — SSH Hardening |
CFG-07.nix |
CFG-07 |
Least Functionality — Minimal Attack Surface |
NET-02.nix |
NET-02 |
Network Access Controls — Host Firewall |
LGO-02.nix |
LGO-02 |
Audit Logging — Linux Audit Subsystem |
LGO-06.nix |
LGO-06 |
Time Synchronization — chrony NTP |
VPM-03.nix |
VPM-03 |
Patch Management — NixOS Auto-Upgrade |
IAC-02.nix |
IAC-02 |
Account Management — Declarative User Provisioning |
IAC-06.nix |
IAC-06 |
Least Privilege — sudo Access Controls |
default.nix imports these files directly into allControlImpls:
allControlImpls = [
(import ./controls/CFG-02.nix {lib = l;})
(import ./controls/CFG-07.nix {lib = l;})
(import ./controls/NET-02.nix {lib = l;})
(import ./controls/LGO-02.nix {lib = l;})
(import ./controls/LGO-06.nix {lib = l;})
(import ./controls/VPM-03.nix {lib = l;})
(import ./controls/IAC-02.nix {lib = l;})
(import ./controls/IAC-06.nix {lib = l;})
];
example.nix is just a reference listing
modules/compliance/example.nix is not the source of truth. It only enumerates
controls for interactive exploration, e.g.:
nix eval --file modules/compliance/example.nix \
--apply 'ctrls: map (c: c.scf_id) ctrls'
Control schema (mkControl)#
Each control is created with mkControl (lib.nix) and has:
name– human-readable control namescf_id– the real SCF control identifier (e.g.CFG-02)configToApply– a NixOS module fragment (attrset) that enforces the controlassessmentObjectives– a list of typed assessment-objective records (see below)alerts– optional metadata for alerting systemsmonitors– optional metadata for external monitoringmetadata– control metadata, including aframeworkscrosswalk (FDI → reference string)
Legacy arguments are ignored
external_id, applicable, and group are accepted for backwards compatibility
but are otherwise ignored when scf_id is present. There is no .applicable
filtering in the current schema, and there is no flat checks string list — runtime
assertions live inside assessmentObjectives.
Assessment objectives (mkAssessmentObjective)#
Each entry models one SCF assessment objective in the PPTDF (Process / People / Technology / Data / Facilities) framework:
id– objective id (e.g.CFG-02_A03)text– the SCF assessment-objective textpptdf– the tier ("Process","People","Technology","Data","Facilities")rigor– an integer rigor levelcheck(optional) – a Python snippet (run againstmachinein the NixOS test). Used for Technology/Data-tier objectives that are runtime-verifiable.evidence(optional) – attestation prose for Process/People-tier objectives.status(optional) – overridden at build time ("automatable"when acheckis present, otherwise"configured").
Example (the real CFG-02 control, abridged):
{lib}:
lib.mkControl {
name = "Secure Baseline Configurations — SSH Hardening";
scf_id = "CFG-02";
configToApply = {
services.openssh = {
enable = true;
settings = {
PasswordAuthentication = false;
PubkeyAuthentication = true;
PermitRootLogin = "prohibit-password";
StrictModes = true;
X11Forwarding = false;
PermitEmptyPasswords = false;
};
};
environment.etc."ssh/sshd_config" = {
mode = "0600";
user = "root";
group = "root";
};
};
assessmentObjectives = [
# Process tier: build-time attestation (no live check)
(lib.mkAssessmentObjective {
id = "CFG-02_A01";
text = "a current baseline configuration ... is developed and documented.";
pptdf = "Process";
rigor = 1;
status = "configured";
evidence = "SSH baseline declared in NixOS module CFG-02.nix (pure, reproducible, git-tracked)";
})
# Technology tier: runtime NixOS test assertion
(lib.mkAssessmentObjective {
id = "CFG-02_A03";
text = "security configuration settings ... are enforced.";
pptdf = "Technology";
rigor = 1;
evidence = "sshd_config enforces PasswordAuthentication no, PubkeyAuthentication yes, ...";
check = ''
machine.succeed("systemctl is-active sshd")
print("PASS CFG-02_A03: sshd service is active")
machine.succeed("grep -E '^PasswordAuthentication no$' /etc/ssh/sshd_config")
print("PASS CFG-02_A03: PasswordAuthentication is disabled")
'';
})
];
metadata = {
scf_domain = "Configuration Management";
control_type = "Preventive";
implementation_status = "Implemented";
frameworks = {
"general-nist-800-53-r5" = "CM-2, CM-6, CM-7";
"general-pci-dss-4-0-1" = "1.1, 1.2.1, 2.2, 2.2.1, 8.3.2, 8.5";
"us-federal-cmmc-2-l2" = "CM.L2-3.4.1, CM.L2-3.4.2";
"general-cis-csc-8-1" = "4.1, 4.2";
"general-iso-27001-2022" = "A.8.9";
};
};
}
3.2. Framework selection and aggregation#
Selection is driven by the SCF crosswalk in scf-data.nix, not by any per-control
.applicable flag. The filterControlsByFrameworks helper returns the controls whose
frameworks.<FDI> reference is non-empty for any selected framework:
# Controls required by the test frameworks (used to build the check test).
testFrameworks = ["general-pci-dss-4-0-1" "general-nist-800-53-r5"];
testRequiredIds = builtins.attrNames (l.filterControlsByFrameworks scfData.controls testFrameworks);
testApplicable = builtins.filter (c: builtins.elem c.scf_id testRequiredIds) allControlImpls;
The flake-level NixOS test only runs the framework-applicable controls’ Technology-tier checks:
testChecks =
lib.concatMap
(ctrl: map (ao: ao.check) (builtins.filter (ao: ao ? check) ctrl.assessmentObjectives))
testApplicable;
The NixOS module applies ALL controls
Framework filtering is a flake-level concern. The compliance NixOS module
deliberately merges every control in allControlImpls, not a framework-filtered
subset:
mergedNixosConfig = lib.foldl' lib.recursiveUpdate {} (map (c: c.configToApply) allControlImpls);
Filtering by cfg.frameworks here would make mergedNixosConfig depend on
config.tsunaminoai.compliance.frameworks, creating a config fixed-point cycle the
module system cannot resolve. Applying all controls avoids that cycle.
3.3. Flake-level exports#
flake.compliance = {
scfData = scfData; # imported scf-data.nix crosswalk
controls = allControlImpls; # all 8 control implementations
testFrameworks = testFrameworks;
testApplicable = testApplicable;
};
3.4. The compliance NixOS module#
flake.nixosModules.compliance declares the options (via ./options.nix), merges all
controls, and emits the evidence trail — all gated behind lib.mkIf cfg.enable:
flake.nixosModules.compliance = { config, lib, ... }: let
cfg = config.tsunaminoai.compliance;
mergedNixosConfig = lib.foldl' lib.recursiveUpdate {} (map (c: c.configToApply) allControlImpls);
evidenceTrail = map (ctrl: {
scf_id = ctrl.scf_id;
name = ctrl.name;
domain = ctrl.metadata.scf_domain or "Unknown";
frameworks_covered = /* FDIs with a non-empty crosswalk in scf-data.nix */;
framework_references = ctrl.metadata.frameworks or {};
config_applied = ctrl.configToApply;
assessment_objectives = /* AOs with derived status */;
}) allControlImpls;
in {
imports = [./options.nix];
config = lib.mkIf cfg.enable (mergedNixosConfig // {
compliance.evidence = evidenceTrail;
});
};
Importing the module is not enough
The entire config — both the merged controls and the evidence trail — is behind
lib.mkIf cfg.enable. Importing self.nixosModules.compliance applies nothing
until you also set tsunaminoai.compliance.enable = true.
3.5. Options#
The module declares the following options (see options.nix):
| Option | Type | Description |
|---|---|---|
tsunaminoai.compliance.enable |
bool | Enable the SCF compliance controls (off by default). |
tsunaminoai.compliance.frameworks |
list of str | SCF Focal Document Identifiers (FDIs) this host must comply with. Valid FDIs are the authoritativeSources keys in scf-data.nix (e.g. general-nist-800-53-r5, general-pci-dss-4-0-1, us-federal-cmmc-2-l2). |
tsunaminoai.compliance.additionalControls |
list of str | Extra SCF control IDs to apply regardless of framework selection (e.g. ["CFG-02" "IAC-06"]). |
compliance.evidence |
list of attrs | Read-only attestation trail. Each entry contains scf_id, name, domain, frameworks_covered, framework_references, config_applied, and assessment_objectives (with per-AO status). |
Enabling compliance on a host looks like:
{
imports = [
# ...
self.nixosModules.compliance
];
tsunaminoai.compliance = {
enable = true;
frameworks = ["general-nist-800-53-r5" "general-pci-dss-4-0-1"];
};
}
4. The Compliance VM Test#
The compliance test is provided via perSystem.checks.compliance and is defined using
pkgs.testers.runNixOSTest:
perSystem = {pkgs, ...}: {
checks.compliance = pkgs.testers.runNixOSTest {
name = "SCF Compliance Check — CFG-02, CFG-07, NET-02, LGO-02, LGO-06, VPM-03, IAC-02, IAC-06";
nodes.machine = {
imports = [self.nixosModules.compliance];
tsunaminoai.compliance = {
enable = true;
frameworks = testFrameworks;
};
system.stateVersion = "26.05";
};
testScript = ''
machine.wait_for_unit("default.target")
print("System booted with SCF compliance module (frameworks: ...)")
${builtins.concatStringsSep "\n" testChecks}
print("All SCF assessment objective checks passed")
'';
};
};
Key points:
- The single VM node is named
machine. - The node sets
tsunaminoai.compliance.enable = true(required — see §3.4) andframeworks = testFrameworks. testScriptboots the VM, then runs every framework-applicable Technology-tier assessment-objectivechecksnippet (testChecks) againstmachine.
There is no longer any flat per-control checks list, and there are no separate
security-vm-* checks — SSH, firewall, file-permission, audit, and similar assertions
are assessment-objective check snippets inside the relevant controls (CFG-02, NET-02,
LGO-02, etc.) and are all executed by this single compliance test.
5. The Attestation Report#
perSystem.packages.attestation-report renders the evidence trail for the
framework-applicable controls to a self-contained Markdown document:
packages.attestation-report =
pkgs.runCommand "attestation-report.md" {
buildInputs = [pkgs.python3];
passAsFile = ["evidenceJson"];
evidenceJson = reportEvidenceJson;
} ''
python3 ${./scripts/render-report.py} "$evidenceJsonPath" > $out
'';
Build and view it with:
nix build .#attestation-report && cat result
The renderer (modules/compliance/scripts/render-report.py) walks each control’s
assessment objectives, deriving status = "automatable" for objectives that carry a
check and status = "configured" for Process/People-tier objectives attested by
their evidence prose.
6. How to Add a New Control#
There is a skill for this
The scf-add-control skill automates these steps. Invoke it to add a new
NixOS-implementable SCF control to the module.
To do it by hand:
- Create
modules/compliance/controls/<SCF-ID>.nix. Return a singlemkControlrecord:
{lib}:
lib.mkControl {
name = "...";
scf_id = "<SCF-ID>";
configToApply = { /* NixOS fragment that enforces the control */ };
assessmentObjectives = [
(lib.mkAssessmentObjective {
id = "<SCF-ID>_A01";
text = "...";
pptdf = "Technology"; # or Process / People / Data / Facilities
rigor = 1;
check = ''machine.succeed("...")''; # Technology/Data tier
# evidence = "..."; # Process/People tier
})
];
metadata = {
scf_domain = "...";
frameworks = {
"general-nist-800-53-r5" = "...";
# other FDIs the control maps to
};
};
}
- Register it in
allControlImplsinmodules/compliance/default.nix:
allControlImpls = [
# ...
(import ./controls/<SCF-ID>.nix {lib = l;})
];
-
Add the crosswalk entry to
scf-data.nix. Add the control undercontrols(with itsframeworksreferences and assessment objectives) sofilterControlsByFrameworksand theframeworks_coveredevidence field pick it up. -
Rebuild and run the test:
nix build '.#checks.x86_64-linux.compliance' -L
- Inspect output – you should see
PASS <SCF-ID>_...lines for the new control’s Technology-tier objectives (when the control is referenced bytestFrameworks).
7. CI / Automation#
Because the compliance test is wired through perSystem.checks.compliance, it works
naturally with:
nix flake checklocally- CI systems that run
nix flake checkor build.#checks.<system>.compliancedirectly
A typical CI setup might:
- Run
nix flake checkon every push / PR - Build
.#checks.x86_64-linux.compliance -Lfor better error isolation - Build
.#attestation-reportand publish the rendered Markdown as an artifact - Fail the pipeline if any compliance assertion fails
8. Summary#
This flake’s compliance layer gives you:
- Declarative SCF controls as Nix expressions, one file per control under
controls/ - Typed assessment objectives (PPTDF) with runtime
checksnippets orevidenceprose - Framework-driven selection via the
scf-data.nixcrosswalk andfilterControlsByFrameworks - VM-based validation of the runtime-verifiable objectives via a single NixOS test
- A structured evidence trail (
compliance.evidence) and a rendered attestation report
Use:
nix flake checkfor the full suitenix build '.#checks.<system>.compliance' -Lfor targeted compliance testingnix build .#attestation-report && cat resultfor the rendered evidence report- The
scf-add-controlskill (or §6) to add a new control