nexpose_thycotic 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 72d72a2f1294229464df29985e8c3a0145f18d60
4
- data.tar.gz: 13e76a6eaeb8f69879b2a808251f9ad2a4bb2716
3
+ metadata.gz: def1949052dd9744a7b1803cf32153ca840c3e04
4
+ data.tar.gz: 616f386251fa0515e8d0df1a4b6a67b88eaa7a0b
5
5
  SHA512:
6
- metadata.gz: 0b6f9d979b6064ca1eadfa76d0026461057701110fcddb172bde51429412a97589e4de7b4e1c96bab5b0f5e760337620f955f4d95b7294d1b5d03fbc6d606bef
7
- data.tar.gz: c9f6ab9b22517feb3035b214492ab2cd7fffa1b0a3b5a4e2c4ef1823ed9e7e41113fa20d7cf25f2db5600346acc2b94b93a4f6911fbef3579d2bc1ca0349e635
6
+ metadata.gz: cb7b619bf897b98bc5910ee284711366dc3be9cbe4dde8653c9e23fc981d9373cd54c0fb96bf43f04ece34f5e2fd76f1bccafdeddc9979fe73a8044996686ce6
7
+ data.tar.gz: 4922dcd8ee3930d85c0ddffb277fb38251c943bce86d906b6e54093e8b14cbfe9090a7357b13786fb98f2707ccbf64cbfe56273099b77ab89185ab660fee3b57
data/README.md CHANGED
@@ -63,6 +63,11 @@ The gem is available as open source under the terms of the [MIT License](http://
63
63
 
64
64
  ## Changelog
65
65
 
66
+ ### 0.2.0
67
+ - User may configure the gem to delete or preserve existing credentials.
68
+ - A customisable comment is now attached to password retrieval requests.
69
+ - Multiple credentials for the same address may be imported.
70
+
66
71
  ### 0.0.7
67
72
  - User now has the option to configure the gem using a configuration file as well as with environment variables. Nexpose and Thycotic options have been added to the configuration file.
68
73
  - Added an encryption configuration file. Usernames and passwords within the configuration file are now encrypted when the application runs.
@@ -24,15 +24,18 @@ options = settings[:options]
24
24
 
25
25
  vault_options = { url: thycotic_options[:thycotic_url],
26
26
  username: thycotic_options[:thycotic_username],
27
- password: thycotic_options[:thycotic_password] }
27
+ password: thycotic_options[:thycotic_password],
28
+ comment: thycotic_options[:comment] }
28
29
 
29
30
  nexpose_options = { nexpose_ip: nexpose_options[:nexpose_url],
30
31
  nexpose_username: nexpose_options[:nexpose_username],
31
32
  nexpose_password: nexpose_options[:nexpose_password],
32
33
  nexpose_port: nexpose_options[:nexpose_port],
33
34
  sites: options[:sites],
35
+ clear_creds: options[:clear_creds],
34
36
  logging_enabled: options[:logging_enabled],
35
37
  log_level: options[:log_level],
36
- log_console: options[:log_console] }
38
+ log_console: options[:log_console] }
39
+
37
40
  NexposeThycotic.update_credentials(vault_options, nexpose_options)
38
41
 
@@ -3,55 +3,86 @@ require 'nexpose_thycotic/operations'
3
3
  require 'nexpose_thycotic/utilities/nx_logger'
4
4
  require 'nexpose'
5
5
 
6
- module NexposeThycotic
6
+ module NexposeThycotic
7
7
  def self.update_credentials(vault_options, nexpose_options = nil)
8
+ #TODO: Should we add the logging alias' from the SCCM gem?
8
9
  log = NexposeThycotic::NxLogger.instance
9
- log.setup_statistics_collection(NexposeThycotic::VENDOR,
10
- NexposeThycotic::PRODUCT,
10
+ log.setup_statistics_collection(NexposeThycotic::VENDOR,
11
+ NexposeThycotic::PRODUCT,
11
12
  NexposeThycotic::VERSION)
12
- log.setup_logging(true, nexpose_options[:log_level], nexpose_options[:log_console])
13
- log.log_message('Starting integration.')
14
-
15
- ss = ThycoticOperations.new(vault_options[:url])
16
- log.log_message("Logging to Thycotic at #{vault_options[:url]}")
13
+ log.setup_logging(true,
14
+ nexpose_options[:log_level],
15
+ nexpose_options[:log_console])
16
+ log.info('Starting integration.')
17
+
18
+ ss = ThycoticOperations.new(vault_options[:url], vault_options[:comment])
19
+ log.info("Logging into Thycotic at #{vault_options[:url]}")
17
20
  token = ss.authenticate(vault_options[:username], vault_options[:password])
18
-
19
- @nx = NexposeOperations.new(nexpose_options[:nexpose_ip],
20
- nexpose_options[:nexpose_username],
21
- nexpose_options[:nexpose_password],
21
+
22
+ @nx = NexposeOperations.new(nexpose_options[:nexpose_ip],
23
+ nexpose_options[:nexpose_username],
24
+ nexpose_options[:nexpose_password],
22
25
  nexpose_options[:nexpose_port])
23
- log.log_message('Processing sites')
26
+ log.info('Processing sites')
24
27
 
25
28
  nexpose_options[:sites].each do |site_id|
26
29
  log.log_debug_message("Processing site #{site_id}")
27
- ips = @nx.get_ips_from_site(site_id)
30
+ addresses = @nx.get_device_addresses(site_id)
31
+
28
32
  site_credentials = []
33
+ if nexpose_options[:clear_creds]
34
+ log.debug('Clearing existing credentials.')
35
+ else
36
+ log.debug('Preserving existing credentials.')
37
+ site_credentials = @nx.get_existing_credentials(site_id)
38
+ end
39
+
40
+ addresses.each do |addr|
41
+ log.debug("Getting credentials for #{addr}")
42
+ summaries = ss.get_secret_summaries(token, addr)
43
+ next if summaries.empty?
44
+
45
+ log.debug("Discovered #{summaries.count} credentials for #{addr}")
46
+
47
+ summaries.each do |summary|
48
+ cred = self.create_credential(ss, token, summary, addr)
29
49
 
30
- ips.each do |ip|
31
- ip_from = ip.instance_of?(Nexpose::HostName) ? ip.host : ip.from
32
- log.log_debug_message("Getting credentials for #{ip_from}")
33
- secret_summary = ss.get_secret_id(token, ip_from)
34
- unless secret_summary.nil?
35
- log.log_debug_message("Found credentials for #{ip_from}")
36
- # Gets OS
37
- asset_data = {}
38
- asset_data[:ip] = ip_from
39
- res = ss.get_secret_simple(token, secret_summary[:secret_id])
40
- asset_data[:username] = res[:username]
41
- asset_data[:password] = res[:password]
42
- credential = Nexpose::Credential.for_service(ss.check_type(secret_summary[:secret_type]),
43
- asset_data[:username],
44
- asset_data[:password],
45
- nil,
46
- ip.from)
47
- site_credentials.push(credential)
50
+ site_credentials.reject! do |c|
51
+ c.host_restriction == cred.host_restriction &&
52
+ c.user_name == cred.user_name && c.service == cred.service
53
+ end
54
+
55
+ site_credentials.push(cred)
48
56
  end
49
57
  end
58
+
50
59
  @nx.save_site(site_id, site_credentials)
51
- log.log_message("Finished processing #{site_id}")
60
+ log.info("Finished processing #{site_id}")
52
61
  end
53
62
  end
54
63
 
64
+ def self.create_credential(connection, token, secret_summary, address)
65
+ # Get the kind of credential e.g. ssh, cifs
66
+ service = connection.check_type(secret_summary[:secret_type])
67
+
68
+ # Define the credential's base properties
69
+ cred_name = secret_summary[:secret_name]
70
+ cred_desc = "Thycotic imported Credential for #{address}"
71
+
72
+ credential = Nexpose::SiteCredentials.for_service(cred_name,
73
+ -1,
74
+ cred_desc,
75
+ address,
76
+ nil,
77
+ service)
78
+ # Retrieve and store the credentials
79
+ res = connection.get_secret(token, secret_summary[:secret_id])
80
+ credential.user_name = res[:username]
81
+ credential.password = res[:password]
82
+
83
+ credential
84
+ end
85
+
55
86
  def self.set_variables(options)
56
87
  settings = {}
57
88
  options.each_key do |key|
@@ -13,6 +13,8 @@
13
13
  # Filters to specific sites one per line, leave empty to generate for all sites.
14
14
  :sites:
15
15
  - '1'
16
+ # Delete existing credentials for the site before importing
17
+ :clear_creds: true
16
18
  :nexpose_options:
17
19
  # (M) Nexpose IP address
18
20
  :nexpose_url: 127.0.0.1
@@ -29,6 +31,8 @@
29
31
  :thycotic_username: user
30
32
  # (M) The password for the above user
31
33
  :thycotic_password: password
34
+ # (M) The comment used when retrieving each password
35
+ :comment: 'Retrieved via Thycotic gem.'
32
36
  :encryption_options:
33
37
  # (M) Path to the encryption.config file
34
38
  :directory: ../../config/encryption.config
@@ -1,3 +1,6 @@
1
+ require 'nexpose_thycotic/utilities/nx_logger'
2
+ require 'set'
3
+
1
4
  module NexposeThycotic
2
5
  class NexposeOperations
3
6
  require 'nexpose'
@@ -7,28 +10,58 @@ module NexposeThycotic
7
10
  def initialize(ip, user, pass, port = 3780)
8
11
  @nsc = Connection.new(ip, user, pass, port)
9
12
  @nsc.login
10
- NexposeThycotic::NxLogger.instance.on_connect(ip, port, @nsc.session_id, "{}")
13
+
14
+ @logger = NexposeThycotic::NxLogger.instance
15
+ @logger.on_connect(ip, port, @nsc.session_id, '{}')
16
+ end
17
+
18
+ def get_site(site_id)
19
+ Site.load(@nsc, site_id)
20
+ end
21
+
22
+ def get_devices_from_site(site_id)
23
+ @nsc.list_devices(site_id)
24
+ end
25
+
26
+ def get_device_addresses(site_id)
27
+ site = get_site(site_id)
28
+ included_targets = site.included_addresses
29
+
30
+ # Get a list of known IP addresses from a site
31
+ devices = get_devices_from_site(site_id)
32
+ device_ips = devices.map { |d| d.address }
33
+
34
+ hosts = []
35
+ ips = device_ips.to_set
36
+ included_targets.each do |address|
37
+ if address.instance_of?(Nexpose::HostName)
38
+ hosts << address.host
39
+ elsif address.to.nil?
40
+ ips.add(address.from)
41
+ end
42
+ end
43
+
44
+ @logger.debug("Discovered #{hosts.count} hosts and #{ips.count} IPs.")
45
+ ips.to_a + hosts
11
46
  end
12
47
 
13
- def get_ips_from_site(site_id)
14
- site = Site.load(@nsc, site_id)
15
- site.assets
48
+ def get_existing_credentials(site_id)
49
+ site = get_site(site_id)
50
+ site.site_credentials
16
51
  end
17
52
 
18
53
  def save_site(site_id, credentials)
19
- site = Site.load(@nsc, site_id)
20
- site.credentials = credentials
54
+ site = get_site(site_id)
55
+ site.site_credentials = credentials
21
56
  site.save(@nsc)
22
57
  end
23
58
 
24
59
  def delete_site_credentials(site_id)
25
- site = Site.load(@nsc, site_id)
26
- site.credentials = []
27
- site.save(@nsc)
60
+ save_site(site_id, [])
28
61
  end
29
62
 
30
63
  def start_scan(site_id)
31
- site = Site.load(@nsc, site_id)
64
+ site = get_site(site_id)
32
65
  site.scan(@nsc)
33
66
  end
34
67
 
@@ -40,11 +73,12 @@ module NexposeThycotic
40
73
  class ThycoticOperations
41
74
  require 'savon'
42
75
  attr_accessor :client
43
- def initialize(url = nil)
76
+ def initialize(url = nil, comment = '')
44
77
  # log: true, log_level: :info
45
- @client = Savon.client(
46
- wsdl: url,
47
- ssl_verify_mode: :none)
78
+ @client = Savon.client(wsdl: url, ssl_verify_mode: :none)
79
+
80
+ # Comment used when retrieving passwords
81
+ @comment = comment
48
82
  end
49
83
 
50
84
  def operations
@@ -70,67 +104,83 @@ module NexposeThycotic
70
104
  end
71
105
 
72
106
  def authenticate(username, password)
73
- auth = @client.call(:authenticate, message: { username: username, password: password })
74
- auth_response = auth.hash
75
- auth_result = auth_response[:envelope][:body][:authenticate_response][:authenticate_result]
76
- CheckForErrors(auth_result)
107
+ operation = :authenticate
108
+ message = { username: username, password: password }
109
+ auth_result = get_secret_result(operation, message)
110
+ check_for_errors(auth_result)
77
111
  @token = auth_result[:token]
78
112
  end
79
113
 
80
- def parse_field(secret_response_result, fieldName)
81
- items = secret_response_result[:secret][:items]
82
- for item in items[:secret_item]
83
- if item[:field_display_name].downcase == fieldName.downcase then
84
- return item[:value]
85
- end
86
- end
114
+ def parse_field(secret_response_result, field_name)
115
+ items = secret_response_result[:secret][:items][:secret_item]
116
+ item = items.find { |i| i[:field_display_name].casecmp(field_name) == 0 }
117
+ item[:value]
87
118
  end
88
119
 
89
- def get_secret_id(token, ip)
90
- secret_id = @client.call( :search_secrets_by_field_value, message: { token: token, fieldName: 'machine', searchTerm: ip, showDeleted: true, showRestricted: true})
91
- secret_result = secret_id.hash
92
- unless secret_result[:envelope][:body][:search_secrets_by_field_value_response][:search_secrets_by_field_value_result][:secret_summaries].nil? then
93
- secret_summary = secret_result[:envelope][:body][:search_secrets_by_field_value_response][:search_secrets_by_field_value_result][:secret_summaries][:secret_summary]
94
- secret_info = { secret_id: secret_summary[:secret_id], secret_type: secret_summary[:secret_type_name] }
95
- end
120
+ def get_secret_result(operation, message)
121
+ secret = @client.call(operation, message: message)
122
+ resp = secret.hash[:envelope][:body]["#{operation}_response".to_sym]
123
+ resp["#{operation}_result".to_sym]
96
124
  end
97
125
 
98
- def get_secret_simple(token, secret_id)
99
- secret = @client.call( :get_secret_legacy, message: { token: token, secretId: secret_id } )
100
- secret_response = secret.hash
101
- secret_response_result = secret_response[:envelope][:body][:get_secret_legacy_response][:get_secret_legacy_result]
102
- CheckForErrors(secret_response_result)
103
- username = parse_field(secret_response_result, "Username")
104
- password = parse_field(secret_response_result, "Password")
105
- asset = { username: username, password: password }
126
+ def get_secret_summaries(token, ip)
127
+ operation = :search_secrets_by_field_value
128
+ message = {
129
+ token: token,
130
+ fieldName: 'machine',
131
+ searchTerm: ip,
132
+ showDeleted: true,
133
+ showRestricted: true
134
+ }
135
+ secret_result = get_secret_result(operation, message)
136
+
137
+ secrets = []
138
+ unless secret_result[:secret_summaries].nil?
139
+ summaries = secret_result[:secret_summaries][:secret_summary]
140
+
141
+ # Ensure summaries is iterable
142
+ summaries = [summaries] if summaries.is_a?(Hash)
143
+
144
+ summaries.each do |secret|
145
+ secret_info = {
146
+ secret_id: secret[:secret_id],
147
+ secret_type: secret[:secret_type_name],
148
+ secret_name: secret[:secret_name]
149
+ }
150
+ secrets << secret_info
151
+ end
152
+ end
106
153
 
154
+ secrets
107
155
  end
108
156
 
109
157
  def get_secret(token, secret_id)
110
- #Code Responses are used if additional information is required when getting the Secret (Ex: Requires Comment is turned on so need to pass Code Response as..)
111
- code_response = nil
112
- #code_response = @client.new("codeResponse") #Code is not working, need to create object of type codeResponse from the wsdl
113
- #code_response.ErrorCode = "COMMENT";
114
- #code_response.Comment = "Running scan";
115
-
116
- #CodeResponses is an array because there could be multiple additional responses needed
117
- code_responses = [code_response]
118
-
119
- secret = @client.call( :get_secret, message: { token: token, secretId: secret_id, CodeResponse: code_responses } )
120
- secret_response = secret.hash
121
- secret_response_result = secret_response[:envelope][:body][:get_secret_response][:get_secret_result]
122
- CheckForErrors(secret_response_result)
123
- username = parse_field(secret_response_result, "Username")
124
- password = parse_field(secret_response_result, "Password")
158
+ operation = :get_secret
159
+ message = { token: token,
160
+ secretId: secret_id,
161
+ loadSettingsAndPermissions: false,
162
+ "codeResponses" => {"CodeResponse" =>
163
+ [{
164
+ "ErrorCode" => "COMMENT",
165
+ "Comment" => @comment
166
+ }]
167
+ }}
168
+ secret_result = get_secret_result(operation, message)
169
+
170
+ check_for_errors(secret_result)
171
+ username = parse_field(secret_result, 'Username')
172
+ password = parse_field(secret_result, 'Password')
173
+
174
+ { username: username, password: password }
125
175
  end
126
176
 
127
- def CheckForErrors(result)
177
+ def check_for_errors(result)
128
178
  errors = result[:errors]
129
- if !errors.blank? then
179
+ unless errors.blank?
130
180
  puts errors
181
+ #TODO: Logging
131
182
  raise Exception.new(errors)
132
183
  end
133
-
134
184
  end
135
185
  end
136
- end
186
+ end
@@ -31,7 +31,11 @@ module NexposeThycotic
31
31
  end
32
32
  end
33
33
 
34
- def setup_logging(enabled, log_level = 'info', stdout=false)
34
+ def setup_logging(enabled,
35
+ log_level = 'info',
36
+ stdout=false,
37
+ rotation='weekly',
38
+ log_size=nil)
35
39
  @stdout = stdout
36
40
 
37
41
  log_message('Logging disabled.') unless enabled || @log.nil?
@@ -46,9 +50,9 @@ module NexposeThycotic
46
50
  io = IO.for_fd(IO.sysopen(@logger_file, 'a'), 'a')
47
51
  io.autoclose = false
48
52
  io.sync = true
49
- @log = Logger.new(io, 'weekly')
50
- @log.level = if log_level.to_s.casecmp('info') == 0
51
- Logger::INFO
53
+ @log = Logger.new(io, rotation, log_size)
54
+ @log.level = if log_level.to_s.casecmp('info') == 0
55
+ Logger::INFO
52
56
  else
53
57
  Logger::DEBUG
54
58
  end
@@ -58,11 +62,12 @@ module NexposeThycotic
58
62
  def create_calls
59
63
  levels = [:info, :debug, :error, :warn]
60
64
  levels.each do |level|
61
- method_name =
62
- define_singleton_method("log_#{level.to_s}_message") do |message|
65
+ method_name = "log_#{level.to_s}_message"
66
+ define_singleton_method(method_name) do |message|
63
67
  puts message if @stdout
64
68
  @log.send(level, message) unless !@enabled || @log.nil?
65
69
  end
70
+ singleton_class.send(:alias_method, level, method_name.to_sym)
66
71
  end
67
72
  end
68
73
 
@@ -93,8 +98,8 @@ module NexposeThycotic
93
98
  end
94
99
 
95
100
  def get_product(product, version)
96
- return nil if ((product.nil? || product.empty?) ||
97
- (version.nil? || version.empty?))
101
+ return nil if ((product.nil? || product.empty?) ||
102
+ (version.nil? || version.empty?))
98
103
 
99
104
  product.gsub!('-', '_')
100
105
  product.slice! product.rindex('_') until product.count('_') <= 1
@@ -148,7 +153,7 @@ module NexposeThycotic
148
153
  log_stat_message('Statistics collection not enabled.')
149
154
  return
150
155
  end
151
-
156
+
152
157
  begin
153
158
  payload = generate_payload value
154
159
  send(nexpose_address, nexpose_port, session_id, payload)
@@ -1,5 +1,5 @@
1
1
  module NexposeThycotic
2
- VERSION = "0.1.0"
2
+ VERSION = "0.2.0"
3
3
  VENDOR = "Thycotic"
4
4
  PRODUCT = "Secret Server"
5
5
  def self.show_version
metadata CHANGED
@@ -1,43 +1,16 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: nexpose_thycotic
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Damian Finol, Adam Robinson
8
+ - David Valente
8
9
  autorequire:
9
10
  bindir: bin
10
11
  cert_chain: []
11
- date: 2017-05-30 00:00:00.000000000 Z
12
+ date: 2018-04-06 00:00:00.000000000 Z
12
13
  dependencies:
13
- - !ruby/object:Gem::Dependency
14
- name: bundler
15
- requirement: !ruby/object:Gem::Requirement
16
- requirements:
17
- - - "~>"
18
- - !ruby/object:Gem::Version
19
- version: '1.5'
20
- type: :development
21
- prerelease: false
22
- version_requirements: !ruby/object:Gem::Requirement
23
- requirements:
24
- - - "~>"
25
- - !ruby/object:Gem::Version
26
- version: '1.5'
27
- - !ruby/object:Gem::Dependency
28
- name: rake
29
- requirement: !ruby/object:Gem::Requirement
30
- requirements:
31
- - - ">="
32
- - !ruby/object:Gem::Version
33
- version: '0'
34
- type: :development
35
- prerelease: false
36
- version_requirements: !ruby/object:Gem::Requirement
37
- requirements:
38
- - - ">="
39
- - !ruby/object:Gem::Version
40
- version: '0'
41
14
  - !ruby/object:Gem::Dependency
42
15
  name: savon
43
16
  requirement: !ruby/object:Gem::Requirement
@@ -52,34 +25,20 @@ dependencies:
52
25
  - - ">="
53
26
  - !ruby/object:Gem::Version
54
27
  version: '0'
55
- - !ruby/object:Gem::Dependency
56
- name: rubyntlm
57
- requirement: !ruby/object:Gem::Requirement
58
- requirements:
59
- - - ">="
60
- - !ruby/object:Gem::Version
61
- version: '0'
62
- type: :runtime
63
- prerelease: false
64
- version_requirements: !ruby/object:Gem::Requirement
65
- requirements:
66
- - - ">="
67
- - !ruby/object:Gem::Version
68
- version: '0'
69
28
  - !ruby/object:Gem::Dependency
70
29
  name: nexpose
71
30
  requirement: !ruby/object:Gem::Requirement
72
31
  requirements:
73
32
  - - "~>"
74
33
  - !ruby/object:Gem::Version
75
- version: '0.9'
34
+ version: '7.2'
76
35
  type: :runtime
77
36
  prerelease: false
78
37
  version_requirements: !ruby/object:Gem::Requirement
79
38
  requirements:
80
39
  - - "~>"
81
40
  - !ruby/object:Gem::Version
82
- version: '0.9'
41
+ version: '7.2'
83
42
  - !ruby/object:Gem::Dependency
84
43
  name: symmetric-encryption
85
44
  requirement: !ruby/object:Gem::Requirement
@@ -143,7 +102,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
143
102
  version: '0'
144
103
  requirements: []
145
104
  rubyforge_project:
146
- rubygems_version: 2.5.1
105
+ rubygems_version: 2.6.14
147
106
  signing_key:
148
107
  specification_version: 4
149
108
  summary: Nexpose Thycotic Gem Integration