provenance 2.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/CHANGELOG.md +19 -0
- data/LICENSE +21 -0
- data/README.md +358 -0
- data/lib/generators/provenance/install/install_generator.rb +34 -0
- data/lib/generators/provenance/install/templates/create_provenance_outbox.rb.tt +21 -0
- data/lib/generators/provenance/install/templates/initializer.rb.tt +12 -0
- data/lib/provenance/action.rb +160 -0
- data/lib/provenance/actor.rb +86 -0
- data/lib/provenance/configuration.rb +152 -0
- data/lib/provenance/context.rb +30 -0
- data/lib/provenance/controller.rb +93 -0
- data/lib/provenance/emitter.rb +66 -0
- data/lib/provenance/entity_change.rb +73 -0
- data/lib/provenance/errors.rb +12 -0
- data/lib/provenance/event.rb +42 -0
- data/lib/provenance/integrity.rb +75 -0
- data/lib/provenance/job.rb +62 -0
- data/lib/provenance/json_safe.rb +41 -0
- data/lib/provenance/middleware.rb +44 -0
- data/lib/provenance/model.rb +123 -0
- data/lib/provenance/outbox/record.rb +23 -0
- data/lib/provenance/outbox/relay.rb +81 -0
- data/lib/provenance/outbox.rb +67 -0
- data/lib/provenance/railtie.rb +35 -0
- data/lib/provenance/rake.rb +27 -0
- data/lib/provenance/recorder.rb +206 -0
- data/lib/provenance/redactor.rb +63 -0
- data/lib/provenance/registry.rb +48 -0
- data/lib/provenance/relay_job.rb +15 -0
- data/lib/provenance/rspec.rb +139 -0
- data/lib/provenance/serializers/cloud_events.rb +26 -0
- data/lib/provenance/serializers/native.rb +16 -0
- data/lib/provenance/serializers/ocsf.rb +127 -0
- data/lib/provenance/serializers.rb +31 -0
- data/lib/provenance/sinks/http.rb +81 -0
- data/lib/provenance/sinks/io.rb +24 -0
- data/lib/provenance/sinks/kafka.rb +45 -0
- data/lib/provenance/sinks/logger.rb +22 -0
- data/lib/provenance/sinks/memory.rb +29 -0
- data/lib/provenance/sinks/proc.rb +20 -0
- data/lib/provenance/sinks.rb +35 -0
- data/lib/provenance/testing.rb +36 -0
- data/lib/provenance/uuid.rb +20 -0
- data/lib/provenance/version.rb +6 -0
- data/lib/provenance.rb +166 -0
- data/lib/tasks/provenance.rake +22 -0
- data/schema/event-2.json +126 -0
- metadata +150 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Provenance
|
|
4
|
+
module Sinks
|
|
5
|
+
# Calls a block or callable with each batch.
|
|
6
|
+
class Proc
|
|
7
|
+
# @param callable [#call, nil]
|
|
8
|
+
def initialize(callable = nil, &block)
|
|
9
|
+
@callable = callable || block
|
|
10
|
+
raise ArgumentError, "proc sink needs a callable or a block" unless @callable.respond_to?(:call)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# @param batch [Array<Hash>]
|
|
14
|
+
# @return [void]
|
|
15
|
+
def deliver(batch)
|
|
16
|
+
@callable.call(batch)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "provenance/sinks/logger"
|
|
4
|
+
require "provenance/sinks/io"
|
|
5
|
+
require "provenance/sinks/http"
|
|
6
|
+
require "provenance/sinks/proc"
|
|
7
|
+
require "provenance/sinks/memory"
|
|
8
|
+
|
|
9
|
+
module Provenance
|
|
10
|
+
# Destinations for serialized events. A sink is any object responding to
|
|
11
|
+
# +#deliver(batch)+, where +batch+ is an Array of serialized events (Hashes).
|
|
12
|
+
module Sinks
|
|
13
|
+
module_function
|
|
14
|
+
|
|
15
|
+
# Builds a sink from a type symbol or returns a sink object unchanged.
|
|
16
|
+
#
|
|
17
|
+
# @param type [Symbol, #deliver]
|
|
18
|
+
# @return [#deliver]
|
|
19
|
+
def build(type, *args, **options, &block)
|
|
20
|
+
return type if type.respond_to?(:deliver)
|
|
21
|
+
|
|
22
|
+
case type.to_sym
|
|
23
|
+
when :logger then Logger.new(*args, **options)
|
|
24
|
+
when :io then IO.new(*args, **options)
|
|
25
|
+
when :http then HTTP.new(*args, **options)
|
|
26
|
+
when :proc then Proc.new(*args, &block)
|
|
27
|
+
when :memory then Memory.new
|
|
28
|
+
when :kafka
|
|
29
|
+
require "provenance/sinks/kafka"
|
|
30
|
+
Kafka.new(*args, **options)
|
|
31
|
+
else raise ConfigurationError, "unknown sink #{type.inspect}"
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Provenance
|
|
4
|
+
# Test helpers. While {capture} runs, tracking is enabled regardless of
|
|
5
|
+
# +config.enabled+ and events go to the capture instead of the outbox or sinks.
|
|
6
|
+
module Testing
|
|
7
|
+
@captures = []
|
|
8
|
+
@lock = Mutex.new
|
|
9
|
+
|
|
10
|
+
class << self
|
|
11
|
+
# Collects the native events emitted while the block runs.
|
|
12
|
+
#
|
|
13
|
+
# @return [Array<Hash>] native events
|
|
14
|
+
def capture
|
|
15
|
+
events = []
|
|
16
|
+
@lock.synchronize { @captures << events }
|
|
17
|
+
begin
|
|
18
|
+
yield
|
|
19
|
+
ensure
|
|
20
|
+
@lock.synchronize { @captures.delete_if { |c| c.equal?(events) } }
|
|
21
|
+
end
|
|
22
|
+
events
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# @return [Boolean] whether a capture is active
|
|
26
|
+
def capturing?
|
|
27
|
+
!@captures.empty?
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# @api private
|
|
31
|
+
def record(event)
|
|
32
|
+
@lock.synchronize { @captures.each { |c| c << event } }
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Provenance
|
|
4
|
+
# UUIDv7 generation (RFC 9562): 48-bit Unix millisecond timestamp followed by
|
|
5
|
+
# random bits, so ids sort by creation time.
|
|
6
|
+
module UUID
|
|
7
|
+
module_function
|
|
8
|
+
|
|
9
|
+
# @param time [Time] timestamp to embed
|
|
10
|
+
# @return [String] a UUIDv7 in canonical 8-4-4-4-12 form
|
|
11
|
+
def v7(time = Time.now)
|
|
12
|
+
ms = (time.to_r * 1000).to_i
|
|
13
|
+
bytes = [ms >> 16, ms & 0xffff].pack("Nn").b + SecureRandom.random_bytes(10)
|
|
14
|
+
bytes.setbyte(6, (bytes.getbyte(6) & 0x0f) | 0x70)
|
|
15
|
+
bytes.setbyte(8, (bytes.getbyte(8) & 0x3f) | 0x80)
|
|
16
|
+
hex = bytes.unpack1("H*")
|
|
17
|
+
[hex[0, 8], hex[8, 4], hex[12, 4], hex[16, 4], hex[20, 12]].join("-")
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
data/lib/provenance.rb
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require "securerandom"
|
|
5
|
+
require "active_support"
|
|
6
|
+
require "active_support/core_ext"
|
|
7
|
+
require "active_support/isolated_execution_state"
|
|
8
|
+
require "active_record"
|
|
9
|
+
|
|
10
|
+
require "provenance/version"
|
|
11
|
+
require "provenance/errors"
|
|
12
|
+
require "provenance/uuid"
|
|
13
|
+
require "provenance/json_safe"
|
|
14
|
+
require "provenance/configuration"
|
|
15
|
+
require "provenance/actor"
|
|
16
|
+
require "provenance/context"
|
|
17
|
+
require "provenance/entity_change"
|
|
18
|
+
require "provenance/action"
|
|
19
|
+
require "provenance/registry"
|
|
20
|
+
require "provenance/redactor"
|
|
21
|
+
require "provenance/event"
|
|
22
|
+
require "provenance/serializers"
|
|
23
|
+
require "provenance/sinks"
|
|
24
|
+
require "provenance/integrity"
|
|
25
|
+
require "provenance/emitter"
|
|
26
|
+
require "provenance/recorder"
|
|
27
|
+
require "provenance/model"
|
|
28
|
+
require "provenance/middleware"
|
|
29
|
+
require "provenance/controller"
|
|
30
|
+
require "provenance/job"
|
|
31
|
+
require "provenance/rake"
|
|
32
|
+
require "provenance/outbox"
|
|
33
|
+
|
|
34
|
+
# Provenance emits one action-level audit event per HTTP request, background job,
|
|
35
|
+
# rake task or explicit block, and delivers it to security and observability tooling.
|
|
36
|
+
#
|
|
37
|
+
# @example
|
|
38
|
+
# Provenance.configure do |c|
|
|
39
|
+
# c.sink :logger
|
|
40
|
+
# end
|
|
41
|
+
module Provenance
|
|
42
|
+
autoload :RelayJob, "provenance/relay_job"
|
|
43
|
+
|
|
44
|
+
STATE_KEY = :provenance_action
|
|
45
|
+
SUPPRESS_KEY = :provenance_suppressed
|
|
46
|
+
private_constant :STATE_KEY, :SUPPRESS_KEY
|
|
47
|
+
|
|
48
|
+
class << self
|
|
49
|
+
# Yields the global configuration and validates it afterwards.
|
|
50
|
+
#
|
|
51
|
+
# @yieldparam config [Configuration]
|
|
52
|
+
# @return [Configuration]
|
|
53
|
+
# @raise [ConfigurationError] when the resulting configuration is invalid
|
|
54
|
+
def configure
|
|
55
|
+
yield config
|
|
56
|
+
config.validate!
|
|
57
|
+
config
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# @return [Configuration] the global configuration
|
|
61
|
+
def config
|
|
62
|
+
@config ||= Configuration.new
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Replaces the global configuration with defaults. Mostly useful in tests.
|
|
66
|
+
#
|
|
67
|
+
# @return [Configuration]
|
|
68
|
+
def reset_config!
|
|
69
|
+
@config = Configuration.new
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# @return [Registry] the registry of models tracked with +has_provenance+
|
|
73
|
+
def registry
|
|
74
|
+
@registry ||= Registry.new
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# @return [Boolean] whether tracking is active for the current execution context
|
|
78
|
+
def enabled?
|
|
79
|
+
(config.enabled || Testing.capturing?) && !suppressed?
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# @return [Boolean] whether tracking is suppressed by {without}
|
|
83
|
+
def suppressed?
|
|
84
|
+
ActiveSupport::IsolatedExecutionState[SUPPRESS_KEY].to_i.positive?
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# The action currently open in this thread or fiber, if any.
|
|
88
|
+
#
|
|
89
|
+
# @return [Action, nil]
|
|
90
|
+
def current
|
|
91
|
+
ActiveSupport::IsolatedExecutionState[STATE_KEY]
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Opens an explicit action. Explicit actions are always auditable. Nested scopes
|
|
95
|
+
# attach their changes and metadata to the outermost action.
|
|
96
|
+
#
|
|
97
|
+
# @param name [String] action name, e.g. "users.import"
|
|
98
|
+
# @param actor [Object, Hash, Actor, nil] who performs the action
|
|
99
|
+
# @param metadata [Hash] arbitrary JSON-safe data
|
|
100
|
+
# @yieldparam action [Action]
|
|
101
|
+
# @return [Object] the block's return value
|
|
102
|
+
def action(name, actor: nil, metadata: {}, &block)
|
|
103
|
+
raise ArgumentError, "a block is required" unless block
|
|
104
|
+
|
|
105
|
+
track(kind: :custom, name: name, actor: actor, metadata: metadata, explicit: true, &block)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# Runs the block with change tracking and emission suppressed.
|
|
109
|
+
#
|
|
110
|
+
# @return [Object] the block's return value
|
|
111
|
+
def without
|
|
112
|
+
state = ActiveSupport::IsolatedExecutionState
|
|
113
|
+
state[SUPPRESS_KEY] = state[SUPPRESS_KEY].to_i + 1
|
|
114
|
+
yield
|
|
115
|
+
ensure
|
|
116
|
+
state[SUPPRESS_KEY] -= 1
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# Runs the block inside an action of the given kind, or inside the already open
|
|
120
|
+
# action when there is one. Used by the HTTP, job and rake integrations.
|
|
121
|
+
#
|
|
122
|
+
# @param kind [Symbol] :http, :job, :task or :custom
|
|
123
|
+
# @param name [String, nil]
|
|
124
|
+
# @param actor [Object, nil]
|
|
125
|
+
# @param metadata [Hash]
|
|
126
|
+
# @param explicit [Boolean]
|
|
127
|
+
# @param caused_by [String, nil] id of the parent action
|
|
128
|
+
# @param context [Context, nil]
|
|
129
|
+
# @yieldparam action [Action]
|
|
130
|
+
# @return [Object] the block's return value
|
|
131
|
+
def track(kind:, name:, actor: nil, metadata: {}, explicit: false, caused_by: nil, context: nil)
|
|
132
|
+
if (outer = current)
|
|
133
|
+
outer.annotate(**metadata) if metadata.present?
|
|
134
|
+
outer.actor = actor if actor && outer.explicit_actor.nil?
|
|
135
|
+
return yield(outer)
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
action = Action.new(kind: kind, name: name, actor: actor, metadata: metadata,
|
|
139
|
+
explicit: explicit, caused_by: caused_by, context: context)
|
|
140
|
+
within(action) { yield(action) }
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# Makes +action+ current for the duration of the block, records an unhandled
|
|
144
|
+
# exception as the outcome and hands the action to the emitter afterwards.
|
|
145
|
+
#
|
|
146
|
+
# @param action [Action]
|
|
147
|
+
# @return [Object] the block's return value
|
|
148
|
+
def within(action)
|
|
149
|
+
state = ActiveSupport::IsolatedExecutionState
|
|
150
|
+
previous = state[STATE_KEY]
|
|
151
|
+
state[STATE_KEY] = action
|
|
152
|
+
begin
|
|
153
|
+
yield
|
|
154
|
+
rescue Exception => e # rubocop:disable Lint/RescueException
|
|
155
|
+
action.fail!(e)
|
|
156
|
+
raise
|
|
157
|
+
ensure
|
|
158
|
+
state[STATE_KEY] = previous
|
|
159
|
+
Emitter.finish(action)
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
require "provenance/testing"
|
|
166
|
+
require "provenance/railtie" if defined?(Rails::Railtie)
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
namespace :provenance do
|
|
4
|
+
desc "Verify the integrity chain of events stored in the outbox"
|
|
5
|
+
task verify: :environment do
|
|
6
|
+
key = Provenance.config.integrity.key
|
|
7
|
+
abort "provenance:verify: integrity.key is not configured" if key.blank?
|
|
8
|
+
|
|
9
|
+
broken = Provenance::Outbox::Record.where.not(seq: nil).distinct.pluck(:app).filter_map do |app|
|
|
10
|
+
events = Provenance::Outbox::Record.where(app: app).where.not(seq: nil).order(:seq).map(&:event)
|
|
11
|
+
seq = Provenance::Integrity.verify(events, key: key)
|
|
12
|
+
puts "#{app}: #{seq ? "broken at seq #{seq}" : "ok (#{events.size} events)"}"
|
|
13
|
+
seq
|
|
14
|
+
end
|
|
15
|
+
abort "provenance:verify: integrity check failed" if broken.any?
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
desc "Deliver pending outbox events now"
|
|
19
|
+
task relay: :environment do
|
|
20
|
+
puts "delivered #{Provenance::Outbox::Relay.run} events"
|
|
21
|
+
end
|
|
22
|
+
end
|
data/schema/event-2.json
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "urn:provenance:event:2",
|
|
4
|
+
"title": "Provenance action event",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"additionalProperties": false,
|
|
7
|
+
"required": ["schema", "id", "occurred_at", "app", "action", "actor", "request", "outcome", "changes", "metadata"],
|
|
8
|
+
"properties": {
|
|
9
|
+
"schema": {"const": "provenance/event@2"},
|
|
10
|
+
"id": {
|
|
11
|
+
"type": "string",
|
|
12
|
+
"pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$"
|
|
13
|
+
},
|
|
14
|
+
"occurred_at": {
|
|
15
|
+
"type": "string",
|
|
16
|
+
"pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}\\.\\d{3}Z$"
|
|
17
|
+
},
|
|
18
|
+
"app": {"type": "string", "minLength": 1},
|
|
19
|
+
"action": {
|
|
20
|
+
"type": "object",
|
|
21
|
+
"additionalProperties": false,
|
|
22
|
+
"required": ["kind", "name", "caused_by"],
|
|
23
|
+
"properties": {
|
|
24
|
+
"kind": {"enum": ["http", "job", "task", "custom"]},
|
|
25
|
+
"name": {"type": "string"},
|
|
26
|
+
"caused_by": {"type": ["string", "null"]}
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
"actor": {
|
|
30
|
+
"oneOf": [{"type": "null"}, {"$ref": "#/$defs/actor"}]
|
|
31
|
+
},
|
|
32
|
+
"request": {
|
|
33
|
+
"oneOf": [
|
|
34
|
+
{"type": "null"},
|
|
35
|
+
{
|
|
36
|
+
"type": "object",
|
|
37
|
+
"additionalProperties": false,
|
|
38
|
+
"required": ["id", "method", "path", "status", "ip", "user_agent"],
|
|
39
|
+
"properties": {
|
|
40
|
+
"id": {"type": ["string", "null"]},
|
|
41
|
+
"method": {"type": "string"},
|
|
42
|
+
"path": {"type": "string"},
|
|
43
|
+
"status": {"type": ["integer", "null"]},
|
|
44
|
+
"ip": {"type": ["string", "null"]},
|
|
45
|
+
"user_agent": {"type": ["string", "null"]}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
]
|
|
49
|
+
},
|
|
50
|
+
"outcome": {
|
|
51
|
+
"type": "object",
|
|
52
|
+
"additionalProperties": false,
|
|
53
|
+
"required": ["result", "error"],
|
|
54
|
+
"properties": {
|
|
55
|
+
"result": {"enum": ["success", "failure", "denied"]},
|
|
56
|
+
"error": {
|
|
57
|
+
"oneOf": [
|
|
58
|
+
{"type": "null"},
|
|
59
|
+
{
|
|
60
|
+
"type": "object",
|
|
61
|
+
"additionalProperties": false,
|
|
62
|
+
"required": ["class", "message"],
|
|
63
|
+
"properties": {
|
|
64
|
+
"class": {"type": "string"},
|
|
65
|
+
"message": {"type": "string", "maxLength": 500}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
]
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
"changes": {"type": "array", "items": {"$ref": "#/$defs/change"}},
|
|
73
|
+
"metadata": {"type": "object"},
|
|
74
|
+
"integrity": {
|
|
75
|
+
"type": "object",
|
|
76
|
+
"additionalProperties": false,
|
|
77
|
+
"required": ["seq", "prev", "mac"],
|
|
78
|
+
"properties": {
|
|
79
|
+
"seq": {"type": "integer", "minimum": 1},
|
|
80
|
+
"prev": {"type": ["string", "null"], "pattern": "^[0-9a-f]{64}$"},
|
|
81
|
+
"mac": {"type": "string", "pattern": "^[0-9a-f]{64}$"}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
"$defs": {
|
|
86
|
+
"actor": {
|
|
87
|
+
"type": "object",
|
|
88
|
+
"additionalProperties": false,
|
|
89
|
+
"required": ["id", "type", "display", "roles", "impersonator"],
|
|
90
|
+
"properties": {
|
|
91
|
+
"id": {"type": ["string", "null"]},
|
|
92
|
+
"type": {"type": ["string", "null"]},
|
|
93
|
+
"display": {"type": ["string", "null"]},
|
|
94
|
+
"roles": {"type": "array", "items": {"type": "string"}},
|
|
95
|
+
"impersonator": {"oneOf": [{"type": "null"}, {"$ref": "#/$defs/actor"}]}
|
|
96
|
+
}
|
|
97
|
+
},
|
|
98
|
+
"change": {
|
|
99
|
+
"type": "object",
|
|
100
|
+
"additionalProperties": false,
|
|
101
|
+
"required": ["entity", "entity_id", "operation", "diff"],
|
|
102
|
+
"properties": {
|
|
103
|
+
"entity": {"type": "string"},
|
|
104
|
+
"entity_id": {"type": ["string", "null"]},
|
|
105
|
+
"operation": {
|
|
106
|
+
"enum": ["create", "update", "destroy", "link", "unlink", "bulk_update", "bulk_delete", "bulk_insert"]
|
|
107
|
+
},
|
|
108
|
+
"diff": {"type": ["object", "null"]},
|
|
109
|
+
"association": {"type": "string"},
|
|
110
|
+
"target": {
|
|
111
|
+
"type": "object",
|
|
112
|
+
"additionalProperties": false,
|
|
113
|
+
"required": ["type", "id"],
|
|
114
|
+
"properties": {
|
|
115
|
+
"type": {"type": "string"},
|
|
116
|
+
"id": {"type": ["string", "null"]}
|
|
117
|
+
}
|
|
118
|
+
},
|
|
119
|
+
"count": {"type": ["integer", "null"]},
|
|
120
|
+
"where": {"type": ["string", "null"]},
|
|
121
|
+
"ids": {"type": "array", "items": {"type": "string"}},
|
|
122
|
+
"truncated": {"type": "boolean"}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
metadata
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: provenance
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 2.0.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Ivan Nikolaev
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: activejob
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '7.2'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '7.2'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: activerecord
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - ">="
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '7.2'
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - ">="
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '7.2'
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: activesupport
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - ">="
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '7.2'
|
|
47
|
+
type: :runtime
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - ">="
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '7.2'
|
|
54
|
+
- !ruby/object:Gem::Dependency
|
|
55
|
+
name: railties
|
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
|
57
|
+
requirements:
|
|
58
|
+
- - ">="
|
|
59
|
+
- !ruby/object:Gem::Version
|
|
60
|
+
version: '7.2'
|
|
61
|
+
type: :runtime
|
|
62
|
+
prerelease: false
|
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
64
|
+
requirements:
|
|
65
|
+
- - ">="
|
|
66
|
+
- !ruby/object:Gem::Version
|
|
67
|
+
version: '7.2'
|
|
68
|
+
description: 'Provenance records who did what, through which entry point, with what
|
|
69
|
+
outcome and which records changed: one event per HTTP request, job, rake task or
|
|
70
|
+
explicit block, delivered to SIEM, log pipelines and webhooks in native, OCSF or
|
|
71
|
+
CloudEvents format.'
|
|
72
|
+
email:
|
|
73
|
+
- ivan_n6_20@icloud.com
|
|
74
|
+
executables: []
|
|
75
|
+
extensions: []
|
|
76
|
+
extra_rdoc_files: []
|
|
77
|
+
files:
|
|
78
|
+
- CHANGELOG.md
|
|
79
|
+
- LICENSE
|
|
80
|
+
- README.md
|
|
81
|
+
- lib/generators/provenance/install/install_generator.rb
|
|
82
|
+
- lib/generators/provenance/install/templates/create_provenance_outbox.rb.tt
|
|
83
|
+
- lib/generators/provenance/install/templates/initializer.rb.tt
|
|
84
|
+
- lib/provenance.rb
|
|
85
|
+
- lib/provenance/action.rb
|
|
86
|
+
- lib/provenance/actor.rb
|
|
87
|
+
- lib/provenance/configuration.rb
|
|
88
|
+
- lib/provenance/context.rb
|
|
89
|
+
- lib/provenance/controller.rb
|
|
90
|
+
- lib/provenance/emitter.rb
|
|
91
|
+
- lib/provenance/entity_change.rb
|
|
92
|
+
- lib/provenance/errors.rb
|
|
93
|
+
- lib/provenance/event.rb
|
|
94
|
+
- lib/provenance/integrity.rb
|
|
95
|
+
- lib/provenance/job.rb
|
|
96
|
+
- lib/provenance/json_safe.rb
|
|
97
|
+
- lib/provenance/middleware.rb
|
|
98
|
+
- lib/provenance/model.rb
|
|
99
|
+
- lib/provenance/outbox.rb
|
|
100
|
+
- lib/provenance/outbox/record.rb
|
|
101
|
+
- lib/provenance/outbox/relay.rb
|
|
102
|
+
- lib/provenance/railtie.rb
|
|
103
|
+
- lib/provenance/rake.rb
|
|
104
|
+
- lib/provenance/recorder.rb
|
|
105
|
+
- lib/provenance/redactor.rb
|
|
106
|
+
- lib/provenance/registry.rb
|
|
107
|
+
- lib/provenance/relay_job.rb
|
|
108
|
+
- lib/provenance/rspec.rb
|
|
109
|
+
- lib/provenance/serializers.rb
|
|
110
|
+
- lib/provenance/serializers/cloud_events.rb
|
|
111
|
+
- lib/provenance/serializers/native.rb
|
|
112
|
+
- lib/provenance/serializers/ocsf.rb
|
|
113
|
+
- lib/provenance/sinks.rb
|
|
114
|
+
- lib/provenance/sinks/http.rb
|
|
115
|
+
- lib/provenance/sinks/io.rb
|
|
116
|
+
- lib/provenance/sinks/kafka.rb
|
|
117
|
+
- lib/provenance/sinks/logger.rb
|
|
118
|
+
- lib/provenance/sinks/memory.rb
|
|
119
|
+
- lib/provenance/sinks/proc.rb
|
|
120
|
+
- lib/provenance/testing.rb
|
|
121
|
+
- lib/provenance/uuid.rb
|
|
122
|
+
- lib/provenance/version.rb
|
|
123
|
+
- lib/tasks/provenance.rake
|
|
124
|
+
- schema/event-2.json
|
|
125
|
+
homepage: https://github.com/inikalaev/provenance
|
|
126
|
+
licenses:
|
|
127
|
+
- MIT
|
|
128
|
+
metadata:
|
|
129
|
+
homepage_uri: https://github.com/inikalaev/provenance
|
|
130
|
+
source_code_uri: https://github.com/inikalaev/provenance
|
|
131
|
+
changelog_uri: https://github.com/inikalaev/provenance/blob/v2/CHANGELOG.md
|
|
132
|
+
rubygems_mfa_required: 'true'
|
|
133
|
+
rdoc_options: []
|
|
134
|
+
require_paths:
|
|
135
|
+
- lib
|
|
136
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
137
|
+
requirements:
|
|
138
|
+
- - ">="
|
|
139
|
+
- !ruby/object:Gem::Version
|
|
140
|
+
version: '3.2'
|
|
141
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
142
|
+
requirements:
|
|
143
|
+
- - ">="
|
|
144
|
+
- !ruby/object:Gem::Version
|
|
145
|
+
version: '0'
|
|
146
|
+
requirements: []
|
|
147
|
+
rubygems_version: 3.6.9
|
|
148
|
+
specification_version: 4
|
|
149
|
+
summary: Action-level, tamper-evident audit events for Rails applications
|
|
150
|
+
test_files: []
|