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.
@@ -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
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'runner'
4
+
5
+ begin
6
+ runner = Runner.new
7
+ puts runner.run
8
+ exit runner.exit_code
9
+ rescue StandardError => e
10
+ puts e
11
+ exit 1
12
+ end
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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: []