probium 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/bin/probium +12 -0
- data/lib/command_line.rb +46 -0
- data/lib/extensions.rb +50 -0
- data/lib/extensions/port.rb +103 -0
- data/lib/log.rb +18 -0
- data/lib/policy.rb +71 -0
- data/lib/resource.rb +90 -0
- data/lib/result_viewer.rb +99 -0
- data/lib/rule.rb +51 -0
- data/lib/runner.rb +117 -0
- metadata +94 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 0d8175b7893bcfa662a261942ea892c4d8be515f
|
4
|
+
data.tar.gz: 4e91a34fe46154dfc218d1b69346fe069a98eb90
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 3ab68bd317fcf6788e044010caf43134e84963d56e0f39d8cddf62bbea5dc25f3029a0bbeff49a5b51dae20088c32f2028be51f04e064e1e33cc62dffb53afcb
|
7
|
+
data.tar.gz: ebac34f2db1e54128678caf76b93c81226be2e0898a96904463ab30d28f22bb3f18364a0c250f538cbb4662e2c5e9d2cd3f69d29bda7311f980daec1f878d92d
|
data/bin/probium
ADDED
data/lib/command_line.rb
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
|
3
|
+
class CommandLine
|
4
|
+
VALID_OUTPUT_FORMATS = [:graphic, :json, :yaml, :csv].freeze
|
5
|
+
|
6
|
+
def self.parse!(options)
|
7
|
+
OptionParser.new do |opts|
|
8
|
+
opts.banner = 'Usage: probium my_policy.yaml [options]'
|
9
|
+
|
10
|
+
opts.on('-o', '--output-format=FORMAT', 'Format in which to display policy report (graphic, json, yaml, csv)') do |of|
|
11
|
+
if VALID_OUTPUT_FORMATS.include?(f = of.downcase.to_sym)
|
12
|
+
options[:output_format] = f
|
13
|
+
else
|
14
|
+
options[:message] = "Invalid output-format '#{of}'. Options are #{VALID_OUTPUT_FORMATS.join(', ')}"
|
15
|
+
options[:state] = :fail
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
opts.on('-e', '--extension-dir=PATH', 'Location of extension files') do |path|
|
20
|
+
options[:extensions_path] = path
|
21
|
+
end
|
22
|
+
|
23
|
+
opts.on('-d', '--debug', 'Enable debug output') do
|
24
|
+
options[:debug] = true
|
25
|
+
end
|
26
|
+
|
27
|
+
opts.on('--no-color', 'Disable color in output') do
|
28
|
+
options[:color] = false
|
29
|
+
end
|
30
|
+
|
31
|
+
opts.on('-h', '--help', 'Print this help') do
|
32
|
+
options[:message] = opts
|
33
|
+
options[:state] = :exit
|
34
|
+
end
|
35
|
+
end.parse!
|
36
|
+
options
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.policy!
|
40
|
+
policy_file = ARGV.shift
|
41
|
+
unless policy_file
|
42
|
+
raise StandardError, 'Missing required policy file as argument'
|
43
|
+
end
|
44
|
+
policy_file
|
45
|
+
end
|
46
|
+
end
|
data/lib/extensions.rb
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'log'
|
2
|
+
|
3
|
+
class Extensions
|
4
|
+
@@extensions = {}
|
5
|
+
|
6
|
+
def self.[](extension_name)
|
7
|
+
@@extensions[extension_name]
|
8
|
+
end
|
9
|
+
|
10
|
+
def initialize(location = File.join(File.dirname(__FILE__), 'extensions'))
|
11
|
+
validate_location(location)
|
12
|
+
@location = File.join(location, '*.rb')
|
13
|
+
load_extensions
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def load_extensions
|
19
|
+
extension_files = Dir.glob(@location)
|
20
|
+
extension_files.each do |extension_file|
|
21
|
+
next if File.directory?(extension_file)
|
22
|
+
begin
|
23
|
+
Log.debug { "Loading extension file - #{extension_file}"}
|
24
|
+
instance_eval(File.read(extension_file))
|
25
|
+
Log.debug { "Successfully loaded extension file - #{extension_file}"}
|
26
|
+
rescue Exception => e
|
27
|
+
Log.debug { "Error in extension #{extension_file} - #{e}" }
|
28
|
+
raise "Cannot load extension file '#{extension_file}'"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def create_resource(name, &block)
|
34
|
+
@@extensions[name] = { :resource => block }
|
35
|
+
end
|
36
|
+
|
37
|
+
def compare_fn(name, &block)
|
38
|
+
@@extensions[name] = { :compare_fn => block }
|
39
|
+
end
|
40
|
+
|
41
|
+
def validate_location(location)
|
42
|
+
unless File.exist?(location)
|
43
|
+
raise "Extensions directory '#{location}' does not exist"
|
44
|
+
end
|
45
|
+
|
46
|
+
unless File.directory?(location)
|
47
|
+
raise "Extensions direcotry '#{location}' is not a directory"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
require 'socket'
|
2
|
+
require 'openssl'
|
3
|
+
|
4
|
+
def is_ssl_enabled?(tcp_socket)
|
5
|
+
ctx = OpenSSL::SSL::SSLContext.new
|
6
|
+
ctx.set_params({ :options=>OpenSSL::SSL::OP_ALL })
|
7
|
+
ctx.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
8
|
+
enabled = true
|
9
|
+
|
10
|
+
OpenSSL::SSL::SSLSocket.new(tcp_socket, ctx).tap do |socket|
|
11
|
+
begin
|
12
|
+
socket.sync_close = true
|
13
|
+
socket.connect_nonblock
|
14
|
+
rescue IO::WaitReadable
|
15
|
+
if IO.select([socket], nil, nil, 1)
|
16
|
+
retry
|
17
|
+
else
|
18
|
+
enabled = false
|
19
|
+
end
|
20
|
+
rescue IO::WaitWritable
|
21
|
+
if IO.select([socket], nil, nil, 1)
|
22
|
+
retry
|
23
|
+
else
|
24
|
+
enabled = false
|
25
|
+
end
|
26
|
+
rescue OpenSSL::SSL::SSLError
|
27
|
+
enabled = false
|
28
|
+
end
|
29
|
+
|
30
|
+
return enabled
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def connect_to_port(port)
|
35
|
+
begin
|
36
|
+
TCPSocket.new('0.0.0.0', port)
|
37
|
+
rescue StandardError # Errno::ECONNREFUSED mainly but covering for timeouts
|
38
|
+
nil
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def get_port_state(port)
|
43
|
+
state = { :open => false,
|
44
|
+
:ssl => "unknown" }
|
45
|
+
|
46
|
+
tcp_socket = connect_to_port(port)
|
47
|
+
|
48
|
+
return state unless tcp_socket # couldn't connect, can't figure anything out
|
49
|
+
|
50
|
+
state[:open] = true
|
51
|
+
state[:ssl] = is_ssl_enabled?(tcp_socket)
|
52
|
+
|
53
|
+
tcp_socket.close
|
54
|
+
|
55
|
+
state
|
56
|
+
end
|
57
|
+
|
58
|
+
def combine_port_states(states)
|
59
|
+
states.reduce({}) do |old_state, state|
|
60
|
+
old_state[:open] ||= state[:open]
|
61
|
+
old_state[:ssl] ||= state[:ssl]
|
62
|
+
|
63
|
+
old_state[:open] &&= state[:open]
|
64
|
+
old_state[:ssl] &&= state[:open]
|
65
|
+
|
66
|
+
old_state
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
# TODO(ploubser): Write custom compare function
|
71
|
+
#compare_fn(:port) do |name, expected, actual|
|
72
|
+
#end
|
73
|
+
|
74
|
+
create_resource(:port) do |port|
|
75
|
+
resource = Puppet::Resource.new('ssl', port.to_s)
|
76
|
+
state = {}
|
77
|
+
|
78
|
+
if port =~ /^(\d+)-(\d+)$/
|
79
|
+
port_states = []
|
80
|
+
ports = ($1.to_i..$2.to_i).to_a
|
81
|
+
threads = (0...10).map do # god help us all
|
82
|
+
Thread.new do
|
83
|
+
while p = ports.pop
|
84
|
+
port_states << get_port_state(p)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
threads.map(&:join)
|
89
|
+
state = combine_port_states(port_states)
|
90
|
+
elsif port =~ /^(\d+)$/
|
91
|
+
state = get_port_state(port)
|
92
|
+
else
|
93
|
+
state = { :open => 'unknown',
|
94
|
+
:ssl => 'unknown' }
|
95
|
+
end
|
96
|
+
|
97
|
+
# add the state keys to the resource
|
98
|
+
state.each do |key, val|
|
99
|
+
resource[key] = val
|
100
|
+
end
|
101
|
+
|
102
|
+
resource
|
103
|
+
end
|
data/lib/log.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'logger'
|
2
|
+
|
3
|
+
class Log
|
4
|
+
@@log = Logger.new(STDOUT)
|
5
|
+
@@log.level = Logger::WARN
|
6
|
+
|
7
|
+
def self.initialize
|
8
|
+
@@log.level = Logger::DEBUG
|
9
|
+
@@log.formatter = proc do |severity, datetime, progname, msg|
|
10
|
+
calling_position = caller[5].split(/^.+\//).last.split(/:/)[0,2].join(':') # gross
|
11
|
+
"[#{datetime}] #{calling_position}: - #{msg}\n"
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.debug(&blk)
|
16
|
+
@@log.debug &blk
|
17
|
+
end
|
18
|
+
end
|
data/lib/policy.rb
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
require 'rule'
|
2
|
+
require 'facter'
|
3
|
+
require 'log'
|
4
|
+
|
5
|
+
class Policy
|
6
|
+
include Enumerable
|
7
|
+
extend Forwardable
|
8
|
+
def_delegators :@rules, :each
|
9
|
+
|
10
|
+
class PolicyError < StandardError
|
11
|
+
def initialize(message)
|
12
|
+
super("Invalid policy - #{message}")
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
VALID_KEYS = [:name, 'name', :rules, 'rules', :confine, 'confine'].freeze
|
17
|
+
|
18
|
+
attr_reader :rules
|
19
|
+
attr_reader :name
|
20
|
+
attr_reader :confines
|
21
|
+
|
22
|
+
def initialize(policy)
|
23
|
+
if (invalid_keys = policy.keys - VALID_KEYS).size > 0
|
24
|
+
raise PolicyError, "invalid field(s) '#{invalid_keys.join(',')}'"
|
25
|
+
end
|
26
|
+
|
27
|
+
@name = policy[:name] or policy['name'] or raise PolicyError, 'missing required field "name"'
|
28
|
+
@rules = policy[:rules] or policy['rules'] or raise PolicyError, 'missing required field "rules"'
|
29
|
+
@confines = policy[:confine] or policy['confine']
|
30
|
+
@confines ||= {}
|
31
|
+
|
32
|
+
unless @rules.is_a?(Array)
|
33
|
+
raise PolicyError, 'rules field must be an Array'
|
34
|
+
end
|
35
|
+
|
36
|
+
unless @rules.size > 0
|
37
|
+
raise PolicyError, 'rules Array must contain at least one rule'
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def enabled?
|
42
|
+
Log.debug { "Checking confine rules for policy - #{@name}" }
|
43
|
+
|
44
|
+
@confines.each do |fact_name, value|
|
45
|
+
if (fact_value = Facter.value(fact_name)) != value
|
46
|
+
Log.debug { "Skipping policy '#{@name} - #{fact_name}: #{fact_value.inspect} != #{value.inspect}"}
|
47
|
+
return false
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
Log.debug { "Policy '#{@name}' passed all confine rules." }
|
52
|
+
true
|
53
|
+
end
|
54
|
+
|
55
|
+
def check_rules
|
56
|
+
# Delay loading rules until Policy is checked. Puppet resources are expensive
|
57
|
+
# and we avoid it incase enabled? = false
|
58
|
+
@rules.map! { |r| Rule.new(r) }
|
59
|
+
|
60
|
+
result = { :name => @name,
|
61
|
+
:success => true,
|
62
|
+
:rules => [] }
|
63
|
+
@rules.each do |rule|
|
64
|
+
rule_result = rule.check_resources
|
65
|
+
result[:rules] << rule_result
|
66
|
+
result[:success] = false unless rule_result[:success]
|
67
|
+
end
|
68
|
+
|
69
|
+
result
|
70
|
+
end
|
71
|
+
end
|
data/lib/resource.rb
ADDED
@@ -0,0 +1,90 @@
|
|
1
|
+
require 'extensions'
|
2
|
+
require 'log'
|
3
|
+
require 'puppet'
|
4
|
+
|
5
|
+
class Resource
|
6
|
+
class ResourceError < StandardError
|
7
|
+
def initialize(msg)
|
8
|
+
super("Invalid resource - #{msg}")
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
attr_reader :title
|
13
|
+
attr_reader :type
|
14
|
+
attr_reader :name
|
15
|
+
attr_reader :expected_properties
|
16
|
+
attr_reader :puppet_resource
|
17
|
+
|
18
|
+
def initialize(title, properties)
|
19
|
+
if title.to_s =~ /^([A-Z].+)\[(.+)\]$/
|
20
|
+
@type = $1.to_s.downcase.to_sym
|
21
|
+
@name =$2.to_s.gsub(/'|"/, '')
|
22
|
+
else
|
23
|
+
raise ResourceError, "invalid resource title - #{title}"
|
24
|
+
end
|
25
|
+
|
26
|
+
@title = title
|
27
|
+
@expected_properties = properties
|
28
|
+
|
29
|
+
@compare_fn = lambda do |name, expected, actual|
|
30
|
+
if name == :ensure
|
31
|
+
if expected == 'present'
|
32
|
+
return actual != :absent
|
33
|
+
end
|
34
|
+
|
35
|
+
if expected == 'absent'
|
36
|
+
expected = expected.to_sym # user can express it as 'absent' or :absent
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
expected == actual
|
41
|
+
end
|
42
|
+
|
43
|
+
create_resource
|
44
|
+
end
|
45
|
+
|
46
|
+
def check_properties
|
47
|
+
result = { :success => true,
|
48
|
+
:title => @title,
|
49
|
+
:properties => [] }
|
50
|
+
@expected_properties.each do |property, value|
|
51
|
+
status = @compare_fn.call(property, value, @puppet_resource[property])
|
52
|
+
result[:success] = false unless status
|
53
|
+
result[:properties] << { :name => property,
|
54
|
+
:expected => value,
|
55
|
+
:actual => @puppet_resource[property].to_s,
|
56
|
+
:success => status }
|
57
|
+
end
|
58
|
+
|
59
|
+
result
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
def create_resource
|
65
|
+
Log.debug { "Loading resource #{@title}"}
|
66
|
+
start_time = Time.now
|
67
|
+
if extension = Extensions[@type]
|
68
|
+
Log.debug { "Found resource type '#{@type}' in extensions." }
|
69
|
+
@puppet_resource = extension[:resource].call(@name)
|
70
|
+
if extension[:compare_fn]
|
71
|
+
Log.debug { "Loading custom compare function for resource - #{@title}"}
|
72
|
+
@compare_fn = extension[:compare_fn]
|
73
|
+
end
|
74
|
+
else
|
75
|
+
begin
|
76
|
+
Log.debug { "Couldn't find resource type '#{@type}' in extensions. Creating Puppet resource." }
|
77
|
+
@puppet_resource = Puppet::Resource.indirection.find("#{@type}/#{@name}")
|
78
|
+
rescue Puppet::Error => e
|
79
|
+
msg = "Cannot create resource type - '#{@type}'. Unkown error - #{e}"
|
80
|
+
if e.message =~ /^.*Permission denied.*$/
|
81
|
+
msg = "Insufficient permissions to create resource - #{@title}"
|
82
|
+
elsif e.message =~ /^.*Could not find type.*$/
|
83
|
+
msg = "Cannot create unknown resource type - '#{@type}'"
|
84
|
+
end
|
85
|
+
raise Resource::ResourceError, msg
|
86
|
+
end
|
87
|
+
end
|
88
|
+
Log.debug { "Loaded resource #{@title} in #{Time.now - start_time}" }
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'json'
|
3
|
+
require 'csv'
|
4
|
+
require 'rainbow'
|
5
|
+
|
6
|
+
class ResultViewer
|
7
|
+
attr_reader :run_state
|
8
|
+
|
9
|
+
def initialize(policies)
|
10
|
+
@run_state = { :policies => policies,
|
11
|
+
:total => policies.size,
|
12
|
+
:successes => policies.reduce(0) { |count, policy| policy[:success] ? count + 1 : count } }
|
13
|
+
end
|
14
|
+
|
15
|
+
def to_s
|
16
|
+
string_buffer = []
|
17
|
+
string_buffer << "Total Policies: #{@run_state[:total]}"
|
18
|
+
|
19
|
+
@run_state[:policies].each do |policy|
|
20
|
+
string_buffer << stringify_policy(policy)
|
21
|
+
end
|
22
|
+
|
23
|
+
string_buffer << "Passed: #{@run_state[:successes]}/#{@run_state[:total]}"
|
24
|
+
string_buffer.join("\n\n")
|
25
|
+
end
|
26
|
+
|
27
|
+
def to_yaml
|
28
|
+
@run_state.to_yaml
|
29
|
+
end
|
30
|
+
|
31
|
+
def to_json
|
32
|
+
@run_state.to_json
|
33
|
+
end
|
34
|
+
|
35
|
+
def to_csv
|
36
|
+
## CSV.generate do |csv|
|
37
|
+
# csv << ['Rule', 'Property', 'Expected Value', 'Actual Value', 'Success']
|
38
|
+
# rule_results.each do |rule, result|
|
39
|
+
# result.each do |r|
|
40
|
+
# csv << [rule, r[:property], r[:expected], r[:actual], r[:success]]
|
41
|
+
# end
|
42
|
+
# end
|
43
|
+
# end
|
44
|
+
|
45
|
+
"TODO(ploubser): Implement"
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def stringify_policy(policy)
|
51
|
+
string_buffer = []
|
52
|
+
color = policy[:success] ? :green : :red
|
53
|
+
string_buffer << "Policy: #{Rainbow(policy[:name]).send(color)}"
|
54
|
+
policy[:rules].each do |rule|
|
55
|
+
string_buffer << stringify_rule(rule)
|
56
|
+
end
|
57
|
+
string_buffer.join("\n\n")
|
58
|
+
end
|
59
|
+
|
60
|
+
def stringify_rule(rule)
|
61
|
+
string_buffer = []
|
62
|
+
color = rule[:success] ? :green : :red
|
63
|
+
string_buffer << "Description: #{Rainbow(rule[:description]).send(color)}"
|
64
|
+
|
65
|
+
if rule[:severity]
|
66
|
+
string_buffer << "Severity: #{Rainbow(rule[:severity]).send(color)}"
|
67
|
+
end
|
68
|
+
|
69
|
+
rule[:resources].each do |resource|
|
70
|
+
string_buffer << stringify_resource(resource)
|
71
|
+
end
|
72
|
+
|
73
|
+
indent_buffer(string_buffer).join("\n")
|
74
|
+
end
|
75
|
+
|
76
|
+
def stringify_resource(resource)
|
77
|
+
string_buffer = []
|
78
|
+
string_buffer << resource[:title]
|
79
|
+
|
80
|
+
resource[:properties].each do |property|
|
81
|
+
string_buffer << stringify_property(property)
|
82
|
+
end
|
83
|
+
indent_buffer(string_buffer).join("\n")
|
84
|
+
end
|
85
|
+
|
86
|
+
def stringify_property(property)
|
87
|
+
string_buffer = []
|
88
|
+
color = property[:success] ? :green : :red
|
89
|
+
string_buffer << "#{property[:name]}: #{property[:expected]} -> #{Rainbow(property[:actual]).send(color)}"
|
90
|
+
indent_buffer(string_buffer, 8).join("\n")
|
91
|
+
end
|
92
|
+
|
93
|
+
|
94
|
+
def indent_buffer(buffer, count=4)
|
95
|
+
buffer.map do |s|
|
96
|
+
' ' * count + s.to_s
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
data/lib/rule.rb
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'log'
|
2
|
+
require 'resource'
|
3
|
+
|
4
|
+
class Rule
|
5
|
+
class RuleError < StandardError
|
6
|
+
def initialize(msg)
|
7
|
+
super("Invalid rule - #{msg}")
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
VALID_KEYS = [:resources, 'resources',
|
12
|
+
:description, 'description',
|
13
|
+
:severity, 'severity'].freeze
|
14
|
+
|
15
|
+
attr_reader :resources
|
16
|
+
attr_reader :description
|
17
|
+
attr_reader :severity
|
18
|
+
|
19
|
+
def initialize(rule)
|
20
|
+
if (invalid_keys = rule.keys - VALID_KEYS).size > 0
|
21
|
+
raise RuleError, "invalid field(s) '#{invalid_keys.join(',')}'"
|
22
|
+
end
|
23
|
+
|
24
|
+
tmp_resources = rule[:resources] or rule['resources'] or
|
25
|
+
raise RuleError, 'missing required field "resources"'
|
26
|
+
@description = rule[:description] or rule['description'] or
|
27
|
+
raise RuleError, 'missing required field "description"'
|
28
|
+
@severity = rule[:severity] or rule['severity']
|
29
|
+
|
30
|
+
unless tmp_resources.is_a? Hash
|
31
|
+
raise RuleError, 'resources field must be an Hash'
|
32
|
+
end
|
33
|
+
|
34
|
+
@resources = tmp_resources.map do |title, properties|
|
35
|
+
Resource.new(title, properties)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def check_resources
|
40
|
+
result = { :description => @description,
|
41
|
+
:severity => @severity,
|
42
|
+
:success => true,
|
43
|
+
:resources => [] }
|
44
|
+
@resources.each do |resource|
|
45
|
+
resource_result = resource.check_properties
|
46
|
+
result[:resources] << resource_result
|
47
|
+
result[:success] = false unless resource_result[:success]
|
48
|
+
end
|
49
|
+
result
|
50
|
+
end
|
51
|
+
end
|
data/lib/runner.rb
ADDED
@@ -0,0 +1,117 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'erb'
|
3
|
+
require 'command_line'
|
4
|
+
require 'extensions'
|
5
|
+
require 'result_viewer'
|
6
|
+
require 'policy'
|
7
|
+
require 'log'
|
8
|
+
|
9
|
+
class Runner
|
10
|
+
attr_reader :exit_code
|
11
|
+
|
12
|
+
def initialize
|
13
|
+
@defaults = { :output_format => :graphic,
|
14
|
+
:extensions_path => File.join(File.dirname(__FILE__), 'extensions'),
|
15
|
+
:debug => false,
|
16
|
+
:color => true,
|
17
|
+
:state => :run,
|
18
|
+
:msg => nil }
|
19
|
+
@options = CommandLine.parse!(@defaults)
|
20
|
+
|
21
|
+
if msg = @options.delete(:message)
|
22
|
+
puts msg
|
23
|
+
end
|
24
|
+
|
25
|
+
case @options.delete(:state)
|
26
|
+
when :exit
|
27
|
+
exit 0
|
28
|
+
when :fail
|
29
|
+
exit 1
|
30
|
+
end
|
31
|
+
|
32
|
+
if @options[:debug]
|
33
|
+
Log.initialize
|
34
|
+
end
|
35
|
+
|
36
|
+
unless @options[:color]
|
37
|
+
Rainbow.enabled = false
|
38
|
+
end
|
39
|
+
|
40
|
+
Log.debug { "Starting policy check with configured option - #{@options.inspect}" }
|
41
|
+
|
42
|
+
@extensions = Extensions.new(@options[:extensions_path])
|
43
|
+
policy_file = CommandLine.policy!
|
44
|
+
@policies = load_policies(policy_file).map { |policy| Policy.new(policy) }
|
45
|
+
@exit_code = 0
|
46
|
+
end
|
47
|
+
|
48
|
+
def run
|
49
|
+
processed_policies = []
|
50
|
+
|
51
|
+
@policies.each do |policy|
|
52
|
+
if policy.enabled?
|
53
|
+
processed_policies << policy.check_rules
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
result_viewer = ResultViewer.new(processed_policies)
|
58
|
+
|
59
|
+
if result_viewer.run_state[:successes] != result_viewer.run_state[:total]
|
60
|
+
@exit_code = 1
|
61
|
+
end
|
62
|
+
|
63
|
+
case @options[:output_format]
|
64
|
+
when :graphic
|
65
|
+
result_viewer.to_s
|
66
|
+
when :json
|
67
|
+
result_viewer.to_json
|
68
|
+
when :yaml
|
69
|
+
result_viewer.to_yaml
|
70
|
+
# TODO(ploubser): Fix this
|
71
|
+
#when :csv
|
72
|
+
# result_viewer.to_csv
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
def load_policies(target_location)
|
79
|
+
policies = []
|
80
|
+
|
81
|
+
if File.directory?(target_location)
|
82
|
+
Dir.glob(target_location + '/*').each do |policy_file|
|
83
|
+
policies << load_policy_file(policy_file)
|
84
|
+
end
|
85
|
+
else
|
86
|
+
policies << load_policy_file(target_location)
|
87
|
+
end
|
88
|
+
|
89
|
+
policies
|
90
|
+
end
|
91
|
+
|
92
|
+
def load_policy_file(policy_file)
|
93
|
+
Log.debug { "Loading policy file - #{policy_file}" }
|
94
|
+
|
95
|
+
unless File.exist?(policy_file)
|
96
|
+
raise "Cannot find policy file - '#{policy_file}'"
|
97
|
+
end
|
98
|
+
|
99
|
+
policy = {}
|
100
|
+
|
101
|
+
begin
|
102
|
+
if policy_file =~ /^.*\.erb$/
|
103
|
+
template = ERB.new(File.read(policy_file, 3, '>')).result
|
104
|
+
policy = YAML.load(template)
|
105
|
+
else
|
106
|
+
policy = YAML.load_file(policy_file)
|
107
|
+
end
|
108
|
+
rescue StandardError => e
|
109
|
+
Log.debug { e.message }
|
110
|
+
raise "Invalid Policy file - '#{policy_file}'"
|
111
|
+
end
|
112
|
+
|
113
|
+
Log.debug { "Successfully loaded policy file - #{policy_file}"}
|
114
|
+
|
115
|
+
policy
|
116
|
+
end
|
117
|
+
end
|
metadata
ADDED
@@ -0,0 +1,94 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: probium
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Pieter Loubser
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-04-12 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rainbow
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '2.2'
|
20
|
+
- - ">="
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: 2.2.1
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - "~>"
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '2.2'
|
30
|
+
- - ">="
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 2.2.1
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: puppet
|
35
|
+
requirement: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - "~>"
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '4.3'
|
40
|
+
- - ">="
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
version: 4.3.2
|
43
|
+
type: :runtime
|
44
|
+
prerelease: false
|
45
|
+
version_requirements: !ruby/object:Gem::Requirement
|
46
|
+
requirements:
|
47
|
+
- - "~>"
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
version: '4.3'
|
50
|
+
- - ">="
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
version: 4.3.2
|
53
|
+
description: A CLI tool that uses Puppet resources to validate YAML or JSON policies.
|
54
|
+
email: ploubser@gmail.com
|
55
|
+
executables:
|
56
|
+
- probium
|
57
|
+
extensions: []
|
58
|
+
extra_rdoc_files: []
|
59
|
+
files:
|
60
|
+
- bin/probium
|
61
|
+
- lib/command_line.rb
|
62
|
+
- lib/extensions.rb
|
63
|
+
- lib/extensions/port.rb
|
64
|
+
- lib/log.rb
|
65
|
+
- lib/policy.rb
|
66
|
+
- lib/resource.rb
|
67
|
+
- lib/result_viewer.rb
|
68
|
+
- lib/rule.rb
|
69
|
+
- lib/runner.rb
|
70
|
+
homepage: https://github.com/ploubser/probium
|
71
|
+
licenses:
|
72
|
+
- Apache-2.0
|
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.6.8
|
91
|
+
signing_key:
|
92
|
+
specification_version: 4
|
93
|
+
summary: CLI policy checking tool
|
94
|
+
test_files: []
|