smplkit 1.0.23 → 1.0.25

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bff1572898b8af4d21af85f39f82e0e7b684173ef64b86163c81dacf20563028
4
- data.tar.gz: 50adc24985da3c81d82ed2847716e333c8d9589f74fc6eb86a7d53f6b4ae054e
3
+ metadata.gz: c5467982be79b924356216ec4b8a4158589ac70802cefbee82c819f5d390281d
4
+ data.tar.gz: 93f707d0f9951956db82350eb298d4a14f42ccb2effca770da64f9084e6908d4
5
5
  SHA512:
6
- metadata.gz: bcb2099f897e6a2590abdf0bf56067bed0b0e876620a93d34fb074d10c30d81e081923024301f5a10d8a85230fdf8081f642957c3e368bb0b41307e15f1fe0f7
7
- data.tar.gz: 4e8ebcafe5029e2f49f7518a1b103c454aab008ea38f0e8af4ff65732f662deeb4ded0426973185935fbf4948db0b93abd2f9b0945c02c3179284667f57abad4
6
+ metadata.gz: 50b0fd8883a16c7b2aff012f26c956a6b5c2f1e3501cb08a02b5641fa360eb1ca60cca85acb0290a141f416497be3a0f0c3a62ced5c6593a357667a149911dec
7
+ data.tar.gz: 36731f5638835a9939bd712574377f4f6ad7279883ad535f430c986173322c1d30044f95f2b47225c67b85c8eb2d5212ca9e736c4d6f5989b05b8c7260c43d68
@@ -8,17 +8,26 @@ 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
- def initialize(api_key:, base_url:, timeout: 10.0)
13
+ SDK_OWNED_HEADERS = %w[authorization content-type user-agent].freeze
14
+
15
+ def initialize(api_key:, base_url:, timeout: 10.0, extra_headers: nil)
14
16
  cfg = SmplkitGeneratedClient::Audit::Configuration.new
15
17
  cfg.host = URI.parse(base_url).host
16
18
  cfg.scheme = URI.parse(base_url).scheme
17
19
  cfg.access_token = api_key
18
20
  cfg.timeout = timeout
19
21
  api_client = SmplkitGeneratedClient::Audit::ApiClient.new(cfg)
20
- api = SmplkitGeneratedClient::Audit::EventsApi.new(api_client)
21
- @events = Events.new(api)
22
+ api_client.default_headers["User-Agent"] = "smplkit-ruby-sdk/#{Smplkit::VERSION}"
23
+ extra_headers&.each do |k, v|
24
+ api_client.default_headers[k] = v unless SDK_OWNED_HEADERS.include?(k.downcase)
25
+ end
26
+ events_api = SmplkitGeneratedClient::Audit::EventsApi.new(api_client)
27
+ forwarders_api = SmplkitGeneratedClient::Audit::ForwardersApi.new(api_client)
28
+ @events = Events.new(events_api)
29
+ @forwarders = Forwarders.new(forwarders_api)
30
+ @functions = Functions.new(forwarders_api)
22
31
  end
23
32
 
24
33
  def _close
@@ -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
@@ -43,7 +43,8 @@ module Smplkit
43
43
  end
44
44
 
45
45
  def initialize(api_key: nil, environment: nil, service: nil, profile: nil, # rubocop:disable Metrics/AbcSize
46
- base_domain: nil, scheme: nil, debug: nil, telemetry: nil)
46
+ base_domain: nil, scheme: nil, debug: nil, telemetry: nil,
47
+ extra_headers: nil)
47
48
  cfg = ConfigResolution.resolve_config(
48
49
  profile: profile, api_key: api_key, base_domain: base_domain, scheme: scheme,
49
50
  environment: environment, service: service, debug: debug, telemetry: telemetry
@@ -67,7 +68,8 @@ module Smplkit
67
68
  mgmt_cfg = ConfigResolution::ResolvedManagementConfig.new(
68
69
  api_key: cfg.api_key, base_domain: cfg.base_domain, scheme: cfg.scheme, debug: cfg.debug
69
70
  )
70
- @manage = ManagementClient.from_resolved(mgmt_cfg)
71
+ @extra_headers = extra_headers
72
+ @manage = ManagementClient.from_resolved(mgmt_cfg, extra_headers: extra_headers)
71
73
 
72
74
  app_url = ConfigResolution.service_url(cfg.scheme, "app", cfg.base_domain)
73
75
  flags_url = ConfigResolution.service_url(cfg.scheme, "flags", cfg.base_domain)
@@ -87,7 +89,8 @@ module Smplkit
87
89
  flags_base_url: flags_url, app_base_url: app_url)
88
90
  @logging = Logging::LoggingClient.new(self, manage: @manage, metrics: @metrics,
89
91
  logging_base_url: logging_url, app_base_url: app_url)
90
- @audit = Audit::AuditClient.new(api_key: cfg.api_key, base_url: audit_url)
92
+ @audit = Audit::AuditClient.new(api_key: cfg.api_key, base_url: audit_url,
93
+ extra_headers: extra_headers)
91
94
 
92
95
  @closed = false
93
96
  schedule_periodic_flush
@@ -170,6 +173,7 @@ module Smplkit
170
173
  def _api_key = @api_key
171
174
  def _app_base_url = @app_base_url
172
175
  def _metrics = @metrics
176
+ def _extra_headers = @extra_headers
173
177
 
174
178
  def _ensure_ws
175
179
  @_ensure_ws ||= begin
@@ -30,12 +30,12 @@ module Smplkit
30
30
  attr_reader :contexts, :context_types, :environments, :account_settings,
31
31
  :config, :flags, :loggers, :log_groups
32
32
 
33
- def self.from_resolved(resolved)
34
- new(_resolved: resolved)
33
+ def self.from_resolved(resolved, extra_headers: nil)
34
+ new(_resolved: resolved, extra_headers: extra_headers)
35
35
  end
36
36
 
37
37
  def initialize(api_key: nil, base_domain: nil, scheme: nil, profile: nil,
38
- debug: nil, _resolved: nil)
38
+ debug: nil, _resolved: nil, extra_headers: nil)
39
39
  cfg = _resolved ||
40
40
  ConfigResolution.resolve_management_config(
41
41
  api_key: api_key, base_domain: base_domain, scheme: scheme,
@@ -45,6 +45,7 @@ module Smplkit
45
45
 
46
46
  @resolved = cfg
47
47
 
48
+ @extra_headers = extra_headers
48
49
  @app_api_client = build_api_client(SmplkitGeneratedClient::App, "app", cfg)
49
50
  @config_api_client = build_api_client(SmplkitGeneratedClient::Config, "config", cfg)
50
51
  @flags_api_client = build_api_client(SmplkitGeneratedClient::Flags, "flags", cfg)
@@ -71,6 +72,8 @@ module Smplkit
71
72
  def _flags_http = @flags_api_client
72
73
  def _logging_http = @logging_api_client
73
74
 
75
+ SDK_OWNED_HEADERS = %w[authorization content-type user-agent].freeze
76
+
74
77
  private
75
78
 
76
79
  def build_api_client(generated_module, subdomain, cfg)
@@ -82,6 +85,9 @@ module Smplkit
82
85
  configuration.debugging = cfg.debug
83
86
  generated_module::ApiClient.new(configuration).tap do |client|
84
87
  client.default_headers["User-Agent"] = "smplkit-ruby-sdk/#{Smplkit::VERSION}"
88
+ @extra_headers&.each do |k, v|
89
+ client.default_headers[k] = v unless SDK_OWNED_HEADERS.include?(k.downcase)
90
+ end
85
91
  end
86
92
  end
87
93
 
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.23
4
+ version: 1.0.25
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