Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -557,6 +557,14 @@ jobs:
needs: [vet]
steps:
- uses: actions/checkout@v4
# Validate the release config (dockers, docker_signs, manifests,
# signs, nfpms, homebrew_casks) parses before a tag ever relies on
# it — a malformed .goreleaser.yaml otherwise aborts the release.
- name: goreleaser check
uses: goreleaser/goreleaser-action@v6
with:
version: latest
args: check
- name: shellcheck packaging scripts
run: |
sudo apt-get update -qq
Expand Down
25 changes: 15 additions & 10 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,12 @@ jobs:
uses: sigstore/cosign-installer@v3
- name: install syft (SBOM)
uses: anchore/sbom-action/download-syft@v0
- name: set up QEMU (multi-arch image builds)
if: vars.PUBLISH_CONTAINERS == 'true'
uses: docker/setup-qemu-action@v3
- name: set up Docker Buildx
if: vars.PUBLISH_CONTAINERS == 'true'
uses: docker/setup-buildx-action@v3
- name: docker login (GHCR)
uses: docker/login-action@v3
with:
Expand All @@ -51,16 +57,15 @@ jobs:
uses: goreleaser/goreleaser-action@v6
with:
version: latest
# --skip=docker: GHCR package-write is not yet enabled on the
# org, so the image+manifest push fails with
# `denied: permission_denied: write_package` — and that
# failure aborts goreleaser's whole publish phase, taking the
# binaries/.deb/.rpm/GitHub Release down with it. Skipping
# docker lets the rest of the release ship; drop this flag
# once the org grants Actions package-write and re-tag to
# publish the image. (--skip=docker covers docker_manifests
# too; there is no separate manifest skip key.)
args: release --clean --skip=docker
# Container images publish to GHCR only when the repo/org
# variable PUBLISH_CONTAINERS == 'true'. Until the org enables
# Actions package-write on ghcr.io (otherwise the push fails
# `denied: permission_denied: write_package` and aborts the
# whole release), --skip=docker is passed so the binaries /
# .deb / .rpm / Homebrew / GitHub Release still ship. Flip the
# variable to 'true' once GHCR is enabled, then re-tag to
# publish images. (--skip=docker covers docker_manifests too.)
args: release --clean ${{ vars.PUBLISH_CONTAINERS == 'true' && '' || '--skip=docker' }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# Separate fine-grained PAT (contents:write on the homebrew-tap
Expand Down
13 changes: 13 additions & 0 deletions .goreleaser.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,19 @@ docker_manifests:
- "ghcr.io/cybertec-postgresql/pg_hardstorage:latest-amd64"
- "ghcr.io/cybertec-postgresql/pg_hardstorage:latest-arm64"

# Keyless cosign signing of the published images + manifests (Sigstore +
# GitHub Actions OIDC), mirroring the blob `signs:` stanza. Only runs in
# the docker phase, i.e. when PUBLISH_CONTAINERS enables image publishing.
docker_signs:
- id: cosign-images
cmd: cosign
artifacts: all
args:
- "sign"
- "--yes"
- "${artifact}@${digest}"
output: true

release:
github:
owner: cybertec-postgresql
Expand Down
29 changes: 29 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,35 @@ on-disk and on-the-wire schema (backup manifests, configuration, output JSON,
and the on-disk chunk envelope): an agent built against a given schema version
keeps reading that version for at least 24 months after a successor lands.

## [1.0.3] — 2026-06-24

### Documentation: correctness sweep + cloud-support accuracy

Audited the documentation against the codebase and corrected false or
stale claims. The big one: pg_hardstorage backs up self-managed
PostgreSQL over the physical replication protocol (`BASE_BACKUP` + a
physical slot); fully-managed DBaaS — Amazon RDS, Aurora, Cloud SQL,
Azure Database, Neon, Supabase — do **not** expose `BASE_BACKUP` and are
out of scope. Every "works on managed PG" claim was removed (web-verified
against each vendor's replication docs). Also fixed: feature counts (six
storage backends, one LLM provider), PG-version support (15–18; 15/16/17
CI-required, 18 allow-failure), nonexistent CLI flags in tutorials / ops
guides, broken in-repo file paths, stale version strings, and the
AES-256-GCM-SIV-vs-GCM and cosign-vs-Ed25519 descriptions. CNPG-I, Rekor
anchoring, skill signing, and the FIPS image are now clearly marked
roadmap. Download / verify examples use a `VERSION` variable so they no
longer go stale.

### Packaging: wire container-image publishing (GHCR)

The release pipeline can now build and publish multi-arch (amd64/arm64)
distroless images to GHCR with keyless cosign image signatures. Publishing
is gated on the `PUBLISH_CONTAINERS` repo variable — set it once the org
enables Actions package-write on `ghcr.io`; until then the release ships
binaries / `.deb` / `.rpm` / Homebrew as before. Image-level SLSA
provenance remains roadmap. A `goreleaser check` step now validates the
release config in CI.

## [Unreleased]

### Packaging: remove the obsolete homebrew-formula.json manifest
Expand Down
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ contributions, but the bar is "this would land in the project anyway"

## Before you start

1. **Read `docs/SPEC.md`.** It's the single source of truth for what
1. **Read `SPEC.md`** (at the repo root). It's the single source of truth for what
the system is and isn't supposed to do. Most of what looks like a
missing feature is on a later milestone (v0.5 or v1.0); arguing for
an earlier landing is a great use of an issue.
Expand Down
2 changes: 1 addition & 1 deletion SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,4 @@ The systemd units we ship apply the standard hardening set
(`NoNewPrivileges=yes`, `ProtectSystem=strict`, `PrivateTmp=yes`,
`SystemCallFilter=@system-service`, etc.). If you're running outside
systemd, the equivalent posture is documented in
[docs/operator-guide.md](docs/operator-guide.md).
[docs/operations/operator-guide.md](docs/operations/operator-guide.md).
8 changes: 4 additions & 4 deletions docs/compliance/fedramp.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ JSON is `fedramp`.
| CP-9 | System backup | Full backup pipeline | `pg_hardstorage backup ...` | `backup.create` |
| CP-9(1) | Testing for reliability and integrity | Verifier subsystem + post-restore `pg_verifybackup` | `pg_hardstorage verify ...` | `verify.run` |
| CP-9(3) | Separate storage for critical information | Cross-region replication | `pg_hardstorage repo replicate ...` | `replicate.completed` |
| CP-9(8) | Cryptographic protection | AES-256-GCM-SIV per chunk; Ed25519 manifest signatures | (automatic) | `backup.create` |
| CP-9(8) | Cryptographic protection | AES-256-GCM per chunk; Ed25519 manifest signatures | (automatic) | `backup.create` |
| CP-10 | System recovery and reconstitution | Restore command + sandbox `pg_verifybackup` gate | `pg_hardstorage restore ...` | `restore.completed` |

---
Expand All @@ -85,9 +85,9 @@ JSON is `fedramp`.
| SC-7 | Boundary protection | Air-gap mode rejects public endpoints; storage plugin per-region scoping | `pg_hardstorage residency set ...` | (config) |
| SC-8 | Transmission confidentiality | TLS 1.2+ on all storage backends; mTLS control plane | (config) | — |
| SC-12 | Cryptographic key establishment and management | KMS-backed RKEK; per-tenant KEK | `pg_hardstorage kms ...` | `kms.*` |
| SC-13 | Cryptographic protection | AES-256-GCM-SIV (default); AES-256-GCM (FIPS variant) | (automatic) | `backup.create` |
| SC-13 | Cryptographic protection | AES-256-GCM (shipping today); AES-256-GCM-SIV planned, also for FIPS variant when GCM-SIV lands | (automatic) | `backup.create` |
| SC-28 | Protection of information at rest | Three-layer envelope encryption | (automatic) | `backup.create` |
| SC-28(1) | Cryptographic protection | AES-256-GCM-SIV per chunk; Ed25519 manifest sigs | (automatic) | `backup.create` |
| SC-28(1) | Cryptographic protection | AES-256-GCM per chunk; Ed25519 manifest sigs | (automatic) | `backup.create` |

---

Expand All @@ -97,7 +97,7 @@ JSON is `fedramp`.
| --- | --- | --- | --- | --- |
| SI-3 | Malicious code protection | Tier-1 plugin model + cosign-signed releases | (build) | — |
| SI-4 | System monitoring | Prometheus metrics; OpenTelemetry traces; insider-threat scanner | `pg_hardstorage insider scan` | `insider.scan` |
| SI-7 | Software, firmware, and information integrity | SLSA L3 build provenance + reproducible builds | (build-time) | (cosign attest) |
| SI-7 | Software, firmware, and information integrity | SLSA L3 build provenance + reproducible builds | (build-time) | (SLSA provenance via slsa-github-generator + cosign sign-blob) |
| SI-7(1) | Integrity checks | Continuous-attestation `integrity` runs over the repo | `pg_hardstorage integrity run` | `integrity.run` |
| SI-7(7) | Integration of detection and response | `doctor` emits structured remediation commands for operator action | `pg_hardstorage doctor` | `doctor.suggested_fix` |

Expand Down
9 changes: 5 additions & 4 deletions docs/compliance/gdpr-art-17-crypto-shred.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,10 @@ Every encrypted chunk is sealed under a layered key system:
wrapped under the RKEK. Stored in
`manifest.json.encryption.wrapped_dek`.
3. **Per-chunk key** derived `Kc = HKDF-SHA256(BDEK,
info=chunk_hash)`. Cipher: AES-256-GCM-SIV by default
(RFC 8452, nonce-misuse resistant); AES-256-GCM with
random 96-bit nonce in FIPS mode.
info=chunk_hash)`. Cipher: AES-256-GCM with a random
96-bit nonce is shipping today; AES-256-GCM-SIV (RFC 8452,
nonce-misuse resistant) is the planned default once a
validated implementation lands.

A tenant's KEK wraps every BDEK for every backup of every
deployment under that tenant. Destroy that one key and
Expand Down Expand Up @@ -182,7 +183,7 @@ pg_hardstorage audit append kms.shred \
| Art. 17(1) — right to erasure | `kms shred` per-tenant KEK destruction | `kms.shred` |
| Art. 17(2) — communication of erasure to recipients | Cross-region replicas hold the same wrapped DEKs; KEK destruction propagates implicitly. Replica audit chain records `kms.shred`. | `kms.shred` (replicated) |
| Art. 30 — record of processing activities | Hash-chained audit log; `audit verify-chain` proves untampered. | `audit.*` |
| Art. 32(1)(a) — encryption of personal data | AES-256-GCM-SIV per chunk (FIPS-validated GCM in FIPS build). | `backup.create` (records `encryption.scheme`) |
| Art. 32(1)(a) — encryption of personal data | AES-256-GCM per chunk (AES-256-GCM-SIV planned; FIPS-validated GCM in FIPS build). | `backup.create` (records `encryption.scheme`) |
| Art. 32(1)(b) — confidentiality, integrity | Ed25519-signed manifests, Merkle audit chain. | `backup.create`, `audit.*` |

---
Expand Down
6 changes: 3 additions & 3 deletions docs/compliance/hipaa.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,14 +41,14 @@ JSON is `hipaa`.
| §164.312(a)(2)(i) | Unique user identification | RBAC actor identity recorded on every event | (automatic) | (every event records `actor`) |
| §164.312(a)(2)(ii) | Emergency access procedure | JIT tokens + n-of-m approval | `pg_hardstorage jit issue ...`, `approval request ...` | `jit.issue`, `approval.request` |
| §164.312(a)(2)(iii) | Automatic logoff | JIT tokens auto-expire (max 24h) | (automatic) | `jit.issue` |
| §164.312(a)(2)(iv) | Encryption and decryption (Addressable) | AES-256-GCM-SIV per chunk; FIPS variant uses AES-256-GCM | (automatic) | `backup.create` records `encryption.scheme` |
| §164.312(a)(2)(iv) | Encryption and decryption (Addressable) | AES-256-GCM per chunk (AES-256-GCM-SIV planned) | (automatic) | `backup.create` records `encryption.scheme` |
| §164.312(b) | Audit controls | Hash-chained Merkle audit log | `pg_hardstorage audit verify-chain` | (every event) |
| §164.312(c)(1) | Integrity | Ed25519-signed manifests + chunk SHA round-trip | `pg_hardstorage verify ...` | `verify.run` |
| §164.312(c)(2) | Mechanism to authenticate ePHI | Manifest signatures + audit chain hashing | `pg_hardstorage audit verify-chain` | `verify.manifest_signature` (on fail) |
| §164.312(d) | Person or entity authentication | mTLS for control plane ↔ agent; Ed25519 keypair per operator | (config) | (auth failures captured) |
| §164.312(e)(1) | Transmission security | TLS 1.2+ for storage backends; air-gap mode for offline networks | (config) | — |
| §164.312(e)(2)(i) | Integrity controls | End-to-end checksums; chunk SHA round-trip | (automatic) | `verify.scrub_mismatch` (on fail) |
| §164.312(e)(2)(ii) | Encryption (Addressable, transmission) | TLS 1.2+ on every storage backend; AES-256-GCM-SIV on the chunk itself | (config) | — |
| §164.312(e)(2)(ii) | Encryption (Addressable, transmission) | TLS 1.2+ on every storage backend; AES-256-GCM on the chunk itself | (config) | — |

---

Expand All @@ -60,7 +60,7 @@ when:

| Layer | Requirement | `pg_hardstorage` posture |
| --- | --- | --- |
| At rest | NIST-validated cipher | AES-256-GCM-SIV (RFC 8452) by default; AES-256-GCM (FIPS 140-validated via BoringCrypto) in `pg-hardstorage-fips` build |
| At rest | NIST-validated cipher | AES-256-GCM (random 96-bit nonce) shipping today; AES-256-GCM-SIV (RFC 8452) is the planned default once a validated implementation lands. The `pg-hardstorage-fips` build uses AES-256-GCM (FIPS 140-validated via BoringCrypto) — BoringCrypto does not ship GCM-SIV, so the FIPS variant will likewise use GCM when GCM-SIV lands |
| In transit (storage backend) | TLS 1.2+ | All cloud backends; on-prem via plugin config |
| In transit (agent ↔ control plane) | TLS / mTLS | mTLS by default |
| Key management | NIST SP 800-57 lifecycle | KMS-backed RKEK; per-tenant KEK; KEK rotation with documented audit trail |
Expand Down
4 changes: 2 additions & 2 deletions docs/compliance/iso-27001-control-mapping.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ constrains the geography.
| A.8.16 | Monitoring activities | Prometheus metrics; OpenTelemetry traces; insider-threat scan | `pg_hardstorage insider scan` | `insider.scan` |
| A.8.17 | Clock synchronisation | UTC timestamps everywhere; NTP recommended in operator guide | (operator) | (timestamp on every event) |
| A.8.21 | Security of network services | TLS 1.2+ for storage backends; mTLS for control plane ↔ agent | (config) | (network failures captured) |
| A.8.24 | Use of cryptography | AES-256-GCM-SIV + Ed25519 + SHA-256 + HKDF-SHA256 | (automatic) | `backup.create` records `encryption.scheme` |
| A.8.24 | Use of cryptography | AES-256-GCM + Ed25519 + SHA-256 + HKDF-SHA256 (AES-256-GCM-SIV planned) | (automatic) | `backup.create` records `encryption.scheme` |
| A.8.25 | Secure development life cycle | SLSA L3 build provenance; reproducible builds verified weekly | (build-time) | (cosign attest) |
| A.8.26 | Application security requirements | Static analysis + race detector + sanitizers in CI | (build-time) | — |
| A.8.27 | Secure system architecture and engineering principles | Documented threat model + design north stars in `SPEC.md` | — | — |
Expand Down Expand Up @@ -107,7 +107,7 @@ information-asset register entry should record:
| Asset | `pg_hardstorage` backup repository at `s3://...` |
| Owner | (operator org) |
| Classification | Per-deployment via `classify` |
| Encryption | AES-256-GCM-SIV per chunk (FIPS variant: AES-256-GCM) |
| Encryption | AES-256-GCM per chunk (AES-256-GCM-SIV planned) |
| Retention | Per `retention` policy in deployment config |
| Audit | Hash-chained Merkle log; quarterly bundle export |
| Region | Per `residency` policy |
Expand Down
10 changes: 6 additions & 4 deletions docs/compliance/pci-dss.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ report.

| Requirement | Description | Product feature | Command | Audit event |
| --- | --- | --- | --- | --- |
| 3.5.1 | PAN rendered unreadable | AES-256-GCM-SIV on every chunk; per-chunk key derivation | (automatic) | `backup.create` records `encryption.scheme` |
| 3.5.1.1 | Strong cryptography | AES-256-GCM-SIV (RFC 8452) by default; AES-256-GCM (FIPS) in FIPS variant | (automatic) | `backup.create` |
| 3.5.1 | PAN rendered unreadable | AES-256-GCM on every chunk; per-chunk key derivation | (automatic) | `backup.create` records `encryption.scheme` |
| 3.5.1.1 | Strong cryptography | AES-256-GCM (random 96-bit nonce) shipping today; AES-256-GCM-SIV (RFC 8452) planned | (automatic) | `backup.create` |
| 3.5.1.2 | Disk-level encryption alone is insufficient | Per-chunk encryption is in addition to any underlying disk encryption | (automatic) | `backup.create` |
| 3.6.1.1 | Cryptographic key custody | KMS-backed RKEK; per-tenant KEK; on-disk keyring with mode 0600 | `pg_hardstorage kms inspect` | `kms.*` |
| 3.6.1.2 | Cryptographic keys protected | Keys never appear in logs or output; `kms inspect` is read-only and shows only fingerprints | `pg_hardstorage kms inspect` | (read-only) |
Expand Down Expand Up @@ -143,13 +143,15 @@ audit period plus the cosign signatures on the running
binary:

```sh
VERSION=1.0.3 # the release / image tag you're attesting

# 1. Audit chain bundle
pg_hardstorage audit export-bundle \
--repo s3://acme-pci-backups/ \
--since 2026-01-01T00:00:00Z \
--until 2026-04-01T00:00:00Z \
--include-anchors \
-o ./qsa-q1-2026-audit.tar.gz
--out ./qsa-q1-2026-audit.tar.gz

# 2. Compliance report (Markdown for the QSA)
pg_hardstorage compliance report \
Expand All @@ -161,7 +163,7 @@ pg_hardstorage compliance report \
# 3. Build provenance attestation
cosign verify-attestation \
--type slsaprovenance \
ghcr.io/cybertec-postgresql/pg_hardstorage:v0.9.2 \
"ghcr.io/cybertec-postgresql/pg_hardstorage:v${VERSION}" \
--certificate-identity-regexp \
"https://github.com/cybertec-postgresql/pg_hardstorage/.*" \
--certificate-oidc-issuer \
Expand Down
Loading
Loading