conrad 1.0.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.
- checksums.yaml +7 -0
- data/lib/conrad/add_timestamp.rb +52 -0
- data/lib/conrad/add_uuid.rb +24 -0
- data/lib/conrad/errors.rb +38 -0
- data/lib/conrad/json_formatter.rb +17 -0
- data/lib/conrad/recorder.rb +93 -0
- data/lib/conrad/stdout_emitter.rb +9 -0
- data/lib/conrad/version.rb +5 -0
- data/lib/conrad.rb +5 -0
- metadata +94 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: eb4af3e930e6793ecb9a869e52c0150ea2aa87753b3f062713c872b5042ea754
|
|
4
|
+
data.tar.gz: 73c9bdc8e25b35527cf7064c934ef4a5762c2705c2b6c1c22bcc56c102fab547
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: e16f4db32806aa47a4411a6b9dbc5964eb522f477f48d2e97995839636b0b66e73bbc6392f02756d12e4a9f8244d1f17d6693a17082c08a5ac623de2408495dd
|
|
7
|
+
data.tar.gz: a866c01f282c5cfa5b1b71c83d9653dd697f25252f448d3df1b772d05bcc838bf127f4fb92a36f3848372ded0aac1473ca68761620a1309a0ce00456a9a18d99
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
require 'conrad/errors'
|
|
2
|
+
|
|
3
|
+
module Conrad
|
|
4
|
+
# Used to add timestamps to an audit event in seconds or milliseconds.
|
|
5
|
+
#
|
|
6
|
+
# @!attribute [r] generator
|
|
7
|
+
# object used to generate the timestamp
|
|
8
|
+
class AddTimestamp
|
|
9
|
+
# :nodoc:
|
|
10
|
+
class Error < Conrad::Error; end
|
|
11
|
+
|
|
12
|
+
# Types of units supported for generation.
|
|
13
|
+
ALLOWED_TIME_UNITS = %i[milliseconds seconds].freeze
|
|
14
|
+
|
|
15
|
+
attr_reader :generator, :timestamp_key
|
|
16
|
+
|
|
17
|
+
# Creates a new instance of AddTimestmap processor
|
|
18
|
+
#
|
|
19
|
+
# @param units [Symbol] type of time units for the timestamp generated.
|
|
20
|
+
# Allows :seconds or :milliseconds.
|
|
21
|
+
# @param timestamp_key [Symbol] key to add to the event hash.
|
|
22
|
+
# @raise [ArgumentError] if the given units value is not one of
|
|
23
|
+
# ALLOWED_TIME_UNITS
|
|
24
|
+
def initialize(units: :milliseconds, timestamp_key: :timestamp)
|
|
25
|
+
unless ALLOWED_TIME_UNITS.include? units
|
|
26
|
+
raise ArgumentError, "Provided units of `#{units}` must be one of #{ALLOWED_TIME_UNITS}"
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
@generator = generator_from_units(units)
|
|
30
|
+
@timestamp_key = timestamp_key
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Generates and adds a timestamp to the provided Hash.
|
|
34
|
+
#
|
|
35
|
+
# @param event [Hash]
|
|
36
|
+
# @return [Hash]
|
|
37
|
+
def call(event)
|
|
38
|
+
event.merge(timestamp_key => generator.call)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
private
|
|
42
|
+
|
|
43
|
+
def generator_from_units(units)
|
|
44
|
+
case units
|
|
45
|
+
when :milliseconds then -> { (Time.now.to_f * 1000).to_i }
|
|
46
|
+
when :seconds then -> { Time.now.to_i }
|
|
47
|
+
else
|
|
48
|
+
raise UnrecognizedTimeUnit, units
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
require 'securerandom'
|
|
2
|
+
|
|
3
|
+
module Conrad
|
|
4
|
+
# Generalized processor for inserting a UUID into the event. Allows
|
|
5
|
+
# configuring the key used for insertion.
|
|
6
|
+
#
|
|
7
|
+
# @!attribute [r] uuid_key
|
|
8
|
+
# The key inserted into the event hash for the generated UUID.
|
|
9
|
+
class AddUUID
|
|
10
|
+
attr_reader :uuid_key
|
|
11
|
+
|
|
12
|
+
# @param uuid_key [Symbol] key to use for the generated UUID
|
|
13
|
+
def initialize(uuid_key = :event_uuid)
|
|
14
|
+
@uuid_key = uuid_key
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# @param event [Hash] the current event
|
|
18
|
+
#
|
|
19
|
+
# @return [Hash] the hash with the UUID inserted
|
|
20
|
+
def call(event)
|
|
21
|
+
event.merge(uuid_key => SecureRandom.uuid)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Conrad
|
|
4
|
+
# Base error class
|
|
5
|
+
class Error < StandardError
|
|
6
|
+
# :nodoc:
|
|
7
|
+
def to_s
|
|
8
|
+
'An unexpected error has occurred'
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# Error raised when the value of an event attribute is not of one of the
|
|
13
|
+
# allowed types
|
|
14
|
+
class ForbiddenValue < Error
|
|
15
|
+
def initialize(key, value)
|
|
16
|
+
@key = key
|
|
17
|
+
@value = value
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# :nodoc:
|
|
21
|
+
def to_s
|
|
22
|
+
"Key of #{@key} provided invalid value type of #{@value.class}"
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Error raised when the key of an event attribute is not of one of the
|
|
27
|
+
# allowed types
|
|
28
|
+
class ForbiddenKey < Error
|
|
29
|
+
def initialize(key)
|
|
30
|
+
@key = key
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# :nodoc:
|
|
34
|
+
def to_s
|
|
35
|
+
"Invalid key #{@key}. Keys must be either Strings or Symbols"
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
4
|
+
|
|
5
|
+
module Conrad
|
|
6
|
+
# Formats a given Hash into a presentable JSON format.
|
|
7
|
+
class JSONFormatter
|
|
8
|
+
# Formats a given Hash into a presentable JSON format.
|
|
9
|
+
#
|
|
10
|
+
# @param event [Hash] event to be formatted
|
|
11
|
+
#
|
|
12
|
+
# @return [String] JSON formatted string
|
|
13
|
+
def call(event)
|
|
14
|
+
event.to_json
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
require 'conrad/errors'
|
|
2
|
+
require 'conrad/stdout_emitter'
|
|
3
|
+
require 'conrad/json_formatter'
|
|
4
|
+
|
|
5
|
+
module Conrad
|
|
6
|
+
# Provides the ability to record an event took place.
|
|
7
|
+
# Currently recording an event accepts a hash and passes it through the
|
|
8
|
+
# configured processors, formatter, and emitter. Each of these may transform,
|
|
9
|
+
# validate, format, and send the event as the user sees fit.
|
|
10
|
+
#
|
|
11
|
+
# @!attribute [r] formatter
|
|
12
|
+
# Configured formatter for creating the final event. Defaults to
|
|
13
|
+
# JSONFormatter.
|
|
14
|
+
# @see Conrad::JSONFormatter
|
|
15
|
+
# @!attribute [r] emitter
|
|
16
|
+
# Configured emitter for sending the final event. Defaults to
|
|
17
|
+
# StdoutEmitter.
|
|
18
|
+
# @see Conrad::StdoutEmitter
|
|
19
|
+
# @!attribute [r] processors
|
|
20
|
+
# Configured processors for processing the event pre-formatting and
|
|
21
|
+
# emission. Defaults to an empty array.
|
|
22
|
+
class Recorder
|
|
23
|
+
attr_reader :formatter, :emitter, :processors
|
|
24
|
+
|
|
25
|
+
# All arguments passed must *explicitly* respond to a `call` method.
|
|
26
|
+
#
|
|
27
|
+
# @param formatter [#call] formatter for creating the final event
|
|
28
|
+
# @param emitter [#call] emitter for sending the final event
|
|
29
|
+
# @param processors [Array<#call>] processors for processing the event
|
|
30
|
+
# pre-formatting and emission
|
|
31
|
+
#
|
|
32
|
+
# @raise [ArgumentError] if the formatter, emitter, or any of the
|
|
33
|
+
# processors do not respond_to? `call` with a truthy value.
|
|
34
|
+
def initialize(formatter: JSONFormatter.new, emitter: StdoutEmitter.new, processors: [])
|
|
35
|
+
check_callability(formatter: formatter, emitter: emitter, processors: processors)
|
|
36
|
+
|
|
37
|
+
@formatter = formatter
|
|
38
|
+
@emitter = emitter
|
|
39
|
+
@processors = processors
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Processes the given event, formats it, then emits it. It is possible
|
|
43
|
+
# to `throw :halt_conrad_processing` to stop the processing stack. There
|
|
44
|
+
# should be no additional arguments to the `throw` call. At this point, the
|
|
45
|
+
# processing will stop and the audit event will be discarded. The formatter
|
|
46
|
+
# and the emitter will not be called.
|
|
47
|
+
#
|
|
48
|
+
# @param event [Hash] the set of key value pairs to be emitted
|
|
49
|
+
# as a single audit event. It is expected that all keys will be given as
|
|
50
|
+
# Symbols or Strings. All values should be of a type that matches the
|
|
51
|
+
# SCALAR_TYPES or an array once the processor cycle is complete but before
|
|
52
|
+
# final formatting.
|
|
53
|
+
#
|
|
54
|
+
# @raise [ForbiddenKey] when a key is neither a Symbol nor a String
|
|
55
|
+
def audit_event(event)
|
|
56
|
+
processed_event = process_event(event)
|
|
57
|
+
|
|
58
|
+
return unless processed_event
|
|
59
|
+
|
|
60
|
+
validate_event_keys(processed_event)
|
|
61
|
+
|
|
62
|
+
format_and_emit(processed_event)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
private
|
|
66
|
+
|
|
67
|
+
def process_event(event)
|
|
68
|
+
catch :halt_conrad_processing do
|
|
69
|
+
processors.reduce(event) do |previous_built_event, processor|
|
|
70
|
+
processor.call(previous_built_event)
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def format_and_emit(event)
|
|
76
|
+
emitter.call(
|
|
77
|
+
formatter.call(event)
|
|
78
|
+
)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def check_callability(formatter:, emitter:, processors:)
|
|
82
|
+
[formatter, emitter, *processors].each do |callable|
|
|
83
|
+
raise ArgumentError, "#{callable} does not respond to `#call`" unless callable.respond_to?(:call)
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def validate_event_keys(event)
|
|
88
|
+
event.each_key do |key|
|
|
89
|
+
raise ForbiddenKey, key unless key.is_a?(Symbol) || key.is_a?(String)
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
data/lib/conrad.rb
ADDED
metadata
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: conrad
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Jonathon Anderson
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: exe
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2018-12-21 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: minitest
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - "~>"
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '5.0'
|
|
20
|
+
type: :development
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - "~>"
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '5.0'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: rake
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - "~>"
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '10.0'
|
|
34
|
+
type: :development
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - "~>"
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '10.0'
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: rubocop
|
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - "~>"
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: 0.60.0
|
|
48
|
+
type: :development
|
|
49
|
+
prerelease: false
|
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - "~>"
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: 0.60.0
|
|
55
|
+
description:
|
|
56
|
+
email:
|
|
57
|
+
- jonathon.anderson@outreach.io
|
|
58
|
+
executables: []
|
|
59
|
+
extensions: []
|
|
60
|
+
extra_rdoc_files: []
|
|
61
|
+
files:
|
|
62
|
+
- lib/conrad.rb
|
|
63
|
+
- lib/conrad/add_timestamp.rb
|
|
64
|
+
- lib/conrad/add_uuid.rb
|
|
65
|
+
- lib/conrad/errors.rb
|
|
66
|
+
- lib/conrad/json_formatter.rb
|
|
67
|
+
- lib/conrad/recorder.rb
|
|
68
|
+
- lib/conrad/stdout_emitter.rb
|
|
69
|
+
- lib/conrad/version.rb
|
|
70
|
+
homepage: https://github.com/getoutreach/conrad
|
|
71
|
+
licenses:
|
|
72
|
+
- MIT
|
|
73
|
+
metadata: {}
|
|
74
|
+
post_install_message:
|
|
75
|
+
rdoc_options: []
|
|
76
|
+
require_paths:
|
|
77
|
+
- lib
|
|
78
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
79
|
+
requirements:
|
|
80
|
+
- - ">="
|
|
81
|
+
- !ruby/object:Gem::Version
|
|
82
|
+
version: '0'
|
|
83
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
84
|
+
requirements:
|
|
85
|
+
- - ">="
|
|
86
|
+
- !ruby/object:Gem::Version
|
|
87
|
+
version: '0'
|
|
88
|
+
requirements: []
|
|
89
|
+
rubyforge_project:
|
|
90
|
+
rubygems_version: 2.7.6
|
|
91
|
+
signing_key:
|
|
92
|
+
specification_version: 4
|
|
93
|
+
summary: Tool for auditing events.
|
|
94
|
+
test_files: []
|