controls 1.0.0.beta.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/.yardopts +1 -0
- data/Gemfile +4 -0
- data/LICENSE.md +20 -0
- data/README.md +78 -0
- data/Rakefile +3 -0
- data/controls.gemspec +30 -0
- data/docs/images/controlsinsight+shield.png +0 -0
- data/docs/images/controlsinsight.png +0 -0
- data/lib/controls.rb +42 -0
- data/lib/controls/authentication.rb +46 -0
- data/lib/controls/client.rb +151 -0
- data/lib/controls/client/assessments.rb +17 -0
- data/lib/controls/client/assets.rb +64 -0
- data/lib/controls/client/configurations.rb +31 -0
- data/lib/controls/client/guidance.rb +46 -0
- data/lib/controls/client/prioritized_guidance.rb +34 -0
- data/lib/controls/client/security_controls.rb +36 -0
- data/lib/controls/client/threat_vectors.rb +29 -0
- data/lib/controls/client/threats.rb +26 -0
- data/lib/controls/configurable.rb +83 -0
- data/lib/controls/default.rb +108 -0
- data/lib/controls/error.rb +132 -0
- data/lib/controls/response.rb +76 -0
- data/lib/controls/response/raise_error.rb +21 -0
- data/lib/controls/version.rb +4 -0
- data/spec/controls_spec.rb +22 -0
- data/spec/helper.rb +42 -0
- metadata +187 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: fadb777b4fbaf3fdc6b52f08d20b285c8f1904fb
|
4
|
+
data.tar.gz: c7589578857e422ebc60be2dcd6ef9b9e42bcb6e
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: ee1c3c4c8a44dd97310f9616752ff416204055d21cf40984605be06534f9fdf853cca8a8374f606322372b408005dd06bbc4c71288cfece0e8667e06a6204b3d
|
7
|
+
data.tar.gz: 15212139246d997a70435bb8d7fd5c8cde611b4bab1c7b778a9971fd899dfb8cac37f1ee69637e9b5c07d2713a98d6eeec7d5b3ad52eba8a2b0f6c03ca087035
|
data/.gitignore
ADDED
data/.yardopts
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
- LICENSE.md
|
data/Gemfile
ADDED
data/LICENSE.md
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2013 Erran Carey
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
6
|
+
this software and associated documentation files (the "Software"), to deal in
|
7
|
+
the Software without restriction, including without limitation the rights to
|
8
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
9
|
+
the Software, and to permit persons to whom the Software is furnished to do so,
|
10
|
+
subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
17
|
+
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
18
|
+
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
19
|
+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
20
|
+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
# ![controls insight](https://raw.github.com/ipwnstuff/controls.rb/master/docs/images/controlsinsight.png "controlsinsight") client gem
|
2
|
+
The **controls**insight (controls) gem interfaces with [Rapid7's **controls**insight API](http://docs.controlsinsight.apiary.io).
|
3
|
+
|
4
|
+
## Installation
|
5
|
+
Add this line to your application's Gemfile:
|
6
|
+
|
7
|
+
gem 'controls'
|
8
|
+
|
9
|
+
And then execute:
|
10
|
+
|
11
|
+
$ bundle
|
12
|
+
|
13
|
+
Or install it yourself as:
|
14
|
+
|
15
|
+
$ gem install controls
|
16
|
+
|
17
|
+
## Documentation
|
18
|
+
* [Apiary API documentation](http://docs.controlsinsight.apiary.io)
|
19
|
+
* [YARD documentation for the Ruby client](http://www.rubydoc.info/github/ipwnstuff/controls.rb)
|
20
|
+
|
21
|
+
## Basic Resources
|
22
|
+
### Authentication
|
23
|
+
```ruby
|
24
|
+
Controls.web_endpoint = 'https://nexpose.local:3780/insight/controls'
|
25
|
+
Controls.api_endpoint = "#{Controls.web_endpoint}/api/1.0"
|
26
|
+
|
27
|
+
# If your endpoint uses a self-signed cert. turn off SSL cert. verification
|
28
|
+
Controls.verify_ssl = false
|
29
|
+
|
30
|
+
Controls.login :user => 'admin', :password => 'password'
|
31
|
+
|
32
|
+
Controls.client.methods
|
33
|
+
```
|
34
|
+
|
35
|
+
### Assessments
|
36
|
+
```ruby
|
37
|
+
# Retrieve all the assessments that have been ran
|
38
|
+
Controls.assessments
|
39
|
+
# => TODO: Add example output
|
40
|
+
|
41
|
+
# Only retrieve a single assessment
|
42
|
+
Controls.assessments(1)
|
43
|
+
# => TODO: Add example output
|
44
|
+
```
|
45
|
+
|
46
|
+
|
47
|
+
### Assets
|
48
|
+
```ruby
|
49
|
+
# Retrieve a list of all the assets that Controls has access to
|
50
|
+
Controls.assets
|
51
|
+
# => TODO: Add example output
|
52
|
+
|
53
|
+
# Only retrieve a single assessment
|
54
|
+
Controls.assets('your-asset-uuid-here')
|
55
|
+
# => TODO: Add example output
|
56
|
+
```
|
57
|
+
|
58
|
+
### Guidance
|
59
|
+
```ruby
|
60
|
+
# Only retrieve a single guidance by name
|
61
|
+
Controls.guidance('your-guidance-name-here')
|
62
|
+
# => TODO: Add example output
|
63
|
+
```
|
64
|
+
|
65
|
+
### Threats
|
66
|
+
```ruby
|
67
|
+
# Retrieve a list of all the threats
|
68
|
+
Controls.threats
|
69
|
+
# => TODO: Add example output
|
70
|
+
|
71
|
+
# Only retrieve a single threat
|
72
|
+
Controls.threats('threat-name-here')
|
73
|
+
# => TODO: Add example output
|
74
|
+
```
|
75
|
+
|
76
|
+
|
77
|
+
## License
|
78
|
+
This project was created by [Erran Carey (@ipwnstuff)](http://ipwnstuff.github.io) and licensed under [the MIT License](LICENSE.md).
|
data/Rakefile
ADDED
data/controls.gemspec
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'controls/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'controls'
|
8
|
+
spec.version = Controls::VERSION
|
9
|
+
spec.authors = ['Erran Carey']
|
10
|
+
spec.email = %w['me@errancarey.com']
|
11
|
+
spec.description = %q{This gem interfaces to Rapid7's **controls**insight API.}
|
12
|
+
spec.summary = %q{This gem interfaces to Rapid7's **controls**insight API.}
|
13
|
+
spec.homepage = ''
|
14
|
+
spec.license = 'MIT'
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = %w[lib]
|
20
|
+
|
21
|
+
spec.add_dependency 'activesupport'
|
22
|
+
spec.add_dependency 'faraday'
|
23
|
+
spec.add_dependency 'nokogiri'
|
24
|
+
|
25
|
+
spec.add_development_dependency 'bundler', '~> 1.3'
|
26
|
+
spec.add_development_dependency 'netrc'
|
27
|
+
spec.add_development_dependency 'rake'
|
28
|
+
spec.add_development_dependency 'vcr'
|
29
|
+
spec.add_development_dependency 'yard'
|
30
|
+
end
|
Binary file
|
Binary file
|
data/lib/controls.rb
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'controls/client'
|
2
|
+
require 'controls/default'
|
3
|
+
|
4
|
+
# A Ruby client for the **controls**insight API
|
5
|
+
module Controls
|
6
|
+
class << self
|
7
|
+
include Controls::Configurable
|
8
|
+
|
9
|
+
# A {Client} object that includes {Configurable}
|
10
|
+
#
|
11
|
+
# @return [Client] the current {Client} object or a newly initialized
|
12
|
+
# {Client} object
|
13
|
+
def client
|
14
|
+
unless defined?(@client) && @client.same_options?(options)
|
15
|
+
@client = Controls::Client.new(options)
|
16
|
+
end
|
17
|
+
|
18
|
+
@client
|
19
|
+
end
|
20
|
+
|
21
|
+
# @yield [client]
|
22
|
+
def configure
|
23
|
+
yield client
|
24
|
+
end
|
25
|
+
|
26
|
+
def respond_to_missing?(method_name, include_private = false)
|
27
|
+
client.respond_to?(method_name, include_private)
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def method_missing(method_name, *args, &block)
|
33
|
+
if client.respond_to?(method_name)
|
34
|
+
client.send(method_name, *args, &block)
|
35
|
+
else
|
36
|
+
super
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
Controls.setup
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module Controls
|
2
|
+
# A module that holds authentication methods for {Controls::Client}
|
3
|
+
module Authentication
|
4
|
+
# @return [Boolean] whether the user is authenticated using Basic Auth
|
5
|
+
def basic_authenticated?
|
6
|
+
@username && @password
|
7
|
+
end
|
8
|
+
|
9
|
+
# @note the following aliases should become real methods once new methods
|
10
|
+
# of authentication become available such as token/OAuth authentication
|
11
|
+
alias_method :authenticated?, :basic_authenticated?
|
12
|
+
alias_method :user_authenticated?, :basic_authenticated?
|
13
|
+
|
14
|
+
# @note this method should be updated if new methods for authentication
|
15
|
+
# become available.
|
16
|
+
def login(username, password)
|
17
|
+
middleware.basic_auth(username, password)
|
18
|
+
end
|
19
|
+
|
20
|
+
# @return [Boolean] whether the netrc file was successfully loaded, otherwise
|
21
|
+
# returns false and prints an error to STDERR
|
22
|
+
def login_from_netrc
|
23
|
+
return false unless netrc?
|
24
|
+
|
25
|
+
require 'netrc'
|
26
|
+
host = URI.parse(api_endpoint).host
|
27
|
+
creds = Netrc.read(File.expand_path(netrc_file))[host]
|
28
|
+
|
29
|
+
if creds
|
30
|
+
self.username = creds.shift
|
31
|
+
self.password = creds.shift
|
32
|
+
|
33
|
+
middleware.basic_auth(username, password)
|
34
|
+
else
|
35
|
+
warn "No credentials found for '#{host}' in '#{netrc_file}'."
|
36
|
+
|
37
|
+
false
|
38
|
+
end
|
39
|
+
rescue LoadError
|
40
|
+
warn 'You must install the netrc gem to login via netrc.',
|
41
|
+
'Retry after running `gem install netrc`.'
|
42
|
+
|
43
|
+
false
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,151 @@
|
|
1
|
+
require 'faraday'
|
2
|
+
require 'json'
|
3
|
+
require 'nokogiri'
|
4
|
+
require 'controls/authentication'
|
5
|
+
require 'controls/configurable'
|
6
|
+
require 'controls/client/assessments'
|
7
|
+
require 'controls/client/assets'
|
8
|
+
require 'controls/client/guidance'
|
9
|
+
require 'controls/client/security_controls'
|
10
|
+
require 'controls/client/threats'
|
11
|
+
require 'controls/response'
|
12
|
+
|
13
|
+
module Controls
|
14
|
+
# A class that handles interactions with the **controls**insight API
|
15
|
+
class Client
|
16
|
+
include Controls::Authentication
|
17
|
+
include Controls::Configurable
|
18
|
+
include Controls::Client::Assessments
|
19
|
+
include Controls::Client::Assets
|
20
|
+
include Controls::Client::Guidance
|
21
|
+
include Controls::Client::SecurityControls
|
22
|
+
include Controls::Client::Threats
|
23
|
+
|
24
|
+
SSL_WARNING = ["The API endpoint used a self-signed or invalid SSL certificate.",
|
25
|
+
"To allow this connection temporarily use `Controls.verify_ssl = false`.",
|
26
|
+
"See the Controls.rb wiki on GitHub for more information on SSL verification."]
|
27
|
+
|
28
|
+
# Creates a new {Controls::Client} object
|
29
|
+
#
|
30
|
+
# @param [Hash] options the options to use when adding keys from
|
31
|
+
# {Controls::Configurable}
|
32
|
+
def initialize(options = {})
|
33
|
+
Controls::Configurable.keys.each do |key|
|
34
|
+
value = options[key].nil? ? Controls.instance_variable_get(:"@#{key}") : options[key]
|
35
|
+
instance_variable_set(:"@#{key}", value)
|
36
|
+
end
|
37
|
+
|
38
|
+
if options[:verify_ssl].nil?
|
39
|
+
middleware.ssl[:verify] = if ENV['CONTROLS_VERIFY_SSL'].nil?
|
40
|
+
true
|
41
|
+
else
|
42
|
+
!(ENV['CONTROLS_VERIFY_SSL'] =~ /false/)
|
43
|
+
end
|
44
|
+
else
|
45
|
+
middleware.ssl[:verify] = !!options[:verify_ssl]
|
46
|
+
end
|
47
|
+
|
48
|
+
login_from_netrc unless authenticated?
|
49
|
+
|
50
|
+
if basic_authenticated?
|
51
|
+
middleware.basic_auth(@username, @password)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def verify_ssl
|
56
|
+
middleware.ssl[:verify].nil? || !!middleware.ssl[:verify]
|
57
|
+
end
|
58
|
+
|
59
|
+
def verify_ssl=(verify)
|
60
|
+
middleware.ssl[:verify] = !!verify
|
61
|
+
end
|
62
|
+
|
63
|
+
# Censors the password from the output of {#inspect}
|
64
|
+
#
|
65
|
+
# @return [String] the censored data
|
66
|
+
def inspect
|
67
|
+
raw = super
|
68
|
+
raw.sub!(/(@password=")#{@password}(")/, "\\1*********\\2") if @password
|
69
|
+
|
70
|
+
raw
|
71
|
+
end
|
72
|
+
|
73
|
+
# A wrapper for GET requests
|
74
|
+
#
|
75
|
+
# @return [Array,Hash] an array or hash of parsed JSON data
|
76
|
+
def get(path, params = {}, headers = {})
|
77
|
+
headers = connection_options[:headers].merge(headers)
|
78
|
+
url = URI.escape(File.join(api_endpoint, path))
|
79
|
+
resp = middleware.get(url, params, headers)
|
80
|
+
|
81
|
+
Response.generate_ruby(resp.body)
|
82
|
+
rescue Faraday::Error::ConnectionFailed => e
|
83
|
+
if e.message =~ /^SSL_connect/
|
84
|
+
warn(*SSL_WARNING)
|
85
|
+
else
|
86
|
+
raise e
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
|
91
|
+
# A wrapper for GET requests
|
92
|
+
#
|
93
|
+
# @return [Array,Hash] an array or hash of parsed JSON data
|
94
|
+
def web_get(path, params = {}, headers = {})
|
95
|
+
headers = connection_options[:headers].merge(headers)
|
96
|
+
url = URI.escape(File.join(web_endpoint, path))
|
97
|
+
resp = middleware.get(url, params, headers)
|
98
|
+
|
99
|
+
# Response.parse(resp.body)
|
100
|
+
Response.generate_ruby(resp.body)
|
101
|
+
rescue Faraday::Error::ConnectionFailed => e
|
102
|
+
if e.message =~ /^SSL_connect/
|
103
|
+
warn(*SSL_WARNING)
|
104
|
+
else
|
105
|
+
raise e
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def api_methods
|
110
|
+
mods = Controls::Client.included_modules.map do |mod|
|
111
|
+
if mod.to_s =~ /^Controls::Client::/
|
112
|
+
mod
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
mods.compact.map { |mod| mod.instance_methods(false) }.flatten.sort
|
117
|
+
end
|
118
|
+
|
119
|
+
def references(version = '1.0')
|
120
|
+
version = '1.0' unless version =~ /\d.\d/
|
121
|
+
|
122
|
+
web_get "/api/#{version}"
|
123
|
+
|
124
|
+
@references = Hash[Response.generate_ruby(resp.body).sort]
|
125
|
+
rescue Faraday::Error::ConnectionFailed => e
|
126
|
+
if e.message =~ /^SSL_connect/
|
127
|
+
warn(*SSL_WARNING)
|
128
|
+
else
|
129
|
+
raise e
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
def same_options?(opts)
|
134
|
+
opts.hash.eql? options.hash
|
135
|
+
end
|
136
|
+
|
137
|
+
%w[assessment asset configuration
|
138
|
+
guidance security_control threat
|
139
|
+
threat_vector].each do |predicate_method|
|
140
|
+
pluralized_method = predicate_method.eql?('guidance') ? 'guidance' : "#{predicate_method}s"
|
141
|
+
|
142
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
143
|
+
def #{predicate_method}?(*args) # def assessment?(*args)
|
144
|
+
send(:#{pluralized_method}, *args) # assessments(*args)
|
145
|
+
rescue Controls::NotFound => e # rescue Controls::NotFound => e
|
146
|
+
false # false
|
147
|
+
end # end
|
148
|
+
RUBY
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Controls
|
2
|
+
class Client
|
3
|
+
# A module to encapsulate API methods related to assessments
|
4
|
+
# @since API v1.0
|
5
|
+
# @version v1.0.0
|
6
|
+
module Assessments
|
7
|
+
# @return [Array<Hash>] an array of assessment hashes
|
8
|
+
def assessments(assessment_id = nil)
|
9
|
+
if assessment_id
|
10
|
+
get "/assessments/#{assessment_id}"
|
11
|
+
else
|
12
|
+
get '/assessments'
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
module Controls
|
2
|
+
class Client
|
3
|
+
# A module to encapsulate API methods related to assets
|
4
|
+
# @since API v1.0
|
5
|
+
# @version v1.0.0
|
6
|
+
# TODO: Update docs
|
7
|
+
module Assets
|
8
|
+
# @note since the uuid is an optional param it has been added to the
|
9
|
+
# params options hash
|
10
|
+
# @raise [Controls::NotFound] if the uuid didn't match any assets
|
11
|
+
# @return [Hash] a hash representing the matching asset
|
12
|
+
def assets(params = {})
|
13
|
+
if params.is_a? Hash
|
14
|
+
uuid = params.delete(:uuid)
|
15
|
+
else
|
16
|
+
uuid = params
|
17
|
+
params = {}
|
18
|
+
end
|
19
|
+
|
20
|
+
if uuid
|
21
|
+
get "/assets/#{uuid}", params
|
22
|
+
else
|
23
|
+
get '/assets', params
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# @param [String] guidance the guidance name to search by
|
28
|
+
# @return [Array<Hash>] an array of hashes that represent assets
|
29
|
+
def applicable_assets(guidance, params = {})
|
30
|
+
get "/guidance/#{guidance}/applicable_assets", params
|
31
|
+
end
|
32
|
+
alias_method :assets_by_guidance, :applicable_assets
|
33
|
+
|
34
|
+
# @param [String] configuration the name of the configuration to search by
|
35
|
+
# @return [Array<Hash>] an array of hashes that represent assets
|
36
|
+
def misconfigured_assets(configuration, params = {})
|
37
|
+
get "/configurations/#{configuration}/uncovered_assets", params
|
38
|
+
end
|
39
|
+
alias_method :assets_by_configuration, :misconfigured_assets
|
40
|
+
|
41
|
+
# @param [String] threat the threat name to search by
|
42
|
+
# @return [Array<Hash>] an array of hashes that represent assets
|
43
|
+
def threat_assets(threat, params = {})
|
44
|
+
get "/threats/#{threat}/assets", params
|
45
|
+
end
|
46
|
+
alias_method :assets_by_threat, :threat_assets
|
47
|
+
|
48
|
+
# @param [String] security_control the name of the security control to
|
49
|
+
# search by
|
50
|
+
# @return [Array<Hash>] an array of hashes that represent assets
|
51
|
+
def uncovered_assets(security_control, params = {})
|
52
|
+
get "/security_controls/#{security_control}/uncovered_assets", params
|
53
|
+
end
|
54
|
+
alias_method :assets_by_security_control, :uncovered_assets
|
55
|
+
|
56
|
+
# @param [String] threat_vector the threat vectory to search by
|
57
|
+
# @return [Array<Hash>] an array of hashes that represent assets
|
58
|
+
def undefended_assets(threat_vector, params = {})
|
59
|
+
get "/threat_vectors/#{threat_vector}/undefended_assets", params
|
60
|
+
end
|
61
|
+
alias_method :assets_by_threat_vector, :undefended_assets
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|