@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,399 @@
|
|
|
1
|
+
{
|
|
2
|
+
"profileVersion": "0.1",
|
|
3
|
+
"name": "message-broker-management",
|
|
4
|
+
"version": "0.1",
|
|
5
|
+
"status": "experimental",
|
|
6
|
+
"coreVersions": ["0.1"],
|
|
7
|
+
"title": "Message Broker Management Profile",
|
|
8
|
+
"description": "Additional conformance requirements for message broker control-plane audit events: cluster, topic, queue, exchange, stream and consumer-group administration, access control lists, quotas, broker configuration, offset resets and message replay. Every requirement adds to the OpenAuditModel Core Specification; none relaxes it. Data-plane traffic — publishing, consuming, acknowledging and automatic rebalancing — is deliberately not governed, and message payloads are never recorded.",
|
|
9
|
+
"rules": [
|
|
10
|
+
{
|
|
11
|
+
"id": "BROKER-CORE-001",
|
|
12
|
+
"description": "Every governed control-plane operation records the authorization decision that permitted it, the broker family it was performed against and the broker deployment it acted on.",
|
|
13
|
+
"rationale": "A broker administration event without a recorded decision cannot be reviewed: nothing distinguishes an operation policy allowed from one performed with a standing cluster-admin credential that policy never saw. The broker family is what makes every other field interpretable, because the same word means different things across families — an offset is a durable log position on one broker and has no meaning at all on a work queue, and an ACL is a per-resource rule on one and a virtual-host regular expression on another. The deployment identifier is what makes the trail searchable: fleets run dozens of clusters holding identically named topics, and `application.environment` alone cannot tell a reviewer which of them lost its data.",
|
|
14
|
+
"severity": "error",
|
|
15
|
+
"events": [
|
|
16
|
+
"broker.acl.grant",
|
|
17
|
+
"broker.acl.revoke",
|
|
18
|
+
"broker.cluster.create",
|
|
19
|
+
"broker.cluster.delete",
|
|
20
|
+
"broker.cluster.failover",
|
|
21
|
+
"broker.cluster.scale",
|
|
22
|
+
"broker.cluster.upgrade",
|
|
23
|
+
"broker.configuration.update",
|
|
24
|
+
"broker.consumer-group.create",
|
|
25
|
+
"broker.consumer-group.delete",
|
|
26
|
+
"broker.consumer-group.update",
|
|
27
|
+
"broker.exchange.create",
|
|
28
|
+
"broker.exchange.delete",
|
|
29
|
+
"broker.exchange.update",
|
|
30
|
+
"broker.message.replay",
|
|
31
|
+
"broker.offset.reset",
|
|
32
|
+
"broker.permission.grant",
|
|
33
|
+
"broker.permission.revoke",
|
|
34
|
+
"broker.queue.create",
|
|
35
|
+
"broker.queue.delete",
|
|
36
|
+
"broker.queue.purge",
|
|
37
|
+
"broker.queue.update",
|
|
38
|
+
"broker.quota.create",
|
|
39
|
+
"broker.quota.delete",
|
|
40
|
+
"broker.quota.update",
|
|
41
|
+
"broker.stream.create",
|
|
42
|
+
"broker.stream.delete",
|
|
43
|
+
"broker.stream.trim",
|
|
44
|
+
"broker.stream.update",
|
|
45
|
+
"broker.topic.create",
|
|
46
|
+
"broker.topic.delete",
|
|
47
|
+
"broker.topic.update"
|
|
48
|
+
],
|
|
49
|
+
"requiredPaths": ["/authorization"],
|
|
50
|
+
"requiredMetadata": [
|
|
51
|
+
{
|
|
52
|
+
"path": "/broker/system",
|
|
53
|
+
"type": "string"
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
"path": "/broker/clusterId",
|
|
57
|
+
"type": "string"
|
|
58
|
+
}
|
|
59
|
+
]
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
"id": "BROKER-CORE-002",
|
|
63
|
+
"description": "A governed control-plane operation should record why it happened, how the operator was authenticated, which container the affected resource lives in, and how it correlates with the wider administrative workflow.",
|
|
64
|
+
"rationale": "These are the fields a reviewer reaches for first and an automated control plane omits most often. They are recommended rather than required because a conforming producer may legitimately have none of them: a broker managed entirely by a reconciliation loop has no interactive authentication, a cluster-level event has no containing resource, and a single-step change has nothing to correlate with. A missing justification should prompt a question, not fail a build.",
|
|
65
|
+
"severity": "warning",
|
|
66
|
+
"events": [
|
|
67
|
+
"broker.acl.grant",
|
|
68
|
+
"broker.acl.revoke",
|
|
69
|
+
"broker.cluster.create",
|
|
70
|
+
"broker.cluster.delete",
|
|
71
|
+
"broker.cluster.failover",
|
|
72
|
+
"broker.cluster.scale",
|
|
73
|
+
"broker.cluster.upgrade",
|
|
74
|
+
"broker.configuration.update",
|
|
75
|
+
"broker.consumer-group.create",
|
|
76
|
+
"broker.consumer-group.delete",
|
|
77
|
+
"broker.consumer-group.update",
|
|
78
|
+
"broker.exchange.create",
|
|
79
|
+
"broker.exchange.delete",
|
|
80
|
+
"broker.exchange.update",
|
|
81
|
+
"broker.message.replay",
|
|
82
|
+
"broker.offset.reset",
|
|
83
|
+
"broker.permission.grant",
|
|
84
|
+
"broker.permission.revoke",
|
|
85
|
+
"broker.queue.create",
|
|
86
|
+
"broker.queue.delete",
|
|
87
|
+
"broker.queue.purge",
|
|
88
|
+
"broker.queue.update",
|
|
89
|
+
"broker.quota.create",
|
|
90
|
+
"broker.quota.delete",
|
|
91
|
+
"broker.quota.update",
|
|
92
|
+
"broker.stream.create",
|
|
93
|
+
"broker.stream.delete",
|
|
94
|
+
"broker.stream.trim",
|
|
95
|
+
"broker.stream.update",
|
|
96
|
+
"broker.topic.create",
|
|
97
|
+
"broker.topic.delete",
|
|
98
|
+
"broker.topic.update"
|
|
99
|
+
],
|
|
100
|
+
"recommendedPaths": [
|
|
101
|
+
"/reason",
|
|
102
|
+
"/authentication",
|
|
103
|
+
"/resource/parentId",
|
|
104
|
+
"/request/correlationId"
|
|
105
|
+
]
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
"id": "BROKER-RISK-001",
|
|
109
|
+
"description": "Every governed control-plane operation declares whether it discards data or withdraws access.",
|
|
110
|
+
"rationale": "A broker control plane exposes reversible and irreversible operations through the same interface, and the profile cannot tell them apart from an event name alone: whether a configuration change discards retained messages depends on the direction of the change, and whether a consumer-group update loses committed positions depends on the broker. Only the producer knows. Recording the answer — including `false`, which is an answer rather than an absence — is what lets a reviewer filter the small set of operations that cannot be undone out of a stream dominated by routine ones, and it is the flag the conditional rules in this profile depend on.",
|
|
111
|
+
"severity": "error",
|
|
112
|
+
"events": [
|
|
113
|
+
"broker.acl.grant",
|
|
114
|
+
"broker.acl.revoke",
|
|
115
|
+
"broker.cluster.create",
|
|
116
|
+
"broker.cluster.delete",
|
|
117
|
+
"broker.cluster.failover",
|
|
118
|
+
"broker.cluster.scale",
|
|
119
|
+
"broker.cluster.upgrade",
|
|
120
|
+
"broker.configuration.update",
|
|
121
|
+
"broker.consumer-group.create",
|
|
122
|
+
"broker.consumer-group.delete",
|
|
123
|
+
"broker.consumer-group.update",
|
|
124
|
+
"broker.exchange.create",
|
|
125
|
+
"broker.exchange.delete",
|
|
126
|
+
"broker.exchange.update",
|
|
127
|
+
"broker.message.replay",
|
|
128
|
+
"broker.offset.reset",
|
|
129
|
+
"broker.permission.grant",
|
|
130
|
+
"broker.permission.revoke",
|
|
131
|
+
"broker.queue.create",
|
|
132
|
+
"broker.queue.delete",
|
|
133
|
+
"broker.queue.purge",
|
|
134
|
+
"broker.queue.update",
|
|
135
|
+
"broker.quota.create",
|
|
136
|
+
"broker.quota.delete",
|
|
137
|
+
"broker.quota.update",
|
|
138
|
+
"broker.stream.create",
|
|
139
|
+
"broker.stream.delete",
|
|
140
|
+
"broker.stream.trim",
|
|
141
|
+
"broker.stream.update",
|
|
142
|
+
"broker.topic.create",
|
|
143
|
+
"broker.topic.delete",
|
|
144
|
+
"broker.topic.update"
|
|
145
|
+
],
|
|
146
|
+
"requiredMetadata": [
|
|
147
|
+
{
|
|
148
|
+
"path": "/broker/operation/destructive",
|
|
149
|
+
"type": "boolean"
|
|
150
|
+
}
|
|
151
|
+
]
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
"id": "BROKER-RISK-002",
|
|
155
|
+
"description": "An operation the producer declared destructive is justified, and should be approved.",
|
|
156
|
+
"rationale": "Purging a queue, deleting a topic or shortening a retention window destroys the very messages an investigation would later want, so the event is the only surviving evidence of why it was acceptable. These operations are also the ones most often performed under incident pressure, when the justification is clearest to the operator and least recoverable afterwards. Approval is recommended rather than required because a great many destructive broker operations are legitimately unilateral — clearing a development queue, deleting a topic the same team created an hour ago — and a rule that demanded a second signature for all of them would be switched off rather than met.",
|
|
157
|
+
"severity": "error",
|
|
158
|
+
"events": [
|
|
159
|
+
"broker.acl.grant",
|
|
160
|
+
"broker.acl.revoke",
|
|
161
|
+
"broker.cluster.create",
|
|
162
|
+
"broker.cluster.delete",
|
|
163
|
+
"broker.cluster.failover",
|
|
164
|
+
"broker.cluster.scale",
|
|
165
|
+
"broker.cluster.upgrade",
|
|
166
|
+
"broker.configuration.update",
|
|
167
|
+
"broker.consumer-group.create",
|
|
168
|
+
"broker.consumer-group.delete",
|
|
169
|
+
"broker.consumer-group.update",
|
|
170
|
+
"broker.exchange.create",
|
|
171
|
+
"broker.exchange.delete",
|
|
172
|
+
"broker.exchange.update",
|
|
173
|
+
"broker.message.replay",
|
|
174
|
+
"broker.offset.reset",
|
|
175
|
+
"broker.permission.grant",
|
|
176
|
+
"broker.permission.revoke",
|
|
177
|
+
"broker.queue.create",
|
|
178
|
+
"broker.queue.delete",
|
|
179
|
+
"broker.queue.purge",
|
|
180
|
+
"broker.queue.update",
|
|
181
|
+
"broker.quota.create",
|
|
182
|
+
"broker.quota.delete",
|
|
183
|
+
"broker.quota.update",
|
|
184
|
+
"broker.stream.create",
|
|
185
|
+
"broker.stream.delete",
|
|
186
|
+
"broker.stream.trim",
|
|
187
|
+
"broker.stream.update",
|
|
188
|
+
"broker.topic.create",
|
|
189
|
+
"broker.topic.delete",
|
|
190
|
+
"broker.topic.update"
|
|
191
|
+
],
|
|
192
|
+
"when": {
|
|
193
|
+
"path": "/metadata/broker/operation/destructive",
|
|
194
|
+
"equals": true
|
|
195
|
+
},
|
|
196
|
+
"requiredPaths": ["/reason"],
|
|
197
|
+
"recommendedPaths": ["/approval"]
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
"id": "BROKER-RISK-003",
|
|
201
|
+
"description": "An operation the producer declared to need approval records the approval it obtained or failed to obtain.",
|
|
202
|
+
"rationale": "Which broker operations require a second signature is an organizational decision that differs by cluster, environment and team, so the profile does not attempt to decide it: the producer declares that policy applied, and the profile enforces the consequence. Without the approval object an event asserts that a control existed and says nothing about whether it was satisfied, which is the least useful thing an audit trail can record. The rule requires the approval to be present, not to be granted, so that a rejected or expired approval remains recordable — a change blocked by a control is exactly the event a reviewer most wants to find.",
|
|
203
|
+
"severity": "error",
|
|
204
|
+
"events": [
|
|
205
|
+
"broker.acl.grant",
|
|
206
|
+
"broker.acl.revoke",
|
|
207
|
+
"broker.cluster.create",
|
|
208
|
+
"broker.cluster.delete",
|
|
209
|
+
"broker.cluster.failover",
|
|
210
|
+
"broker.cluster.scale",
|
|
211
|
+
"broker.cluster.upgrade",
|
|
212
|
+
"broker.configuration.update",
|
|
213
|
+
"broker.consumer-group.create",
|
|
214
|
+
"broker.consumer-group.delete",
|
|
215
|
+
"broker.consumer-group.update",
|
|
216
|
+
"broker.exchange.create",
|
|
217
|
+
"broker.exchange.delete",
|
|
218
|
+
"broker.exchange.update",
|
|
219
|
+
"broker.message.replay",
|
|
220
|
+
"broker.offset.reset",
|
|
221
|
+
"broker.permission.grant",
|
|
222
|
+
"broker.permission.revoke",
|
|
223
|
+
"broker.queue.create",
|
|
224
|
+
"broker.queue.delete",
|
|
225
|
+
"broker.queue.purge",
|
|
226
|
+
"broker.queue.update",
|
|
227
|
+
"broker.quota.create",
|
|
228
|
+
"broker.quota.delete",
|
|
229
|
+
"broker.quota.update",
|
|
230
|
+
"broker.stream.create",
|
|
231
|
+
"broker.stream.delete",
|
|
232
|
+
"broker.stream.trim",
|
|
233
|
+
"broker.stream.update",
|
|
234
|
+
"broker.topic.create",
|
|
235
|
+
"broker.topic.delete",
|
|
236
|
+
"broker.topic.update"
|
|
237
|
+
],
|
|
238
|
+
"when": {
|
|
239
|
+
"path": "/metadata/broker/approvalRequired",
|
|
240
|
+
"equals": true
|
|
241
|
+
},
|
|
242
|
+
"requiredPaths": ["/approval"]
|
|
243
|
+
},
|
|
244
|
+
{
|
|
245
|
+
"id": "BROKER-FAIL-001",
|
|
246
|
+
"description": "A failed control-plane operation records a coarse classification of the failure.",
|
|
247
|
+
"rationale": "Failed broker administration is where an outage and an intrusion look identical at first glance, and both arrive in bulk: a misconfigured operator retries, and a principal probing for permissions produces the same shape of traffic. The core requires a producer-defined failure code; the profile additionally requires the coarse class, because a reviewer triaging several hundred failures needs to separate authorization refusals from timeouts and validation errors without first learning every producer's code vocabulary. The classification is a small token and, unlike a message, cannot carry broker state or payload fragments.",
|
|
248
|
+
"severity": "error",
|
|
249
|
+
"events": [
|
|
250
|
+
"broker.acl.grant",
|
|
251
|
+
"broker.acl.revoke",
|
|
252
|
+
"broker.cluster.create",
|
|
253
|
+
"broker.cluster.delete",
|
|
254
|
+
"broker.cluster.failover",
|
|
255
|
+
"broker.cluster.scale",
|
|
256
|
+
"broker.cluster.upgrade",
|
|
257
|
+
"broker.configuration.update",
|
|
258
|
+
"broker.consumer-group.create",
|
|
259
|
+
"broker.consumer-group.delete",
|
|
260
|
+
"broker.consumer-group.update",
|
|
261
|
+
"broker.exchange.create",
|
|
262
|
+
"broker.exchange.delete",
|
|
263
|
+
"broker.exchange.update",
|
|
264
|
+
"broker.message.replay",
|
|
265
|
+
"broker.offset.reset",
|
|
266
|
+
"broker.permission.grant",
|
|
267
|
+
"broker.permission.revoke",
|
|
268
|
+
"broker.queue.create",
|
|
269
|
+
"broker.queue.delete",
|
|
270
|
+
"broker.queue.purge",
|
|
271
|
+
"broker.queue.update",
|
|
272
|
+
"broker.quota.create",
|
|
273
|
+
"broker.quota.delete",
|
|
274
|
+
"broker.quota.update",
|
|
275
|
+
"broker.stream.create",
|
|
276
|
+
"broker.stream.delete",
|
|
277
|
+
"broker.stream.trim",
|
|
278
|
+
"broker.stream.update",
|
|
279
|
+
"broker.topic.create",
|
|
280
|
+
"broker.topic.delete",
|
|
281
|
+
"broker.topic.update"
|
|
282
|
+
],
|
|
283
|
+
"when": {
|
|
284
|
+
"path": "/event/outcome",
|
|
285
|
+
"equals": "failure"
|
|
286
|
+
},
|
|
287
|
+
"requiredPaths": ["/event/error/type"],
|
|
288
|
+
"recommendedPaths": ["/event/error/retryable"]
|
|
289
|
+
},
|
|
290
|
+
{
|
|
291
|
+
"id": "BROKER-LIFECYCLE-001",
|
|
292
|
+
"description": "Creating a topic, queue, stream or exchange records the classification of the data it will carry, and should record who owns it.",
|
|
293
|
+
"rationale": "A broker resource is created once and lives for years, and the sensitivity of what flows through it is decided at that moment and almost never revisited. An unclassified topic becomes a data store nobody can triage: when it is later replayed, exported or exposed by a misconfigured ACL, the first question a responder asks — was this payment data or build telemetry — has no recorded answer, and reconstructing it means reading the messages, which is precisely what the audit trail exists to avoid. Ownership is recommended rather than required because a resource created by a platform pipeline may genuinely have no owning principal at creation time.",
|
|
294
|
+
"severity": "error",
|
|
295
|
+
"events": [
|
|
296
|
+
"broker.topic.create",
|
|
297
|
+
"broker.queue.create",
|
|
298
|
+
"broker.stream.create",
|
|
299
|
+
"broker.exchange.create"
|
|
300
|
+
],
|
|
301
|
+
"requiredPaths": ["/resource/classification"],
|
|
302
|
+
"recommendedPaths": ["/resource/ownerId"]
|
|
303
|
+
},
|
|
304
|
+
{
|
|
305
|
+
"id": "BROKER-CHANGE-001",
|
|
306
|
+
"description": "An operation that modifies broker configuration, access control, a quota or a consumer position records what it changed.",
|
|
307
|
+
"rationale": "These operations mutate state that was already there, so the event's value is entirely in the transition: an access-control change that records only that an ACL was written cannot answer whether a principal gained or lost the ability to read a topic, and a configuration change that records only the resulting value cannot show that retention was cut from thirty days to one. The core deliberately accepts changed field names, a sanitized subset, a hash or a reference in place of full before and after states, so this requirement can be met without copying broker state into the audit trail.",
|
|
308
|
+
"severity": "error",
|
|
309
|
+
"events": [
|
|
310
|
+
"broker.acl.grant",
|
|
311
|
+
"broker.acl.revoke",
|
|
312
|
+
"broker.configuration.update",
|
|
313
|
+
"broker.consumer-group.update",
|
|
314
|
+
"broker.exchange.update",
|
|
315
|
+
"broker.offset.reset",
|
|
316
|
+
"broker.permission.grant",
|
|
317
|
+
"broker.permission.revoke",
|
|
318
|
+
"broker.queue.update",
|
|
319
|
+
"broker.quota.create",
|
|
320
|
+
"broker.quota.delete",
|
|
321
|
+
"broker.quota.update",
|
|
322
|
+
"broker.stream.update",
|
|
323
|
+
"broker.topic.update"
|
|
324
|
+
],
|
|
325
|
+
"requiredPaths": ["/change"],
|
|
326
|
+
"recommendedPaths": ["/change/changedFields"]
|
|
327
|
+
},
|
|
328
|
+
{
|
|
329
|
+
"id": "BROKER-ACL-001",
|
|
330
|
+
"description": "A broker access-control change names the principal whose access changed and the permission involved, and should record whether the rule allows or denies.",
|
|
331
|
+
"rationale": "Broker access control is where the confidentiality of every topic on a cluster is actually decided, and an ACL event that does not name the principal is not reviewable at all — the resource tells a reviewer which topic was touched and nothing about who can now read it.The allow-or-deny effect is recommended rather than required because several broker families support only positive grants, and forcing them to assert `allow` on every event adds a constant rather than a fact. The principal is recorded in namespaced metadata rather than in `/subject`, because [actor-model.md](../../specification/actor-model.md) §5 defines `subject` as the principal *on whose behalf* the actor acted, and forbids its use as a generic target. A grantee or assignee is neither. This follows the identity profile, which records a role in `/metadata/role/id` for the same reason.",
|
|
332
|
+
"severity": "error",
|
|
333
|
+
"events": [
|
|
334
|
+
"broker.acl.grant",
|
|
335
|
+
"broker.acl.revoke",
|
|
336
|
+
"broker.permission.grant",
|
|
337
|
+
"broker.permission.revoke"
|
|
338
|
+
],
|
|
339
|
+
"requiredMetadata": [
|
|
340
|
+
{
|
|
341
|
+
"path": "/broker/acl/permission",
|
|
342
|
+
"type": "string"
|
|
343
|
+
},
|
|
344
|
+
{
|
|
345
|
+
"path": "/broker/acl/principalId",
|
|
346
|
+
"type": "string"
|
|
347
|
+
}
|
|
348
|
+
],
|
|
349
|
+
"recommendedPaths": ["/metadata/broker/acl/effect"]
|
|
350
|
+
},
|
|
351
|
+
{
|
|
352
|
+
"id": "BROKER-QUOTA-001",
|
|
353
|
+
"description": "A quota change records which dimension of consumption was limited.",
|
|
354
|
+
"rationale": "A quota is an availability control, and tightening one is a denial-of-service risk while loosening one removes a guardrail that protected every other tenant on the cluster. Neither can be assessed without knowing what was limited: a change to a throughput ceiling, a connection count and a storage allowance have completely different consequences and completely different suspects. The dimension is required as a token; the profile deliberately does not constrain the numeric values, because the rule language cannot compare numbers and a limit is only meaningful against a deployment's own baseline.",
|
|
355
|
+
"severity": "error",
|
|
356
|
+
"events": ["broker.quota.create", "broker.quota.delete", "broker.quota.update"],
|
|
357
|
+
"requiredMetadata": [
|
|
358
|
+
{
|
|
359
|
+
"path": "/broker/quota/dimension",
|
|
360
|
+
"type": "string"
|
|
361
|
+
}
|
|
362
|
+
]
|
|
363
|
+
},
|
|
364
|
+
{
|
|
365
|
+
"id": "BROKER-OFFSET-001",
|
|
366
|
+
"description": "An offset reset records the position it moved from, the position it moved to, and why.",
|
|
367
|
+
"rationale": "Resetting a consumer position is the broker operation with the largest invisible blast radius: moving backwards replays side effects that already happened, and moving forwards silently abandons messages that will never be processed. Neither is visible in the resource, the outcome or the actor — only the two positions show what happened, and only the justification shows whether it was deliberate. Both positions are recorded as strings because broker position types differ irreconcilably: a 64-bit log offset, a stream entry identifier and a composite message identifier are all positions, and forcing one numeric shape would exclude most brokers.",
|
|
368
|
+
"severity": "error",
|
|
369
|
+
"events": ["broker.offset.reset"],
|
|
370
|
+
"requiredPaths": ["/reason"],
|
|
371
|
+
"requiredMetadata": [
|
|
372
|
+
{
|
|
373
|
+
"path": "/broker/offset/previous",
|
|
374
|
+
"type": "string"
|
|
375
|
+
},
|
|
376
|
+
{
|
|
377
|
+
"path": "/broker/offset/target",
|
|
378
|
+
"type": "string"
|
|
379
|
+
}
|
|
380
|
+
],
|
|
381
|
+
"recommendedPaths": ["/metadata/broker/offset/strategy"]
|
|
382
|
+
},
|
|
383
|
+
{
|
|
384
|
+
"id": "BROKER-REPLAY-001",
|
|
385
|
+
"description": "A message replay records how much was replayed and why, and should record how many messages it covered.",
|
|
386
|
+
"rationale": "A replay re-delivers messages that consumers already handled, so its consequence is measured in duplicated side effects — repeated payments, repeated notifications, repeated downstream writes — and the difference between replaying one message and replaying an entire topic is the difference between a correction and an incident. The scope is required because it is the only field that carries that difference; the count is recommended because a producer streaming a replay may not know it in advance. The replayed messages themselves are never recorded: an audit event states that a replay happened, and the messages remain in the broker under their own controls.",
|
|
387
|
+
"severity": "error",
|
|
388
|
+
"events": ["broker.message.replay"],
|
|
389
|
+
"requiredPaths": ["/reason"],
|
|
390
|
+
"requiredMetadata": [
|
|
391
|
+
{
|
|
392
|
+
"path": "/broker/replay/scope",
|
|
393
|
+
"type": "string"
|
|
394
|
+
}
|
|
395
|
+
],
|
|
396
|
+
"recommendedPaths": ["/metadata/broker/replay/messageCount"]
|
|
397
|
+
}
|
|
398
|
+
]
|
|
399
|
+
}
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://openauditmodel.org/schemas/profile-definition/0.1/schema.json",
|
|
4
|
+
"title": "OpenAuditModel Profile Definition Schema",
|
|
5
|
+
"description": "Structure of a declarative OpenAuditModel profile definition. This schema validates profile documents; it is NOT part of the canonical audit event schema and never constrains an audit event.",
|
|
6
|
+
"$comment": "A profile may only add constraints. Nothing in this schema can relax, replace or reinterpret the core model: the rule vocabulary can require values to be present or equal, and can do nothing else. See decisions/0008-declarative-profile-conformance.md.",
|
|
7
|
+
"type": "object",
|
|
8
|
+
"additionalProperties": false,
|
|
9
|
+
"required": [
|
|
10
|
+
"profileVersion",
|
|
11
|
+
"name",
|
|
12
|
+
"version",
|
|
13
|
+
"status",
|
|
14
|
+
"coreVersions",
|
|
15
|
+
"title",
|
|
16
|
+
"description",
|
|
17
|
+
"rules"
|
|
18
|
+
],
|
|
19
|
+
"properties": {
|
|
20
|
+
"profileVersion": {
|
|
21
|
+
"description": "Version of the profile definition format this document uses.",
|
|
22
|
+
"const": "0.1"
|
|
23
|
+
},
|
|
24
|
+
"name": {
|
|
25
|
+
"description": "Profile identifier. Matches the directory the profile lives in.",
|
|
26
|
+
"$ref": "#/$defs/token"
|
|
27
|
+
},
|
|
28
|
+
"version": {
|
|
29
|
+
"description": "Version of this profile's rules, independent of the core specification version.",
|
|
30
|
+
"type": "string",
|
|
31
|
+
"minLength": 1,
|
|
32
|
+
"maxLength": 32,
|
|
33
|
+
"pattern": "^[0-9]+(\\.[0-9]+){0,2}$"
|
|
34
|
+
},
|
|
35
|
+
"status": {
|
|
36
|
+
"description": "Maturity of the profile.",
|
|
37
|
+
"enum": ["experimental", "stable", "deprecated"]
|
|
38
|
+
},
|
|
39
|
+
"coreVersions": {
|
|
40
|
+
"description": "Core specification versions this profile applies to. An event declaring another `specVersion` is out of scope for the profile.",
|
|
41
|
+
"type": "array",
|
|
42
|
+
"minItems": 1,
|
|
43
|
+
"maxItems": 16,
|
|
44
|
+
"uniqueItems": true,
|
|
45
|
+
"items": { "type": "string", "minLength": 1, "maxLength": 16 }
|
|
46
|
+
},
|
|
47
|
+
"title": { "$ref": "#/$defs/text" },
|
|
48
|
+
"description": { "$ref": "#/$defs/text" },
|
|
49
|
+
"rules": {
|
|
50
|
+
"description": "Additional requirements. A rule applies only to the events its selector matches.",
|
|
51
|
+
"type": "array",
|
|
52
|
+
"minItems": 1,
|
|
53
|
+
"maxItems": 256,
|
|
54
|
+
"items": { "$ref": "#/$defs/rule" }
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
"$defs": {
|
|
58
|
+
"token": {
|
|
59
|
+
"type": "string",
|
|
60
|
+
"minLength": 1,
|
|
61
|
+
"maxLength": 64,
|
|
62
|
+
"pattern": "^[a-z][a-z0-9]*(-[a-z0-9]+)*$"
|
|
63
|
+
},
|
|
64
|
+
"text": {
|
|
65
|
+
"type": "string",
|
|
66
|
+
"minLength": 1,
|
|
67
|
+
"maxLength": 1024
|
|
68
|
+
},
|
|
69
|
+
"ruleId": {
|
|
70
|
+
"description": "Stable rule identifier, upper case and hyphen separated, such as IAM-ROLE-ASSIGN-001.",
|
|
71
|
+
"type": "string",
|
|
72
|
+
"minLength": 3,
|
|
73
|
+
"maxLength": 64,
|
|
74
|
+
"pattern": "^[A-Z][A-Z0-9]*(-[A-Z0-9]+){1,5}$"
|
|
75
|
+
},
|
|
76
|
+
"eventName": {
|
|
77
|
+
"description": "Exact event name, using the core event naming rules.",
|
|
78
|
+
"type": "string",
|
|
79
|
+
"minLength": 3,
|
|
80
|
+
"maxLength": 256,
|
|
81
|
+
"pattern": "^[a-z][a-z0-9]*(-[a-z0-9]+)*(\\.[a-z][a-z0-9]*(-[a-z0-9]+)*)+$"
|
|
82
|
+
},
|
|
83
|
+
"eventPrefix": {
|
|
84
|
+
"description": "Event name prefix, which MUST end with a dot so that `identity.role.` cannot accidentally match `identity.roles-export`.",
|
|
85
|
+
"type": "string",
|
|
86
|
+
"minLength": 2,
|
|
87
|
+
"maxLength": 256,
|
|
88
|
+
"pattern": "^[a-z][a-z0-9]*(-[a-z0-9]+)*(\\.[a-z][a-z0-9]*(-[a-z0-9]+)*)*\\.$"
|
|
89
|
+
},
|
|
90
|
+
"jsonPointer": {
|
|
91
|
+
"description": "Non-empty JSON Pointer, as defined by RFC 6901.",
|
|
92
|
+
"type": "string",
|
|
93
|
+
"minLength": 2,
|
|
94
|
+
"maxLength": 256,
|
|
95
|
+
"pattern": "^(/([^~/]|~[01])*)+$"
|
|
96
|
+
},
|
|
97
|
+
"scalar": {
|
|
98
|
+
"description": "Value a conditional or required-value constraint compares against. Scalars only: comparison is strict equality, never a structural match.",
|
|
99
|
+
"type": ["string", "number", "boolean", "null"]
|
|
100
|
+
},
|
|
101
|
+
"condition": {
|
|
102
|
+
"description": "The only conditional mechanism in v0.1: one path compared for equality against one scalar. There is no expression language, no boolean combination and no nesting.",
|
|
103
|
+
"type": "object",
|
|
104
|
+
"additionalProperties": false,
|
|
105
|
+
"required": ["path", "equals"],
|
|
106
|
+
"properties": {
|
|
107
|
+
"path": {
|
|
108
|
+
"description": "Absolute JSON Pointer into the event, such as /metadata/role/privileged.",
|
|
109
|
+
"$ref": "#/$defs/jsonPointer"
|
|
110
|
+
},
|
|
111
|
+
"equals": { "$ref": "#/$defs/scalar" }
|
|
112
|
+
}
|
|
113
|
+
},
|
|
114
|
+
"requiredValue": {
|
|
115
|
+
"description": "A value that must be present and strictly equal to a scalar.",
|
|
116
|
+
"type": "object",
|
|
117
|
+
"additionalProperties": false,
|
|
118
|
+
"required": ["path", "equals"],
|
|
119
|
+
"properties": {
|
|
120
|
+
"path": {
|
|
121
|
+
"description": "Absolute JSON Pointer into the event, such as /authentication/mfa.",
|
|
122
|
+
"$ref": "#/$defs/jsonPointer"
|
|
123
|
+
},
|
|
124
|
+
"equals": { "$ref": "#/$defs/scalar" }
|
|
125
|
+
}
|
|
126
|
+
},
|
|
127
|
+
"requiredMetadata": {
|
|
128
|
+
"description": "A field required under /metadata.",
|
|
129
|
+
"type": "object",
|
|
130
|
+
"additionalProperties": false,
|
|
131
|
+
"required": ["path", "type"],
|
|
132
|
+
"properties": {
|
|
133
|
+
"path": {
|
|
134
|
+
"description": "JSON Pointer RELATIVE to /metadata. `/role/id` means `/metadata/role/id`.",
|
|
135
|
+
"$ref": "#/$defs/jsonPointer"
|
|
136
|
+
},
|
|
137
|
+
"type": {
|
|
138
|
+
"description": "JSON type the value must have. Type checking only; profiles do not embed schema fragments in v0.1.",
|
|
139
|
+
"enum": ["string", "number", "integer", "boolean", "object", "array"]
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
},
|
|
143
|
+
"rule": {
|
|
144
|
+
"type": "object",
|
|
145
|
+
"additionalProperties": false,
|
|
146
|
+
"required": ["id", "description"],
|
|
147
|
+
"anyOf": [{ "required": ["events"] }, { "required": ["eventPrefixes"] }],
|
|
148
|
+
"properties": {
|
|
149
|
+
"id": { "$ref": "#/$defs/ruleId" },
|
|
150
|
+
"description": { "$ref": "#/$defs/text" },
|
|
151
|
+
"rationale": {
|
|
152
|
+
"description": "Why the requirement exists. Informative.",
|
|
153
|
+
"$ref": "#/$defs/text"
|
|
154
|
+
},
|
|
155
|
+
"severity": {
|
|
156
|
+
"description": "How a violation of this rule is reported. `error` fails conformance; `warning` and `info` do not. These values are the profile vocabulary and are unrelated to the privacy linter's severities.",
|
|
157
|
+
"enum": ["info", "warning", "error"],
|
|
158
|
+
"default": "error"
|
|
159
|
+
},
|
|
160
|
+
"events": {
|
|
161
|
+
"description": "Exact event names this rule applies to.",
|
|
162
|
+
"type": "array",
|
|
163
|
+
"minItems": 1,
|
|
164
|
+
"maxItems": 64,
|
|
165
|
+
"uniqueItems": true,
|
|
166
|
+
"items": { "$ref": "#/$defs/eventName" }
|
|
167
|
+
},
|
|
168
|
+
"eventPrefixes": {
|
|
169
|
+
"description": "Event name prefixes this rule applies to.",
|
|
170
|
+
"type": "array",
|
|
171
|
+
"minItems": 1,
|
|
172
|
+
"maxItems": 64,
|
|
173
|
+
"uniqueItems": true,
|
|
174
|
+
"items": { "$ref": "#/$defs/eventPrefix" }
|
|
175
|
+
},
|
|
176
|
+
"when": {
|
|
177
|
+
"description": "Applies the rule's requirements only when this condition holds. When the condition path is absent, the condition does not hold and the rule contributes nothing.",
|
|
178
|
+
"$ref": "#/$defs/condition"
|
|
179
|
+
},
|
|
180
|
+
"requiredPaths": {
|
|
181
|
+
"description": "Absolute JSON Pointers that must resolve to a present value.",
|
|
182
|
+
"type": "array",
|
|
183
|
+
"minItems": 1,
|
|
184
|
+
"maxItems": 64,
|
|
185
|
+
"uniqueItems": true,
|
|
186
|
+
"items": { "$ref": "#/$defs/jsonPointer" }
|
|
187
|
+
},
|
|
188
|
+
"requiredMetadata": {
|
|
189
|
+
"description": "Fields required under /metadata, with their JSON type.",
|
|
190
|
+
"type": "array",
|
|
191
|
+
"minItems": 1,
|
|
192
|
+
"maxItems": 64,
|
|
193
|
+
"items": { "$ref": "#/$defs/requiredMetadata" }
|
|
194
|
+
},
|
|
195
|
+
"requiredValues": {
|
|
196
|
+
"description": "Values required to be present and strictly equal to a scalar.",
|
|
197
|
+
"type": "array",
|
|
198
|
+
"minItems": 1,
|
|
199
|
+
"maxItems": 64,
|
|
200
|
+
"items": { "$ref": "#/$defs/requiredValue" }
|
|
201
|
+
},
|
|
202
|
+
"recommendedPaths": {
|
|
203
|
+
"description": "Absolute JSON Pointers that SHOULD resolve to a present value. Absence produces a warning and never fails conformance.",
|
|
204
|
+
"type": "array",
|
|
205
|
+
"minItems": 1,
|
|
206
|
+
"maxItems": 64,
|
|
207
|
+
"uniqueItems": true,
|
|
208
|
+
"items": { "$ref": "#/$defs/jsonPointer" }
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|