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
7 changes: 7 additions & 0 deletions changelog.d/3108.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
- **`erase submit --plaintext-matcher` for data written before an IPCrypt
rule.** The repeatable `--plaintext-matcher KEY=VALUE` submits its value
exactly as written, with no encryption and no epoch fan-out, for data
stored before the key's rule existed. The command warns on stderr that the
erasure log stores those values in the clear, refuses a key with no rule
for `--signal`, and refuses a command that also carries a matcher the
policy would encrypt, since no record could match both.
49 changes: 42 additions & 7 deletions docs/deletion-and-gc.md
Original file line number Diff line number Diff line change
Expand Up @@ -767,10 +767,44 @@ encrypts for the caller (ADR-2993 decision 8):
requests each carry an id derived from that base id and the request's
matcher keys and epochs, so rerunning with the same `--request-id`
re-submits the same requests and finds them already present. A failure
part-way names the base id to rerun with.
part-way names the base id to rerun with, except when a request conflicts
with a different request already stored under its derived id: a rerun
derives the same id and conflicts again, so that error carries no rerun
hint.
- With several requests the output lists the base id, then each derived id
with its epochs, then `<n> erasure requests: <c> created, <p> already
present`.
with its epochs (a metric matcher whose key differs from its rule's key
shows `<key> (rule <rule key>) epoch <n>`) and, under it, the
`ravel-cli <flags> erase status --tenant <tenant> --signal <signal>
--request-id <derived id>` command for that request, then `<n> erasure
requests: <c> created, <p> already present`. Both the store flags and the
tenancy flags are top-level and go between `ravel-cli` and `erase`; after
`erase` they do not parse. `<flags>` holds the ones the submit ran with:
`--store`, under `--store s3` each S3 flag set away from its default
other than the inline credentials (`--s3-credentials-file` by its path),
and `--tenant-hash-key-file` by its path or `--tenant-hash-unkeyed`.
S3 access keys, secret keys and session tokens are never printed; they
are left to their `RAVEL_S3_*` environment variables, and one given as a
flag with its variable unset prints `<your S3 credential flags>` where
those flags go. Nothing is stored under the base id, so `erase status`
takes the derived ids; given the base id it reports `UNKNOWN`.

`--plaintext-matcher KEY=VALUE` (repeatable) is for data written before the
key's rule existed, which holds the raw value:

- The value is submitted exactly as written: no encryption and no epoch
fan-out of its own. It is added to every request the command submits.
- The erasure log stores the value in the clear. Before submitting, the
command prints a warning to stderr naming the keys concerned.
- The key must have a rule for `--signal` in `--ipcrypt-config`, looked up
by its sanitized form on the metric signals. A key with no rule is
refused, since an ordinary `--matcher` already submits its value as
written.
- A command that combines a plaintext matcher with an ordinary matcher the
policy would encrypt is refused before anything is written. Matchers in
one request are a conjunction: data written before the rules holds raw
values for both keys, and data written after holds ciphertext for both,
so no record could match. To erase a subject across both periods, submit
one command with `--plaintext-matcher` and one with `--matcher`.

Every submit, with or without `--ipcrypt-config`, reads the
`sys/ipcrypt/policy` marker ([ingest.md](ingest.md#startup-marker)) after
Expand All @@ -784,10 +818,11 @@ marker (ADR-2993, erasure amendment):
- No marker and a policy with rules means the bucket's data was written
without the policy, so an encrypted matcher would match nothing. A request
with at least one matcher the policy would encrypt is refused, naming
those matchers, and nothing is written; submit it without
`--ipcrypt-config` so its matchers stay plaintext. A request with no
encrypted matcher (every key has no rule for `--signal`, or every ruled
value is not an address) is submitted as written.
those matchers, and nothing is written; pass those values with
`--plaintext-matcher` instead of `--matcher`. A request with no encrypted
matcher (every matcher is a plaintext matcher, its key has no rule for
`--signal`, or its ruled value is not an address) is submitted as written,
and no marker is written.

### The stages in detail

Expand Down
12 changes: 10 additions & 2 deletions docs/guides/ingest.md
Original file line number Diff line number Diff line change
Expand Up @@ -1218,8 +1218,16 @@ the address:

- metric labels are matched by their sanitized name, so a rule written as
`client.address` covers the `client_address` label;
- log and span attributes are matched by their raw key, on the resource, on
the record or span, and in a loaded attributes map.
- log and span attributes are matched by their raw key, on the resource and
on the record or span. Only spans load an attributes map
(`attrs_map_column`), and its entries are matched the same way. The logs
`attrs_map_column` is export only, so load never reads a logs map.

IPCrypt encrypts string values only. A logs or spans mapping column
declared `type = "bytes"` under a key that has a rule is stored
unencrypted: logs store the bytes as written, and spans store their
lowercase hex, which never parses as an address. The load prints a warning
naming the column and the key before any data is written.

A policy with rules needs the deployment key the tenant keys derive from.
Pass it as the top-level `--tenant-hash-key-file`, before the subcommand:
Expand Down
15 changes: 15 additions & 0 deletions docs/guides/operations/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -1263,6 +1263,21 @@ through `--tenant-token`, `--tenant-token-file` or `--oidc-ddl-claim`, and
`ipcrypt` only through `--tenant-token`, `--tenant-token-file` or
`--oidc-ipcrypt-claim`. Flight SQL grants neither.

Withholding `ipcrypt` does not stop a caller encrypting guesses if it can
also write: write access to a tenant is an encryption oracle, because a token
that can ingest can write chosen addresses under a ruled key and read the
ciphertext back with a query. A deployment that needs readers unable to
encrypt guesses must keep readers' tokens off ingest. Tokens carry no ingest
or query scope, so the line is drawn by which processes accept a token. Of
the bearer-token resolvers, only flag-configured tokens (`--tenant-token` or
`--tenant-token-file`) split by process mode: put readers' tokens on
`--mode query` processes only and exporters' tokens on `--mode gateway`
processes only. Durable `sys/auth` tokens cannot: on a keyed bucket, every
`--mode all`, `--mode gateway` and `--mode query` process reads the map, so
a token in it resolves for ingest and query alike. OIDC can only with a
separate `--oidc-audience` per process role, since an OIDC identity resolves
on every process configured with its issuer and audience.

### Production authentication

Two additive resolvers join the same first-success chain. They do not disable
Expand Down
3 changes: 2 additions & 1 deletion docs/reference/ravel-cli-flags.md
Original file line number Diff line number Diff line change
Expand Up @@ -368,7 +368,8 @@ Submit an immutable erasure request: a conjunction of exact-match label/attribut
| Flag | Environment variable | Default | Help |
| --- | --- | --- | --- |
| `--ipcrypt-config` | | | IPCrypt policy TOML (ADR-2993), the file ingest ran with. A matcher whose key has a rule for `--signal` (metric keys matched by their sanitized form) carries the ciphertext instead of the address, one request per rule epoch (the current one and each retired one); several encrypted matchers submit one request per combination of epochs, at most 16. The matcher key is submitted as written. Every submit, with or without this flag, first compares the policy's fingerprint with the bucket's `sys/ipcrypt/policy` marker and refuses on a mismatch or when the marker cannot be read |
| `--matcher` | | | Exact-match predicate matcher `key=value`, repeatable; the request matches a record only when every matcher holds (logical AND). At least one is required |
| `--matcher` | | | Exact-match predicate matcher `key=value`, repeatable; the request matches a record only when every matcher holds (logical AND). At least one `--matcher` or `--plaintext-matcher` is required |
| `--plaintext-matcher` | | | Matcher `key=value` for data written before the key's IPCrypt rule, repeatable: the value is submitted exactly as written, with no encryption and no epoch fan-out, and the erasure log stores it in the clear (a warning naming the keys goes to stderr). The key must have a rule in `--ipcrypt-config` for `--signal` (metric keys by their sanitized form), and the command is refused when an ordinary `--matcher` would be encrypted, since no record holds both a raw value and a ciphertext |
| `--reason` | | | Optional free-text operator reason |
| `--request-id` | | | Reuse an explicit request id (UUID) instead of generating one, to retry a prior submit idempotently under CreateIfAbsent. When the IPCrypt policy fans the submit out to several requests, this is the base id each request's id is derived from |
| `--signal` | | | |
Expand Down
Loading
Loading