chronicle-shell 0.2.4 → 0.3.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 83dc9bab0e1b65930d8243a16ba6debe3d92cb4424b50e80d8511dc13dc6f3e4
4
- data.tar.gz: 57d4a5d1f434dedacb3071c3f96ac211990f42b92b75db1416b852b3b5f93806
3
+ metadata.gz: cba457d01ec6e7ee3719b80d980ee171797eabef20c7aaadc751e4e985f96f73
4
+ data.tar.gz: 8ec830dcd00de94eb7d9e9ff4a0942d3cc49f8975a88a792be4ad2be756630a4
5
5
  SHA512:
6
- metadata.gz: de0dba35c9ea5e3612c52f2adcf543c4922485e69e44e650113a89cb03ad24737bf7a1a271cdb3febd409ab02a481ed1d436a9374fe50a181c634dc6d69c2b93
7
- data.tar.gz: 5cbfef0e14a2d6e1ee7ffabdc99627ab5af6bad3d30368e082dad3057a701bc5dfef3da38e5ce7c8aadcf8689b35910060fe36486e060f1f7ffd358599a414c2
6
+ metadata.gz: f8f50a8c830a4687d9769d04d9a074f68feafb1ba9a07ac03dd2e4cc3e8794842436d2b968cc25cce91c41a555634ee5bf99a2bfcc5f70c37d306a08ce7108e0
7
+ data.tar.gz: 0ddbb6f0cb224a324a8b2150ca931e8f226b572b2e4a8fcb29df9616921a08c1b3f208c15737978e655ae3514a489ee234ce20378b973be37991e44a59ea427e
data/.rubocop.yml ADDED
@@ -0,0 +1,2 @@
1
+ inherit_gem:
2
+ chronicle-core: .rubocop-plugin.yml
data/Gemfile CHANGED
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- source "https://rubygems.org"
3
+ source 'https://rubygems.org'
4
4
 
5
5
  # Specify your gem's dependencies in chronicle-shell.gemspec
6
6
  gemspec
7
7
 
8
- gem "rake", "~> 13.0"
8
+ gem 'rake', '~> 13.0'
data/README.md CHANGED
@@ -3,13 +3,6 @@
3
3
 
4
4
  Shell importer for [chronicle-etl](https://github.com/chronicle-app/chronicle-etl)
5
5
 
6
- ## Available Connectors
7
- ### Extractors
8
- - `shell:history` - Extract shell history from bash or zsh
9
-
10
- ### Transformers
11
- - `shell:history` - Turn a shell command into Chronicle Schema
12
-
13
6
  ## Usage and examples
14
7
 
15
8
  ```bash
@@ -17,9 +10,13 @@ Shell importer for [chronicle-etl](https://github.com/chronicle-app/chronicle-et
17
10
  gem install chronicle-etl
18
11
  chronicle-etl plugins:install shell
19
12
 
20
- # output commands since Feb 7 as json
21
- chronicle-etl --extractor shell:history --transformer shell:history --since "2022-02-07" --loader json
13
+ # output commands since 2 weeks ago
14
+ $ chronicle-etl --extractor shell:command --schema chronicle --since 2w --loader json
22
15
 
23
16
  # Show recent commands sorted by frequency of use
24
- chronicle-etl --extractor shell:history --limit 500 --fields command --silent | sort | uniq -c | sort -nr
17
+ $ chronicle-etl --extractor shell:command --loader table --limit 500 --fields command --silent | sort | uniq -c | sort -nr
25
18
  ```
19
+
20
+ ## Available Connectors
21
+ ### Extractors
22
+ - `shell:command` - Extract shell history from bash or zsh
data/Rakefile CHANGED
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "bundler/gem_tasks"
3
+ require 'bundler/gem_tasks'
4
4
  task default: %i[]
@@ -1,24 +1,23 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "lib/chronicle/shell/version"
3
+ require_relative 'lib/chronicle/shell/version'
4
4
 
5
5
  Gem::Specification.new do |spec|
6
- spec.name = "chronicle-shell"
6
+ spec.name = 'chronicle-shell'
7
7
  spec.version = Chronicle::Shell::VERSION
8
- spec.authors = ["Andrew Louis"]
9
- spec.email = ["andrew@hyfen.net"]
8
+ spec.authors = ['Andrew Louis']
9
+ spec.email = ['andrew@hyfen.net']
10
10
 
11
- spec.summary = "Shell connectors for Chronicle-ETL"
12
- spec.description = "Extract shell command history from Bash or Zsh"
13
- spec.homepage = "https://github.com/chronicle-app/chronicle-shell"
14
- spec.required_ruby_version = ">= 2.7.0"
15
- spec.license = "MIT"
11
+ spec.summary = 'Shell connectors for Chronicle-ETL'
12
+ spec.description = 'Extract shell command history from Bash or Zsh'
13
+ spec.homepage = 'https://github.com/chronicle-app/chronicle-shell'
14
+ spec.license = 'MIT'
16
15
 
17
- spec.metadata['allowed_push_host'] = "https://rubygems.org"
16
+ spec.metadata['allowed_push_host'] = 'https://rubygems.org'
18
17
 
19
- spec.metadata["homepage_uri"] = spec.homepage
20
- spec.metadata["source_code_uri"] = "https://github.com/chronicle-app/chronicle-shell"
21
- spec.metadata["changelog_uri"] = "https://github.com/chronicle-app/chronicle-shell/releases"
18
+ spec.metadata['homepage_uri'] = spec.homepage
19
+ spec.metadata['source_code_uri'] = 'https://github.com/chronicle-app/chronicle-shell'
20
+ spec.metadata['changelog_uri'] = 'https://github.com/chronicle-app/chronicle-shell/releases'
22
21
 
23
22
  # Specify which files should be added to the gem when it is released.
24
23
  # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
@@ -27,9 +26,17 @@ Gem::Specification.new do |spec|
27
26
  (f == __FILE__) || f.match(%r{\A(?:(?:bin|test|spec|features)/|\.(?:git|travis|circleci)|appveyor)})
28
27
  end
29
28
  end
30
- spec.bindir = "exe"
29
+ spec.bindir = 'exe'
31
30
  spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
32
- spec.require_paths = ["lib"]
31
+ spec.require_paths = ['lib']
32
+ spec.required_ruby_version = '>= 3.1'
33
+ spec.metadata['rubygems_mfa_required'] = 'true'
33
34
 
34
- spec.add_dependency "chronicle-etl", "~> 0.5"
35
+ spec.add_dependency 'chronicle-core', '~> 0.3'
36
+ spec.add_dependency 'chronicle-etl', '~> 0.6'
37
+
38
+ spec.add_development_dependency 'bundler', '~> 2.3'
39
+ spec.add_development_dependency 'pry-byebug', '~> 3.10'
40
+ spec.add_development_dependency 'rake', '~> 13.0'
41
+ spec.add_development_dependency 'rubocop', '~> 1.63'
35
42
  end
@@ -4,9 +4,10 @@ module Chronicle
4
4
  module Shell
5
5
  class ShellHistoryExtractor < Chronicle::ETL::Extractor
6
6
  register_connector do |r|
7
- r.provider = 'shell'
7
+ r.source = :shell
8
+ r.type = :command
9
+ r.strategy = :local
8
10
  r.description = 'shell command history'
9
- r.identifier = 'history'
10
11
  end
11
12
 
12
13
  setting :input
@@ -25,11 +26,11 @@ module Chronicle
25
26
  def extract
26
27
  @commands.each do |command|
27
28
  meta = {
28
- username: username,
29
- hostname: hostname,
29
+ username:,
30
+ hostname:,
30
31
  shell_name: @config.shell
31
32
  }
32
- yield Chronicle::ETL::Extraction.new(data: command, meta: meta)
33
+ yield build_extraction(data: command, meta:)
33
34
  end
34
35
  end
35
36
 
@@ -42,11 +43,11 @@ module Chronicle
42
43
  end
43
44
 
44
45
  def history_filename_default_bash
45
- File.join(Dir.home, ".bash_history")
46
+ File.join(Dir.home, '.bash_history')
46
47
  end
47
48
 
48
49
  def history_filename_default_zsh
49
- File.join(Dir.home, ".zsh_history")
50
+ File.join(Dir.home, '.zsh_history')
50
51
  end
51
52
 
52
53
  def username
@@ -63,16 +64,17 @@ module Chronicle
63
64
  def load_commands
64
65
  commands = []
65
66
 
66
- loader = "load_commands_from_#{@config.shell}"
67
- __send__(loader) do |command|
68
- next if @config.since && command[:timestamp] < @config.since
69
- next if @config.until && command[:timestamp] > @config.until
70
-
71
- if block_given?
72
- yield command
73
- else
74
- commands << command
67
+ case @config.shell.to_sym
68
+ when :bash
69
+ load_commands_from_bash do |command|
70
+ process_command(command, commands)
71
+ end
72
+ when :zsh
73
+ load_commands_from_zsh do |command|
74
+ process_command(command, commands)
75
75
  end
76
+ else
77
+ raise "Unknown loader: #{@config.shell}"
76
78
  end
77
79
 
78
80
  commands = commands.first(@config.limit) if @config.limit
@@ -80,13 +82,24 @@ module Chronicle
80
82
  commands
81
83
  end
82
84
 
85
+ def process_command(command, commands)
86
+ return if @config.since && command[:timestamp] < @config.since
87
+ return if @config.until && command[:timestamp] > @config.until
88
+
89
+ if block_given?
90
+ yield command
91
+ else
92
+ commands << command
93
+ end
94
+ end
95
+
83
96
  def load_commands_from_bash
84
97
  command = nil
85
98
  File.readlines(history_filename).reverse_each do |line|
86
99
  timestamp_line = line.scrub.match(BASH_TIMESTAMP_REGEX)
87
100
  if timestamp_line && command
88
101
  timestamp = Time.at(timestamp_line[:timestamp].to_i)
89
- command = { timestamp: timestamp, command: command }
102
+ command = { timestamp:, command: }
90
103
  yield command
91
104
  else
92
105
  command = line.scrub.strip
@@ -1,59 +1,64 @@
1
1
  require 'chronicle/etl'
2
+ require 'chronicle/models'
2
3
 
3
4
  module Chronicle
4
- module Shell
5
+ module Shell
5
6
  class ShellHistoryTransformer < Chronicle::ETL::Transformer
6
7
  register_connector do |r|
7
- r.provider = 'shell'
8
+ r.source = :shell
9
+ r.type = :command
10
+ r.strategy = :local
8
11
  r.description = 'a shell command'
9
- r.identifier = 'history'
12
+ r.from_schema = :extraction
13
+ r.to_schema = :chronicle
10
14
  end
11
15
 
12
- def transform
13
- @command = @extraction.data
14
- build_commanded
15
- end
16
-
17
- def id
18
- end
16
+ def transform(record)
17
+ username = record.extraction.meta[:username]
18
+ hostname = record.extraction.meta[:hostname]
19
+ shell_name = record.extraction.meta[:shell_name]
20
+ timestamp = record.data[:timestamp]
21
+ command = record.data[:command]
19
22
 
20
- def timestamp
21
- @command[:timestamp]
23
+ build_command(username:, hostname:, command:, shell_name:,
24
+ timestamp:)
22
25
  end
23
26
 
24
27
  private
25
28
 
26
- def build_commanded
27
- record = ::Chronicle::ETL::Models::Activity.new
28
- record.verb = 'commanded'
29
- record.end_at = timestamp
30
- record.provider = @extraction.meta[:shell_name]
31
- record.dedupe_on << [:verb, :end_at, :provider]
32
- record.involved = build_command
33
- record.actor = build_actor
34
- record
29
+ def build_command(username:, hostname:, command:, shell_name:, timestamp:)
30
+ Chronicle::Models::ControlAction.new do |r|
31
+ r.source = shell_name
32
+ r.result = build_command_result(command)
33
+ r.agent = build_agent(username, hostname)
34
+ r.end_time = timestamp
35
+ # r.object = build_host
36
+ r.dedupe_on << %i[source type end_time]
37
+ end
35
38
  end
36
39
 
37
- def build_command
38
- record = ::Chronicle::ETL::Models::Entity.new
39
- record.represents = 'command'
40
- record.provider = @extraction.meta[:shell_name]
41
- record.body = @command[:command]
42
- record.dedupe_on << [:body, :provider, :represents]
43
- record
40
+ def build_command_result(text)
41
+ Chronicle::Models::ComputerCommand.new do |r|
42
+ r.text = text
43
+ r.source = 'system'
44
+ r.dedupe_on << %i[source text type]
45
+ end
44
46
  end
45
47
 
46
- def build_actor
47
- record = ::Chronicle::ETL::Models::Entity.new
48
- record.represents = 'identity'
49
- record.provider = 'system'
50
- record.slug = build_user_slug
51
- record.dedupe_on << [:represents, :provider, :slug]
52
- record
48
+ def build_agent(username, hostname)
49
+ Chronicle::Models::Person.new do |r|
50
+ r.source = 'system'
51
+ r.slug = build_user_slug(username, hostname)
52
+ r.dedupe_on << %i[source slug type]
53
+ end
53
54
  end
54
55
 
55
- def build_user_slug
56
- "#{@extraction.meta[:username]}@#{@extraction.meta[:hostname]}"
56
+ # TODO: implement this.
57
+ # TODO: figure out how to represent the host in schema
58
+ def build_host(hostname); end
59
+
60
+ def build_user_slug(username, hostname)
61
+ "#{username}@#{hostname}"
57
62
  end
58
63
  end
59
64
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Chronicle
4
4
  module Shell
5
- VERSION = "0.2.4"
5
+ VERSION = '0.3.0'
6
6
  end
7
7
  end
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "shell/version"
4
- require_relative "shell/shell_history_extractor"
5
- require_relative "shell/shell_history_transformer"
3
+ require_relative 'shell/version'
4
+ require_relative 'shell/shell_history_extractor'
5
+ require_relative 'shell/shell_history_transformer'
6
6
 
7
7
  module Chronicle
8
8
  module Shell
metadata CHANGED
@@ -1,29 +1,99 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: chronicle-shell
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.4
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrew Louis
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-03-25 00:00:00.000000000 Z
11
+ date: 2024-05-01 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: chronicle-core
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '0.3'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '0.3'
13
27
  - !ruby/object:Gem::Dependency
14
28
  name: chronicle-etl
15
29
  requirement: !ruby/object:Gem::Requirement
16
30
  requirements:
17
31
  - - "~>"
18
32
  - !ruby/object:Gem::Version
19
- version: '0.5'
33
+ version: '0.6'
20
34
  type: :runtime
21
35
  prerelease: false
22
36
  version_requirements: !ruby/object:Gem::Requirement
23
37
  requirements:
24
38
  - - "~>"
25
39
  - !ruby/object:Gem::Version
26
- version: '0.5'
40
+ version: '0.6'
41
+ - !ruby/object:Gem::Dependency
42
+ name: bundler
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '2.3'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '2.3'
55
+ - !ruby/object:Gem::Dependency
56
+ name: pry-byebug
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '3.10'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '3.10'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rake
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '13.0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '13.0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rubocop
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '1.63'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '1.63'
27
97
  description: Extract shell command history from Bash or Zsh
28
98
  email:
29
99
  - andrew@hyfen.net
@@ -31,6 +101,7 @@ executables: []
31
101
  extensions: []
32
102
  extra_rdoc_files: []
33
103
  files:
104
+ - ".rubocop.yml"
34
105
  - CODE_OF_CONDUCT.md
35
106
  - Gemfile
36
107
  - LICENSE.txt
@@ -49,6 +120,7 @@ metadata:
49
120
  homepage_uri: https://github.com/chronicle-app/chronicle-shell
50
121
  source_code_uri: https://github.com/chronicle-app/chronicle-shell
51
122
  changelog_uri: https://github.com/chronicle-app/chronicle-shell/releases
123
+ rubygems_mfa_required: 'true'
52
124
  post_install_message:
53
125
  rdoc_options: []
54
126
  require_paths:
@@ -57,14 +129,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
57
129
  requirements:
58
130
  - - ">="
59
131
  - !ruby/object:Gem::Version
60
- version: 2.7.0
132
+ version: '3.1'
61
133
  required_rubygems_version: !ruby/object:Gem::Requirement
62
134
  requirements:
63
135
  - - ">="
64
136
  - !ruby/object:Gem::Version
65
137
  version: '0'
66
138
  requirements: []
67
- rubygems_version: 3.3.3
139
+ rubygems_version: 3.4.10
68
140
  signing_key:
69
141
  specification_version: 4
70
142
  summary: Shell connectors for Chronicle-ETL