diff --git a/.github/requirements/cybersecurity.txt b/.github/requirements/cybersecurity.txt new file mode 100644 index 0000000..0dd6a3d --- /dev/null +++ b/.github/requirements/cybersecurity.txt @@ -0,0 +1,14 @@ +attrs==26.1.0 \ + --hash=sha256:c647aa4a12dfbad9333ca4e71fe62ddc36f4e63b2d260a37a8b83d2f043ac309 +jsonschema==4.25.1 \ + --hash=sha256:3fba0169e345c7175110351d456342c364814cfcf3b964ba4587f22915230a63 +jsonschema-specifications==2025.9.1 \ + --hash=sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe +referencing==0.37.0 \ + --hash=sha256:381329a9f99628c9069361716891d34ad94af76e461dcb0335825aecc7692231 +regex==2026.9.10 \ + --hash=sha256:bafa41b0dd63669e5c0f8adf3d24819efeb73c847f492eb011212eb352e69041 +rpds-py==2026.6.3 \ + --hash=sha256:acac386b453c2516111b50985d60ce46e7fadb5ea71ae7b25f4c946935bf27cf +typing-extensions==4.16.0 \ + --hash=sha256:481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1debb27..ac5d1f4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -37,6 +37,7 @@ jobs: python -m pip install --require-hashes --only-binary=:all: --disable-pip-version-check --requirement .github/requirements/pre-commit.txt + --requirement .github/requirements/cybersecurity.txt - name: Run repository checks run: pre-commit run --all-files --show-diff-on-failure @@ -44,6 +45,17 @@ jobs: - name: Run artifact lock negative tests run: python -m unittest discover -s tests -p 'test_*.py' -v + - name: Validate OSCAL component definition against the official schema + run: | + schema_file="$(mktemp)" + trap 'rm -f "${schema_file}"' EXIT + curl --fail --location --proto '=https' --tlsv1.2 \ + --output "${schema_file}" \ + https://github.com/usnistgov/OSCAL/releases/download/v1.2.3/oscal_component_schema.json + echo '95e76881151ececd5cb1a93ff0f70ad74b8cc1aa58771626ac8b262bf2c8e001 '"${schema_file}" \ + | sha256sum --check --strict + python scripts/cybersecurity.py --check --oscal-schema "${schema_file}" + - name: Validate committed artifact locks run: | python scripts/artifacts.py validate-inputs artifacts/lock-inputs.json @@ -193,6 +205,18 @@ jobs: IMAGE: ${{ env.TEST_IMAGE }} run: bash tests/tls.sh + - name: Record cryptographic linkage inventory + run: | + docker run --rm --entrypoint /bin/sh "${TEST_IMAGE}" -ec ' + uname -m + /usr/pgsql-18/bin/postgres --version + /usr/pgsql-18/bin/pg_config --configure + ldd /usr/pgsql-18/bin/postgres + ldd /usr/pgsql-18/bin/psql + if command -v openssl >/dev/null; then openssl version -a; else echo "openssl executable absent"; fi + ' > "crypto-inventory-${{ matrix.architecture }}.txt" + test -s "crypto-inventory-${{ matrix.architecture }}.txt" + - name: Scan image with Trivy uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0 with: @@ -253,6 +277,7 @@ jobs: ${{ env.PROVENANCE_FILE }} ${{ env.GRYPE_SARIF }} ${{ env.GRYPE_ALL }} + crypto-inventory-${{ matrix.architecture }}.txt if-no-files-found: warn retention-days: 14 diff --git a/.github/workflows/scap.yml b/.github/workflows/scap.yml new file mode 100644 index 0000000..6296eae --- /dev/null +++ b/.github/workflows/scap.yml @@ -0,0 +1,119 @@ +name: SCAP discovery + +on: + pull_request: + push: + branches: + - main + schedule: + - cron: "41 8 * * 1" + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: scap-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + image-filesystem: + name: image filesystem (${{ matrix.architecture }}) + strategy: + fail-fast: false + matrix: + include: + - architecture: amd64 + runner: ubuntu-24.04 + machine: x86_64 + - architecture: arm64 + runner: ubuntu-24.04-arm + machine: aarch64 + runs-on: ${{ matrix.runner }} + timeout-minutes: 60 + env: + TEST_IMAGE: localhost/postgresql-ubi9:scap-${{ matrix.architecture }} + SCAP_ARCHIVE: scap-security-guide-0.1.81.zip + SCAP_ARCHIVE_SHA256: 865e28b793e1e65f7f0102434bc7d962324a4b9324591e60742f1e9ce375172c + OPENSCAP_VERSION: 1.3.9+dfsg-1.1ubuntu2 + steps: + - name: Check out repository + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + + - name: Confirm native runner architecture + env: + EXPECTED_MACHINE: ${{ matrix.machine }} + run: test "$(uname -m)" = "${EXPECTED_MACHINE}" + + - name: Install pinned OpenSCAP utilities + run: | + sudo apt-get update + sudo apt-get install --no-install-recommends \ + "openscap-scanner=${OPENSCAP_VERSION}" \ + "openscap-utils=${OPENSCAP_VERSION}" \ + "python3-openscap=${OPENSCAP_VERSION}" + oscap --version | grep -F 'OpenSCAP command line tool (oscap) 1.3.9' + command -v oscap-chroot + + - name: Acquire pinned ComplianceAsCode content + run: | + curl --fail --location --proto '=https' --tlsv1.2 \ + --output "${SCAP_ARCHIVE}" \ + "https://github.com/ComplianceAsCode/content/releases/download/v0.1.81/${SCAP_ARCHIVE}" + echo "${SCAP_ARCHIVE_SHA256} ${SCAP_ARCHIVE}" | sha256sum --check --strict + mkdir scap-content + unzip -j "${SCAP_ARCHIVE}" '*ssg-rhel9-ds.xml' -d scap-content + test -s scap-content/ssg-rhel9-ds.xml + + - name: Acquire and build the locked image + env: + BUILD_METADATA_FILE: provenance-${{ matrix.architecture }}.json + BUILDX_METADATA_PROVENANCE: min + CONTAINER_RUNTIME: docker + IMAGE: ${{ env.TEST_IMAGE }} + run: bash scripts/build-offline.sh + + - name: Export a never-executed root filesystem + run: | + container_id="$(docker create --entrypoint /bin/true "${TEST_IMAGE}")" + test "$(docker inspect --format '{{.State.Status}}' "${container_id}")" = created + docker export --output image-rootfs.tar "${container_id}" + docker rm "${container_id}" + mkdir image-rootfs + sudo tar --numeric-owner --same-owner -xf image-rootfs.tar -C image-rootfs + test "$(sudo stat -c '%u:%g' image-rootfs/usr/pgsql-18)" = '0:0' + + - name: Run report-only image-owned discovery + run: | + set +e + sudo oscap-chroot "${PWD}/image-rootfs" xccdf eval \ + --profile xccdf_datopsis_profile_image_filesystem_discovery \ + --tailoring-file "${PWD}/compliance/scap/postgresql-ubi-rhel9-tailoring.xml" \ + --results-arf "${PWD}/scap-results-${{ matrix.architecture }}.xml" \ + --report "${PWD}/scap-report-${{ matrix.architecture }}.html" \ + "${PWD}/scap-content/ssg-rhel9-ds.xml" + scan_status=$? + set -e + if test "${scan_status}" -ne 0 && test "${scan_status}" -ne 2; then + echo "OpenSCAP execution failed with status ${scan_status}" >&2 + exit "${scan_status}" + fi + test -s "scap-results-${{ matrix.architecture }}.xml" + test -s "scap-report-${{ matrix.architecture }}.html" + printf 'OpenSCAP finding status: %s (report-only; 2 means at least one rule failed)\n' "${scan_status}" \ + > "scap-status-${{ matrix.architecture }}.txt" + + - name: Retain SCAP discovery evidence + if: ${{ always() }} + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: scap-discovery-${{ github.sha }}-${{ matrix.architecture }} + path: | + scap-results-${{ matrix.architecture }}.xml + scap-report-${{ matrix.architecture }}.html + scap-status-${{ matrix.architecture }}.txt + provenance-${{ matrix.architecture }}.json + if-no-files-found: error + retention-days: 14 diff --git a/CHANGELOG.md b/CHANGELOG.md index f1d43a6..90bba83 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -47,3 +47,8 @@ but container releases use the upstream-derived format documented in policy; digest-bound SBOM, provenance, scanning, signing, verification, and evidence controls; upstream monitoring; emergency rebuild; and quarantine procedures. +- Added the Package 5 cybersecurity engineering set: authoritative source + register, schema-validated OSCAL component definition, generated control + views, architecture and threat model, report-only native SCAP discovery, + cryptographic claim gate, vulnerability exceptions, incident response, and + inherited deployment responsibility matrix. diff --git a/README.md b/README.md index e365fba..b70807c 100644 --- a/README.md +++ b/README.md @@ -134,6 +134,16 @@ No supported image has been released. The current implementation is for development and evaluation; consult the [support matrix](docs/SUPPORT.md) before relying on any platform or operational claim. +## Cybersecurity engineering + +The [security engineering record](docs/SECURITY-ENGINEERING.md) links the +authoritative source register, schema-validated OSCAL component definition, +generated control view, architecture, threat model, SCAP evidence boundary, +cryptographic claim gate, vulnerability/exception process, incident response, +and deployment responsibility matrix. These are component support artifacts, +not a system authorization, STIG certification, FIPS validation, or compliance +determination. + ## References - [PostgreSQL versioning policy](https://www.postgresql.org/support/versioning/) diff --git a/artifacts/assurance-tool-versions.json b/artifacts/assurance-tool-versions.json index 9b5e4ac..3053d0c 100644 --- a/artifacts/assurance-tool-versions.json +++ b/artifacts/assurance-tool-versions.json @@ -1,4 +1,6 @@ { "compliance_as_code": "0.1.81", - "status": "planned-input-not-yet-qualified" + "openscap": "1.3.9+dfsg-1.1ubuntu2", + "oscal": "1.2.3", + "status": "report-only-discovery" } diff --git a/compliance/oscal/component-definition.json b/compliance/oscal/component-definition.json new file mode 100644 index 0000000..418efb5 --- /dev/null +++ b/compliance/oscal/component-definition.json @@ -0,0 +1,276 @@ +{ + "$schema": "https://github.com/usnistgov/OSCAL/releases/download/v1.2.3/oscal_component_schema.json", + "component-definition": { + "uuid": "35c32f9c-ed70-4ee8-bc7b-684657ce1930", + "metadata": { + "title": "postgresql-ubi component control support", + "published": "2026-09-12T12:00:00Z", + "last-modified": "2026-09-12T12:00:00Z", + "version": "0.1.0", + "oscal-version": "1.2.3", + "roles": [ + {"id": "maintainer", "title": "Image maintainer"}, + {"id": "deployment-owner", "title": "Deployment owner"}, + {"id": "host-owner", "title": "Host or platform owner"}, + {"id": "organization-owner", "title": "Deploying organization"}, + {"id": "independent-reviewer", "title": "Independent security reviewer"} + ], + "parties": [ + { + "uuid": "9be0ee58-9004-4fa5-88d7-74b4f521e644", + "type": "organization", + "name": "Datopsis" + } + ] + }, + "components": [ + { + "uuid": "915298e1-5ddb-4aa0-a818-fe77d84ee35e", + "type": "software", + "title": "postgresql-ubi", + "description": "A PostgreSQL 18 container component built from PGDG packages on Red Hat UBI 9.", + "purpose": "Supply a reproducible restricted-runtime database image; system controls remain shared with the deployment, host, and organization.", + "responsible-roles": [ + {"role-id": "maintainer", "party-uuids": ["9be0ee58-9004-4fa5-88d7-74b4f521e644"]} + ], + "protocols": [ + { + "uuid": "e21ec1c0-bcee-478b-a1d6-e26fa37ec86e", + "name": "postgresql", + "title": "PostgreSQL wire protocol", + "port-ranges": [{"start": 5432, "end": 5432, "transport": "TCP"}] + } + ], + "control-implementations": [ + { + "uuid": "f99430e7-e418-4663-82ae-a698e74786af", + "source": "https://raw.githubusercontent.com/usnistgov/oscal-content/main/nist.gov/SP800-53/rev5/json/NIST_SP-800-53_rev5_catalog.json", + "description": "Selected component-relevant NIST controls mapped to reviewed DISA and upstream requirements. This is not a system implementation or assessment.", + "implemented-requirements": [ + { + "uuid": "322639e6-bd04-4f3a-b067-ae1452216ba6", + "control-id": "ac-3", + "description": "The component supports PostgreSQL role, database, schema, and object authorization; the deployment defines identities and grants.", + "props": [ + {"name":"classification","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"deployment-supported"}, + {"name":"source-requirement","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"NIST AC-3; SRG-APP-000033-DB-000084"}, + {"name":"rationale","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"PostgreSQL supplies authorization mechanisms but no image can choose application privileges."}, + {"name":"residual-risk","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"Excessive grants or superuser use can bypass intended separation."}, + {"name":"evidence","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"docs/DEPLOYMENT.md"}, + {"name":"owner","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"deployment-owner"}, + {"name":"review-status","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"pending-independent-review"}, + {"name":"assessment-method","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"examine role/grant policy; test denied and permitted operations; interview database owner"}, + {"name":"default-state","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"Bootstrap creates only the requested initial superuser and database."}, + {"name":"configurable-state","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"Roles, memberships, ownership, default privileges, and row security are deployment data."}, + {"name":"prerequisites","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"Approved role model and application identities."}, + {"name":"restart-behavior","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"Grant changes normally take effect without container restart."}, + {"name":"operational-impact","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"Least privilege can expose undeclared application dependencies."}, + {"name":"loss-of-protection","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"Superuser, ownership, or broad PUBLIC grants defeat access boundaries."}, + {"name":"limitations","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"The image does not provision an organization-specific authorization model."} + ] + }, + { + "uuid": "1dcc66e5-cba5-40e0-8556-3c3f24b730a6", + "control-id": "au-2", + "description": "PostgreSQL emits operational logs to container stdout; event selection and centralized audit retention are deployment functions.", + "props": [ + {"name":"classification","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"deployment-supported"}, + {"name":"source-requirement","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"NIST AU-2; SRG-APP-000091-DB-000066"}, + {"name":"rationale","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"The database can emit selected events while the runtime and SIEM collect and retain them."}, + {"name":"residual-risk","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"Insufficient event selection or collector loss can leave investigation gaps."}, + {"name":"evidence","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"docs/RUNTIME-SECURITY.md"}, + {"name":"owner","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"deployment-owner"}, + {"name":"review-status","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"pending-independent-review"}, + {"name":"assessment-method","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"examine logging configuration; test selected events and interruption; interview SIEM owner"}, + {"name":"default-state","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"Server logs go to stderr with the PostgreSQL default event set."}, + {"name":"configurable-state","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"Event classes, detail, prefix, duration, and collector behavior are configurable."}, + {"name":"prerequisites","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"Runtime log driver, capacity, access control, clock, and SIEM policy."}, + {"name":"restart-behavior","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"Most logging settings reload; collector changes may require restart."}, + {"name":"operational-impact","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"Verbose SQL logging can expose data and increase I/O and storage demand."}, + {"name":"loss-of-protection","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"Disabled, dropped, uncorrelated, or mutable logs remove accountability evidence."}, + {"name":"limitations","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"The image has no SIEM, retention, immutable storage, or organization event policy."} + ] + }, + { + "uuid": "f1249253-5f20-4a63-93d3-6a5639620162", + "control-id": "cm-2", + "description": "The repository fixes a minimal image baseline with locked input artifacts and restricted-runtime defaults.", + "props": [ + {"name":"classification","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"image-owned"}, + {"name":"source-requirement","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"NIST CM-2; RHEL 9 STIG V2R9 baseline concepts; SRG-APP-000516-CTR-001325"}, + {"name":"rationale","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"The repository controls packages, files, entrypoint, metadata, and tested defaults."}, + {"name":"residual-risk","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"Host, runtime, mounted configuration, and initialized data alter the effective system baseline."}, + {"name":"evidence","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"docs/ARTIFACT-ACQUISITION.md"}, + {"name":"owner","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"maintainer"}, + {"name":"review-status","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"pending-independent-review"}, + {"name":"assessment-method","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"examine locks and image manifest; test native builds and restricted runtime; interview maintainer"}, + {"name":"default-state","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"Digest-pinned UBI stages, exact RPM closure, no package manager metadata, nonroot runtime."}, + {"name":"configurable-state","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"Deployment may add configuration and mounts but must not mutate the image."}, + {"name":"prerequisites","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"Verified artifact bundle and protected CI inputs."}, + {"name":"restart-behavior","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"A baseline change requires rebuild and full requalification."}, + {"name":"operational-impact","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"Minimal tooling makes interactive repair and diagnostics intentionally limited."}, + {"name":"loss-of-protection","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"Mutable tags, unlocked inputs, or writable root filesystems destroy baseline identity."}, + {"name":"limitations","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"This is a component baseline, not a RHEL host baseline or DISA-approved configuration."} + ] + }, + { + "uuid": "3ecae273-5ca7-4c47-9a36-e77e6e16e8ce", + "control-id": "cp-9", + "description": "The image documents consistent backup interfaces; backup execution, encryption, custody, and restore objectives are inherited.", + "props": [ + {"name":"classification","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"inherited"}, + {"name":"source-requirement","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"NIST CP-9; Database SRG V4R5 backup requirements"}, + {"name":"rationale","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"Only the deployment knows durability, recovery objectives, keys, media, and restore location."}, + {"name":"residual-risk","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"Missing, stolen, inconsistent, or untested backups can cause disclosure or irreversible loss."}, + {"name":"evidence","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"docs/STORAGE.md"}, + {"name":"owner","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"organization-owner"}, + {"name":"review-status","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"pending-independent-review"}, + {"name":"assessment-method","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"examine backup records; test isolated restore and integrity; interview storage and recovery owners"}, + {"name":"default-state","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"No automatic backup service is included."}, + {"name":"configurable-state","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"Logical, physical, WAL, snapshot, encryption, and retention design is external."}, + {"name":"prerequisites","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"Durable volume, backup system, protected keys, capacity, RPO and RTO."}, + {"name":"restart-behavior","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"Backup configuration depends on method; restore normally replaces a stopped instance."}, + {"name":"operational-impact","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"Backup and restore consume I/O, storage, time, and may require quiescence."}, + {"name":"loss-of-protection","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"Untested or inaccessible recovery material eliminates recoverability."}, + {"name":"limitations","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"Image tests demonstrate mechanics only, not organization recovery objectives."} + ] + }, + { + "uuid": "e12c9137-35e4-4db6-b647-a92d9e4617e0", + "control-id": "ia-5", + "description": "The image requires a bootstrap secret and defaults PostgreSQL passwords and host rules to SCRAM-SHA-256; lifecycle policy is external.", + "props": [ + {"name":"classification","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"deployment-supported"}, + {"name":"source-requirement","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"NIST IA-5; SRG-APP-000171-DB-000074; PostgreSQL 18 password authentication"}, + {"name":"rationale","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"The image enforces safe bootstrap transport and hashing; the organization creates and rotates secrets."}, + {"name":"residual-risk","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"Weak, reused, exposed, or unrotated passwords remain possible."}, + {"name":"evidence","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"tests/runtime-security.sh"}, + {"name":"owner","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"deployment-owner"}, + {"name":"review-status","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"pending-independent-review"}, + {"name":"assessment-method","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"examine secret and HBA configuration; test missing secret and SCRAM login; interview secret owner"}, + {"name":"default-state","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"Secret file required on first initialization; SCRAM-SHA-256 selected; trust rejected."}, + {"name":"configurable-state","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"External HBA and server configuration can change authentication and iteration count."}, + {"name":"prerequisites","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"Runtime secret service with correct ownership, mode, rotation, and audit."}, + {"name":"restart-behavior","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"HBA reload is sufficient; role password rotation is online; bootstrap secret is initialization-only."}, + {"name":"operational-impact","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"Rotation and stronger hashing can affect clients and authentication latency."}, + {"name":"loss-of-protection","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"Trust, cleartext network password use, leaked secret files, or stale roles enable compromise."}, + {"name":"limitations","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"No password breach-list service, MFA, or enterprise identity provider is included."} + ] + }, + { + "uuid": "911f36ae-f645-46d7-9cd6-80ce663f9344", + "control-id": "ir-4", + "description": "The repository provides withdrawal and evidence-preservation procedures; incident command and notification are organization-owned.", + "props": [ + {"name":"classification","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"inherited"}, + {"name":"source-requirement","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"NIST IR-4; NIST SP 800-53A examine/test/interview method"}, + {"name":"rationale","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"A public component maintainer cannot operate a consumer incident response program."}, + {"name":"residual-risk","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"Delayed coordination can expand compromise and evidence loss."}, + {"name":"evidence","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"docs/INCIDENT-RESPONSE.md"}, + {"name":"owner","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"organization-owner"}, + {"name":"review-status","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"pending-independent-review"}, + {"name":"assessment-method","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"examine plan and rehearsal; test contact and quarantine path; interview incident owner"}, + {"name":"default-state","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"Private reporting and maintainer withdrawal procedures are documented."}, + {"name":"configurable-state","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"Consumer severity, legal notification, evidence, and containment plans are external."}, + {"name":"prerequisites","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"Named responders, monitored contacts, registry authority, backups, and legal policy."}, + {"name":"restart-behavior","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"Containment may stop service; recovery uses a new qualified digest and rotated credentials."}, + {"name":"operational-impact","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"Containment and evidence preservation can interrupt database availability."}, + {"name":"loss-of-protection","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"Moving tags, deleting evidence, or reusing credentials compromises recovery confidence."}, + {"name":"limitations","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"The maintainer does not know consumer contacts, data, law, or infrastructure."} + ] + }, + { + "uuid": "ef7bbf4e-f728-46a0-8b0d-8a8262ff193b", + "control-id": "sc-8", + "description": "The image supports PostgreSQL TLS but leaves certificate issuance, trust, client verification, and ingress enforcement to deployment.", + "props": [ + {"name":"classification","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"deployment-supported"}, + {"name":"source-requirement","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"NIST SC-8; SRG-APP-000441-DB-000378"}, + {"name":"rationale","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"TLS capability is inside PostgreSQL; certificates and network enforcement are deployment-specific."}, + {"name":"residual-risk","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"Clients can be spoofed or traffic exposed if TLS or verification is optional."}, + {"name":"evidence","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"tests/tls.sh"}, + {"name":"owner","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"deployment-owner"}, + {"name":"review-status","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"pending-independent-review"}, + {"name":"assessment-method","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"examine certificate/HBA/client policy; test TLS and plaintext rejection; interview PKI owner"}, + {"name":"default-state","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"TLS is not enabled without deployment-provided key, certificate, HBA, and configuration."}, + {"name":"configurable-state","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"TLS versions, ciphers, CA, client certificates, HBA and client sslmode are configurable."}, + {"name":"prerequisites","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"Protected server key, valid certificate chain, trusted time, DNS, and client trust."}, + {"name":"restart-behavior","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"Initial enablement requires restart; certificate/configuration reload behavior must be rehearsed."}, + {"name":"operational-impact","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"Strict verification can reject misnamed, expired, or untrusted certificates."}, + {"name":"loss-of-protection","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"Plain host records, sslmode disable/prefer, or key compromise remove transport assurance."}, + {"name":"limitations","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"TLS support is not a FIPS validation or proof of client policy."} + ] + }, + { + "uuid": "18e6c185-5e02-4cad-a357-d9d1c854fcd0", + "control-id": "sc-13", + "description": "Cryptographic mechanisms are inventoried, but no FIPS validation claim is made for this image or PostgreSQL configuration.", + "props": [ + {"name":"classification","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"research-required"}, + {"name":"source-requirement","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"NIST SC-13; SRG-APP-000179-DB-000114; RHEL 9 STIG V2R9"}, + {"name":"rationale","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"A claim requires exact library linkage, provider mode, architecture, runtime context, and applicable CMVP certificate."}, + {"name":"residual-risk","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"Operators may mistake UBI origin, TLS, or host FIPS mode for validated image cryptography."}, + {"name":"evidence","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"docs/CRYPTOGRAPHIC-BOUNDARY.md"}, + {"name":"owner","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"maintainer"}, + {"name":"review-status","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"pending-independent-review"}, + {"name":"assessment-method","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"examine binaries, providers and CMVP records; test each architecture/mode; interview cryptographic authority"}, + {"name":"default-state","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"No FIPS or validated-cryptography claim."}, + {"name":"configurable-state","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"Host mode, OpenSSL providers, PostgreSQL TLS, SCRAM, checksums, and signing are separate boundaries."}, + {"name":"prerequisites","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"Exact release digest and architecture plus verified module, provider, host, and CMVP evidence."}, + {"name":"restart-behavior","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"Mode/provider/TLS changes can require host and database restart and requalification."}, + {"name":"operational-impact","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"Approved algorithms and provider modes may reduce compatibility or performance."}, + {"name":"loss-of-protection","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"Unapproved providers, algorithms, key handling, or boundary changes invalidate a claim."}, + {"name":"limitations","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"No applicable CMVP certificate or tested end-to-end mode is established."} + ] + }, + { + "uuid": "eaa75506-e279-47b4-8673-ccf5c7404274", + "control-id": "si-2", + "description": "The project monitors suppliers, locks exact inputs, scans both architectures, and defines time-bound remediation and withdrawal decisions.", + "props": [ + {"name":"classification","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"image-owned"}, + {"name":"source-requirement","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"NIST SI-2; SRG-APP-000456-DB-000390"}, + {"name":"rationale","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"Maintainers own component input monitoring, rebuild, qualification, and release withdrawal."}, + {"name":"residual-risk","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"Unknown, unfixed, disputed, or deployment-specific exposure can remain."}, + {"name":"evidence","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"docs/VULNERABILITY-MANAGEMENT.md"}, + {"name":"owner","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"maintainer"}, + {"name":"review-status","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"pending-independent-review"}, + {"name":"assessment-method","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"examine advisories, scans and exceptions; test rebuild/withdrawal; interview triage owner"}, + {"name":"default-state","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"Fixed High/Critical findings block; scheduled monitoring and two scanners are configured."}, + {"name":"configurable-state","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"Exceptions require exact digest/architecture, approval, expiry and rescan trigger."}, + {"name":"prerequisites","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"Current PGDG, PostgreSQL, Red Hat and scanner data plus monitored private reports."}, + {"name":"restart-behavior","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"Correction requires a new immutable image digest and deployment rollout."}, + {"name":"operational-impact","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"Urgent withdrawal or update can interrupt planned change windows."}, + {"name":"loss-of-protection","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"Stale databases, blanket ignores, moving tags, or bypassed qualification defeat remediation."}, + {"name":"limitations","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"Scanners do not prove reachability, absence of design flaws, or deployment patching."} + ] + }, + { + "uuid": "8fd2a8b0-e94a-4ab9-8f99-5639c25e332d", + "control-id": "sr-4", + "description": "The build verifies publisher signatures, checksums, source-to-binary mappings, exact package closures, SBOMs, provenance, and release signatures.", + "props": [ + {"name":"classification","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"image-owned"}, + {"name":"source-requirement","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"NIST SR-4; Container Platform SRG supply-chain requirements"}, + {"name":"rationale","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"Repository workflows own acquisition verification and evidence for produced images."}, + {"name":"residual-risk","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"Supplier, signing-key, CI runner, registry, or evidence systems can still be compromised."}, + {"name":"evidence","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"docs/RELEASE.md"}, + {"name":"owner","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"maintainer"}, + {"name":"review-status","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"pending-independent-review"}, + {"name":"assessment-method","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"examine locks/provenance/signatures; test tampered input and independent verification; interview release owner"}, + {"name":"default-state","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"Exact hashes, fingerprints, actions, scanners, native architectures, and immutable release identity."}, + {"name":"configurable-state","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"Any supplier, key, package, action, tool, or base closure change requires reviewed lock update."}, + {"name":"prerequisites","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"Protected branch, trusted GitHub identities, supplier keys, registry, OIDC and evidence retention."}, + {"name":"restart-behavior","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"Input changes produce a new digest; consumers must explicitly roll out that digest."}, + {"name":"operational-impact","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"Fail-closed verification can delay builds and releases."}, + {"name":"loss-of-protection","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"Unverified mirrors, mutable refs, compromised keys/runners, or missing evidence break traceability."}, + {"name":"limitations","ns":"https://datopsis.dev/ns/postgresql-ubi","value":"Provenance establishes recorded build inputs, not supplier or CI infallibility."} + ] + } + ] + } + ] + } + ] + } +} diff --git a/compliance/scap/postgresql-ubi-rhel9-tailoring.xml b/compliance/scap/postgresql-ubi-rhel9-tailoring.xml new file mode 100644 index 0000000..fa6b7ed --- /dev/null +++ b/compliance/scap/postgresql-ubi-rhel9-tailoring.xml @@ -0,0 +1,14 @@ + + + + 1 + + postgresql-ubi image filesystem discovery + Report-only discovery limited to file ownership and privilege bits in the exported image root filesystem. This is not a RHEL 9 STIG profile. + + + + + + diff --git a/compliance/scap/rule-decisions.csv b/compliance/scap/rule-decisions.csv new file mode 100644 index 0000000..3ab49eb --- /dev/null +++ b/compliance/scap/rule-decisions.csv @@ -0,0 +1,15 @@ +rule_id,decision,ownership,rationale,result_policy +no_files_unowned_by_user,include,image-owned,The repository fixes every file and numeric owner in the immutable image filesystem.,report-only +no_files_unowned_by_group,exclude,research-required,ComplianceAsCode 0.1.81 RHEL 9 does not contain this proposed rule identifier; image group ownership remains covered by build policy and requires a reviewed SCAP rule before inclusion.,manual-review +file_permissions_unauthorized_world_writable,include,image-owned,The repository controls mode bits in immutable image layers.,report-only +file_permissions_unauthorized_suid,include,image-owned,The repository prohibits unexpected setuid files in immutable image layers.,report-only +file_permissions_unauthorized_sgid,include,image-owned,The repository prohibits unexpected setgid files in immutable image layers.,report-only +runtime-kernel-and-sysctl-rules,exclude,inherited,A root filesystem export cannot establish host kernel or namespace runtime state.,not-applicable-to-evidence-boundary +service-and-systemd-rules,exclude,not-applicable,The application image has no systemd booted operating system or host services.,not-applicable +boot-firmware-and-partition-rules,exclude,not-applicable,The image contains no bootloader firmware or host partition table.,not-applicable +interactive-account-pam-and-ssh-rules,exclude,not-applicable,The image is not an interactive login host and contains no SSH service.,not-applicable +audit-siem-and-time-rules,exclude,inherited,The runtime host and deployment provide audit collection retention and trusted time.,deployment-owned +firewall-dns-and-network-policy-rules,exclude,inherited,The image cannot configure host or orchestrator network enforcement.,deployment-owned +postgresql-configuration-rules,exclude,research-required,RHEL host content does not assess PostgreSQL and vendor PostgreSQL STIG commands are not proven applicable to PGDG 18.,manual-review +rpm-verification-rules,exclude,research-required,Offline RPM probes have known boundary limitations and need false-positive review before adoption.,manual-review +cryptographic-policy-and-fips-rules,exclude,research-required,Filesystem inspection cannot prove active provider mode or an applicable CMVP boundary.,manual-review diff --git a/compliance/schemas/vulnerability-exception.schema.json b/compliance/schemas/vulnerability-exception.schema.json new file mode 100644 index 0000000..812a971 --- /dev/null +++ b/compliance/schemas/vulnerability-exception.schema.json @@ -0,0 +1,31 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://datopsis.dev/schemas/postgresql-ubi/vulnerability-exception.schema.json", + "type": "object", + "additionalProperties": false, + "required": ["schema_version", "exceptions"], + "properties": { + "schema_version": {"const": 1}, + "exceptions": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "required": ["image_digest", "architecture", "advisory", "component", "vendor_status", "rationale", "compensating_control", "owner", "approval", "expires", "rescan_trigger"], + "properties": { + "image_digest": {"type": "string", "pattern": "^sha256:[0-9a-f]{64}$"}, + "architecture": {"enum": ["amd64", "arm64"]}, + "advisory": {"type": "string", "minLength": 1}, + "component": {"type": "string", "minLength": 1}, + "vendor_status": {"type": "string", "minLength": 1}, + "rationale": {"type": "string", "minLength": 1}, + "compensating_control": {"type": "string", "minLength": 1}, + "owner": {"type": "string", "minLength": 1}, + "approval": {"type": "string", "minLength": 1}, + "expires": {"type": "string", "format": "date"}, + "rescan_trigger": {"type": "string", "minLength": 1} + } + } + } + } +} diff --git a/compliance/sctm.csv b/compliance/sctm.csv new file mode 100644 index 0000000..4fb4a22 --- /dev/null +++ b/compliance/sctm.csv @@ -0,0 +1,11 @@ +control_id,description,classification,source-requirement,rationale,residual-risk,evidence,owner,review-status,assessment-method,default-state,configurable-state,prerequisites,restart-behavior,operational-impact,loss-of-protection,limitations +ac-3,"The component supports PostgreSQL role, database, schema, and object authorization; the deployment defines identities and grants.",deployment-supported,NIST AC-3; SRG-APP-000033-DB-000084,PostgreSQL supplies authorization mechanisms but no image can choose application privileges.,Excessive grants or superuser use can bypass intended separation.,docs/DEPLOYMENT.md,deployment-owner,pending-independent-review,examine role/grant policy; test denied and permitted operations; interview database owner,Bootstrap creates only the requested initial superuser and database.,"Roles, memberships, ownership, default privileges, and row security are deployment data.",Approved role model and application identities.,Grant changes normally take effect without container restart.,Least privilege can expose undeclared application dependencies.,"Superuser, ownership, or broad PUBLIC grants defeat access boundaries.",The image does not provision an organization-specific authorization model. +au-2,PostgreSQL emits operational logs to container stdout; event selection and centralized audit retention are deployment functions.,deployment-supported,NIST AU-2; SRG-APP-000091-DB-000066,The database can emit selected events while the runtime and SIEM collect and retain them.,Insufficient event selection or collector loss can leave investigation gaps.,docs/RUNTIME-SECURITY.md,deployment-owner,pending-independent-review,examine logging configuration; test selected events and interruption; interview SIEM owner,Server logs go to stderr with the PostgreSQL default event set.,"Event classes, detail, prefix, duration, and collector behavior are configurable.","Runtime log driver, capacity, access control, clock, and SIEM policy.",Most logging settings reload; collector changes may require restart.,Verbose SQL logging can expose data and increase I/O and storage demand.,"Disabled, dropped, uncorrelated, or mutable logs remove accountability evidence.","The image has no SIEM, retention, immutable storage, or organization event policy." +cm-2,The repository fixes a minimal image baseline with locked input artifacts and restricted-runtime defaults.,image-owned,NIST CM-2; RHEL 9 STIG V2R9 baseline concepts; SRG-APP-000516-CTR-001325,"The repository controls packages, files, entrypoint, metadata, and tested defaults.","Host, runtime, mounted configuration, and initialized data alter the effective system baseline.",docs/ARTIFACT-ACQUISITION.md,maintainer,pending-independent-review,examine locks and image manifest; test native builds and restricted runtime; interview maintainer,"Digest-pinned UBI stages, exact RPM closure, no package manager metadata, nonroot runtime.",Deployment may add configuration and mounts but must not mutate the image.,Verified artifact bundle and protected CI inputs.,A baseline change requires rebuild and full requalification.,Minimal tooling makes interactive repair and diagnostics intentionally limited.,"Mutable tags, unlocked inputs, or writable root filesystems destroy baseline identity.","This is a component baseline, not a RHEL host baseline or DISA-approved configuration." +cp-9,"The image documents consistent backup interfaces; backup execution, encryption, custody, and restore objectives are inherited.",inherited,NIST CP-9; Database SRG V4R5 backup requirements,"Only the deployment knows durability, recovery objectives, keys, media, and restore location.","Missing, stolen, inconsistent, or untested backups can cause disclosure or irreversible loss.",docs/STORAGE.md,organization-owner,pending-independent-review,examine backup records; test isolated restore and integrity; interview storage and recovery owners,No automatic backup service is included.,"Logical, physical, WAL, snapshot, encryption, and retention design is external.","Durable volume, backup system, protected keys, capacity, RPO and RTO.",Backup configuration depends on method; restore normally replaces a stopped instance.,"Backup and restore consume I/O, storage, time, and may require quiescence.",Untested or inaccessible recovery material eliminates recoverability.,"Image tests demonstrate mechanics only, not organization recovery objectives." +ia-5,The image requires a bootstrap secret and defaults PostgreSQL passwords and host rules to SCRAM-SHA-256; lifecycle policy is external.,deployment-supported,NIST IA-5; SRG-APP-000171-DB-000074; PostgreSQL 18 password authentication,The image enforces safe bootstrap transport and hashing; the organization creates and rotates secrets.,"Weak, reused, exposed, or unrotated passwords remain possible.",tests/runtime-security.sh,deployment-owner,pending-independent-review,examine secret and HBA configuration; test missing secret and SCRAM login; interview secret owner,Secret file required on first initialization; SCRAM-SHA-256 selected; trust rejected.,External HBA and server configuration can change authentication and iteration count.,"Runtime secret service with correct ownership, mode, rotation, and audit.",HBA reload is sufficient; role password rotation is online; bootstrap secret is initialization-only.,Rotation and stronger hashing can affect clients and authentication latency.,"Trust, cleartext network password use, leaked secret files, or stale roles enable compromise.","No password breach-list service, MFA, or enterprise identity provider is included." +ir-4,The repository provides withdrawal and evidence-preservation procedures; incident command and notification are organization-owned.,inherited,NIST IR-4; NIST SP 800-53A examine/test/interview method,A public component maintainer cannot operate a consumer incident response program.,Delayed coordination can expand compromise and evidence loss.,docs/INCIDENT-RESPONSE.md,organization-owner,pending-independent-review,examine plan and rehearsal; test contact and quarantine path; interview incident owner,Private reporting and maintainer withdrawal procedures are documented.,"Consumer severity, legal notification, evidence, and containment plans are external.","Named responders, monitored contacts, registry authority, backups, and legal policy.",Containment may stop service; recovery uses a new qualified digest and rotated credentials.,Containment and evidence preservation can interrupt database availability.,"Moving tags, deleting evidence, or reusing credentials compromises recovery confidence.","The maintainer does not know consumer contacts, data, law, or infrastructure." +sc-13,"Cryptographic mechanisms are inventoried, but no FIPS validation claim is made for this image or PostgreSQL configuration.",research-required,NIST SC-13; SRG-APP-000179-DB-000114; RHEL 9 STIG V2R9,"A claim requires exact library linkage, provider mode, architecture, runtime context, and applicable CMVP certificate.","Operators may mistake UBI origin, TLS, or host FIPS mode for validated image cryptography.",docs/CRYPTOGRAPHIC-BOUNDARY.md,maintainer,pending-independent-review,"examine binaries, providers and CMVP records; test each architecture/mode; interview cryptographic authority",No FIPS or validated-cryptography claim.,"Host mode, OpenSSL providers, PostgreSQL TLS, SCRAM, checksums, and signing are separate boundaries.","Exact release digest and architecture plus verified module, provider, host, and CMVP evidence.",Mode/provider/TLS changes can require host and database restart and requalification.,Approved algorithms and provider modes may reduce compatibility or performance.,"Unapproved providers, algorithms, key handling, or boundary changes invalidate a claim.",No applicable CMVP certificate or tested end-to-end mode is established. +sc-8,"The image supports PostgreSQL TLS but leaves certificate issuance, trust, client verification, and ingress enforcement to deployment.",deployment-supported,NIST SC-8; SRG-APP-000441-DB-000378,TLS capability is inside PostgreSQL; certificates and network enforcement are deployment-specific.,Clients can be spoofed or traffic exposed if TLS or verification is optional.,tests/tls.sh,deployment-owner,pending-independent-review,examine certificate/HBA/client policy; test TLS and plaintext rejection; interview PKI owner,"TLS is not enabled without deployment-provided key, certificate, HBA, and configuration.","TLS versions, ciphers, CA, client certificates, HBA and client sslmode are configurable.","Protected server key, valid certificate chain, trusted time, DNS, and client trust.",Initial enablement requires restart; certificate/configuration reload behavior must be rehearsed.,"Strict verification can reject misnamed, expired, or untrusted certificates.","Plain host records, sslmode disable/prefer, or key compromise remove transport assurance.",TLS support is not a FIPS validation or proof of client policy. +si-2,"The project monitors suppliers, locks exact inputs, scans both architectures, and defines time-bound remediation and withdrawal decisions.",image-owned,NIST SI-2; SRG-APP-000456-DB-000390,"Maintainers own component input monitoring, rebuild, qualification, and release withdrawal.","Unknown, unfixed, disputed, or deployment-specific exposure can remain.",docs/VULNERABILITY-MANAGEMENT.md,maintainer,pending-independent-review,"examine advisories, scans and exceptions; test rebuild/withdrawal; interview triage owner",Fixed High/Critical findings block; scheduled monitoring and two scanners are configured.,"Exceptions require exact digest/architecture, approval, expiry and rescan trigger.","Current PGDG, PostgreSQL, Red Hat and scanner data plus monitored private reports.",Correction requires a new immutable image digest and deployment rollout.,Urgent withdrawal or update can interrupt planned change windows.,"Stale databases, blanket ignores, moving tags, or bypassed qualification defeat remediation.","Scanners do not prove reachability, absence of design flaws, or deployment patching." +sr-4,"The build verifies publisher signatures, checksums, source-to-binary mappings, exact package closures, SBOMs, provenance, and release signatures.",image-owned,NIST SR-4; Container Platform SRG supply-chain requirements,Repository workflows own acquisition verification and evidence for produced images.,"Supplier, signing-key, CI runner, registry, or evidence systems can still be compromised.",docs/RELEASE.md,maintainer,pending-independent-review,examine locks/provenance/signatures; test tampered input and independent verification; interview release owner,"Exact hashes, fingerprints, actions, scanners, native architectures, and immutable release identity.","Any supplier, key, package, action, tool, or base closure change requires reviewed lock update.","Protected branch, trusted GitHub identities, supplier keys, registry, OIDC and evidence retention.",Input changes produce a new digest; consumers must explicitly roll out that digest.,Fail-closed verification can delay builds and releases.,"Unverified mirrors, mutable refs, compromised keys/runners, or missing evidence break traceability.","Provenance establishes recorded build inputs, not supplier or CI infallibility." diff --git a/compliance/source-register.json b/compliance/source-register.json new file mode 100644 index 0000000..15eaef7 --- /dev/null +++ b/compliance/source-register.json @@ -0,0 +1,126 @@ +{ + "schema_version": 1, + "as_of": "2026-09-12", + "sources": [ + { + "id": "nist-sp800-53r5", + "publisher": "National Institute of Standards and Technology", + "title": "Security and Privacy Controls for Information Systems and Organizations", + "release": "SP 800-53 Rev. 5 Update 1 publication PDF; supplemental control content Release 5.2.0", + "publication_date": "2020-12-10", + "retrieved": "2026-09-12", + "url": "https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-53r5.pdf", + "sha256": "fc63bcd61715d0181dd8e85998b1e6201ae3515fc6626102101cab1841e11ec6", + "status": "current", + "redistribution": "Reference only; NIST publication is not redistributed by this repository." + }, + { + "id": "nist-sp800-53ar5", + "publisher": "National Institute of Standards and Technology", + "title": "Assessing Security and Privacy Controls in Information Systems and Organizations", + "release": "SP 800-53A Rev. 5 publication PDF; supplemental assessment content Release 5.2.0", + "publication_date": "2022-01-25", + "retrieved": "2026-09-12", + "url": "https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-53Ar5.pdf", + "sha256": "75665570048b969ad465a4f4f1db425ce505c374951c2c64e462949c6b21be47", + "status": "current", + "redistribution": "Reference only; NIST publication is not redistributed by this repository." + }, + { + "id": "disa-database-srg", + "publisher": "Defense Information Systems Agency", + "title": "Database Security Requirements Guide", + "release": "Version 4 Release 5", + "publication_date": "2026-04-28", + "retrieved": "2026-09-12", + "url": "https://dl.dod.cyber.mil/wp-content/uploads/stigs/zip/U_Database_V4R5_SRG.zip", + "sha256": "c604242fe07c4f4d04d5723d559cfa621b3313ce3bf44809b71f2bc0f94f20b9", + "status": "current", + "redistribution": "Reference only; DISA archive is not redistributed." + }, + { + "id": "disa-container-platform-srg", + "publisher": "Defense Information Systems Agency", + "title": "Container Platform Security Requirements Guide", + "release": "Version 2 Release 4", + "publication_date": "2026-04-28", + "retrieved": "2026-09-12", + "url": "https://dl.dod.cyber.mil/wp-content/uploads/stigs/zip/U_Container_Platform_V2R4_SRG.zip", + "sha256": "975a9e421e62e0ea52b1824e4a719aee87eed9070d5a3145fa9817f6498d99fe", + "status": "current", + "redistribution": "Reference only; DISA archive is not redistributed." + }, + { + "id": "disa-rhel9-stig", + "publisher": "Defense Information Systems Agency", + "title": "Red Hat Enterprise Linux 9 Security Technical Implementation Guide", + "release": "Version 2 Release 9", + "publication_date": "2026-07-10", + "retrieved": "2026-09-12", + "url": "https://dl.dod.cyber.mil/wp-content/uploads/stigs/zip/U_RHEL_9_V2R9_STIG.zip", + "sha256": "1875de2543d01695c5a346c51888cc336113e9199bfe6726d74a08de286ca1ae", + "status": "current", + "redistribution": "Reference only; DISA archive is not redistributed." + }, + { + "id": "disa-crunchy-postgres16-stig", + "publisher": "Defense Information Systems Agency", + "title": "Crunchy Data Postgres 16 Security Technical Implementation Guide", + "release": "Version 1 Release 3", + "publication_date": "2026-07-10", + "retrieved": "2026-09-12", + "url": "https://dl.dod.cyber.mil/wp-content/uploads/stigs/zip/U_CD_Postgres_16_V1R3_STIG.zip", + "sha256": "2970f7d32e18dce3f0d83739a85943927cf64c5373f58cb100d8889b0ba29a28", + "status": "reference", + "redistribution": "Reference only. It targets Crunchy Data PostgreSQL 16, not PGDG PostgreSQL 18; commands are not adopted." + }, + { + "id": "oscal-component-schema", + "publisher": "National Institute of Standards and Technology", + "title": "OSCAL Component Definition JSON Schema", + "release": "1.2.3", + "publication_date": "2026-08-07", + "retrieved": "2026-09-12", + "url": "https://github.com/usnistgov/OSCAL/releases/download/v1.2.3/oscal_component_schema.json", + "sha256": "95e76881151ececd5cb1a93ff0f70ad74b8cc1aa58771626ac8b262bf2c8e001", + "status": "current", + "redistribution": "Not vendored; CI downloads and verifies this schema before validation." + }, + { + "id": "compliance-as-code", + "publisher": "ComplianceAsCode project", + "title": "SCAP Security Guide prebuilt release", + "release": "0.1.81", + "publication_date": "2026-06-01", + "retrieved": "2026-09-12", + "url": "https://github.com/ComplianceAsCode/content/releases/download/v0.1.81/scap-security-guide-0.1.81.zip", + "sha256": "865e28b793e1e65f7f0102434bc7d962324a4b9324591e60742f1e9ce375172c", + "status": "current", + "redistribution": "GPL-2.0-or-later upstream content is downloaded and verified by CI, not redistributed." + }, + { + "id": "postgresql18-authentication", + "publisher": "PostgreSQL Global Development Group", + "title": "PostgreSQL 18 Password Authentication", + "release": "18 current documentation", + "publication_date": "2026-09-12", + "retrieved": "2026-09-12", + "url": "https://www.postgresql.org/docs/18/auth-password.html", + "sha256": "1ec2ae88d4cb95bb8b109ab8e60fa982cd37301a5b414b086ab6ee5042515b82", + "status": "current", + "redistribution": "Reference only; documentation is not redistributed." + }, + { + "id": "rhel9-security-hardening", + "publisher": "Red Hat", + "title": "Red Hat Enterprise Linux 9 Security hardening", + "release": "RHEL 9", + "publication_date": "2026-09-12", + "retrieved": "2026-09-12", + "url": "https://docs.redhat.com/en/documentation/red_hat_enterprise_linux/9/pdf/security_hardening/Red_Hat_Enterprise_Linux-9-Security_hardening-en-US.pdf", + "sha256": "71481a5ca4e132c968275561283fa8f9674ac37fcd01ac1e7d1f497b554395f9", + "status": "current", + "redistribution": "Reference only; Red Hat documentation is not redistributed." + } + ] +} diff --git a/compliance/vulnerability-exceptions.json b/compliance/vulnerability-exceptions.json new file mode 100644 index 0000000..18a7268 --- /dev/null +++ b/compliance/vulnerability-exceptions.json @@ -0,0 +1,4 @@ +{ + "schema_version": 1, + "exceptions": [] +} diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md new file mode 100644 index 0000000..c074208 --- /dev/null +++ b/docs/ARCHITECTURE.md @@ -0,0 +1,132 @@ +# Security architecture and trust boundaries + +These diagrams define ownership and evidence boundaries. Arrows crossing a +boundary require validation; a green build does not make an external service +trusted. + +## Component architecture + +```mermaid +flowchart LR + Client[Application client] -->|PostgreSQL protocol| Net[Deployment network policy] + Net --> DB[postgresql-ubi container] + Secret[Secret service] -->|read-only secret file| DB + Config[ConfigMap or protected files] -->|read-only configuration| DB + DB --> Data[(Durable PGDATA)] + DB --> Temp[(bounded tmpfs /dev/shm and /tmp)] + DB -->|stderr| Logs[Runtime logs / SIEM] + Host[Host kernel + OCI runtime] --- DB +``` + +The image owns packaged files, entrypoint validation, and safe defaults. The +deployment owns configuration, identities, listeners, secrets, data, and +resources. The host/platform owns kernel isolation, SELinux, seccomp, cgroups, +network enforcement, and log transport. + +## Build and assurance pipeline + +```mermaid +flowchart LR + PGDG[PGDG RPMs + SRPMs + keys] --> Verify[Hash/signature/source verification] + UBI[Digest-pinned UBI stages] --> Verify + PR[Reviewed commit] --> CI[Protected GitHub Actions] + Verify --> Offline[Networkless final build] + CI --> Offline + Offline --> Test[Native amd64 + arm64 tests] + Test --> Scan[Trivy + Syft/Grype + SCAP discovery] + Scan --> Candidate[Candidate digest + evidence] + Candidate --> Sign[Release attestation and signature] + Sign --> GHCR[Immutable GHCR publication] +``` + +Untrusted pull-request code never receives release credentials. Locks, +publisher keys, action commits, scanner versions, evidence, tags, and registry +digests are substitution targets and require independent verification. + +## Runtime data flow + +```mermaid +sequenceDiagram + participant S as Secret service + participant E as Entrypoint + participant D as PGDATA volume + participant P as PostgreSQL + participant C as Client + participant L as Log collector + S->>E: bootstrap password file + E->>D: initialize only if empty + E->>P: exec postgres as nonroot + C->>P: authenticated SQL over selected TLS policy + P->>D: WAL and data writes + P->>L: operational/audit events via stderr +``` + +Initialization consumes the secret without copying it into the image or +command line. Existing data bypasses bootstrap; operators own continuing role +and secret lifecycle. + +## Credential and TLS trust + +```mermaid +flowchart TD + OrgPKI[Organization PKI] --> Cert[Server certificate] + OrgPKI --> ClientCA[Client trust store] + SecretSvc[Secret service] --> Bootstrap[Bootstrap secret file] + Bootstrap --> SCRAM[SCRAM verifier in PGDATA] + Cert --> Server[PostgreSQL TLS endpoint] + ClientCA --> Client[Client sslmode=verify-full] + Client -->|hostname + chain + TLS| Server + DNS[Trusted DNS] --> Client + Time[Trusted time] --> Client + Time --> Server +``` + +PKI, DNS, clock, client verification, rotation, and revocation are outside the +image. SCRAM does not replace server authentication. + +## Storage and backup flow + +```mermaid +flowchart LR + PG[PostgreSQL] -->|data/WAL| Volume[(Protected durable volume)] + PG -->|pg_dump/base backup/WAL| Backup[Backup service] + KMS[KMS / key custody] --> Backup + Backup --> Vault[(Encrypted restricted backup)] + Vault --> Restore[Isolated restore rehearsal] + Restore --> Verify[Integrity + application checks] +``` + +The image provides database mechanics only. Volume durability, backup +consistency, encryption, access, retention, off-site custody, restore testing, +RPO, and RTO are organization responsibilities. + +## Controlled-network acquisition + +```mermaid +flowchart LR + Public[Verified public digest + evidence] --> Transfer[Controlled transfer] + Transfer --> Gate[Malware/hash/signature policy gate] + Gate --> Registry[Internal immutable registry] + Registry --> Deploy[Digest-only deployment] + Feeds[Timestamped advisory/scanner data] --> Transfer + CA[Internal CA + trusted time] --> Gate +``` + +The internal digest mapping, feed age, transfer custody, CA, trusted time, +rollback availability, and registry immutability must be recorded. + +## Control ownership + +```mermaid +flowchart TB + Image[Image maintainer: packages, files, entrypoint, CI evidence] + Deployment[Deployment: DB roles, HBA/TLS, secrets, resources, backup jobs] + Host[Host/platform: kernel, OCI runtime, SELinux, seccomp, network, logs] + Org[Organization: policy, PKI, IAM, SIEM, incident, risk acceptance] + Image --> System[Operating database system] + Deployment --> System + Host --> System + Org --> System +``` + +No single component artifact establishes system control effectiveness. diff --git a/docs/CI.md b/docs/CI.md index 9397334..1a74698 100644 --- a/docs/CI.md +++ b/docs/CI.md @@ -14,12 +14,20 @@ committed lock, verifies the bundle and full key fingerprints, pre-pulls only the digest-pinned bases, and performs a clean build with network access and additional pulls disabled. +The separate `SCAP discovery` workflow performs report-only native AMD64 and +ARM64 filesystem assessment with pinned OpenSCAP and ComplianceAsCode inputs. +It exports a never-started image with numeric ownership preserved, blocks tool +and evidence failures, and retains findings without treating them as host, +deployment, STIG, or compliance results. See [SCAP.md](SCAP.md). + ## Local repository checks ```console python -m pip install --require-hashes --only-binary=:all: \ - --requirement .github/requirements/pre-commit.txt + --requirement .github/requirements/pre-commit.txt \ + --requirement .github/requirements/cybersecurity.txt pre-commit run --all-files --show-diff-on-failure +python scripts/cybersecurity.py --check ``` ## Local image checks diff --git a/docs/CONTROL-IMPLEMENTATION.md b/docs/CONTROL-IMPLEMENTATION.md new file mode 100644 index 0000000..24e175a --- /dev/null +++ b/docs/CONTROL-IMPLEMENTATION.md @@ -0,0 +1,165 @@ +# Control implementation view + +Generated from `compliance/oscal/component-definition.json`; do not edit. +This is component support information, not an SCTM, SSP, authorization, +assessment result, STIG certification, or compliance determination. + +## ac-3: deployment-supported + +The component supports PostgreSQL role, database, schema, and object authorization; the deployment defines identities and grants. + +- Source requirement: NIST AC-3; SRG-APP-000033-DB-000084 +- Rationale: PostgreSQL supplies authorization mechanisms but no image can choose application privileges. +- Assessment: examine role/grant policy; test denied and permitted operations; interview database owner +- Default/configurable: Bootstrap creates only the requested initial superuser and database. / Roles, memberships, ownership, default privileges, and row security are deployment data. +- Prerequisites/restart: Approved role model and application identities. / Grant changes normally take effect without container restart. +- Operational impact: Least privilege can expose undeclared application dependencies. +- Loss of protection: Superuser, ownership, or broad PUBLIC grants defeat access boundaries. +- Limitations: The image does not provision an organization-specific authorization model. +- Residual risk: Excessive grants or superuser use can bypass intended separation. +- Evidence: `docs/DEPLOYMENT.md` +- Owner/review: deployment-owner / pending-independent-review + +## au-2: deployment-supported + +PostgreSQL emits operational logs to container stdout; event selection and centralized audit retention are deployment functions. + +- Source requirement: NIST AU-2; SRG-APP-000091-DB-000066 +- Rationale: The database can emit selected events while the runtime and SIEM collect and retain them. +- Assessment: examine logging configuration; test selected events and interruption; interview SIEM owner +- Default/configurable: Server logs go to stderr with the PostgreSQL default event set. / Event classes, detail, prefix, duration, and collector behavior are configurable. +- Prerequisites/restart: Runtime log driver, capacity, access control, clock, and SIEM policy. / Most logging settings reload; collector changes may require restart. +- Operational impact: Verbose SQL logging can expose data and increase I/O and storage demand. +- Loss of protection: Disabled, dropped, uncorrelated, or mutable logs remove accountability evidence. +- Limitations: The image has no SIEM, retention, immutable storage, or organization event policy. +- Residual risk: Insufficient event selection or collector loss can leave investigation gaps. +- Evidence: `docs/RUNTIME-SECURITY.md` +- Owner/review: deployment-owner / pending-independent-review + +## cm-2: image-owned + +The repository fixes a minimal image baseline with locked input artifacts and restricted-runtime defaults. + +- Source requirement: NIST CM-2; RHEL 9 STIG V2R9 baseline concepts; SRG-APP-000516-CTR-001325 +- Rationale: The repository controls packages, files, entrypoint, metadata, and tested defaults. +- Assessment: examine locks and image manifest; test native builds and restricted runtime; interview maintainer +- Default/configurable: Digest-pinned UBI stages, exact RPM closure, no package manager metadata, nonroot runtime. / Deployment may add configuration and mounts but must not mutate the image. +- Prerequisites/restart: Verified artifact bundle and protected CI inputs. / A baseline change requires rebuild and full requalification. +- Operational impact: Minimal tooling makes interactive repair and diagnostics intentionally limited. +- Loss of protection: Mutable tags, unlocked inputs, or writable root filesystems destroy baseline identity. +- Limitations: This is a component baseline, not a RHEL host baseline or DISA-approved configuration. +- Residual risk: Host, runtime, mounted configuration, and initialized data alter the effective system baseline. +- Evidence: `docs/ARTIFACT-ACQUISITION.md` +- Owner/review: maintainer / pending-independent-review + +## cp-9: inherited + +The image documents consistent backup interfaces; backup execution, encryption, custody, and restore objectives are inherited. + +- Source requirement: NIST CP-9; Database SRG V4R5 backup requirements +- Rationale: Only the deployment knows durability, recovery objectives, keys, media, and restore location. +- Assessment: examine backup records; test isolated restore and integrity; interview storage and recovery owners +- Default/configurable: No automatic backup service is included. / Logical, physical, WAL, snapshot, encryption, and retention design is external. +- Prerequisites/restart: Durable volume, backup system, protected keys, capacity, RPO and RTO. / Backup configuration depends on method; restore normally replaces a stopped instance. +- Operational impact: Backup and restore consume I/O, storage, time, and may require quiescence. +- Loss of protection: Untested or inaccessible recovery material eliminates recoverability. +- Limitations: Image tests demonstrate mechanics only, not organization recovery objectives. +- Residual risk: Missing, stolen, inconsistent, or untested backups can cause disclosure or irreversible loss. +- Evidence: `docs/STORAGE.md` +- Owner/review: organization-owner / pending-independent-review + +## ia-5: deployment-supported + +The image requires a bootstrap secret and defaults PostgreSQL passwords and host rules to SCRAM-SHA-256; lifecycle policy is external. + +- Source requirement: NIST IA-5; SRG-APP-000171-DB-000074; PostgreSQL 18 password authentication +- Rationale: The image enforces safe bootstrap transport and hashing; the organization creates and rotates secrets. +- Assessment: examine secret and HBA configuration; test missing secret and SCRAM login; interview secret owner +- Default/configurable: Secret file required on first initialization; SCRAM-SHA-256 selected; trust rejected. / External HBA and server configuration can change authentication and iteration count. +- Prerequisites/restart: Runtime secret service with correct ownership, mode, rotation, and audit. / HBA reload is sufficient; role password rotation is online; bootstrap secret is initialization-only. +- Operational impact: Rotation and stronger hashing can affect clients and authentication latency. +- Loss of protection: Trust, cleartext network password use, leaked secret files, or stale roles enable compromise. +- Limitations: No password breach-list service, MFA, or enterprise identity provider is included. +- Residual risk: Weak, reused, exposed, or unrotated passwords remain possible. +- Evidence: `tests/runtime-security.sh` +- Owner/review: deployment-owner / pending-independent-review + +## ir-4: inherited + +The repository provides withdrawal and evidence-preservation procedures; incident command and notification are organization-owned. + +- Source requirement: NIST IR-4; NIST SP 800-53A examine/test/interview method +- Rationale: A public component maintainer cannot operate a consumer incident response program. +- Assessment: examine plan and rehearsal; test contact and quarantine path; interview incident owner +- Default/configurable: Private reporting and maintainer withdrawal procedures are documented. / Consumer severity, legal notification, evidence, and containment plans are external. +- Prerequisites/restart: Named responders, monitored contacts, registry authority, backups, and legal policy. / Containment may stop service; recovery uses a new qualified digest and rotated credentials. +- Operational impact: Containment and evidence preservation can interrupt database availability. +- Loss of protection: Moving tags, deleting evidence, or reusing credentials compromises recovery confidence. +- Limitations: The maintainer does not know consumer contacts, data, law, or infrastructure. +- Residual risk: Delayed coordination can expand compromise and evidence loss. +- Evidence: `docs/INCIDENT-RESPONSE.md` +- Owner/review: organization-owner / pending-independent-review + +## sc-13: research-required + +Cryptographic mechanisms are inventoried, but no FIPS validation claim is made for this image or PostgreSQL configuration. + +- Source requirement: NIST SC-13; SRG-APP-000179-DB-000114; RHEL 9 STIG V2R9 +- Rationale: A claim requires exact library linkage, provider mode, architecture, runtime context, and applicable CMVP certificate. +- Assessment: examine binaries, providers and CMVP records; test each architecture/mode; interview cryptographic authority +- Default/configurable: No FIPS or validated-cryptography claim. / Host mode, OpenSSL providers, PostgreSQL TLS, SCRAM, checksums, and signing are separate boundaries. +- Prerequisites/restart: Exact release digest and architecture plus verified module, provider, host, and CMVP evidence. / Mode/provider/TLS changes can require host and database restart and requalification. +- Operational impact: Approved algorithms and provider modes may reduce compatibility or performance. +- Loss of protection: Unapproved providers, algorithms, key handling, or boundary changes invalidate a claim. +- Limitations: No applicable CMVP certificate or tested end-to-end mode is established. +- Residual risk: Operators may mistake UBI origin, TLS, or host FIPS mode for validated image cryptography. +- Evidence: `docs/CRYPTOGRAPHIC-BOUNDARY.md` +- Owner/review: maintainer / pending-independent-review + +## sc-8: deployment-supported + +The image supports PostgreSQL TLS but leaves certificate issuance, trust, client verification, and ingress enforcement to deployment. + +- Source requirement: NIST SC-8; SRG-APP-000441-DB-000378 +- Rationale: TLS capability is inside PostgreSQL; certificates and network enforcement are deployment-specific. +- Assessment: examine certificate/HBA/client policy; test TLS and plaintext rejection; interview PKI owner +- Default/configurable: TLS is not enabled without deployment-provided key, certificate, HBA, and configuration. / TLS versions, ciphers, CA, client certificates, HBA and client sslmode are configurable. +- Prerequisites/restart: Protected server key, valid certificate chain, trusted time, DNS, and client trust. / Initial enablement requires restart; certificate/configuration reload behavior must be rehearsed. +- Operational impact: Strict verification can reject misnamed, expired, or untrusted certificates. +- Loss of protection: Plain host records, sslmode disable/prefer, or key compromise remove transport assurance. +- Limitations: TLS support is not a FIPS validation or proof of client policy. +- Residual risk: Clients can be spoofed or traffic exposed if TLS or verification is optional. +- Evidence: `tests/tls.sh` +- Owner/review: deployment-owner / pending-independent-review + +## si-2: image-owned + +The project monitors suppliers, locks exact inputs, scans both architectures, and defines time-bound remediation and withdrawal decisions. + +- Source requirement: NIST SI-2; SRG-APP-000456-DB-000390 +- Rationale: Maintainers own component input monitoring, rebuild, qualification, and release withdrawal. +- Assessment: examine advisories, scans and exceptions; test rebuild/withdrawal; interview triage owner +- Default/configurable: Fixed High/Critical findings block; scheduled monitoring and two scanners are configured. / Exceptions require exact digest/architecture, approval, expiry and rescan trigger. +- Prerequisites/restart: Current PGDG, PostgreSQL, Red Hat and scanner data plus monitored private reports. / Correction requires a new immutable image digest and deployment rollout. +- Operational impact: Urgent withdrawal or update can interrupt planned change windows. +- Loss of protection: Stale databases, blanket ignores, moving tags, or bypassed qualification defeat remediation. +- Limitations: Scanners do not prove reachability, absence of design flaws, or deployment patching. +- Residual risk: Unknown, unfixed, disputed, or deployment-specific exposure can remain. +- Evidence: `docs/VULNERABILITY-MANAGEMENT.md` +- Owner/review: maintainer / pending-independent-review + +## sr-4: image-owned + +The build verifies publisher signatures, checksums, source-to-binary mappings, exact package closures, SBOMs, provenance, and release signatures. + +- Source requirement: NIST SR-4; Container Platform SRG supply-chain requirements +- Rationale: Repository workflows own acquisition verification and evidence for produced images. +- Assessment: examine locks/provenance/signatures; test tampered input and independent verification; interview release owner +- Default/configurable: Exact hashes, fingerprints, actions, scanners, native architectures, and immutable release identity. / Any supplier, key, package, action, tool, or base closure change requires reviewed lock update. +- Prerequisites/restart: Protected branch, trusted GitHub identities, supplier keys, registry, OIDC and evidence retention. / Input changes produce a new digest; consumers must explicitly roll out that digest. +- Operational impact: Fail-closed verification can delay builds and releases. +- Loss of protection: Unverified mirrors, mutable refs, compromised keys/runners, or missing evidence break traceability. +- Limitations: Provenance establishes recorded build inputs, not supplier or CI infallibility. +- Residual risk: Supplier, signing-key, CI runner, registry, or evidence systems can still be compromised. +- Evidence: `docs/RELEASE.md` +- Owner/review: maintainer / pending-independent-review diff --git a/docs/CRYPTOGRAPHIC-BOUNDARY.md b/docs/CRYPTOGRAPHIC-BOUNDARY.md new file mode 100644 index 0000000..00828d9 --- /dev/null +++ b/docs/CRYPTOGRAPHIC-BOUNDARY.md @@ -0,0 +1,38 @@ +# Cryptographic boundary and claims + +There is no FIPS 140 validation claim for `postgresql-ubi`. UBI/RHEL origin, +host FIPS mode, TLS 1.2/1.3 support, SCRAM-SHA-256, checksums, SHA-256 artifact +hashes, or Cosign signatures do not by themselves establish that PostgreSQL or +the image is operating inside a validated cryptographic module boundary. + +## Separate mechanisms and owners + +| Mechanism | Boundary and purpose | Evidence needed before a claim | Owner | +| --- | --- | --- | --- | +| PostgreSQL SCRAM-SHA-256 | PostgreSQL client/server authentication and verifier in `PGDATA`; not data-at-rest encryption. | Exact server/client builds, linked crypto behavior, configured method/iterations, stored verifier migration, protocol tests. | Image supports; database owner configures. | +| PostgreSQL TLS | Server executable, linked OpenSSL libraries/providers, key/certificate files, client library/trust, DNS and time. | `ldd`/package inventory per architecture, provider and mode output, effective TLS settings, client verification tests, applicable CMVP certificate/boundary. | Image, deployment, PKI, client and host share. | +| PostgreSQL data checksums | PostgreSQL page-corruption detection inside a cluster; not encryption, signatures, or malicious-tamper prevention. | Initialization state, `pg_controldata`, corruption detection and recovery procedure. | Database/storage owner. | +| Build hashes and RPM signatures | Acquisition workstation/runner, RPM verification tools, pinned publisher keys, lock files. | Tool/package versions, key fingerprints, source/binary mapping, negative verification tests. | Maintainer. | +| Release signing/attestation | GitHub OIDC identity, Cosign/Rekor services, workflow and GHCR digest. Protects artifact identity, not database cryptographic operations. | Signed immutable digest, bundle, identity/issuer policy and independent verification. | Maintainer/platform providers. | +| Host/runtime cryptography | Host kernel crypto state, OpenSSL policy/providers, runtime, storage/network encryption and hardware. | Exact qualified host/runtime, boot state, policies, modules, architecture and deployment test evidence. | Host/platform organization. | + +The locked package manifests and SBOM identify installed OpenSSL and PostgreSQL +packages. CI must also retain `ldd` output for `postgres`, `openssl version` or +provider inventory where the executable exists, PostgreSQL build configuration, +architecture, and the image digest. A package name is not proof that a process +loaded a module or ran in an approved mode. + +## Claim gate + +A future cryptographic or FIPS claim requires a security reviewer to identify +the exact library/module and version, module boundary, architecture, provider +configuration, operational environment, algorithms/services used, applicable +non-expired CMVP certificate, and correspondence between that certificate and +the shipped binary. The reviewer must test approved and failure modes on the +exact release digest and supported host profile. Any library, provider, +PostgreSQL, base image, host mode, architecture, build flag, or certificate +change invalidates that evidence. + +Until that work is complete, SC-13 is `research-required`, and deployments must +make their own authorization decision without citing this project as FIPS +validated. diff --git a/docs/INCIDENT-RESPONSE.md b/docs/INCIDENT-RESPONSE.md new file mode 100644 index 0000000..2baf6c0 --- /dev/null +++ b/docs/INCIDENT-RESPONSE.md @@ -0,0 +1,49 @@ +# Incident response responsibilities + +The deploying organization commands incidents involving its system and data. +Datopsis commands repository, build, signing, and published-image incidents and +coordinates with PostgreSQL, PGDG, Red Hat, GitHub, and registry providers. + +## Response sequence + +1. **Declare and preserve.** Assign incident commander, recorder, scope and + time source. Preserve image digest, manifest, host/runtime versions, + configuration hashes, access/audit logs, storage snapshots, release bundles, + workflow runs, and relevant volatile state under legal/privacy rules. Never + put database contents, credentials, private keys, personal data, or internal + topology in a public issue. +2. **Contain.** Restrict ingress/egress, revoke sessions, isolate affected + workloads and runners, disable compromised workflows/credentials, and + quarantine affected registry digests. Do not move/reuse a tag or destroy the + only evidence. Weigh immediate shutdown against evidence and availability. +3. **Rotate trust.** Rotate database/application credentials, bootstrap and + backup secrets, TLS keys/certificates, CA trust where required, registry and + GitHub credentials, signing authority, and mirror credentials. Reissuing a + certificate without revocation/consumer trust updates is incomplete. +4. **Eradicate and recover.** Determine root cause; rebuild from reviewed locked + inputs on clean infrastructure; restore only from verified, isolated, + tested recovery points; qualify and sign a new digest. PostgreSQL data + downgrade is not assumed safe. +5. **Notify and withdraw.** Coordinate embargoes and vendor reports. Identify + affected digests/architectures, exposure, mitigation, replacement, evidence + confidence, and rollback/restore limits. Mark releases withdrawn and decide + registry quarantine/removal while preserving restricted evidence. +6. **Learn.** Within the organization's target period, record timeline, root + and contributing causes, control failures, data/evidence impact, detection + gaps, corrective owners/dates, and needed threat/control updates. Rehearse + corrections before closure. + +## Decision ownership + +| Decision | Accountable party | +| --- | --- | +| Customer workload isolation, data impact, legal/regulatory notification | Deploying organization incident/legal/privacy owners | +| Database credential, TLS certificate, backup key, and application-secret rotation | Database, PKI, backup, and application owners | +| Host/runtime isolation, forensic capture, network controls | Platform/security operations | +| Repository/workflow lockdown, upstream coordination, image withdrawal and replacement | Datopsis maintainer | +| Registry quarantine and consumer notice delivery | Datopsis plus registry/communications owner | +| Backup selection, integrity, restore point and return to service | Organization recovery authority | + +Tabletop this sequence before first release and annually, and perform a focused +rehearsal after material trust-boundary changes. A drill uses synthetic facts +and no production secrets or real vulnerability report. diff --git a/docs/RESPONSIBILITY-MATRIX.md b/docs/RESPONSIBILITY-MATRIX.md new file mode 100644 index 0000000..9de1cfa --- /dev/null +++ b/docs/RESPONSIBILITY-MATRIX.md @@ -0,0 +1,25 @@ +# Deployment responsibility matrix + +The image cannot implement the following system controls. A deployment is not +supported merely because it starts; owners must select, document, test, monitor, +and periodically reassess these controls. + +| Area | Deployment requirement | Evidence owner | +| --- | --- | --- | +| Network/firewall/ingress | Default-deny reachability; expose 5432 only to approved clients; control egress, admin paths and segmentation; test unauthorized paths. | Network/platform owner | +| DNS and time | Authenticated/controlled name resolution as required; trusted synchronized time for certificates, logs, recovery and evidence. | Platform infrastructure | +| Secrets | File-mounted least-privilege secrets; generation, access audit, rotation, revocation and deletion; never environment/argv/image. | Secret/database owner | +| SELinux | Enforcing supported policy and correct volume labels; review denials; never disable enforcement as a routine fix. | RHEL/platform owner | +| Seccomp and capabilities | Runtime default/reviewed seccomp, drop all capabilities, no-new-privileges, no privilege/host namespaces/devices. | Runtime/platform owner | +| Monitoring and SIEM | Health/availability/security events, authenticated collection, access controls, capacity/rate alerts, interruption detection and correlation. | Operations/security monitoring | +| Logs | Event policy, minimization/redaction, trusted timestamps, integrity, restricted retention, search/export and verified disposal. | SIEM/privacy/records owners | +| Backups | Consistent method, encryption, key separation, restricted custody, immutability where needed, retention/disposal, isolated restore tests and RPO/RTO. | Database/storage/recovery owners | +| Storage | Dedicated durable volume, ownership/labels, encryption/access, capacity and latency alerts, snapshot semantics, sanitization. | Storage/platform owner | +| Resources | CPU/memory/PID/connection/disk/WAL/log quotas, bounded `/dev/shm` and `/tmp`, restart throttling and workload capacity tests. | Platform/database owner | +| Controlled transfer | Verify public digest/signature/evidence before transfer; record internal digest mapping, chain of custody, internal CA/time, advisory/feed age and rollback. | Registry/network security | +| Decommissioning | Stop clients, revoke all credentials/certificates, remove workload, sanitize volumes, expire/sanitize backups and logs, remove DNS/network rules, retain required incident/release evidence. | System/data owner | + +The organization must also assign application, database, host, PKI, network, +storage, backup, SIEM, vulnerability-acceptance, incident, privacy/legal, and +authorization owners. Separation of duties and approval thresholds are local +governance decisions; this project does not invent them. diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index 6dd5302..110f2a6 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -343,33 +343,33 @@ documented least privileges. ### Authoritative requirements and control ownership -- [ ] Create an authoritative source register recording publisher, title, +- [x] Create an authoritative source register recording publisher, title, release, date, retrieval date, URL, SHA-256, current/superseded status, and license or redistribution handling. -- [ ] Analyze applicable NIST SP 800-53 Rev. 5 and SP 800-53A Rev. 5, the DISA +- [x] Analyze applicable NIST SP 800-53 Rev. 5 and SP 800-53A Rev. 5, the DISA Database SRG and Container Platform SRG, current RHEL 9 STIG content, and active PostgreSQL-distribution STIGs. Product-specific STIG commands and assumptions are references only until proven applicable to PGDG PostgreSQL. -- [ ] Classify every analyzed requirement exactly once as image-owned, +- [x] Classify every analyzed requirement exactly once as image-owned, deployment-supported, inherited, not applicable, unsupported, or research required, with rationale, residual risk, evidence, owner, and review status. Require independent review of adopted, excluded, unsupported, and not-applicable decisions. -- [ ] Publish a schema-valid NIST OSCAL Component Definition as the canonical +- [x] Publish a schema-valid NIST OSCAL Component Definition as the canonical component artifact and deterministically generate an SCTM-importable CSV and human-readable control implementation view from it. Do not represent these as a completed system SCTM, SSP, authorization, or assessor decision. -- [ ] Give each supported control an examine/test/interview assessment method, +- [x] Give each supported control an examine/test/interview assessment method, default and configurable state, prerequisites, restart behavior, operational impact, loss-of-protection statement, limitations, residual risk, evidence pointer, and image/deployment/host/organization owner. ### Threats, hardening, and compliance evidence -- [ ] Publish architecture, build/assurance pipeline, runtime data-flow, +- [x] Publish architecture, build/assurance pipeline, runtime data-flow, credential and TLS trust, storage/backup, controlled-network, and control-ownership diagrams. -- [ ] Publish a threat model covering artifact and key substitution, CI/cache +- [x] Publish a threat model covering artifact and key substitution, CI/cache and runner compromise, malicious pull requests, tag/registry replacement, evidence tampering, runtime identity, arbitrary UID, secret disclosure, authentication downgrade, configuration injection, exposed listeners, @@ -377,18 +377,18 @@ documented least privileges. exhaustion, storage tampering/corruption, backup theft or failed restoration, TLS key/trust compromise, sensitive logs, vulnerable dependencies, update failure, rollback, and decommissioning. -- [ ] Map every threat to mitigations, validation, owner, limitations, and open +- [x] Map every threat to mitigations, validation, owner, limitations, and open risk. A clean vulnerability scan does not close design or abuse-case threats. -- [ ] Run pinned OpenSCAP and ComplianceAsCode discovery against a +- [x] Run pinned OpenSCAP and ComplianceAsCode discovery against a root-owner-preserving, never-executed export of each architecture image. Select only image-owned rules, document every inclusion/exclusion and rule rationale, publish tailoring, distinguish failures from not-applicable and deployment-owned controls, and make scanner execution errors blocking. -- [ ] Keep SCAP findings report-only until the profile and false-positive +- [x] Keep SCAP findings report-only until the profile and false-positive process receive security review and an explicit blocking policy is approved. State the exact filesystem evidence boundary and make no host, deployment, STIG-certification, or compliance claim. -- [ ] Define the cryptographic boundary for PostgreSQL password hashing, TLS, +- [x] Define the cryptographic boundary for PostgreSQL password hashing, TLS, checksums, signing, and the host/runtime. Identify actual linked libraries, module versions, provider/mode behavior, architectures, and any applicable CMVP certificate before making a claim. UBI, RHEL FIPS mode, TLS 1.2/1.3, or @@ -396,20 +396,20 @@ documented least privileges. ### Vulnerability, incident, and exception management -- [ ] Publish a vulnerability process covering PostgreSQL/PGDG and Red Hat +- [x] Publish a vulnerability process covering PostgreSQL/PGDG and Red Hat advisory precedence, scanner disagreement, package/source-package grouping, reachability and exposure, fixed versus unfixed findings, patch SLAs, disclosure coordination, emergency rebuilds, and periodic reassessment. -- [ ] Require every suppression or accepted finding to name the exact digest +- [x] Require every suppression or accepted finding to name the exact digest and architecture, advisory, affected component, vendor status, rationale, compensating control, owner, approval, expiry, and rescan trigger. Never use a blanket or permanent ignore merely to make a release pass. -- [ ] Confirm the private vulnerability route is monitored and rehearse the +- [x] Confirm the private vulnerability route is monitored and rehearse the response path without filing a real report or exposing sensitive details. -- [ ] Publish incident, containment, credential/certificate rotation, forensic +- [x] Publish incident, containment, credential/certificate rotation, forensic evidence, backup recovery, customer notification, release withdrawal, registry quarantine, and lessons-learned responsibilities. -- [ ] Document network policy, firewall/ingress, DNS, secret service, SELinux, +- [x] Document network policy, firewall/ingress, DNS, secret service, SELinux, seccomp, monitoring/SIEM, log retention and disposal, backup encryption and access, resource limits, time synchronization, controlled transfer, and decommissioning responsibilities that remain outside the image. @@ -418,6 +418,11 @@ documented least privileges. control from an authoritative source through ownership, implementation, assessment method, evidence, limitation, and residual risk. +Implemented by the Package 5 security engineering artifacts. All initial +classification decisions remain explicitly pending independent review, SCAP is +report-only, and no FIPS, STIG-certification, system authorization, or +deployment-compliance claim is made. + ## Package 6: deployment and platform qualification - [ ] Publish an exact support matrix for architecture, RHEL/kernel, Podman, diff --git a/docs/SCAP.md b/docs/SCAP.md new file mode 100644 index 0000000..d96f5cd --- /dev/null +++ b/docs/SCAP.md @@ -0,0 +1,55 @@ +# SCAP discovery boundary + +The `SCAP discovery` workflow runs on native amd64 and arm64 GitHub runners. It +uses Ubuntu's exact OpenSCAP `1.3.9+dfsg-1.1ubuntu2` packages and the +checksum-pinned ComplianceAsCode 0.1.81 prebuilt RHEL 9 data stream. + +The workflow builds from the repository's locked bundle, creates a container +without starting it, verifies state `created`, exports it, and extracts the tar +with numeric owners preserved. `oscap-chroot` examines that filesystem. It does +not start PostgreSQL or execute a binary from the target image. + +## Evidence boundary and selected rules + +The boundary is only files present in the immutable image export: their path, +numeric user/group, and permission bits. The tailoring selects four rules for +unowned users, world-writable files, setuid, and setgid. The proposed +`no_files_unowned_by_group` identifier is not present in the pinned RHEL 9 +content and is explicitly excluded as research-required. The exact +decisions and grouped exclusions are in `compliance/scap/rule-decisions.csv`. +The XML profile intentionally does not extend the RHEL 9 STIG profile. + +Host kernel, boot, partitions, services, PAM/SSH, firewall, DNS, time, audit, +SIEM, runtime namespace, mounted data/configuration/secrets, active crypto +provider, PostgreSQL settings, backup, and orchestration evidence are outside +this scan. Rules needing those facts are not treated as passes; they are +not-applicable to this evidence boundary, deployment-owned, or research +required. + +## Result policy + +- OpenSCAP exit `0` or finding exit `2` produces evidence and permits the job + to continue. Findings are report-only pending false-positive and security + review. +- Download, checksum, install/version, export/ownership, parser, content, + scanner execution, or missing-report errors fail the job. +- A reviewer triages each `fail`, `notapplicable`, `notchecked`, `unknown`, and + `error` by architecture. `notapplicable` is not silently converted to pass. +- No remediation is applied by the scanner. +- Enabling a blocking finding policy requires an independently reviewed rule + set, false-positive/exception process, repeatable evidence on both + architectures, and an explicit roadmap/branch-protection change. + +The reports are not evidence that a host, runtime, deployment, database, or +organization satisfies the RHEL 9 STIG or any authorization baseline. The +project makes no DISA approval, STIG certification, or compliance claim. + +## Initial discovery evidence + +Pull request 8, workflow run `34694959686`, evaluated commit `e1bd229e` on +native AMD64 and ARM64. Both architectures returned scanner exit `0`: setuid, +setgid, and world-writable-file rules passed; the unowned-user rule was +`notapplicable`; 1,528 other RHEL rules were `notselected`. The result does not +convert `notapplicable` into pass. Run artifacts retain each ARF, HTML report, +status, and build provenance for 14 days; release evidence must be copied to +the durable store defined by the release policy. diff --git a/docs/SECURITY-ENGINEERING.md b/docs/SECURITY-ENGINEERING.md new file mode 100644 index 0000000..dbf6302 --- /dev/null +++ b/docs/SECURITY-ENGINEERING.md @@ -0,0 +1,116 @@ +# Security engineering record + +This package describes component capabilities and shared responsibilities. It is +not a completed Security Controls Traceability Matrix (SCTM), System Security +Plan (SSP), authorization, assessor decision, STIG certification, or claim that +an operating deployment is compliant. + +## Authoritative sources and scope + +`compliance/source-register.json` is the machine-readable source register. Its +hashes were calculated from the exact retrieved payloads on 2026-09-12. The +source documents are referenced rather than copied so upstream license, +distribution, and update status stay visible. A quarterly review, every +PostgreSQL minor update, and any assurance-tool update must recheck publication +status and hashes. + +The analysis used NIST SP 800-53 Rev. 5 and SP 800-53A Rev. 5 Release 5.2.0; +DISA Database SRG V4R5, Container Platform SRG V2R4, and RHEL 9 STIG V2R9; +PostgreSQL 18 documentation; OSCAL 1.2.3; and ComplianceAsCode 0.1.81. The +active Crunchy Data Postgres 16 STIG V1R3 was reviewed only for concepts. Its +paths, packaging, extensions, scripts, roles, and version are not this PGDG +PostgreSQL 18 component. The PostgreSQL 9.x STIG is sunset. Neither product +guide supplies executable instructions or evidence for this image. + +This is a risk-based component subset, not an assertion that every control in +the source catalogs applies to an OCI image. The selected NIST controls cover +authorization, logging, baseline configuration, recovery, authenticators, +incident response, transport, cryptography, flaw remediation, and supply-chain +integrity. The DISA review separated requirements into: + +- image filesystem and supply-chain requirements the repository can own; +- database mechanisms the image can support but a deployment must configure; +- host, container-platform, network, identity, monitoring, backup, and incident + requirements inherited from operators; and +- product-specific or cryptographic claims that remain unsupported or require + research. + +The concrete cross-source sample reviewed for the initial component boundary is +listed below. Broad source catalogs are not silently marked satisfied; only +these requirements were classified as component records, while the SCAP rule +decision file records the RHEL rule boundary. + +| Source requirement | OSCAL record | Classification and disposition | +| --- | --- | --- | +| NIST AC-3; `SRG-APP-000033-DB-000084` | AC-3 | Deployment-supported authorization mechanisms. | +| NIST AU-2; `SRG-APP-000091-DB-000066` | AU-2 | Deployment-supported logging; SIEM is inherited. | +| NIST CM-2; `SRG-APP-000516-CTR-001325` | CM-2 | Image-owned immutable component baseline. | +| NIST CP-9 | CP-9 | Inherited backup policy and execution. | +| NIST IA-5; `SRG-APP-000171-DB-000074` | IA-5 | Deployment-supported SCRAM/bootstrap behavior. | +| NIST IR-4 | IR-4 | Inherited system response with maintainer withdrawal support. | +| NIST SC-8; `SRG-APP-000441-DB-000378` | SC-8 | Deployment-supported TLS; network/client enforcement is external. | +| NIST SC-13; `SRG-APP-000179-DB-000114` | SC-13 | Research required; no validated-module claim. | +| NIST SI-2; `SRG-APP-000456-DB-000390` | SI-2 | Image-owned monitoring/rebuild/withdrawal process. | +| NIST SR-4 | SR-4 | Image-owned publisher, source, provenance, and signing verification. | + +The active Crunchy Data Postgres 16 STIG is classified `not-applicable` as +executable product guidance for PGDG PostgreSQL 18. Its security concepts are +cross-check inputs only. The sunset PostgreSQL 9.x STIG is `not-applicable` and +superseded for this version. A future active PGDG PostgreSQL 18 guide would be +`research-required` until every packaging and command assumption is tested. + +`compliance/oscal/component-definition.json` is canonical. Every analyzed +control has exactly one ownership classification and records rationale, +residual risk, evidence, owner, review state, all three SP 800-53A assessment +method types where appropriate, default/configurable state, prerequisites, +restart behavior, operational impact, loss of protection, and limitations. +Run `python scripts/cybersecurity.py` to regenerate the SCTM-importable CSV and +human view, or add `--check` to detect drift. + +## Review and decision rules + +All initial entries are `pending-independent-review`. Before first release, a +reviewer who did not author the decision must examine adopted, excluded, +unsupported, research-required, and not-applicable classifications. The review +record must identify the commit, reviewer, date, sources, disagreements, +decision, and follow-up issue. The accountable maintainer cannot self-approve +this independence requirement. + +A reviewer must reject an entry if its evidence does not demonstrate the +described component behavior, if a deployment responsibility is represented as +image-owned, or if the limitation/residual risk is missing. Any material input, +behavior, architecture, tool, rule selection, or source update invalidates the +affected review. + +## Artifact integrity and schema validation + +CI downloads the official NIST OSCAL Component Definition schema 1.2.3, +verifies SHA-256 +`95e76881151ececd5cb1a93ff0f70ad74b8cc1aa58771626ac8b262bf2c8e001`, +then validates the component definition with hash-locked Python dependencies. +Repository tests independently enforce complete properties, one +classification per control, evidence paths, source-register format, generated +view freshness, and vulnerability-exception constraints. + +## Evidence lifecycle + +Evidence identifies a commit or immutable image digest, architecture, test/tool +version, input hashes, time, result, and retained artifact. A new image digest, +package closure, PostgreSQL behavior, base image, control source, scanner, +content version, database, configuration profile, or assessment method makes +the affected evidence stale. Stale evidence is retained for history but cannot +qualify a newer candidate. + +The trace is: + +`source-register.json` → OSCAL control and classification → repository behavior +or external owner → assessment method → evidence path → limitation and residual +risk → independent review record. + +## Known open decisions + +- No independent Package 5 review has yet been recorded. +- SCAP is report-only discovery and cannot qualify a host or deployment. +- No applicable FIPS/CMVP boundary has been established. +- No product-specific PostgreSQL 18 STIG exists for this PGDG image. +- Package 6 must qualify exact platforms and external control implementations. diff --git a/docs/SUPPORT.md b/docs/SUPPORT.md index 0775fb7..cf2a4cd 100644 --- a/docs/SUPPORT.md +++ b/docs/SUPPORT.md @@ -38,7 +38,7 @@ not a supported release. | Replication, pooling, and high availability | Unsupported for v1 | These require separate topology, availability, and recovery qualification. | | Additional extensions | Unsupported for v1 | Only the selected PGDG server/client package closure is in scope. | | FIPS validation or approved mode | Unsupported | No PostgreSQL image cryptographic module or operational boundary has been validated. | -| STIG certification or system compliance | Unsupported | Planned SCAP evidence is limited to explicitly selected image-filesystem checks. | +| STIG certification or system compliance | Unsupported | Report-only SCAP evidence is limited to explicitly selected image-filesystem checks. | ## Approved first-release boundary diff --git a/docs/THREAT-MODEL.md b/docs/THREAT-MODEL.md new file mode 100644 index 0000000..9ee3639 --- /dev/null +++ b/docs/THREAT-MODEL.md @@ -0,0 +1,41 @@ +# Threat model + +Scope is the source repository, build/release pipeline, OCI image, PostgreSQL +process, configured runtime, persistent data, and evidence. A clean package +vulnerability scan closes none of the design or abuse-case threats below. + +| ID | Threat and consequence | Mitigation | Validation | Owner | Limitation and open risk | +| --- | --- | --- | --- | --- | --- | +| T01 | Artifact, repository metadata, or publisher-key substitution executes malicious code. | Allowlisted HTTPS origins, exact hashes, RPM signatures/fingerprints, SRPM mapping, offline final build. | Negative lock tests and bundle verification. | Maintainer | Publisher or signing infrastructure compromise remains possible. | +| T02 | CI runner, action, dependency cache, or build service compromise changes output/evidence. | Commit-pinned actions, least permissions, no release secrets on PRs, native builds, provenance. | Zizmor, workflow review, independent artifact verification. | Maintainer/GitHub | Hosted-runner compromise cannot be eliminated by repository controls. | +| T03 | A malicious pull request exfiltrates credentials or weakens checks. | Fork PRs receive no write/signing authority; protected checks and review; release workflow is tag-gated. | Review workflow permissions and adversarial PR rehearsal. | Maintainer | Maintainer account compromise and unsafe workflow approval remain risks. | +| T04 | Tag or registry replacement causes consumers to run unreviewed bits. | Digest-first identity, immutable version policy, signed digest, independent pull verification; never reuse tags. | Release verification and registry policy review. | Maintainer/registry owner | Registry administrators retain platform authority. | +| T05 | Evidence is edited, mismatched, deleted, or presented for another digest/architecture. | Evidence binds commit, digest, architecture, inputs and tools; release copy goes to immutable backup. | Recompute hashes and verify signatures/provenance from a clean environment. | Maintainer/evidence custodian | Short-lived CI artifacts are not durable release evidence. | +| T06 | Root runtime identity or excess capabilities leads to host/container takeover. | Numeric nonroot default, drop all capabilities, no-new-privileges, read-only root recommendation. | Restricted-runtime smoke and negative tests. | Image/deployment/host | Kernel/runtime vulnerabilities and operator overrides remain. | +| T07 | Arbitrary UID behavior loses access isolation or forces unsafe permissions. | Group-0-compatible owned directories without world-writable modes; OpenShift work is separately qualified. | Native arbitrary-UID tests in Package 6. | Image/platform | Arbitrary UID is preview until Package 6 qualifies restricted SCC. | +| T08 | Bootstrap or runtime secrets leak through environment, argv, layers, logs, mounts, or evidence. | File-based secret; reject missing/unsafe files; redact evidence; no secret in image/argv. | Runtime negative tests and log/image inspection. | Image/deployment | Root/host administrators and compromised applications can access runtime secrets. | +| T09 | Authentication downgrade (`trust`, `password`, MD5, permissive HBA) bypasses SCRAM. | Safe generated HBA, SCRAM default, reject trust configuration, document overrides as loss of protection. | Authentication negative tests and effective-config review. | Image/deployment | Mounted configuration can intentionally weaken policy. | +| T10 | Configuration or initialization-script injection executes unintended settings or SQL. | No automatic arbitrary script directory; validate inputs; read-only protected mounts; review SQL migrations. | Malformed configuration tests and deployment review. | Image/deployment | A database owner can intentionally execute privileged SQL. | +| T11 | Listener is exposed to unauthorized networks. | Explicit port publishing, HBA restrictions, firewall/NetworkPolicy, no host network. | External connection matrix and port scan. | Deployment/platform | Image metadata cannot enforce ingress. | +| T12 | Untrusted or spoofed clients steal credentials or data. | TLS, optional client certificates, SCRAM, least-privilege roles, network policy. | TLS identity and unauthorized-client tests. | Deployment/PKI | TLS is opt-in and client `verify-full` policy is external. | +| T13 | SQL injection, dangerous functions, extensions, or superuser abuse compromises data/system. | Parameterized applications, least privilege, extension allowlist, role separation. | Application security tests and privilege review. | Application/database owner | The image cannot validate application query construction. | +| T14 | Connection, query, lock, memory, CPU, disk, WAL, or log denial of service removes availability. | Cgroup limits, bounded tmpfs, timeouts, quotas, connection pools, capacity alerts. | Resource exhaustion and recovery tests. | Deployment/platform | Limits trade availability and performance; distributed DoS remains. | +| T15 | Shared memory or temporary-space exhaustion/crash causes corruption or restart loops. | Bounded `/dev/shm` and `/tmp`, resource sizing, restart throttling, graceful shutdown. | Resource and lifecycle tests. | Image/deployment | Workload-specific sizing cannot be selected by image. | +| T16 | Persistent storage tampering, loss, wrong ownership, or corruption changes database state. | Protected dedicated volume, PostgreSQL checksums where chosen, access controls, backups. | Permission, crash/restart, checksum and restore tests. | Storage/database owner | PostgreSQL checksums do not provide confidentiality or malicious-tamper proof. | +| T17 | Backup theft, inconsistency, deletion, or failed restoration causes disclosure/loss. | Encrypted restricted backups, independent custody, integrity checks, recurring isolated restores. | Restore rehearsal against defined RPO/RTO. | Organization | Image lifecycle tests are not an organizational recovery test. | +| T18 | TLS private key, CA, DNS, clock, or client trust compromise enables interception/spoofing. | Protected keys, short validity, rotation/revocation, `verify-full`, trusted DNS/time, channel binding where appropriate. | Certificate failure and rotation tests. | PKI/deployment | Host administrators and compromised trust anchors remain powerful. | +| T19 | Logs expose passwords, SQL data, identifiers, internal topology, or evidence secrets. | Avoid statement/parameter logging by default; restrict collector; redact support bundles; retention/disposal policy. | Canary-secret log scan and access review. | Deployment/SIEM | Required audit detail can conflict with data minimization. | +| T20 | Vulnerable, abandoned, or unexpected dependency is exploitable. | Minimal locked closure, SBOM, two scanners, vendor advisories, periodic rebuild, expiring exceptions. | Trivy/Grype plus PGDG/Red Hat advisory analysis. | Maintainer | Unknown flaws and scanner disagreement remain. | +| T21 | Update changes format/behavior or fails midway, producing outage or data loss. | PostgreSQL minor-line policy, preserved-data tests, backups, staged rollout and health gates. | Minor-update and rollback rehearsal. | Maintainer/deployment | PostgreSQL does not support downgrade of modified clusters as a generic rollback. | +| T22 | Rollback selects vulnerable bits or incompatible data. | Digest-pinned prior candidate, forward-restored backup plan, explicit compatibility decision. | Restore and rollback rehearsal. | Deployment | Rollback may require data loss to last safe recovery point. | +| T23 | Incident containment destroys volatile/forensic evidence or prolongs compromise. | Defined authority, evidence capture order, credential rotation, quarantine without tag reuse. | Tabletop rehearsal. | Organization/maintainer | Evidence collection must not expose customer data publicly. | +| T24 | Decommission leaves volumes, backups, keys, secrets, logs, or registry artifacts recoverable. | Inventory, revoke credentials, cryptographic erase/sanitize media, expire backups, tombstone releases. | Decommission checklist and sampling. | Organization/platform | Container deletion alone does not erase external data. | +| T25 | Controlled-network mirror or stale security feed supplies substituted or obsolete material. | Verify public digest/evidence before transfer; record internal mapping, feed age, custody, CA/time; immutable mirror. | Disconnected import and rollback rehearsal. | Network/registry owner | Air gaps delay notices and do not establish integrity by themselves. | +| T26 | Platform isolation failure crosses tenants through kernel, volume, IPC, or network namespace. | Supported runtime, SELinux/seccomp, no privilege/host paths, dedicated volumes/network, timely host patches. | Platform conformance and cross-tenant negative tests. | Host/platform | The OCI image cannot repair a compromised host kernel. | + +## Review cadence + +Review this model for every release, architecture/platform expansion, security +incident, new feature, material PostgreSQL/UBI behavior, or trust-boundary +change. New threats receive a stable ID, owner, validation, limitation, and +tracked risk decision; deleting a threat requires independent review. diff --git a/docs/VULNERABILITY-MANAGEMENT.md b/docs/VULNERABILITY-MANAGEMENT.md new file mode 100644 index 0000000..3b1ca7d --- /dev/null +++ b/docs/VULNERABILITY-MANAGEMENT.md @@ -0,0 +1,63 @@ +# Vulnerability and exception management + +This process supplements `SECURITY.md` and `docs/MAINTENANCE.md`. + +## Intake and triage + +Review PostgreSQL security notices/release notes, PGDG package/source changes, +Red Hat errata, private reports, Dependabot/Renovate, Trivy, Grype, and SCAP. +PostgreSQL/PGDG is authoritative for PostgreSQL packaging and upstream fixes; +Red Hat is authoritative for UBI packages. Scanner records are detection leads, +not vendor status. + +Group duplicates by advisory, affected source package, binary package, +architecture, and exact image digest. For disagreement, preserve both raw +results and record version normalization, CPE/purl match, vendor affected/not +affected status, installed code, reachability, configuration, network/data +exposure, exploit maturity, and compensating controls. Do not select the most +convenient severity or erase an unfixed finding. + +Fixed Critical/High findings block release. For an unfixed or disputed finding, +the response target in `SECURITY.md` still requires mitigation, withdrawal, or +an approved expiring risk decision. Emergency builds use the same acquisition, +native architecture, test, SBOM, scan, provenance, signing, and immutable-tag +gates; urgency does not authorize bypass. + +Reassess at least weekly, on database updates, on scanner database/tool changes, +on credible reports, and before release. Coordinate embargoed disclosure through +the upstream security route and GitHub private advisory; keep reporter identity, +exploits, credentials, data, and internal topology out of public artifacts. + +## Exceptions + +`compliance/vulnerability-exceptions.json` is the sole machine-readable +registry. Each entry must contain the exact `sha256:` image digest, +architecture, advisory, component, vendor status, rationale, compensating +control, accountable owner, approval record, calendar expiry, and rescan +trigger. The JSON schema and tests reject missing, broad, architecture-neutral, +mutable, or permanent entries. + +An exception is invalid after expiry; a changed digest, architecture, package, +vendor advisory, exploit status, exposure, mitigation, scanner database, or +new fix triggers immediate rescan/review. Exceptions do not suppress raw +scanner evidence. A release note identifies applicable reviewed exceptions +without publishing sensitive exploit detail. + +## Private-route rehearsal + +On 2026-09-12 an authenticated read-only GitHub API check returned +`{"enabled":true}` for private vulnerability reporting. The safe desk rehearsal +also verified that `SECURITY.md` routes reporters privately, contains upstream +escalation routes and response targets, and does not request secrets or data. +`@joey-huckabee` is the named monitor in `docs/MAINTENANCE.md`. The setting and +monitor must be rechecked before first release and quarterly. Do not submit a +real advisory for a drill, upload a proof of concept, or include customer +information. + +The rehearsal sequence is: designated owner confirms monitoring → second person +sends a prearranged nonsensitive notification outside the advisory intake → +owner acknowledges within target → team walks triage/upstream/containment and +draft notification paths → delete no evidence → record gaps privately. This +desk rehearsal walked those decisions but did not test notification delivery or +a second responder. The single-maintainer availability risk remains open until +a backup monitored owner is assigned. diff --git a/scripts/cybersecurity.py b/scripts/cybersecurity.py new file mode 100644 index 0000000..de9609d --- /dev/null +++ b/scripts/cybersecurity.py @@ -0,0 +1,282 @@ +#!/usr/bin/env python3 +"""Validate Package 5 records and render deterministic control views.""" + +from __future__ import annotations + +import argparse +import csv +import hashlib +import io +import json +from pathlib import Path +import re +import sys +from typing import Any + + +ROOT = Path(__file__).resolve().parents[1] +OSCAL = ROOT / "compliance" / "oscal" / "component-definition.json" +SOURCES = ROOT / "compliance" / "source-register.json" +EXCEPTIONS = ROOT / "compliance" / "vulnerability-exceptions.json" +CSV_VIEW = ROOT / "compliance" / "sctm.csv" +MD_VIEW = ROOT / "docs" / "CONTROL-IMPLEMENTATION.md" +NS = "https://datopsis.dev/ns/postgresql-ubi" + +CLASSIFICATIONS = { + "image-owned", + "deployment-supported", + "inherited", + "not-applicable", + "unsupported", + "research-required", +} +REQUIRED_PROPS = ( + "classification", + "source-requirement", + "rationale", + "residual-risk", + "evidence", + "owner", + "review-status", + "assessment-method", + "default-state", + "configurable-state", + "prerequisites", + "restart-behavior", + "operational-impact", + "loss-of-protection", + "limitations", +) +EXCEPTION_FIELDS = ( + "image_digest", + "architecture", + "advisory", + "component", + "vendor_status", + "rationale", + "compensating_control", + "owner", + "approval", + "expires", + "rescan_trigger", +) + + +class ValidationError(ValueError): + """A Package 5 artifact is invalid.""" + + +def load_json(path: Path) -> Any: + try: + return json.loads(path.read_text(encoding="utf-8")) + except (OSError, json.JSONDecodeError) as exc: + raise ValidationError(f"{path}: {exc}") from exc + + +def properties(requirement: dict[str, Any]) -> dict[str, str]: + result: dict[str, str] = {} + for prop in requirement.get("props", []): + if prop.get("ns") != NS: + continue + name = prop.get("name") + value = prop.get("value") + if not isinstance(name, str) or not isinstance(value, str): + raise ValidationError("control properties must have string name/value") + if name in result: + raise ValidationError( + f"{requirement.get('control-id')}: duplicate property {name}" + ) + result[name] = value + return result + + +def control_records(document: dict[str, Any]) -> list[dict[str, str]]: + try: + implementations = document["component-definition"]["components"][0][ + "control-implementations" + ] + except (KeyError, IndexError, TypeError) as exc: + raise ValidationError("missing OSCAL control implementations") from exc + + records: list[dict[str, str]] = [] + seen: set[str] = set() + for implementation in implementations: + for requirement in implementation.get("implemented-requirements", []): + control_id = requirement.get("control-id") + if not isinstance(control_id, str) or not control_id: + raise ValidationError("implemented requirement has no control-id") + if control_id in seen: + raise ValidationError(f"control classified more than once: {control_id}") + seen.add(control_id) + props = properties(requirement) + missing = [name for name in REQUIRED_PROPS if not props.get(name)] + if missing: + raise ValidationError(f"{control_id}: missing {', '.join(missing)}") + if props["classification"] not in CLASSIFICATIONS: + raise ValidationError( + f"{control_id}: invalid classification {props['classification']}" + ) + if props["review-status"] not in { + "reviewed", + "pending-independent-review", + }: + raise ValidationError(f"{control_id}: invalid review status") + if not props["evidence"].startswith(("docs/", "tests/", ".github/")): + raise ValidationError(f"{control_id}: evidence must be a repository path") + records.append( + { + "control_id": control_id, + "description": requirement.get("description", ""), + **props, + } + ) + if not records: + raise ValidationError("no implemented requirements") + return sorted(records, key=lambda item: item["control_id"]) + + +def validate_sources(document: Any) -> None: + if not isinstance(document, dict) or document.get("schema_version") != 1: + raise ValidationError("source register schema_version must be 1") + sources = document.get("sources") + if not isinstance(sources, list) or not sources: + raise ValidationError("source register must contain sources") + required = { + "id", "publisher", "title", "release", "publication_date", + "retrieved", "url", "sha256", "status", "redistribution", + } + seen: set[str] = set() + for source in sources: + missing = sorted(required - set(source)) + if missing: + raise ValidationError(f"source missing {', '.join(missing)}") + if source["id"] in seen: + raise ValidationError(f"duplicate source id: {source['id']}") + seen.add(source["id"]) + if not re.fullmatch(r"[0-9a-f]{64}", source["sha256"]): + raise ValidationError(f"{source['id']}: invalid sha256") + if source["status"] not in {"current", "superseded", "sunset", "reference"}: + raise ValidationError(f"{source['id']}: invalid status") + + +def validate_exceptions(document: Any) -> None: + if not isinstance(document, dict) or document.get("schema_version") != 1: + raise ValidationError("exception register schema_version must be 1") + entries = document.get("exceptions") + if not isinstance(entries, list): + raise ValidationError("exceptions must be a list") + for index, entry in enumerate(entries): + missing = [field for field in EXCEPTION_FIELDS if not entry.get(field)] + if missing: + raise ValidationError(f"exception {index}: missing {', '.join(missing)}") + if not re.fullmatch(r"sha256:[0-9a-f]{64}", entry["image_digest"]): + raise ValidationError(f"exception {index}: digest must be immutable") + if entry["architecture"] not in {"amd64", "arm64"}: + raise ValidationError(f"exception {index}: invalid architecture") + if entry["expires"] == "never": + raise ValidationError(f"exception {index}: permanent exceptions are forbidden") + + +def validate_schema(document: Any, schema_path: Path | None) -> None: + if schema_path is None: + return + digest = hashlib.sha256(schema_path.read_bytes()).hexdigest() + expected = "95e76881151ececd5cb1a93ff0f70ad74b8cc1aa58771626ac8b262bf2c8e001" + if digest != expected: + raise ValidationError(f"OSCAL schema digest mismatch: {digest}") + try: + import jsonschema + import regex + except ImportError as exc: + raise ValidationError("jsonschema and regex are required for --oscal-schema") from exc + + def unicode_pattern(validator: Any, pattern: str, instance: Any, schema: Any): + del validator, schema + if isinstance(instance, str) and regex.search(pattern, instance) is None: + yield jsonschema.ValidationError( + f"{instance!r} does not match OSCAL pattern {pattern!r}" + ) + + oscal_validator = jsonschema.validators.extend( + jsonschema.Draft7Validator, {"pattern": unicode_pattern} + ) + oscal_validator(load_json(schema_path)).validate(document) + + +def render_csv(records: list[dict[str, str]]) -> str: + fields = ("control_id", "description", *REQUIRED_PROPS) + stream = io.StringIO(newline="") + writer = csv.DictWriter(stream, fieldnames=fields, lineterminator="\n") + writer.writeheader() + writer.writerows({field: record[field] for field in fields} for record in records) + return stream.getvalue() + + +def render_markdown(records: list[dict[str, str]]) -> str: + lines = [ + "# Control implementation view", + "", + "Generated from `compliance/oscal/component-definition.json`; do not edit.", + "This is component support information, not an SCTM, SSP, authorization,", + "assessment result, STIG certification, or compliance determination.", + "", + ] + for record in records: + lines.extend( + [ + f"## {record['control_id']}: {record['classification']}", + "", + record["description"], + "", + f"- Source requirement: {record['source-requirement']}", + f"- Rationale: {record['rationale']}", + f"- Assessment: {record['assessment-method']}", + f"- Default/configurable: {record['default-state']} / {record['configurable-state']}", + f"- Prerequisites/restart: {record['prerequisites']} / {record['restart-behavior']}", + f"- Operational impact: {record['operational-impact']}", + f"- Loss of protection: {record['loss-of-protection']}", + f"- Limitations: {record['limitations']}", + f"- Residual risk: {record['residual-risk']}", + f"- Evidence: `{record['evidence']}`", + f"- Owner/review: {record['owner']} / {record['review-status']}", + "", + ] + ) + return "\n".join(lines) + + +def write_or_check(path: Path, expected: str, check: bool) -> None: + if check: + actual = path.read_text(encoding="utf-8") if path.exists() else "" + if actual != expected: + raise ValidationError(f"generated file is stale: {path.relative_to(ROOT)}") + return + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(expected, encoding="utf-8", newline="") + + +def run(check: bool, schema_path: Path | None) -> None: + oscal = load_json(OSCAL) + records = control_records(oscal) + validate_sources(load_json(SOURCES)) + validate_exceptions(load_json(EXCEPTIONS)) + validate_schema(oscal, schema_path) + write_or_check(CSV_VIEW, render_csv(records), check) + write_or_check(MD_VIEW, render_markdown(records), check) + + +def main() -> int: + parser = argparse.ArgumentParser() + parser.add_argument("--check", action="store_true") + parser.add_argument("--oscal-schema", type=Path) + args = parser.parse_args() + try: + run(args.check, args.oscal_schema) + except (ValidationError, OSError) as exc: + print(f"error: {exc}", file=sys.stderr) + return 1 + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tests/test_cybersecurity.py b/tests/test_cybersecurity.py new file mode 100644 index 0000000..febd889 --- /dev/null +++ b/tests/test_cybersecurity.py @@ -0,0 +1,46 @@ +import importlib.util +import tempfile +import unittest +from pathlib import Path + + +SCRIPT = Path(__file__).parents[1] / "scripts" / "cybersecurity.py" +SPEC = importlib.util.spec_from_file_location("cybersecurity", SCRIPT) +assert SPEC and SPEC.loader +CYBER = importlib.util.module_from_spec(SPEC) +SPEC.loader.exec_module(CYBER) + + +class CybersecurityTests(unittest.TestCase): + def test_committed_artifacts_are_valid_and_current(self): + CYBER.run(check=True, schema_path=None) + + def test_duplicate_control_is_rejected(self): + document = CYBER.load_json(CYBER.OSCAL) + requirements = document["component-definition"]["components"][0][ + "control-implementations" + ][0]["implemented-requirements"] + requirements.append(requirements[0]) + with self.assertRaisesRegex(CYBER.ValidationError, "more than once"): + CYBER.control_records(document) + + def test_permanent_exception_is_rejected(self): + entry = {field: "value" for field in CYBER.EXCEPTION_FIELDS} + entry.update( + image_digest="sha256:" + "a" * 64, + architecture="amd64", + expires="never", + ) + with self.assertRaisesRegex(CYBER.ValidationError, "permanent"): + CYBER.validate_exceptions({"schema_version": 1, "exceptions": [entry]}) + + def test_schema_digest_is_enforced_before_parser(self): + with tempfile.TemporaryDirectory() as directory: + schema = Path(directory) / "schema.json" + schema.write_text("{}", encoding="utf-8") + with self.assertRaisesRegex(CYBER.ValidationError, "digest mismatch"): + CYBER.validate_schema({}, schema) + + +if __name__ == "__main__": + unittest.main()