controls 1.0.0.beta.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,31 @@
1
+ module Controls
2
+ class Client
3
+ # A module to encapsulate API methods related to security controls and
4
+ # configurations
5
+ # @since API v1.0
6
+ # @version v1.0.0
7
+ module Configurations
8
+ # @!group Configuration Methods
9
+
10
+ # @param [String] configuration the name of the configuration to search
11
+ # for
12
+ # @return [Array<Hash>] a list of hashes representing configurations
13
+ def configurations(configuration = nil)
14
+ if configuration
15
+ get "/configurations/#{configuration}"
16
+ else
17
+ get "/configurations"
18
+ end
19
+ end
20
+
21
+ # @param [String] control the security control look up configurations for
22
+ # @return [Array<Hash>] a list of hashes representing configurations
23
+ def security_control_configurations(control)
24
+ get "/security_controls/#{control}/configurations"
25
+ end
26
+ alias_method :configurations_by_security_control, :security_control_configurations
27
+
28
+ # @!endgroup
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,46 @@
1
+ require 'controls/client/prioritized_guidance'
2
+
3
+ module Controls
4
+ class Client
5
+ # A module to encapsulate API methods related to guidance
6
+ # @since API v1.0
7
+ # @version v1.0.0
8
+ module Guidance
9
+ include PrioritizedGuidance
10
+
11
+ # @!group Guidance Methods
12
+
13
+ # @param [String] name the name of the guidance to search for
14
+ # @return [Hash] a hash representing the matching guidance
15
+ def guidance(name)
16
+ get "/guidance/#{name}"
17
+ end
18
+
19
+ # @param [String] configuration the configuration name to search by
20
+ # @return [Array<Hash>] an array of "guidance hashes"
21
+ def guidance_by_configuration(security_control, configuration)
22
+ get "/configurations/#{configuration}/guidance"
23
+ end
24
+
25
+ # @param [String] security_control the security control name to search by
26
+ # @return [Array<Hash>] an array of "guidance hashes"
27
+ def guidance_by_security_control(security_control)
28
+ get "/security_controls/#{security_control}/guidance"
29
+ end
30
+
31
+ # @param [String] threat the threat name to search by
32
+ # @return [Array<Hash>] an array of "guidance hashes"
33
+ def guidance_by_threat(threat)
34
+ get "/threats/#{threat}/guidance"
35
+ end
36
+
37
+ # @param [String] threat_vector the threat name to search by
38
+ # @return [Array<Hash>] an array of "guidance hashes"
39
+ def guidance_by_threat_vector(threat_vector)
40
+ get "/threat_vectors/#{threat_vector}/guidance"
41
+ end
42
+
43
+ # @!endgroup
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,34 @@
1
+ module Controls
2
+ class Client
3
+ # A module to encapsulate API methods related to guidance
4
+ # @since API v1.0
5
+ # @version v1.0.0
6
+ module PrioritizedGuidance
7
+ # @!group Prioritized Guidance Methods
8
+
9
+ # @param [String] security_control the security control name to search by
10
+ # @return [Array<Hash>] an array of "guidance hashes"
11
+ def prioritized_guidance_by_security_control(security_control)
12
+ get "/security_controls/#{security_control}/prioritized_guidance"
13
+ end
14
+
15
+ # @param [String] configuration the configuration name to search by
16
+ # @return [Array<Hash>] an array of "guidance hashes"
17
+ def prioritized_guidance_by_configuration(configuration)
18
+ get "/configurations/#{configuration}/prioritized_guidance"
19
+ end
20
+
21
+ # @param [String] threat the threat name to search by
22
+ # @return [Array<Hash>] an array of "guidance hashes"
23
+ def prioritized_guidance_by_threat(threat)
24
+ get "/threats/#{threat}/prioritized_guidance"
25
+ end
26
+
27
+ # @param [String] threat_vector the threat name to search by
28
+ # @return [Array<Hash>] an array of "guidance hashes"
29
+ def prioritized_guidance_by_threat_vector(threat_vector)
30
+ get "/threat_vectors/#{threat_vector}/prioritized_guidance"
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,36 @@
1
+ require 'controls/client/configurations'
2
+
3
+ module Controls
4
+ class Client
5
+ # A module to encapsulate API methods related to security controls and
6
+ # configurations
7
+ # @since API v1.0
8
+ # @version v1.0.0
9
+ module SecurityControls
10
+ include Configurations
11
+
12
+ #!@group Security Control Methods
13
+
14
+ # @param [String] control the name of the security control name to
15
+ # retrieve
16
+ # @return [Hash] a hash representing a security control
17
+ def security_controls(control = nil)
18
+ if control
19
+ get "/security_controls/#{control}"
20
+ else
21
+ get '/security_controls'
22
+ end
23
+ end
24
+
25
+ # @param [String] vector the threat vector to search for securuty controls
26
+ # by
27
+ # @return [Array<Hash>] a list of hashes representing threats
28
+ def threat_vector_security_controls(vector)
29
+ get "/threat_vectors/#{vector}/security_controls"
30
+ end
31
+ alias_method :security_controls_by_threat_vector, :threat_vector_security_controls
32
+
33
+ # @!endgroup
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,29 @@
1
+ module Controls
2
+ class Client
3
+ # A module to encapsulate API methods related to threats and threat vectors
4
+ # @since API v1.0
5
+ # @version v1.0.0
6
+ module ThreatVectors
7
+ # @!group Threat Vector Methods
8
+
9
+ # @param [String] vector the threat vector to search for
10
+ # @return [String] a hash representing the specified threat vector
11
+ def threat_vectors(vector = nil)
12
+ if vector
13
+ get "/threat_vectors/#{vector}"
14
+ else
15
+ get '/threat_vectors'
16
+ end
17
+ end
18
+
19
+ # @param [String] threat the threat to search for threat vectors by
20
+ # @return [Array<Hash>] a list of hashes representing threats
21
+ def threat_threat_vectors(threat)
22
+ get "/threats/#{threat}/threat_vectors"
23
+ end
24
+ alias_method :threat_vectors_by_threat, :threat_threat_vectors
25
+
26
+ # @!endgroup
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,26 @@
1
+ require 'controls/client/threat_vectors'
2
+
3
+ module Controls
4
+ class Client
5
+ # A module to encapsulate API methods related to threats and threat vectors
6
+ # @since API v1.0
7
+ # @version v1.0.0
8
+ module Threats
9
+ include ThreatVectors
10
+
11
+ # @!group Threat Methods
12
+
13
+ # @param [String] threat the threat name to search for
14
+ # @return [String] a hash representing the specified threat
15
+ def threats(threat = nil)
16
+ if threat
17
+ get "/threats/#{threat}"
18
+ else
19
+ get '/threats'
20
+ end
21
+ end
22
+
23
+ # @!endgroup
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,83 @@
1
+ module Controls
2
+ # A module to hold configurable options to be used by other classes such as
3
+ # the {Controls} eigenclass or the {Controls::Client} class
4
+ module Configurable
5
+ # @!attribute api_endpoint
6
+ # @return [String] the endpoint to connect to. default: https://nexpose.local:3780/insight/controls/api/1.0
7
+ # @!attribute api_endpoint
8
+ # @return [String] the endpoint to connect to. default: https://nexpose.local:3780/insight/controls/api/1.0
9
+ # @!attribute connection_options
10
+ # @return [Hash] the current connection options (headers, etc.)
11
+ # @!attribute default_media_type
12
+ # @return [String] the default media type to send with requests. default: application/json
13
+ # @!attribute middleware
14
+ # @return [Faraday::Connection] the middleware used to send requests
15
+ # @!attribute netrc
16
+ # @return [Boolean] whether to use the netrc credentials to authentcicate with the **controls**insight API
17
+ # @!attribute netrc_file
18
+ # @return [String] the path of the .netrc file to look for credentials in. default: ~/.netrc
19
+ # @!attribute user_agent
20
+ # @return [String] the user agent to send with API requests. example: "controls/v1.0.0.beta (ruby; 2.0.0p247; [x86_64-darwin12.4.0]; Faraday v0.8.8)"
21
+ # @!attribute username
22
+ # @return [String] the username to use for authentication
23
+ # @!attribute web_endpoint
24
+ # @return [String] the endpoint to connect to. default: https://nexpose.local:3780/insight/controls
25
+ attr_accessor :api_endpoint, :api_version, :connection_options,
26
+ :default_media_type, :middleware, :netrc, :netrc_file,
27
+ :user_agent, :username, :web_endpoint
28
+
29
+ # @!attribute [w] password
30
+ # @return [String] the password specified on login
31
+ attr_writer :password
32
+
33
+ class << self
34
+ # @return [Array<Symbol>] a list of configurable keys
35
+ def keys
36
+ @keys ||= [
37
+ :api_endpoint,
38
+ :api_version,
39
+ :connection_options,
40
+ :default_media_type,
41
+ :middleware,
42
+ :netrc,
43
+ :netrc_file,
44
+ :password,
45
+ :user_agent,
46
+ :username,
47
+ :web_endpoint
48
+ ]
49
+ end
50
+ end
51
+
52
+ # @yield [self]
53
+ def configure
54
+ yield self
55
+ end
56
+
57
+ def netrc?
58
+ !!@netrc
59
+ end
60
+
61
+ # Configures {Controls::Configurable} to use options found in
62
+ # {Controls::Default}
63
+ #
64
+ # @return [self]
65
+ def setup
66
+ Controls::Configurable.keys.each do |key|
67
+ instance_variable_set(:"@#{key}", Controls::Default.options[key])
68
+ end
69
+
70
+ self
71
+ end
72
+ # NOTE: This method currently leaves some "updated defaults" intact
73
+ # alias_method :reset!, :setup
74
+
75
+ private
76
+
77
+ # @return [Hash] a hash representation of the options mapping key names to
78
+ # their instance variable counterpart's value
79
+ def options
80
+ Hash[Controls::Configurable.keys.map { |key| [key, instance_variable_get(:"@#{key}")] }]
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,108 @@
1
+ require 'controls/version'
2
+
3
+ module Controls
4
+ # Default options merged with environment specific overrides to satisfy the
5
+ # options specified in the {Controls::Configurable} module
6
+ module Default
7
+ # @return [String] the API version to connect to. default: 1.0
8
+ # @since API v1.0.0
9
+ API_VERSION = '1.0'.freeze
10
+
11
+ # @return [String] the API endpoint to connect to. default: https://nexpose.local:3780/insight/controls/api/1.0
12
+ # @since API v1.0.0
13
+ API_ENDPOINT = "https://localhost:3780/insight/controls/api/#{API_VERSION}".freeze
14
+
15
+ # @return [String] the default media type to send with requests. default: application/json
16
+ # @since API v1.0.0
17
+ MEDIA_TYPE = 'application/json'
18
+
19
+ # @return [String] the user agent to send with API requests. example: "controls/v1.0.0.beta (ruby; 2.0.0p247; [x86_64-darwin12.4.0]; Faraday v0.8.8)"
20
+ USER_AGENT = "controls/v#{Controls::VERSION} (#{(RUBY_DESCRIPTION.split[0..1] + [RUBY_DESCRIPTION.split.last]).join('; ')}; Faraday v#{Faraday::VERSION})".freeze
21
+
22
+ # @return [String] the web endpoint to connect to. default: https://nexpose.local:3780/insight/controls
23
+ WEB_ENDPOINT = 'https://localhost:3780/insight/controls'.freeze
24
+
25
+ class << self
26
+ # @return [Hash] options as a Hash, mapped by keys from {Controls::Configurable}
27
+ def options
28
+ Hash[Controls::Configurable.keys.map { |key| [key, send(key)] }]
29
+ end
30
+
31
+ # @return [String] the API endpoint's URI as a URL
32
+ def api_endpoint
33
+ endpoint = ENV['CONTROLS_API_ENDPOINT'] || API_ENDPOINT
34
+ URI.parse(endpoint).to_s
35
+ end
36
+
37
+ # @return [String] the API version to connect to
38
+ def api_version
39
+ if ENV['CONTROLS_API_VERSION'].to_s =~ /\d+.\d+/
40
+ ENV['CONTROLS_API_VERSION']
41
+ else
42
+ API_VERSION
43
+ end
44
+ end
45
+
46
+ # @return [Hash] the current connection options (headers, etc.)
47
+ def connection_options
48
+ {
49
+ headers: {
50
+ accept: default_media_type,
51
+ user_agent: user_agent
52
+ }
53
+ }
54
+ end
55
+
56
+ # @return [String] the environment specific default media type.
57
+ # default: {MEDIA_TYPE}
58
+ def default_media_type
59
+ ENV['CONTROLS_MEDIA_TYPE'] || MEDIA_TYPE
60
+ end
61
+
62
+ # REVIEW: Ensure that middleware is unique to the client instance
63
+ # @return [Faraday::Connection] the middleware used to send requests
64
+ def middleware
65
+ @middleware ||= Faraday.new(api_endpoint, connection_options) do |conn|
66
+ conn.adapter Faraday.default_adapter
67
+ conn.response :logger if ENV['CONTROLS_DEBUG']
68
+ conn.use Controls::Response::RaiseError
69
+ end
70
+ end
71
+
72
+ # @return [Boolean] whether to fallback on authentication using the
73
+ # specified netrc file
74
+ def netrc
75
+ ENV['CONTROLS_NETRC'] || false
76
+ end
77
+
78
+ # @return [String] the netrc file to use for authentication.
79
+ # default: ~/.netrc
80
+ def netrc_file
81
+ ENV['CONTROLS_NETRC_FILE'] || File.join(Dir.home, '.netrc')
82
+ end
83
+
84
+
85
+ # @return [String] the password to use for authentication
86
+ def password
87
+ ENV['CONTROLS_PASSWORD']
88
+ end
89
+
90
+ # @return [String] the user agent that will be sent along any requests
91
+ # sent using {#connection_options}
92
+ def user_agent
93
+ ENV['CONTROLS_USER_AGENT'] || USER_AGENT
94
+ end
95
+
96
+ # @return [String] the username to use for authentication
97
+ def username
98
+ ENV['CONTROLS_USERNAME']
99
+ end
100
+
101
+ # @return [String] the web endpoint's URI as a URL
102
+ def web_endpoint
103
+ endpoint = ENV['CONTROLS_WEB_ENDPOINT'] || WEB_ENDPOINT
104
+ URI.parse(endpoint).to_s
105
+ end
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,132 @@
1
+ module Controls
2
+ # A namespace for errors in the Controls module
3
+ class Error < StandardError
4
+ # @!attribute [r] error_message
5
+ # @return [String] the error message
6
+ # @!attribute [r] error_status
7
+ # @return [Fixnum] the error message
8
+ # @!attribute [r] error_json
9
+ # @return [Hash] the JSON body from the error message
10
+ attr_reader :error_json
11
+ attr_reader :error_message
12
+ attr_reader :error_status
13
+
14
+ # @raise [BadRequest,Unauthorized,NotFound,InternalServerError] a subclass
15
+ # of {Controls::Error}
16
+ # @return [nil] if no error was raised
17
+ def self.from_response(response)
18
+ error = case response[:status].to_i
19
+ when 302
20
+ # TODO: Nexpose/ControlsInsight returns 302 when you either visit a
21
+ # none existant path (Nexpose) or unauthenticated
22
+ # (ControlsInsight).
23
+ Found # if response[:body].empty?
24
+ when 400 then BadRequest
25
+ when 401 then Unauthorized
26
+ when 404 then NotFound
27
+ when 500 then InternalServerError
28
+ end
29
+
30
+ error.new(response) if error
31
+ end
32
+
33
+ # @return [self] generates an error and passes it to super
34
+ def initialize(response = nil)
35
+ @response = response
36
+ super(generate_error)
37
+ end
38
+
39
+ private
40
+
41
+ # @return [String] an error message to be used by {#generate_error}
42
+ def response_message
43
+ return @response_message if @response_message
44
+
45
+ resp = @response[:response]
46
+
47
+ if resp.headers['content-type']
48
+ resp.headers['content-type'][/(\S+);charset=utf-8/i]
49
+ html = Regexp.last_match && Regexp.last_match[1].eql?('text/html')
50
+ end
51
+
52
+ @response_message = if html
53
+ doc = Nokogiri::XML.parse(resp.body)
54
+
55
+ if doc.css('title').text.eql? 'ControlsInsight'
56
+ message = ['message'].zip(doc.css('h1').map(&:text))
57
+ reason = ['reason'].zip([doc.css('p').map(&:text)])
58
+ Hash[message + reason]
59
+ else
60
+ Hash[doc.css('HR').children.map { |elem| elem.text.split(' ', 2) }]
61
+ end
62
+ else
63
+ if resp.body.empty?
64
+ @error_json = {}
65
+ else
66
+ @error_json = JSON.parse(resp.body)
67
+ @error_message = @error_json['message']
68
+ @error_status = @error_json['status'].to_i
69
+
70
+ @error_json
71
+ end
72
+ end
73
+ end
74
+
75
+ # @return [String] the error message passed to super on {#initialize}
76
+ def generate_error
77
+ return unless @response
78
+
79
+ message = "#{@response[:method]} ".upcase
80
+ message << "#{@response[:url].path}"
81
+
82
+ if response_message.is_a? Hash
83
+ message << ": #{response_message['message']}\n" if response_message['message']
84
+
85
+ if response_message['reason'].respond_to?(:join)
86
+ message << response_message['reason'].join("\n")
87
+ elsif response_message['reason']
88
+ message << response_message['reason'].to_s
89
+ end
90
+ elsif response_message.is_a? String
91
+ message << response_message
92
+ else
93
+ message << "#{@response[:status]} -"
94
+ message << self.class.to_s.split('::', 2).last
95
+ end
96
+
97
+ message.strip
98
+ end
99
+ end
100
+
101
+ # @!group Generic errors
102
+
103
+ # TODO REVIEW: To be raised when a user hasn't explicitly supplied credentials or set up
104
+ # any environment defaults.
105
+ Unauthenticated = Class.new(StandardError)
106
+
107
+ # @!endgroup
108
+
109
+ # @!group HTTP errors
110
+
111
+ # @return [Found] an error to be raised when a status code of 401 is
112
+ # returned by the API
113
+ Found = Class.new(Error)
114
+
115
+ # @return [Unauthorized] an error to be raised when a status code of 401 is
116
+ # returned by the API
117
+ Unauthorized = Class.new(Error)
118
+
119
+ # @return [BadRequest] an error to be raised when a status code of 400 is
120
+ # returned by the API
121
+ BadRequest = Class.new(Error)
122
+
123
+ # @return [NotFound] an error to be raised when a status code of 404 is
124
+ # returned by the API
125
+ NotFound = Class.new(Error)
126
+
127
+ # @return [InternalServerError] an error to be raised when a status code of
128
+ # 500 is returned by the API
129
+ InternalServerError = Class.new(Error)
130
+
131
+ # @!endgroup
132
+ end