alert_logic 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +21 -0
- data/.rspec +2 -0
- data/.rubocop.yml +13 -0
- data/Gemfile +20 -0
- data/Guardfile +13 -0
- data/LICENSE.txt +22 -0
- data/README.md +109 -0
- data/Rakefile +45 -0
- data/alert_logic.gemspec +26 -0
- data/lib/alert_logic.rb +45 -0
- data/lib/alert_logic/client.rb +2 -0
- data/lib/alert_logic/client/base_client.rb +98 -0
- data/lib/alert_logic/client/rest_methods.rb +58 -0
- data/lib/alert_logic/log.rb +23 -0
- data/lib/alert_logic/resources.rb +5 -0
- data/lib/alert_logic/resources/appliance.rb +6 -0
- data/lib/alert_logic/resources/base_resource.rb +123 -0
- data/lib/alert_logic/resources/filters.rb +22 -0
- data/lib/alert_logic/resources/policy.rb +6 -0
- data/lib/alert_logic/resources/protected_host.rb +67 -0
- data/lib/alert_logic/utils.rb +11 -0
- data/lib/alert_logic/version.rb +4 -0
- data/spec/alert_logic_spec.rb +80 -0
- data/spec/client/base_client_spec.rb +51 -0
- data/spec/client/rest_methods_spec.rb +48 -0
- data/spec/log_spec.rb +36 -0
- data/spec/resources/appliance_spec.rb +1 -0
- data/spec/resources/base_resource_spec.rb +1 -0
- data/spec/resources/filters_spec.rb +1 -0
- data/spec/resources/policy_spec.rb +1 -0
- data/spec/resources/protected_host_spec.rb +1 -0
- data/spec/spec_helper.rb +80 -0
- data/spec/support/api_console.rb +38 -0
- data/spec/support/build_json_responses.rb +59 -0
- data/spec/support/fake_alert_logic_api.rb +39 -0
- data/spec/support/fake_api_console.rb +18 -0
- data/spec/utils_spec.rb +27 -0
- metadata +110 -0
@@ -0,0 +1,23 @@
|
|
1
|
+
# Module accessor for logger instance
|
2
|
+
module AlertLogic
|
3
|
+
@logger = nil
|
4
|
+
|
5
|
+
# Set or return a logger instance
|
6
|
+
def self.logger(logger_file = nil)
|
7
|
+
if !@logger || logger_file
|
8
|
+
if defined?(Chef::Log.logger) && !logger_file
|
9
|
+
@logger = Chef::Log.logger
|
10
|
+
else
|
11
|
+
@logger = Logger.new(logger_file || $stdout)
|
12
|
+
@logger.level = Logger::INFO
|
13
|
+
end
|
14
|
+
end
|
15
|
+
@logger
|
16
|
+
end
|
17
|
+
|
18
|
+
# Set logger instance to another instance
|
19
|
+
def self.logger=(logger)
|
20
|
+
logger.is_a?(String) ? self.logger(logger) : @logger = logger
|
21
|
+
@logger
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,123 @@
|
|
1
|
+
module AlertLogic
|
2
|
+
# Common methods that we'll mix into our resource classes
|
3
|
+
module Resource
|
4
|
+
include Utils
|
5
|
+
|
6
|
+
def self.included(klass)
|
7
|
+
klass.extend Resource
|
8
|
+
end
|
9
|
+
|
10
|
+
def find_by_id(id)
|
11
|
+
resource_type = name.split('::').last
|
12
|
+
klass = AlertLogic.const_get(resource_type.to_sym)
|
13
|
+
resource = AlertLogic \
|
14
|
+
.api_client \
|
15
|
+
.retrieve(resource_type.downcase, id) \
|
16
|
+
.body \
|
17
|
+
.first
|
18
|
+
klass.new(resource)
|
19
|
+
end
|
20
|
+
|
21
|
+
def find(*params)
|
22
|
+
resource_type = name.split('::').last
|
23
|
+
options = params.empty? ? {} : eval_filters(params)
|
24
|
+
klass = AlertLogic.const_get(resource_type.to_sym)
|
25
|
+
resources = AlertLogic \
|
26
|
+
.api_client \
|
27
|
+
.list(resource_type.downcase, options) \
|
28
|
+
.body
|
29
|
+
resources.map! { |resource_hash| klass.new(resource_hash) }
|
30
|
+
end
|
31
|
+
|
32
|
+
def initialize(resource_hash)
|
33
|
+
@resource_type = self.class.to_s.downcase.split('::').last
|
34
|
+
objectify(resource_hash)
|
35
|
+
end
|
36
|
+
|
37
|
+
def name=(name)
|
38
|
+
payload = { @resource_type => { 'name' => name } }
|
39
|
+
AlertLogic.api_client.edit(@resource_type, id, payload)
|
40
|
+
reload!
|
41
|
+
end
|
42
|
+
|
43
|
+
def tags=(tags)
|
44
|
+
msg = 'Tags must be a space separated string'
|
45
|
+
fail ClientError, msg unless tags.is_a?(String)
|
46
|
+
tags = tags.split.map! { |tag| { 'name' => tag } }
|
47
|
+
payload = { @resource_type => { 'tags' => tags } }
|
48
|
+
AlertLogic.api_client.edit(@resource_type, id, payload)
|
49
|
+
reload!
|
50
|
+
end
|
51
|
+
|
52
|
+
def reload!
|
53
|
+
objectify(
|
54
|
+
AlertLogic \
|
55
|
+
.api_client \
|
56
|
+
.list(@resource_type, 'id' => id) \
|
57
|
+
.body \
|
58
|
+
.first
|
59
|
+
)
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
def eval_filters(params)
|
65
|
+
filters = Resource.filters
|
66
|
+
unknown_filters = []
|
67
|
+
params = params.map do |param|
|
68
|
+
if param.is_a?(Symbol)
|
69
|
+
if filters.key?(param)
|
70
|
+
filters[param]
|
71
|
+
else
|
72
|
+
unknown_filters << param
|
73
|
+
{}
|
74
|
+
end
|
75
|
+
elsif param.is_a?(Hash)
|
76
|
+
param
|
77
|
+
else
|
78
|
+
{}
|
79
|
+
end
|
80
|
+
end.reduce(&:merge)
|
81
|
+
msg = "Unknown filter(s) passed: #{unknown_filters.inspect}."
|
82
|
+
msg << " Valid filters: #{filters.keys.inspect}"
|
83
|
+
AlertLogic.logger.warn(msg) unless unknown_filters.empty?
|
84
|
+
params
|
85
|
+
end
|
86
|
+
|
87
|
+
# Recursively traverse the hash and create an instance variable and
|
88
|
+
# accessor method for each key/value pair.
|
89
|
+
def objectify(hash)
|
90
|
+
hash.each do |name, value|
|
91
|
+
if value.is_a?(Hash)
|
92
|
+
value = uniquify_keys(name, value)
|
93
|
+
objectify(value)
|
94
|
+
else
|
95
|
+
def_method(name, value)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
# Some hash pairs have similar namespaces. We want to rename those to
|
101
|
+
# avoid a naming collision.
|
102
|
+
def uniquify_keys(name, hash)
|
103
|
+
conflicts = /^config$|^appliance$|^created$|
|
104
|
+
^modified$|^config_policy$|^appliance_policy$/
|
105
|
+
return hash unless name =~ conflicts
|
106
|
+
hash.map { |k, v| { "#{name}_#{k}" => v } }.reduce(&:merge)
|
107
|
+
end
|
108
|
+
|
109
|
+
# create and set an instance variable and define an accessor method if it's
|
110
|
+
# not already there.
|
111
|
+
def def_method(name, value)
|
112
|
+
instance_variable_set("@#{name}", value)
|
113
|
+
# Don't overwrite existing methods unless it's id. id isnt fully
|
114
|
+
# deprecatedin 1.8.7
|
115
|
+
(return true) if respond_to?(name.to_sym) && name != 'id'
|
116
|
+
self.class.send(
|
117
|
+
:define_method,
|
118
|
+
name,
|
119
|
+
proc { instance_variable_get("@#{name}") }
|
120
|
+
)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module AlertLogic
|
2
|
+
# Resource Filters
|
3
|
+
module Resource
|
4
|
+
@filters = nil
|
5
|
+
# Resource filters accessor
|
6
|
+
def self.filters(filters = nil)
|
7
|
+
defaults = {
|
8
|
+
:ok => { 'status.status' => 'ok' },
|
9
|
+
:online => { 'status.status' => 'ok' },
|
10
|
+
:offline => { 'status.status' => 'offline' },
|
11
|
+
:error => { 'status.status' => 'error' },
|
12
|
+
:windows => { 'metadata.os_type' => 'windows' },
|
13
|
+
:linux => { 'metadata.os_type' => 'unix' },
|
14
|
+
:unix => { 'metadata.os_type' => 'unix' },
|
15
|
+
:appliance_assignment => { 'type' => 'appliance_assignment' },
|
16
|
+
:all => {}
|
17
|
+
}
|
18
|
+
filters && @filters = filters
|
19
|
+
@filters ||= defaults
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
module AlertLogic
|
2
|
+
# AlertLogic ProtectedHost built from a JSON API response.
|
3
|
+
class ProtectedHost
|
4
|
+
include Resource
|
5
|
+
|
6
|
+
def appliance_policy_id=(policy_id)
|
7
|
+
update_policy('appliance', policy_id)
|
8
|
+
end
|
9
|
+
|
10
|
+
def config_policy_id=(policy_id)
|
11
|
+
update_policy('config', policy_id)
|
12
|
+
end
|
13
|
+
|
14
|
+
def appliance?(appliance)
|
15
|
+
# sometimes appliance_assigned_to wont exist if the Protected Host isn't
|
16
|
+
# already assigned to an appliance.
|
17
|
+
if respond_to?(:appliance_assigned_to)
|
18
|
+
appliance = find_appliance(appliance) if appliance.is_a?(String)
|
19
|
+
appliance.id == appliance_assigned_to
|
20
|
+
else
|
21
|
+
false
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def assign_appliance(appliance)
|
26
|
+
if appliance?(appliance)
|
27
|
+
AlertLogic.logger.info 'Host is already assigned to that Appliance'
|
28
|
+
else
|
29
|
+
appliance = find_appliance(appliance) if appliance.is_a?(String)
|
30
|
+
policy = AlertLogic::Policy \
|
31
|
+
.find('type' => 'appliance_assignment') \
|
32
|
+
.select { |pol| pol.appliances.any? { |ap| ap == appliance.id } } \
|
33
|
+
.first
|
34
|
+
self.appliance_policy_id = policy.id
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def reload!
|
39
|
+
[:@appliance_assigned_to,
|
40
|
+
:@appliance_connected_to,
|
41
|
+
:@appliance_policy_id,
|
42
|
+
:@config_policy_id
|
43
|
+
].each do |var|
|
44
|
+
remove_instance_variable(var) if instance_variable_defined?(var)
|
45
|
+
end
|
46
|
+
super
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def find_appliance(name)
|
52
|
+
msg = "Searching for an appliance with name: #{name}"
|
53
|
+
AlertLogic.logger.info msg
|
54
|
+
Appliance.find.select { |ap| ap.name == name }.first
|
55
|
+
end
|
56
|
+
|
57
|
+
def update_policy(policy_type, policy_id)
|
58
|
+
payload = { 'protectedhost' =>
|
59
|
+
{ policy_type => { 'policy' => { 'id' => policy_id } } }
|
60
|
+
}
|
61
|
+
res = AlertLogic.api_client.edit('protectedhost', id, payload)
|
62
|
+
AlertLogic.logger.debug res.body.inspect
|
63
|
+
reload!
|
64
|
+
AlertLogic.logger.info "#{policy_type} policy updated!"
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
module AlertLogic
|
2
|
+
# common utility methods that are required in multiple classes
|
3
|
+
module Utils
|
4
|
+
private
|
5
|
+
|
6
|
+
# simple string pluralizer to translate singular Alert Logic resources
|
7
|
+
def pluralize(resource)
|
8
|
+
resource =~ /^\w+y$/ ? resource.sub(/y$/, 'ies') : "#{resource}s"
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module AlertLogic
|
4
|
+
describe AlertLogic, '.api_client' do
|
5
|
+
before(:each) do
|
6
|
+
@client = double(:secret_key => '3234', :endpoint => 'https://fake.com')
|
7
|
+
AlertLogic.instance_variable_set(:@api_client, @client)
|
8
|
+
allow(Client).to receive(:new).and_return(@client)
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'accepts no params and returns a client' do
|
12
|
+
AlertLogic.instance_variable_set(:@api_client, nil)
|
13
|
+
AlertLogic.api_client.should eq(@client)
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'accepts a non-matching secret_key param and returns a new client' do
|
17
|
+
new_key = '8F1E9F714E7C70DADF5C26BDC89618999E211FA45766279f07'
|
18
|
+
Client.should_receive(:new).with(new_key, nil)
|
19
|
+
AlertLogic.api_client(new_key)
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'accepts a matching secret_key param and returns an existing client' do
|
23
|
+
AlertLogic.api_client('3234').should eq(@client)
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'accepts a non-matching endpoint param and returns a new client' do
|
27
|
+
endpoint = 'https://differentfake.com'
|
28
|
+
Client.should_receive(:new).with(nil, endpoint)
|
29
|
+
AlertLogic.api_client(nil, endpoint)
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'accepts a matching endpoint param and returns an existing client' do
|
33
|
+
AlertLogic.api_client(nil, '3234').should eq(@client)
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'accepts a non-matching endpoint/key params and returns a new client' do
|
37
|
+
endpoint = 'https://differentfake.com'
|
38
|
+
new_key = '8F1E9F714E7C70DADF5C26BDC89618999E211FA45766279f07'
|
39
|
+
Client.should_receive(:new).with(new_key, endpoint)
|
40
|
+
AlertLogic.api_client(new_key, endpoint)
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'accepts a matching endpoint param and returns an existing client' do
|
44
|
+
AlertLogic.api_client('https://fake.com', '3234').should eq(@client)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
describe AlertLogic, '.secret_key' do
|
49
|
+
before(:each) do
|
50
|
+
AlertLogic.instance_variable_set(:@secret_key, '1234')
|
51
|
+
allow(AlertLogic).to receive(:api_client).with(/\d+/)
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'returns nil when not set' do
|
55
|
+
AlertLogic.instance_variable_set(:@secret_key, nil)
|
56
|
+
AlertLogic.secret_key.should be_nil
|
57
|
+
end
|
58
|
+
|
59
|
+
it 'returns a value when set' do
|
60
|
+
AlertLogic.secret_key.should_not be_nil
|
61
|
+
AlertLogic.secret_key.should eq('1234')
|
62
|
+
end
|
63
|
+
|
64
|
+
it 'configures a new api_client when the key doesnt match' do
|
65
|
+
AlertLogic.should_receive(:api_client).with('5678')
|
66
|
+
AlertLogic.secret_key('5678')
|
67
|
+
end
|
68
|
+
|
69
|
+
it 'returns the existing key when the keys match' do
|
70
|
+
AlertLogic.secret_key('1234').should eq('1234')
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
describe AlertLogic, '.secret_key=' do
|
75
|
+
it 'should proxy request to #secret_key' do
|
76
|
+
AlertLogic.should_receive(:secret_key)
|
77
|
+
AlertLogic.secret_key = '1234'
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module AlertLogic
|
4
|
+
describe Client, '.new' do
|
5
|
+
before(:each) do
|
6
|
+
AlertLogic.secret_key = Test.secret_key
|
7
|
+
end
|
8
|
+
|
9
|
+
it 'initializes a new client with no params and a secret_key set' do
|
10
|
+
client = Client.new
|
11
|
+
client.secret_key.should eq(AlertLogic.secret_key)
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'raises an exception when no key is configure or passed' do
|
15
|
+
AlertLogic.secret_key = nil
|
16
|
+
expect { Client.new }.to raise_error(InvalidKey)
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'initializes a new client with a key param' do
|
20
|
+
key = '8F1E9F714E7C70DADF5C26BDC89618999E211FA45766279f07'
|
21
|
+
client = Client.new(key)
|
22
|
+
client.secret_key.should eq(key)
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'initializes a new client with an endpoint param' do
|
26
|
+
endpoint = 'https://fake.com'
|
27
|
+
client = Client.new(nil, endpoint)
|
28
|
+
client.endpoint.should eq(endpoint)
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'initializes a new client with both params' do
|
32
|
+
endpoint = 'https://fake.com'
|
33
|
+
key = '8F1E9F714E7C70DADF5C26BDC89618999E211FA45766279f07'
|
34
|
+
client = Client.new(key, endpoint)
|
35
|
+
client.endpoint.should eq(endpoint)
|
36
|
+
client.secret_key.should eq(key)
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'creates all the required instance variables' do
|
40
|
+
# ruby 1.8.7 can't sort an array of symbols so we have to define <=>
|
41
|
+
# to convert them to strings and compare against an array of strings.
|
42
|
+
vars = ['@secret_key', '@endpoint', '@logger', '@connection']
|
43
|
+
client = Client.new
|
44
|
+
client.instance_variables.map(&:to_s).sort \
|
45
|
+
.should eq(vars.sort)
|
46
|
+
client \
|
47
|
+
.instance_variable_get(:@connection) \
|
48
|
+
.should be_instance_of(Faraday::Connection)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module AlertLogic
|
4
|
+
describe RestMethods, :env do
|
5
|
+
let(:client) { AlertLogic.api_client(Test.secret_key) }
|
6
|
+
|
7
|
+
Test.all_resources.each do |resource, id|
|
8
|
+
describe '.delete' do
|
9
|
+
it 'interpolates the params and calls the proper client method' do
|
10
|
+
client.should_receive(:delete).with('resource', 'resource_id')
|
11
|
+
client.delete('resource', 'resource_id')
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
describe '.get' do
|
16
|
+
it 'interpolates the params and calls the proper client method' do
|
17
|
+
client.should_receive(:get).with(resource, id)
|
18
|
+
client.get(resource, id)
|
19
|
+
end
|
20
|
+
|
21
|
+
it "properly parses #{resource} index" do
|
22
|
+
res = client.get(resource, nil)
|
23
|
+
expect(res.body).to be_instance_of(Array)
|
24
|
+
expect(res.status).to eq(200)
|
25
|
+
end
|
26
|
+
|
27
|
+
it "properly parses a singular #{resource}" do
|
28
|
+
res = client.get(resource, id)
|
29
|
+
res.body.first['id'].should eq(id)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
describe '.put' do
|
34
|
+
it 'interpolates the params and calls the proper client method' do
|
35
|
+
client.should_receive(:put).with('resource', 'resource_id')
|
36
|
+
client.put('resource', 'resource_id')
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
describe '.post' do
|
41
|
+
it 'interpolates the params and calls the proper client method' do
|
42
|
+
client.should_receive(:post).with('resource', 'resource_id')
|
43
|
+
client.post('resource', 'resource_id')
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|