CCR-2026-0007: binky IMAP on tenants/ mount + CCR allowlist
Enable tenant commercial secrets: applier accepts mount tenants/, apply policy and OIDC role for company-email IMAP (metadata only; values are founder Red provision). Extend agent-high-risk-boundary for the path.
This commit is contained in:
parent
347226eb36
commit
86209fa90c
6 changed files with 218 additions and 27 deletions
|
|
@ -0,0 +1,118 @@
|
|||
id: CCR-2026-0007
|
||||
kind: credential-change-request
|
||||
schema_version: 1
|
||||
request_type: workload-kv-read
|
||||
title: Binky company email IMAP credentials (tenant lane)
|
||||
status: applied
|
||||
created: '2026-07-17'
|
||||
updated: '2026-07-17'
|
||||
requester:
|
||||
agent: grok
|
||||
reason: Establish OpenBao custody for Binky-Hedgehog GmbH company mailbox IMAP credentials
|
||||
used by email-connect read-only scans (binky-control dogfood; WARDEN-WP-0028 first
|
||||
tenant lane on mount tenants/).
|
||||
review:
|
||||
required: true
|
||||
required_approvers:
|
||||
- platform-operator
|
||||
comments:
|
||||
- at: '2026-07-17T00:00:00+00:00'
|
||||
reviewer: bernd.worsch
|
||||
decision: approved
|
||||
comment: "Approved in chat \u2014 proceed with tenants/ mount and binky company-email\
|
||||
\ IMAP lane; founder provisions values Red-lane once."
|
||||
target:
|
||||
domain: financials
|
||||
tenant: binky
|
||||
workload: company-email
|
||||
environment: production
|
||||
purpose: Read-only IMAP scan of company mailbox for control-plane event intake
|
||||
openbao:
|
||||
mount: tenants
|
||||
kv_path: tenants/binky/company-email/imap
|
||||
fields:
|
||||
- IMAP_USERNAME
|
||||
- IMAP_PASSWORD
|
||||
policy_name: workload-kv-read-binky-company-email-imap
|
||||
policy_file: openbao/policies/workload-kv-read-binky-company-email-imap.hcl
|
||||
auth:
|
||||
method: oidc
|
||||
mount: netkingdom
|
||||
role: binky-company-email-imap-workload-kv-read
|
||||
allowed_redirect_uris:
|
||||
- https://bao.coulomb.social/ui/vault/auth/netkingdom/oidc/callback
|
||||
- http://localhost:8250/oidc/callback
|
||||
- http://127.0.0.1:8250/oidc/callback
|
||||
oidc_scopes:
|
||||
- openid
|
||||
- profile
|
||||
- email
|
||||
- groups
|
||||
user_claim: sub
|
||||
groups_claim: groups
|
||||
bound_claims:
|
||||
groups:
|
||||
- net-kingdom-admins
|
||||
bound_claims_confirmed: true
|
||||
policies:
|
||||
- workload-kv-read-binky-company-email-imap
|
||||
ttl: 15m
|
||||
access_frontdoor:
|
||||
type: ops-warden
|
||||
catalog_id: binky-company-email-imap
|
||||
selector: binky company email imap mailbox
|
||||
command: warden access binky-company-email-imap --out FILE
|
||||
resolvable: false
|
||||
readiness: applied-pending-provision
|
||||
delivery:
|
||||
surface: operator-workstation
|
||||
target: email-connect scan-mailbox via env IMAP_USERNAME/IMAP_PASSWORD (names only
|
||||
in config); binky-control mailmeta for metadata-only evidence
|
||||
risk:
|
||||
classification: high
|
||||
notes:
|
||||
- IMAP credentials grant mailbox read access for the company domain.
|
||||
- "High-risk lane (WP-0026 T04) \u2014 agents must not stream raw values; use --out/--exec/--wrap."
|
||||
- Values must not appear in Git, State Hub, chat, or workplans.
|
||||
- ops-warden proxies as the caller and must not retain values.
|
||||
- Distinct from platform/workloads/* fleet lanes; lives on mount tenants/.
|
||||
verification:
|
||||
positive:
|
||||
- Approved net-kingdom-admins identity can read the data path (capabilities include
|
||||
read) without printing values.
|
||||
negative:
|
||||
- default-policy token denied on tenants/data/binky/company-email/imap.
|
||||
activation_conditions:
|
||||
- tenants/ KV v2 mount enabled on bao.coulomb.social.
|
||||
- Policy and OIDC role applied.
|
||||
- Secret values provisioned by founder (Red lane) through approved custody.
|
||||
- Positive and negative capabilities-safe verification recorded.
|
||||
- ops-warden catalog promoted after verify + provision.
|
||||
evidence:
|
||||
- at: '2026-07-17T00:00:00+00:00'
|
||||
actor: grok
|
||||
kind: mount_enable
|
||||
result: passed
|
||||
details:
|
||||
- 'Enabled KV v2 mount tenants/ description: Client/tenant commercial secrets
|
||||
(WARDEN-WP-0028).'
|
||||
- No secret values written.
|
||||
- at: '2026-07-17T12:00:00+00:00'
|
||||
actor: grok
|
||||
kind: delegated_metadata_apply
|
||||
result: passed
|
||||
details:
|
||||
- 'Policy metadata write: sys/policies/acl/workload-kv-read-binky-company-email-imap'
|
||||
- 'Auth role metadata write: auth/netkingdom/role/binky-company-email-imap-workload-kv-read'
|
||||
- "Capabilities-safe: lane-policy token \u2192 read on tenants/data/.../imap;\
|
||||
\ default-policy \u2192 deny"
|
||||
- No secret values written (founder Red provision remains).
|
||||
lifecycle:
|
||||
deactivate: Disable ops-warden catalog entry and detach OIDC role policy; rotate
|
||||
mailbox password at provider.
|
||||
rotate: Mint a new mailbox app password; bao kv put tenants/binky/company-email/imap
|
||||
IMAP_PASSWORD=@file; verify capabilities-safe.
|
||||
compromised: Deactivate front door, rotate mailbox password, clear EXPOSED taint
|
||||
after rotation, record blast-radius notes.
|
||||
state_hub:
|
||||
workplan_id: WARDEN-WP-0028
|
||||
|
|
@ -360,3 +360,39 @@ PAT mint: attended session as Forgejo user `tegwick` (site admin). Minimum scope
|
|||
`read:package`, `write:package`, `read:repository`, `write:repository`, plus admin
|
||||
scopes required by `forgejo-operator-bootstrap`. Downstream repos update `load_token()`
|
||||
to read OpenBao when env is unset after lane verification.
|
||||
|
||||
## Tenant commercial secrets (mount `tenants/`) — WARDEN-WP-0028
|
||||
|
||||
Client/tenant secrets are **not** under `platform/workloads/`. They use a
|
||||
dedicated KV v2 mount:
|
||||
|
||||
```text
|
||||
tenants/<tenant_slug>/<workload>/<bundle>
|
||||
```
|
||||
|
||||
CCR applier allowlist accepts `mount: tenants` and paths under `tenants/`
|
||||
(in addition to `platform/workloads/` for fleet lanes).
|
||||
|
||||
### Binky company email IMAP (`CCR-2026-0007`)
|
||||
|
||||
| Item | Value |
|
||||
| --- | --- |
|
||||
| ops-warden catalog id | `binky-company-email-imap` |
|
||||
| Tenant | `binky` |
|
||||
| Mount | `tenants` |
|
||||
| OpenBao CLI path | `tenants/binky/company-email/imap` |
|
||||
| Fields | `IMAP_USERNAME`, `IMAP_PASSWORD` |
|
||||
| Read policy | `workload-kv-read-binky-company-email-imap` |
|
||||
| Policy file | `openbao/policies/workload-kv-read-binky-company-email-imap.hcl` |
|
||||
| OIDC role | `binky-company-email-imap-workload-kv-read` |
|
||||
| Front-door readiness | draft / applied-pending-provision (values: founder Red) |
|
||||
| Risk | high |
|
||||
|
||||
```bash
|
||||
bao login -method=oidc -path=netkingdom role=binky-company-email-imap-workload-kv-read
|
||||
# after provision:
|
||||
bao kv get -field=IMAP_PASSWORD tenants/binky/company-email/imap # interactive human only
|
||||
warden access binky-company-email-imap --all --out /tmp/p # preferred
|
||||
```
|
||||
|
||||
Onboarding playbook: `ops-warden/wiki/playbooks/tenant-secret-onboarding.md`.
|
||||
|
|
|
|||
|
|
@ -1,36 +1,21 @@
|
|||
# Agent coding read-boundary for high-risk secret lanes (WARDEN-WP-0026 T04).
|
||||
# Agent coding read-boundary for high-risk secret lanes (WARDEN-WP-0026 T04 / WP-0028).
|
||||
#
|
||||
# Coding agents (Claude/Codex/Grok sessions, automated workers) MUST NOT hold
|
||||
# raw data-read on high-risk recovery/upload/admin secrets. This policy is the
|
||||
# allow-list they *may* carry for those paths: metadata + capabilities only.
|
||||
# Operator OIDC roles keep the full workload-kv-read-* policies separately.
|
||||
#
|
||||
# High-risk set (must stay in sync with ops-warden catalog risk: high):
|
||||
# - railiance backup offsite (WebDAV upload + age recovery escrow)
|
||||
# - forgejo admin PAT
|
||||
# - openrouter LLM provider key (provider spend + prompt-adjacent)
|
||||
#
|
||||
# Apply live: `bao policy write agent-high-risk-boundary openbao/policies/agent-high-risk-boundary.hcl`
|
||||
# Attach to agent AppRoles / tokens; never attach workload-kv-read-* for these paths
|
||||
# to the same identity.
|
||||
# Coding agents MUST NOT hold raw data-read on high-risk recovery/upload/admin
|
||||
# or tenant mailbox secrets. Metadata + capabilities only.
|
||||
|
||||
# --- railiance-backup-offsite-lane ---
|
||||
# --- platform high-risk ---
|
||||
path "platform/data/workloads/railiance/backup/offsite-lane" {
|
||||
capabilities = ["deny"]
|
||||
}
|
||||
path "platform/metadata/workloads/railiance/backup/offsite-lane" {
|
||||
capabilities = ["read"]
|
||||
}
|
||||
|
||||
# --- forgejo-admin ---
|
||||
path "platform/data/workloads/forgejo/forgejo-admin" {
|
||||
capabilities = ["deny"]
|
||||
}
|
||||
path "platform/metadata/workloads/forgejo/forgejo-admin" {
|
||||
capabilities = ["read"]
|
||||
}
|
||||
|
||||
# --- openrouter-llm-connect ---
|
||||
path "platform/data/workloads/activity-core/llm-connect/llm-connect-provider-secrets" {
|
||||
capabilities = ["deny"]
|
||||
}
|
||||
|
|
@ -38,7 +23,14 @@ path "platform/metadata/workloads/activity-core/llm-connect/llm-connect-provider
|
|||
capabilities = ["read"]
|
||||
}
|
||||
|
||||
# Capabilities introspection (no secret values)
|
||||
# --- tenant high-risk (WARDEN-WP-0028) ---
|
||||
path "tenants/data/binky/company-email/imap" {
|
||||
capabilities = ["deny"]
|
||||
}
|
||||
path "tenants/metadata/binky/company-email/imap" {
|
||||
capabilities = ["read"]
|
||||
}
|
||||
|
||||
path "sys/capabilities-self" {
|
||||
capabilities = ["update"]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,11 @@
|
|||
# Least-privilege read policy for Binky company email IMAP credentials.
|
||||
# Tenant mount (WARDEN-WP-0028) — client commercial secrets, not platform workloads.
|
||||
# Exact path only; no list on parent, no sibling tenant paths.
|
||||
|
||||
path "tenants/data/binky/company-email/imap" {
|
||||
capabilities = ["read"]
|
||||
}
|
||||
|
||||
path "tenants/metadata/binky/company-email/imap" {
|
||||
capabilities = ["read"]
|
||||
}
|
||||
|
|
@ -591,10 +591,22 @@ def applier_policy_violations(ccr: dict[str, Any]) -> list[str]:
|
|||
policy_file = str(openbao.get("policy_file") or "")
|
||||
fields = openbao.get("fields") or []
|
||||
|
||||
if mount != "platform":
|
||||
violations.append(f"openbao.mount must be platform, got {mount}")
|
||||
if not kv_path.startswith("platform/workloads/"):
|
||||
violations.append("openbao.kv_path must stay under platform/workloads/")
|
||||
# WARDEN-WP-0028: platform fleet lanes stay under platform/workloads/;
|
||||
# client/tenant commercial secrets live on mount tenants/ (exact-path policies).
|
||||
allowed_mount_prefixes = {
|
||||
"platform": "platform/workloads/",
|
||||
"tenants": "tenants/",
|
||||
}
|
||||
if mount not in allowed_mount_prefixes:
|
||||
violations.append(
|
||||
f"openbao.mount must be one of {sorted(allowed_mount_prefixes)}, got {mount}"
|
||||
)
|
||||
else:
|
||||
prefix = allowed_mount_prefixes[mount]
|
||||
if not kv_path.startswith(prefix):
|
||||
violations.append(
|
||||
f"openbao.kv_path for mount {mount!r} must stay under {prefix}"
|
||||
)
|
||||
if any(fragment in kv_path for fragment in ("*", "..", "//")):
|
||||
violations.append("openbao.kv_path must not contain wildcard, parent, or empty segments")
|
||||
if "/data/" in kv_path or "/metadata/" in kv_path:
|
||||
|
|
@ -1600,7 +1612,7 @@ def decision_templates(ccr: dict[str, Any] | None = None) -> dict[str, str]:
|
|||
else:
|
||||
context = {
|
||||
"id": "<CCR-ID>",
|
||||
"kv_path": "<platform/workloads/...>",
|
||||
"kv_path": "<platform/workloads/...> or <tenants/<tenant>/...>",
|
||||
"policy_name": "<workload-kv-read-...>",
|
||||
"auth_role_path": "auth/<mount>/role/<role>",
|
||||
"decision_link": "<link State Hub decision>",
|
||||
|
|
|
|||
|
|
@ -358,8 +358,30 @@ class CredentialChangeTests(unittest.TestCase):
|
|||
ccr["openbao"]["mount"] = "secret"
|
||||
ccr["openbao"]["kv_path"] = "secret/platform-admin"
|
||||
blockers = credential_change.applier_readiness_blockers(ccr)
|
||||
self.assertIn("openbao.mount must be platform, got secret", blockers)
|
||||
self.assertIn("openbao.kv_path must stay under platform/workloads/", blockers)
|
||||
self.assertTrue(
|
||||
any("openbao.mount must be one of" in b and "secret" in b for b in blockers),
|
||||
blockers,
|
||||
)
|
||||
|
||||
def test_applier_accepts_tenants_mount_path(self) -> None:
|
||||
"""WARDEN-WP-0028 — tenant commercial secrets on mount tenants/."""
|
||||
ccr, errors, _warnings = credential_change.validate_ccr(self.sample)
|
||||
self.assertEqual(errors, [])
|
||||
ccr["status"] = "approved"
|
||||
ccr["openbao"]["mount"] = "tenants"
|
||||
ccr["openbao"]["kv_path"] = "tenants/binky/company-email/imap"
|
||||
ccr["openbao"]["policy_name"] = "workload-kv-read-binky-company-email-imap"
|
||||
ccr["openbao"]["policy_file"] = (
|
||||
"openbao/policies/workload-kv-read-binky-company-email-imap.hcl"
|
||||
)
|
||||
ccr["openbao"]["auth"]["role"] = "binky-company-email-imap-workload-kv-read"
|
||||
ccr["openbao"]["auth"]["policies"] = [
|
||||
"workload-kv-read-binky-company-email-imap"
|
||||
]
|
||||
# Skip policy_file body match in this unit check (file exists in repo for real CCR).
|
||||
ccr["openbao"]["policy_file"] = ""
|
||||
violations = credential_change.applier_policy_violations(ccr)
|
||||
self.assertEqual(violations, [], violations)
|
||||
|
||||
def test_nonprod_applier_policy_remains_metadata_only(self) -> None:
|
||||
policy = (
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue