smplkit 1.0.23 → 1.0.24
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/client.rb +6 -3
- data/lib/smplkit/audit/events.rb +7 -4
- data/lib/smplkit/audit/forwarders.rb +246 -0
- data/lib/smplkit/audit/functions.rb +58 -0
- data/lib/smplkit.rb +2 -0
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 716ac41c7f53fba00f896876246f36ca44e3db661aa3306d647675972edd64e9
|
|
4
|
+
data.tar.gz: 96d4b2c60234daea039444a9e05ee9441adcf84bc99cd21b690fa504ab1b5ff8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 04dc39a5fdc6e5d809686e9d0b5fc8468771722ff4b50fd8cf644e9baa9be89815213facb0dee695c7d832c7d45c04f1090837300eddd3bb7e95020fa62a4900
|
|
7
|
+
data.tar.gz: faf0afd4dc392a14c44fcf91d34d631f8c819aa85561296839246346ef429015df0fa16d5d6427f931da1ea2960b0177c45f07ff070b4299aa11282d878b759c
|
data/lib/smplkit/audit/client.rb
CHANGED
|
@@ -8,7 +8,7 @@ module Smplkit
|
|
|
8
8
|
# iterations may add SIEM exports as additional sub-clients
|
|
9
9
|
# (ADR-047 §2.7 lists SIEM streaming as a Pro-tier capability).
|
|
10
10
|
class AuditClient
|
|
11
|
-
attr_reader :events
|
|
11
|
+
attr_reader :events, :forwarders, :functions
|
|
12
12
|
|
|
13
13
|
def initialize(api_key:, base_url:, timeout: 10.0)
|
|
14
14
|
cfg = SmplkitGeneratedClient::Audit::Configuration.new
|
|
@@ -17,8 +17,11 @@ module Smplkit
|
|
|
17
17
|
cfg.access_token = api_key
|
|
18
18
|
cfg.timeout = timeout
|
|
19
19
|
api_client = SmplkitGeneratedClient::Audit::ApiClient.new(cfg)
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
events_api = SmplkitGeneratedClient::Audit::EventsApi.new(api_client)
|
|
21
|
+
forwarders_api = SmplkitGeneratedClient::Audit::ForwardersApi.new(api_client)
|
|
22
|
+
@events = Events.new(events_api)
|
|
23
|
+
@forwarders = Forwarders.new(forwarders_api)
|
|
24
|
+
@functions = Functions.new(forwarders_api)
|
|
22
25
|
end
|
|
23
26
|
|
|
24
27
|
def _close
|
data/lib/smplkit/audit/events.rb
CHANGED
|
@@ -19,7 +19,8 @@ module Smplkit
|
|
|
19
19
|
# with +smpl.+ are rejected by the server with a 403 (the buffer
|
|
20
20
|
# logs and drops permanent failures).
|
|
21
21
|
def record(action:, resource_type:, resource_id:,
|
|
22
|
-
occurred_at: nil, snapshot: nil, data: nil, idempotency_key: nil
|
|
22
|
+
occurred_at: nil, snapshot: nil, data: nil, idempotency_key: nil,
|
|
23
|
+
do_not_forward: false)
|
|
23
24
|
raise ArgumentError, "action is required" if action.nil? || action.to_s.empty?
|
|
24
25
|
raise ArgumentError, "resource_type is required" if resource_type.nil? || resource_type.to_s.empty?
|
|
25
26
|
raise ArgumentError, "resource_id is required" if resource_id.nil? || resource_id.to_s.empty?
|
|
@@ -45,7 +46,8 @@ module Smplkit
|
|
|
45
46
|
resource_id: resource_id,
|
|
46
47
|
occurred_at: normalized_occurred_at,
|
|
47
48
|
snapshot: snapshot,
|
|
48
|
-
data: data || {}
|
|
49
|
+
data: data || {},
|
|
50
|
+
do_not_forward: do_not_forward
|
|
49
51
|
)
|
|
50
52
|
resource = SmplkitGeneratedClient::Audit::EventResource.new(
|
|
51
53
|
id: "",
|
|
@@ -117,7 +119,7 @@ module Smplkit
|
|
|
117
119
|
:id, :action, :resource_type, :resource_id,
|
|
118
120
|
:occurred_at, :created_at,
|
|
119
121
|
:actor_type, :actor_id, :actor_label,
|
|
120
|
-
:snapshot, :data, :idempotency_key,
|
|
122
|
+
:snapshot, :data, :idempotency_key, :do_not_forward,
|
|
121
123
|
keyword_init: true
|
|
122
124
|
) do
|
|
123
125
|
def self.from_resource(resource)
|
|
@@ -134,7 +136,8 @@ module Smplkit
|
|
|
134
136
|
actor_label: attrs.actor_label,
|
|
135
137
|
snapshot: attrs.snapshot,
|
|
136
138
|
data: attrs.data || {},
|
|
137
|
-
idempotency_key: attrs.idempotency_key
|
|
139
|
+
idempotency_key: attrs.idempotency_key,
|
|
140
|
+
do_not_forward: attrs.do_not_forward || false
|
|
138
141
|
)
|
|
139
142
|
end
|
|
140
143
|
end
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Smplkit
|
|
4
|
+
module Audit
|
|
5
|
+
# SIEM streaming forwarders for the authenticated account.
|
|
6
|
+
#
|
|
7
|
+
# Pro tier only — every method here raises a wrapped 402
|
|
8
|
+
# +SmplkitGeneratedClient::Audit::ApiError+ on lower tiers.
|
|
9
|
+
class Forwarders
|
|
10
|
+
attr_reader :deliveries, :actions
|
|
11
|
+
|
|
12
|
+
def initialize(api)
|
|
13
|
+
@api = api
|
|
14
|
+
@deliveries = ForwarderDeliveries.new(api)
|
|
15
|
+
@actions = ForwarderActions.new(api)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def create(name:, forwarder_type:, http:, enabled: true,
|
|
19
|
+
filter: nil, transform: nil, data: nil)
|
|
20
|
+
body = wrap_forwarder(nil, name, forwarder_type, http, enabled, filter, transform, data)
|
|
21
|
+
resp = @api.create_forwarder(body)
|
|
22
|
+
Forwarder.from_resource(resp.data)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def list(forwarder_type: nil, enabled: nil, page_size: nil, page_after: nil)
|
|
26
|
+
opts = {}
|
|
27
|
+
opts[:filter_forwarder_type] = forwarder_type if forwarder_type
|
|
28
|
+
opts[:filter_enabled] = enabled unless enabled.nil?
|
|
29
|
+
opts[:page_size] = page_size if page_size
|
|
30
|
+
opts[:page_after] = page_after if page_after
|
|
31
|
+
resp = @api.list_forwarders(opts)
|
|
32
|
+
forwarders = (resp.data || []).map { |r| Forwarder.from_resource(r) }
|
|
33
|
+
ListForwardersPage.new(forwarders, Forwarders.next_cursor(resp.links&._next))
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def get(forwarder_id)
|
|
37
|
+
resp = @api.get_forwarder(forwarder_id)
|
|
38
|
+
Forwarder.from_resource(resp.data)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def update(forwarder_id, name:, forwarder_type:, http:, enabled: true,
|
|
42
|
+
filter: nil, transform: nil, data: nil)
|
|
43
|
+
body = wrap_forwarder(forwarder_id, name, forwarder_type, http, enabled, filter, transform, data)
|
|
44
|
+
resp = @api.update_forwarder(forwarder_id, body)
|
|
45
|
+
Forwarder.from_resource(resp.data)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def delete(forwarder_id)
|
|
49
|
+
@api.delete_forwarder(forwarder_id)
|
|
50
|
+
nil
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def self.next_cursor(link)
|
|
54
|
+
return nil unless link.is_a?(String)
|
|
55
|
+
|
|
56
|
+
idx = link.index("page[after]=")
|
|
57
|
+
return nil if idx.nil?
|
|
58
|
+
|
|
59
|
+
token = link[(idx + "page[after]=".length)..]
|
|
60
|
+
amp = token.index("&")
|
|
61
|
+
amp ? token[0...amp] : token
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
private
|
|
65
|
+
|
|
66
|
+
def wrap_forwarder(id, name, forwarder_type, http, enabled, filter, transform, data)
|
|
67
|
+
attrs = SmplkitGeneratedClient::Audit::Forwarder.new(
|
|
68
|
+
name: name,
|
|
69
|
+
forwarder_type: forwarder_type,
|
|
70
|
+
enabled: enabled,
|
|
71
|
+
# Server-side validation rejects ``data: null`` (the field is
|
|
72
|
+
# required-non-null in the OpenAPI schema). Default to an empty
|
|
73
|
+
# hash mirroring the AuditEvents.record fix.
|
|
74
|
+
data: data || {},
|
|
75
|
+
http: ForwarderHttp.to_wire(http),
|
|
76
|
+
filter: filter,
|
|
77
|
+
transform: transform
|
|
78
|
+
)
|
|
79
|
+
resource = SmplkitGeneratedClient::Audit::ForwarderResource.new(
|
|
80
|
+
id: id ? id.to_s : "",
|
|
81
|
+
type: "forwarder",
|
|
82
|
+
attributes: attrs
|
|
83
|
+
)
|
|
84
|
+
SmplkitGeneratedClient::Audit::ForwarderResponse.new(data: resource)
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Sub-namespace for the per-forwarder delivery log + per-delivery retry.
|
|
89
|
+
class ForwarderDeliveries
|
|
90
|
+
attr_reader :actions
|
|
91
|
+
|
|
92
|
+
def initialize(api)
|
|
93
|
+
@api = api
|
|
94
|
+
@actions = DeliveryActions.new(api)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def list(forwarder_id, status: nil, created_at_range: nil, page_size: nil, page_after: nil)
|
|
98
|
+
opts = {}
|
|
99
|
+
opts[:filter_status] = status if status
|
|
100
|
+
opts[:filter_created_at] = created_at_range if created_at_range
|
|
101
|
+
opts[:page_size] = page_size if page_size
|
|
102
|
+
opts[:page_after] = page_after if page_after
|
|
103
|
+
resp = @api.list_forwarder_deliveries(forwarder_id, opts)
|
|
104
|
+
deliveries = (resp.data || []).map { |r| ForwarderDelivery.from_resource(r) }
|
|
105
|
+
ListDeliveriesPage.new(deliveries, Forwarders.next_cursor(resp.links&._next))
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# +client.audit.forwarders.deliveries.actions.retry(forwarder_id, delivery_id)+
|
|
110
|
+
class DeliveryActions
|
|
111
|
+
def initialize(api)
|
|
112
|
+
@api = api
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def retry(forwarder_id, delivery_id)
|
|
116
|
+
resp = @api.retry_forwarder_delivery(forwarder_id, delivery_id)
|
|
117
|
+
ForwarderDelivery.from_resource(resp.data)
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# +client.audit.forwarders.actions.retry_failed_deliveries(forwarder_id)+
|
|
122
|
+
class ForwarderActions
|
|
123
|
+
def initialize(api)
|
|
124
|
+
@api = api
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def retry_failed_deliveries(forwarder_id)
|
|
128
|
+
resp = @api.retry_failed_forwarder_deliveries(forwarder_id)
|
|
129
|
+
RetryFailedDeliveriesSummary.new(
|
|
130
|
+
attempted: resp.attempted,
|
|
131
|
+
succeeded: resp.succeeded,
|
|
132
|
+
failed: resp.failed
|
|
133
|
+
)
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# ----------------------------------------------------------------------
|
|
138
|
+
# Public-facing model structs
|
|
139
|
+
# ----------------------------------------------------------------------
|
|
140
|
+
|
|
141
|
+
HttpHeader = Struct.new(:name, :value, keyword_init: true)
|
|
142
|
+
|
|
143
|
+
# rubocop:disable Lint/StructNewOverride -- ``:method`` matches the
|
|
144
|
+
# API attribute and shadowing Struct#method is the expected ergonomics.
|
|
145
|
+
ForwarderHttp = Struct.new(:method, :url, :headers, :body, :success_status, keyword_init: true) do
|
|
146
|
+
def initialize(method: "POST", url: "", headers: nil, body: nil, success_status: "2xx")
|
|
147
|
+
super(method: method, url: url, headers: headers || [], body: body, success_status: success_status)
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def self.to_wire(src)
|
|
151
|
+
h = src.is_a?(Hash) ? new(**src) : src
|
|
152
|
+
SmplkitGeneratedClient::Audit::ForwarderHttp.new(
|
|
153
|
+
method: h.method,
|
|
154
|
+
url: h.url,
|
|
155
|
+
headers: (h.headers || []).map do |hdr|
|
|
156
|
+
name, value = if hdr.is_a?(Hash)
|
|
157
|
+
[hdr[:name] || hdr["name"],
|
|
158
|
+
hdr[:value] || hdr["value"]]
|
|
159
|
+
else
|
|
160
|
+
[hdr.name, hdr.value]
|
|
161
|
+
end
|
|
162
|
+
SmplkitGeneratedClient::Audit::HttpHeader.new(name: name, value: value)
|
|
163
|
+
end,
|
|
164
|
+
body: h.body,
|
|
165
|
+
success_status: h.success_status
|
|
166
|
+
)
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def self.from_wire(src)
|
|
170
|
+
return new if src.nil?
|
|
171
|
+
|
|
172
|
+
new(
|
|
173
|
+
method: src.method || "POST",
|
|
174
|
+
url: src.url || "",
|
|
175
|
+
headers: (src.headers || []).map { |h| HttpHeader.new(name: h.name, value: h.value) },
|
|
176
|
+
body: src.body,
|
|
177
|
+
success_status: src.success_status || "2xx"
|
|
178
|
+
)
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
# rubocop:enable Lint/StructNewOverride
|
|
182
|
+
|
|
183
|
+
# rubocop:disable Lint/StructNewOverride -- ``:filter`` matches the
|
|
184
|
+
# API attribute and shadowing Struct#filter is the expected ergonomics.
|
|
185
|
+
Forwarder = Struct.new(
|
|
186
|
+
:id, :name, :slug, :forwarder_type, :enabled,
|
|
187
|
+
:filter, :transform, :http, :data,
|
|
188
|
+
:created_at, :updated_at, :deleted_at, :version,
|
|
189
|
+
keyword_init: true
|
|
190
|
+
) do
|
|
191
|
+
def self.from_resource(resource)
|
|
192
|
+
a = resource.attributes
|
|
193
|
+
new(
|
|
194
|
+
id: resource.id,
|
|
195
|
+
name: a.name,
|
|
196
|
+
slug: a.slug,
|
|
197
|
+
forwarder_type: a.forwarder_type,
|
|
198
|
+
enabled: a.enabled.nil? || a.enabled,
|
|
199
|
+
filter: a.filter,
|
|
200
|
+
transform: a.transform,
|
|
201
|
+
http: ForwarderHttp.from_wire(a.http),
|
|
202
|
+
data: a.data || {},
|
|
203
|
+
created_at: a.created_at,
|
|
204
|
+
updated_at: a.updated_at,
|
|
205
|
+
deleted_at: a.deleted_at,
|
|
206
|
+
version: a.version
|
|
207
|
+
)
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
# rubocop:enable Lint/StructNewOverride
|
|
211
|
+
|
|
212
|
+
ListForwardersPage = Struct.new(:forwarders, :next_cursor)
|
|
213
|
+
|
|
214
|
+
ForwarderDelivery = Struct.new(
|
|
215
|
+
:id, :forwarder_id, :event_id, :attempt_number, :status,
|
|
216
|
+
:request, :response_status, :response_body, :latency_ms, :error, :created_at,
|
|
217
|
+
keyword_init: true
|
|
218
|
+
) do
|
|
219
|
+
def self.from_resource(resource)
|
|
220
|
+
a = resource.attributes
|
|
221
|
+
new(
|
|
222
|
+
id: resource.id,
|
|
223
|
+
forwarder_id: a.forwarder_id,
|
|
224
|
+
event_id: a.event_id,
|
|
225
|
+
attempt_number: a.attempt_number,
|
|
226
|
+
status: a.status,
|
|
227
|
+
request: a.request,
|
|
228
|
+
response_status: a.response_status,
|
|
229
|
+
response_body: a.response_body,
|
|
230
|
+
latency_ms: a.latency_ms,
|
|
231
|
+
error: a.error,
|
|
232
|
+
created_at: a.created_at
|
|
233
|
+
)
|
|
234
|
+
end
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
ListDeliveriesPage = Struct.new(:deliveries, :next_cursor)
|
|
238
|
+
|
|
239
|
+
RetryFailedDeliveriesSummary = Struct.new(:attempted, :succeeded, :failed, keyword_init: true)
|
|
240
|
+
|
|
241
|
+
TestForwarderResult = Struct.new(
|
|
242
|
+
:succeeded, :response_status, :response_headers, :response_body, :latency_ms, :error,
|
|
243
|
+
keyword_init: true
|
|
244
|
+
)
|
|
245
|
+
end
|
|
246
|
+
end
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Smplkit
|
|
4
|
+
module Audit
|
|
5
|
+
# +client.audit.functions.test_forwarder.actions.execute(...)+
|
|
6
|
+
class Functions
|
|
7
|
+
attr_reader :test_forwarder
|
|
8
|
+
|
|
9
|
+
def initialize(api)
|
|
10
|
+
@test_forwarder = TestForwarderNamespace.new(api)
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# Sub-namespace for the test_forwarder action.
|
|
15
|
+
class TestForwarderNamespace
|
|
16
|
+
attr_reader :actions
|
|
17
|
+
|
|
18
|
+
def initialize(api)
|
|
19
|
+
@actions = TestForwarderActions.new(api)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# +execute+ is a server-side proxy that lets the console preview a
|
|
24
|
+
# destination without browser CORS getting in the way. The audit
|
|
25
|
+
# service applies its SSRF guard before resolving the URL —
|
|
26
|
+
# private/loopback/link-local addresses (incl. the EC2 IMDS at
|
|
27
|
+
# +169.254.169.254+) and disallowed ports are rejected.
|
|
28
|
+
class TestForwarderActions
|
|
29
|
+
def initialize(api)
|
|
30
|
+
@api = api
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def execute(url:, method: "POST", headers: nil, body: nil,
|
|
34
|
+
success_status: "2xx", timeout_ms: nil)
|
|
35
|
+
req = SmplkitGeneratedClient::Audit::TestForwarderRequest.new(
|
|
36
|
+
url: url,
|
|
37
|
+
method: method,
|
|
38
|
+
headers: (headers || []).map do |h|
|
|
39
|
+
name, value = h.is_a?(Hash) ? [h[:name] || h["name"], h[:value] || h["value"]] : [h.name, h.value]
|
|
40
|
+
SmplkitGeneratedClient::Audit::HttpHeader.new(name: name, value: value)
|
|
41
|
+
end,
|
|
42
|
+
body: body,
|
|
43
|
+
success_status: success_status,
|
|
44
|
+
timeout_ms: timeout_ms
|
|
45
|
+
)
|
|
46
|
+
resp = @api.execute_test_forwarder(req)
|
|
47
|
+
TestForwarderResult.new(
|
|
48
|
+
succeeded: resp.succeeded || false,
|
|
49
|
+
response_status: resp.response_status,
|
|
50
|
+
response_headers: resp.response_headers || {},
|
|
51
|
+
response_body: resp.response_body || "",
|
|
52
|
+
latency_ms: resp.latency_ms,
|
|
53
|
+
error: resp.error
|
|
54
|
+
)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
data/lib/smplkit.rb
CHANGED
|
@@ -64,6 +64,8 @@ require_relative "smplkit/management/buffer"
|
|
|
64
64
|
require_relative "smplkit/management/client"
|
|
65
65
|
require_relative "smplkit/audit/buffer"
|
|
66
66
|
require_relative "smplkit/audit/events"
|
|
67
|
+
require_relative "smplkit/audit/forwarders"
|
|
68
|
+
require_relative "smplkit/audit/functions"
|
|
67
69
|
require_relative "smplkit/audit/client"
|
|
68
70
|
require_relative "smplkit/client"
|
|
69
71
|
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: smplkit
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.0.
|
|
4
|
+
version: 1.0.24
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Smpl Solutions LLC
|
|
@@ -583,6 +583,8 @@ files:
|
|
|
583
583
|
- lib/smplkit/audit/buffer.rb
|
|
584
584
|
- lib/smplkit/audit/client.rb
|
|
585
585
|
- lib/smplkit/audit/events.rb
|
|
586
|
+
- lib/smplkit/audit/forwarders.rb
|
|
587
|
+
- lib/smplkit/audit/functions.rb
|
|
586
588
|
- lib/smplkit/client.rb
|
|
587
589
|
- lib/smplkit/config/client.rb
|
|
588
590
|
- lib/smplkit/config/helpers.rb
|