Skip to content
Draft
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
16 changes: 12 additions & 4 deletions docs/adr/model/0020-hardening-is-one-platform-posture.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,11 @@ listed in [chapter 10](../../../spec/v1/10-project-intent.md#hardening).

## Rests on

Kubernetes applies the four controls (non-root, an immutable root filesystem, no
capabilities, the default syscall filter) per pod and per container
Kubernetes applies the five controls (non-root, an immutable root filesystem, no
capabilities, no privilege escalation, the default syscall filter) per pod and
per container
([0002](0002-kubernetes-is-the-substrate-for-one-applier.md)), so the render can
state all four on every container with no author involved. Ephemeral disk is
state all five on every container with no author involved. Ephemeral disk is
contended, so its size is platform-assigned
([0004](0004-contention-decides-authority.md)). The lock is a pinned input
([0006](0006-pinned-inputs.md)), so a user read from it is a derivation, not a
Expand All @@ -42,7 +43,7 @@ registry read at render time.
exception to render, a Process's writable set cannot be enumerated ahead of
time, or an image the estate depends on has no numeric user and cannot be
replaced. **Settled by:** the committed trees under
`spec/v1/examples/{auth,data,knowledge,minimal}/rendered/` carrying the four
`spec/v1/examples/{auth,data,knowledge,minimal}/rendered/` carrying the five
controls on every container, the `postgres-exporter` sidecar included, with no
project file carrying a hardening field (a test asserting both is to be
written, owned by joris); and `platform-postgres` running `initdb` on a freshly
Expand Down Expand Up @@ -71,6 +72,13 @@ A fresh `local-path` directory is root-owned, so a non-root pod with no
to resolve a digest, so it reads the user too. An init container that chowns the
volume would need root on every stateful Process.

**Privilege escalation is closed, not implied.** Non-root with every capability
dropped still lets a setuid binary in the image raise its process to uid 0;
`allowPrivilegeEscalation: false` sets `no_new_privs` and closes that. It is
also one of the controls Kubernetes' own `restricted` Pod Security Standard
requires, so a posture under that name without it would be refused by a
namespace enforcing the standard.

**A low port is refused.** `auth-ui` declared port 80 under `restricted` and
rendered a pod that could not bind it. Deriving `NET_BIND_SERVICE` would re-add a
dropped capability silently. A route names a surface, not a number
Expand Down
3 changes: 2 additions & 1 deletion spec/v1/10-project-intent.md
Original file line number Diff line number Diff line change
Expand Up @@ -1149,14 +1149,15 @@ The posture itself is **not authored per Process**. It is one estate-wide value,
it thirty times would be restating the only value there is, and a field with one
legal value carries no information ([0022](../../docs/adr/model/0022-a-derived-value-has-one-declaring-site.md)
deleted `minAvailable` for the same reason). What a Process authors is the
paths it must write, and nothing else. `restricted` is four controls, applied
paths it must write, and nothing else. `restricted` is five controls, applied
together:

| control | rendered as |
|---|---|
| non-root | `runAsNonRoot: true`, with the numeric UID the images lock resolved ([0020](../../docs/adr/model/0020-hardening-is-one-platform-posture.md)) |
| immutable root filesystem | `readOnlyRootFilesystem: true` |
| no capabilities | `capabilities.drop: [ALL]` |
| no privilege escalation | `allowPrivilegeEscalation: false` |
| default syscall filter | `seccompProfile.type: RuntimeDefault` |

### Writable paths are declared, not exempted
Expand Down
4 changes: 2 additions & 2 deletions spec/v1/20-resolved-deployment.md
Original file line number Diff line number Diff line change
Expand Up @@ -657,8 +657,8 @@ Five rules carry most of the weight:
with different volumes, which is the property that makes a restore rehearsal
meaningful ([0018](../../docs/adr/model/0018-durability-class-derives-a-backup.md)).
- **Hardening is one platform posture, and nothing else.** `restricted` (
`runAsNonRoot`, `readOnlyRootFilesystem`, all capabilities dropped, seccomp
`RuntimeDefault`) is declared once in the Platform document and authored by no
`runAsNonRoot`, `readOnlyRootFilesystem`, all capabilities dropped, no
privilege escalation, seccomp `RuntimeDefault`) is declared once in the Platform document and authored by no
Process. There is no per-control relaxation and no exception vocabulary: a
Process declares the paths it must write, and an image that cannot meet the
class is `E_HARDENING_UNMET`
Expand Down
2 changes: 1 addition & 1 deletion spec/v1/30-deliverables.md
Original file line number Diff line number Diff line change
Expand Up @@ -330,7 +330,7 @@ spells only what the projection holds:
| the `secretStore` endpoint | `vso` | a `VaultConnection` named `secret-store` in the project's namespace, at that address, `skipTLSVerify: false` |
| a grant delivered `env` or `file` | `vso` | a `VaultAuth` per holding identity, its `ServiceAccount` that identity and its role `<namespace>-<identity>`, over the `secret-store` connection; a `VaultStaticSecret` named for the `destination`, `kv-v2` on the `secret` mount at the path below `secret/data/`, `refreshAfter: 1h`, its destination created and `flagger.app/config-tracking: disabled`, each restart target a `Deployment`, a `blue-green` Process's `-primary` |
| a `read` grant | `vault-policy` | `read` on its path and on the same document's `secret/metadata/` path |
| `hardening: restricted` | `kubernetes` | `runAsNonRoot`, the images lock's `uid` and `gid`, seccomp `RuntimeDefault`, a read-only root filesystem, every capability dropped |
| `hardening: restricted` | `kubernetes` | `runAsNonRoot`, the images lock's `uid` and `gid`, seccomp `RuntimeDefault`, a read-only root filesystem, every capability dropped, `allowPrivilegeEscalation: false` |
| a probe's `period`, `timeout`, `failures` | `kubernetes` | `periodSeconds`, `timeoutSeconds`, `failureThreshold`; `initialDelaySeconds: 0` on readiness and liveness only |
| `ingress`, `egress`, an edge's `peers` | `networking` | one rule per peer, from or to its namespace (by `kubernetes.io/metadata.name`) and, where the peer is a Process, its `instance`, on TCP; the `cluster-dns` peer on UDP and TCP both |
| a held `api`'s `server` | `networking` | one egress rule per range in the Process's own policy, after its peers, to an `ipBlock` of its `cidr` on its port over TCP |
Expand Down
Loading