nexpose_cyberark 0.0.4-java → 0.0.5-java
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 +13 -5
- data/bin/nx_cyberark.rb +29 -32
- data/lib/nexpose_cyberark.rb +174 -28
- data/lib/nexpose_cyberark/config/nexpose_cyberark.config +30 -0
- data/lib/nexpose_cyberark/nexpose_ops.rb +71 -8
- data/lib/nexpose_cyberark/nx_logger.rb +150 -0
- data/lib/nexpose_cyberark/password_ops.rb +27 -8
- data/lib/nexpose_cyberark/version.rb +3 -1
- metadata +35 -21
- data/Gemfile.lock +0 -30
- data/nexpose_cyberark-0.0.3-java.gem +0 -0
- data/nexpose_cyberark.gemspec +0 -27
checksums.yaml
CHANGED
@@ -1,7 +1,15 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
MDIzMGQ4MWQxMmJhYTEwYWUyNjI2OGNjMWE5ZDllMmViZjhhMTg1Yg==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
ZDBiNTUzMWNkOGI4OTIyOWQ4Njk2YzRmZGVjOWVkMThhNGQzNzUzNg==
|
5
7
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
NTlkNTI2MTdlOWUyZDdmZjg3NWE3YjQ2OThhZDVlZjA0M2ViMDNiMmVhODQz
|
10
|
+
M2ZkYWU3NWI2M2JiMmZmNmQ4YzFjNDc1ZDQyNDkyYjM4N2Y3M2I0YTZlOGUz
|
11
|
+
M2ExYmFiZDJlMTQ0YTQ4MjAzMzViZjEzNmNmYzE3NzU3OGM1YmY=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
ZTA1NmVmOTc4Y2RhZWVlZTcxODQ1MzA3ODE3MDBjNjc4NzUwODM0N2Q5ZmY1
|
14
|
+
NjhjOWEyMjA5ODBmZWU4ZjM2ZDMxMmE1ZjdiY2VkNWE3NzZjNDllZmU1NjVl
|
15
|
+
MDY2NDczZDYxNjYxNDM2MTljYjk4MDM2ZDczOTQwYzU1YjU3ZDA=
|
data/bin/nx_cyberark.rb
CHANGED
@@ -3,43 +3,40 @@
|
|
3
3
|
# Please refer to the configuration documentation for instructions on how to run this gem
|
4
4
|
# Do NOT run this gem without proper pre-configuration.
|
5
5
|
require 'nexpose_cyberark'
|
6
|
+
require 'nexpose_cyberark/version'
|
7
|
+
require 'nexpose_cyberark/nx_logger'
|
8
|
+
require 'yaml'
|
6
9
|
|
7
|
-
|
8
|
-
# Vault Options
|
9
|
-
# App ID
|
10
|
-
app_id = ''
|
11
|
-
# Safe
|
12
|
-
safe = ''
|
13
|
-
# Folder
|
14
|
-
folder = ''
|
15
|
-
# Objects should have the same name as their counterparts in Nexpose
|
16
|
-
# IE: 'serverx01.mydomain.com' in both Cyberark and Nexpose.
|
17
|
-
# Individual IP's will be queried and should also correspond to Object names.
|
18
|
-
# For ranges, this integration will use the first IP as the credential for all the range.
|
19
|
-
# Finally, PolicyIDs in Cyberark should have 'unix' or 'windows' as part of their name for proper credential
|
20
|
-
# type assignment in Nexpose (ssh / cifs)
|
21
|
-
# ---- End CyberArk Configuration ---- #
|
10
|
+
CONFIG_PATH = File.join(File.dirname(__FILE__), '../lib/nexpose_cyberark/config/nexpose_cyberark.config')
|
22
11
|
|
23
|
-
#
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
start_scans = false
|
36
|
-
# ---- End Nexpose Configuration ---- #
|
12
|
+
# Obtain Nexpose settings from Environment Variables.
|
13
|
+
configuration_settings = begin
|
14
|
+
YAML.load_file(CONFIG_PATH)
|
15
|
+
rescue ArgumentError => e
|
16
|
+
raise "Could not parse YAML #{CONFIG_PATH} : #{e.message}"
|
17
|
+
end
|
18
|
+
|
19
|
+
raise 'Must configure nexpose settings before starting' if ENV['NEXPOSE_URL'].nil? || ENV['NEXPOSE_USERNAME'].nil? || ENV['NEXPOSE_PASSWORD'].nil?
|
20
|
+
configuration_settings[:nexpose_address] = ENV['NEXPOSE_URL']
|
21
|
+
configuration_settings[:nexpose_port] = ENV['NEXPOSE_PORT']
|
22
|
+
configuration_settings[:nexpose_username] = ENV['NEXPOSE_USERNAME']
|
23
|
+
configuration_settings[:nexpose_password] = ENV['NEXPOSE_PASSWORD']
|
37
24
|
|
38
25
|
|
39
26
|
|
40
27
|
# --- DO NOT EDIT BELOW THIS LINE --- #
|
41
28
|
|
42
|
-
vault_options = { :app_id => app_id, :safe => safe, :folder => folder }
|
43
|
-
nexpose_options = { :nxip =>
|
29
|
+
vault_options = { :app_id => configuration_settings[:ca_options][:app_id], :safe => configuration_settings[:ca_options][:safe], :folder => configuration_settings[:ca_options][:folder] }
|
30
|
+
nexpose_options = { :nxip => configuration_settings[:nexpose_address],
|
31
|
+
:nxport => configuration_settings[:nexpose_port],
|
32
|
+
:nxuser => configuration_settings[:nexpose_username],
|
33
|
+
:nxpassword => configuration_settings[:nexpose_password],
|
34
|
+
:sites => configuration_settings[:ca_options][:sites],
|
35
|
+
:windows_policy_ids => configuration_settings[:ca_options][:windows_policy_ids],
|
36
|
+
:unix_policy_ids => configuration_settings[:ca_options][:unix_policy_ids],
|
37
|
+
:logging => configuration_settings[:ca_options][:logging],
|
38
|
+
:log_level => configuration_settings[:ca_options][:log_level]}
|
44
39
|
NexposeCyberark::Vault.update_credentials(vault_options, nexpose_options)
|
45
|
-
NexposeCyberark::Vault.start_scans(nexpose_options) if start_scans
|
40
|
+
NexposeCyberark::Vault.start_scans(nexpose_options) if configuration_settings[:ca_options][:start_scans].casecmp('y') == 0
|
41
|
+
log = NexposeCyberark::NxLogger.instance
|
42
|
+
log.log_message('Importing credentials complete. Exiting...')
|
data/lib/nexpose_cyberark.rb
CHANGED
@@ -1,49 +1,195 @@
|
|
1
|
-
require "nexpose_cyberark/version"
|
2
1
|
Dir[File.dirname(__FILE__)+'/nexpose_cyberark/lib/java/*.jar'].each { |jar| require jar }
|
3
|
-
require
|
4
|
-
require
|
2
|
+
require 'nexpose_cyberark/password_ops'
|
3
|
+
require 'nexpose_cyberark/nexpose_ops'
|
4
|
+
require 'nexpose_cyberark/version'
|
5
|
+
require 'waitutil'
|
6
|
+
require 'Resolv'
|
7
|
+
|
5
8
|
module NexposeCyberark
|
6
9
|
module Vault
|
10
|
+
|
7
11
|
def self.update_credentials(vault_options, nexpose_options = nil)
|
8
|
-
|
12
|
+
#Setup logger
|
13
|
+
@log = NexposeCyberark::NxLogger.instance
|
14
|
+
@log.setup_logging(nexpose_options[:logging] || true, nexpose_options[:log_level] || 'info')
|
15
|
+
@log.setup_statistics_collection(NexposeCyberark::VENDOR, NexposeCyberark::PRODUCT_NAME, NexposeCyberark::VERSION)
|
16
|
+
|
17
|
+
@nx = Ops::Nexpose.new(nexpose_options[:nxip], nexpose_options[:nxport], nexpose_options[:nxuser], nexpose_options[:nxpassword])
|
18
|
+
@log.log_message('Connection to the Nexpose console complete!')
|
19
|
+
|
9
20
|
# Parse sites from config
|
10
21
|
nexpose_options[:sites].each do |site_id|
|
11
|
-
# Get
|
12
|
-
|
22
|
+
# Get included scan targets
|
23
|
+
site_scan_target_addresses = @nx.get_site_scan_target_addresses(site_id)
|
24
|
+
|
25
|
+
|
26
|
+
# We now have all Nexpose scan targets. Defined scan targets can be IP/Host where
|
27
|
+
# devices that have been scanned before will be an IP. Get the scan targets & subtract
|
28
|
+
# the defined devices to produce a list of new devices to be scanned.
|
29
|
+
|
30
|
+
# Nexpose site credentials expect the credential to match the scan target which may not be the IP.
|
31
|
+
site_devices = @nx.get_site_devices(site_id)
|
32
|
+
|
33
|
+
all_asset_details = []
|
34
|
+
site_devices.each { |device|
|
35
|
+
# Start by getting all the details we have on current devices
|
36
|
+
asset = @nx.load_asset(device.id)
|
37
|
+
asset_details= {}
|
38
|
+
asset_details[:address] = device.address
|
39
|
+
asset_details[:host_names] = asset.host_names
|
40
|
+
asset_details[:os_name] = [asset.os_name]
|
41
|
+
|
42
|
+
|
43
|
+
# Next subtract the defined assets from the defined scan targets to get devices never scanned before.
|
44
|
+
# Also monitor the deleted identifier. We need to remember this to sync the credential to the site for scanning.
|
45
|
+
scan_target_idq = site_scan_target_addresses.delete(device.address)
|
46
|
+
asset_details[:scan_target_idq] = scan_target_idq unless scan_target_idq.nil?
|
47
|
+
asset.host_names.each do |host_name|
|
48
|
+
scan_target_idq = site_scan_target_addresses.delete(host_name)
|
49
|
+
asset_details[:scan_target_idq] = scan_target_idq unless scan_target_idq.nil?
|
50
|
+
end unless asset.host_names.nil?
|
51
|
+
# Handle the case that a device may have been scanned before but is no longer in the scan target list.
|
52
|
+
# in this case we do not add it to the list of assets to import credentials for (think old sites with many inactive devices)
|
53
|
+
# asset_details[:scan_target_idq] = asset_details[:address] if asset_details[:scan_target_idq].nil?
|
54
|
+
if asset_details[:scan_target_idq].nil?
|
55
|
+
@log.log_warn_message("Found a site device not in the scan target list. Not fetching credentials for address <#{asset_details[:address]}>")
|
56
|
+
else
|
57
|
+
all_asset_details << asset_details
|
58
|
+
end
|
59
|
+
}
|
60
|
+
|
61
|
+
#Start by trying to get credentials for defined assets.
|
62
|
+
@log.log_message('Starting to query CyberArk for defined asset credentials...')
|
13
63
|
site_credentials = []
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
64
|
+
credential_data = nil
|
65
|
+
all_asset_details.each do |asset_details|
|
66
|
+
#The address stored in the vault could be any of the values we have.. so try and find it!
|
67
|
+
vault_options[:address] = asset_details[:address]
|
68
|
+
vault_options[:nexpose_os] = asset_details[:os_name]
|
69
|
+
credential_data = PasswordOps::get_password(nexpose_options, vault_options)
|
70
|
+
|
71
|
+
if credential_data.empty?
|
72
|
+
@log.log_debug_message("Failed to fetch credential for IP <#{asset_details[:address]}>")
|
73
|
+
# No credential for the IP. Let's check if Nexpose has any hostnames and if these match credentials
|
74
|
+
# within CyberArk. If they do not then try and resolve one from the IP.
|
75
|
+
if asset_details[:host_names].nil?
|
76
|
+
asset_details[:host_names] = resolve_address(asset_details[:address])
|
77
|
+
end
|
78
|
+
|
79
|
+
@log.log_debug_message("Trying to fetch credentials for resolved addresses <#{asset_details[:address]}> instead...")
|
80
|
+
|
81
|
+
asset_details[:host_names].each do |hostname|
|
82
|
+
vault_options[:address] = hostname
|
83
|
+
vault_options[:nexpose_os] = asset_details[:os_name]
|
84
|
+
credential_data = PasswordOps::get_password(nexpose_options, vault_options)
|
85
|
+
break unless credential_data.empty?
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
if credential_data.empty?
|
90
|
+
@log.log_error_message("Failed to get credential for asset with IP <#{asset_details[:address]}> and names <#{asset_details[:host_names]}>. Please check configuration!!!")
|
91
|
+
else
|
92
|
+
credential = @nx.credential_for_service(asset_details[:scan_target_idq], nil, "Automated import for Nexpose scan target <#{asset_details[:scan_target_idq]}> and CyberArk credential <#{vault_options[:address]}>", asset_details[:scan_target_idq], nil, credential_data[:service])
|
93
|
+
credential.user_name = credential_data[:user]
|
94
|
+
credential.password = credential_data[:password]
|
95
|
+
# Only Linux SUDO support currently
|
96
|
+
credential.permission_elevation_user = credential_data[:p_e_user] unless credential_data[:p_e_user].nil?
|
97
|
+
credential.permission_elevation_password = credential_data[:password] unless credential_data[:password].nil?
|
98
|
+
credential.permission_elevation_type = credential_data[:p_e_type]
|
27
99
|
site_credentials.push(credential)
|
28
100
|
end
|
29
101
|
end
|
102
|
+
|
103
|
+
@log.log_message('Starting to query CyberArk for new scan target credentials...')
|
104
|
+
|
105
|
+
# Now let's deal with assets that have never been scanned.
|
106
|
+
# These are more difficult as we only have the scan target address (whatever that may be).
|
107
|
+
site_scan_target_addresses.each do |address|
|
108
|
+
vault_options[:address] = address
|
109
|
+
credential_data = PasswordOps::get_password(nexpose_options, vault_options)
|
110
|
+
|
111
|
+
if credential_data.empty?
|
112
|
+
@log.log_debug_message("Failed to fetch credential for IP <#{address}>")
|
113
|
+
other_addresses = resolve_address(address)
|
114
|
+
@log.log_debug_message("Trying to fetch credentials for resolved addresses <#{other_addresses}> instead...")
|
115
|
+
other_addresses.each do |other_address|
|
116
|
+
vault_options[:address] = other_address
|
117
|
+
credential_data = PasswordOps::get_password(nexpose_options, vault_options)
|
118
|
+
break unless credential_data.empty?
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
if credential_data.empty?
|
123
|
+
@log.log_error_message("Failed to get credential for asset with IP <#{address}> and names <#{other_addresses}>. Please check configuration!!!")
|
124
|
+
else
|
125
|
+
credential = @nx.credential_for_service(address, nil, "Automated import for Nexpose scan target <#{address}> and CyberArk credential <#{vault_options[:address]}>",address, nil, credential_data[:service])
|
126
|
+
credential.user_name = credential_data[:user]
|
127
|
+
credential.password = credential_data[:password]
|
128
|
+
#Only Linux SUDO support currently
|
129
|
+
credential.permission_elevation_user = credential_data[:p_e_user] unless credential_data[:p_e_user].nil?
|
130
|
+
credential.permission_elevation_password = credential_data[:password] unless credential_data[:password].nil?
|
131
|
+
credential.permission_elevation_type = credential_data[:p_e_type]
|
132
|
+
site_credentials.push(credential)
|
133
|
+
end
|
134
|
+
end
|
135
|
+
@log.log_message("Saving credentials for site <#{site_id}>. Number of credentials to be saved <#{site_credentials.size}>.")
|
30
136
|
# Save site
|
31
137
|
@nx.save_site(site_id, site_credentials)
|
32
138
|
end
|
33
139
|
end
|
140
|
+
|
34
141
|
def self.start_scans(nexpose_options = nil)
|
35
|
-
@nx = Ops::Nexpose.new(nexpose_options[:nxip], nexpose_options[:nxuser], nexpose_options[:nxpassword])
|
142
|
+
@nx = Ops::Nexpose.new(nexpose_options[:nxip], nexpose_options[:nxport], nexpose_options[:nxuser], nexpose_options[:nxpassword])
|
143
|
+
all_site_scan_details = []
|
36
144
|
nexpose_options[:sites].each do |site_id|
|
37
|
-
puts "Starting scan #{site_id}"
|
38
145
|
scan = @nx.start_scan(site_id)
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
146
|
+
if scan.nil?
|
147
|
+
@log.log_error_message("Failed to start scan for site <#{site_id}>!")
|
148
|
+
next
|
149
|
+
end
|
150
|
+
@log.log_message("Started scan for site <#{site_id}>. Scan ID is <#{scan.id}>.")
|
151
|
+
site_scan_details = {}
|
152
|
+
site_scan_details[:site_id] = site_id
|
153
|
+
site_scan_details[:scan_id] = scan.id
|
154
|
+
all_site_scan_details << site_scan_details
|
155
|
+
end
|
156
|
+
|
157
|
+
WaitUtil.wait_for_condition('wait_for_all_scans_to_finish', :timeout_sec => 10800, :delay_sec => 60) do
|
158
|
+
@completed = false
|
159
|
+
all_site_scan_details.delete_if do |site_scan_details|
|
160
|
+
status = @nx.scan_status(site_scan_details[:scan_id])
|
161
|
+
@log.log_debug_message("Scan status for scan ID <#{site_scan_details[:scan_id]}> is <#{status}>.")
|
162
|
+
if status == Scan::Status::RUNNING
|
163
|
+
@log.log_debug_message("Waiting for scan <#{site_scan_details[:scan_id]}> for site <#{site_scan_details[:site_id]}> to finish.")
|
164
|
+
false
|
165
|
+
else
|
166
|
+
@log.log_message("Scan <#{site_scan_details[:scan_id]}> for site <#{site_scan_details[:scan_id]}> finished. Removing credentials")
|
167
|
+
@nx.delete_site_credentials(site_scan_details[:site_id])
|
168
|
+
true
|
169
|
+
end
|
170
|
+
end
|
171
|
+
@completed = true if all_site_scan_details.empty?
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
def self.resolve_address(address)
|
176
|
+
is_ip = !!((address =~ Resolv::IPv4::Regex) || (address =~ Resolv::IPv6::Regex))
|
177
|
+
resolved_addresses = []
|
178
|
+
@log.log_debug_message("Resolving address <#{address}>.")
|
179
|
+
begin
|
180
|
+
if is_ip
|
181
|
+
resolved_addresses = Resolv.getnames address
|
182
|
+
@log.log_debug_message("Address <#{address}> is an IP. Resolved names are <#{resolved_addresses}>.")
|
183
|
+
else
|
184
|
+
resolved_addresses = Resolv.getaddress address
|
185
|
+
@log.log_debug_message("Address <#{address}> is a name. Resolved IPs are <#{resolved_addresses}>.")
|
186
|
+
end
|
187
|
+
resolved_addresses = [resolved_addresses] unless resolved_addresses.kind_of?(Array)
|
188
|
+
rescue Resolv::ResolvError => e
|
189
|
+
@log.log_error_message("Unable to resolve address <#{address}>. Error was <#{e}>")
|
46
190
|
end
|
191
|
+
return resolved_addresses
|
47
192
|
end
|
193
|
+
|
48
194
|
end
|
49
195
|
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
---
|
2
|
+
# This configuration file defines all the particular options necessary to run the service.
|
3
|
+
# Fields marked (M) are mandatory.
|
4
|
+
#
|
5
|
+
# Service options:
|
6
|
+
:ca_options:
|
7
|
+
# Vault Options
|
8
|
+
# App ID
|
9
|
+
:app_id: my_app_id
|
10
|
+
# Safe
|
11
|
+
:safe: my_safe
|
12
|
+
# Folder
|
13
|
+
:folder: my_folder
|
14
|
+
# This setting will start scans on those sites, wait for the site to complete and then remove the credentials
|
15
|
+
# If you prefer to let the scans run on schedule, set this to 'N', otherwise set to 'Y'.
|
16
|
+
:start_scans: 'Y'
|
17
|
+
# CyberArk policy IDs that apply to assets that will require a CIFS connection for authenticated scanning
|
18
|
+
:windows_policy_ids:
|
19
|
+
- 'WinServerLocal'
|
20
|
+
# CyberArk policy IDs that apply to assets that will require an SSH connection for authenticated scanning
|
21
|
+
:unix_policy_ids:
|
22
|
+
- 'UnixSSH'
|
23
|
+
# Nexpose sites to import CyberArk credentials for.
|
24
|
+
:sites:
|
25
|
+
- '1'
|
26
|
+
# - '2'
|
27
|
+
# Enable or disable logging ('N' or 'Y'.)
|
28
|
+
:logging: 'Y'
|
29
|
+
#Support version are 'info' and 'debug'
|
30
|
+
:log_level: 'info'
|
@@ -1,37 +1,100 @@
|
|
1
1
|
require 'nexpose'
|
2
2
|
include Nexpose
|
3
|
+
|
3
4
|
module Ops
|
4
5
|
class Nexpose
|
5
6
|
attr_accessor :nsc
|
6
|
-
def initialize(nxip, nxuser, nxpasword)
|
7
|
+
def initialize(nxip, nxport, nxuser, nxpasword)
|
8
|
+
@log = NexposeCyberark::NxLogger.instance
|
9
|
+
@log.log_message('Connecting to the Nexpose console..')
|
7
10
|
@nsc = Connection.new(nxip, nxuser, nxpasword)
|
8
11
|
@nsc.login
|
12
|
+
@log.on_connect(nxip, nxport, @nsc.session_id, '{}')
|
9
13
|
end
|
10
14
|
|
11
|
-
def
|
15
|
+
def get_site_scan_targets(site_id)
|
16
|
+
@log.log_debug_message("Fetching list of scan targets for site <#{site_id}> from console")
|
12
17
|
site = Site.load(@nsc, site_id)
|
13
|
-
site.
|
18
|
+
site.included_addresses
|
19
|
+
end
|
20
|
+
|
21
|
+
def get_site_scan_target_addresses(site_id)
|
22
|
+
@log.log_debug_message("Fetching list of scan targets addresses for site <#{site_id}>")
|
23
|
+
site_scan_targets = get_site_scan_targets(site_id)
|
24
|
+
@log.log_message('Console returned scan targets!')
|
25
|
+
|
26
|
+
site_scan_target_addresses = []
|
27
|
+
#Convert this to a list of only addresses
|
28
|
+
site_scan_targets.each do |scan_target|
|
29
|
+
host = scan_target.host if scan_target.is_a?(HostName)
|
30
|
+
host = scan_target.from if scan_target.is_a?(IPRange)
|
31
|
+
|
32
|
+
range_scenario = false
|
33
|
+
range_scenario = true if scan_target.is_a?(IPRange)
|
34
|
+
|
35
|
+
if range_scenario
|
36
|
+
start_ip = IPAddr.new(scan_target.from)
|
37
|
+
end_ip = IPAddr.new(scan_target.to) unless scan_target.to.nil?
|
38
|
+
end_ip = IPAddr.new(scan_target.from) if scan_target.to.nil?
|
39
|
+
site_scan_target_addresses.concat (start_ip..end_ip).map(&:to_s)
|
40
|
+
else
|
41
|
+
site_scan_target_addresses << host
|
42
|
+
end
|
43
|
+
end
|
44
|
+
@log.log_debug_message('Processed scan targets. Returning addresses.')
|
45
|
+
return site_scan_target_addresses
|
46
|
+
end
|
47
|
+
|
48
|
+
def get_site_devices(site_id)
|
49
|
+
@log.log_debug_message("Fetching list of devices for site <#{site_id}>")
|
50
|
+
@nsc.list_site_devices(site_id)
|
51
|
+
end
|
52
|
+
|
53
|
+
def load_asset(device_id)
|
54
|
+
@log.log_debug_message("Fetching asset details for asset <#{device_id}>")
|
55
|
+
Asset.load(@nsc, device_id)
|
56
|
+
end
|
57
|
+
|
58
|
+
def credential_for_service(address, id, description, host, port, service)
|
59
|
+
@log.log_debug_message("Generating credential for address <#{address}>")
|
60
|
+
SiteCredentials.for_service(address, id, description, host, port, service)
|
14
61
|
end
|
15
62
|
|
16
63
|
def save_site(site_id, credentials)
|
64
|
+
@log.log_debug_message("Saving <#{credentials.size}> credentials for site <#{site_id}>")
|
17
65
|
site = Site.load(@nsc, site_id)
|
18
|
-
site.
|
66
|
+
site.site_credentials = credentials
|
19
67
|
site.save(@nsc)
|
20
68
|
end
|
21
69
|
|
22
70
|
def delete_site_credentials(site_id)
|
71
|
+
@log.log_debug_message("Deleting existing credentials for site <#{site_id}>")
|
23
72
|
site = Site.load(@nsc, site_id)
|
24
|
-
site.
|
73
|
+
site.site_credentials.clear
|
25
74
|
site.save(@nsc)
|
26
75
|
end
|
27
76
|
|
77
|
+
def load_site(site_id)
|
78
|
+
@log.log_debug_message("Fetching details for site <#{site_id}>")
|
79
|
+
Site.load(@nsc, site_id)
|
80
|
+
end
|
81
|
+
|
28
82
|
def start_scan(site_id)
|
29
|
-
|
30
|
-
|
83
|
+
@log.log_debug_message("Starting for site <#{site_id}>")
|
84
|
+
scan_details = nil
|
85
|
+
begin
|
86
|
+
site = load_site(site_id)
|
87
|
+
scan_details = site.scan(@nsc)
|
88
|
+
rescue Exception => e
|
89
|
+
@log.log_error_message("Failed to start scan for site <#{site_id}>. Error is <#{e}>")
|
90
|
+
end
|
91
|
+
scan_details
|
31
92
|
end
|
32
93
|
|
33
94
|
def scan_status(scan_id)
|
34
|
-
@nsc.scan_status(scan_id)
|
95
|
+
status = @nsc.scan_status(scan_id)
|
96
|
+
@log.log_debug_message("Scan status for scan ID <#{scan_id}> is <#{status}>.")
|
97
|
+
status
|
35
98
|
end
|
36
99
|
end
|
37
100
|
end
|
@@ -0,0 +1,150 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'json'
|
3
|
+
require 'net/http'
|
4
|
+
require 'singleton'
|
5
|
+
|
6
|
+
module NexposeCyberark
|
7
|
+
class NxLogger
|
8
|
+
include Singleton
|
9
|
+
attr_accessor :options, :statistic_key, :product, :logger_file
|
10
|
+
LOG_PATH = "./logs/rapid7_%s.log"
|
11
|
+
KEY_FORMAT = "external.integration.%s"
|
12
|
+
PRODUCT_FORMAT = "%s_%s"
|
13
|
+
|
14
|
+
DEFAULT_LOG = 'integration'
|
15
|
+
PRODUCT_RANGE = 3..30
|
16
|
+
KEY_RANGE = 3..15
|
17
|
+
|
18
|
+
ENDPOINT = '/data/external/statistic/'
|
19
|
+
|
20
|
+
def initialize()
|
21
|
+
@logger_file = get_log_path product
|
22
|
+
setup_logging(true, 'info')
|
23
|
+
end
|
24
|
+
|
25
|
+
def setup_statistics_collection(vendor, product_name, gem_version)
|
26
|
+
#Remove illegal characters
|
27
|
+
vendor.to_s.gsub!('-', '_')
|
28
|
+
product_name.to_s.gsub!('-', '_')
|
29
|
+
|
30
|
+
begin
|
31
|
+
@statistic_key = get_statistic_key vendor
|
32
|
+
@product = get_product product_name, gem_version
|
33
|
+
rescue => e
|
34
|
+
#Continue
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def setup_logging(enabled, log_level = nil)
|
39
|
+
unless enabled || @log.nil?
|
40
|
+
log_message('Logging disabled.')
|
41
|
+
return
|
42
|
+
end
|
43
|
+
|
44
|
+
@logger_file = get_log_path product
|
45
|
+
|
46
|
+
require 'logger'
|
47
|
+
directory = File.dirname(@logger_file)
|
48
|
+
FileUtils.mkdir_p(directory) unless File.directory?(directory)
|
49
|
+
io = IO.for_fd(IO.sysopen(@logger_file, 'a'))
|
50
|
+
io.autoclose = false
|
51
|
+
io.sync = true
|
52
|
+
@log = Logger.new(io, 'weekly')
|
53
|
+
@log.level = if log_level.casecmp('info') == 0
|
54
|
+
Logger::INFO
|
55
|
+
else
|
56
|
+
Logger::DEBUG
|
57
|
+
end
|
58
|
+
log_message("Logging enabled at level <#{log_level}>")
|
59
|
+
end
|
60
|
+
|
61
|
+
# Logs an info message
|
62
|
+
def log_message(message)
|
63
|
+
@log.info(message) unless @log.nil?
|
64
|
+
end
|
65
|
+
|
66
|
+
# Logs a debug message
|
67
|
+
def log_debug_message(message)
|
68
|
+
@log.debug(message) unless @log.nil?
|
69
|
+
end
|
70
|
+
|
71
|
+
# Logs an error message
|
72
|
+
def log_error_message(message)
|
73
|
+
@log.error(message) unless @log.nil?
|
74
|
+
end
|
75
|
+
|
76
|
+
# Logs a warn message
|
77
|
+
def log_warn_message(message)
|
78
|
+
@log.warn(message) unless @log.nil?
|
79
|
+
end
|
80
|
+
|
81
|
+
def log_stat_message(message)
|
82
|
+
end
|
83
|
+
|
84
|
+
def get_log_path(product)
|
85
|
+
product.downcase! unless product.nil?
|
86
|
+
File.join(File.dirname(__FILE__), LOG_PATH % (product || DEFAULT_LOG))
|
87
|
+
end
|
88
|
+
|
89
|
+
def get_statistic_key(vendor)
|
90
|
+
if vendor.nil? || vendor.length < KEY_RANGE.min
|
91
|
+
log_stat_message("Vendor length is below minimum of <#{KEY_RANGE}>")
|
92
|
+
return nil
|
93
|
+
end
|
94
|
+
|
95
|
+
KEY_FORMAT % vendor[0...KEY_RANGE.max].downcase
|
96
|
+
end
|
97
|
+
|
98
|
+
def get_product(product, version)
|
99
|
+
return nil if (product.nil? || version.nil?)
|
100
|
+
product = (PRODUCT_FORMAT % [product, version])[0...PRODUCT_RANGE.max]
|
101
|
+
|
102
|
+
if product.length < PRODUCT_RANGE.min
|
103
|
+
log_stat_message("Product length below minimum <#{PRODUCT_RANGE.min}>.")
|
104
|
+
return nil
|
105
|
+
end
|
106
|
+
product.downcase
|
107
|
+
end
|
108
|
+
|
109
|
+
def generate_payload(statistic_value='')
|
110
|
+
payload = {'statistic-key' => @statistic_key,
|
111
|
+
'statistic-value' => statistic_value,
|
112
|
+
'product' => @product}
|
113
|
+
JSON.generate(payload)
|
114
|
+
end
|
115
|
+
|
116
|
+
def send(nexpose_address, nexpose_port, session_id, payload)
|
117
|
+
header = {'Content-Type' => 'application/json',
|
118
|
+
'nexposeCCSessionID' => session_id,
|
119
|
+
'Cookie' => "nexposeCCSessionID=#{session_id}"}
|
120
|
+
req = Net::HTTP::Put.new(ENDPOINT, header)
|
121
|
+
req.body = payload
|
122
|
+
http_instance = Net::HTTP.new(nexpose_address, nexpose_port)
|
123
|
+
http_instance.use_ssl = true
|
124
|
+
http_instance.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
125
|
+
response = http_instance.start { |http| http.request(req) }
|
126
|
+
log_stat_message "Received code #{response.code} from Nexpose console."
|
127
|
+
log_stat_message "Received message #{response.msg} from Nexpose console."
|
128
|
+
log_stat_message 'Finished sending statistics data to Nexpose.'
|
129
|
+
response.code
|
130
|
+
end
|
131
|
+
|
132
|
+
def on_connect(nexpose_address, nexpose_port, session_id, value)
|
133
|
+
log_stat_message 'Sending statistics data to Nexpose'
|
134
|
+
|
135
|
+
if @product.nil? || @statistic_key.nil?
|
136
|
+
log_stat_message('Invalid product name and/or statistics key.')
|
137
|
+
log_stat_message('Statistics collection not enabled.')
|
138
|
+
return
|
139
|
+
end
|
140
|
+
|
141
|
+
begin
|
142
|
+
payload = generate_payload value
|
143
|
+
send(nexpose_address, nexpose_port, session_id, payload)
|
144
|
+
rescue => e
|
145
|
+
#Let the program continue
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
end
|
150
|
+
end
|
@@ -1,28 +1,47 @@
|
|
1
1
|
module PasswordOps
|
2
|
-
|
2
|
+
require 'nexpose'
|
3
|
+
include Nexpose
|
3
4
|
def self.cyberark
|
4
5
|
Java::javapasswordsdk
|
5
6
|
end
|
6
7
|
|
7
|
-
def self.get_password(vault_options = {}, password_req_sdk = nil, password_sdk = nil )
|
8
|
+
def self.get_password(nexpose_options = {}, vault_options = {}, password_req_sdk = nil, password_sdk = nil )
|
9
|
+
@log = NexposeCyberark::NxLogger.instance
|
8
10
|
password_req_sdk = cyberark.PSDKPasswordRequest.new if password_req_sdk.nil?
|
9
11
|
asset_data = {}
|
10
12
|
begin
|
11
13
|
password_req_sdk.set_app_id(vault_options[:app_id])
|
12
14
|
password_req_sdk.set_safe(vault_options[:safe])
|
13
15
|
password_req_sdk.set_folder(vault_options[:folder])
|
14
|
-
password_req_sdk.
|
16
|
+
password_req_sdk.set_address(vault_options[:address])
|
15
17
|
password_sdk = cyberark.PasswordSDK if password_sdk.nil?
|
16
18
|
password_result = password_sdk.getPassword(password_req_sdk)
|
17
|
-
if password_result.get_policy_id
|
18
|
-
|
19
|
-
|
19
|
+
if nexpose_options[:windows_policy_ids].any?{ |policy| policy.casecmp(password_result.get_policy_id)==0 }
|
20
|
+
if nexpose_options[:unix_policy_ids].any?{ |policy| policy.casecmp(password_result.get_policy_id)==0 }
|
21
|
+
# Has both Windows and Unix policies. Check Nexpose to see if we have an OS listing
|
22
|
+
@log.log_error_message("Asset with credential address <#{vault_options[:address]}> has a conflicting policy configuration! Checking Nexpose fingerprint.. ")
|
23
|
+
if vault_options[:nexpose_os].downcase.include? 'windows'
|
24
|
+
@log.log_debug_message('Nexpose fingerprinting estimates the system is Windows.')
|
25
|
+
asset_data[:service] = Credential::Service::CIFS
|
26
|
+
else
|
27
|
+
@log.log_debug_message('Nexpose fingerprinting estimates the system is Unix based.')
|
28
|
+
asset_data[:service] = Credential::Service::SSH
|
29
|
+
asset_data[:p_e_user] = 'root'
|
30
|
+
asset_data[:p_e_type] = Nexpose::Credential::ElevationType::SUDO
|
31
|
+
end
|
32
|
+
end
|
33
|
+
@log.log_debug_message('Policy ID indicates the system is Windows based.')
|
34
|
+
asset_data[:service] = Credential::Service::CIFS
|
20
35
|
else
|
21
|
-
|
36
|
+
@log.log_debug_message('Policy ID indicates the system is Unix based.')
|
37
|
+
asset_data[:service] = Credential::Service::SSH
|
38
|
+
asset_data[:p_e_user] = 'root'
|
39
|
+
asset_data[:p_e_type] = Nexpose::Credential::ElevationType::SUDO
|
22
40
|
end
|
23
41
|
asset_data[:password] = password_result.get_content
|
42
|
+
asset_data[:user] = password_result.get_user_name
|
24
43
|
rescue Exception => e
|
25
|
-
|
44
|
+
@log.log_debug_message("Error fetching credential for address <#{vault_options[:address]}>. Error was <#{e}>")
|
26
45
|
end
|
27
46
|
asset_data
|
28
47
|
end
|
metadata
CHANGED
@@ -1,93 +1,107 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: nexpose_cyberark
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.5
|
5
5
|
platform: java
|
6
6
|
authors:
|
7
7
|
- Damian Finol
|
8
|
+
- JJ Cassidy
|
8
9
|
autorequire:
|
9
10
|
bindir: bin
|
10
11
|
cert_chain: []
|
11
|
-
date: 2015-
|
12
|
+
date: 2015-12-11 00:00:00.000000000 Z
|
12
13
|
dependencies:
|
13
14
|
- !ruby/object:Gem::Dependency
|
14
15
|
name: bundler
|
15
16
|
requirement: !ruby/object:Gem::Requirement
|
16
17
|
requirements:
|
17
|
-
- -
|
18
|
+
- - ~>
|
18
19
|
- !ruby/object:Gem::Version
|
19
20
|
version: '1.6'
|
20
21
|
type: :development
|
21
22
|
prerelease: false
|
22
23
|
version_requirements: !ruby/object:Gem::Requirement
|
23
24
|
requirements:
|
24
|
-
- -
|
25
|
+
- - ~>
|
25
26
|
- !ruby/object:Gem::Version
|
26
27
|
version: '1.6'
|
27
28
|
- !ruby/object:Gem::Dependency
|
28
29
|
name: rake
|
29
30
|
requirement: !ruby/object:Gem::Requirement
|
30
31
|
requirements:
|
31
|
-
- -
|
32
|
+
- - ~>
|
32
33
|
- !ruby/object:Gem::Version
|
33
|
-
version: '
|
34
|
+
version: '10.4'
|
34
35
|
type: :development
|
35
36
|
prerelease: false
|
36
37
|
version_requirements: !ruby/object:Gem::Requirement
|
37
38
|
requirements:
|
38
|
-
- -
|
39
|
+
- - ~>
|
39
40
|
- !ruby/object:Gem::Version
|
40
|
-
version: '
|
41
|
+
version: '10.4'
|
41
42
|
- !ruby/object:Gem::Dependency
|
42
43
|
name: rspec
|
43
44
|
requirement: !ruby/object:Gem::Requirement
|
44
45
|
requirements:
|
45
|
-
- -
|
46
|
+
- - ~>
|
46
47
|
- !ruby/object:Gem::Version
|
47
48
|
version: '2.1'
|
48
49
|
type: :development
|
49
50
|
prerelease: false
|
50
51
|
version_requirements: !ruby/object:Gem::Requirement
|
51
52
|
requirements:
|
52
|
-
- -
|
53
|
+
- - ~>
|
53
54
|
- !ruby/object:Gem::Version
|
54
55
|
version: '2.1'
|
55
56
|
- !ruby/object:Gem::Dependency
|
56
57
|
name: nexpose
|
57
58
|
requirement: !ruby/object:Gem::Requirement
|
58
59
|
requirements:
|
59
|
-
- -
|
60
|
+
- - ~>
|
60
61
|
- !ruby/object:Gem::Version
|
61
|
-
version:
|
62
|
+
version: '2.1'
|
63
|
+
type: :runtime
|
64
|
+
prerelease: false
|
65
|
+
version_requirements: !ruby/object:Gem::Requirement
|
66
|
+
requirements:
|
67
|
+
- - ~>
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '2.1'
|
70
|
+
- !ruby/object:Gem::Dependency
|
71
|
+
name: waitutil
|
72
|
+
requirement: !ruby/object:Gem::Requirement
|
73
|
+
requirements:
|
74
|
+
- - ~>
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: '0.2'
|
62
77
|
type: :runtime
|
63
78
|
prerelease: false
|
64
79
|
version_requirements: !ruby/object:Gem::Requirement
|
65
80
|
requirements:
|
66
|
-
- -
|
81
|
+
- - ~>
|
67
82
|
- !ruby/object:Gem::Version
|
68
|
-
version: 0.
|
83
|
+
version: '0.2'
|
69
84
|
description: Nexpose Cyberark integration provides credentials for authenticated scans
|
70
85
|
in Nexpose.
|
71
86
|
email:
|
72
|
-
-
|
87
|
+
- integrations_support@rapid7.com
|
73
88
|
executables:
|
74
89
|
- nx_cyberark.rb
|
75
90
|
extensions: []
|
76
91
|
extra_rdoc_files: []
|
77
92
|
files:
|
78
93
|
- Gemfile
|
79
|
-
- Gemfile.lock
|
80
94
|
- LICENSE.txt
|
81
95
|
- README.md
|
82
96
|
- Rakefile
|
83
97
|
- bin/nx_cyberark.rb
|
84
98
|
- lib/nexpose_cyberark.rb
|
99
|
+
- lib/nexpose_cyberark/config/nexpose_cyberark.config
|
85
100
|
- lib/nexpose_cyberark/lib/java/JavaPasswordSDK.jar
|
86
101
|
- lib/nexpose_cyberark/nexpose_ops.rb
|
102
|
+
- lib/nexpose_cyberark/nx_logger.rb
|
87
103
|
- lib/nexpose_cyberark/password_ops.rb
|
88
104
|
- lib/nexpose_cyberark/version.rb
|
89
|
-
- nexpose_cyberark-0.0.3-java.gem
|
90
|
-
- nexpose_cyberark.gemspec
|
91
105
|
homepage: http://www.rapid7.com/
|
92
106
|
licenses:
|
93
107
|
- MIT
|
@@ -98,17 +112,17 @@ require_paths:
|
|
98
112
|
- lib
|
99
113
|
required_ruby_version: !ruby/object:Gem::Requirement
|
100
114
|
requirements:
|
101
|
-
- -
|
115
|
+
- - ! '>='
|
102
116
|
- !ruby/object:Gem::Version
|
103
117
|
version: '0'
|
104
118
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
105
119
|
requirements:
|
106
|
-
- -
|
120
|
+
- - ! '>='
|
107
121
|
- !ruby/object:Gem::Version
|
108
122
|
version: '0'
|
109
123
|
requirements: []
|
110
124
|
rubyforge_project:
|
111
|
-
rubygems_version: 2.
|
125
|
+
rubygems_version: 2.2.2
|
112
126
|
signing_key:
|
113
127
|
specification_version: 4
|
114
128
|
summary: Nexpose Cyberark integration.
|
data/Gemfile.lock
DELETED
@@ -1,30 +0,0 @@
|
|
1
|
-
PATH
|
2
|
-
remote: .
|
3
|
-
specs:
|
4
|
-
nexpose_cyberark (0.0.2-java)
|
5
|
-
nexpose (~> 0.6.0)
|
6
|
-
|
7
|
-
GEM
|
8
|
-
remote: https://rubygems.org/
|
9
|
-
specs:
|
10
|
-
diff-lcs (1.2.5)
|
11
|
-
librex (0.0.999)
|
12
|
-
nexpose (0.6.5)
|
13
|
-
librex (~> 0.0, >= 0.0.68)
|
14
|
-
rex (~> 1.0, >= 1.0.2)
|
15
|
-
rex (1.0.2)
|
16
|
-
rspec (2.99.0)
|
17
|
-
rspec-core (~> 2.99.0)
|
18
|
-
rspec-expectations (~> 2.99.0)
|
19
|
-
rspec-mocks (~> 2.99.0)
|
20
|
-
rspec-core (2.99.2)
|
21
|
-
rspec-expectations (2.99.2)
|
22
|
-
diff-lcs (>= 1.1.3, < 2.0)
|
23
|
-
rspec-mocks (2.99.3)
|
24
|
-
|
25
|
-
PLATFORMS
|
26
|
-
java
|
27
|
-
|
28
|
-
DEPENDENCIES
|
29
|
-
nexpose_cyberark!
|
30
|
-
rspec (~> 2.1)
|
Binary file
|
data/nexpose_cyberark.gemspec
DELETED
@@ -1,27 +0,0 @@
|
|
1
|
-
# coding: utf-8
|
2
|
-
lib = File.expand_path('../lib', __FILE__)
|
3
|
-
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
-
require 'nexpose_cyberark/version'
|
5
|
-
|
6
|
-
Gem::Specification.new do |spec|
|
7
|
-
spec.name = 'nexpose_cyberark'
|
8
|
-
spec.version = NexposeCyberark::VERSION
|
9
|
-
spec.authors = ['Damian Finol']
|
10
|
-
spec.email = ['damian_finol@rapid7.com']
|
11
|
-
spec.summary = %q{Nexpose Cyberark integration.}
|
12
|
-
spec.description = %q{Nexpose Cyberark integration provides credentials for authenticated scans in Nexpose.}
|
13
|
-
spec.homepage = 'http://www.rapid7.com/'
|
14
|
-
spec.license = 'MIT'
|
15
|
-
|
16
|
-
spec.files = Dir['[A-Z]*'] + Dir['lib/**/*'] + Dir['bin/**']
|
17
|
-
spec.files.reject! { |fn| fn.include? "CVS" }
|
18
|
-
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
19
|
-
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
20
|
-
spec.require_paths = ['lib']
|
21
|
-
spec.platform = 'java'
|
22
|
-
|
23
|
-
spec.add_development_dependency 'bundler', '~> 1.6'
|
24
|
-
spec.add_development_dependency 'rake'
|
25
|
-
spec.add_development_dependency 'rspec', '~> 2.1'
|
26
|
-
spec.add_runtime_dependency('nexpose', '~> 0.6.0')
|
27
|
-
end
|