diff --git a/CHANGELOG.md b/CHANGELOG.md index 2d98570..730b3e6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -140,6 +140,17 @@ but container releases use the upstream-derived format documented in - Fixed a race in the reload test that read `/proc/PID/status` for a worker that had already exited, which aborted the listing instead of skipping the vanished process. +- Resolved the Application Server SRG, pinning V4R5 by digest and recording it + as not applicable to this component: 27 of its 137 rules presuppose a + management interface or hosted applications and 18 refer to accounts, none of + which exist here. Pinned rather than dropped so the determination stays tied + to the revision it was made against. +- Recorded why the DISA Container Platform SRG cannot be retrieved: V2R1 is + officially released and its filename follows DISA's convention, but it is no + longer served from the public download path, and the download index is now a + JavaScript-rendered portal whose HTML contains no download links. It stays + unresolved with the release, filename, and finding recorded, so the search is + not repeated. - Established the requirement-source register, pinning the 800-53 Rev 5.2.0 catalogue, its High baseline profile, the DISA Web Server SRG, and the RHEL 9 STIG by digest, and recording the Container Platform SRG, Application Server diff --git a/artifacts/requirement-sources.json b/artifacts/requirement-sources.json index e97fbc4..6eb5f32 100644 --- a/artifacts/requirement-sources.json +++ b/artifacts/requirement-sources.json @@ -71,8 +71,8 @@ "id": "disa-container-platform-srg", "publisher": "Defense Information Systems Agency", "title": "Container Platform Security Requirements Guide", - "release": null, - "released_on": null, + "release": "V2R1", + "released_on": "2024-07-24", "retrieved_on": null, "url": null, "sha256": null, @@ -81,23 +81,23 @@ "role": "cross-reference", "license": "US Government work; redistribution terms stated in the package", "redistributable": false, - "notes": "Named in the roadmap but not located at a predictable URL on 2026-09-18. DISA rotates release identifiers in the filename, so the exact current package has to be obtained from the published download index by a person rather than guessed. Blocking for the container-platform cross-reference only; the spine does not depend on it." + "notes": "Officially released as V2R1 on 2024-07-24, and the filename follows DISA's published convention as U_Container_Platform_V2R1_SRG.zip, but it is no longer served from the public download path: that URL returns 404 while U_Web_Server_V3R3_SRG.zip returns 200 from the same directory, so the path and method are sound and the file is not there. The download index cannot be used to locate it either; it is now a JavaScript-rendered portal whose HTML contains no download links, so it cannot be resolved programmatically. Retrieval needs a browser session or the SRG-STIG library compilation. Blocking for the container-platform cross-reference only; the spine does not depend on it." }, { "id": "disa-application-server-srg", "publisher": "Defense Information Systems Agency", "title": "Application Server Security Requirements Guide", - "release": null, - "released_on": null, - "retrieved_on": null, - "url": null, - "sha256": null, - "size": null, - "status": "unresolved", - "role": "cross-reference", + "release": "V4R5", + "released_on": "2026-07-01", + "retrieved_on": "2026-09-18", + "url": "https://dl.dod.cyber.mil/wp-content/uploads/stigs/zip/U_Application_Server_V4R5_SRG.zip", + "sha256": "ea33d7f18f950e86c9e0cc63835cf8802d319804ac143b2020b1fbac13ff2643", + "size": 1253893, + "status": "pinned", + "role": "not-applicable", "license": "US Government work; redistribution terms stated in the package", "redistributable": false, - "notes": "Same situation as the container platform SRG. Applicability is also uncertain: this image serves and proxies HTTP rather than hosting an application runtime, so the web server SRG may be the only applicable one. Decide applicability before pinning." + "notes": "Retrieved and pinned, then assessed as not applicable to this component; see docs/adr/0010-application-server-srg-not-applicable.md. Of its 137 rules, 27 presuppose a management interface or hosted applications and 18 refer to accounts. This image has no management interface, no accounts, and hosts no application, so those rules govern objects that do not exist here; the remainder are stated for a component of this kind by the Web Server SRG, which is the applicable DISA cross-reference. Pinned rather than dropped so the determination is tied to the revision it was made against." }, { "id": "cis-benchmark", diff --git a/docs/CONTROL-MODEL.md b/docs/CONTROL-MODEL.md index eea3e0c..a2aa82c 100644 --- a/docs/CONTROL-MODEL.md +++ b/docs/CONTROL-MODEL.md @@ -93,6 +93,13 @@ replaces it, and the difference is then visible rather than silent. CIS is cited by identifier only. Its terms restrict redistribution, so no benchmark text is reproduced here. +The Application Server SRG is pinned but carries the role `not-applicable`: it +presupposes a management interface, accounts, and hosted applications, none of +which exist here. The reasoning and the counts behind that are in +[ADR-0010](adr/0010-application-server-srg-not-applicable.md). It is pinned +rather than dropped so the determination stays tied to the revision it was made +against. + ## Assessment methods Every `image-owned` control carries an examine, test, or interview method with diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index 28b4c02..366a8b2 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -108,13 +108,15 @@ resolved. ### Foundation -- [ ] Resolve the three outstanding sources recorded as unresolved or - unavailable in `artifacts/requirement-sources.json`: the DISA Container - Platform SRG and Application Server SRG need their exact current packages - obtained from the published download index, and the CIS benchmark needs an - account holder to retrieve it and record its digest. Decide whether the - Application Server SRG applies at all, since this image serves and proxies - HTTP rather than hosting an application runtime. +- [ ] Obtain the two sources in `artifacts/requirement-sources.json` that + cannot be retrieved without a person. The DISA Container Platform SRG V2R1 is + no longer served from the public download path and the download index is a + JavaScript-rendered portal with no links in its HTML, so it needs a browser + session or the SRG-STIG library compilation; the CIS benchmark needs an + account holder to retrieve it and record its digest. Neither blocks the + spine. The Application Server SRG applicability question is closed: it is + pinned and assessed not applicable, per + [ADR-0010](adr/0010-application-server-srg-not-applicable.md). - [ ] Verify the pinned sources against their recorded digests as a scheduled check, so a replaced DISA package is reported rather than silently mapped against. The structural checks over the component definition are in place. diff --git a/docs/adr/0010-application-server-srg-not-applicable.md b/docs/adr/0010-application-server-srg-not-applicable.md new file mode 100644 index 0000000..59d6666 --- /dev/null +++ b/docs/adr/0010-application-server-srg-not-applicable.md @@ -0,0 +1,113 @@ +--- +status: accepted +date: 2026-09-18 +decision-makers: Joey +--- + +# Treat the Application Server SRG as not applicable to this component + +## Context and Problem Statement + +[ADR-0009](0009-control-catalogue-and-baseline.md) settled that DISA SRGs and +STIGs are cross-references attached to the 800-53 spine, and that every source +is pinned by URL, release, and SHA-256 before any mapping is made against it. + +The requirement-source register named four DISA cross-references. Two resolved +immediately: the Web Server SRG and the RHEL 9 STIG. Two did not, and were +recorded as `unresolved` with the reason rather than omitted. The register also +recorded an open question about one of them: whether the **Application Server +SRG** applies to this component at all, given that the image serves and proxies +HTTP rather than hosting an application runtime. + +That question had been answered by assumption. An SRG is either in scope or it +is not, and an assessor reading the component definition is entitled to know +which, and on what basis. Guessing in either direction is the failure mode: +including an inapplicable SRG manufactures 137 rules of hand-waving, and +excluding an applicable one silently drops a body of guidance. + +## Decision Drivers + +* The applicability of a cross-reference should be decided from its contents, + not from its title +* An assessor will ask why a named SRG is absent; "we assumed" is not an answer +* A not-applicable determination is a claim in its own right and needs evidence +* The decision must be cheap to reverse if the component's function changes + +## Considered Options + +* **Map it** — treat the Application Server SRG as a cross-reference alongside + the Web Server SRG, marking individual rules not applicable as they arise +* **Exclude it without pinning** — leave it `unresolved` and out of scope +* **Pin it, and record it as assessed not applicable** — retrieve the package, + fix its identity by digest, read it, and record the determination against + that exact revision + +## Decision Outcome + +**Pin it, and record it as assessed not applicable.** + +The package was retrieved and pinned as `V4R5`, benchmark date 01 Jul 2026, +SHA-256 `ea33d7f1…`, in `artifacts/requirement-sources.json` with +`"role": "not-applicable"`. Pinning a source this project does not map against +looks redundant until the determination is questioned: the digest is what ties +the determination to the revision it was made against. + +### The evidence + +The V4R5 XCCDF contains 137 rules. Counted across them: + +| | | +| --- | --- | +| Rules presupposing a management or administrative interface | 23 | +| Rules presupposing hosted applications | 6 | +| Rules presupposing either | **27** | +| Rules referring to accounts | 18 | + +This image has no management interface, no user accounts, and hosts no +application. Those rules are not merely unsatisfied; the objects they govern do +not exist here. The remainder — logging, TLS, session handling — are the +general requirements the **Web Server SRG** states for a component of this +kind, and it states them in terms that match what this image actually is. + +The Web Server SRG therefore remains the applicable DISA cross-reference for +this component's function, as the register already recorded. + +### Consequences + +* Good: the determination cites a revision and a count, so a reader can check it + rather than accept it +* Good: no cross-reference work is spent on a body of guidance whose subject + this component is not +* Good: reversal is cheap. The source is already pinned, so revisiting means + changing a role and authoring mappings, not re-establishing identity +* Bad: a reader scanning the register for DISA coverage sees an + application-server SRG present and may assume it is mapped. The + `not-applicable` role and this ADR are the mitigation, and a structural check + requires the basis to be recorded +* Bad: the determination is made against V4R5 only. If DISA broadens the SRG's + scope in a later release, this must be revisited — which is exactly what + pinning by digest makes visible + +### The Container Platform SRG is a different problem + +It is not a scope question. The document exists and is officially released — +**V2R1**, 24 July 2024 — and the filename follows DISA's published convention, +`U_Container_Platform_V2R1_SRG.zip`. It is simply no longer served from the +public download path: that URL returns 404 while `U_Web_Server_V3R3_SRG.zip` +returns 200 from the same directory, so the path and method are sound and the +file is genuinely not there. + +The download index cannot be used to find its current location either. It is +now a JavaScript-rendered portal whose HTML contains no download links at all, +so it cannot be resolved programmatically. + +It therefore stays `unresolved` — with the release, filename, and this finding +recorded, so whoever retrieves it is not repeating the search. It is a missing +cross-reference, not a missing part of the spine, and it blocks nothing. + +### Enforcement + +`tests/test_control_model.py` requires a source whose role is `not-applicable` +to record the basis for that determination, on the same principle that an +unpinned source must record why it is unpinned: a determination without a +stated reason is indistinguishable from an omission. diff --git a/docs/adr/README.md b/docs/adr/README.md index 1b352b6..77e6fa4 100644 --- a/docs/adr/README.md +++ b/docs/adr/README.md @@ -50,3 +50,4 @@ record says so, because an unenforced decision decays quietly. | [0007](0007-liveness-independent-of-upstreams.md) | Keep liveness independent of upstream health | accepted | | [0008](0008-hand-authored-svg-diagrams.md) | Author diagrams as hand-written SVG with no rendering toolchain | accepted | | [0009](0009-control-catalogue-and-baseline.md) | Map controls against 800-53 High as the spine, with SRG, STIG and CIS as cross-references | accepted | +| [0010](0010-application-server-srg-not-applicable.md) | Treat the Application Server SRG as not applicable to this component | accepted | diff --git a/tests/test_control_model.py b/tests/test_control_model.py index b1e0c24..5fc607b 100644 --- a/tests/test_control_model.py +++ b/tests/test_control_model.py @@ -111,6 +111,18 @@ def test_the_spine_and_baseline_are_pinned(self) -> None: for role in ("spine", "baseline"): self.assertEqual(by_role[role]["status"], "pinned") + def test_a_source_ruled_not_applicable_records_the_basis(self) -> None: + # Deciding an SRG does not apply is a claim in its own right. Without a + # stated basis it is indistinguishable from having forgotten it. + for source in register()["sources"]: + if source["role"] != "not-applicable": + continue + with self.subTest(source=source["id"]): + self.assertTrue( + (source.get("notes") or "").strip(), + "a source ruled not applicable must record why", + ) + def test_a_non_redistributable_source_is_marked_as_such(self) -> None: for source in register()["sources"]: with self.subTest(source=source["id"]):