@openauditmodel/cli 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +215 -0
- package/README.md +647 -0
- package/dist/conformance/src/cli.d.ts +20 -0
- package/dist/conformance/src/cli.js +560 -0
- package/dist/conformance/src/cli.js.map +1 -0
- package/dist/conformance/src/format-errors.d.ts +20 -0
- package/dist/conformance/src/format-errors.js +116 -0
- package/dist/conformance/src/format-errors.js.map +1 -0
- package/dist/conformance/src/integrity/canonicalize.d.ts +25 -0
- package/dist/conformance/src/integrity/canonicalize.js +100 -0
- package/dist/conformance/src/integrity/canonicalize.js.map +1 -0
- package/dist/conformance/src/integrity/digest.d.ts +54 -0
- package/dist/conformance/src/integrity/digest.js +126 -0
- package/dist/conformance/src/integrity/digest.js.map +1 -0
- package/dist/conformance/src/integrity/types.d.ts +86 -0
- package/dist/conformance/src/integrity/types.js +36 -0
- package/dist/conformance/src/integrity/types.js.map +1 -0
- package/dist/conformance/src/integrity/verify-chain.d.ts +23 -0
- package/dist/conformance/src/integrity/verify-chain.js +235 -0
- package/dist/conformance/src/integrity/verify-chain.js.map +1 -0
- package/dist/conformance/src/integrity/verify-event.d.ts +29 -0
- package/dist/conformance/src/integrity/verify-event.js +140 -0
- package/dist/conformance/src/integrity/verify-event.js.map +1 -0
- package/dist/conformance/src/privacy/entropy.d.ts +14 -0
- package/dist/conformance/src/privacy/entropy.js +86 -0
- package/dist/conformance/src/privacy/entropy.js.map +1 -0
- package/dist/conformance/src/privacy/field-names.d.ts +36 -0
- package/dist/conformance/src/privacy/field-names.js +121 -0
- package/dist/conformance/src/privacy/field-names.js.map +1 -0
- package/dist/conformance/src/privacy/lint-event.d.ts +18 -0
- package/dist/conformance/src/privacy/lint-event.js +272 -0
- package/dist/conformance/src/privacy/lint-event.js.map +1 -0
- package/dist/conformance/src/privacy/rules.d.ts +44 -0
- package/dist/conformance/src/privacy/rules.js +161 -0
- package/dist/conformance/src/privacy/rules.js.map +1 -0
- package/dist/conformance/src/privacy/safe-formats.d.ts +32 -0
- package/dist/conformance/src/privacy/safe-formats.js +118 -0
- package/dist/conformance/src/privacy/safe-formats.js.map +1 -0
- package/dist/conformance/src/privacy/size-analysis.d.ts +36 -0
- package/dist/conformance/src/privacy/size-analysis.js +87 -0
- package/dist/conformance/src/privacy/size-analysis.js.map +1 -0
- package/dist/conformance/src/privacy/token-patterns.d.ts +24 -0
- package/dist/conformance/src/privacy/token-patterns.js +126 -0
- package/dist/conformance/src/privacy/token-patterns.js.map +1 -0
- package/dist/conformance/src/privacy/traverse.d.ts +33 -0
- package/dist/conformance/src/privacy/traverse.js +61 -0
- package/dist/conformance/src/privacy/traverse.js.map +1 -0
- package/dist/conformance/src/privacy/types.d.ts +58 -0
- package/dist/conformance/src/privacy/types.js +32 -0
- package/dist/conformance/src/privacy/types.js.map +1 -0
- package/dist/conformance/src/privacy/url-analysis.d.ts +40 -0
- package/dist/conformance/src/privacy/url-analysis.js +148 -0
- package/dist/conformance/src/privacy/url-analysis.js.map +1 -0
- package/dist/conformance/src/profiles/check-profile.d.ts +22 -0
- package/dist/conformance/src/profiles/check-profile.js +72 -0
- package/dist/conformance/src/profiles/check-profile.js.map +1 -0
- package/dist/conformance/src/profiles/evaluate-rule.d.ts +27 -0
- package/dist/conformance/src/profiles/evaluate-rule.js +87 -0
- package/dist/conformance/src/profiles/evaluate-rule.js.map +1 -0
- package/dist/conformance/src/profiles/load-profile.d.ts +30 -0
- package/dist/conformance/src/profiles/load-profile.js +95 -0
- package/dist/conformance/src/profiles/load-profile.js.map +1 -0
- package/dist/conformance/src/profiles/resolve-pointer.d.ts +44 -0
- package/dist/conformance/src/profiles/resolve-pointer.js +123 -0
- package/dist/conformance/src/profiles/resolve-pointer.js.map +1 -0
- package/dist/conformance/src/profiles/select-rules.d.ts +13 -0
- package/dist/conformance/src/profiles/select-rules.js +42 -0
- package/dist/conformance/src/profiles/select-rules.js.map +1 -0
- package/dist/conformance/src/profiles/types.d.ts +101 -0
- package/dist/conformance/src/profiles/types.js +31 -0
- package/dist/conformance/src/profiles/types.js.map +1 -0
- package/dist/conformance/src/profiles/validate-profile-definition.d.ts +12 -0
- package/dist/conformance/src/profiles/validate-profile-definition.js +47 -0
- package/dist/conformance/src/profiles/validate-profile-definition.js.map +1 -0
- package/dist/conformance/src/sources.d.ts +53 -0
- package/dist/conformance/src/sources.js +153 -0
- package/dist/conformance/src/sources.js.map +1 -0
- package/dist/conformance/src/validate-core.d.ts +30 -0
- package/dist/conformance/src/validate-core.js +62 -0
- package/dist/conformance/src/validate-core.js.map +1 -0
- package/dist/conformance/src/validate.d.ts +34 -0
- package/dist/conformance/src/validate.js +62 -0
- package/dist/conformance/src/validate.js.map +1 -0
- package/dist/conformance/src/validator-interface.d.ts +35 -0
- package/dist/conformance/src/validator-interface.js +19 -0
- package/dist/conformance/src/validator-interface.js.map +1 -0
- package/package.json +85 -0
- package/profiles/README.md +201 -0
- package/profiles/api-and-integration-management/README.md +271 -0
- package/profiles/api-and-integration-management/profile.json +258 -0
- package/profiles/backup-and-recovery/README.md +318 -0
- package/profiles/backup-and-recovery/profile.json +178 -0
- package/profiles/customer-and-account-management/README.md +340 -0
- package/profiles/customer-and-account-management/profile.json +237 -0
- package/profiles/deployment-and-change-management/README.md +359 -0
- package/profiles/deployment-and-change-management/profile.json +298 -0
- package/profiles/document-management/README.md +120 -0
- package/profiles/document-management/profile.json +170 -0
- package/profiles/financial-transaction-management/README.md +339 -0
- package/profiles/financial-transaction-management/profile.json +247 -0
- package/profiles/identity-and-access-management/README.md +112 -0
- package/profiles/identity-and-access-management/profile.json +120 -0
- package/profiles/incident-management/README.md +338 -0
- package/profiles/incident-management/profile.json +256 -0
- package/profiles/message-broker-management/README.md +344 -0
- package/profiles/message-broker-management/profile.json +399 -0
- package/profiles/profile-definition.schema.json +213 -0
- package/profiles/secrets-and-key-management/README.md +331 -0
- package/profiles/secrets-and-key-management/profile.json +219 -0
- package/schemas/v0.1/audit-event.schema.json +911 -0
- package/semantic-conventions/README.md +52 -0
- package/semantic-conventions/authentication.md +111 -0
- package/semantic-conventions/configuration-and-change.md +122 -0
- package/semantic-conventions/correlation-and-tracing.md +387 -0
- package/semantic-conventions/data-access.md +115 -0
- package/semantic-conventions/event-naming.md +114 -0
- package/semantic-conventions/identity-and-access.md +112 -0
- package/semantic-conventions/privileged-operations.md +110 -0
- package/semantic-conventions/workflow-and-approval.md +109 -0
- package/specification/actor-model.md +135 -0
- package/specification/approval-and-delegation.md +150 -0
- package/specification/authentication.md +116 -0
- package/specification/authorization.md +117 -0
- package/specification/change-model.md +134 -0
- package/specification/delivery.md +136 -0
- package/specification/design-principles.md +137 -0
- package/specification/event-model.md +387 -0
- package/specification/evidence-model.md +109 -0
- package/specification/extension-model.md +159 -0
- package/specification/integrity.md +314 -0
- package/specification/overview.md +172 -0
- package/specification/privacy.md +386 -0
- package/specification/resource-model.md +116 -0
- package/specification/terminology.md +135 -0
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
# Secrets and Key Management Profile
|
|
2
|
+
|
|
3
|
+
**Profile version: 0.1 · Core versions: 0.1 · Status: Experimental · Implemented, 14 rules (12
|
|
4
|
+
enforceable).**
|
|
5
|
+
|
|
6
|
+
Additional conformance requirements for the **custody** of secrets, cryptographic keys and
|
|
7
|
+
certificates: the operations a secret store, a key management service, a hardware custody module or a
|
|
8
|
+
certificate authority performs on the material it holds.
|
|
9
|
+
|
|
10
|
+
The enforceable rules are in [profile.json](profile.json).
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
auditmodel check-profile examples/profiles/secrets-and-key-management/valid \
|
|
14
|
+
--profile secrets-and-key-management
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Purpose
|
|
18
|
+
|
|
19
|
+
Custody operations are the ones an attacker performs after gaining a foothold and before doing
|
|
20
|
+
anything visible, and they are the ones an organization is least able to reconstruct afterwards —
|
|
21
|
+
because the evidence is material that was replaced, copied or destroyed. This profile requires the
|
|
22
|
+
handful of fields that make such an event reviewable months later: who decided it was allowed, how
|
|
23
|
+
sensitive the material was, what kind of material it was, who owns it, what changed, and why.
|
|
24
|
+
|
|
25
|
+
**The one thing this domain must never do is record the material itself.** An event records _that_ a
|
|
26
|
+
secret was rotated, revealed or exported. No rule in this profile requires a value, a hash of a
|
|
27
|
+
value, a key fingerprint or anything else from which material could be reconstructed or verified
|
|
28
|
+
against a guess, and no fixture contains one.
|
|
29
|
+
|
|
30
|
+
## Scope
|
|
31
|
+
|
|
32
|
+
Applications that hold material on behalf of other systems — secret stores and vaults, key management
|
|
33
|
+
services, hardware custody modules, certificate authorities and the credential-issuing parts of
|
|
34
|
+
platform services.
|
|
35
|
+
|
|
36
|
+
The profile is vendor-neutral. It describes operations that any custody system performs, not the
|
|
37
|
+
feature list of any product, and it assumes no particular storage backend, cryptographic provider,
|
|
38
|
+
approval workflow or deployment model.
|
|
39
|
+
|
|
40
|
+
## Event families
|
|
41
|
+
|
|
42
|
+
| Family | Events | Governed |
|
|
43
|
+
| ------------------------ | ------------------------------------------------------------------------------ | -------- |
|
|
44
|
+
| Secret lifecycle | `secret.create`, `.update`, `.rotate`, `.revoke`, `.delete` | yes |
|
|
45
|
+
| Secret value access | `secret.reveal`, `secret.export` | yes |
|
|
46
|
+
| Key lifecycle | `key.generate`, `.import`, `.rotate`, `.enable`, `.disable`, `.destroy` | yes |
|
|
47
|
+
| Key material export | `key.export` | yes |
|
|
48
|
+
| Certificate lifecycle | `certificate.issue`, `.renew`, `.revoke`, `.delete` | yes |
|
|
49
|
+
| Custody policy | `secret.policy.*`, `key.policy.*` | yes |
|
|
50
|
+
| Routine secret retrieval | `secret.retrieve`, `secret.cache-refresh` | **no** |
|
|
51
|
+
| Cryptographic data plane | `key.encrypt`, `key.decrypt`, `key.sign`, `key.verify`, `certificate.validate` | **no** |
|
|
52
|
+
|
|
53
|
+
Names follow [event-naming.md](../../semantic-conventions/event-naming.md): the two-segment form is
|
|
54
|
+
used where the domain _is_ the object acted on, and the three-segment form where a distinct object —
|
|
55
|
+
a policy — is acted on within the domain.
|
|
56
|
+
|
|
57
|
+
## Explicit exclusions
|
|
58
|
+
|
|
59
|
+
**Routine automated retrieval is not governed.** A workload that reads its own credential at start-up
|
|
60
|
+
and refreshes it on a timer produces the highest-volume event a custody system emits. Requiring an
|
|
61
|
+
authorization decision, a classification, a material type and a justification on each of them would
|
|
62
|
+
add cost to that event in exchange for very little review value, and the requirement would be
|
|
63
|
+
switched off rather than met.
|
|
64
|
+
|
|
65
|
+
**The cryptographic data plane is not governed either.** `key.encrypt`, `key.decrypt`, `key.sign` and
|
|
66
|
+
`key.verify` are operations _with_ a key, not operations _on_ a key. They run millions of times a day
|
|
67
|
+
and they do not change the custody state of anything.
|
|
68
|
+
|
|
69
|
+
The exclusion is structural, not a matter of discipline: **no selector in this profile uses a bare
|
|
70
|
+
`secret.`, `key.` or `certificate.` prefix**. Every governed name is either listed exactly or sits
|
|
71
|
+
under a `.policy.` prefix, so `secret.retrieve` and `key.decrypt` match no rule and `check-profile`
|
|
72
|
+
reports them as not applicable. A test asserts that, because widening one prefix later would silently
|
|
73
|
+
start governing every secret read in a deployment.
|
|
74
|
+
|
|
75
|
+
Excluded does not mean unaudited. Those are conforming OpenAuditModel events, and
|
|
76
|
+
[data-access.md](../../semantic-conventions/data-access.md) covers recording reads. Where retrieval
|
|
77
|
+
volume is high, [configuration-and-change.md](../../semantic-conventions/configuration-and-change.md)
|
|
78
|
+
§6 is the relevant guidance: recording grants, rotations and out-of-pattern access is usually worth
|
|
79
|
+
more than recording every read.
|
|
80
|
+
|
|
81
|
+
## Rules
|
|
82
|
+
|
|
83
|
+
| Rule | Applies to | Requires |
|
|
84
|
+
| ---------------------- | ----------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------- |
|
|
85
|
+
| `SECRET-CORE-001` | every governed event | `/authorization`, `/resource/classification` |
|
|
86
|
+
| `SECRET-CORE-002` | every governed event | `/metadata/secret/type` string |
|
|
87
|
+
| `SECRET-CORE-003` | every governed event | _recommends_ `/reason`, `/request/correlationId`, `/metadata/secret/provider` |
|
|
88
|
+
| `SECRET-LIFECYCLE-001` | `secret.create`, `key.generate`, `key.import`, `certificate.issue` | `/resource/ownerId` |
|
|
89
|
+
| `SECRET-ROTATE-001` | `secret.rotate`, `key.rotate`, `certificate.renew` | `/change`; recommends `/change/changedFields` |
|
|
90
|
+
| `SECRET-ACCESS-001` | `secret.reveal`, `secret.export`, `key.export` | `/authentication`, `/reason`; recommends `/approval` |
|
|
91
|
+
| `SECRET-ACCESS-002` | the same, **when** `/metadata/secret/emergencyAccess` is `true` | `/approval`, and `/authentication/mfa` equal to `true` |
|
|
92
|
+
| `SECRET-EXPORT-001` | `secret.export`, `key.export` | `/metadata/secret/destinationType` string |
|
|
93
|
+
| `SECRET-DESTROY-001` | `secret.revoke`, `secret.delete`, `key.disable`, `key.destroy`, `certificate.revoke`, `.delete` | `/reason`; recommends `/approval` |
|
|
94
|
+
| `SECRET-APPROVAL-001` | every governed event, **when** `/metadata/secret/approvalRequired` is `true` | `/approval` |
|
|
95
|
+
| `SECRET-POLICY-001` | `secret.policy.*`, `key.policy.*` | `/change`, `/reason`; recommends `/approval` |
|
|
96
|
+
| `SECRET-CERT-001` | `certificate.issue`, `certificate.renew` | `/metadata/secret/expiresAt` string; recommends `/metadata/secret/algorithm` |
|
|
97
|
+
| `SECRET-KEY-001` | `key.import` | `/reason`; recommends `/approval` |
|
|
98
|
+
| `SECRET-KEY-002` | `key.generate`, `key.import`, `key.rotate` | _recommends_ `/metadata/secret/algorithm`, `/metadata/secret/expiresAt` |
|
|
99
|
+
|
|
100
|
+
`SECRET-CORE-003` and `SECRET-KEY-002` are `warning` rules: they never fail conformance. Each rule's
|
|
101
|
+
full text and rationale is in [profile.json](profile.json).
|
|
102
|
+
|
|
103
|
+
## Metadata namespace
|
|
104
|
+
|
|
105
|
+
Every requirement this profile places on `metadata` sits under `/metadata/secret/`. The namespace
|
|
106
|
+
names the **control domain** — secrets and key management — not only password-shaped material, so a
|
|
107
|
+
key and a certificate event use it too.
|
|
108
|
+
|
|
109
|
+
| Field | Type | Status | Records |
|
|
110
|
+
| ----------------------------------- | ------- | --------------------------------------------- | --------------------------------------------- |
|
|
111
|
+
| `/metadata/secret/type` | string | REQUIRED on every governed event | The kind of protected material |
|
|
112
|
+
| `/metadata/secret/provider` | string | RECOMMENDED | The kind of custody system holding it |
|
|
113
|
+
| `/metadata/secret/algorithm` | string | RECOMMENDED for keys and certificates | The cryptographic algorithm |
|
|
114
|
+
| `/metadata/secret/expiresAt` | string | RECOMMENDED on keys; REQUIRED on certificates | When the material stops being valid |
|
|
115
|
+
| `/metadata/secret/destinationType` | string | REQUIRED on export | The kind of destination material was moved to |
|
|
116
|
+
| `/metadata/secret/approvalRequired` | boolean | Producer declaration | That local policy requires approval |
|
|
117
|
+
| `/metadata/secret/emergencyAccess` | boolean | Producer declaration | That this was break-glass access |
|
|
118
|
+
|
|
119
|
+
Illustrative values, all open vocabularies: `type` — `database-credential`, `service-credential`,
|
|
120
|
+
`signing-key`, `encryption-key`, `certificate`; `provider` — `software-vault`,
|
|
121
|
+
`hardware-security-module`, `managed-key-service`, `internal-certificate-authority`;
|
|
122
|
+
`destinationType` — `hardware-security-module`, `managed-key-service`, `offline-backup`,
|
|
123
|
+
`operator-console`, `external-party`.
|
|
124
|
+
|
|
125
|
+
`provider` and `destinationType` record the **kind** of system, not a product name and not an
|
|
126
|
+
address. A product name dates the trail and an address is frequently sensitive itself; neither is
|
|
127
|
+
comparable across deployments.
|
|
128
|
+
|
|
129
|
+
Namespacing keeps two profiles from assigning different meanings to the same key when one event is
|
|
130
|
+
governed by both. `expiresAt` on a share, on a service account and on a certificate are not the same
|
|
131
|
+
fact.
|
|
132
|
+
|
|
133
|
+
**A scalar must never be placed at `/metadata/secret` itself.** The container is a descriptor; a
|
|
134
|
+
string there would be read as the secret, and `auditmodel lint-privacy` reports it as one.
|
|
135
|
+
|
|
136
|
+
## Conditional policy fields
|
|
137
|
+
|
|
138
|
+
The rule language offers exactly one conditional mechanism: one path compared for equality against
|
|
139
|
+
one scalar. This profile spends it twice, and in both cases on a **producer declaration** rather than
|
|
140
|
+
on the profile's own guess.
|
|
141
|
+
|
|
142
|
+
- `/metadata/secret/approvalRequired` — `true` makes `/approval` required by `SECRET-APPROVAL-001`.
|
|
143
|
+
- `/metadata/secret/emergencyAccess` — `true` makes `/approval` and multi-factor authentication
|
|
144
|
+
required by `SECRET-ACCESS-002`.
|
|
145
|
+
|
|
146
|
+
Neither flag is itself required, and **an absent flag does not hold**: a rule whose condition path is
|
|
147
|
+
missing contributes nothing. The alternative — treating an absent flag as possibly true — would fail
|
|
148
|
+
every event that omitted a field the rule was never meant to govern. A recorded `false` is an answer,
|
|
149
|
+
not an absence, and the profile treats it as one.
|
|
150
|
+
|
|
151
|
+
## Approval model
|
|
152
|
+
|
|
153
|
+
**Approval is never required unconditionally**, and a test enforces that no rule requires `/approval`
|
|
154
|
+
without a `when` condition.
|
|
155
|
+
|
|
156
|
+
Requiring approval for every rotation would describe one organization's process and be ignored by
|
|
157
|
+
everyone else: automated rotation and unattended certificate renewal are the healthy paths in this
|
|
158
|
+
domain, they run thousands of times a day without a human anywhere near them, and a profile that
|
|
159
|
+
punished them would push deployments back towards long-lived material. So the profile **recommends**
|
|
160
|
+
approval where it is often appropriate — reveal, export, destruction, policy change — and
|
|
161
|
+
**requires** it in exactly two places: where the producer's own policy declared it necessary, and
|
|
162
|
+
where the producer declared the access to be break-glass.
|
|
163
|
+
|
|
164
|
+
Both rules require the `/approval` **object**, not an `approved` status. Emergencies are frequently
|
|
165
|
+
approved retrospectively, and `status: pending` on a completed operation is an accurate record of a
|
|
166
|
+
control bypass — precisely what a post-incident review looks for. Making that unrepresentable would
|
|
167
|
+
encourage producers to suppress it, which is the outcome the model exists to prevent. See
|
|
168
|
+
[configuration-and-change.md](../../semantic-conventions/configuration-and-change.md) §4.
|
|
169
|
+
|
|
170
|
+
## Privacy considerations
|
|
171
|
+
|
|
172
|
+
The following MUST NOT appear in any event governed by this profile, in any field, under any
|
|
173
|
+
property name:
|
|
174
|
+
|
|
175
|
+
1. Secret values, old or new, whole or partial.
|
|
176
|
+
2. Private or symmetric key material, wrapped or unwrapped.
|
|
177
|
+
3. Recovery phrases, key shares, unwrapping keys and passphrases.
|
|
178
|
+
4. Passwords, client secrets, tokens and API credentials.
|
|
179
|
+
5. Connection strings, in `metadata`, in `change.before`/`change.after` or anywhere else.
|
|
180
|
+
6. A hash or fingerprint of any of the above that would allow verification against a guess.
|
|
181
|
+
|
|
182
|
+
This is a requirement of the profile that **no rule in it can check**, and the profile says so rather
|
|
183
|
+
than approximating it with a rule that would be wrong. Checking values is
|
|
184
|
+
[`auditmodel lint-privacy`](../../specification/privacy.md), a separate and complementary command: a
|
|
185
|
+
profile says which fields must be present, the linter says which values must not. Every published
|
|
186
|
+
fixture here is required by test to pass both, and is additionally scanned field by field for a
|
|
187
|
+
scalar under any credential-shaped property name.
|
|
188
|
+
|
|
189
|
+
A rotation records `change.changedFields: ["secret", "version", "rotatedAt"]` and version identifiers
|
|
190
|
+
in `change.before` and `change.after`. The fact of the rotation is fully auditable; the material
|
|
191
|
+
appears nowhere. See [change-model.md](../../specification/change-model.md) §5.
|
|
192
|
+
|
|
193
|
+
No rule requires a personal identifier. `/resource/ownerId` is satisfied by an owning team or service
|
|
194
|
+
and needs no individual's name; `/metadata/secret/type` describes material, not people.
|
|
195
|
+
|
|
196
|
+
## Known rule-language limitations
|
|
197
|
+
|
|
198
|
+
These are requirements or checks the v0.1 rule language cannot express. They are stated rather than
|
|
199
|
+
approximated.
|
|
200
|
+
|
|
201
|
+
1. **No numeric comparison.** `SECRET-CERT-001` can require an expiry to be present and to be a
|
|
202
|
+
string. It cannot check that it is after the event time, that a certificate lifetime is below a
|
|
203
|
+
maximum, or that a rotation interval in a policy change did not get longer.
|
|
204
|
+
2. **No cross-field comparison.** The profile cannot require that `change.before` and `change.after`
|
|
205
|
+
differ, so a rotation that changed nothing and a rotation that worked are indistinguishable to it.
|
|
206
|
+
3. **No array-content predicates.** It cannot assert that `change.changedFields` names the member
|
|
207
|
+
that was replaced, that `approval.approvers` holds two distinct principals, or that
|
|
208
|
+
`receivedApprovals` reached `requiredApprovals`.
|
|
209
|
+
4. **No disjunction.** "either an expiry or a declared non-expiring justification" is unexpressible;
|
|
210
|
+
the profile requires the strictly weaker thing and documents the rest here.
|
|
211
|
+
5. **One condition per rule.** "emergency access to material classified `secret`" cannot be written;
|
|
212
|
+
`SECRET-ACCESS-002` fires on the emergency declaration alone.
|
|
213
|
+
6. **Semantics are unverifiable.** A checker can confirm `/resource/id` is present. It cannot confirm
|
|
214
|
+
that it names the material acted on, that the actor is the operator rather than the custody
|
|
215
|
+
service, or that `/metadata/secret/type` is truthful.
|
|
216
|
+
7. **Absence of material is unverifiable by a profile.** Rule evaluation is presence, JSON type and
|
|
217
|
+
scalar equality. Nothing in it could detect a secret placed in a field the profile requires.
|
|
218
|
+
|
|
219
|
+
## Cross-profile overlaps
|
|
220
|
+
|
|
221
|
+
Four families in this repository touch secrets, and they are deliberately separated by **whose control
|
|
222
|
+
domain the event belongs to**, not by what the material is called.
|
|
223
|
+
|
|
224
|
+
| Event | Owned by | Why |
|
|
225
|
+
| ---------------------------------------- | ------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |
|
|
226
|
+
| `identity.credential.rotate` | [identity-and-access-management](../identity-and-access-management/) (`IAM-CRED-001`) | The credential belongs to a **principal**. The reviewable fact is whose access it grants. |
|
|
227
|
+
| `configuration.secret.rotate`, `.access` | configuration and change conventions | The secret is an application **setting**. The reviewable fact is that a deployed configuration changed. |
|
|
228
|
+
| `api-key.*` | [api-and-integration-management](../api-and-integration-management/) | The credential is an **interface grant** to a caller. The reviewable fact is who may call what. |
|
|
229
|
+
| `secret.*`, `key.*`, `certificate.*` | **this profile** | The material is held **in custody**. The reviewable fact is what the custodian did with material it holds for others. |
|
|
230
|
+
|
|
231
|
+
This profile **does not govern** `identity.credential.rotate`, `configuration.secret.*` or
|
|
232
|
+
`api-key.*`, and duplicates none of their rules. `IAM-CRED-001` already requires authorization, a
|
|
233
|
+
justification and `/metadata/credential/type` for principal credentials; adding a second rule over
|
|
234
|
+
the same name would mean two profiles disagreeing about one event. The separation is also structural:
|
|
235
|
+
no selector here uses a `key.` prefix, so `api-key.create` could not match this profile even by
|
|
236
|
+
accident.
|
|
237
|
+
|
|
238
|
+
A deployment whose secret store _is_ its configuration system should pick one domain and use it
|
|
239
|
+
consistently rather than emitting both. Where an operation genuinely belongs to two domains — a
|
|
240
|
+
custody rotation that also rotates a service account's credential — emit the event of the system that
|
|
241
|
+
performed it and reference the other through `relatedResources` or `/request/correlationId`.
|
|
242
|
+
|
|
243
|
+
There is one further overlap outside the profiles: [integrity.md](../../specification/integrity.md)
|
|
244
|
+
§8 (10) states that key management — generation, storage, rotation, revocation, distribution and
|
|
245
|
+
custody — is outside the core specification. That remains true. This profile governs **audit events
|
|
246
|
+
about** key management; it defines no key management behaviour, no algorithm requirement and no trust
|
|
247
|
+
model.
|
|
248
|
+
|
|
249
|
+
## Fixtures
|
|
250
|
+
|
|
251
|
+
[examples/profiles/secrets-and-key-management/](../../examples/profiles/secrets-and-key-management/)
|
|
252
|
+
— twelve valid, twelve invalid, three not applicable.
|
|
253
|
+
|
|
254
|
+
| Fixture | Event | Rules exercised |
|
|
255
|
+
| ------------------------------ | ---------------------- | ------------------------------------------------------- |
|
|
256
|
+
| `secret-create.json` | `secret.create` | `CORE-001`, `CORE-002`, `LIFECYCLE-001` |
|
|
257
|
+
| `secret-rotate.json` | `secret.rotate` | `ROTATE-001`, unattended rotation with no justification |
|
|
258
|
+
| `secret-reveal.json` | `secret.reveal` | `ACCESS-001` |
|
|
259
|
+
| `secret-reveal-emergency.json` | `secret.reveal` | `ACCESS-002` with a retrospective approval |
|
|
260
|
+
| `secret-export.json` | `secret.export` | `ACCESS-001`, `EXPORT-001`, `APPROVAL-001` |
|
|
261
|
+
| `secret-revoke.json` | `secret.revoke` | `DESTROY-001` |
|
|
262
|
+
| `secret-policy-update.json` | `secret.policy.update` | `POLICY-001` |
|
|
263
|
+
| `key-generate.json` | `key.generate` | `LIFECYCLE-001`, `KEY-002` |
|
|
264
|
+
| `key-import.json` | `key.import` | `KEY-001`, `LIFECYCLE-001` |
|
|
265
|
+
| `key-destroy.json` | `key.destroy` | `DESTROY-001`, `APPROVAL-001` |
|
|
266
|
+
| `key-export.json` | `key.export` | `ACCESS-001`, `EXPORT-001` |
|
|
267
|
+
| `certificate-issue.json` | `certificate.issue` | `CERT-001`, `LIFECYCLE-001` |
|
|
268
|
+
|
|
269
|
+
Every enforceable rule has exactly one negative fixture, listed in the
|
|
270
|
+
[fixture README](../../examples/profiles/secrets-and-key-management/README.md). Each removes one
|
|
271
|
+
required value from a valid fixture and fails for one documented reason.
|
|
272
|
+
|
|
273
|
+
### Not-applicable rationale
|
|
274
|
+
|
|
275
|
+
`secret-retrieve.json`, `secret-cache-refresh.json` and `key-decrypt.json` are ordinary,
|
|
276
|
+
well-formed audit events that this profile deliberately does not govern: a workload reading its own
|
|
277
|
+
credential at start-up, the same workload refreshing its cached copy, and a service decrypting a
|
|
278
|
+
record with a key it is permitted to use. `check-profile` reports each as not applicable, with exit
|
|
279
|
+
code 3.
|
|
280
|
+
|
|
281
|
+
They exist to hold the exclusion in place. If a future edit widened a selector to a bare `secret.` or
|
|
282
|
+
`key.` prefix, these fixtures would start conforming instead of being skipped and the test would fail
|
|
283
|
+
— which is the point, because that edit would silently impose an authorization decision, a
|
|
284
|
+
classification and a material type on every secret read and every cryptographic operation in a
|
|
285
|
+
production system.
|
|
286
|
+
|
|
287
|
+
## Not required, and why
|
|
288
|
+
|
|
289
|
+
- **Trace identifiers.** `/request/traceId` presupposes a tracing discipline a conforming producer
|
|
290
|
+
may not have. `/request/correlationId` is recommended, never required, including for rotation.
|
|
291
|
+
- **A justification for scheduled rotation.** Automated rotation is the behaviour the profile wants
|
|
292
|
+
to encourage; a required `/reason` would make the safest path the most expensive one.
|
|
293
|
+
- **`/request/protocol`, `/request/ipAddress`, `/request/userAgent`.** Real signals for interactive
|
|
294
|
+
access, but they describe one access mechanism. A custody operation performed by a scheduler has
|
|
295
|
+
none of them.
|
|
296
|
+
- **Key fingerprints and material hashes.** They are exactly what an attacker needs to confirm a
|
|
297
|
+
guess. `/resource/id` identifies the material without describing it.
|
|
298
|
+
- **Quorum, key shares and split knowledge.** Genuine controls in high-assurance custody, but the
|
|
299
|
+
rule language cannot inspect `approval.approvers`, and requiring the object without being able to
|
|
300
|
+
check its contents would suggest a guarantee the tool does not give.
|
|
301
|
+
- **A `secret.approval.*` event family.** Approval is already modelled by `workflow.approval.*` in
|
|
302
|
+
[workflow-and-approval.md](../../semantic-conventions/workflow-and-approval.md) and by the core
|
|
303
|
+
`/approval` object.
|
|
304
|
+
|
|
305
|
+
## Open questions
|
|
306
|
+
|
|
307
|
+
- Is a single `/metadata/secret/type` enough, or do keys need a separate usage field — signing,
|
|
308
|
+
encryption, key-wrapping — that a rotation cannot change? The profile currently folds usage into
|
|
309
|
+
the type vocabulary.
|
|
310
|
+
- Should `secret.reveal` by a service actor be treated differently from a reveal by a human? The
|
|
311
|
+
profile requires `/authentication` for both and lets `actor.type` carry the distinction, because
|
|
312
|
+
automated reveal is real in migration tooling and rejecting it would push producers to mislabel it.
|
|
313
|
+
- Should `key.export` require an approval unconditionally? It is the operation with the largest
|
|
314
|
+
irreversible consequence in the profile, but disaster-recovery replication performs it on a
|
|
315
|
+
schedule in deployments that are not doing anything wrong.
|
|
316
|
+
- Is `/metadata/secret/destinationType` the right granularity, or is an external-versus-internal
|
|
317
|
+
boolean the fact reviewers actually filter on, as the document profile's `recipientType` suggests?
|
|
318
|
+
|
|
319
|
+
## Compatibility
|
|
320
|
+
|
|
321
|
+
The profile version is independent of the core specification version. `coreVersions` declares which
|
|
322
|
+
core versions the profile applies to; an event declaring any other `specVersion` is **not applicable**
|
|
323
|
+
rather than in violation. Adding a rule is a breaking change for producers, in the same sense as
|
|
324
|
+
adding a required core field.
|
|
325
|
+
|
|
326
|
+
## Not a compliance statement
|
|
327
|
+
|
|
328
|
+
Conformance to this profile means an event carries the fields this profile requires. It is not
|
|
329
|
+
compliance with any law, regulation, standard, certification scheme or contract, and MUST NOT be
|
|
330
|
+
presented as evidence of one. Nothing here asserts that any cryptographic algorithm, key length,
|
|
331
|
+
rotation interval or custody arrangement is adequate.
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
{
|
|
2
|
+
"profileVersion": "0.1",
|
|
3
|
+
"name": "secrets-and-key-management",
|
|
4
|
+
"version": "0.1",
|
|
5
|
+
"status": "experimental",
|
|
6
|
+
"coreVersions": ["0.1"],
|
|
7
|
+
"title": "Secrets and Key Management Profile",
|
|
8
|
+
"description": "Additional conformance requirements for the custody of secrets, cryptographic keys and certificates: creation, rotation, revocation, destruction, policy change, and the privileged reveal and export operations that turn protected material into copied material. Every requirement adds to the OpenAuditModel Core Specification; none relaxes it. Routine automated retrieval of a secret and ordinary cryptographic operations against a key are deliberately not governed. An audit event records that a secret operation occurred; it never records the secret.",
|
|
9
|
+
"rules": [
|
|
10
|
+
{
|
|
11
|
+
"id": "SECRET-CORE-001",
|
|
12
|
+
"description": "Every governed secret, key or certificate operation records the authorization decision that permitted it and the classification of the material it acted on.",
|
|
13
|
+
"rationale": "Custody operations are the ones an attacker performs after gaining a foothold and before doing anything visible, so an operation with no recorded decision cannot be separated from one that bypassed policy. Classification is what makes the record triageable: a key protecting settlement records and a key in a sandbox produce identical events otherwise, and a reviewer who cannot tell them apart must treat every rotation as an incident or none of them.",
|
|
14
|
+
"severity": "error",
|
|
15
|
+
"events": [
|
|
16
|
+
"secret.create",
|
|
17
|
+
"secret.update",
|
|
18
|
+
"secret.rotate",
|
|
19
|
+
"secret.revoke",
|
|
20
|
+
"secret.delete",
|
|
21
|
+
"secret.reveal",
|
|
22
|
+
"secret.export",
|
|
23
|
+
"key.generate",
|
|
24
|
+
"key.import",
|
|
25
|
+
"key.rotate",
|
|
26
|
+
"key.enable",
|
|
27
|
+
"key.disable",
|
|
28
|
+
"key.destroy",
|
|
29
|
+
"key.export",
|
|
30
|
+
"certificate.issue",
|
|
31
|
+
"certificate.renew",
|
|
32
|
+
"certificate.revoke",
|
|
33
|
+
"certificate.delete"
|
|
34
|
+
],
|
|
35
|
+
"eventPrefixes": ["secret.policy.", "key.policy."],
|
|
36
|
+
"requiredPaths": ["/authorization", "/resource/classification"]
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"id": "SECRET-CORE-002",
|
|
40
|
+
"description": "Every governed operation records the kind of protected material it acted on.",
|
|
41
|
+
"rationale": "The kind of material is the blast radius. Replacing a read-only reporting credential and replacing the key that signs every payment instruction are the same event shape and entirely different incidents, and no external reader can recover the difference from an opaque identifier once the material is gone. The type is recorded rather than inferred because only the custodian knows it, and it is known at exactly the moment the event is emitted.",
|
|
42
|
+
"severity": "error",
|
|
43
|
+
"events": [
|
|
44
|
+
"secret.create",
|
|
45
|
+
"secret.update",
|
|
46
|
+
"secret.rotate",
|
|
47
|
+
"secret.revoke",
|
|
48
|
+
"secret.delete",
|
|
49
|
+
"secret.reveal",
|
|
50
|
+
"secret.export",
|
|
51
|
+
"key.generate",
|
|
52
|
+
"key.import",
|
|
53
|
+
"key.rotate",
|
|
54
|
+
"key.enable",
|
|
55
|
+
"key.disable",
|
|
56
|
+
"key.destroy",
|
|
57
|
+
"key.export",
|
|
58
|
+
"certificate.issue",
|
|
59
|
+
"certificate.renew",
|
|
60
|
+
"certificate.revoke",
|
|
61
|
+
"certificate.delete"
|
|
62
|
+
],
|
|
63
|
+
"eventPrefixes": ["secret.policy.", "key.policy."],
|
|
64
|
+
"requiredMetadata": [{ "path": "/secret/type", "type": "string" }]
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
"id": "SECRET-CORE-003",
|
|
68
|
+
"description": "A governed operation should record why it happened, which custody system holds the material, and how it correlates with the wider operation.",
|
|
69
|
+
"rationale": "These are the fields a reviewer reaches for first and a producer omits most often. They are recommended rather than required because a scheduled rotation has no justification beyond the policy that scheduled it, because a deployment may have exactly one custody system and nothing to distinguish, and because correlation identifiers presuppose a tracing or workflow discipline that a conforming producer may not have. A missing answer here should prompt a question, not fail a build.",
|
|
70
|
+
"severity": "warning",
|
|
71
|
+
"events": [
|
|
72
|
+
"secret.create",
|
|
73
|
+
"secret.update",
|
|
74
|
+
"secret.rotate",
|
|
75
|
+
"secret.revoke",
|
|
76
|
+
"secret.delete",
|
|
77
|
+
"secret.reveal",
|
|
78
|
+
"secret.export",
|
|
79
|
+
"key.generate",
|
|
80
|
+
"key.import",
|
|
81
|
+
"key.rotate",
|
|
82
|
+
"key.enable",
|
|
83
|
+
"key.disable",
|
|
84
|
+
"key.destroy",
|
|
85
|
+
"key.export",
|
|
86
|
+
"certificate.issue",
|
|
87
|
+
"certificate.renew",
|
|
88
|
+
"certificate.revoke",
|
|
89
|
+
"certificate.delete"
|
|
90
|
+
],
|
|
91
|
+
"eventPrefixes": ["secret.policy.", "key.policy."],
|
|
92
|
+
"recommendedPaths": ["/reason", "/request/correlationId", "/metadata/secret/provider"]
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
"id": "SECRET-LIFECYCLE-001",
|
|
96
|
+
"description": "Material brought into custody records who owns it.",
|
|
97
|
+
"rationale": "An unowned secret cannot be rotated and cannot be retired, because nobody knows which systems break when it changes. Ownership is knowable at exactly one moment — when the material is created, generated, imported or issued — and every later attempt to reconstruct it is archaeology across deployment manifests. Recording an owning team or service is not personal data and does not require naming an individual.",
|
|
98
|
+
"severity": "error",
|
|
99
|
+
"events": ["secret.create", "key.generate", "key.import", "certificate.issue"],
|
|
100
|
+
"requiredPaths": ["/resource/ownerId"]
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
"id": "SECRET-ROTATE-001",
|
|
104
|
+
"description": "Rotation records the transition it performed, naming what changed rather than what the material now is.",
|
|
105
|
+
"rationale": "Rotation is the healthy operation in this domain and the one most often recorded as a bare fact. Without `/change` the trail says something was replaced and nothing about which version succeeded which, so a consumer failing on a stale value cannot be tied to the rotation that caused it and a rotation that silently did nothing is indistinguishable from one that worked. `/change` is the core structure designed to carry that transition without carrying a value: `changedFields` names members, and `before` and `after` carry version identifiers, never key or secret material.",
|
|
106
|
+
"severity": "error",
|
|
107
|
+
"events": ["secret.rotate", "key.rotate", "certificate.renew"],
|
|
108
|
+
"requiredPaths": ["/change"],
|
|
109
|
+
"recommendedPaths": ["/change/changedFields"]
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
"id": "SECRET-ACCESS-001",
|
|
113
|
+
"description": "Revealing a stored value or exporting material records how the principal authenticated and why the access was needed.",
|
|
114
|
+
"rationale": "Reveal and export are the two operations that convert protected material into copied material: after them the custodian is no longer the only holder and no later control can undo that. The authentication context is what distinguishes a reviewed operator session from a replayed one, and the justification is the only thing a reviewer has months later, when the actor has changed teams and the copy is still valid. Recording the fact of the access never requires recording any part of the value.",
|
|
115
|
+
"severity": "error",
|
|
116
|
+
"events": ["secret.reveal", "secret.export", "key.export"],
|
|
117
|
+
"requiredPaths": ["/authentication", "/reason"],
|
|
118
|
+
"recommendedPaths": ["/approval"]
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
"id": "SECRET-ACCESS-002",
|
|
122
|
+
"description": "Access the producer has declared to be emergency or break-glass access is approved and performed from a multi-factor authenticated session.",
|
|
123
|
+
"rationale": "Break-glass access exists precisely to bypass the controls that normally apply, so it is the one path where the audit record is the only remaining control. `/approval` is required as a present object rather than as an approved decision, because emergencies are frequently approved retrospectively and the model can say so — `status: pending` on a successful access is an accurate record of a control bypass and exactly what a post-incident review looks for. Suppressing it would be the failure this rule exists to prevent. The rule fires only on the producer's own declaration, so the profile never has to guess what counts as an emergency in a given deployment.",
|
|
124
|
+
"severity": "error",
|
|
125
|
+
"events": ["secret.reveal", "secret.export", "key.export"],
|
|
126
|
+
"when": { "path": "/metadata/secret/emergencyAccess", "equals": true },
|
|
127
|
+
"requiredPaths": ["/approval"],
|
|
128
|
+
"requiredValues": [{ "path": "/authentication/mfa", "equals": true }]
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
"id": "SECRET-EXPORT-001",
|
|
132
|
+
"description": "An export records the kind of destination the material was moved to.",
|
|
133
|
+
"rationale": "Where exported material went is the single fact that determines the risk of the operation, and it is the fact least likely to survive in anyone's memory. Material copied into a second hardware custodian, written to offline backup media, displayed on an operator console and handed to an external party are four different exposures that produce identical events without this field. The profile requires the kind of destination rather than an address, because an address is often itself sensitive and is never comparable across deployments.",
|
|
134
|
+
"severity": "error",
|
|
135
|
+
"events": ["secret.export", "key.export"],
|
|
136
|
+
"requiredMetadata": [{ "path": "/secret/destinationType", "type": "string" }]
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
"id": "SECRET-DESTROY-001",
|
|
140
|
+
"description": "Revoking, disabling, destroying or deleting protected material is justified.",
|
|
141
|
+
"rationale": "These are the operations an audit trail is least able to reconstruct afterwards, because the evidence is what was removed. Destroying an encryption key destroys the readability of everything it protected, and revoking a certificate can take a service offline as effectively as an outage — so the difference between a planned decommissioning, an incident response and a mistake has to be stated at the time, by the only party who knows it. Approval is recommended rather than required because many deployments legitimately let a team retire its own test material, and a rule that forbade that would be switched off rather than met.",
|
|
142
|
+
"severity": "error",
|
|
143
|
+
"events": [
|
|
144
|
+
"secret.revoke",
|
|
145
|
+
"secret.delete",
|
|
146
|
+
"key.disable",
|
|
147
|
+
"key.destroy",
|
|
148
|
+
"certificate.revoke",
|
|
149
|
+
"certificate.delete"
|
|
150
|
+
],
|
|
151
|
+
"requiredPaths": ["/reason"],
|
|
152
|
+
"recommendedPaths": ["/approval"]
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
"id": "SECRET-APPROVAL-001",
|
|
156
|
+
"description": "An operation the producer has declared to require approval records the approval.",
|
|
157
|
+
"rationale": "Approval requirements in this domain are set locally: one organization requires two signatures to destroy a key and another rotates certificates unattended thousands of times a day, and a profile that imposed either model on the other would describe one deployment and be ignored by the rest. So the producer declares the requirement and the profile enforces the consequence — an operation that its own policy says needs approval, and that carries none, is the control gap this rule makes visible. The rule requires the approval object, not an approved status, so that a rejected or still-pending approval on a completed operation is recordable rather than unrepresentable.",
|
|
158
|
+
"severity": "error",
|
|
159
|
+
"events": [
|
|
160
|
+
"secret.create",
|
|
161
|
+
"secret.update",
|
|
162
|
+
"secret.rotate",
|
|
163
|
+
"secret.revoke",
|
|
164
|
+
"secret.delete",
|
|
165
|
+
"secret.reveal",
|
|
166
|
+
"secret.export",
|
|
167
|
+
"key.generate",
|
|
168
|
+
"key.import",
|
|
169
|
+
"key.rotate",
|
|
170
|
+
"key.enable",
|
|
171
|
+
"key.disable",
|
|
172
|
+
"key.destroy",
|
|
173
|
+
"key.export",
|
|
174
|
+
"certificate.issue",
|
|
175
|
+
"certificate.renew",
|
|
176
|
+
"certificate.revoke",
|
|
177
|
+
"certificate.delete"
|
|
178
|
+
],
|
|
179
|
+
"eventPrefixes": ["secret.policy.", "key.policy."],
|
|
180
|
+
"when": { "path": "/metadata/secret/approvalRequired", "equals": true },
|
|
181
|
+
"requiredPaths": ["/approval"]
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
"id": "SECRET-POLICY-001",
|
|
185
|
+
"description": "A change to a secret or key policy records the transition and why it was made.",
|
|
186
|
+
"rationale": "A policy change is where the controls over every piece of material in scope are switched off at once, and it usually leaves no trace anywhere else: lengthening a rotation interval, removing an approval requirement or widening who may reveal a value weakens thousands of secrets in a single event that looks like routine configuration. Recording the before and after state is what turns that event into evidence, and recording the reason is what separates a deliberate policy decision from an accident nobody noticed for a year.",
|
|
187
|
+
"severity": "error",
|
|
188
|
+
"eventPrefixes": ["secret.policy.", "key.policy."],
|
|
189
|
+
"requiredPaths": ["/change", "/reason"],
|
|
190
|
+
"recommendedPaths": ["/approval"]
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
"id": "SECRET-CERT-001",
|
|
194
|
+
"description": "Issuing or renewing a certificate records when it stops being valid.",
|
|
195
|
+
"rationale": "A certificate always has an end of validity, it is fixed at the moment of issue, and it is the fact that causes the outage when nobody wrote it down. This profile recommends an expiry everywhere else and requires it here for that reason: expiry is optional for material that may legitimately be indefinite, and mandatory for material that cannot be. The rule constrains presence and JSON type only; the v0.1 rule language cannot compare an expiry against the event time or against a maximum lifetime.",
|
|
196
|
+
"severity": "error",
|
|
197
|
+
"events": ["certificate.issue", "certificate.renew"],
|
|
198
|
+
"requiredMetadata": [{ "path": "/secret/expiresAt", "type": "string" }],
|
|
199
|
+
"recommendedPaths": ["/metadata/secret/algorithm"]
|
|
200
|
+
},
|
|
201
|
+
{
|
|
202
|
+
"id": "SECRET-KEY-001",
|
|
203
|
+
"description": "Importing key material generated outside the custody system is justified.",
|
|
204
|
+
"rationale": "Imported material existed somewhere else before the import, under procedures the custodian did not run and cannot attest to, and it may still exist there afterwards. That is the one property of a key that a custody system cannot establish for itself, so the reason for accepting external material has to be recorded by the party that decided to accept it. Generation inside the custodian carries no such requirement, which is deliberate: the profile should make the safer path the cheaper one.",
|
|
205
|
+
"severity": "error",
|
|
206
|
+
"events": ["key.import"],
|
|
207
|
+
"requiredPaths": ["/reason"],
|
|
208
|
+
"recommendedPaths": ["/approval"]
|
|
209
|
+
},
|
|
210
|
+
{
|
|
211
|
+
"id": "SECRET-KEY-002",
|
|
212
|
+
"description": "Key lifecycle events should record the algorithm the key uses and when it stops being valid.",
|
|
213
|
+
"rationale": "Algorithm and expiry are what make a key inventory answerable — which keys use a deprecated algorithm, which expire this quarter — and neither can be reconstructed after the key is destroyed. They are recommended rather than required because a custodian may legitimately treat the algorithm as an internal detail of a managed key service it does not control, and because a key held for as long as the data it protects has no expiry to record.",
|
|
214
|
+
"severity": "warning",
|
|
215
|
+
"events": ["key.generate", "key.import", "key.rotate"],
|
|
216
|
+
"recommendedPaths": ["/metadata/secret/algorithm", "/metadata/secret/expiresAt"]
|
|
217
|
+
}
|
|
218
|
+
]
|
|
219
|
+
}
|