NK-WP-0054-T02: draft-2 of the Service Identity and Privilege standard after the founder's review
Applied the founder's four decisions: OpenBao platform-admin binds aal2 (SI-8 MUST); the human administrator keeps directory admin rights through an admin group (SI-5); declarations start in this repository and move to the services; agents get their own class and rule SI-14. Finding F-1: the coding-agent role presents as a service against IAM Profile v0.3. Schema, validator and tests follow (37 pass). Rehearsals listed, not decided. Not ratified; owner review still open. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Assistant: claude-code Assistant-Model: sonnet Assistant-Process: 328925@bnt-lap001 Assistant-Session: 14b795fd-c632-4c01-8d28-e7e490c7b0ed
This commit is contained in:
parent
48385816fd
commit
ca03da52c6
8 changed files with 194 additions and 46 deletions
|
|
@ -140,6 +140,7 @@
|
|||
"bind",
|
||||
"service-admin",
|
||||
"workload",
|
||||
"agent",
|
||||
"infrastructure"
|
||||
]
|
||||
}
|
||||
|
|
@ -285,6 +286,43 @@
|
|||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
"agent": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"mode",
|
||||
"presents_principal_type",
|
||||
"max_token_ttl_seconds",
|
||||
"boundary_policy"
|
||||
],
|
||||
"properties": {
|
||||
"mode": {
|
||||
"enum": [
|
||||
"autonomous",
|
||||
"delegated",
|
||||
"unknown"
|
||||
]
|
||||
},
|
||||
"presents_principal_type": {
|
||||
"enum": [
|
||||
"agent",
|
||||
"service",
|
||||
"human",
|
||||
"unknown"
|
||||
]
|
||||
},
|
||||
"max_token_ttl_seconds": {
|
||||
"type": "integer",
|
||||
"minimum": 1,
|
||||
"maximum": 86400
|
||||
},
|
||||
"boundary_policy": {
|
||||
"type": "string",
|
||||
"minLength": 3,
|
||||
"maxLength": 200
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"allOf": [
|
||||
|
|
@ -304,6 +342,25 @@
|
|||
"disposition"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"if": {
|
||||
"properties": {
|
||||
"classes": {
|
||||
"contains": {
|
||||
"const": "agent"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"classes"
|
||||
]
|
||||
},
|
||||
"then": {
|
||||
"required": [
|
||||
"agent"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ version: "0.1"
|
|||
created: "2026-10-24"
|
||||
updated: "2026-10-24"
|
||||
scope: service-account-governance
|
||||
revision: "draft-1"
|
||||
revision: "draft-2"
|
||||
owner: net-kingdom
|
||||
last_reviewed: "2026-10-24"
|
||||
review_interval: 3m
|
||||
|
|
@ -27,10 +27,17 @@ related:
|
|||
|
||||
## 0. Status
|
||||
|
||||
**Proposed, draft-1.** It binds nothing until the founder ratifies it and each owner adopts it.
|
||||
**Proposed, draft-2.** It binds nothing until the founder ratifies it and each owner adopts it.
|
||||
It was started on 2026-10-24 from the founder's direction and from the first inventory
|
||||
(`docs/service-identity-inventory.md`, NK-WP-0054-T01). Its machine-readable declaration
|
||||
(NK-WP-0054-T03) and conformance check (T04) do not exist yet.
|
||||
(`docs/service-identity-inventory.md`, NK-WP-0054-T01).
|
||||
|
||||
- **draft-1** (2026-10-24): thirteen rules, naming, five initial exceptions, seven open questions.
|
||||
- **draft-2** (2026-10-24): the founder's review answered four questions (§10). Added the `agent` class and
|
||||
SI-14; SI-8 now requires the assurance claim for admin-policy roles; the human administrator keeps
|
||||
directory admin rights through group membership; declarations start in this repository and move to each
|
||||
service. Building the offline validator (NK-WP-0054-T03) also made custody a list and broadened SI-X1.
|
||||
|
||||
The offline validator exists (`tools/service-identity`, provisional). The live health check (T04) does not.
|
||||
|
||||
The finding behind it: one LLDAP account, `uid=admin`, is at the same time the human platform
|
||||
administrator, the directory super-admin and the bind credential of Authelia, KeyCape, the
|
||||
|
|
@ -67,7 +74,8 @@ the credential. Every identity has exactly one **class**.
|
|||
| `break-glass` | Final authority held offline, not routine | `platform-root` (king credential) |
|
||||
| `bind` | A service's own lookup in another service's directory | planned `svc-authelia` |
|
||||
| `service-admin` | The administrative account of one service | `pi-admin`, planned `ld-admin` |
|
||||
| `workload` | A service or agent that obtains tokens or secrets | KeyCape clients, `secrets-engine-openbao` |
|
||||
| `workload` | A service that obtains tokens or secrets | KeyCape clients, `secrets-engine-openbao` |
|
||||
| `agent` | Automation that acts autonomously or under delegated authority; IAM Profile v0.3 agent principal | the coding agent |
|
||||
| `infrastructure` | A role below the application | database role `privacyidea` |
|
||||
|
||||
## 3. Rules
|
||||
|
|
@ -94,8 +102,9 @@ automation unless the declaration says so.
|
|||
|
||||
**SI-5 Attributed administration first.** Where a service supports administrative roles granted
|
||||
to an attributed human identity (for example OpenBao policies bound to a group), day-to-day
|
||||
administration SHOULD use that route. The local `service-admin` is the sealed fallback and is
|
||||
listed in the crisis custody list. A service that has only a local administrator (privacyIDEA
|
||||
administration SHOULD use that route. The human platform administrator holds directory and policy administration rights through membership
|
||||
of an administrative group on their own account, with their own password and second factor. The local
|
||||
`service-admin` is the sealed fallback and is listed in the crisis custody list. A service that has only a local administrator (privacyIDEA
|
||||
today) records that as an exception, not as a norm.
|
||||
|
||||
**SI-6 Break-glass.** There MUST be a break-glass identity, held offline. Using it is an event
|
||||
|
|
@ -106,9 +115,10 @@ test subject of a drill. It authorizes; it does not operate.
|
|||
custody items and MUST NOT hold a production service credential. It builds and tests.
|
||||
|
||||
**SI-8 Assurance.** Every `human` and `service-admin` identity MUST require a second factor
|
||||
where the service can enforce one. A role that grants administrative policy SHOULD require the
|
||||
where the service can enforce one. A role that grants administrative policy MUST require the
|
||||
assurance level itself (for example an `aal2` claim bound in the OpenBao role), not only rely on
|
||||
an upstream service having checked. A role that does not MUST record why in an exception.
|
||||
Decided 2026-10-24: OpenBao `platform-admin` binds `aal2`.
|
||||
|
||||
**SI-9 Custody class.** Every credential MUST declare a custody class: `offline-key`,
|
||||
`personal-safe`, `openbao-lease`, `delivered-secret` (delivered into the workload by the
|
||||
|
|
@ -132,6 +142,11 @@ cannot be used while its own service is down MUST NOT be the only recovery path.
|
|||
MUST be run by the production operator identity, with that identity's own token as the test
|
||||
subject, and MUST record which identity ran each stage.
|
||||
|
||||
**SI-14 Agents.** An agent identity MUST present as an agent: its tokens carry `principal_type: agent` and
|
||||
name the agent's mode, autonomous or delegated (IAM Profile v0.3). It MUST NOT use a human's credential, MUST
|
||||
have short-lived tokens (this draft proposes a ceiling of 3600 seconds) and MUST be bounded by a named
|
||||
boundary policy. A delegated agent names the delegating actor in its tokens.
|
||||
|
||||
## 4. Naming
|
||||
|
||||
| Class | Pattern | Notes |
|
||||
|
|
@ -141,6 +156,7 @@ subject, and MUST record which identity ran each stage.
|
|||
| `break-glass` | `platform-root` | The king credential |
|
||||
| `human` | the person's own login | `tegwick`, `admin` |
|
||||
| `workload` | the token subject form already in use | for example `service:secrets-engine`; IAM Profile v0.3 governs the token |
|
||||
| `agent` | the agent's own subject | for example `service:codex:railiance-platform` today; the tokens must carry `principal_type: agent` (SI-14) |
|
||||
|
||||
Names are reserved for their class. A `svc-` or `-admin` name MUST NOT be used for a human account. A
|
||||
federated role that holds no credential (custody `none`), such as an OpenBao role that maps a group to a
|
||||
|
|
@ -153,7 +169,7 @@ steps are NK-WP-0054-T05 and T06.
|
|||
|
||||
| Service | `service-admin` | `bind` and write identities | Human route |
|
||||
| --- | --- | --- | --- |
|
||||
| LLDAP | `ld-admin` (the built-in super-admin, renamed or replaced after rehearsal) | `svc-authelia`, `svc-keycape`, `svc-privacyidea` read-only; a scoped provisioner identity | `admin` as a normal member of the admin group, no shared password |
|
||||
| LLDAP | `ld-admin` (the built-in super-admin, renamed or replaced after rehearsal) | `svc-authelia`, `svc-keycape`, `svc-privacyidea` read-only; a scoped provisioner identity | `admin` with directory admin rights through the admin group, own password and OTP |
|
||||
| Authelia | none (LDAP backend only) | uses `svc-authelia` | none |
|
||||
| privacyIDEA | `pi-admin` | resolver uses `svc-privacyidea` | none (local administrator only: exception) |
|
||||
| KeyCape | none | uses `svc-keycape` | none |
|
||||
|
|
@ -162,7 +178,9 @@ steps are NK-WP-0054-T05 and T06.
|
|||
## 6. Declaration
|
||||
|
||||
Each service publishes one declaration of every identity it holds, in a machine-readable form
|
||||
defined by NK-WP-0054-T03. For each identity: name, class, purpose, privileges, custody class,
|
||||
defined by NK-WP-0054-T03. Decided 2026-10-24: declarations start in this repository
|
||||
(`tools/service-identity/declarations/`) and move to each service's repository as its owner confirms them.
|
||||
The owner's confirmation is recorded in the declaration (`owner_confirmed`). For each identity: name, class, purpose, privileges, custody class,
|
||||
consumers, owner, rotation path and coupling, second-factor requirement, status
|
||||
(`active`, `legacy`, `planned`) and any exception id. A declaration holds no credential and no
|
||||
secret-shaped value.
|
||||
|
|
@ -173,8 +191,8 @@ For each identity the result is one of `conformant`, `non-conformant` (with the
|
|||
`excepted` (with the exception id) or `unknown`. Unknown is a finding, not a pass.
|
||||
|
||||
The offline validator `tools/service-identity/validate.py` (NK-WP-0054-T03, provisional) evaluates the
|
||||
rules that a declaration alone can decide: SI-1, SI-2, SI-3, SI-6, SI-8, SI-9, SI-11, SI-12 and the naming
|
||||
rules. SI-4, SI-5, SI-7, SI-10 and SI-13 are advisory or process rules and are not decided offline; SI-4
|
||||
rules that a declaration alone can decide: SI-1, SI-2, SI-3, SI-6, SI-8, SI-9, SI-11, SI-12, SI-14 and the
|
||||
naming rules. SI-4, SI-5, SI-7, SI-10 and SI-13 are advisory or process rules and are not decided offline; SI-4
|
||||
appears as an advisory. Unknown facts are reported as `unknown` for every class. Dates are explicit: the
|
||||
validator takes `--as-of`.
|
||||
|
||||
|
|
@ -198,9 +216,13 @@ Initial exceptions at adoption:
|
|||
| SI-X1 | SI-1, SI-2, SI-3, SI-9, NAMING | LLDAP `admin` | One account is the human, the super-admin and the shared bind of four services, and its password is in a person's safe and in a Kubernetes Secret | NK-WP-0054-T05 completes |
|
||||
| SI-X2 | SI-10 | LDAP binds | Static long-lived credential; LDAP has no lease | Tenancy Posture §9 gap review |
|
||||
| SI-X3 | NAMING, SI-11 | `trigger-admin` (privacyIDEA) | Legacy account, not named in the admin form, with uninventoried consumers; its disposition is tracked | NK-WP-0049-T22 disposition |
|
||||
| SI-X4 | SI-8 | OpenBao `netkingdom-platform-admin` | Role binds on the group claim only in the repository file | Decision on §10 question 1 |
|
||||
| SI-X4 | SI-8 | OpenBao `netkingdom-platform-admin` | Role binds on the group claim only in the repository file; decided 2026-10-24 to bind `aal2`, change owned by railiance-platform | The role binds `aal2` |
|
||||
| SI-X5 | SI-5 | privacyIDEA | Only a local administrator is supported | Upstream capability |
|
||||
|
||||
**Finding at adoption, not excepted:** F-1. The coding-agent role in OpenBao binds `principal_type: service`, so
|
||||
the agent presents as a service against IAM Profile v0.3 (SI-14). The validator reports it as non-conformant
|
||||
until railiance-platform changes the role or records an exception with a compensating control.
|
||||
|
||||
SI-X2 and SI-X5 concern SI-10 and SI-5, which cannot be decided from a declaration. They are recorded
|
||||
here and are not encoded in the declarations; the offline validator would call them stale. The
|
||||
declarations carry the exceptions the validator can evaluate (SI-X1, SI-X3, SI-X4).
|
||||
|
|
@ -208,7 +230,8 @@ declarations carry the exceptions the validator can evaluate (SI-X1, SI-X3, SI-X
|
|||
## 9. Relationship to other documents
|
||||
|
||||
- **IAM Profile v0.3** defines the principal types in tokens. This standard governs the
|
||||
accounts that exist behind them and does not change the token profile.
|
||||
accounts that exist behind them and does not change the token profile. KeyCape's confidential clients
|
||||
stay governed by the profile plus a declaration (settled 2026-10-24).
|
||||
- **Credential Management v0.2 §6** lists `LLDAP_LDAP_USER_PASS` as a three-way coupling.
|
||||
On adoption that row is corrected and replaced by the per-service bind accounts.
|
||||
- **Tenancy Posture §9** treats long-lived credentials as a gap; SI-10 applies it to binds.
|
||||
|
|
@ -218,28 +241,32 @@ declarations carry the exceptions the validator can evaluate (SI-X1, SI-X3, SI-X
|
|||
- The registry of `<short>-admin` forms lives with the declarations (T03) and starts with
|
||||
`pi-admin` and the proposed `ld-admin`.
|
||||
|
||||
## 10. Open questions
|
||||
## 10. Decisions and open items
|
||||
|
||||
1. **OpenBao assurance.** Should the `platform-admin` role bind `aal2` itself (SI-8) or rely on
|
||||
KeyCape's `requireForAll`? The other privileged roles already bind it.
|
||||
2. **Renaming LLDAP's built-in admin.** The directory's super-admin name is configurable, but
|
||||
renaming it on an existing directory is untested. It needs a rehearsal in isolation before
|
||||
`ld-admin` is decided (T06).
|
||||
3. **The human `admin` in LLDAP.** Does the human keep LLDAP admin-UI rights through group
|
||||
membership, or administer users only through the governed provisioner path?
|
||||
4. **The provisioner's narrowest scope.** What is the least privilege that still lets it create
|
||||
users and groups? It must be tested against the pinned LLDAP image.
|
||||
5. **Workload clients.** KeyCape's confidential clients are governed by IAM Profile v0.3. This
|
||||
standard only requires that each is declared, owned and not shared. Is that enough?
|
||||
6. **Where the declarations live.** In each service's repository (preferred, owners are
|
||||
accountable) or in this repository (simpler to validate).
|
||||
7. **Roles named like accounts.** The reserved-name rule is scoped to identities that hold a credential, so
|
||||
OpenBao's `netkingdom-platform-admin` role is not flagged. Is that the intended reading, or should such
|
||||
roles be renamed too?
|
||||
**Decided by the founder, 2026-10-24 (review of draft-1):**
|
||||
|
||||
1. OpenBao `platform-admin` binds the `aal2` claim itself (SI-8). The change is railiance-platform's.
|
||||
2. The human administrator keeps directory admin rights through an administrative group on their own
|
||||
account (SI-5); the service-admin account is a sealed fallback.
|
||||
3. Declarations start in this repository and move to each service repository as owners confirm them (§6).
|
||||
4. Agents get their own class, with SI-14.
|
||||
|
||||
**Settled unless the founder objects:** KeyCape's confidential clients are governed by IAM Profile v0.3 plus a
|
||||
declaration; the reserved-name rule applies to credential-holding accounts, not to federated roles (§4).
|
||||
|
||||
**To be rehearsed in isolation, not decided:**
|
||||
|
||||
- Renaming LLDAP's built-in admin to `ld-admin` on an existing directory (NK-WP-0054-T06).
|
||||
- The provisioner's narrowest working privilege against the pinned LLDAP image (T05).
|
||||
- Whether a read-only bind account can be prevented from signing in interactively. If not, SI-3's
|
||||
"unable to sign in" becomes "has a long random password that nobody uses and a read-only group".
|
||||
|
||||
**Open:** the ceiling of 3600 seconds in SI-14 is a proposal; the right figure comes from the existing
|
||||
agent roles (15 minutes today).
|
||||
|
||||
## 11. Adoption
|
||||
|
||||
Structure first, tooling second, as with Tenancy Posture. The order is: ratify the vocabulary and
|
||||
rules; complete the declarations (T03); run the health check read-only (T04) and publish the
|
||||
rules; confirm the declarations with their owners (T03); run the health check read-only (T04) and publish the
|
||||
first report; then retire the exceptions one by one, starting with SI-X1 (T05). No exception is
|
||||
closed by writing a declaration. Each is closed by the live change and an evidence run.
|
||||
|
|
|
|||
|
|
@ -84,7 +84,7 @@ Authelia has no local user store or admin UI in this configuration (LDAP backend
|
|||
| OIDC role for credential-broker | human | Group `net-kingdom-admins` **and** `aal2` receive the credential-broker policy | `credential-broker-keycape-client-secret-generate-oidc-role.json` | Established |
|
||||
| OIDC role for the fluid-telegram operators | human | Group `fluid-telegram-operators` and `aal2` | `fluid-telegram-hall-of-helix-oidc-role.json` | Established |
|
||||
| JWT role `secrets-engine-openbao` | workload | Subject `service:secrets-engine`, scope `openbao:login`, policy `secrets-engine-login-self`, five-minute token | `secrets-engine-jwt-role.json` | Established |
|
||||
| JWT role and AppRole for the coding agent | workload | Subject `service:codex:railiance-platform`; policies `agent-high-risk-boundary`, `workload-kv-read-issue-core-runtime`; fifteen minutes | `coding-agent-jwt-role.json`, `coding-agent-approle.json` | Established |
|
||||
| JWT role and AppRole for the coding agent | agent (filed as workload in the first draft) | Subject `service:codex:railiance-platform`; policies `agent-high-risk-boundary`, `workload-kv-read-issue-core-runtime`; fifteen minutes | `coding-agent-jwt-role.json`, `coding-agent-approle.json` | Established |
|
||||
| ESO roles, backup publisher and retention policies | workload | One policy per delivery, for example `net-kingdom-backup-publisher-eso` and `net-kingdom-backup-retention-eso` | `openbao/policies/` | Established as files; live roles **unknown** |
|
||||
| Root grant, unseal shares | break-glass | Final authority | `tools/custody-record`, custody docs | Holders **unknown**; see the crisis custody list |
|
||||
|
||||
|
|
@ -111,7 +111,11 @@ repositories. They are **unknown** here and each owner must supply a declaration
|
|||
credential-broker and fluid-telegram roles also require `aal2`. MFA is enforced earlier by
|
||||
KeyCape (`requireForAll: true`), so this may be intended, but the policy should say whether
|
||||
OpenBao must assert the assurance level itself. This is a question for T02, not a finding of a defect.
|
||||
6. **The backup contains the admin password** (the `lldap-secrets` Secret). That is by design
|
||||
6. **The coding agent presents as a service.** The OpenBao role for the coding agent binds `principal_type:
|
||||
service`, while IAM Profile v0.3 requires agent tokens to carry `principal_type: agent` and to name the
|
||||
agent's mode. This is finding F-1 in the standard (§8). The role's fifteen-minute token and its boundary
|
||||
policy already fit the intent of SI-14.
|
||||
7. **The backup contains the admin password** (the `lldap-secrets` Secret). That is by design
|
||||
for recovery, and it is another reason the human and service uses must be separated.
|
||||
|
||||
## What must be confirmed
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ NK-WP-0054-T03. Offline and read-only. Checks declarations against
|
|||
`canon/schemas/service-identity_v0.1.schema.json` and evaluates the rules of the
|
||||
proposed [Service Identity and Privilege standard](../../canon/standards/service-identity-privilege_v0.1.md)
|
||||
that a declaration alone can decide. It was built against draft-1, before the founder's review,
|
||||
so a rule change in the standard changes this tool.
|
||||
so a rule change in the standard changes this tool. It now follows draft-2.
|
||||
|
||||
```bash
|
||||
make service-identity-test # unit tests
|
||||
|
|
@ -33,15 +33,16 @@ and more than one break-glass identity.
|
|||
## Rules decided here
|
||||
|
||||
SI-1, SI-2, SI-3 (including a credential shared by more than one service), SI-6, SI-8 (including an
|
||||
admin policy without a bound assurance claim), SI-9, SI-11 (overdue legacy disposition), SI-12 and the
|
||||
naming rules (`NAMING`). SI-4 (one admin per service) is an advisory. SI-5, SI-7, SI-10 and SI-13 are
|
||||
admin policy without a bound assurance claim), SI-9, SI-11 (overdue legacy disposition), SI-12, SI-14
|
||||
(an agent must present as an agent, with a token lifetime at or below the proposed 3600-second ceiling) and
|
||||
the naming rules (`NAMING`). SI-4 (one admin per service) is an advisory. SI-5, SI-7, SI-10 and SI-13 are
|
||||
not decidable from a declaration. Observed state (what is really configured) is the health check,
|
||||
NK-WP-0054-T04.
|
||||
|
||||
## Declarations
|
||||
|
||||
`declarations/` holds the first set, authored from `docs/service-identity-inventory.md`. The location is
|
||||
provisional (standard section 10, question 6). Every one says `owner_confirmed: false`: they describe what
|
||||
`declarations/` holds the first set, authored from `docs/service-identity-inventory.md`. Decided 2026-10-24:
|
||||
declarations start here and move to each service's repository as its owner confirms them. Every one says `owner_confirmed: false`: they describe what
|
||||
the repository establishes, not what the owning service has attested. Facts the repository cannot
|
||||
establish are `unknown`, never guessed.
|
||||
|
||||
|
|
|
|||
|
|
@ -60,7 +60,8 @@ identities:
|
|||
rotation: {path: "Federated; the KeyCape client secret rotates in the keycape declaration", coupling: [keycape]}
|
||||
status: active
|
||||
- name: coding-agent
|
||||
classes: [workload]
|
||||
classes: [agent]
|
||||
agent: {mode: unknown, presents_principal_type: service, max_token_ttl_seconds: 900, boundary_policy: agent-high-risk-boundary}
|
||||
purpose: "JWT role and AppRole for the coding agent; policies agent-high-risk-boundary and workload-kv-read-issue-core-runtime, fifteen minutes"
|
||||
privilege_level: unknown
|
||||
interactive_login: false
|
||||
|
|
|
|||
|
|
@ -142,6 +142,38 @@ class RuleTests(unittest.TestCase):
|
|||
self.assertNotIn("SI-11", result_of(declaration(legacy))["rules"])
|
||||
|
||||
|
||||
class AgentTests(unittest.TestCase):
|
||||
def agent(self, **agent_over):
|
||||
agent = {"mode": "autonomous", "presents_principal_type": "agent", "max_token_ttl_seconds": 900,
|
||||
"boundary_policy": "agent-high-risk-boundary"}
|
||||
agent.update(agent_over)
|
||||
return identity(name="coding-agent", classes=["agent"], agent=agent, privilege_level="read",
|
||||
custody=["none"], consumers=[{"name": "agent-host", "kind": "service"}])
|
||||
|
||||
def test_agent_class_needs_the_agent_object(self):
|
||||
doc = declaration(identity(name="a", classes=["agent"], custody=["none"]))
|
||||
self.assertTrue(V.structural_errors(doc, SCHEMA))
|
||||
|
||||
def test_conformant_agent(self):
|
||||
self.assertEqual(result_of(declaration(self.agent()))["result"], "conformant")
|
||||
|
||||
def test_agent_presenting_as_a_service_breaks_si14(self):
|
||||
r = result_of(declaration(self.agent(presents_principal_type="service")))
|
||||
self.assertEqual((r["result"], r["rules"]), ("non-conformant", ["SI-14"]))
|
||||
|
||||
def test_agent_token_lifetime_above_the_ceiling_breaks_si14(self):
|
||||
self.assertIn("SI-14", result_of(declaration(self.agent(max_token_ttl_seconds=7200)))["rules"])
|
||||
|
||||
def test_unknown_agent_mode_is_unknown_not_a_pass(self):
|
||||
r = result_of(declaration(self.agent(mode="unknown")))
|
||||
self.assertEqual((r["result"], r["unknown"]), ("unknown", ["agent.mode"]))
|
||||
|
||||
def test_agent_must_not_live_in_a_persons_safe_or_serve_a_person(self):
|
||||
doc = declaration({**self.agent(), "custody": ["personal-safe"],
|
||||
"consumers": [{"name": "p", "kind": "person"}]})
|
||||
self.assertLessEqual({"SI-9", "SI-2"}, set(result_of(doc)["rules"]))
|
||||
|
||||
|
||||
class ResultTests(unittest.TestCase):
|
||||
def test_unknown_is_a_finding_for_every_class(self):
|
||||
for fact, value in (("privilege_level", "unknown"), ("custody", ["unknown"])):
|
||||
|
|
@ -192,6 +224,11 @@ class CheckedInDeclarations(unittest.TestCase):
|
|||
self.assertEqual(rejected, [])
|
||||
self.assertEqual({r["service"] for r in reports} >= {"lldap", "keycape", "privacyidea", "openbao", "platform"}, True)
|
||||
|
||||
def test_the_coding_agent_role_is_a_finding_against_the_iam_profile(self):
|
||||
reports, _ = V.run(self.paths(), NOW)
|
||||
agent = [i for r in reports for i in r["identities"] if i["identity"] == "coding-agent"][0]
|
||||
self.assertEqual((agent["result"], agent["rules"]), ("non-conformant", ["SI-14"]))
|
||||
|
||||
def test_the_shared_admin_credential_is_excepted_today_and_fails_after_expiry(self):
|
||||
def admin(as_of):
|
||||
reports, _ = V.run(self.paths(), as_of)
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ SECRET_VALUE = re.compile(
|
|||
|
||||
BIND_NAME = re.compile(r"^svc-[a-z0-9]+(-[a-z0-9]+)*$")
|
||||
ADMIN_NAME = re.compile(r"^[a-z]{2,6}-admin$")
|
||||
AGENT_MAX_TTL_SECONDS = 3600 # draft-2 proposal (standard section 10); the figure is not settled
|
||||
ORDER = {"non-conformant": 0, "unknown": 1, "excepted": 2, "planned": 3, "conformant": 4}
|
||||
|
||||
|
||||
|
|
@ -126,8 +127,20 @@ def _violations(identity: dict[str, Any]) -> tuple[set[str], list[str]]:
|
|||
broken.add("SI-8")
|
||||
if identity.get("grants_admin_policy") and not identity.get("assurance_claim_bound"):
|
||||
broken.add("SI-8")
|
||||
if ("bind" in classes or "workload" in classes) and "personal-safe" in custody:
|
||||
broken.add("SI-9") # a person's safe must not be the home of a service credential
|
||||
if ("bind" in classes or "workload" in classes or "agent" in classes) and "personal-safe" in custody:
|
||||
broken.add("SI-9") # a person's safe must not be the home of a service or agent credential
|
||||
if "agent" in classes:
|
||||
agent = identity["agent"]
|
||||
if agent["presents_principal_type"] == "unknown":
|
||||
unknown.append("agent.presents_principal_type")
|
||||
elif agent["presents_principal_type"] != "agent":
|
||||
broken.add("SI-14") # IAM Profile v0.3: agent tokens carry principal_type agent
|
||||
if agent["mode"] == "unknown":
|
||||
unknown.append("agent.mode")
|
||||
if agent["max_token_ttl_seconds"] > AGENT_MAX_TTL_SECONDS:
|
||||
broken.add("SI-14")
|
||||
if persons:
|
||||
broken.add("SI-2")
|
||||
if "break-glass" in classes:
|
||||
if "unknown" not in custody and custody != ["offline-key"]:
|
||||
broken.add("SI-6")
|
||||
|
|
|
|||
|
|
@ -79,7 +79,7 @@ assignee: claude-code
|
|||
needs_human: true
|
||||
decision_id: "a3582e79-4ece-4902-9bfa-e962aef2b8f2"
|
||||
depends_on: [NK-WP-0054-T01]
|
||||
blocking_reason: "Draft-1 delivered 2026-10-24. Await the founder review and answers to the six open questions in section 10."
|
||||
blocking_reason: "Draft-2 reflects the founder review of 2026-10-24 (four decisions). Await ratification and review by the service owners the inventory names."
|
||||
state_hub_task_id: "5830e2eb-334a-52fa-9c55-a8c00d89c09f"
|
||||
```
|
||||
|
||||
|
|
@ -93,7 +93,12 @@ Profile v0.3, Credential Management v0.2 and the Security Zones standard.
|
|||
|
||||
**Done when:** the draft is reviewed by the founder and the service owners it names.
|
||||
|
||||
Draft-1 delivered 2026-10-24: `canon/standards/service-identity-privilege_v0.1.md` (proposed). Thirteen
|
||||
Draft-2 (2026-10-24, after the founder's review): decisions applied are OpenBao `platform-admin` binds
|
||||
`aal2`; the human administrator keeps directory admin rights through an admin group; declarations start here
|
||||
and move to the services; agents get their own class and rule SI-14. Rehearsals still to do (not decisions):
|
||||
renaming LLDAP's built-in admin, the provisioner's narrowest scope, and whether a bind account can be kept
|
||||
from signing in interactively. Finding F-1: the coding-agent role in OpenBao presents as a service against
|
||||
IAM Profile v0.3. Draft-1 was delivered earlier the same day: `canon/standards/service-identity-privilege_v0.1.md` (proposed). Thirteen
|
||||
rules (SI-1 to SI-13), naming, a target shape for today's services, the declaration fields, the
|
||||
conformance result states, five initial exceptions (SI-X1 to SI-X5) and six open questions. It
|
||||
extends IAM Profile v0.3 and Credential Management v0.2 without changing either. Review is the
|
||||
|
|
@ -169,7 +174,9 @@ Create `svc-authelia`, `svc-keycape` and `svc-privacyidea` as read-only director
|
|||
narrowly scoped provisioner account that can create users and groups without being the
|
||||
directory super-admin (the inventory found these four consumers beside the human; the
|
||||
narrowest working privilege for the provisioner must be tested against the pinned LLDAP image
|
||||
first). Switch the Authelia, KeyCape and privacyIDEA resolver configurations and the
|
||||
first). Per the founder's decision the human `admin` keeps directory admin rights through an admin group
|
||||
on its own account; because today's `admin` is also LLDAP's built-in super-admin, the migration must state
|
||||
which of the two accounts keeps the name `admin` and rehearse it before any change. Switch the Authelia, KeyCape and privacyIDEA resolver configurations and the
|
||||
provisioner to them, make `admin`'s password
|
||||
human-only and rotate it, and remove the shared secret from the service Secrets. Update
|
||||
the backup profile, the restore adapters (`isolated-lldap.py`, `isolated-authelia.py`,
|
||||
|
|
@ -199,7 +206,8 @@ For each service decide whether a dedicated admin account exists and what it is
|
|||
LLDAP (`ld-admin`), privacyIDEA (`pi-admin`, existing), Authelia, KeyCape (client
|
||||
registry), OpenBao (the `platform-admin` role) and others. Record the result in the
|
||||
inventory and the standard. LLDAP's built-in admin name is configurable but renaming it
|
||||
on an existing directory is untested here, so it needs a rehearsal before any decision.
|
||||
on an existing directory is untested here, so it needs a rehearsal before any decision. Also rehearse
|
||||
whether a read-only bind account can be kept from signing in interactively (SI-3).
|
||||
|
||||
**Done when:** every service has a recorded disposition and a custody location.
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue