137 lines
4.6 KiB
Markdown
137 lines
4.6 KiB
Markdown
# AwoooP RLS Preflight Runbook
|
|
|
|
> Purpose: verify whether production is ready for PostgreSQL Row-Level Security
|
|
> without enabling RLS or changing data.
|
|
|
|
## Command
|
|
|
|
Default path runs the probe inside the production API pod through the 120
|
|
control-plane host. `DATABASE_URL` stays inside Kubernetes and is not printed.
|
|
|
|
```bash
|
|
bash scripts/ops/awooop-rls-preflight.sh
|
|
```
|
|
|
|
Before enabling RLS, run exact backfill counts:
|
|
|
|
```bash
|
|
bash scripts/ops/awooop-rls-preflight.sh --exact-counts
|
|
```
|
|
|
|
Useful variants:
|
|
|
|
```bash
|
|
bash scripts/ops/awooop-rls-preflight.sh --json
|
|
bash scripts/ops/awooop-rls-preflight.sh --local
|
|
AWOOOP_RLS_SSH_TARGET=wooo@192.168.0.120 bash scripts/ops/awooop-rls-preflight.sh
|
|
```
|
|
|
|
Exit code `2` means the gate is blocked and RLS must not be enabled yet.
|
|
|
|
## Exact Count Scope
|
|
|
|
After any target table has RLS enabled, `--exact-counts` runs as the production
|
|
app DB user and is filtered by the current `app.project_id`. The output marks
|
|
these rows with:
|
|
|
|
```text
|
|
scope=rls_filtered project_context=...
|
|
```
|
|
|
|
Treat those counts as tenant-visible evidence, not global row counts. Use a
|
|
reviewed postgres/operator path for global counts after RLS is enabled.
|
|
|
|
## 2026-05-12 Initial Production Result
|
|
|
|
`--exact-counts` returned:
|
|
|
|
- `PASS current_role_rls_enforced`: current DB user is `awoooi`, not superuser and not `BYPASSRLS`.
|
|
- `PASS project_context_set_config`: `set_config('app.project_id', 'awoooi', TRUE)` works in the API pod.
|
|
- `BLOCKED required_roles`: `awooop_app`, `awooop_platform_admin`, and `awooop_migration` do not exist.
|
|
- `WARN role_bootstrap_authority`: current API DB user `awoooi` is not `CREATEROLE`; role bootstrap requires `postgres` or a `CREATEROLE` operator.
|
|
- `WARN app_role_membership`: `awooop_app` is missing, so membership cannot be evaluated yet.
|
|
- `PASS project_id_columns`: every existing target table has `project_id`.
|
|
- `BLOCKED rls_enabled_forced_policy`: existing target tables are not yet RLS enabled, forced, or policied.
|
|
- `PASS fail_open_policies`: production DB currently has no fail-open policy expressions.
|
|
- `PASS project_id_backfill`: exact counts found zero `NULL project_id` rows in counted target tables.
|
|
|
|
Current blocker summary:
|
|
|
|
```text
|
|
PASS=5 WARN=2 BLOCKED=2
|
|
```
|
|
|
|
Important exact counts from the same run:
|
|
|
|
| Table | Rows | NULL project_id |
|
|
| --- | ---: | ---: |
|
|
| `audit_logs` | 686 | 0 |
|
|
| `awooop_mcp_tool_registry` | 4 | 0 |
|
|
| `awooop_outbound_message` | 235 | 0 |
|
|
| `awooop_projects` | 2 | 0 |
|
|
| `awooop_run_state` | 113 | 0 |
|
|
| `incidents` | 1518 | 0 |
|
|
| `knowledge_entries` | 2102 | 0 |
|
|
| `playbooks` | 220 | 0 |
|
|
|
|
## 2026-05-12 Role Bootstrap Applied
|
|
|
|
At `19:33 CST`, the manual role bootstrap was applied through the host
|
|
PostgreSQL socket as `postgres`. It did not enable RLS policies.
|
|
|
|
Post-bootstrap `--exact-counts` returned:
|
|
|
|
- `PASS current_role_rls_enforced`: current DB user is still `awoooi`, not
|
|
superuser and not `BYPASSRLS`.
|
|
- `PASS project_context_set_config`: `set_config('app.project_id', 'awoooi', TRUE)` works.
|
|
- `PASS required_roles`: `awooop_app`, `awooop_platform_admin`, and
|
|
`awooop_migration` now exist.
|
|
- `PASS app_role_membership`: current API DB user is a member of `awooop_app`.
|
|
- `PASS project_id_columns`: every existing target table has `project_id`.
|
|
- `BLOCKED rls_enabled_forced_policy`: target tables are still not RLS enabled,
|
|
forced, or policied.
|
|
- `PASS fail_open_policies`: no fail-open policy expressions detected.
|
|
- `PASS project_id_backfill`: exact counts found zero `NULL project_id` rows in
|
|
counted target tables.
|
|
|
|
Current blocker summary:
|
|
|
|
```text
|
|
PASS=7 WARN=0 BLOCKED=1
|
|
```
|
|
|
|
Updated exact counts:
|
|
|
|
| Table | Rows | NULL project_id |
|
|
| --- | ---: | ---: |
|
|
| `audit_logs` | 686 | 0 |
|
|
| `awooop_mcp_tool_registry` | 4 | 0 |
|
|
| `awooop_outbound_message` | 248 | 0 |
|
|
| `awooop_projects` | 2 | 0 |
|
|
| `awooop_run_state` | 126 | 0 |
|
|
| `incidents` | 1524 | 0 |
|
|
| `knowledge_entries` | 2103 | 0 |
|
|
| `playbooks` | 220 | 0 |
|
|
|
|
## Remediation Order
|
|
|
|
1. Verify all DB access paths use `get_db()` / `get_db_context()` or otherwise set
|
|
`app.project_id` before queries.
|
|
2. Apply policies first in staging or a canary DB.
|
|
3. In production, enable one batch at a time.
|
|
4. After each batch, run:
|
|
|
|
```bash
|
|
bash scripts/ops/awooop-rls-preflight.sh --exact-counts
|
|
```
|
|
|
|
5. Validate AwoooP Runs, Approvals, Monitoring, Tickets, Cost, alert ingestion,
|
|
background workers, and TelegramGateway mirror paths.
|
|
|
|
## Do Not
|
|
|
|
- Do not enable all policies in production before the role path is decided.
|
|
- Do not rely on fail-open `IS NULL` or empty-string policies as the target state.
|
|
- Do not run destructive rollback SQL unless the incident commander explicitly
|
|
approves it.
|