probium 0.0.1
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/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: []
|