kriterion 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 42aff9f85dd3bbed3821c4028322c8918b5dc3e0ec7a5ce46691b6eb737aef7b
4
- data.tar.gz: 62929aa1fd49a1f16739c0914b4739678e7e551198796635a1b52d092a88ebcb
3
+ metadata.gz: 2ab8ab52ebad0244b1765403f402b557a447230df58eb131c0dcbe2e57a6c56a
4
+ data.tar.gz: ad31c92bc460c6ac60b4cfe9040cc2a2df2072c9b1c03938e5898462621fe095
5
5
  SHA512:
6
- metadata.gz: 3ae4cf132a87e5d2834c9cb13f6e7ff9c352856192ba8efa041eb28a31c21f6b28f12afa5513a2303f3d4ff56925ba80d3226cd0ff049128e87059c534f1e4bc
7
- data.tar.gz: 04d59dc56805ce22a40d77cdd7779a17b8baf43db965d93c2d3b6db2740f7e57e11cef86c3b9bb2790a16734d9995769134c40a9e38e45ab1c9754314341c072
6
+ metadata.gz: 31aef891445eb70aa59053bb9fa198ca8e0a8cbf3e8d540ee752b8282e368643ea03a470122f5a1cf8a75e2ac8457fd9d9d769b5049e65b669ca83a2b7759152
7
+ data.tar.gz: 9b0ba3e9dd101cd8d91e37ae8a3a40cca2edd3476124e02fb0d4215fbf21458e420c96e046793e433ffc6ef19eba18263e08747bf0f5e74a424f7c05ec784d1c
@@ -0,0 +1,3 @@
1
+
2
+ Style/ClassVars:
3
+ Enabled: false
data/Dockerfile CHANGED
@@ -14,5 +14,4 @@ ENV mongo_port 27017
14
14
  ENV queue reports
15
15
  ENV uri http://restmq:8888
16
16
 
17
- ENTRYPOINT ["bundle", "exec", "kriterion", "worker"]
18
- CMD ["--debug"]
17
+ ENTRYPOINT ["bundle", "exec", "kriterion"]
data/Gemfile CHANGED
@@ -9,4 +9,6 @@ group :development do
9
9
  gem 'nokogiri'
10
10
  gem 'pry'
11
11
  gem 'pry-byebug'
12
+ gem 'kubeclient'
13
+ gem 'googleauth'
12
14
  end
@@ -1,57 +1,119 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- kriterion (0.0.1)
4
+ kriterion (0.1.0)
5
5
  cri (~> 2.10)
6
6
  httparty (~> 0.16)
7
7
  mongo (~> 2.5)
8
+ sinatra (~> 2.0)
8
9
 
9
10
  GEM
10
11
  remote: https://rubygems.org/
11
12
  specs:
13
+ addressable (2.5.2)
14
+ public_suffix (>= 2.0.2, < 4.0)
12
15
  bson (4.3.0)
13
16
  byebug (10.0.2)
14
17
  coderay (1.1.2)
15
18
  colored (1.2)
16
- cri (2.10.1)
19
+ cri (2.12.0)
17
20
  colored (~> 1.2)
18
21
  diff-lcs (1.3)
22
+ domain_name (0.5.20180417)
23
+ unf (>= 0.0.5, < 1.0.0)
24
+ faraday (0.15.2)
25
+ multipart-post (>= 1.2, < 3)
26
+ googleauth (0.6.4)
27
+ faraday (~> 0.12)
28
+ jwt (>= 1.4, < 3.0)
29
+ memoist (~> 0.12)
30
+ multi_json (~> 1.11)
31
+ os (>= 0.9, < 2.0)
32
+ signet (~> 0.7)
33
+ http (3.3.0)
34
+ addressable (~> 2.3)
35
+ http-cookie (~> 1.0)
36
+ http-form_data (~> 2.0)
37
+ http_parser.rb (~> 0.6.0)
38
+ http-cookie (1.0.3)
39
+ domain_name (~> 0.5)
40
+ http-form_data (2.1.1)
41
+ http_parser.rb (0.6.0)
19
42
  httparty (0.16.2)
20
43
  multi_xml (>= 0.5.2)
44
+ jwt (2.1.0)
45
+ kubeclient (4.0.0)
46
+ http (~> 3.0)
47
+ recursive-open-struct (~> 1.0, >= 1.0.4)
48
+ rest-client (~> 2.0)
49
+ memoist (0.16.0)
21
50
  method_source (0.9.0)
51
+ mime-types (3.2.2)
52
+ mime-types-data (~> 3.2015)
53
+ mime-types-data (3.2018.0812)
22
54
  mini_portile2 (2.3.0)
23
- mongo (2.6.1)
55
+ mongo (2.6.2)
24
56
  bson (>= 4.3.0, < 5.0.0)
57
+ multi_json (1.13.1)
25
58
  multi_xml (0.6.0)
59
+ multipart-post (2.0.0)
60
+ mustermann (1.0.2)
61
+ netrc (0.11.0)
26
62
  nokogiri (1.8.4)
27
63
  mini_portile2 (~> 2.3.0)
64
+ os (1.0.0)
28
65
  pry (0.11.3)
29
66
  coderay (~> 1.1.0)
30
67
  method_source (~> 0.9.0)
31
68
  pry-byebug (3.6.0)
32
69
  byebug (~> 10.0)
33
70
  pry (~> 0.10)
71
+ public_suffix (3.0.3)
72
+ rack (2.0.5)
73
+ rack-protection (2.0.3)
74
+ rack
34
75
  rake (10.5.0)
35
- rspec (3.7.0)
36
- rspec-core (~> 3.7.0)
37
- rspec-expectations (~> 3.7.0)
38
- rspec-mocks (~> 3.7.0)
39
- rspec-core (3.7.1)
40
- rspec-support (~> 3.7.0)
41
- rspec-expectations (3.7.0)
76
+ recursive-open-struct (1.1.0)
77
+ rest-client (2.0.2)
78
+ http-cookie (>= 1.0.2, < 2.0)
79
+ mime-types (>= 1.16, < 4.0)
80
+ netrc (~> 0.8)
81
+ rspec (3.8.0)
82
+ rspec-core (~> 3.8.0)
83
+ rspec-expectations (~> 3.8.0)
84
+ rspec-mocks (~> 3.8.0)
85
+ rspec-core (3.8.0)
86
+ rspec-support (~> 3.8.0)
87
+ rspec-expectations (3.8.1)
42
88
  diff-lcs (>= 1.2.0, < 2.0)
43
- rspec-support (~> 3.7.0)
44
- rspec-mocks (3.7.0)
89
+ rspec-support (~> 3.8.0)
90
+ rspec-mocks (3.8.0)
45
91
  diff-lcs (>= 1.2.0, < 2.0)
46
- rspec-support (~> 3.7.0)
47
- rspec-support (3.7.1)
92
+ rspec-support (~> 3.8.0)
93
+ rspec-support (3.8.0)
94
+ signet (0.8.1)
95
+ addressable (~> 2.3)
96
+ faraday (~> 0.9)
97
+ jwt (>= 1.5, < 3.0)
98
+ multi_json (~> 1.10)
99
+ sinatra (2.0.3)
100
+ mustermann (~> 1.0)
101
+ rack (~> 2.0)
102
+ rack-protection (= 2.0.3)
103
+ tilt (~> 2.0)
104
+ tilt (2.0.8)
105
+ unf (0.1.4)
106
+ unf_ext
107
+ unf_ext (0.0.7.5)
48
108
 
49
109
  PLATFORMS
50
110
  ruby
51
111
 
52
112
  DEPENDENCIES
53
113
  bundler (~> 1.16)
114
+ googleauth
54
115
  kriterion!
116
+ kubeclient
55
117
  nokogiri
56
118
  pry
57
119
  pry-byebug
data/README.md CHANGED
@@ -1,9 +1,5 @@
1
1
  # Kriterion
2
2
 
3
- Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/kriterion`. To experiment with that code, run `bin/console` for an interactive prompt.
4
-
5
- TODO: Delete this and the text above, and describe your gem
6
-
7
3
  ## Installation
8
4
 
9
5
  Add this line to your application's Gemfile:
@@ -28,6 +24,8 @@ TODO: Write usage instructions here
28
24
 
29
25
  This project requires MongoDB and RestMQ to be up and working. You can run them up manually using the commands below, or run `docker-compose up` to spin up everything.
30
26
 
27
+ To populate the queue with example reports, run `bundle exec ruby spec/populate_queue.rb http://localhost:8888`
28
+
31
29
  ### Docker Containers
32
30
 
33
31
  #### `kriterion_worker`
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env bash
2
+
3
+ docker build -t kriterion-base .
4
+ docker build -t kriterion-worker docker/worker
5
+ docker build -t kriterion-api docker/api
@@ -0,0 +1,60 @@
1
+ #! /bin/env ruby
2
+ require 'kubeclient'
3
+ require 'pry'
4
+
5
+ deployment = ARGV[0]
6
+ auth_method = ARGV[1]
7
+
8
+ namespace = 'kriterion'
9
+
10
+ case auth_method
11
+ when 'k8s'
12
+ # Use the token file that exists within K8s
13
+ auth_options = {
14
+ bearer_token_file: '/var/run/secrets/kubernetes.io/serviceaccount/token'
15
+ }
16
+
17
+ ssl_options = {}
18
+ if File.exist?('/var/run/secrets/kubernetes.io/serviceaccount/ca.crt')
19
+ ssl_options[:ca_file] = '/var/run/secrets/kubernetes.io/serviceaccount/ca.crt'
20
+ end
21
+
22
+ client = Kubeclient::Client.new(
23
+ 'https://kubernetes.default.svc',
24
+ 'v1',
25
+ auth_options: auth_options,
26
+ ssl_options: ssl_options
27
+ )
28
+ when 'local'
29
+ # require 'googleauth'
30
+ config = Kubeclient::Config.read(ENV['KUBECONFIG'] || '~/.kube/config')
31
+ context = config.context
32
+
33
+ auth_options = {
34
+ bearer_token: ENV['KUBETOKEN']
35
+ }
36
+
37
+ client = Kubeclient::Client.new(
38
+ context.api_endpoint,
39
+ context.api_version,
40
+ ssl_options: context.ssl_options,
41
+ auth_options: auth_options
42
+ )
43
+ end
44
+
45
+ puts "Discovering Services..."
46
+ client.discover
47
+
48
+ puts "Getting Pods..."
49
+
50
+ client.get_pods(namespace: namespace).each do |pod|
51
+ # Check if it's one that we want to kill
52
+ if pod[:metadata][:labels][:deployment] == deployment
53
+ puts "Found pod #{pod[:metadata][:name]} in deployment #{pod[:metadata][:labels][:deployment]}"
54
+ puts "Killing #{pod[:metadata][:name]}..."
55
+ client.delete_pod(pod[:metadata][:name], namespace)
56
+ puts "Done."
57
+ end
58
+ end
59
+
60
+ puts "Done."
@@ -0,0 +1,2 @@
1
+ require './lib/kriterion/api'
2
+ run Kriterion::API
@@ -24,6 +24,7 @@ Gem::Specification.new do |spec|
24
24
  spec.add_runtime_dependency 'cri', '~> 2.10'
25
25
  spec.add_runtime_dependency 'httparty', '~> 0.16'
26
26
  spec.add_runtime_dependency 'mongo', '~> 2.5'
27
+ spec.add_runtime_dependency 'sinatra', '~> 2.0'
27
28
 
28
29
  spec.add_development_dependency 'bundler', '~> 1.16'
29
30
  spec.add_development_dependency 'rake', '~> 10.0'
@@ -2,7 +2,16 @@ version: '3'
2
2
  services:
3
3
  worker:
4
4
  build: .
5
+ image: kriterion
6
+ command: worker
5
7
  tty: true
8
+ api:
9
+ build: .
10
+ image: kriterion
11
+ command: api
12
+ tty: true
13
+ ports:
14
+ - "4567:4567"
6
15
  mongodb:
7
16
  image: mongo:latest
8
17
  ports:
@@ -1,27 +1,122 @@
1
1
  require 'json'
2
- require 'mongo'
2
+ require 'sinatra/base'
3
3
  require 'kriterion/logs'
4
- include Kriterion::Logs
4
+ require 'kriterion/connector'
5
5
 
6
6
  class Kriterion
7
- class API
8
- attr_reader :mongo
9
- attr_reader :standards_dir
7
+ class API < Sinatra::Application
8
+ attr_accessor :queue_uri
9
+ attr_accessor :metrics
10
+ attr_accessor :backend
10
11
 
11
- def initialize(opts)
12
- if opts[:debug]
13
- logger.level = Kriterion::Logs::DEBUG
12
+ include Kriterion::Logs
13
+
14
+ # We only every want to have one instance of this running at a time. This is
15
+ # required due to the way that Sinatra initialises things This adds the
16
+ # initialize method etc.
17
+ @@instance = nil
18
+
19
+ def initialize(opts = {})
20
+ # If there is already an instance, copy the objects from that
21
+ if @@instance
22
+ @queue_uri = @@instance.queue_uri
23
+ @metrics = @@instance.metrics
24
+ @backend = @@instance.backend
25
+ else
26
+ @queue_uri, @metrics, @backend = Kriterion::Connector.connections(opts)
27
+ @@instance = self
28
+ end
29
+ super()
30
+ logger.info "Initialised Kritioner API version #{Kriterion::VERSION}"
31
+ end
32
+
33
+ set :bind, '0.0.0.0'
34
+ set :environment, :test
35
+
36
+ # Set headers and default values
37
+ before do
38
+ headers 'Content-Type' => 'application/json'
39
+ end
40
+
41
+ get '/standards' do
42
+ find 'standards'
43
+ end
44
+
45
+ get '/standards/:name' do |name|
46
+ find 'standards', name: name
47
+ end
48
+
49
+ get '/sections' do
50
+ find 'sections', standard: options[:standard]
51
+ end
52
+
53
+ get '/sections/:uuid' do |uuid|
54
+ find 'sections', uuid: uuid
55
+ end
56
+
57
+ get '/sections/:uuid/sections' do |uuid|
58
+ # Get the main section
59
+ parent = backend.find_section(
60
+ { uuid: uuid },
61
+ recurse: true
62
+ )
63
+
64
+ # Return all direct children
65
+ parent.sections.map do |section|
66
+ section.to_h(options[:mode])
67
+ end.to_json
68
+ end
69
+
70
+ get '/resources' do
71
+ find 'resources', parent_uuid: options[:item]
72
+ end
73
+
74
+ get '/resources/:uuid' do |uuid|
75
+ find 'resources', uuid: uuid
76
+ end
77
+
78
+ private
79
+
80
+ # Finds things in the backend and returns them as JSON
81
+ def find(thing, query = {})
82
+ result = backend.send("find_#{thing}", query, recurse: options[:recurse])
83
+
84
+ if result.is_a? Array
85
+ result.map do |object|
86
+ object.to_h(options[:mode])
87
+ end.to_json
88
+ else
89
+ result.to_h(options[:mode]).to_json
90
+ end
91
+ end
92
+
93
+ # Returns options that are relevant to the queries we will be doing based
94
+ # on the params that were passed
95
+ def options
96
+ # Defualt level should be full
97
+ level = params['level'] || 'full'
98
+
99
+ # Convert all other params to symbols for later use
100
+ sym_params = params.each_with_object({}) do |memo, (k, v)|
101
+ memo[k.to_sym] = v
102
+ memo
14
103
  end
15
104
 
16
- @mongo_hostname = opts[:mongo_hostname]
17
- @mongo_port = opts[:mongo_port]
18
- @mongo_database = opts[:mongo_database]
19
- @mongo = Mongo::Client.new([ "#{@mongo_hostname}:#{@mongo_port}" ], :database => @mongo_database)
20
- @standards_dir = opts[:standards_dir]
105
+ # Return all data
106
+ sym_params.merge(mode_options[level])
21
107
  end
22
108
 
23
- def run
24
- # Find all standards and add them to mongodb
109
+ def mode_options
110
+ {
111
+ 'basic' => {
112
+ recurse: false,
113
+ mode: :basic
114
+ },
115
+ 'full' => {
116
+ recurse: true,
117
+ mode: :full
118
+ }
119
+ }
25
120
  end
26
121
  end
27
122
  end
@@ -1,6 +1,73 @@
1
+ require 'kriterion/metrics'
2
+
3
+
1
4
  class Kriterion
2
5
  class Backend
3
- @@nackend = nil
6
+ @@backend = nil
7
+
8
+ attr_reader :metrics
9
+
10
+ def initialize(opts)
11
+ @metrics = opts[:metrics] || Kriterion::Metrics.new
12
+ end
13
+
14
+ # This is the meat of what a backend is. This section defines the following
15
+ # methods:
16
+ #
17
+ # - find_standard
18
+ # - find_standards
19
+ # - find_section
20
+ # - find_sections
21
+ # - find_item
22
+ # - find_items
23
+ # - find_resource
24
+ # - find_resources
25
+ #
26
+ # These are the main methods that Kriterion will use to interact with the
27
+ # backend. The backend must in turn implement the following methods:
28
+ #
29
+ # - find(object_type, query, options)
30
+ # - insert(object)
31
+ #
32
+ %i[
33
+ standard
34
+ section
35
+ item
36
+ resource
37
+ event
38
+ ].each do |thing|
39
+ define_method("find_#{thing}") do |query, opts|
40
+ validate_opts opts
41
+
42
+ # Call out to find_{thing}s
43
+ results = send("find_#{thing}s", query, opts)
44
+
45
+ # Validate that we only have one result and return it
46
+ return results.first if results.count == 1
47
+ raise "Found > 1 #{thing} with name: #{name}" if results.count > 1
48
+ nil
49
+ end
50
+
51
+ define_method("find_#{thing}s") do |query, opts|
52
+ result = nil
53
+ metrics[:"backend_find_#{thing}s"] += Benchmark.realtime do
54
+ result = find(thing, query, opts)
55
+ end
56
+ result
57
+ end
58
+
59
+ define_method("ensure_#{thing}") do |object|
60
+ pk = object.primary_key
61
+ query = {
62
+ pk => object.send(pk)
63
+ }
64
+
65
+ metrics[:backend_insert] += Benchmark.realtime do
66
+ insert(object) unless send("find_#{thing}", query, recurse: false)
67
+ end
68
+ object
69
+ end
70
+ end
4
71
 
5
72
  def self.set(backend)
6
73
  @@backend = backend
@@ -9,5 +76,18 @@ class Kriterion
9
76
  def self.get
10
77
  @@backend
11
78
  end
79
+
80
+ private
81
+
82
+ # Validate options hash
83
+ def validate_opts(opts)
84
+ valid_keys = [
85
+ :recurse
86
+ ]
87
+
88
+ unless opts.keys.all? { |k| valid_keys.include?(k) }
89
+ raise "Options hash is invalid #{opts}"
90
+ end
91
+ end
12
92
  end
13
93
  end
@@ -23,11 +23,10 @@ class Kriterion
23
23
  attr_reader :resources_db
24
24
  attr_reader :events_db
25
25
  attr_reader :standard_details_db
26
- attr_reader :metrics
27
26
 
28
27
  def initialize(opts)
28
+ super(opts)
29
29
  logger.info 'Initializing MongoDB backend'
30
- @metrics = opts[:metrics] || Kriterion::Metrics.new
31
30
  @hostname = opts[:hostname]
32
31
  @port = opts[:port]
33
32
  @database = opts[:database]
@@ -43,47 +42,23 @@ class Kriterion
43
42
  @standard_details_db = @client[:standard_details]
44
43
  end
45
44
 
46
- def get_standard(name, opts = {})
47
- standard = nil
48
- metrics[:backend_get_standard] += Benchmark.realtime do
49
- # Set recursion to false by default
50
- opts[:recurse] = opts[:recurse] || false
45
+ def find(type, query, opts)
46
+ database_for(type).find(query).map do |result|
47
+ # Sanitise the data if required
48
+ params = sanitise_data(type, result)
51
49
 
52
- standard = sanitise_standard(find_standard(name))
53
- return nil if standard.nil?
50
+ # Create the object and return in an array
51
+ object = class_for(type).new(params)
54
52
 
55
- find_children!(standard) if opts[:recurse]
56
- end
57
-
58
- standard
59
- end
53
+ # Find children if required
54
+ find_children!(object) if opts[:recurse]
60
55
 
61
- def find_sections(query)
62
- sections_db.find(
63
- query
64
- ).map do |section|
65
- Kriterion::Section.new(section)
56
+ object
66
57
  end
67
58
  end
68
59
 
69
- def add_standard(standard)
70
- insert_into_db(standards_db, standard)
71
- end
72
-
73
- def add_section(section)
74
- insert_into_db(sections_db, section)
75
- end
76
-
77
- def add_item(item)
78
- insert_into_db(items_db, item)
79
- end
80
-
81
- def add_resource(resource)
82
- insert_into_db(resources_db, resource)
83
- end
84
-
85
- def add_event(event)
86
- insert_into_db(events_db, event)
60
+ def insert(object)
61
+ insert_into_db(database_for(object), object)
87
62
  end
88
63
 
89
64
  def add_unchanged_node(resource, certname)
@@ -96,14 +71,7 @@ class Kriterion
96
71
  end
97
72
 
98
73
  def update_compliance!(thing)
99
- databases = {
100
- Kriterion::Standard => standards_db,
101
- Kriterion::Section => sections_db,
102
- Kriterion::Item => items_db,
103
- Kriterion::Resource => resources_db
104
- }
105
-
106
- databases[thing.class].update_one(
74
+ database_for(thing).update_one(
107
75
  { uuid: thing.uuid },
108
76
  '$set' => {
109
77
  compliance: thing.compliance
@@ -128,6 +96,42 @@ class Kriterion
128
96
 
129
97
  private
130
98
 
99
+ def sanitise_data(type, data)
100
+ if type == :standard
101
+ return nil if data.nil?
102
+ # Compile the regex from a lazy-compiled BSON regex back to a ruby one
103
+ data['item_syntax'] = data['item_syntax'].compile
104
+ end
105
+ data
106
+ end
107
+
108
+ # Returns the database for a given object type
109
+ def database_for(object)
110
+ cls = class_for(object)
111
+ databases = {
112
+ Kriterion::Standard => @standards_db,
113
+ Kriterion::Section => @sections_db,
114
+ Kriterion::Item => @items_db,
115
+ Kriterion::Resource => @resources_db,
116
+ Kriterion::Event => @events_db
117
+ }
118
+ databases[cls]
119
+ end
120
+
121
+ def class_for(name)
122
+ classes = {
123
+ 'standard' => Kriterion::Standard,
124
+ 'section' => Kriterion::Section,
125
+ 'item' => Kriterion::Item,
126
+ 'resource' => Kriterion::Resource,
127
+ 'event' => Kriterion::Event
128
+ }
129
+ # If someone has passed in an object, just return the class
130
+ return name.class if classes.value? name.class
131
+
132
+ classes[name.to_s]
133
+ end
134
+
131
135
  def find_children!(object)
132
136
  accepted_objects = [
133
137
  Kriterion::Standard,
@@ -209,27 +213,6 @@ class Kriterion
209
213
  Kriterion::Section.new(section)
210
214
  end
211
215
  end
212
-
213
- def find_standard(name)
214
- result = standards_db.find(name: name)
215
- count = result.count
216
- case count
217
- when 0
218
- nil
219
- when 1
220
- result.first
221
- else
222
- raise "Found > 1 standards with name: #{name}"
223
- end
224
- end
225
-
226
- # Takes a result and sanities it to Kriterion::Standard object
227
- def sanitise_standard(result)
228
- return nil if result.nil?
229
- # Compile the regex from a lazy-compiled BSON regex back to a ruby one
230
- result['item_syntax'] = result['item_syntax'].compile
231
- Kriterion::Standard.new(result)
232
- end
233
216
  end
234
217
  end
235
218
  end
@@ -26,3 +26,4 @@ class Kriterion
26
26
  end
27
27
 
28
28
  require 'kriterion/cli/worker'
29
+ require 'kriterion/cli/api'
@@ -14,17 +14,21 @@ class Kriterion
14
14
  exit 0
15
15
  end
16
16
 
17
- option :u, :standards_dir, 'URI of the RestMQ server', argument: :required
18
- optional :h, :mongo_hostname, 'Hostname of the MongoDB server to use', default: 'localhost'
19
- optional :d, :mongo_database, 'Name of the MongoDB database to use', default: 'kriterion'
20
- optional :p, :mongo_port, 'Port for MongoDB', default: 27017
17
+ optional :u, :uri , 'URI of the RestMQ server' , default: ENV['uri'] || 'http://localhost:8888'
18
+ optional :q, :queue , 'Queue to subscribe to' , default: ENV['queue']|| 'reports'
19
+ optional :h, :mongo_hostname, 'Hostname of the MongoDB server to use', default: ENV['mongo_hostname']|| 'localhost'
20
+ optional :d, :mongo_database, 'Name of the MongoDB database to use' , default: ENV['mongo_database']|| 'kriterion'
21
+ optional :p, :mongo_port , 'Port for MongoDB' , default: ENV['mongo_port']|| 27017
21
22
 
22
23
 
23
24
  run do |opts, args, cmd|
24
25
  # TODO: Get log levels working properly
25
26
  require 'kriterion/api'
26
- worker = Kriterion::API.new(opts)
27
- worker.run
27
+
28
+ # Create a new worker withe the options we want, run! will detect
29
+ # this
30
+ Kriterion::API.new(opts)
31
+ Kriterion::API.run!
28
32
  end
29
33
  end
30
34
  end
@@ -0,0 +1,42 @@
1
+ require 'kriterion/logs'
2
+ require 'kriterion/metrics'
3
+ require 'kriterion/backend'
4
+ require 'kriterion/backend/mongodb'
5
+
6
+ class Kriterion
7
+ module Connector
8
+ def self.connections(opts = {})
9
+ logger.level = if opts[:debug]
10
+ Kriterion::Logs::DEBUG
11
+ else
12
+ Kriterion::Logs::INFO
13
+ end
14
+
15
+ # Set up connections
16
+ uri = opts[:uri]
17
+ queue = opts[:queue]
18
+ queue_uri = URI("#{uri}/q/#{queue}")
19
+ metrics = Kriterion::Metrics.new
20
+
21
+ # Set up the backend
22
+ # TODO: Clean this up and make fully dynamic
23
+ backend_name = opts[:backend] || 'mongodb'
24
+ case backend_name
25
+ when 'mongodb'
26
+ require 'kriterion/backend/mongodb'
27
+ Kriterion::Backend.set(
28
+ Kriterion::Backend::MongoDB.new(
29
+ hostname: opts[:mongo_hostname],
30
+ port: opts[:mongo_port],
31
+ database: opts[:mongo_database],
32
+ metrics: metrics
33
+ )
34
+ )
35
+ end
36
+
37
+ backend = Kriterion::Backend.get
38
+
39
+ [queue_uri, metrics, backend]
40
+ end
41
+ end
42
+ end
@@ -31,6 +31,23 @@ class Kriterion
31
31
  @corrective_change = data['corrective_change']
32
32
  @certname = data['certname']
33
33
  @resource = data['resource']
34
+ @full_description = "#{@certname}/#{@resource}/#{@property}: #{@message}"
35
+ end
36
+
37
+ def full_description
38
+ # We want to update this when it is called to ensure it is up to date.
39
+ # This could just be amethod instead of an instance variable but that
40
+ # would mean that it wouldn't get stored in the database, which we want.
41
+ @full_description = "#{@certname}/#{@resource}/#{@property}: #{@message}"
42
+ end
43
+
44
+ # Resources don't have compliance so we don't want this to do anything
45
+ def compliance
46
+ nil
47
+ end
48
+
49
+ def primary_key
50
+ :full_description
34
51
  end
35
52
  end
36
53
  end
@@ -13,6 +13,8 @@ class Kriterion
13
13
  attr_accessor :resources
14
14
 
15
15
  def initialize(data)
16
+ super(data)
17
+
16
18
  @uuid = data['uuid'] || SecureRandom.uuid
17
19
  @id = data['id']
18
20
  @title = data['title']
@@ -38,5 +40,17 @@ class Kriterion
38
40
  parents.delete(id)
39
41
  parents.reverse
40
42
  end
43
+
44
+ def expandable?
45
+ true
46
+ end
47
+
48
+ def expandable_keys
49
+ [:resources]
50
+ end
51
+
52
+ def self.primary_key
53
+ :id
54
+ end
41
55
  end
42
56
  end
@@ -18,5 +18,9 @@ class Kriterion
18
18
  logger.info " #{name} #{value.round(2)}s"
19
19
  end
20
20
  end
21
+
22
+ def reset!
23
+ @metrics = {}
24
+ end
21
25
  end
22
26
  end
@@ -1,32 +1,60 @@
1
1
  class Kriterion
2
2
  class Object
3
+ def initialize(data)
4
+ @compliance = data['compliance']
5
+ end
6
+
3
7
  def to_h(mode = :basic)
4
8
  raise 'Mode must be :basic or :full' unless %i[basic full].include? mode
5
9
  hash = {}
6
10
 
11
+ # Add all instance variables to the hash without the @ sign
7
12
  instance_variables.each do |v|
8
13
  hash[v.to_s.gsub(/^@/, '')] = instance_variable_get(v.to_s)
9
14
  end
10
15
 
11
16
  if mode == :basic
12
17
  hash.reject do |k, _v|
13
- %w[
14
- sections
15
- items
16
- resources
17
- events
18
- ].include? k
18
+ full_keys.include? k
19
19
  end
20
20
  elsif mode == :full
21
+ expandable_keys.each do |key|
22
+ hash[key.to_s] = send(key).map { |x| x.to_h(:full) }
23
+ end
21
24
  hash
22
25
  end
23
26
  end
24
27
 
28
+ def full_keys
29
+ %w[
30
+ sections
31
+ items
32
+ resources
33
+ events
34
+ ]
35
+ end
36
+
37
+ # Objects should deflault to not being expandable unless someone has
38
+ # specifided it
39
+ def expandable?
40
+ false
41
+ end
42
+
43
+ def expandable_keys
44
+ []
45
+ end
46
+
25
47
  def find_section(name)
26
48
  sections ? sections.select { |s| s.name == name }[0] : nil
27
49
  end
28
50
 
51
+ # Returns the cahced complicance value or calculates from scratch if
52
+ # required
29
53
  def compliance(objects)
54
+ # Returns cached value if it exists
55
+ return @compliance if @compliance
56
+
57
+ # Calculate compliance
30
58
  total = objects.count
31
59
  compliant = objects.count { |o| o.compliance['compliant'] }
32
60
  non_compliant = total - compliant
@@ -46,5 +74,26 @@ class Kriterion
46
74
  }
47
75
  }
48
76
  end
77
+
78
+ def flush_compliance!
79
+ @compliance = nil
80
+ # Flush the compliance of all children also
81
+ expandable_keys.each do |key|
82
+ send(key).each do |thing|
83
+ thing.flush_compliance!
84
+ yield(thing) if block_given?
85
+ end
86
+ end
87
+ yield(self) if block_given?
88
+ compliance
89
+ end
90
+
91
+ def primary_key
92
+ self.class.primary_key
93
+ end
94
+
95
+ def self.primary_key
96
+ :name
97
+ end
49
98
  end
50
99
  end
@@ -30,6 +30,15 @@ class Kriterion
30
30
  @events = hash['events'] || []
31
31
  @parent_uuid = hash['parent_uuid']
32
32
  @unchanged_nodes = hash['unchanged_nodes'] || []
33
+ @compliance = hash['compliance']
34
+ end
35
+
36
+ def expandable?
37
+ true
38
+ end
39
+
40
+ def expandable_keys
41
+ [:events]
33
42
  end
34
43
 
35
44
  def ==(other)
@@ -37,6 +46,9 @@ class Kriterion
37
46
  end
38
47
 
39
48
  def compliance
49
+ # Returns cached value if it exists
50
+ return @compliance if @compliance
51
+
40
52
  compliant = unchanged_nodes.count
41
53
  non_compliant = events.group_by(&:certname).count
42
54
  total = compliant + non_compliant
@@ -56,5 +68,9 @@ class Kriterion
56
68
  }
57
69
  }
58
70
  end
71
+
72
+ def self.primary_key
73
+ :title
74
+ end
59
75
  end
60
76
  end
@@ -11,6 +11,8 @@ class Kriterion
11
11
  attr_accessor :sections
12
12
 
13
13
  def initialize(data)
14
+ super(data)
15
+
14
16
  @uuid = data['uuid'] || SecureRandom.uuid
15
17
  @name = data['name']
16
18
  @standard = data['standard']
@@ -21,6 +23,17 @@ class Kriterion
21
23
  @parent_uuid = data['parent_uuid']
22
24
  end
23
25
 
26
+ def expandable?
27
+ true
28
+ end
29
+
30
+ def expandable_keys
31
+ %i[
32
+ sections
33
+ items
34
+ ]
35
+ end
36
+
24
37
  def type
25
38
  :section
26
39
  end
@@ -17,6 +17,7 @@ class Kriterion
17
17
  attr_accessor :items
18
18
 
19
19
  def initialize(data)
20
+ super(data)
20
21
  @uuid = data['uuid'] || SecureRandom.uuid
21
22
  @name = data['name']
22
23
  @date = data['date']
@@ -54,6 +55,17 @@ class Kriterion
54
55
  @@standards = backend.standards
55
56
  end
56
57
 
58
+ def expandable?
59
+ true
60
+ end
61
+
62
+ def expandable_keys
63
+ %i[
64
+ sections
65
+ items
66
+ ]
67
+ end
68
+
57
69
  def type
58
70
  :standard
59
71
  end
@@ -1,3 +1,3 @@
1
1
  class Kriterion
2
- VERSION = "0.0.1"
2
+ VERSION = '0.1.0'
3
3
  end
@@ -1,67 +1,37 @@
1
1
  require 'json'
2
- require 'logger'
3
2
  require 'net/http'
4
3
  require 'benchmark'
5
4
  require 'kriterion'
6
- require 'kriterion/logs'
7
5
  require 'kriterion/item'
8
6
  require 'kriterion/report'
9
7
  require 'kriterion/metrics'
10
8
  require 'kriterion/section'
11
9
  require 'kriterion/standard'
12
-
13
- require 'pry'
10
+ require 'kriterion/connector'
14
11
 
15
12
  class Kriterion
16
13
  class Worker
17
14
  include Kriterion::Logs
15
+ include Kriterion::Connector
18
16
 
19
- attr_reader :uri
20
- attr_reader :queue
21
17
  attr_reader :queue_uri
22
18
  attr_reader :standards
23
19
  attr_reader :backend
24
20
  attr_reader :metrics
25
21
 
26
22
  def initialize(opts = {})
27
- logger.level = if opts[:debug]
28
- Kriterion::Logs::DEBUG
29
- else
30
- Kriterion::Logs::INFO
31
- end
32
-
33
- # Set up connections
34
- @uri = opts[:uri]
35
- @queue = opts[:queue]
36
- @queue_uri = URI("#{@uri}/q/#{@queue}")
37
- @metrics = Kriterion::Metrics.new
38
-
39
- # Set up the backend
40
- # TODO: Clean this up and make fully dynamic
41
- backend_name = opts[:backend] || 'mongodb'
42
- case backend_name
43
- when 'mongodb'
44
- require 'kriterion/backend/mongodb'
45
- Kriterion::Backend.set(
46
- Kriterion::Backend::MongoDB.new(
47
- hostname: opts[:mongo_hostname],
48
- port: opts[:mongo_port],
49
- database: opts[:mongo_database],
50
- metrics: metrics
51
- )
52
- )
53
- end
54
-
55
- @backend = Kriterion::Backend.get
23
+ @queue_uri, @metrics, @backend = Kriterion::Connector.connections(opts)
56
24
 
57
- # TODO: Work out how workers are going to get the list of standards frmo the API runner
25
+ # TODO: Work out how workers are going to get the list of standards frmo
26
+ # the API runner
58
27
  # TODO: Remove placeholder code
59
28
  standards_dir = File.expand_path('standards', Kriterion::ROOT)
60
29
  @standards = Kriterion.standards([standards_dir])
30
+ logger.info "Initialised Kritioner worker version #{Kriterion::VERSION}"
61
31
  end
62
32
 
63
33
  def process_report(report)
64
- report = Kriterion::Report.new(report)
34
+ report = Kriterion::Report.new(report)
65
35
 
66
36
  # Check if the report contains any relevant resources
67
37
  standard_names = standards.keys
@@ -85,16 +55,19 @@ class Kriterion
85
55
  end
86
56
 
87
57
  affected_standards.each do |name, resources|
88
- standard = backend.get_standard(name, recurse: true)
58
+ standard = backend.find_standard({ name: name }, recurse: true)
89
59
  unless standard
90
60
  # If the standard doesn't yet exist in the backed, add it
91
61
  standard = Kriterion::Standard.new(@standards[name])
92
62
  logger.debug "Adding starndard #{standard.name} to backend"
93
- backend.add_standard(standard)
63
+ backend.ensure_standard(standard)
94
64
  # TODO: See if there is a better way to deal with this, the reason I'm
95
65
  # doing this is that I want to make sure that there is not difference
96
66
  # between a newly created object and one that came from the database
97
- standard = backend.get_standard(name, recurse: true)
67
+ standard = backend.find_standard(
68
+ { name: name },
69
+ recurse: true
70
+ )
98
71
  end
99
72
 
100
73
  resources.each do |resource|
@@ -124,21 +97,10 @@ class Kriterion
124
97
  if previous.find_section(current)
125
98
  previous.find_section(current)
126
99
  else
127
- # This is a new section that does not yet exist in the database,
128
- # we therefore need to get the details and all them all in
129
- current_section_name = if previous.is_a? Kriterion::Standard
130
- current
131
- elsif previous.is_a? Kriterion::Section
132
- [
133
- previous.name,
134
- current
135
- ].join(standard.section_separator)
136
- end
137
-
138
100
  # Get the details from the standards database (name, description
139
101
  # etc.)
140
102
  current_section = @standards[name]['sections'].select do |s|
141
- s['name'] == current_section_name
103
+ s['name'] == current
142
104
  end[0]
143
105
 
144
106
  if current_section.nil?
@@ -151,7 +113,7 @@ class Kriterion
151
113
  current_section = Kriterion::Section.new(current_section)
152
114
 
153
115
  # Add the section to the backend
154
- backend.add_section(current_section)
116
+ backend.ensure_section(current_section)
155
117
  current_section
156
118
  end
157
119
  end
@@ -172,7 +134,7 @@ class Kriterion
172
134
  item_details['parent_uuid'] = section.uuid
173
135
  item_details['parent_type'] = section.type
174
136
  item_details['section_path'] = captures
175
- backend.add_item(Kriterion::Item.new(item_details))
137
+ backend.ensure_item(Kriterion::Item.new(item_details))
176
138
  else
177
139
  raise "Found muliple sections with the id #{section_tag}"
178
140
  end
@@ -183,7 +145,7 @@ class Kriterion
183
145
  # Add the new resource to the backend if it doesn't exist
184
146
  unless item.resources.include? resource
185
147
  item.resources << resource
186
- backend.add_resource(resource)
148
+ backend.ensure_resource(resource)
187
149
  end
188
150
 
189
151
  # Inform the database that this node is unchanged if we have no events
@@ -196,38 +158,24 @@ class Kriterion
196
158
  event = Kriterion::Event.new(event)
197
159
  event.certname = report.certname
198
160
  event.resource = resource.resource
199
- backend.add_event(event)
161
+ backend.ensure_event(event)
200
162
  event
201
163
  end
202
-
203
- metrics[:update_compliance] += Benchmark.realtime do
204
- # Finally update the compliance details for this resource and its
205
- # parent item
206
- backend.update_compliance! resource
207
- backend.update_compliance! item
208
-
209
- # Find all of the parent sections and update the compliance on them
210
- # Don't recalculate the compliance of the standard yet, wait until
211
- # the end.
212
- item.parent_names(standard.section_separator).each do |parent|
213
- # TODO: Complete this so that it updates the compliance of
214
- # everything. It's probably better if we re-query this stuff from
215
- # the database to reduce the chances of race conditions
216
- result = backend.find_sections(
217
- name: parent,
218
- standard: standard.name
219
- )
220
- result.each { |r| backend.update_compliance! r }
221
- end
222
- end
223
164
  end
224
165
 
225
166
  # Reload the standard as new sections may have been added
226
- standard = backend.get_standard(name, recurse: true)
167
+ standard = backend.find_standard(
168
+ { name: name },
169
+ recurse: true
170
+ )
227
171
 
228
172
  metrics[:update_compliance] += Benchmark.realtime do
229
- # Recalculate the compliance of a given standard once it is done
230
- backend.update_compliance! standard
173
+ # Recalculate the compliance of a given standard once it is done (This
174
+ # also calculates the compliacne of all children and yeilds them to
175
+ # block)
176
+ standard.flush_compliance! do |child|
177
+ backend.update_compliance! child
178
+ end
231
179
  end
232
180
  end
233
181
  end
@@ -254,6 +202,7 @@ class Kriterion
254
202
  end
255
203
 
256
204
  metrics.print
205
+ metrics.reset!
257
206
  end
258
207
  rescue Timeout::Error, Errno::EINVAL, Errno::ECONNRESET, EOFError,
259
208
  Net::HTTPBadResponse, Net::HTTPHeaderSyntaxError,
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kriterion
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dylan Ratcliffe
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2018-07-27 00:00:00.000000000 Z
11
+ date: 2018-08-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: cri
@@ -52,6 +52,20 @@ dependencies:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
54
  version: '2.5'
55
+ - !ruby/object:Gem::Dependency
56
+ name: sinatra
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '2.0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '2.0'
55
69
  - !ruby/object:Gem::Dependency
56
70
  name: bundler
57
71
  requirement: !ruby/object:Gem::Requirement
@@ -103,6 +117,7 @@ extensions: []
103
117
  extra_rdoc_files: []
104
118
  files:
105
119
  - ".gitignore"
120
+ - ".rubocop.yml"
106
121
  - ".ruby-version"
107
122
  - ".travis.yml"
108
123
  - Dockerfile
@@ -111,8 +126,11 @@ files:
111
126
  - LICENSE.txt
112
127
  - README.md
113
128
  - Rakefile
129
+ - bin/build.sh
130
+ - bin/kill_pods_in_deployment.rb
114
131
  - bin/setup
115
132
  - bin/update_stigs.rb
133
+ - config.ru
116
134
  - criterion.gemspec
117
135
  - docker-compose.yml
118
136
  - exe/kriterion
@@ -123,6 +141,7 @@ files:
123
141
  - lib/kriterion/cli.rb
124
142
  - lib/kriterion/cli/api.rb
125
143
  - lib/kriterion/cli/worker.rb
144
+ - lib/kriterion/connector.rb
126
145
  - lib/kriterion/event.rb
127
146
  - lib/kriterion/item.rb
128
147
  - lib/kriterion/logs.rb