apptrail-application-events-sdk 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.md +93 -0
- data/lib/apptrail-application-events-sdk.rb +170 -0
- data/lib/raw-event-schema.json +89 -0
- metadata +88 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 0a56d565f6da67bd2bc4bbfb36c20f599e0ef18bd7ec0f5e969caae6c592409c
|
4
|
+
data.tar.gz: b3c67d43208b9a8eb134bb529d023c33c24bae80b4485ed41dcc6ca224865131
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 9955cd66982d262361d7cbfdd6672ce4f34ff03e4caf65fc883ddcede9ba0fc2d3386b7fe93e32e2f1cbeed1fc0ee3f1d4450c87131eb7e94491ce6f182ed5ce
|
7
|
+
data.tar.gz: 76618a0b1bd9f2800a37f167b2781210664da5bb4458a4be29c50d842d14d0e8c2f46bdafd4c3d353e1652625c08bd3062c007233a18b763c5f373a01010a0ce
|
data/README.md
ADDED
@@ -0,0 +1,93 @@
|
|
1
|
+
# Apptrail Application Events SDK for Ruby
|
2
|
+
|
3
|
+
You can use the Apptrail Application Events SDK for Ruby to send audit logs from your
|
4
|
+
Ruby applications to your customers.
|
5
|
+
|
6
|
+
## Learn more
|
7
|
+
|
8
|
+
- [Working with events](https://apptrail.com/docs/applications/guide/working-with-events/overview)
|
9
|
+
- [Full SDK Reference](https://apptrail.com/docs/applications/guide/working-with-events/using-the-events-sdk/application-events-sdk-ruby)
|
10
|
+
- [Developer Guide](https://apptrail.com/docs/applications/guide)
|
11
|
+
- [Apptrail](https://apptrail.com)
|
12
|
+
|
13
|
+
## Notes and tips
|
14
|
+
|
15
|
+
- Instantiate the client once at the top of your application and reuse it to prevent unnecessary recreation.
|
16
|
+
|
17
|
+
## Installing
|
18
|
+
|
19
|
+
```bash
|
20
|
+
gem "apptrail-application-events-sdk"
|
21
|
+
```
|
22
|
+
|
23
|
+
Inside of your Ruby program, run:
|
24
|
+
|
25
|
+
```
|
26
|
+
require "apptrail-application-events-sdk"
|
27
|
+
```
|
28
|
+
|
29
|
+
## Instantiating client
|
30
|
+
|
31
|
+
```ruby
|
32
|
+
require "apptrail-application-events-sdk"
|
33
|
+
|
34
|
+
my_api_key = loadMySecretApiKey()
|
35
|
+
my_region = "us-west-2"
|
36
|
+
|
37
|
+
events_client = Apptrail::ApptrailEventsClient.new(
|
38
|
+
region: my_region,
|
39
|
+
apiKey: my_api_key,
|
40
|
+
)
|
41
|
+
```
|
42
|
+
|
43
|
+
## Sending an event
|
44
|
+
|
45
|
+
```ruby
|
46
|
+
event = {
|
47
|
+
tenantId: "cust_MGY4MmYzNDMtZjEwOC00OWI",
|
48
|
+
eventName: "CreateRepository",
|
49
|
+
eventTime: "2022-01-26T06:01:00Z",
|
50
|
+
actor: {
|
51
|
+
id: "acct_MmRlODllZDctM2I0Yi0",
|
52
|
+
details: {
|
53
|
+
type: "account",
|
54
|
+
name: "API Access",
|
55
|
+
},
|
56
|
+
},
|
57
|
+
resources: [
|
58
|
+
{
|
59
|
+
id: "repo_YWI5NjkzY2UtNzI1Ny00N",
|
60
|
+
details: {
|
61
|
+
repositoryType: "V2",
|
62
|
+
},
|
63
|
+
},
|
64
|
+
],
|
65
|
+
sourceIpAddress: "103.6.179.245",
|
66
|
+
userAgent: "Apache-HttpClient/4.5.3 (Java/11.0.11)",
|
67
|
+
tags: {
|
68
|
+
severity: "LOW",
|
69
|
+
},
|
70
|
+
eventDetails: {
|
71
|
+
request: {
|
72
|
+
repositoryName: "my-repository",
|
73
|
+
},
|
74
|
+
},
|
75
|
+
}
|
76
|
+
|
77
|
+
events_client.putEvent(event)
|
78
|
+
```
|
79
|
+
|
80
|
+
## Handling errors
|
81
|
+
|
82
|
+
As a best practice, you should handle errors while sending events, especially if you are sending critical logs. The Events client includes automatic retries with backoff, but errors can happen due to rare server issues or client side issues.
|
83
|
+
|
84
|
+
You can choose what to do with failing events. For example, you may sideline them to disk, or a dead letter queue for retry or remediation.
|
85
|
+
|
86
|
+
```ruby
|
87
|
+
begin
|
88
|
+
events_client.putEvent(event);
|
89
|
+
rescue ApptrailError => e
|
90
|
+
puts e
|
91
|
+
# handle error
|
92
|
+
end
|
93
|
+
```
|
@@ -0,0 +1,170 @@
|
|
1
|
+
require 'logger'
|
2
|
+
require 'stringio'
|
3
|
+
require "base64"
|
4
|
+
require "json"
|
5
|
+
require 'securerandom'
|
6
|
+
require "http"
|
7
|
+
require "json-schema"
|
8
|
+
require "retriable"
|
9
|
+
|
10
|
+
#HTTP.default_options = HTTP::Options.new(features: {logging: { logger: Logger.new(STDOUT)}})
|
11
|
+
|
12
|
+
module Apptrail
|
13
|
+
class << self
|
14
|
+
# Defines the logger used for debugging for the Apptrail module.
|
15
|
+
# For example, log to STDOUT by setting this to Logger.new(STDOUT).
|
16
|
+
#
|
17
|
+
# @return [Logger]
|
18
|
+
attr_accessor :logger
|
19
|
+
|
20
|
+
def logger
|
21
|
+
@logger ||= Logger.new($stdout).tap do |log|
|
22
|
+
log.progname = self.name
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
class ApptrailError < StandardError; end
|
30
|
+
class ApptrailRetryableError < ApptrailError; end
|
31
|
+
|
32
|
+
Retriable.configure do |c|
|
33
|
+
c.contexts[:s3] = {
|
34
|
+
tries: 3,
|
35
|
+
base_interval: 0.04,
|
36
|
+
on: Apptrail::ApptrailRetryableError,
|
37
|
+
on_retry: Proc.new { puts 'Retrying...' },
|
38
|
+
max_interval: 0.12,
|
39
|
+
max_elapsed_time: 4,
|
40
|
+
rand_factor: 0.2,
|
41
|
+
}
|
42
|
+
end
|
43
|
+
|
44
|
+
# You can use the Apptrail Application Events Client to send
|
45
|
+
# audit log events from your Ruby applications.
|
46
|
+
#
|
47
|
+
# See [Sending events](https://apptrail.com/docs/applications/guide/working-with-events/overview).
|
48
|
+
class ApptrailEventsClient
|
49
|
+
@_form_data = nil
|
50
|
+
@_upload_url = nil
|
51
|
+
|
52
|
+
SCHEMA = JSON.parse(File.read(
|
53
|
+
File.expand_path('../raw-event-schema.json', __FILE__)
|
54
|
+
)).freeze
|
55
|
+
private_constant :SCHEMA
|
56
|
+
|
57
|
+
def inspect
|
58
|
+
"#<ApptrailEventsClient:#{object_id}>"
|
59
|
+
end
|
60
|
+
|
61
|
+
def to_s
|
62
|
+
"ApptrailEventsClient{}"
|
63
|
+
end
|
64
|
+
|
65
|
+
def initialize(region:, api_key:)
|
66
|
+
@_region = region
|
67
|
+
begin
|
68
|
+
parsed_key = Base64.urlsafe_decode64(api_key)
|
69
|
+
application_id, *_ = parsed_key.split(',', 3)
|
70
|
+
@_application_id = application_id
|
71
|
+
rescue
|
72
|
+
raise ArgumentError, "Invalid API Key."
|
73
|
+
end
|
74
|
+
|
75
|
+
@_base_api_url = "https://events.#{region}.apptrail.com/applications/session"
|
76
|
+
@_api_key = api_key
|
77
|
+
end
|
78
|
+
|
79
|
+
# Send a single audit event to log to Apptrail.
|
80
|
+
#
|
81
|
+
# @param events [Array<ApptrailEvent>]] An array of events to log. See [Apptrail Event Format](https://apptrail.com/docs/applications/guide/event-format) for a full description of the event format.
|
82
|
+
def put_event(event)
|
83
|
+
put_events([event])
|
84
|
+
end
|
85
|
+
|
86
|
+
|
87
|
+
# Send a list of up to 1000 audit events to log to Apptrail.
|
88
|
+
#
|
89
|
+
# @param events [Array<ApptrailEvent>]] An array of events to log. See [Apptrail Event Format](https://apptrail.com/docs/applications/guide/event-format) for a full description of the event format.
|
90
|
+
def put_events(events)
|
91
|
+
unless events.is_a?(Array)
|
92
|
+
raise TypeError, "Must pass in array of events."
|
93
|
+
end
|
94
|
+
unless events.length <= 1000
|
95
|
+
raise ArgumentError, "Can not send more than 1000 events in a single PutEvents call."
|
96
|
+
end
|
97
|
+
|
98
|
+
begin
|
99
|
+
JSON::Validator.validate!(SCHEMA, events, :list => true, :parse_data => false)
|
100
|
+
rescue JSON::Schema::ValidationError => e
|
101
|
+
raise Apptrail::ApptrailError.new(e.message)
|
102
|
+
end
|
103
|
+
|
104
|
+
if @_upload_url.nil? || @_form_data.nil?
|
105
|
+
_refresh_post_policy()
|
106
|
+
end
|
107
|
+
|
108
|
+
content = ""
|
109
|
+
events.each do |evt|
|
110
|
+
content << JSON.generate(evt)
|
111
|
+
content << "\n"
|
112
|
+
end
|
113
|
+
|
114
|
+
filename = SecureRandom.uuid + ".jsonl"
|
115
|
+
s3_key = File.join(@_application_id, filename)
|
116
|
+
|
117
|
+
form_file = HTTP::FormData::File.new(StringIO.new(content), :filename => filename)
|
118
|
+
|
119
|
+
new_form_opts = {
|
120
|
+
**@_form_data,
|
121
|
+
:key => s3_key,
|
122
|
+
:file => form_file
|
123
|
+
}
|
124
|
+
|
125
|
+
begin
|
126
|
+
Retriable.with_context(:s3) do
|
127
|
+
resp = HTTP.post(@_upload_url, :form => new_form_opts)
|
128
|
+
if !resp.status.success?
|
129
|
+
if (resp.status.server_error?)
|
130
|
+
raise ApptrailRetryableError.new("Server Error while putting events.")
|
131
|
+
elsif (resp.status.client_error?)
|
132
|
+
if (resp.status.code === 403 && resp.body.to_s.include?("Policy expired"))
|
133
|
+
_refresh_post_policy()
|
134
|
+
raise ApptrailRetryableError.new("Session expired.")
|
135
|
+
else
|
136
|
+
puts resp.body.to_s
|
137
|
+
raise ApptrailError.new("Error while putting events.")
|
138
|
+
end
|
139
|
+
else
|
140
|
+
raise ApptrailError.new("Error while putting events.")
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
rescue
|
145
|
+
raise ApptrailError.new("Failed to put #{events.length} events. Encountered error.")
|
146
|
+
else
|
147
|
+
Apptrail::logger.info("Successfully wrote #{events.length} events.")
|
148
|
+
end
|
149
|
+
|
150
|
+
end
|
151
|
+
|
152
|
+
private
|
153
|
+
|
154
|
+
def _refresh_post_policy
|
155
|
+
puts @_base_api_url
|
156
|
+
resp = HTTP
|
157
|
+
.auth("Bearer #{@_api_key}")
|
158
|
+
.get(@_base_api_url)
|
159
|
+
if !resp.status.success?
|
160
|
+
raise ApptrailError.new("Error refreshing credentials, status code: #{resp.code}")
|
161
|
+
end
|
162
|
+
result = JSON.parse(resp.body.to_s)
|
163
|
+
@_upload_url = result["uploadUrl"]
|
164
|
+
@_form_data = result["form"]
|
165
|
+
end
|
166
|
+
|
167
|
+
|
168
|
+
end
|
169
|
+
|
170
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
{
|
2
|
+
"type": "object",
|
3
|
+
"description": "An Apptrail event is a record of a an activity performed on your customer resources in your application. \n\n @see https://apptrail.com/docs/applications/guide/working-with-events/overview",
|
4
|
+
"properties": {
|
5
|
+
"tenantId": {
|
6
|
+
"type": "string",
|
7
|
+
"pattern": "^[A-Za-z0-9_\\-+=]*$",
|
8
|
+
"description": "The ID of the tenant this event is associated with and will be delivered to. For more on tenants @see https://apptrail.com/docs/applications/guide/working-with-tenants."
|
9
|
+
},
|
10
|
+
"eventTime": {
|
11
|
+
"type": "string",
|
12
|
+
"format": "date-time",
|
13
|
+
"description": "The time the event occurred. Formatted as an ISO 8601 formatted string, e.g. 2021-12-06T16:09:38Z."
|
14
|
+
},
|
15
|
+
"eventName": {
|
16
|
+
"type": "string",
|
17
|
+
"description": "The name of the event as a string. You should represent unique events with unique names. For example if you are adding audit logs to an API, event names could correspond to your API methods."
|
18
|
+
},
|
19
|
+
"actor": {
|
20
|
+
"type": "object",
|
21
|
+
"description": "Actor specifies the identity that performed the action being logged. This may be a user or machine client. Consumers can filter based on this attribute.",
|
22
|
+
"required": [
|
23
|
+
"id"
|
24
|
+
],
|
25
|
+
"properties": {
|
26
|
+
"id": {
|
27
|
+
"type": "string",
|
28
|
+
"description": "A string representing the identity of the actor."
|
29
|
+
},
|
30
|
+
"details": {
|
31
|
+
"type": "object",
|
32
|
+
"description": "Additional arbitrary metadata related to the actor."
|
33
|
+
}
|
34
|
+
}
|
35
|
+
},
|
36
|
+
"resources": {
|
37
|
+
"type": "array",
|
38
|
+
"description": "If applicable, contains information about the relevant resources that the action this event is recording is related to.",
|
39
|
+
"items": {
|
40
|
+
"type": "object",
|
41
|
+
"required": [
|
42
|
+
"id"
|
43
|
+
],
|
44
|
+
"properties": {
|
45
|
+
"id": {
|
46
|
+
"type": "string",
|
47
|
+
"description": "A unique identifier for this resource."
|
48
|
+
},
|
49
|
+
"details": {
|
50
|
+
"type": "object",
|
51
|
+
"description": "Additional arbitrary metadata related to the resource."
|
52
|
+
}
|
53
|
+
}
|
54
|
+
}
|
55
|
+
},
|
56
|
+
"sourceIpAddress": {
|
57
|
+
"type": "string",
|
58
|
+
"description": "The IP address the activity/request being recorded was made from.",
|
59
|
+
"oneOf": [
|
60
|
+
{
|
61
|
+
"format": "ipv4"
|
62
|
+
},
|
63
|
+
{
|
64
|
+
"format": "ipv6"
|
65
|
+
}
|
66
|
+
]
|
67
|
+
},
|
68
|
+
"userAgent": {
|
69
|
+
"type": "string",
|
70
|
+
"description": "The agent through which the request was made, e.g. `Mozilla/5.0` or `python-requests/2.20.1`."
|
71
|
+
},
|
72
|
+
"eventDetails": {
|
73
|
+
"type": "object",
|
74
|
+
"description": "Any additional custom information as a JSON object can be included in this field."
|
75
|
+
},
|
76
|
+
"tags": {
|
77
|
+
"type": "object",
|
78
|
+
"description": "Tags are key value metadata that you associate with events. Users can search and filter events based on these tags.",
|
79
|
+
"additionalProperties": {
|
80
|
+
"type": "string"
|
81
|
+
}
|
82
|
+
}
|
83
|
+
},
|
84
|
+
"required": [
|
85
|
+
"eventTime",
|
86
|
+
"eventName",
|
87
|
+
"tenantId"
|
88
|
+
]
|
89
|
+
}
|
metadata
ADDED
@@ -0,0 +1,88 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: apptrail-application-events-sdk
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Apptrail Team
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2022-01-27 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: http
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '5.1'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '5.1'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: retriable
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '3.1'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '3.1'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: json-schema
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '2.8'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '2.8'
|
55
|
+
description: Send audit logs from your Ruby applications using the Apptrail Application
|
56
|
+
Events SDK for Ruby.
|
57
|
+
email: support@apptrail.com
|
58
|
+
executables: []
|
59
|
+
extensions: []
|
60
|
+
extra_rdoc_files: []
|
61
|
+
files:
|
62
|
+
- README.md
|
63
|
+
- lib/apptrail-application-events-sdk.rb
|
64
|
+
- lib/raw-event-schema.json
|
65
|
+
homepage: https://apptrail.com/docs/applications/guide
|
66
|
+
licenses:
|
67
|
+
- MIT
|
68
|
+
metadata: {}
|
69
|
+
post_install_message:
|
70
|
+
rdoc_options: []
|
71
|
+
require_paths:
|
72
|
+
- lib
|
73
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
74
|
+
requirements:
|
75
|
+
- - ">="
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
78
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
requirements: []
|
84
|
+
rubygems_version: 3.3.3
|
85
|
+
signing_key:
|
86
|
+
specification_version: 4
|
87
|
+
summary: Apptrail Application Events SDK for Ruby
|
88
|
+
test_files: []
|