smplkit 3.0.19 → 3.0.20
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.
- checksums.yaml +4 -4
- data/lib/smplkit/audit/models.rb +239 -29
- data/lib/smplkit/config/models.rb +3 -3
- data/lib/smplkit/flags/types.rb +5 -5
- data/lib/smplkit/log_level.rb +13 -7
- data/lib/smplkit/management/audit.rb +91 -52
- data/lib/smplkit/management/types.rb +2 -2
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4fd326702343cd2c8f212f2964214c6c5b12eaf3d38747207a3aa26642b68af8
|
|
4
|
+
data.tar.gz: 1a298755b3fb4d87ec58d09664720ea078595bd77ce11cd7bef20184b4e20f0a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: fffd9940159bb8e141024235be5a3c0d9992a7ee23cc1bc454e1baca7d286bc19be8ee7114bd36fbff599f9ea05ed71853891fff43a98dcf97c94c7bc9065b3c
|
|
7
|
+
data.tar.gz: 500a7b954f794cd50c59e4d56e4354e9e5908f8f9e27a09c662d64c74aa64f90a9f1c3d8962314c8d312a5c1d80947db823eab2f859b0d87025caee78a3bddce
|
data/lib/smplkit/audit/models.rb
CHANGED
|
@@ -2,10 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
module Smplkit
|
|
4
4
|
module Audit
|
|
5
|
-
# Parse the +page[after]+ cursor out of a JSON:API +links.next+
|
|
6
|
-
# URL. Returns nil for non-string input or when the link carries
|
|
7
|
-
# no cursor parameter; trims trailing query params at the next
|
|
8
|
-
# ampersand so they don't leak into the token.
|
|
9
5
|
# Wrap a generated-audit-API call and translate +ApiError+ into the
|
|
10
6
|
# +Smplkit::Error+ hierarchy. Connection-level failures (no
|
|
11
7
|
# response code) become {Smplkit::ConnectionError}; status-coded
|
|
@@ -24,6 +20,10 @@ module Smplkit
|
|
|
24
20
|
raise
|
|
25
21
|
end
|
|
26
22
|
|
|
23
|
+
# Parse the +page[after]+ cursor out of a JSON:API +links.next+
|
|
24
|
+
# URL. Returns nil for non-string input or when the link carries
|
|
25
|
+
# no cursor parameter; trims trailing query params at the next
|
|
26
|
+
# ampersand so they don't leak into the token.
|
|
27
27
|
def self.next_cursor(link)
|
|
28
28
|
return nil unless link.is_a?(String)
|
|
29
29
|
|
|
@@ -49,23 +49,28 @@ module Smplkit
|
|
|
49
49
|
out
|
|
50
50
|
end
|
|
51
51
|
|
|
52
|
-
#
|
|
52
|
+
# Supported SIEM forwarder destination types (ADR-047 §2.12).
|
|
53
53
|
#
|
|
54
|
-
#
|
|
55
|
-
#
|
|
56
|
-
#
|
|
57
|
-
#
|
|
54
|
+
# Members are declared in alphabetical order. Customers pass these
|
|
55
|
+
# constants — or the equivalent string — to the management
|
|
56
|
+
# +forwarders+ surface; the wrapper validates membership via {coerce}
|
|
57
|
+
# before round-tripping to the wire.
|
|
58
58
|
module ForwarderType
|
|
59
|
-
HTTP = "HTTP"
|
|
60
59
|
DATADOG = "DATADOG"
|
|
60
|
+
ELASTIC = "ELASTIC"
|
|
61
|
+
HONEYCOMB = "HONEYCOMB"
|
|
62
|
+
HTTP = "HTTP"
|
|
63
|
+
NEW_RELIC = "NEW_RELIC"
|
|
61
64
|
SPLUNK_HEC = "SPLUNK_HEC"
|
|
62
65
|
SUMO_LOGIC = "SUMO_LOGIC"
|
|
63
|
-
NEW_RELIC = "NEW_RELIC"
|
|
64
|
-
HONEYCOMB = "HONEYCOMB"
|
|
65
|
-
ELASTIC = "ELASTIC"
|
|
66
66
|
|
|
67
|
-
VALUES = [
|
|
67
|
+
VALUES = [DATADOG, ELASTIC, HONEYCOMB, HTTP, NEW_RELIC, SPLUNK_HEC, SUMO_LOGIC].freeze
|
|
68
68
|
|
|
69
|
+
# Validate and normalize an input to a wire-format string.
|
|
70
|
+
#
|
|
71
|
+
# @param value [String, nil] a published constant or its literal string.
|
|
72
|
+
# @return [String, nil] the canonical wire value (or +nil+ when input is +nil+).
|
|
73
|
+
# @raise [ArgumentError] when +value+ is not a member of {VALUES}.
|
|
69
74
|
def self.coerce(value)
|
|
70
75
|
return nil if value.nil?
|
|
71
76
|
|
|
@@ -77,7 +82,64 @@ module Smplkit
|
|
|
77
82
|
end
|
|
78
83
|
end
|
|
79
84
|
|
|
80
|
-
#
|
|
85
|
+
# HTTP verb used by a forwarder's outbound delivery (ADR-047 §2.12).
|
|
86
|
+
#
|
|
87
|
+
# Mirrors the audit spec's +HttpConfigurationMethod+ enum so the
|
|
88
|
+
# +HttpConfiguration#method+ field is constrained to a known value
|
|
89
|
+
# instead of accepting any string. Members are declared in
|
|
90
|
+
# alphabetical order.
|
|
91
|
+
module HttpMethod
|
|
92
|
+
DELETE = "DELETE"
|
|
93
|
+
GET = "GET"
|
|
94
|
+
PATCH = "PATCH"
|
|
95
|
+
POST = "POST"
|
|
96
|
+
PUT = "PUT"
|
|
97
|
+
|
|
98
|
+
VALUES = [DELETE, GET, PATCH, POST, PUT].freeze
|
|
99
|
+
|
|
100
|
+
# Validate and normalize an input to a wire-format string.
|
|
101
|
+
#
|
|
102
|
+
# @param value [String, nil] a published constant or its literal string.
|
|
103
|
+
# @return [String, nil] the canonical wire value (or +nil+ when input is +nil+).
|
|
104
|
+
# @raise [ArgumentError] when +value+ is not a member of {VALUES}.
|
|
105
|
+
def self.coerce(value)
|
|
106
|
+
return nil if value.nil?
|
|
107
|
+
|
|
108
|
+
s = value.to_s
|
|
109
|
+
return s if VALUES.include?(s)
|
|
110
|
+
|
|
111
|
+
raise ArgumentError,
|
|
112
|
+
"Unknown HttpMethod #{value.inspect}; expected one of #{VALUES.inspect}"
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# A single audit event as returned by the audit service (ADR-047 §2.3.1).
|
|
117
|
+
#
|
|
118
|
+
# @!attribute [rw] id
|
|
119
|
+
# @return [String] Server-assigned UUID for this event.
|
|
120
|
+
# @!attribute [rw] action
|
|
121
|
+
# @return [String] Action slug — e.g. +"user.created"+, +"invoice.paid"+.
|
|
122
|
+
# @!attribute [rw] resource_type
|
|
123
|
+
# @return [String] Type of resource the action operated on — e.g. +"invoice"+.
|
|
124
|
+
# @!attribute [rw] resource_id
|
|
125
|
+
# @return [String] Customer-facing id of the resource the action operated on.
|
|
126
|
+
# @!attribute [rw] occurred_at
|
|
127
|
+
# @return [String] ISO-8601 timestamp of when the action happened, as reported by the source.
|
|
128
|
+
# @!attribute [rw] created_at
|
|
129
|
+
# @return [String] ISO-8601 timestamp of when the audit service first ingested this event.
|
|
130
|
+
# @!attribute [rw] actor_type
|
|
131
|
+
# @return [String, nil] Type of actor (+"user"+, +"api_key"+, +"system"+, …) — +nil+ when unknown.
|
|
132
|
+
# @!attribute [rw] actor_id
|
|
133
|
+
# @return [String, nil] UUID of the actor when the actor is a tracked entity (user, api_key);
|
|
134
|
+
# +nil+ for system or anonymous events.
|
|
135
|
+
# @!attribute [rw] actor_label
|
|
136
|
+
# @return [String, nil] Display label for the actor — typically a name or email.
|
|
137
|
+
# @!attribute [rw] data
|
|
138
|
+
# @return [Hash{String => Object}] Free-form per-event payload defined by the customer.
|
|
139
|
+
# @!attribute [rw] idempotency_key
|
|
140
|
+
# @return [String, nil] Customer-supplied dedupe key, +nil+ if not provided.
|
|
141
|
+
# @!attribute [rw] do_not_forward
|
|
142
|
+
# @return [Boolean] When +true+, skip SIEM forwarder delivery regardless of any matching filter.
|
|
81
143
|
AuditEvent = Struct.new(
|
|
82
144
|
:id, :action, :resource_type, :resource_id,
|
|
83
145
|
:occurred_at, :created_at,
|
|
@@ -110,6 +172,13 @@ module Smplkit
|
|
|
110
172
|
# the customer-facing key as the resource id (ADR-014). The duplication
|
|
111
173
|
# keeps SDK consumers from having to dig into the id field when
|
|
112
174
|
# filtering UI controls; pick whichever name reads better in context.
|
|
175
|
+
#
|
|
176
|
+
# @!attribute [rw] id
|
|
177
|
+
# @return [String] JSON:API resource id (same as +resource_type+).
|
|
178
|
+
# @!attribute [rw] resource_type
|
|
179
|
+
# @return [String] The distinct resource_type slug.
|
|
180
|
+
# @!attribute [rw] created_at
|
|
181
|
+
# @return [String] ISO-8601 timestamp of the earliest sighting for this slug.
|
|
113
182
|
ResourceType = Struct.new(:id, :resource_type, :created_at, keyword_init: true) do
|
|
114
183
|
def self.from_resource(resource)
|
|
115
184
|
attrs = resource.attributes
|
|
@@ -127,6 +196,13 @@ module Smplkit
|
|
|
127
196
|
# +created_at+ is the earliest sighting; when the parent list call
|
|
128
197
|
# filtered by +resource_type+, this is the first sighting of that
|
|
129
198
|
# specific (action, resource_type) triple, not the action overall.
|
|
199
|
+
#
|
|
200
|
+
# @!attribute [rw] id
|
|
201
|
+
# @return [String] JSON:API resource id (same as +action+).
|
|
202
|
+
# @!attribute [rw] action
|
|
203
|
+
# @return [String] The distinct action slug.
|
|
204
|
+
# @!attribute [rw] created_at
|
|
205
|
+
# @return [String] ISO-8601 timestamp of the earliest sighting for this slug.
|
|
130
206
|
Action = Struct.new(:id, :action, :created_at, keyword_init: true) do
|
|
131
207
|
def self.from_resource(resource)
|
|
132
208
|
attrs = resource.attributes
|
|
@@ -138,19 +214,41 @@ module Smplkit
|
|
|
138
214
|
end
|
|
139
215
|
end
|
|
140
216
|
|
|
217
|
+
# A single name/value HTTP header on a forwarder destination.
|
|
218
|
+
#
|
|
219
|
+
# @!attribute [rw] name
|
|
220
|
+
# @return [String] Header name (e.g. +"Authorization"+, +"DD-API-KEY"+).
|
|
221
|
+
# @!attribute [rw] value
|
|
222
|
+
# @return [String] Header value, plaintext on writes. The audit service
|
|
223
|
+
# encrypts values at rest; reads return them as +"<redacted>"+.
|
|
141
224
|
HttpHeader = Struct.new(:name, :value, keyword_init: true)
|
|
142
225
|
|
|
226
|
+
# Forwarder destination HTTP request shape.
|
|
227
|
+
#
|
|
228
|
+
# @!attribute [rw] method
|
|
229
|
+
# @return [String] HTTP verb used for delivery. Defaults to {HttpMethod::POST}.
|
|
230
|
+
# @!attribute [rw] url
|
|
231
|
+
# @return [String] Destination URL the audit service sends each event to.
|
|
232
|
+
# @!attribute [rw] headers
|
|
233
|
+
# @return [Array<HttpHeader>] Headers attached to every outbound request.
|
|
234
|
+
# Values carry credentials and are encrypted at rest server-side; reads
|
|
235
|
+
# return them redacted.
|
|
236
|
+
# @!attribute [rw] success_status
|
|
237
|
+
# @return [String] Status the destination must return for delivery to count
|
|
238
|
+
# as success — an exact code (+"200"+, +"204"+) or a class (+"2xx"+, +"4xx"+).
|
|
239
|
+
# Defaults to +"2xx"+.
|
|
240
|
+
#
|
|
143
241
|
# rubocop:disable Lint/StructNewOverride -- ``:method`` matches the
|
|
144
242
|
# API attribute and shadowing Struct#method is the expected ergonomics.
|
|
145
243
|
HttpConfiguration = Struct.new(:method, :url, :headers, :success_status, keyword_init: true) do
|
|
146
|
-
def initialize(method:
|
|
147
|
-
super(method: method, url: url, headers: headers || [], success_status: success_status)
|
|
244
|
+
def initialize(method: HttpMethod::POST, url: "", headers: nil, success_status: "2xx")
|
|
245
|
+
super(method: HttpMethod.coerce(method), url: url, headers: headers || [], success_status: success_status)
|
|
148
246
|
end
|
|
149
247
|
|
|
150
248
|
def self.to_wire(src)
|
|
151
249
|
h = src.is_a?(Hash) ? new(**src) : src
|
|
152
250
|
SmplkitGeneratedClient::Audit::HttpConfiguration.new(
|
|
153
|
-
method: h.method,
|
|
251
|
+
method: HttpMethod.coerce(h.method),
|
|
154
252
|
url: h.url,
|
|
155
253
|
headers: (h.headers || []).map do |hdr|
|
|
156
254
|
name, value = if hdr.is_a?(Hash)
|
|
@@ -169,7 +267,7 @@ module Smplkit
|
|
|
169
267
|
return new if src.nil?
|
|
170
268
|
|
|
171
269
|
new(
|
|
172
|
-
method: src.method ||
|
|
270
|
+
method: src.method || HttpMethod::POST,
|
|
173
271
|
url: src.url || "",
|
|
174
272
|
headers: (src.headers || []).map { |h| HttpHeader.new(name: h.name, value: h.value) },
|
|
175
273
|
success_status: src.success_status || "2xx"
|
|
@@ -178,17 +276,130 @@ module Smplkit
|
|
|
178
276
|
end
|
|
179
277
|
# rubocop:enable Lint/StructNewOverride
|
|
180
278
|
|
|
181
|
-
#
|
|
182
|
-
#
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
279
|
+
# A SIEM streaming forwarder configured on the customer's account.
|
|
280
|
+
#
|
|
281
|
+
# Active-record style: instantiate via
|
|
282
|
+
# +mgmt.audit.forwarders.new_forwarder(...)+, mutate fields directly,
|
|
283
|
+
# and call {#save} to persist or {#delete} to remove. Header values in
|
|
284
|
+
# +configuration.headers+ are returned redacted on reads — the GET path
|
|
285
|
+
# on the audit API replaces every header value with +"<redacted>"+.
|
|
286
|
+
# Re-supply real values before calling {#save}; the SDK does not cache
|
|
287
|
+
# them client-side.
|
|
288
|
+
class Forwarder
|
|
289
|
+
# @return [String, nil] Server-assigned UUID, +nil+ until {#save} has run.
|
|
290
|
+
attr_accessor :id
|
|
291
|
+
|
|
292
|
+
# @return [String] Display name. Free-form.
|
|
293
|
+
attr_accessor :name
|
|
294
|
+
|
|
295
|
+
# @return [String] One of {ForwarderType::VALUES}.
|
|
296
|
+
attr_accessor :forwarder_type
|
|
297
|
+
|
|
298
|
+
# @return [Boolean] When +false+, the audit service skips delivery for
|
|
299
|
+
# this forwarder but still records +filtered_out+ deliveries.
|
|
300
|
+
attr_accessor :enabled
|
|
301
|
+
|
|
302
|
+
# @return [HttpConfiguration] Destination request configuration.
|
|
303
|
+
attr_accessor :configuration
|
|
304
|
+
|
|
305
|
+
# @return [String, nil] Optional free-text description.
|
|
306
|
+
attr_accessor :description
|
|
307
|
+
|
|
308
|
+
# @return [Hash, nil] Optional JSON Logic expression evaluated per event.
|
|
309
|
+
# When set, events that don't match are recorded as +filtered_out+
|
|
310
|
+
# deliveries instead of being delivered to the destination.
|
|
311
|
+
attr_accessor :filter
|
|
312
|
+
|
|
313
|
+
# @return [String, nil] Optional template applied to each event before
|
|
314
|
+
# delivery. Shape depends on {#transform_type}; for +"JSONATA"+, a
|
|
315
|
+
# JSONata expression. +nil+ delivers the event JSON as-is.
|
|
316
|
+
attr_accessor :transform
|
|
317
|
+
|
|
318
|
+
# @return [String, nil] Engine that evaluates {#transform}. Currently
|
|
319
|
+
# only +"JSONATA"+ is supported.
|
|
320
|
+
attr_accessor :transform_type
|
|
321
|
+
|
|
322
|
+
# @return [String, nil] ISO-8601 timestamp of first persist. +nil+ for an unsaved instance.
|
|
323
|
+
attr_accessor :created_at
|
|
324
|
+
|
|
325
|
+
# @return [String, nil] ISO-8601 timestamp of the most recent mutation.
|
|
326
|
+
attr_accessor :updated_at
|
|
327
|
+
|
|
328
|
+
# @return [String, nil] Soft-delete timestamp. +nil+ for live forwarders.
|
|
329
|
+
attr_accessor :deleted_at
|
|
330
|
+
|
|
331
|
+
# @return [Integer, nil] Monotonic version counter, bumped on every server-side write.
|
|
332
|
+
attr_accessor :version
|
|
333
|
+
|
|
334
|
+
def initialize(client = nil, name:, forwarder_type:, configuration:,
|
|
335
|
+
id: nil, enabled: true, description: nil,
|
|
336
|
+
filter: nil, transform: nil, transform_type: nil,
|
|
337
|
+
created_at: nil, updated_at: nil, deleted_at: nil, version: nil)
|
|
338
|
+
@client = client
|
|
339
|
+
@id = id
|
|
340
|
+
@name = name
|
|
341
|
+
@forwarder_type = ForwarderType.coerce(forwarder_type)
|
|
342
|
+
@configuration = configuration
|
|
343
|
+
@enabled = enabled
|
|
344
|
+
@description = description
|
|
345
|
+
@filter = filter
|
|
346
|
+
@transform = transform
|
|
347
|
+
@transform_type = transform_type
|
|
348
|
+
@created_at = created_at
|
|
349
|
+
@updated_at = updated_at
|
|
350
|
+
@deleted_at = deleted_at
|
|
351
|
+
@version = version
|
|
352
|
+
end
|
|
353
|
+
|
|
354
|
+
# Create or update this forwarder on the server.
|
|
355
|
+
#
|
|
356
|
+
# Upsert behavior is driven by {#created_at}: a forwarder with no
|
|
357
|
+
# +created_at+ is created (POST); otherwise it's full-replace updated
|
|
358
|
+
# (PUT). After the call, every field is refreshed from the server
|
|
359
|
+
# response (including newly-assigned +id+, +created_at+, +updated_at+,
|
|
360
|
+
# +version+).
|
|
361
|
+
#
|
|
362
|
+
# @return [self]
|
|
363
|
+
def save
|
|
364
|
+
raise "Forwarder was constructed without a client; cannot save" if @client.nil?
|
|
365
|
+
|
|
366
|
+
updated = @created_at.nil? ? @client._create_forwarder(self) : @client._update_forwarder(self)
|
|
367
|
+
_apply(updated)
|
|
368
|
+
self
|
|
369
|
+
end
|
|
370
|
+
alias save! save
|
|
371
|
+
|
|
372
|
+
# Soft-delete this forwarder on the server.
|
|
373
|
+
#
|
|
374
|
+
# @return [nil]
|
|
375
|
+
def delete
|
|
376
|
+
raise "Forwarder was constructed without a client or id; cannot delete" if @client.nil? || @id.nil?
|
|
377
|
+
|
|
378
|
+
@client.delete(@id)
|
|
379
|
+
end
|
|
380
|
+
alias delete! delete
|
|
381
|
+
|
|
382
|
+
# @api private
|
|
383
|
+
def _apply(other)
|
|
384
|
+
@id = other.id
|
|
385
|
+
@name = other.name
|
|
386
|
+
@forwarder_type = other.forwarder_type
|
|
387
|
+
@configuration = other.configuration
|
|
388
|
+
@enabled = other.enabled
|
|
389
|
+
@description = other.description
|
|
390
|
+
@filter = other.filter
|
|
391
|
+
@transform = other.transform
|
|
392
|
+
@transform_type = other.transform_type
|
|
393
|
+
@created_at = other.created_at
|
|
394
|
+
@updated_at = other.updated_at
|
|
395
|
+
@deleted_at = other.deleted_at
|
|
396
|
+
@version = other.version
|
|
397
|
+
end
|
|
398
|
+
|
|
399
|
+
def self.from_resource(resource, client: nil)
|
|
190
400
|
a = resource.attributes
|
|
191
401
|
new(
|
|
402
|
+
client,
|
|
192
403
|
id: resource.id,
|
|
193
404
|
name: a.name,
|
|
194
405
|
description: a.description,
|
|
@@ -205,6 +416,5 @@ module Smplkit
|
|
|
205
416
|
)
|
|
206
417
|
end
|
|
207
418
|
end
|
|
208
|
-
# rubocop:enable Lint/StructNewOverride
|
|
209
419
|
end
|
|
210
420
|
end
|
|
@@ -4,12 +4,12 @@ module Smplkit
|
|
|
4
4
|
module Config
|
|
5
5
|
# Type of a +ConfigItem+ value.
|
|
6
6
|
module ItemType
|
|
7
|
-
STRING = "STRING"
|
|
8
|
-
NUMBER = "NUMBER"
|
|
9
7
|
BOOLEAN = "BOOLEAN"
|
|
10
8
|
JSON = "JSON"
|
|
9
|
+
NUMBER = "NUMBER"
|
|
10
|
+
STRING = "STRING"
|
|
11
11
|
|
|
12
|
-
ALL = [
|
|
12
|
+
ALL = [BOOLEAN, JSON, NUMBER, STRING].freeze
|
|
13
13
|
end
|
|
14
14
|
|
|
15
15
|
# A single typed item in a +Config+.
|
data/lib/smplkit/flags/types.rb
CHANGED
|
@@ -7,16 +7,16 @@ module Smplkit
|
|
|
7
7
|
# can validate calls. Raw strings are still accepted for backward
|
|
8
8
|
# compatibility.
|
|
9
9
|
module Op
|
|
10
|
+
CONTAINS = "contains"
|
|
10
11
|
EQ = "=="
|
|
11
|
-
NEQ = "!="
|
|
12
|
-
LT = "<"
|
|
13
|
-
LTE = "<="
|
|
14
12
|
GT = ">"
|
|
15
13
|
GTE = ">="
|
|
16
14
|
IN = "in"
|
|
17
|
-
|
|
15
|
+
LT = "<"
|
|
16
|
+
LTE = "<="
|
|
17
|
+
NEQ = "!="
|
|
18
18
|
|
|
19
|
-
ALL = [
|
|
19
|
+
ALL = [CONTAINS, EQ, GT, GTE, IN, LT, LTE, NEQ].freeze
|
|
20
20
|
end
|
|
21
21
|
|
|
22
22
|
# A typed entity referenced by targeting rules and registered with smplkit.
|
data/lib/smplkit/log_level.rb
CHANGED
|
@@ -4,11 +4,17 @@ module Smplkit
|
|
|
4
4
|
# Log severity levels used by the Smpl Logging service.
|
|
5
5
|
#
|
|
6
6
|
# Acts as a string-valued enum: each constant equals its name when used in
|
|
7
|
-
# string contexts, and supports comparison via the +ordinal+.
|
|
7
|
+
# string contexts, and supports comparison via the +ordinal+. Members are
|
|
8
|
+
# declared in alphabetical order; severity is encoded in {#ordinal}, not
|
|
9
|
+
# in declaration order.
|
|
8
10
|
class LogLevel
|
|
9
|
-
NAMES = %w[
|
|
11
|
+
NAMES = %w[DEBUG ERROR FATAL INFO SILENT TRACE WARN].freeze
|
|
10
12
|
|
|
11
|
-
|
|
13
|
+
# @return [String] Canonical level name (e.g. +"INFO"+).
|
|
14
|
+
attr_reader :name
|
|
15
|
+
|
|
16
|
+
# @return [Integer] Severity ordinal — TRACE=0 (lowest) through SILENT=6 (highest).
|
|
17
|
+
attr_reader :ordinal
|
|
12
18
|
|
|
13
19
|
def initialize(name, ordinal)
|
|
14
20
|
@name = name.freeze
|
|
@@ -26,15 +32,15 @@ module Smplkit
|
|
|
26
32
|
|
|
27
33
|
include Comparable
|
|
28
34
|
|
|
29
|
-
TRACE = new("TRACE", 0)
|
|
30
35
|
DEBUG = new("DEBUG", 1)
|
|
31
|
-
INFO = new("INFO", 2)
|
|
32
|
-
WARN = new("WARN", 3)
|
|
33
36
|
ERROR = new("ERROR", 4)
|
|
34
37
|
FATAL = new("FATAL", 5)
|
|
38
|
+
INFO = new("INFO", 2)
|
|
35
39
|
SILENT = new("SILENT", 6)
|
|
40
|
+
TRACE = new("TRACE", 0)
|
|
41
|
+
WARN = new("WARN", 3)
|
|
36
42
|
|
|
37
|
-
ALL = [
|
|
43
|
+
ALL = [DEBUG, ERROR, FATAL, INFO, SILENT, TRACE, WARN].freeze
|
|
38
44
|
|
|
39
45
|
BY_NAME = ALL.to_h { |lvl| [lvl.name, lvl] }.freeze
|
|
40
46
|
|
|
@@ -8,6 +8,7 @@ module Smplkit
|
|
|
8
8
|
# runtime client owns event recording and read-side queries; this
|
|
9
9
|
# surface owns SIEM forwarder CRUD. ADR-047 §2.7.
|
|
10
10
|
class AuditNamespace
|
|
11
|
+
# @return [ForwardersNamespace] CRUD surface for +mgmt.audit.forwarders+.
|
|
11
12
|
attr_reader :forwarders
|
|
12
13
|
|
|
13
14
|
def initialize(api_client)
|
|
@@ -19,41 +20,56 @@ module Smplkit
|
|
|
19
20
|
|
|
20
21
|
# +mgmt.audit.forwarders.*+ — manage the customer's configured SIEM
|
|
21
22
|
# forwarders.
|
|
23
|
+
#
|
|
24
|
+
# The active-record entry point is {#new_forwarder}: instantiate a
|
|
25
|
+
# draft, mutate fields, then call {Smplkit::Audit::Forwarder#save}.
|
|
26
|
+
# The namespace exposes {#list}, {#get}, and {#delete} directly; the
|
|
27
|
+
# +_create_forwarder+ / +_update_forwarder+ helpers are private and
|
|
28
|
+
# invoked by {Smplkit::Audit::Forwarder#save}.
|
|
22
29
|
class ForwardersNamespace
|
|
23
30
|
def initialize(api)
|
|
24
31
|
@api = api
|
|
25
32
|
end
|
|
26
33
|
|
|
27
|
-
#
|
|
34
|
+
# Construct an unsaved {Smplkit::Audit::Forwarder} bound to this
|
|
35
|
+
# namespace. Call +#save+ on the returned instance to persist.
|
|
28
36
|
#
|
|
29
37
|
# @param name [String] Display name.
|
|
30
|
-
# @param forwarder_type [String
|
|
31
|
-
#
|
|
32
|
-
#
|
|
33
|
-
#
|
|
34
|
-
#
|
|
35
|
-
# forwarder_type uses {HttpConfiguration}; the URL and header
|
|
36
|
-
# values inside are stored encrypted server-side and round-trip
|
|
37
|
-
# to GET in plaintext.
|
|
38
|
+
# @param forwarder_type [String] One of {Smplkit::Audit::ForwarderType::VALUES}.
|
|
39
|
+
# @param configuration [Smplkit::Audit::HttpConfiguration] Destination
|
|
40
|
+
# request configuration. Headers carry credentials and are encrypted at
|
|
41
|
+
# rest server-side; reads return them redacted.
|
|
42
|
+
# @param enabled [Boolean] Whether the forwarder is active. Defaults +true+.
|
|
38
43
|
# @param description [String, nil] Optional free-text description.
|
|
39
|
-
# @param
|
|
40
|
-
#
|
|
41
|
-
#
|
|
42
|
-
#
|
|
43
|
-
#
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
44
|
+
# @param filter [Hash, nil] Optional JSON Logic filter; events that don't
|
|
45
|
+
# match are recorded as +filtered_out+ deliveries.
|
|
46
|
+
# @param transform [String, nil] Optional JSONata template applied to the
|
|
47
|
+
# event payload before delivery. Nil sends the event JSON unchanged.
|
|
48
|
+
# @return [Smplkit::Audit::Forwarder]
|
|
49
|
+
def new_forwarder(name:, forwarder_type:, configuration:,
|
|
50
|
+
enabled: true, description: nil,
|
|
51
|
+
filter: nil, transform: nil)
|
|
52
|
+
Smplkit::Audit::Forwarder.new(
|
|
53
|
+
self,
|
|
54
|
+
name: name,
|
|
55
|
+
forwarder_type: forwarder_type,
|
|
56
|
+
configuration: configuration,
|
|
57
|
+
enabled: enabled,
|
|
58
|
+
description: description,
|
|
59
|
+
filter: filter,
|
|
60
|
+
transform: transform,
|
|
61
|
+
transform_type: transform.nil? ? nil : "JSONATA"
|
|
62
|
+
)
|
|
55
63
|
end
|
|
56
64
|
|
|
65
|
+
# List forwarders for the authenticated account.
|
|
66
|
+
#
|
|
67
|
+
# Offset paginated per ADR-014: pass +page_number+ (1-based) and
|
|
68
|
+
# +page_size+ (default 1000, max 1000). Pass +meta_total: true+ to
|
|
69
|
+
# populate +total+ and +total_pages+ in the returned +pagination+
|
|
70
|
+
# block (costs an extra COUNT query server-side).
|
|
71
|
+
#
|
|
72
|
+
# @return [ForwarderListPage]
|
|
57
73
|
def list(forwarder_type: nil, enabled: nil, page_number: nil, page_size: nil, meta_total: nil)
|
|
58
74
|
opts = {}
|
|
59
75
|
opts[:filter_forwarder_type] = Smplkit::Audit::ForwarderType.coerce(forwarder_type) if forwarder_type
|
|
@@ -63,51 +79,67 @@ module Smplkit
|
|
|
63
79
|
opts[:meta_total] = meta_total unless meta_total.nil?
|
|
64
80
|
|
|
65
81
|
resp = Smplkit::Audit.call_api { @api.list_forwarders(opts) }
|
|
66
|
-
forwarders = (resp.data || []).map
|
|
82
|
+
forwarders = (resp.data || []).map do |r|
|
|
83
|
+
Smplkit::Audit::Forwarder.from_resource(r, client: self)
|
|
84
|
+
end
|
|
67
85
|
ForwarderListPage.new(forwarders, Smplkit::Audit.extract_pagination(resp.meta))
|
|
68
86
|
end
|
|
69
87
|
|
|
88
|
+
# Fetch a single forwarder by id. The returned instance is bound to
|
|
89
|
+
# this namespace, so +forwarder.save+ and +forwarder.delete+ work.
|
|
90
|
+
#
|
|
91
|
+
# @param forwarder_id [String]
|
|
92
|
+
# @return [Smplkit::Audit::Forwarder]
|
|
70
93
|
def get(forwarder_id)
|
|
71
94
|
resp = Smplkit::Audit.call_api { @api.get_forwarder(forwarder_id) }
|
|
72
|
-
Smplkit::Audit::Forwarder.from_resource(resp.data)
|
|
95
|
+
Smplkit::Audit::Forwarder.from_resource(resp.data, client: self)
|
|
73
96
|
end
|
|
74
97
|
|
|
75
|
-
#
|
|
98
|
+
# Soft-delete a forwarder.
|
|
76
99
|
#
|
|
77
|
-
#
|
|
78
|
-
#
|
|
79
|
-
# PUT without re-entering secrets.
|
|
80
|
-
def update(forwarder_id, name:, forwarder_type:, configuration:, description: nil,
|
|
81
|
-
enabled: true, filter: nil, transform_type: nil, transform: nil)
|
|
82
|
-
body = build_body(forwarder_id, name: name, forwarder_type: forwarder_type,
|
|
83
|
-
configuration: configuration, description: description,
|
|
84
|
-
enabled: enabled, filter: filter,
|
|
85
|
-
transform_type: transform_type, transform: transform)
|
|
86
|
-
resp = Smplkit::Audit.call_api { @api.update_forwarder(forwarder_id, body) }
|
|
87
|
-
Smplkit::Audit::Forwarder.from_resource(resp.data)
|
|
88
|
-
end
|
|
89
|
-
|
|
100
|
+
# @param forwarder_id [String]
|
|
101
|
+
# @return [nil]
|
|
90
102
|
def delete(forwarder_id)
|
|
91
103
|
Smplkit::Audit.call_api { @api.delete_forwarder(forwarder_id) }
|
|
92
104
|
nil
|
|
93
105
|
end
|
|
94
106
|
|
|
107
|
+
# @api private — POST a new forwarder. Called by
|
|
108
|
+
# {Smplkit::Audit::Forwarder#save} on unsaved instances.
|
|
109
|
+
def _create_forwarder(forwarder)
|
|
110
|
+
resp = Smplkit::Audit.call_api { @api.create_forwarder(build_body(forwarder)) }
|
|
111
|
+
Smplkit::Audit::Forwarder.from_resource(resp.data, client: self)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# @api private — Full-replace PUT for an existing forwarder. Called
|
|
115
|
+
# by {Smplkit::Audit::Forwarder#save} on instances with +created_at+.
|
|
116
|
+
#
|
|
117
|
+
# Header values must be re-supplied as plaintext; the GET path
|
|
118
|
+
# redacts them, so a PUT body containing +"<redacted>"+ would
|
|
119
|
+
# persist that literal. Track real header values client-side and
|
|
120
|
+
# round-trip them.
|
|
121
|
+
def _update_forwarder(forwarder)
|
|
122
|
+
raise ArgumentError, "cannot update a Forwarder with no id" if forwarder.id.nil?
|
|
123
|
+
|
|
124
|
+
resp = Smplkit::Audit.call_api { @api.update_forwarder(forwarder.id, build_body(forwarder)) }
|
|
125
|
+
Smplkit::Audit::Forwarder.from_resource(resp.data, client: self)
|
|
126
|
+
end
|
|
127
|
+
|
|
95
128
|
private
|
|
96
129
|
|
|
97
|
-
def build_body(
|
|
98
|
-
filter:, transform_type:, transform:)
|
|
130
|
+
def build_body(forwarder)
|
|
99
131
|
attrs = SmplkitGeneratedClient::Audit::Forwarder.new(
|
|
100
|
-
name: name,
|
|
101
|
-
description: description,
|
|
102
|
-
forwarder_type: Smplkit::Audit::ForwarderType.coerce(forwarder_type),
|
|
103
|
-
enabled: enabled,
|
|
104
|
-
filter: filter,
|
|
105
|
-
transform_type: transform_type,
|
|
106
|
-
transform: transform,
|
|
107
|
-
configuration: Smplkit::Audit::HttpConfiguration.to_wire(configuration)
|
|
132
|
+
name: forwarder.name,
|
|
133
|
+
description: forwarder.description,
|
|
134
|
+
forwarder_type: Smplkit::Audit::ForwarderType.coerce(forwarder.forwarder_type),
|
|
135
|
+
enabled: forwarder.enabled,
|
|
136
|
+
filter: forwarder.filter,
|
|
137
|
+
transform_type: forwarder.transform_type,
|
|
138
|
+
transform: forwarder.transform,
|
|
139
|
+
configuration: Smplkit::Audit::HttpConfiguration.to_wire(forwarder.configuration)
|
|
108
140
|
)
|
|
109
141
|
resource = SmplkitGeneratedClient::Audit::ForwarderResource.new(
|
|
110
|
-
id: id ? id.to_s : "",
|
|
142
|
+
id: forwarder.id ? forwarder.id.to_s : "",
|
|
111
143
|
type: "forwarder",
|
|
112
144
|
attributes: attrs
|
|
113
145
|
)
|
|
@@ -115,6 +147,13 @@ module Smplkit
|
|
|
115
147
|
end
|
|
116
148
|
end
|
|
117
149
|
|
|
150
|
+
# A single page returned from {ForwardersNamespace#list}.
|
|
151
|
+
#
|
|
152
|
+
# @!attribute [rw] forwarders
|
|
153
|
+
# @return [Array<Smplkit::Audit::Forwarder>] Forwarders in this page.
|
|
154
|
+
# @!attribute [rw] pagination
|
|
155
|
+
# @return [Hash] +meta.pagination+ block (+:page+, +:size+, and — only when
|
|
156
|
+
# the caller passed +meta_total: true+ — +:total+ / +:total_pages+).
|
|
118
157
|
ForwarderListPage = Struct.new(:forwarders, :pagination)
|
|
119
158
|
end
|
|
120
159
|
end
|
|
@@ -9,10 +9,10 @@ module Smplkit
|
|
|
9
9
|
# +AD_HOC+ environments are transient targets (preview branches, individual
|
|
10
10
|
# developer sandboxes) that should not appear in the standard ordering.
|
|
11
11
|
module EnvironmentClassification
|
|
12
|
-
STANDARD = "STANDARD"
|
|
13
12
|
AD_HOC = "AD_HOC"
|
|
13
|
+
STANDARD = "STANDARD"
|
|
14
14
|
|
|
15
|
-
ALL = [
|
|
15
|
+
ALL = [AD_HOC, STANDARD].freeze
|
|
16
16
|
end
|
|
17
17
|
|
|
18
18
|
HEX_RE = /\A#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})\z/
|