nexpose 0.6.5 → 0.7.0
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.
- checksums.yaml +4 -4
- data/COPYING +33 -0
- data/README.markdown +6 -2
- data/lib/nexpose.rb +6 -0
- data/lib/nexpose/ajax.rb +27 -6
- data/lib/nexpose/dag.rb +5 -13
- data/lib/nexpose/data_table.rb +1 -1
- data/lib/nexpose/discovery.rb +191 -0
- data/lib/nexpose/discovery/filter.rb +36 -0
- data/lib/nexpose/filter.rb +17 -6
- data/lib/nexpose/group.rb +21 -8
- data/lib/nexpose/multi_tenant_user.rb +219 -0
- data/lib/nexpose/report_template.rb +1 -1
- data/lib/nexpose/scan.rb +11 -0
- data/lib/nexpose/silo.rb +282 -278
- data/lib/nexpose/silo_profile.rb +239 -0
- data/lib/nexpose/site.rb +112 -5
- data/lib/nexpose/tag.rb +343 -0
- data/lib/nexpose/tag/criteria.rb +45 -0
- metadata +9 -2
data/lib/nexpose/filter.rb
CHANGED
@@ -134,6 +134,19 @@ module Nexpose
|
|
134
134
|
# Valid Operators: CONTAINS, NOT_CONTAINS
|
135
135
|
SOFTWARE = 'SOFTWARE'
|
136
136
|
|
137
|
+
# Valid Operators: IS, IS_NOT, GREATER_THAN, LESS_THAN, IS_APPLIED, IS_NOT_APPLIED
|
138
|
+
# Valid Values: VERY_HIGH, HIGH, NORMAL, LOW, VERY_LOW
|
139
|
+
USER_ADDED_CRITICALITY_LEVEL = 'TAG_CRITICALITY'
|
140
|
+
|
141
|
+
# Valid Operators: IS, IS_NOT, STARTS_WITH, ENDS_WITH, IS_APPLIED, IS_NOT_APPLIED, CONTAINS, NOT_CONTAINS
|
142
|
+
USER_ADDED_CUSTOM_TAG = 'TAG'
|
143
|
+
|
144
|
+
# Valid Operators: IS, IS_NOT, STARTS_WITH, ENDS_WITH, IS_APPLIED, IS_NOT_APPLIED, CONTAINS, NOT_CONTAINS
|
145
|
+
USER_ADDED_TAG_LOCATION = 'TAG_LOCATION'
|
146
|
+
|
147
|
+
# Valid Operators: IS, IS_NOT, STARTS_WITH, ENDS_WITH, IS_APPLIED, IS_NOT_APPLIED, CONTAINS, NOT_CONTAINS
|
148
|
+
USER_ADDED_TAG_OWNER = 'TAG_OWNER'
|
149
|
+
|
137
150
|
# Valid Operators: ARE
|
138
151
|
# Valid Values: PRESENT, NOT_PRESENT
|
139
152
|
VALIDATED_VULNERABILITIES = 'VULNERABILITY_VALIDATED_STATUS'
|
@@ -171,6 +184,8 @@ module Nexpose
|
|
171
184
|
IS_NOT_EMPTY = 'IS_NOT_EMPTY'
|
172
185
|
INCLUDE = 'INCLUDE'
|
173
186
|
DO_NOT_INCLUDE = 'DO_NOT_INCLUDE'
|
187
|
+
IS_APPLIED = 'IS_APPLIED'
|
188
|
+
IS_NOT_APPLIED = 'IS_NOT_APPLIED'
|
174
189
|
end
|
175
190
|
|
176
191
|
# Specialized values used by certain search fields
|
@@ -263,7 +278,7 @@ module Nexpose
|
|
263
278
|
def to_map
|
264
279
|
{ 'metadata' => { 'fieldName' => field },
|
265
280
|
'operator' => operator,
|
266
|
-
'values' => value
|
281
|
+
'values' => Array(value) }
|
267
282
|
end
|
268
283
|
|
269
284
|
def self.parse(json)
|
@@ -283,11 +298,7 @@ module Nexpose
|
|
283
298
|
attr_accessor :criteria
|
284
299
|
|
285
300
|
def initialize(criteria = [], match = 'AND')
|
286
|
-
|
287
|
-
@criteria = criteria
|
288
|
-
else
|
289
|
-
@criteria = [criteria]
|
290
|
-
end
|
301
|
+
@criteria = Array(criteria)
|
291
302
|
@match = match.upcase
|
292
303
|
end
|
293
304
|
|
data/lib/nexpose/group.rb
CHANGED
@@ -28,9 +28,10 @@ module Nexpose
|
|
28
28
|
if r.success
|
29
29
|
r.res.elements.each('AssetGroupListingResponse/AssetGroupSummary') do |group|
|
30
30
|
groups << AssetGroupSummary.new(group.attributes['id'].to_i,
|
31
|
-
|
32
|
-
|
33
|
-
|
31
|
+
group.attributes['name'],
|
32
|
+
group.attributes['description'],
|
33
|
+
group.attributes['riskscore'].to_f,
|
34
|
+
group.attributes['dynamic'].to_i == 1)
|
34
35
|
end
|
35
36
|
end
|
36
37
|
groups
|
@@ -43,10 +44,14 @@ module Nexpose
|
|
43
44
|
# Summary value object for asset group information.
|
44
45
|
#
|
45
46
|
class AssetGroupSummary
|
46
|
-
attr_reader :id, :name, :description, :risk_score
|
47
|
+
attr_reader :id, :name, :description, :risk_score, :dynamic
|
47
48
|
|
48
|
-
def initialize(id, name, desc, risk)
|
49
|
-
@id, @name, @description, @risk_score = id, name, desc, risk
|
49
|
+
def initialize(id, name, desc, risk, dynamic)
|
50
|
+
@id, @name, @description, @risk_score, @dyanmic = id, name, desc, risk, dynamic
|
51
|
+
end
|
52
|
+
|
53
|
+
def dynamic?
|
54
|
+
dynamic
|
50
55
|
end
|
51
56
|
|
52
57
|
# Delete this asset group and all associated data.
|
@@ -63,7 +68,7 @@ module Nexpose
|
|
63
68
|
class AssetGroup < AssetGroupSummary
|
64
69
|
include Sanitize
|
65
70
|
|
66
|
-
attr_accessor :name, :description, :id
|
71
|
+
attr_accessor :name, :description, :id , :tags
|
67
72
|
|
68
73
|
# Array[Device] of devices associated with this asset group.
|
69
74
|
attr_accessor :assets
|
@@ -73,6 +78,7 @@ module Nexpose
|
|
73
78
|
def initialize(name, desc, id = -1, risk = 0.0)
|
74
79
|
@name, @description, @id, @risk_score = name, desc, id, risk
|
75
80
|
@assets = []
|
81
|
+
@tags = []
|
76
82
|
end
|
77
83
|
|
78
84
|
def save(connection)
|
@@ -98,6 +104,11 @@ module Nexpose
|
|
98
104
|
xml << %(<device id="#{asset.id}"/>)
|
99
105
|
end
|
100
106
|
xml << '</Devices>'
|
107
|
+
xml << '<Tags>'
|
108
|
+
@tags.each do |tag|
|
109
|
+
xml << tag.as_xml.to_s
|
110
|
+
end
|
111
|
+
xml << '</Tags>'
|
101
112
|
xml << '</AssetGroup>'
|
102
113
|
end
|
103
114
|
|
@@ -133,7 +144,6 @@ module Nexpose
|
|
133
144
|
|
134
145
|
def self.parse(xml)
|
135
146
|
return nil unless xml
|
136
|
-
|
137
147
|
group = REXML::XPath.first(xml, 'AssetGroupConfigResponse/AssetGroup')
|
138
148
|
asset_group = new(group.attributes['name'],
|
139
149
|
group.attributes['description'],
|
@@ -146,6 +156,9 @@ module Nexpose
|
|
146
156
|
dev.attributes['riskfactor'].to_f,
|
147
157
|
dev.attributes['riskscore'].to_f)
|
148
158
|
end
|
159
|
+
group.elements.each('Tags/Tag') do |tag|
|
160
|
+
asset_group.tags << TagSummary.parse_xml(tag)
|
161
|
+
end
|
149
162
|
asset_group
|
150
163
|
end
|
151
164
|
end
|
@@ -0,0 +1,219 @@
|
|
1
|
+
module Nexpose
|
2
|
+
|
3
|
+
class Connection
|
4
|
+
include XMLUtils
|
5
|
+
|
6
|
+
# Retrieve a list of all users the user is authorized to view or manage.
|
7
|
+
#
|
8
|
+
# @return [Array[MultiTenantUserSummary]] Array of MultiTenantUserSummary objects.
|
9
|
+
#
|
10
|
+
def list_silo_users
|
11
|
+
r = execute(make_xml('MultiTenantUserListingRequest'), '1.2')
|
12
|
+
arr = []
|
13
|
+
if r.success
|
14
|
+
r.res.elements.each('MultiTenantUserListingResponse/MultiTenantUserSummaries/MultiTenantUserSummary') do |user|
|
15
|
+
arr << MultiTenantUserSummary.parse(user)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
arr
|
19
|
+
end
|
20
|
+
|
21
|
+
# Delete the specified silo user
|
22
|
+
#
|
23
|
+
# @return Whether or not the delete request succeeded.
|
24
|
+
#
|
25
|
+
def delete_silo_user(user_id)
|
26
|
+
r = execute(make_xml('MultiTenantUserDeleteRequest', {'user-id' => user_id}), '1.2')
|
27
|
+
r.success
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
class MultiTenantUserSummary
|
32
|
+
attr_reader :id
|
33
|
+
attr_reader :full_name
|
34
|
+
attr_reader :user_name
|
35
|
+
attr_reader :email
|
36
|
+
attr_reader :superuser
|
37
|
+
attr_reader :enabled
|
38
|
+
attr_reader :auth_module
|
39
|
+
attr_reader :auth_source
|
40
|
+
attr_reader :silo_count
|
41
|
+
attr_reader :locked
|
42
|
+
|
43
|
+
def initialize(&block)
|
44
|
+
instance_eval &block if block_given?
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.parse(xml)
|
48
|
+
new do
|
49
|
+
@id = xml.attributes['id'].to_i
|
50
|
+
@full_name = xml.attributes['full-name']
|
51
|
+
@user_name = xml.attributes['user-name']
|
52
|
+
@email = xml.attributes['email']
|
53
|
+
@superuser = xml.attributes['superuser'].to_s.chomp.eql?('true')
|
54
|
+
@enabled = xml.attributes['enabled'].to_s.chomp.eql?('true')
|
55
|
+
@auth_module = xml.attributes['auth-module']
|
56
|
+
@auth_source = xml.attributes['auth-source']
|
57
|
+
@silo_count = xml.attributes['silo-count'].to_i
|
58
|
+
@locked = xml.attributes['locked'].to_s.chomp.eql?('true')
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
class MultiTenantUser
|
64
|
+
attr_accessor :id
|
65
|
+
attr_accessor :full_name
|
66
|
+
attr_accessor :user_name
|
67
|
+
attr_accessor :auth_source_id
|
68
|
+
attr_accessor :email
|
69
|
+
attr_accessor :password
|
70
|
+
attr_accessor :superuser
|
71
|
+
attr_accessor :enabled
|
72
|
+
attr_accessor :silo_access
|
73
|
+
|
74
|
+
def initialize(&block)
|
75
|
+
instance_eval &block if block_given?
|
76
|
+
|
77
|
+
@silo_access = Array(@silo_access)
|
78
|
+
end
|
79
|
+
|
80
|
+
def save(connection)
|
81
|
+
if (@id)
|
82
|
+
update(connection)
|
83
|
+
else
|
84
|
+
create(connection)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
# Updates this silo user on a Nexpose console.
|
89
|
+
#
|
90
|
+
# @param [Connection] connection Connection to console where this silo user will be saved.
|
91
|
+
# @return [String] User ID assigned to this configuration, if successful.
|
92
|
+
#
|
93
|
+
def update(connection)
|
94
|
+
xml = connection.make_xml('MultiTenantUserUpdateRequest')
|
95
|
+
xml.add_element(as_xml)
|
96
|
+
r = connection.execute(xml, '1.2')
|
97
|
+
@id = r.attributes['user-id'] if r.success
|
98
|
+
end
|
99
|
+
|
100
|
+
# Saves this silo user to a Nexpose console.
|
101
|
+
#
|
102
|
+
# @param [Connection] connection Connection to console where this silo user will be saved.
|
103
|
+
# @return [String] User ID assigned to this configuration, if successful.
|
104
|
+
#
|
105
|
+
def create(connection)
|
106
|
+
xml = connection.make_xml('MultiTenantUserCreateRequest')
|
107
|
+
xml.add_element(as_xml)
|
108
|
+
r = connection.execute(xml, '1.2')
|
109
|
+
@id = r.attributes['user-id'] if r.success
|
110
|
+
end
|
111
|
+
|
112
|
+
def as_xml
|
113
|
+
xml = REXML::Element.new('MultiTenantUserConfig')
|
114
|
+
xml.add_attributes({'id' => @id,
|
115
|
+
'full-name' => @full_name,
|
116
|
+
'user-name' => @user_name,
|
117
|
+
'authsrcid' => @auth_source_id,
|
118
|
+
'email' => @email,
|
119
|
+
'password' => @password,
|
120
|
+
'superuser' => @superuser,
|
121
|
+
'enabled' => @enabled})
|
122
|
+
siloaccesses = xml.add_element('SiloAccesses')
|
123
|
+
@silo_access.each { |silo_access| siloaccesses.add_element(silo_access.as_xml) }
|
124
|
+
xml
|
125
|
+
end
|
126
|
+
|
127
|
+
def delete(connection)
|
128
|
+
connection.delete_silo_user(@id)
|
129
|
+
end
|
130
|
+
|
131
|
+
def to_xml
|
132
|
+
as_xml.to_s
|
133
|
+
end
|
134
|
+
|
135
|
+
def self.parse(xml)
|
136
|
+
new do |user|
|
137
|
+
user.id = xml.attributes['id'].to_i
|
138
|
+
user.full_name = xml.attributes['full-name']
|
139
|
+
user.user_name = xml.attributes['user-name']
|
140
|
+
user.email = xml.attributes['email']
|
141
|
+
user.superuser = xml.attributes['superuser'].to_s.chomp.eql?('true')
|
142
|
+
user.enabled = xml.attributes['enabled'].to_s.chomp.eql?('true')
|
143
|
+
user.auth_source_id = xml.attributes['authsrcid'].to_i
|
144
|
+
user.silo_access = []
|
145
|
+
xml.elements.each('SiloAccesses/SiloAccess') { |access| user.silo_access << SiloAccess.parse(access) }
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
def self.load(connection, user_id)
|
150
|
+
r = connection.execute(connection.make_xml('MultiTenantUserConfigRequest', {'user-id' => user_id}), '1.2')
|
151
|
+
|
152
|
+
if r.success
|
153
|
+
r.res.elements.each('MultiTenantUserConfigResponse/MultiTenantUserConfig') do |config|
|
154
|
+
return MultiTenantUser.parse(config)
|
155
|
+
end
|
156
|
+
end
|
157
|
+
nil
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
class SiloAccess
|
162
|
+
attr_accessor :all_groups
|
163
|
+
attr_accessor :all_sites
|
164
|
+
attr_accessor :role_name
|
165
|
+
attr_accessor :silo_id
|
166
|
+
attr_accessor :default
|
167
|
+
attr_accessor :sites
|
168
|
+
attr_accessor :groups
|
169
|
+
|
170
|
+
def initialize(&block)
|
171
|
+
instance_eval &block if block_given?
|
172
|
+
@sites = Array(@sites)
|
173
|
+
@groups = Array(@groups)
|
174
|
+
end
|
175
|
+
|
176
|
+
def as_xml
|
177
|
+
xml = REXML::Element.new('SiloAccess')
|
178
|
+
xml.add_attributes({'all-groups' => @all_groups,
|
179
|
+
'all-sites' => @all_sites,
|
180
|
+
'role-name' => @role_name,
|
181
|
+
'silo-id' => @silo_id,
|
182
|
+
'default-silo' => @default})
|
183
|
+
|
184
|
+
unless @groups.empty?
|
185
|
+
groups = xml.add_element('AllowedGroups')
|
186
|
+
@groups.each do |group|
|
187
|
+
groups.add_element('AllowedGroup', {'id' => group})
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
unless @sites.empty?
|
192
|
+
sites = xml.add_element('AllowedSites')
|
193
|
+
@sites.each do |site|
|
194
|
+
sites.add_element('AllowedSite', {'id' => site})
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
xml
|
199
|
+
end
|
200
|
+
|
201
|
+
def to_xml
|
202
|
+
as_xml.to_s
|
203
|
+
end
|
204
|
+
|
205
|
+
def self.parse(xml)
|
206
|
+
new do |access|
|
207
|
+
access.all_groups = xml.attributes['all-groups'].to_s.chomp.eql?('true')
|
208
|
+
access.all_sites = xml.attributes['all-sites'].to_s.chomp.eql?('true')
|
209
|
+
access.role_name = xml.attributes['role-name']
|
210
|
+
access.silo_id = xml.attributes['silo-id']
|
211
|
+
access.default = xml.attributes['default-silo'].to_s.chomp.eql?('true')
|
212
|
+
access.sites = []
|
213
|
+
xml.elements.each('AllowedSites/AllowedSite') { |site| access.sites << site.attributes['id'].to_i }
|
214
|
+
access.groups = []
|
215
|
+
xml.elements.each('AllowedGroups/AllowedGroup') { |group| access.groups << group.attributes['id'].to_i }
|
216
|
+
end
|
217
|
+
end
|
218
|
+
end
|
219
|
+
end
|
@@ -27,7 +27,7 @@ module Nexpose
|
|
27
27
|
# @param [String] template_id Unique identifier of the report template to remove.
|
28
28
|
#
|
29
29
|
def delete_report_template(template_id)
|
30
|
-
AJAX.delete(self, "/data/report/templates/#{template_id}")
|
30
|
+
AJAX.delete(self, "/data/report/templates/#{URI.escape(template_id)}")
|
31
31
|
end
|
32
32
|
end
|
33
33
|
|
data/lib/nexpose/scan.rb
CHANGED
@@ -208,6 +208,17 @@ module Nexpose
|
|
208
208
|
false
|
209
209
|
end
|
210
210
|
end
|
211
|
+
|
212
|
+
# Delete a scan and all its data from a console.
|
213
|
+
# Warning, this method is destructive and not guaranteed to leave a site
|
214
|
+
# in a valid state. DBCC may need to be run to correct missing or empty
|
215
|
+
# assets.
|
216
|
+
#
|
217
|
+
# @param [Fixnum] scan_id Scan ID to remove data for.
|
218
|
+
#
|
219
|
+
def delete_scan(scan_id)
|
220
|
+
AJAX.delete(self, "/data/scan/#{scan_id}")
|
221
|
+
end
|
211
222
|
end
|
212
223
|
|
213
224
|
# Object that represents a summary of a scan.
|
data/lib/nexpose/silo.rb
CHANGED
@@ -3,347 +3,351 @@ module Nexpose
|
|
3
3
|
class Connection
|
4
4
|
include XMLUtils
|
5
5
|
|
6
|
-
|
7
|
-
# SILO MANAGEMENT #
|
8
|
-
###################
|
9
|
-
|
10
|
-
#########################
|
11
|
-
# MULTI-TENANT USER OPS #
|
12
|
-
#########################
|
13
|
-
|
14
|
-
#-------------------------------------------------------------------------
|
15
|
-
# Creates a multi-tenant user
|
6
|
+
# Retrieve a list of all silos the user is authorized to view or manage.
|
16
7
|
#
|
17
|
-
#
|
8
|
+
# @return [Array[SiloSummary]] Array of SiloSummary objects.
|
18
9
|
#
|
19
|
-
|
20
|
-
|
10
|
+
def list_silos
|
11
|
+
r = execute(make_xml('SiloListingRequest'), '1.2')
|
12
|
+
arr = []
|
13
|
+
if r.success
|
14
|
+
r.res.elements.each('SiloListingResponse/SiloSummaries/SiloSummary') do |silo|
|
15
|
+
arr << SiloSummary.parse(silo)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
arr
|
19
|
+
end
|
20
|
+
|
21
|
+
alias_method :silos, :list_silos
|
22
|
+
|
23
|
+
# Delete the specified silo
|
21
24
|
#
|
22
|
-
#
|
23
|
-
# email, password
|
25
|
+
# @return Whether or not the delete request succeeded.
|
24
26
|
#
|
25
|
-
|
27
|
+
def delete_silo(silo_id)
|
28
|
+
r = execute(make_xml('SiloDeleteRequest', {'silo-id' => silo_id}), '1.2')
|
29
|
+
r.success
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
class Silo
|
34
|
+
# Required fields
|
35
|
+
attr_accessor :id
|
36
|
+
attr_accessor :profile_id
|
37
|
+
attr_accessor :name
|
38
|
+
attr_accessor :max_assets
|
39
|
+
attr_accessor :max_users
|
40
|
+
attr_accessor :max_hosted_assets
|
41
|
+
|
42
|
+
#Optional fields
|
43
|
+
attr_accessor :description
|
44
|
+
attr_accessor :merchant
|
45
|
+
attr_accessor :organization
|
46
|
+
|
47
|
+
def initialize(&block)
|
48
|
+
instance_eval &block if block_given?
|
49
|
+
end
|
50
|
+
|
51
|
+
# Copy an existing configuration from a Nexpose instance.
|
52
|
+
# Returned object will reset the silo ID and name
|
26
53
|
#
|
27
|
-
#
|
28
|
-
#
|
54
|
+
# @param [Connection] connection Connection to the security console.
|
55
|
+
# @param [String] id Silo ID of an existing silo.
|
56
|
+
# @return [Silo] Silo configuration loaded from a Nexpose console.
|
29
57
|
#
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
# Add the silo access
|
37
|
-
silo_xml = make_xml('SiloAccesses', {}, '', false)
|
38
|
-
silo_configs.each do |silo_config|
|
39
|
-
silo_config_xml = make_xml('SiloAccess', {}, '', false)
|
40
|
-
silo_config.keys.each do |k|
|
41
|
-
if k == 'allowed_sites'
|
42
|
-
allowed_sites_xml = make_xml('AllowedSites', {}, '', false)
|
43
|
-
silo_config['allowed_sites'].each do |allowed_site|
|
44
|
-
allowed_sites_xml.add_element(make_xml('AllowedSite', {'id' => allowed_site}, '', false))
|
45
|
-
end
|
46
|
-
silo_config_xml.add_element(allowed_sites_xml)
|
47
|
-
elsif k == 'allowed_groups'
|
48
|
-
allowed_groups_xml = make_xml('AllowedGroups', {}, '', false)
|
49
|
-
silo_config['allowed_groups'].each do |allowed_group|
|
50
|
-
allowed_groups_xml.add_element(make_xml('AllowedGroup', {'id' => allowed_group}, '', false))
|
51
|
-
end
|
52
|
-
silo_config_xml.add_element(allowed_groups_xml)
|
53
|
-
else
|
54
|
-
silo_config_xml.attributes[k] = silo_config[k]
|
55
|
-
end
|
56
|
-
end
|
57
|
-
silo_xml.add_element(silo_config_xml)
|
58
|
-
end
|
59
|
-
mtu_config_xml.add_element(silo_xml)
|
60
|
-
xml.add_element(mtu_config_xml)
|
61
|
-
r = execute(xml, '1.2')
|
62
|
-
r.success
|
58
|
+
def self.copy(connection, id)
|
59
|
+
silo = load(connection, id)
|
60
|
+
silo.id = nil
|
61
|
+
silo.name = nil
|
62
|
+
silo
|
63
63
|
end
|
64
64
|
|
65
|
-
|
66
|
-
#
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
65
|
+
# Load an existing configuration from a Nexpose instance.
|
66
|
+
#
|
67
|
+
# @param [Connection] connection Connection to console where site exists.
|
68
|
+
# @param [String] id Silo ID of an existing silo.
|
69
|
+
# @return [Silo] Silo configuration loaded from a Nexpose console.
|
70
|
+
#
|
71
|
+
def self.load(connection, id)
|
72
|
+
r = connection.execute(connection.make_xml('SiloConfigRequest', {'silo-id' => id}), '1.2')
|
71
73
|
|
72
74
|
if r.success
|
73
|
-
res
|
74
|
-
|
75
|
-
res << {
|
76
|
-
:id => mtu.attributes['id'],
|
77
|
-
:full_name => mtu.attributes['full-name'],
|
78
|
-
:user_name => mtu.attributes['user-name'],
|
79
|
-
:email => mtu.attributes['email'],
|
80
|
-
:super_user => mtu.attributes['superuser'],
|
81
|
-
:enabled => mtu.attributes['enabled'],
|
82
|
-
:auth_module => mtu.attributes['auth-module'],
|
83
|
-
:silo_count => mtu.attributes['silo-count'],
|
84
|
-
:locked => mtu.attributes['locked']
|
85
|
-
}
|
75
|
+
r.res.elements.each('SiloConfigResponse/SiloConfig') do |config|
|
76
|
+
return Silo.parse(config)
|
86
77
|
end
|
87
|
-
res
|
88
|
-
else
|
89
|
-
false
|
90
78
|
end
|
79
|
+
nil
|
91
80
|
end
|
92
81
|
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
r.success
|
82
|
+
def save(connection)
|
83
|
+
begin
|
84
|
+
update(connection)
|
85
|
+
rescue APIError => error
|
86
|
+
raise error unless (error.message =~ /A silo .* does not exist./)
|
87
|
+
create(connection)
|
88
|
+
end
|
101
89
|
end
|
102
90
|
|
103
|
-
|
104
|
-
# SILO PROFILE OPS #
|
105
|
-
####################
|
106
|
-
|
107
|
-
#-------------------------------------------------------------------------
|
108
|
-
# Creates a silo profile
|
91
|
+
# Updates this silo on a Nexpose console.
|
109
92
|
#
|
110
|
-
#
|
93
|
+
# @param [Connection] connection Connection to console where this silo will be saved.
|
94
|
+
# @return [String] Silo ID assigned to this configuration, if successful.
|
111
95
|
#
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
96
|
+
def update(connection)
|
97
|
+
xml = connection.make_xml('SiloUpdateRequest')
|
98
|
+
xml.add_element(as_xml)
|
99
|
+
r = connection.execute(xml, '1.2')
|
100
|
+
@id = r.attributes['id'] if r.success
|
101
|
+
end
|
102
|
+
|
103
|
+
# Saves a new silo to a Nexpose console.
|
119
104
|
#
|
120
|
-
#
|
121
|
-
#
|
105
|
+
# @param [Connection] connection Connection to console where this silo will be saved.
|
106
|
+
# @return [String] Silo ID assigned to this configuration, if successful.
|
122
107
|
#
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
# Add the permissions
|
130
|
-
if permissions['global_report_templates']
|
131
|
-
grt_xml = make_xml('GlobalReportTemplates', {}, '', false)
|
132
|
-
permissions['global_report_templates'].each do |name|
|
133
|
-
grt_xml.add_element make_xml('GlobalReportTemplate', {'name' => name}, '', false)
|
134
|
-
end
|
135
|
-
spc_xml.add_element grt_xml
|
136
|
-
end
|
108
|
+
def create(connection)
|
109
|
+
xml = connection.make_xml('SiloCreateRequest')
|
110
|
+
xml.add_element(as_xml)
|
111
|
+
r = connection.execute(xml, '1.2')
|
112
|
+
@id = r.attributes['id'] if r.success
|
113
|
+
end
|
137
114
|
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
gse_xml.add_element make_xml('GlobalScanEngine', {'name' => name}, '', false)
|
142
|
-
end
|
143
|
-
spc_xml.add_element gse_xml
|
144
|
-
end
|
115
|
+
def delete(connection)
|
116
|
+
connection.delete_silo(@id)
|
117
|
+
end
|
145
118
|
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
119
|
+
def as_xml
|
120
|
+
xml = REXML::Element.new('SiloConfig')
|
121
|
+
xml.add_attributes({'description' => @description, 'name' => @name, 'id' => @id, 'silo-profile-id' => @profile_id, 'max-assets' => @max_assets, 'max-users' => @max_users, 'max-hosted-assets' => @max_hosted_assets})
|
122
|
+
xml.add(@merchant.as_xml) if @merchant
|
123
|
+
xml.add(@organization.as_xml) if @organization
|
124
|
+
xml
|
125
|
+
end
|
153
126
|
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
lm_xml.add_element make_xml('LicensedModule', {'name' => name}, '', false)
|
158
|
-
end
|
159
|
-
spc_xml.add_element lm_xml
|
160
|
-
end
|
127
|
+
def to_xml
|
128
|
+
as_xml.to_s
|
129
|
+
end
|
161
130
|
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
131
|
+
def self.parse(xml)
|
132
|
+
new do |silo|
|
133
|
+
silo.id = xml.attributes['id']
|
134
|
+
silo.profile_id = xml.attributes['silo-profile-id']
|
135
|
+
silo.name = xml.attributes['name']
|
136
|
+
silo.max_assets = xml.attributes['max-assets'].to_i
|
137
|
+
silo.max_users = xml.attributes['max-users'].to_i
|
138
|
+
silo.max_hosted_assets = xml.attributes['max-hosted-assets'].to_i
|
139
|
+
silo.description = xml.attributes['description']
|
140
|
+
|
141
|
+
xml.elements.each('Merchant') do |merchant|
|
142
|
+
silo.merchant = Merchant.parse(merchant)
|
166
143
|
end
|
167
|
-
spc_xml.add_element rrf_xml
|
168
|
-
end
|
169
144
|
|
170
|
-
|
171
|
-
|
172
|
-
permissions['restricted_report_sections'].each do |name|
|
173
|
-
rrs_xml.add_element make_xml('RestrictedReportSection', {'name' => name}, '', false)
|
145
|
+
xml.elements.each('Organization') do |organization|
|
146
|
+
silo.organization = Organization.parse(organization)
|
174
147
|
end
|
175
|
-
spc_xml.add_element rrs_xml
|
176
148
|
end
|
177
|
-
|
178
|
-
xml.add_element spc_xml
|
179
|
-
r = execute xml, '1.2'
|
180
|
-
r.success
|
181
149
|
end
|
182
150
|
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
151
|
+
class Address
|
152
|
+
attr_accessor :line1
|
153
|
+
attr_accessor :line2
|
154
|
+
attr_accessor :city
|
155
|
+
attr_accessor :state
|
156
|
+
attr_accessor :zip
|
157
|
+
attr_accessor :country
|
189
158
|
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
:all_global_engines => silo_profile.attributes['all-global-engines'],
|
204
|
-
:all_global_report_templates => silo_profile.attributes['all-global-report-templates'],
|
205
|
-
:all_global_scan_templates => silo_profile.attributes['all-global-scan-templates']
|
206
|
-
}
|
159
|
+
|
160
|
+
def initialize(&block)
|
161
|
+
instance_eval &block if block_given?
|
162
|
+
end
|
163
|
+
|
164
|
+
def self.parse(xml)
|
165
|
+
new do |address|
|
166
|
+
address.line1 = xml.attributes['line1']
|
167
|
+
address.line2 = xml.attributes['line2']
|
168
|
+
address.city = xml.attributes['city']
|
169
|
+
address.state = xml.attributes['state']
|
170
|
+
address.zip = xml.attributes['zip']
|
171
|
+
address.country = xml.attributes['country']
|
207
172
|
end
|
208
|
-
res
|
209
|
-
else
|
210
|
-
false
|
211
173
|
end
|
212
|
-
end
|
213
174
|
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
xml = make_xml('SiloProfileDeleteRequest', (using_name ? {'name' => name} : {'silo-profile-id' => id}))
|
220
|
-
r = execute xml, '1.2'
|
221
|
-
r.success
|
175
|
+
def as_xml
|
176
|
+
xml = REXML::Element.new('Address')
|
177
|
+
xml.add_attributes({'city' => @city, 'country' => @country, 'line1' => @line1, 'line2' => @line2, 'state' => @state, 'zip' => @zip})
|
178
|
+
xml
|
179
|
+
end
|
222
180
|
end
|
223
181
|
|
224
|
-
|
225
|
-
|
226
|
-
|
182
|
+
class Organization
|
183
|
+
attr_accessor :company
|
184
|
+
attr_accessor :first_name
|
185
|
+
attr_accessor :last_name
|
186
|
+
attr_accessor :phone
|
187
|
+
attr_accessor :address
|
188
|
+
attr_accessor :email
|
189
|
+
attr_accessor :title
|
190
|
+
attr_accessor :url
|
191
|
+
|
192
|
+
def initialize(&block)
|
193
|
+
instance_eval &block if block_given?
|
194
|
+
end
|
227
195
|
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
# REQUIRED PARAMS
|
234
|
-
# id, name, silo-profile-id, max-assets, max-hosted-assets, max-users
|
235
|
-
#
|
236
|
-
# OPTIONAL PARAMS
|
237
|
-
# description
|
238
|
-
#-------------------------------------------------------------------------
|
239
|
-
def create_silo silo_config
|
240
|
-
xml = make_xml 'SiloCreateRequest'
|
241
|
-
silo_config_xml = make_xml 'SiloConfig', {}, '', false
|
242
|
-
|
243
|
-
# Add the attributes
|
244
|
-
silo_config.keys.each do |key|
|
245
|
-
if not 'merchant'.eql? key and not 'organization'.eql? key
|
246
|
-
silo_config_xml.attributes[key] = silo_config[key]
|
247
|
-
end
|
196
|
+
def as_xml
|
197
|
+
xml = REXML::Element.new('Organization')
|
198
|
+
xml.add_attributes({'company' => @company, 'email-address' => @email, 'first-name' => @first_name, 'last-name' => @last_name, 'phone-number' => @phone, 'title' => @title, 'url' => @url})
|
199
|
+
xml.add(@address.as_xml)
|
200
|
+
xml
|
248
201
|
end
|
249
202
|
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
203
|
+
def self.parse(xml)
|
204
|
+
new do |organization|
|
205
|
+
organization.company = xml.attributes['company']
|
206
|
+
organization.first_name = xml.attributes['first-name']
|
207
|
+
organization.last_name = xml.attributes['last-name']
|
208
|
+
organization.phone = xml.attributes['phone-number']
|
209
|
+
xml.elements.each('Address') do |address|
|
210
|
+
organization.address = Address.parse(address)
|
256
211
|
end
|
212
|
+
organization.email = xml.attributes['email']
|
213
|
+
organization.title = xml.attributes['title']
|
214
|
+
organization.url = xml.attributes['url']
|
257
215
|
end
|
216
|
+
end
|
217
|
+
end
|
258
218
|
|
259
|
-
|
260
|
-
|
261
|
-
|
219
|
+
class Merchant < Organization
|
220
|
+
attr_accessor :acquirer_relationship
|
221
|
+
attr_accessor :agent_relationship
|
222
|
+
attr_accessor :ecommerce
|
223
|
+
attr_accessor :grocery
|
224
|
+
attr_accessor :mail_order
|
225
|
+
attr_accessor :payment_application
|
226
|
+
attr_accessor :payment_version
|
227
|
+
attr_accessor :petroleum
|
228
|
+
attr_accessor :retail
|
229
|
+
attr_accessor :telecommunication
|
230
|
+
attr_accessor :travel
|
231
|
+
attr_accessor :dbas
|
232
|
+
attr_accessor :industries
|
233
|
+
attr_accessor :qsa
|
234
|
+
|
235
|
+
def initialize(&block)
|
236
|
+
instance_eval &block if block_given?
|
237
|
+
@dbas = Array(@dbas)
|
238
|
+
@industries = Array(@industries)
|
239
|
+
@qsa = Array(@qsa)
|
262
240
|
end
|
263
241
|
|
264
|
-
|
265
|
-
|
266
|
-
|
242
|
+
def self.parse(xml)
|
243
|
+
new do |merchant|
|
244
|
+
merchant.acquirer_relationship = xml.attributes['acquirer-relationship'].to_s.chomp.eql?('true')
|
245
|
+
merchant.agent_relationship = xml.attributes['agent-relationship'].to_s.chomp.eql?('true')
|
246
|
+
merchant.ecommerce = xml.attributes['ecommerce'].to_s.chomp.eql?('true')
|
247
|
+
merchant.grocery = xml.attributes['grocery'].to_s.chomp.eql?('true')
|
248
|
+
merchant.mail_order = xml.attributes['mail-order'].to_s.chomp.eql?('true')
|
249
|
+
merchant.payment_application = xml.attributes['payment-application']
|
250
|
+
merchant.payment_version = xml.attributes['payment-version']
|
251
|
+
merchant.petroleum = xml.attributes['petroleum'].to_s.chomp.eql?('true')
|
252
|
+
merchant.retail = xml.attributes['retail'].to_s.chomp.eql?('true')
|
253
|
+
merchant.telecommunication = xml.attributes['telecommunication'].to_s.chomp.eql?('true')
|
254
|
+
merchant.travel = xml.attributes['travel'].to_s.chomp.eql?('true')
|
255
|
+
merchant.company = xml.attributes['company']
|
256
|
+
merchant.first_name = xml.attributes['first-name']
|
257
|
+
merchant.last_name = xml.attributes['last-name']
|
258
|
+
merchant.phone = xml.attributes['phone-number']
|
259
|
+
merchant.email = xml.attributes['email']
|
260
|
+
merchant.title = xml.attributes['title']
|
261
|
+
merchant.url = xml.attributes['url']
|
262
|
+
|
263
|
+
xml.elements.each('Address') do |address|
|
264
|
+
merchant.address = Address.parse(address)
|
265
|
+
end
|
267
266
|
|
268
|
-
|
269
|
-
|
270
|
-
|
267
|
+
merchant.dbas = []
|
268
|
+
xml.elements.each('DBAs/DBA') do |dba|
|
269
|
+
merchant.dbas << dba.attributes['name']
|
271
270
|
end
|
272
|
-
end
|
273
271
|
|
274
|
-
|
275
|
-
|
276
|
-
|
272
|
+
merchant.industries = []
|
273
|
+
xml.elements.each('OtherIndustries/Industry') do |industry|
|
274
|
+
merchant.industries << industry.attributes['name']
|
275
|
+
end
|
277
276
|
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
silo_config['merchant']['dba'].each do |name|
|
282
|
-
dba_xml.add_element make_xml('DBA', {'name' => name}, '', false)
|
277
|
+
merchant.qsa = []
|
278
|
+
xml.elements.each('QSA') do |organization|
|
279
|
+
merchant.qsa << Organization.parse(organization)
|
283
280
|
end
|
284
|
-
merchant_xml.add_element dba_xml
|
285
281
|
end
|
282
|
+
end
|
283
|
+
|
284
|
+
def as_xml
|
285
|
+
xml = super
|
286
|
+
xml.name = 'Merchant'
|
287
|
+
xml.add_attributes({'acquirer-relationship' => @acquirer_relationship, 'agent-relationship' => @agent_relationship, 'ecommerce' => @ecommerce, 'grocery' => @grocery, 'mail-order' => @mail_order})
|
288
|
+
xml.add_attributes({'payment-application' => @payment_application, 'payment-version' => @payment_version, 'petroleum' => @petroleum, 'retail' => @retail, 'telecommunication' => @telecommunication, 'travel' => @travel})
|
286
289
|
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
290
|
+
unless dbas.empty?
|
291
|
+
dbas = REXML::Element.new('DBAs')
|
292
|
+
@dbas.each do |dba|
|
293
|
+
dbas.add_element('DBA', {'name' => dba})
|
291
294
|
end
|
292
|
-
merchant_xml.add_element ois_xml
|
293
295
|
end
|
294
296
|
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
qsa_xml.attributes[key] = silo_config['merchant']['qsa'][key]
|
300
|
-
end
|
297
|
+
unless @industries.empty?
|
298
|
+
industries = REXML::Element.new('OtherIndustries')
|
299
|
+
@industries.each do |industry|
|
300
|
+
industries.add_element('Industry', {'name' => industry})
|
301
301
|
end
|
302
|
+
end
|
302
303
|
|
303
|
-
|
304
|
-
address_xml = make_xml 'Address', silo_config['merchant']['qsa']['address'], '', false
|
304
|
+
xml.add(@qsa.as_xml) unless @qsa.empty?
|
305
305
|
|
306
|
-
|
307
|
-
merchant_xml.add_element qsa_xml
|
308
|
-
end
|
309
|
-
silo_config_xml.add_element merchant_xml
|
306
|
+
xml
|
310
307
|
end
|
311
|
-
|
312
|
-
xml.add_element silo_config_xml
|
313
|
-
r = execute xml, '1.2'
|
314
|
-
r.success
|
315
308
|
end
|
309
|
+
end
|
316
310
|
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
311
|
+
# Object that represents the summary of a Nexpose Site.
|
312
|
+
#
|
313
|
+
class SiloSummary
|
314
|
+
# The silo ID.
|
315
|
+
attr_reader :id
|
316
|
+
# The silo name.
|
317
|
+
attr_reader :name
|
318
|
+
# A description of the silo.
|
319
|
+
attr_reader :description
|
320
|
+
# The ID of the silo profile being used for this silo.
|
321
|
+
attr_reader :profile_id
|
322
|
+
# The asset count for this silo
|
323
|
+
attr_reader :assets
|
324
|
+
# The asset count limit for this silo.
|
325
|
+
attr_reader :max_assets
|
326
|
+
# The hosted asset count limit for this silo.
|
327
|
+
attr_reader :max_hosted_assets
|
328
|
+
# The user count for this silo
|
329
|
+
attr_reader :users
|
330
|
+
# The user count limit for this silo.
|
331
|
+
attr_reader :max_users
|
332
|
+
|
333
|
+
def initialize(&block)
|
334
|
+
instance_eval &block if block_given?
|
335
|
+
end
|
323
336
|
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
337
|
+
def self.parse(xml)
|
338
|
+
new do
|
339
|
+
@id = xml.attributes['id']
|
340
|
+
@name = xml.attributes['name']
|
341
|
+
@description = xml.attributes['description']
|
342
|
+
@profile_id = xml.attributes['silo-profile-id']
|
343
|
+
xml.elements.each('LicenseSummary') do |license|
|
344
|
+
@assets = license.attributes['assets']
|
345
|
+
@max_assets = license.attributes['max-assets']
|
346
|
+
@max_hosted_assets = license.attributes['max-hosted-assets']
|
347
|
+
@users = license.attributes['users']
|
348
|
+
@max_users = license.attributes['max-users']
|
332
349
|
end
|
333
|
-
res
|
334
|
-
else
|
335
|
-
false
|
336
350
|
end
|
337
351
|
end
|
338
|
-
|
339
|
-
#-------------------------------------------------------------------------
|
340
|
-
# Delete a silo
|
341
|
-
#-------------------------------------------------------------------------
|
342
|
-
def delete_silo name, id
|
343
|
-
using_name = (name and not name.empty?)
|
344
|
-
xml = make_xml('SiloDeleteRequest', (using_name ? {'silo-name' => name} : {'silo-id' => id}))
|
345
|
-
r = execute xml, '1.2'
|
346
|
-
r.success
|
347
|
-
end
|
348
352
|
end
|
349
353
|
end
|