Module: Legion::Extensions::Velociraptor::Helpers::Cli

Included in:
Client, Runners::Collections, Runners::Hunts, Runners::Query
Defined in:
lib/legion/extensions/velociraptor/helpers/cli.rb

Defined Under Namespace

Classes: CommandError

Constant Summary collapse

ENV_KEY_PATTERN =
/\A[A-Za-z_][A-Za-z0-9_]*\z/
ID_PATTERN =
/\A[A-Za-z]\.[A-Za-z0-9_-]+\z/
ARTIFACT_PATTERN =
%r{\A[A-Za-z][A-Za-z0-9_.-]*(/[A-Za-z][A-Za-z0-9_.-]*)?\z}

Instance Method Summary collapse

Instance Method Details

#dict_from_env_keys(env) ⇒ Object



112
113
114
# File 'lib/legion/extensions/velociraptor/helpers/cli.rb', line 112

def dict_from_env_keys(env)
  normalize_env(env).keys.map { |key| "#{key}=#{key}" }.join(', ').then { |items| "dict(#{items})" }
end

#normalize_env(env) ⇒ Object



103
104
105
106
107
108
109
110
# File 'lib/legion/extensions/velociraptor/helpers/cli.rb', line 103

def normalize_env(env)
  env.to_h.each_with_object({}) do |(key, value), normalized|
    name = key.to_s
    raise ArgumentError, "invalid VQL env key: #{name}" unless name.match?(ENV_KEY_PATTERN)

    normalized[name] = value.to_s
  end
end

#parse_output(stdout, format) ⇒ Object



67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
# File 'lib/legion/extensions/velociraptor/helpers/cli.rb', line 67

def parse_output(stdout, format)
  case format.to_sym
  when :jsonl
    stdout.to_s.each_line.filter_map do |line|
      next if line.strip.empty?

      stringify_keys(::JSON.parse(line))
    end
  when :json
    parsed = stringify_keys(::JSON.parse(stdout.to_s))
    parsed.is_a?(Array) ? parsed : [parsed]
  else
    stdout.to_s
  end
end

#run_command(command, timeout: nil) ⇒ Object



49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
# File 'lib/legion/extensions/velociraptor/helpers/cli.rb', line 49

def run_command(command, timeout: nil)
  stdout, stderr, status = capture(command, timeout: timeout)
  unless status.success?
    raise CommandError.new(
      "velociraptor command failed with exit #{status.exitstatus}",
      exit_status: status.exitstatus,
      stderr:      stderr,
      stdout:      stdout
    )
  end

  { success: true, stdout: stdout, stderr: stderr }
rescue Errno::ENOENT => e
  raise CommandError, e.message
rescue Timeout::Error => e
  raise CommandError, "velociraptor command timed out: #{e.message}"
end

#run_vql(vql:, env: {}, format: :jsonl, api_config: nil, binary: nil, timeout: nil) ⇒ Object



27
28
29
30
31
32
33
34
35
36
37
38
# File 'lib/legion/extensions/velociraptor/helpers/cli.rb', line 27

def run_vql(vql:, env: {}, format: :jsonl, api_config: nil, binary: nil, timeout: nil, **)
  normalized_env = normalize_env(env)
  command = velociraptor_query_command(
    vql:        vql,
    env:        normalized_env,
    format:     format,
    api_config: api_config,
    binary:     binary
  )
  result = run_command(command, timeout: timeout || option(:timeout))
  result.merge(rows: parse_output(result[:stdout], format))
end

#validate_artifact!(value) ⇒ Object

Raises:

  • (ArgumentError)


97
98
99
100
101
# File 'lib/legion/extensions/velociraptor/helpers/cli.rb', line 97

def validate_artifact!(value)
  return value.to_s if value.to_s.match?(ARTIFACT_PATTERN)

  raise ArgumentError, 'artifact must be a Velociraptor artifact name'
end

#validate_id!(value, label) ⇒ Object

Raises:

  • (ArgumentError)


91
92
93
94
95
# File 'lib/legion/extensions/velociraptor/helpers/cli.rb', line 91

def validate_id!(value, label)
  return value.to_s if value.to_s.match?(ID_PATTERN)

  raise ArgumentError, "#{label} must look like a Velociraptor id"
end

#velociraptor_query_command(vql:, env: {}, format: :jsonl, api_config: nil, binary: nil) ⇒ Object



40
41
42
43
44
45
46
47
# File 'lib/legion/extensions/velociraptor/helpers/cli.rb', line 40

def velociraptor_query_command(vql:, env: {}, format: :jsonl, api_config: nil, binary: nil)
  command = [binary || option(:binary) || 'velociraptor']
  config = api_config || option(:api_config)
  command += ['--api_config', config] if present?(config)
  command += ['query', vql.to_s, '--format', format.to_s]
  normalize_env(env).each { |key, value| command += ['--env', "#{key}=#{value}"] }
  command
end

#vql_list(values) ⇒ Object



87
88
89
# File 'lib/legion/extensions/velociraptor/helpers/cli.rb', line 87

def vql_list(values)
  Array(values).map { |value| vql_string(value) }.join(', ').then { |items| "[#{items}]" }
end

#vql_string(value) ⇒ Object



83
84
85
# File 'lib/legion/extensions/velociraptor/helpers/cli.rb', line 83

def vql_string(value)
  ::JSON.generate(value.to_s)
end