lex-audit 0.1.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/.github/workflows/ci.yml +16 -0
- data/.rspec +3 -0
- data/.rubocop.yml +53 -0
- data/CHANGELOG.md +12 -0
- data/CLAUDE.md +70 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +86 -0
- data/README.md +39 -0
- data/lex-audit.gemspec +37 -0
- data/lib/legion/extensions/audit/actors/audit_writer.rb +17 -0
- data/lib/legion/extensions/audit/runners/audit.rb +68 -0
- data/lib/legion/extensions/audit/transport/exchanges/audit.rb +17 -0
- data/lib/legion/extensions/audit/transport/messages/audit.rb +50 -0
- data/lib/legion/extensions/audit/transport/queues/audit.rb +27 -0
- data/lib/legion/extensions/audit/version.rb +9 -0
- data/lib/legion/extensions/audit.rb +19 -0
- metadata +147 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 678a76434f7d1af470a6c1302b6079dbe86f4096f5e64fff11ffe3bc80d6ab92
|
|
4
|
+
data.tar.gz: e873a54a8b05dee40f5d078f9f5c80e99a332b6479a98ee7a26f4faae6f983d8
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: e0783cc5cc4634ad85ae9d350d05c2d40f350a180689457f2285ace36ef3f227e5a722ffb2bb5ef5596d99d03b6d015e82c3a99728d80de46ff9d0c8074bfcae
|
|
7
|
+
data.tar.gz: f45707d4627e116e12dd303bb26b85abc984a8cb9b342f9c836e89a33d2cd73839e15c2990fa1630ca4956fd01e42c260e892a224ed91b44d80a1c6d586b72c1
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
on:
|
|
3
|
+
push:
|
|
4
|
+
branches: [main]
|
|
5
|
+
pull_request:
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
ci:
|
|
9
|
+
uses: LegionIO/.github/.github/workflows/ci.yml@main
|
|
10
|
+
|
|
11
|
+
release:
|
|
12
|
+
needs: ci
|
|
13
|
+
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
|
14
|
+
uses: LegionIO/.github/.github/workflows/release.yml@main
|
|
15
|
+
secrets:
|
|
16
|
+
rubygems-api-key: ${{ secrets.RUBYGEMS_API_KEY }}
|
data/.rspec
ADDED
data/.rubocop.yml
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
AllCops:
|
|
2
|
+
TargetRubyVersion: 3.4
|
|
3
|
+
NewCops: enable
|
|
4
|
+
SuggestExtensions: false
|
|
5
|
+
|
|
6
|
+
Layout/LineLength:
|
|
7
|
+
Max: 160
|
|
8
|
+
|
|
9
|
+
Layout/SpaceAroundEqualsInParameterDefault:
|
|
10
|
+
EnforcedStyle: space
|
|
11
|
+
|
|
12
|
+
Layout/HashAlignment:
|
|
13
|
+
EnforcedHashRocketStyle: table
|
|
14
|
+
EnforcedColonStyle: table
|
|
15
|
+
|
|
16
|
+
Metrics/MethodLength:
|
|
17
|
+
Max: 50
|
|
18
|
+
|
|
19
|
+
Metrics/ClassLength:
|
|
20
|
+
Max: 1500
|
|
21
|
+
|
|
22
|
+
Metrics/ModuleLength:
|
|
23
|
+
Max: 1500
|
|
24
|
+
|
|
25
|
+
Metrics/BlockLength:
|
|
26
|
+
Max: 40
|
|
27
|
+
Exclude:
|
|
28
|
+
- 'spec/**/*'
|
|
29
|
+
|
|
30
|
+
Metrics/AbcSize:
|
|
31
|
+
Max: 60
|
|
32
|
+
|
|
33
|
+
Metrics/CyclomaticComplexity:
|
|
34
|
+
Max: 15
|
|
35
|
+
|
|
36
|
+
Metrics/PerceivedComplexity:
|
|
37
|
+
Max: 17
|
|
38
|
+
|
|
39
|
+
Style/Documentation:
|
|
40
|
+
Enabled: false
|
|
41
|
+
|
|
42
|
+
Style/SymbolArray:
|
|
43
|
+
Enabled: true
|
|
44
|
+
|
|
45
|
+
Style/FrozenStringLiteralComment:
|
|
46
|
+
Enabled: true
|
|
47
|
+
EnforcedStyle: always
|
|
48
|
+
|
|
49
|
+
Naming/FileName:
|
|
50
|
+
Enabled: false
|
|
51
|
+
|
|
52
|
+
Gemspec/DevelopmentDependencies:
|
|
53
|
+
Enabled: false
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## [0.1.0] - 2026-03-16
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- Initial release: immutable audit logging with SHA-256 hash chain
|
|
7
|
+
- Transport layer: `audit` exchange, `audit.log` queue with `x-single-active-consumer`
|
|
8
|
+
- `Audit` message class with event_type-based routing keys and field validation
|
|
9
|
+
- `write` runner: creates hash-chained audit records in the database
|
|
10
|
+
- `verify` runner: validates hash chain integrity, detects tampering
|
|
11
|
+
- `AuditWriter` subscription actor: consumes audit messages from AMQP
|
|
12
|
+
- 29 specs, 0 failures
|
data/CLAUDE.md
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# lex-audit: Immutable Audit Logging for LegionIO
|
|
2
|
+
|
|
3
|
+
**Repository Level 3 Documentation**
|
|
4
|
+
- **Parent**: `/Users/miverso2/rubymine/legion/extensions-core/CLAUDE.md`
|
|
5
|
+
- **Grandparent**: `/Users/miverso2/rubymine/legion/CLAUDE.md`
|
|
6
|
+
|
|
7
|
+
## Purpose
|
|
8
|
+
|
|
9
|
+
Legion Extension that provides immutable, tamper-evident audit logging with a SHA-256 hash chain. Records runner executions and lifecycle transitions via AMQP. Requires `legion-data` (`data_required? true`).
|
|
10
|
+
|
|
11
|
+
**GitHub**: https://github.com/LegionIO/lex-audit
|
|
12
|
+
**License**: MIT
|
|
13
|
+
**Version**: 0.1.0
|
|
14
|
+
|
|
15
|
+
## Architecture
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
Legion::Extensions::Audit
|
|
19
|
+
├── Actors/
|
|
20
|
+
│ └── AuditWriter # Subscription actor: consumes audit messages, calls write runner
|
|
21
|
+
├── Runners/
|
|
22
|
+
│ └── Audit # write: hash-chained record insert; verify: chain integrity check
|
|
23
|
+
└── Transport/
|
|
24
|
+
├── Exchanges/Audit # Audit exchange
|
|
25
|
+
├── Queues/Audit # audit.log queue (x-single-active-consumer: true)
|
|
26
|
+
└── Messages/Audit # Audit message with event_type routing key, field validation
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Key Files
|
|
30
|
+
|
|
31
|
+
| Path | Purpose |
|
|
32
|
+
|------|---------|
|
|
33
|
+
| `lib/legion/extensions/audit.rb` | Entry point, extension registration (`data_required? true`) |
|
|
34
|
+
| `lib/legion/extensions/audit/runners/audit.rb` | Hash chain write and verify logic |
|
|
35
|
+
| `lib/legion/extensions/audit/actors/audit_writer.rb` | AMQP subscription actor |
|
|
36
|
+
| `lib/legion/extensions/audit/transport/messages/audit.rb` | Message class with validation and routing |
|
|
37
|
+
|
|
38
|
+
## Runner Details
|
|
39
|
+
|
|
40
|
+
**write**: Fetches the last record's hash (or genesis hash `"0"*64`), computes SHA-256 of `prev_hash|event_type|principal_id|action|resource|created_at`, and inserts the record. Uses `.utc.iso8601` for timezone-safe hashing.
|
|
41
|
+
|
|
42
|
+
**verify**: Iterates all records in order, recomputes each hash, and compares. Returns `{ valid:, records_checked:, break_at: }`.
|
|
43
|
+
|
|
44
|
+
## Hash Chain Design
|
|
45
|
+
|
|
46
|
+
- Genesis hash: `"0" * 64` (64 zeros)
|
|
47
|
+
- Hash content: `"#{prev_hash}|#{event_type}|#{principal_id}|#{action}|#{resource}|#{created_at.utc.iso8601}"`
|
|
48
|
+
- Algorithm: SHA-256
|
|
49
|
+
- Single-active-consumer queue ensures ordering across cluster nodes
|
|
50
|
+
- `Sequel.default_timezone = :utc` required for consistent hash verification
|
|
51
|
+
|
|
52
|
+
## Integration Points
|
|
53
|
+
|
|
54
|
+
- `Legion::Audit.record` (LegionIO) publishes messages to this extension
|
|
55
|
+
- `Runner.run` ensure block emits `runner_execution` events
|
|
56
|
+
- `DigitalWorker::Lifecycle.transition!` emits `lifecycle_transition` events
|
|
57
|
+
- `GET /api/audit` and `GET /api/audit/verify` query the audit log
|
|
58
|
+
- `legion audit list` and `legion audit verify` CLI commands
|
|
59
|
+
|
|
60
|
+
## Testing
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
bundle install
|
|
64
|
+
bundle exec rspec # 29 examples, 0 failures
|
|
65
|
+
bundle exec rubocop # 0 offenses
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
**Maintained By**: Matthew Iverson (@Esity)
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
PATH
|
|
2
|
+
remote: .
|
|
3
|
+
specs:
|
|
4
|
+
lex-audit (0.1.0)
|
|
5
|
+
|
|
6
|
+
GEM
|
|
7
|
+
remote: https://rubygems.org/
|
|
8
|
+
specs:
|
|
9
|
+
addressable (2.8.9)
|
|
10
|
+
public_suffix (>= 2.0.2, < 8.0)
|
|
11
|
+
ast (2.4.3)
|
|
12
|
+
bigdecimal (4.0.1)
|
|
13
|
+
diff-lcs (1.6.2)
|
|
14
|
+
json (2.19.1)
|
|
15
|
+
json-schema (6.2.0)
|
|
16
|
+
addressable (~> 2.8)
|
|
17
|
+
bigdecimal (>= 3.1, < 5)
|
|
18
|
+
language_server-protocol (3.17.0.5)
|
|
19
|
+
lint_roller (1.1.0)
|
|
20
|
+
mcp (0.8.0)
|
|
21
|
+
json-schema (>= 4.1)
|
|
22
|
+
parallel (1.27.0)
|
|
23
|
+
parser (3.3.10.2)
|
|
24
|
+
ast (~> 2.4.1)
|
|
25
|
+
racc
|
|
26
|
+
prism (1.9.0)
|
|
27
|
+
public_suffix (7.0.5)
|
|
28
|
+
racc (1.8.1)
|
|
29
|
+
rainbow (3.1.1)
|
|
30
|
+
rake (13.3.1)
|
|
31
|
+
regexp_parser (2.11.3)
|
|
32
|
+
rspec (3.13.2)
|
|
33
|
+
rspec-core (~> 3.13.0)
|
|
34
|
+
rspec-expectations (~> 3.13.0)
|
|
35
|
+
rspec-mocks (~> 3.13.0)
|
|
36
|
+
rspec-core (3.13.6)
|
|
37
|
+
rspec-support (~> 3.13.0)
|
|
38
|
+
rspec-expectations (3.13.5)
|
|
39
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
|
40
|
+
rspec-support (~> 3.13.0)
|
|
41
|
+
rspec-mocks (3.13.8)
|
|
42
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
|
43
|
+
rspec-support (~> 3.13.0)
|
|
44
|
+
rspec-support (3.13.7)
|
|
45
|
+
rubocop (1.85.1)
|
|
46
|
+
json (~> 2.3)
|
|
47
|
+
language_server-protocol (~> 3.17.0.2)
|
|
48
|
+
lint_roller (~> 1.1.0)
|
|
49
|
+
mcp (~> 0.6)
|
|
50
|
+
parallel (~> 1.10)
|
|
51
|
+
parser (>= 3.3.0.2)
|
|
52
|
+
rainbow (>= 2.2.2, < 4.0)
|
|
53
|
+
regexp_parser (>= 2.9.3, < 3.0)
|
|
54
|
+
rubocop-ast (>= 1.49.0, < 2.0)
|
|
55
|
+
ruby-progressbar (~> 1.7)
|
|
56
|
+
unicode-display_width (>= 2.4.0, < 4.0)
|
|
57
|
+
rubocop-ast (1.49.1)
|
|
58
|
+
parser (>= 3.3.7.2)
|
|
59
|
+
prism (~> 1.7)
|
|
60
|
+
rubocop-rspec (3.9.0)
|
|
61
|
+
lint_roller (~> 1.1)
|
|
62
|
+
rubocop (~> 1.81)
|
|
63
|
+
ruby-progressbar (1.13.0)
|
|
64
|
+
sequel (5.102.0)
|
|
65
|
+
bigdecimal
|
|
66
|
+
sqlite3 (2.9.2-arm64-darwin)
|
|
67
|
+
sqlite3 (2.9.2-x86_64-linux-gnu)
|
|
68
|
+
unicode-display_width (3.2.0)
|
|
69
|
+
unicode-emoji (~> 4.1)
|
|
70
|
+
unicode-emoji (4.2.0)
|
|
71
|
+
|
|
72
|
+
PLATFORMS
|
|
73
|
+
arm64-darwin-25
|
|
74
|
+
x86_64-linux
|
|
75
|
+
|
|
76
|
+
DEPENDENCIES
|
|
77
|
+
lex-audit!
|
|
78
|
+
rake
|
|
79
|
+
rspec
|
|
80
|
+
rubocop
|
|
81
|
+
rubocop-rspec
|
|
82
|
+
sequel
|
|
83
|
+
sqlite3
|
|
84
|
+
|
|
85
|
+
BUNDLED WITH
|
|
86
|
+
2.6.9
|
data/README.md
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# lex-audit
|
|
2
|
+
|
|
3
|
+
Immutable audit logging with SHA-256 hash chain for [LegionIO](https://github.com/LegionIO/LegionIO). Records every runner execution and lifecycle transition as a tamper-evident audit trail.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
gem install lex-audit
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Functions
|
|
12
|
+
|
|
13
|
+
- **write** - Create a hash-chained audit record (each record's hash depends on the previous record)
|
|
14
|
+
- **verify** - Validate the entire hash chain to detect any tampering or corruption
|
|
15
|
+
|
|
16
|
+
## How It Works
|
|
17
|
+
|
|
18
|
+
Every audit record includes a SHA-256 hash computed from: `prev_hash|event_type|principal_id|action|resource|created_at`. Each record references the previous record's hash, forming an immutable chain. Breaking any record invalidates all subsequent hashes.
|
|
19
|
+
|
|
20
|
+
The `audit.log` queue uses `x-single-active-consumer: true` to ensure only one consumer writes at a time, preserving hash chain ordering across a cluster.
|
|
21
|
+
|
|
22
|
+
`Legion::Audit.record` in LegionIO publishes audit events via AMQP. It uses triple-guard checks and silent rescue to never interfere with normal operation.
|
|
23
|
+
|
|
24
|
+
## Event Types
|
|
25
|
+
|
|
26
|
+
| Type | Source | Description |
|
|
27
|
+
|------|--------|-------------|
|
|
28
|
+
| `runner_execution` | Runner.run | Every task execution with duration and status |
|
|
29
|
+
| `lifecycle_transition` | DigitalWorker::Lifecycle | Worker state machine transitions |
|
|
30
|
+
|
|
31
|
+
## Requirements
|
|
32
|
+
|
|
33
|
+
- Ruby >= 3.4
|
|
34
|
+
- [LegionIO](https://github.com/LegionIO/LegionIO) framework
|
|
35
|
+
- `legion-data` (database required)
|
|
36
|
+
|
|
37
|
+
## License
|
|
38
|
+
|
|
39
|
+
MIT
|
data/lex-audit.gemspec
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
lib = File.expand_path('lib', __dir__)
|
|
4
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
5
|
+
require 'legion/extensions/audit/version'
|
|
6
|
+
|
|
7
|
+
Gem::Specification.new do |spec|
|
|
8
|
+
spec.name = 'lex-audit'
|
|
9
|
+
spec.version = Legion::Extensions::Audit::VERSION
|
|
10
|
+
spec.authors = ['Esity']
|
|
11
|
+
spec.email = ['matthewdiverson@gmail.com']
|
|
12
|
+
|
|
13
|
+
spec.summary = 'Legion::Extensions::Audit'
|
|
14
|
+
spec.description = 'Immutable audit logging with SHA-256 hash chain for LegionIO execution decisions'
|
|
15
|
+
spec.homepage = 'https://github.com/LegionIO/lex-audit'
|
|
16
|
+
spec.license = 'MIT'
|
|
17
|
+
spec.required_ruby_version = '>= 3.4'
|
|
18
|
+
|
|
19
|
+
spec.metadata['homepage_uri'] = spec.homepage
|
|
20
|
+
spec.metadata['source_code_uri'] = 'https://github.com/LegionIO/lex-audit'
|
|
21
|
+
spec.metadata['changelog_uri'] = 'https://github.com/LegionIO/lex-audit/blob/main/CHANGELOG.md'
|
|
22
|
+
spec.metadata['documentation_uri'] = 'https://github.com/LegionIO/lex-audit'
|
|
23
|
+
spec.metadata['bug_tracker_uri'] = 'https://github.com/LegionIO/lex-audit/issues'
|
|
24
|
+
spec.metadata['rubygems_mfa_required'] = 'true'
|
|
25
|
+
|
|
26
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
|
27
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
|
28
|
+
end
|
|
29
|
+
spec.require_paths = ['lib']
|
|
30
|
+
|
|
31
|
+
spec.add_development_dependency 'rake'
|
|
32
|
+
spec.add_development_dependency 'rspec'
|
|
33
|
+
spec.add_development_dependency 'rubocop'
|
|
34
|
+
spec.add_development_dependency 'rubocop-rspec'
|
|
35
|
+
spec.add_development_dependency 'sequel'
|
|
36
|
+
spec.add_development_dependency 'sqlite3'
|
|
37
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'legion/extensions/actors/subscription'
|
|
4
|
+
|
|
5
|
+
module Legion
|
|
6
|
+
module Extensions
|
|
7
|
+
module Audit
|
|
8
|
+
module Actor
|
|
9
|
+
class AuditWriter < Legion::Extensions::Actors::Subscription
|
|
10
|
+
def runner_function
|
|
11
|
+
'write'
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'digest'
|
|
4
|
+
|
|
5
|
+
module Legion
|
|
6
|
+
module Extensions
|
|
7
|
+
module Audit
|
|
8
|
+
module Runners
|
|
9
|
+
module Audit
|
|
10
|
+
include Legion::Extensions::Helpers::Lex if defined?(Legion::Extensions::Helpers::Lex)
|
|
11
|
+
|
|
12
|
+
GENESIS_HASH = ('0' * 64).freeze
|
|
13
|
+
|
|
14
|
+
def write(event_type:, principal_id:, action:, resource:, **opts)
|
|
15
|
+
prev = Legion::Data::Model::AuditLog.order(Sequel.desc(:id)).first
|
|
16
|
+
prev_hash = prev ? prev.record_hash : GENESIS_HASH
|
|
17
|
+
|
|
18
|
+
created_at = opts[:created_at] ? Time.parse(opts[:created_at].to_s) : Time.now.utc
|
|
19
|
+
content = "#{prev_hash}|#{event_type}|#{principal_id}|#{action}|#{resource}|#{created_at.utc.iso8601}"
|
|
20
|
+
record_hash = Digest::SHA256.hexdigest(content)
|
|
21
|
+
|
|
22
|
+
detail_json = opts[:detail] ? Legion::JSON.dump(opts[:detail]) : nil
|
|
23
|
+
|
|
24
|
+
record = Legion::Data::Model::AuditLog.create(
|
|
25
|
+
event_type: event_type,
|
|
26
|
+
principal_id: principal_id,
|
|
27
|
+
principal_type: opts[:principal_type] || 'system',
|
|
28
|
+
action: action,
|
|
29
|
+
resource: resource,
|
|
30
|
+
source: opts[:source] || 'unknown',
|
|
31
|
+
node: opts[:node] || 'unknown',
|
|
32
|
+
status: opts[:status] || 'success',
|
|
33
|
+
duration_ms: opts[:duration_ms],
|
|
34
|
+
detail: detail_json,
|
|
35
|
+
record_hash: record_hash,
|
|
36
|
+
prev_hash: prev_hash,
|
|
37
|
+
created_at: created_at
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
{ success: true, audit_id: record.id, record_hash: record_hash }
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def verify(limit: nil, **_opts)
|
|
44
|
+
prev_hash = GENESIS_HASH
|
|
45
|
+
broken_at = nil
|
|
46
|
+
count = 0
|
|
47
|
+
|
|
48
|
+
dataset = Legion::Data::Model::AuditLog.order(:id)
|
|
49
|
+
dataset = dataset.limit(limit) if limit
|
|
50
|
+
|
|
51
|
+
dataset.each do |record|
|
|
52
|
+
content = "#{prev_hash}|#{record.event_type}|#{record.principal_id}|#{record.action}|#{record.resource}|#{record.created_at.utc.iso8601}"
|
|
53
|
+
expected = Digest::SHA256.hexdigest(content)
|
|
54
|
+
unless record.record_hash == expected
|
|
55
|
+
broken_at = record.id
|
|
56
|
+
break
|
|
57
|
+
end
|
|
58
|
+
prev_hash = record.record_hash
|
|
59
|
+
count += 1
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
{ valid: broken_at.nil?, records_checked: count, break_at: broken_at }
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module Audit
|
|
6
|
+
module Transport
|
|
7
|
+
module Messages
|
|
8
|
+
class Audit < Legion::Transport::Message
|
|
9
|
+
def routing_key
|
|
10
|
+
"audit.#{@options[:event_type] || 'unknown'}"
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def type
|
|
14
|
+
'audit'
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def encrypt?
|
|
18
|
+
false
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def validate
|
|
22
|
+
raise 'event_type is required' unless @options[:event_type]
|
|
23
|
+
raise 'principal_id is required' unless @options[:principal_id]
|
|
24
|
+
raise 'action is required' unless @options[:action]
|
|
25
|
+
raise 'resource is required' unless @options[:resource]
|
|
26
|
+
|
|
27
|
+
@valid = true
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def message
|
|
31
|
+
{
|
|
32
|
+
event_type: @options[:event_type],
|
|
33
|
+
principal_id: @options[:principal_id],
|
|
34
|
+
principal_type: @options[:principal_type] || 'system',
|
|
35
|
+
action: @options[:action],
|
|
36
|
+
resource: @options[:resource],
|
|
37
|
+
source: @options[:source] || 'unknown',
|
|
38
|
+
node: @options[:node],
|
|
39
|
+
status: @options[:status] || 'success',
|
|
40
|
+
duration_ms: @options[:duration_ms],
|
|
41
|
+
detail: @options[:detail],
|
|
42
|
+
created_at: @options[:created_at] || Time.now.utc.iso8601
|
|
43
|
+
}
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module Audit
|
|
6
|
+
module Transport
|
|
7
|
+
module Queues
|
|
8
|
+
class Audit < Legion::Transport::Queue
|
|
9
|
+
def queue_name
|
|
10
|
+
'audit.log'
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def queue_options
|
|
14
|
+
{
|
|
15
|
+
arguments: {
|
|
16
|
+
'x-single-active-consumer': true,
|
|
17
|
+
'x-dead-letter-exchange': 'audit.dlx'
|
|
18
|
+
},
|
|
19
|
+
auto_delete: false
|
|
20
|
+
}
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'legion/extensions/audit/version'
|
|
4
|
+
|
|
5
|
+
module Legion
|
|
6
|
+
module Extensions
|
|
7
|
+
module Audit
|
|
8
|
+
extend Legion::Extensions::Core if Legion::Extensions.const_defined? :Core
|
|
9
|
+
|
|
10
|
+
def self.data_required?
|
|
11
|
+
true
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def data_required?
|
|
15
|
+
true
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: lex-audit
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Esity
|
|
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: rake
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '0'
|
|
19
|
+
type: :development
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '0'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: rspec
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - ">="
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '0'
|
|
33
|
+
type: :development
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - ">="
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '0'
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: rubocop
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - ">="
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '0'
|
|
47
|
+
type: :development
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - ">="
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '0'
|
|
54
|
+
- !ruby/object:Gem::Dependency
|
|
55
|
+
name: rubocop-rspec
|
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
|
57
|
+
requirements:
|
|
58
|
+
- - ">="
|
|
59
|
+
- !ruby/object:Gem::Version
|
|
60
|
+
version: '0'
|
|
61
|
+
type: :development
|
|
62
|
+
prerelease: false
|
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
64
|
+
requirements:
|
|
65
|
+
- - ">="
|
|
66
|
+
- !ruby/object:Gem::Version
|
|
67
|
+
version: '0'
|
|
68
|
+
- !ruby/object:Gem::Dependency
|
|
69
|
+
name: sequel
|
|
70
|
+
requirement: !ruby/object:Gem::Requirement
|
|
71
|
+
requirements:
|
|
72
|
+
- - ">="
|
|
73
|
+
- !ruby/object:Gem::Version
|
|
74
|
+
version: '0'
|
|
75
|
+
type: :development
|
|
76
|
+
prerelease: false
|
|
77
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
78
|
+
requirements:
|
|
79
|
+
- - ">="
|
|
80
|
+
- !ruby/object:Gem::Version
|
|
81
|
+
version: '0'
|
|
82
|
+
- !ruby/object:Gem::Dependency
|
|
83
|
+
name: sqlite3
|
|
84
|
+
requirement: !ruby/object:Gem::Requirement
|
|
85
|
+
requirements:
|
|
86
|
+
- - ">="
|
|
87
|
+
- !ruby/object:Gem::Version
|
|
88
|
+
version: '0'
|
|
89
|
+
type: :development
|
|
90
|
+
prerelease: false
|
|
91
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
92
|
+
requirements:
|
|
93
|
+
- - ">="
|
|
94
|
+
- !ruby/object:Gem::Version
|
|
95
|
+
version: '0'
|
|
96
|
+
description: Immutable audit logging with SHA-256 hash chain for LegionIO execution
|
|
97
|
+
decisions
|
|
98
|
+
email:
|
|
99
|
+
- matthewdiverson@gmail.com
|
|
100
|
+
executables: []
|
|
101
|
+
extensions: []
|
|
102
|
+
extra_rdoc_files: []
|
|
103
|
+
files:
|
|
104
|
+
- ".github/workflows/ci.yml"
|
|
105
|
+
- ".rspec"
|
|
106
|
+
- ".rubocop.yml"
|
|
107
|
+
- CHANGELOG.md
|
|
108
|
+
- CLAUDE.md
|
|
109
|
+
- Gemfile
|
|
110
|
+
- Gemfile.lock
|
|
111
|
+
- README.md
|
|
112
|
+
- lex-audit.gemspec
|
|
113
|
+
- lib/legion/extensions/audit.rb
|
|
114
|
+
- lib/legion/extensions/audit/actors/audit_writer.rb
|
|
115
|
+
- lib/legion/extensions/audit/runners/audit.rb
|
|
116
|
+
- lib/legion/extensions/audit/transport/exchanges/audit.rb
|
|
117
|
+
- lib/legion/extensions/audit/transport/messages/audit.rb
|
|
118
|
+
- lib/legion/extensions/audit/transport/queues/audit.rb
|
|
119
|
+
- lib/legion/extensions/audit/version.rb
|
|
120
|
+
homepage: https://github.com/LegionIO/lex-audit
|
|
121
|
+
licenses:
|
|
122
|
+
- MIT
|
|
123
|
+
metadata:
|
|
124
|
+
homepage_uri: https://github.com/LegionIO/lex-audit
|
|
125
|
+
source_code_uri: https://github.com/LegionIO/lex-audit
|
|
126
|
+
changelog_uri: https://github.com/LegionIO/lex-audit/blob/main/CHANGELOG.md
|
|
127
|
+
documentation_uri: https://github.com/LegionIO/lex-audit
|
|
128
|
+
bug_tracker_uri: https://github.com/LegionIO/lex-audit/issues
|
|
129
|
+
rubygems_mfa_required: 'true'
|
|
130
|
+
rdoc_options: []
|
|
131
|
+
require_paths:
|
|
132
|
+
- lib
|
|
133
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
134
|
+
requirements:
|
|
135
|
+
- - ">="
|
|
136
|
+
- !ruby/object:Gem::Version
|
|
137
|
+
version: '3.4'
|
|
138
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
139
|
+
requirements:
|
|
140
|
+
- - ">="
|
|
141
|
+
- !ruby/object:Gem::Version
|
|
142
|
+
version: '0'
|
|
143
|
+
requirements: []
|
|
144
|
+
rubygems_version: 3.6.9
|
|
145
|
+
specification_version: 4
|
|
146
|
+
summary: Legion::Extensions::Audit
|
|
147
|
+
test_files: []
|