Locks · lockmq
lockmq configuration
Profiles, auto rules and PII rules: every key of the files in .lockmq/,
and the company's managed configuration that bounds
them.
The commands that write them are in usage.md.
Profiles #
Profiles live in .lockmq/config.toml at the project root. No file holds a
secret: a URL with a password is refused.
[profiles.prod]
url = "https://rabbitmq.example.com:15671" # management API
amqp_url = "amqps://rabbitmq.example.com:5671" # optional
stream_url = "rabbitmq-stream+tls://rabbitmq.example.com:5551" # optional
vhosts = ["orders"] # empty: every vhost the account sees
user = "" # empty: asked at console start
credentials = "vault" # ask | keychain | vault
vault_path = "rabbitmq/creds/lockmq-ro"
tls = "verify-full"
tier = "read" # read | peek
production = true
detectors = ["email", "phone", "iban", "card"]
body_mode = "redact-unknown" # redact-unknown | detect
[profiles.prod.limits]
request_timeout = "10s"
read_max = 20 # messages per stream read (ceiling 200)
peek_max = 5 # messages per peek (ceiling 20)
delivery_headroom = 3
max_body_bytes = 16384
max_output_bytes = 65536
list_page_size = 100
idle_timeout = "20m"
max_session = "4h"
tickets_per_window = 30 # tickets a human decides per window
ticket_window = "5m"
max_pending = 30 # tickets pending at once
[profiles.NAME.auto] shapes lox run's auto mode for the profile, one
rule per action, auto, ask or deny (auto = "full-auto" sets them all
to auto, for a local broker left to the agent):
[profiles.prod.auto]
connect = "ask" # default: a human approves the first connection
list = "auto" # queues, exchanges, bindings, counts
read = "auto" # stream reads
peek = "ask" # default: requeued queue reads, a human approves each
purge = "deny" # get, publish, purge, delete: ask by default (no such operation yet)
See usage.md.
An [profiles.NAME.ssh] table reaches a broker behind a bastion: the console
opens the tunnel itself and checks the bastion's host key; streams are then
read over AMQP.
All profile keys #
| Key | Default | Notes |
|---|---|---|
url |
required | management API, http:// or https://; no userinfo |
amqp_url, stream_url |
none | optional; an operation that needs a missing one says which key to set |
vhosts |
every vhost the account sees | the only vhosts served |
user |
asked at start | with credentials = "vault": the secret's username |
credentials |
ask |
ask, keychain or vault |
vault_path |
required with vault |
e.g. rabbitmq/creds/<role> |
credentials_ttl |
none | at least 1m: the secret is asked again once it is that old |
tls |
verify-full |
disable, prefer, require, verify-ca, verify-full |
tls_ca, tls_cert, tls_key |
none | a private CA; a client certificate (both or neither) |
tier |
read |
read or peek |
production |
false |
typed approval, redact-unknown body mode |
detectors |
card, email, iban, phone |
also be_niss, fr_nir, nl_bsn, us_ssn, ip, secret, person_name; [] disables them |
body_mode |
redact-unknown on production, detect otherwise |
|
limits.request_timeout |
10s | |
limits.read_max |
20 | ceiling 200 |
limits.peek_max |
5 | ceiling 20 |
limits.delivery_headroom |
3 | lowering it is a loosening |
limits.max_body_bytes |
16 384 | per message after masking |
limits.max_output_bytes |
65 536 | messages dropped from the end and counted |
limits.list_page_size |
100 | |
limits.idle_timeout, limits.max_session |
20m, 4h | session ends after this long idle / in all |
limits.tickets_per_window, limits.ticket_window |
30 per 5m |
hub tickets a human decides, opened per window at most; a ticket an auto rule of lox run decides leaves this count, and a safety cap of 300 tickets per window counts every one (ticket limits); more tickets or a shorter window is a loosening |
limits.max_pending |
30 | hub tickets pending a decision at once at most; raising it is a loosening |
auto |
connect/peek ask, list/read auto, get/publish/purge/delete ask |
"full-auto" or a table of auto/ask/deny; a looser rule is a loosening |
ssh.host, ssh.port, ssh.user, ssh.auth, ssh.key, ssh.credentials |
port 22 | as in locksql: auth is key, agent or password |
Changing the file later: tightenings (a lower limit, peek → read, a
removed vhost, a new mask rule) apply at once; loosenings (more tickets, a
shorter ticket_window or more pending tickets among them) wait for you in
the console (:review).
PII rules #
.lockmq/pii.toml:
[[mask]]
queue = ["orders/order-events"] # vhost/name globs; "//name" for the vhost /
body = "$.customer.email" # JSON path: $.a.b, $.a[*].b, $.a[3]
mode = "email" # redact (default) | partial | email
[[mask]]
queue = ["*/*"]
header = "x-customer-*"
[[allow]]
queue = ["orders/order-events", "orders/order-events.dlq"]
body = "$.items[*].sku"
Mask rules of the queue read, of the origin queue in a message's first
x-death and of its exchange apply; allow rules only from the queue read, so
a forged x-death can only add masking. The agent may add a mask rule
(lockmq pii add); an allow rule is a loosening the human confirms.
Managed configuration #
A company can bound every lockmq of a machine with the Lock Family's
managed file, /etc/lockfamily/managed.toml on Linux and
/Library/Application Support/lockfamily/managed.toml on macOS (schema,
signing and deployment:
lockfamily docs/managed.md).
The file and its directory must be owned by root and writable by root
only; a badly owned, invalid or badly signed file is a refusal to start,
never a file ignored. lockmq reads its [lockmq] section:
version = 1
organisation = "Acme"
[lockmq.bounds] # the loosest policy allowed
tier = "read"
vhosts = ["orders", "billing"]
credentials = ["vault"]
hosts = ["*.mq.acme.internal"]
limits = { read_max = 50, max_session = "8h" }
detectors = ["email", "iban"] # always on, added to every profile
[[lockmq.bounds.pii_mask]] # always on, added to the PII rules
queue = ["orders/*"]
body = "$.customer.email"
[lockmq.defaults] # for the keys a profile leaves unset
tls = "verify-full"
limits = { read_max = 20 }
[lockmq.profiles.orders-prod] # served as written
url = "https://mq.acme.internal:15671"
production = true
vhosts = ["orders"]
Precedence: hard-coded floor > managed bounds and profiles > project file > user file > managed defaults > built-in defaults.
- Profiles: a managed profile is served as written (the defaults fill what it leaves unset). A profile of the same name in the project or user config is refused at load, naming it: rename yours.
- Defaults:
[lockmq.defaults]takes every profile key,limits.*included; a value of the config files wins over it. - Bounds: the merged profile is checked against
[lockmq.bounds]at the console's start and at every policy poll (2 s). A profile beyond a bound is refused with no prompt, citing the key (blocked by company policy: lockmq.bounds.tier = read (profile dev: tier = peek)), auditedrefused-managed, and no human can approve past it. A loosening edited in while the console runs is refused at the next poll, never pending. A duration or size beyond its bound is a violation, never clamped. A managed default or a managed profile beyond the bounds is a load error. lockmq managed showprints the file, its fingerprint and signature, the bounds, the defaults and the managed profiles;lockmq managed check [--profile P]checks the profiles as the console does, with no connection and no credential: oneblocked by company policy: …line per violation and exit 3, elseok: …and exit 0 (lox runruns it before it starts the lock).lockmq doctorhas amanagedline.
| Bound key | Type | Looser (refused) |
|---|---|---|
production |
true |
a profile that is not production |
tier |
read or peek |
a higher tier (peek under read) |
tls |
TLS mode, a floor | a weaker mode (disable < prefer < require < verify-ca < verify-full) |
body_mode |
"redact-unknown" |
detect |
vhosts |
allowed vhosts | a vhost outside the list, or no vhosts (every vhost) |
credentials |
allowed modes (ask, keychain, vault) |
a mode outside the list |
hosts |
host patterns (* any run) |
a host of url, amqp_url, stream_url or ssh.host matching none |
credentials_ttl |
duration ceiling | longer, or none (kept for the session) |
auto.<action> (connect, list, read, peek, get, publish, purge, delete) |
deny, ask or auto, a ceiling |
a looser rule (deny < ask < auto) |
limits.read_max, limits.peek_max, limits.max_body_bytes, limits.max_output_bytes, limits.list_page_size |
integer ceiling | a larger value (the built-in default counts when the profile sets none) |
limits.request_timeout, limits.idle_timeout, limits.max_session |
duration ceiling | a longer duration |
detectors |
required detectors | none: they are added to every profile |
[[pii_mask]] |
mask rules (queue, exchange, one of body/header/property, mode), as [[mask]] |
none: they are added to the rules in force; a [[allow]] that could clear one is refused at load, and a user mask of the same target takes the managed mode |
[family] vault = { required = true } acts as credentials = ["vault"],
cited as family.vault.required.