nexpose_thycotic 0.0.4 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile.lock +1 -3
- data/README.md +51 -4
- data/bin/nexpose_thycotic +38 -0
- data/lib/nexpose_thycotic.rb +55 -26
- data/lib/nexpose_thycotic/config/encryption.config +21 -0
- data/lib/nexpose_thycotic/config/nexpose_thycotic.config +34 -0
- data/lib/nexpose_thycotic/operations.rb +5 -1
- data/lib/nexpose_thycotic/utilities/config_parser.rb +141 -0
- data/lib/nexpose_thycotic/utilities/gem_options.rb +91 -0
- data/lib/nexpose_thycotic/utilities/nx_logger.rb +166 -0
- data/lib/nexpose_thycotic/version.rb +3 -1
- metadata +35 -13
- data/bin/nx_thycotic.rb +0 -31
- data/lib/nexpose_thycotic/nx_logger.rb +0 -19
- data/nexpose_thycotic-0.0.3.gem +0 -0
- data/nexpose_thycotic.gemspec +0 -27
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 72d72a2f1294229464df29985e8c3a0145f18d60
|
4
|
+
data.tar.gz: 13e76a6eaeb8f69879b2a808251f9ad2a4bb2716
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0b6f9d979b6064ca1eadfa76d0026461057701110fcddb172bde51429412a97589e4de7b4e1c96bab5b0f5e760337620f955f4d95b7294d1b5d03fbc6d606bef
|
7
|
+
data.tar.gz: c9f6ab9b22517feb3035b214492ab2cd7fffa1b0a3b5a4e2c4ef1823ed9e7e41113fa20d7cf25f2db5600346acc2b94b93a4f6911fbef3579d2bc1ca0349e635
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
nexpose_thycotic (0.0.
|
4
|
+
nexpose_thycotic (0.0.5)
|
5
5
|
nexpose (~> 0.8.0)
|
6
6
|
rubyntlm
|
7
7
|
savon
|
@@ -25,8 +25,6 @@ GEM
|
|
25
25
|
rex (~> 2.0.4, >= 2.0.4)
|
26
26
|
nokogiri (1.6.6.2)
|
27
27
|
mini_portile (~> 0.6.0)
|
28
|
-
nokogiri (1.6.6.2-x86-mingw32)
|
29
|
-
mini_portile (~> 0.6.0)
|
30
28
|
nori (2.6.0)
|
31
29
|
rack (1.6.1)
|
32
30
|
rake (10.4.2)
|
data/README.md
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
# NexposeThycotic
|
2
2
|
|
3
|
-
Nexpose Thycotic Gem allows users to import credentials from Thycotic SecretServer into their Nexpose instance.
|
3
|
+
Nexpose Thycotic Gem allows users to import credentials from Thycotic SecretServer into their Nexpose instance.
|
4
|
+
|
5
|
+
For assistance with using the gem, documentation, or issues, please email the Rapid7 support team at support@rapid7.com, including description of issues and log files.
|
4
6
|
|
5
7
|
## Installation
|
6
8
|
|
@@ -18,6 +20,51 @@ Or install it yourself as:
|
|
18
20
|
|
19
21
|
## Usage
|
20
22
|
|
21
|
-
Edit the
|
22
|
-
and
|
23
|
-
|
23
|
+
Edit the nexpose_thycotic.config file in the */lib/nexpose\_thycotic/config* folder with a text editor.
|
24
|
+
Nexpose and Thycotic configuration options can be set in this file, or as Environment Variables.
|
25
|
+
|
26
|
+
- Add a proper username/password for both Nexpose and SecretServer.
|
27
|
+
- Add the URL for SecretServer webservice and URL for Nexpose, with optional port (defaults to 3780).
|
28
|
+
- The logging level can also be modified.
|
29
|
+
- Add the Site ID(s) to be managed by this integration, save and run on schedule.
|
30
|
+
|
31
|
+
Run the following command from inside the bin folder:
|
32
|
+
|
33
|
+
nexpose_thycotic
|
34
|
+
|
35
|
+
## Encryption Settings
|
36
|
+
|
37
|
+
The usernames and passwords within the configuration files are automatically encrypted when the integration runs. The key and IV files used during encryption/decryption are saved within the config folder by default.
|
38
|
+
|
39
|
+
#### Setting Custom Locations for Encryption Files
|
40
|
+
|
41
|
+
To set custom locations for the key and IV files, update the following values within the encryption.config file:
|
42
|
+
|
43
|
+
- key_filename - The absolute path to where the key file will be created.
|
44
|
+
- iv_file - The absolute path to where the IV file will be created.
|
45
|
+
|
46
|
+
To set a custom path after the integration has already executed, the files must be moved to the new location manually.
|
47
|
+
|
48
|
+
#### Encrypting the Configuration without running the Integration
|
49
|
+
The Nexpose Thycotic integration can encrypt its configuration file without running the gem. This allows users to secure their login information for future use e.g for use in a cron-schedule.
|
50
|
+
|
51
|
+
The command to do so is:
|
52
|
+
```
|
53
|
+
nexpose_thycotic -e
|
54
|
+
```
|
55
|
+
or
|
56
|
+
```
|
57
|
+
nexpose_thycotic --encrypt_config
|
58
|
+
```
|
59
|
+
|
60
|
+
## License
|
61
|
+
|
62
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
63
|
+
|
64
|
+
## Changelog
|
65
|
+
|
66
|
+
### 0.0.7
|
67
|
+
- 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
|
+
- Added an encryption configuration file. Usernames and passwords within the configuration file are now encrypted when the application runs.
|
69
|
+
- Command line options have been added to the gem. Several are common to all Nexpose gem integrations. Call the gem with '-h' or '--help' to view these options.
|
70
|
+
- *Breaking change*: Environment variables **NEXPOSE_PASS** and **THYCOTIC_PASS** have been renamed to **NEXPOSE_PASSWORD** and **THYCOTIC_PASSWORD** respectively.
|
@@ -0,0 +1,38 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# Please refer to the configuration documentation for instructions on how to run this gem
|
3
|
+
# Do NOT run this gem without proper pre-configuration.
|
4
|
+
require 'nexpose_thycotic'
|
5
|
+
require 'nexpose_thycotic/utilities/config_parser.rb'
|
6
|
+
require 'nexpose_thycotic/utilities/gem_options'
|
7
|
+
|
8
|
+
CONFIG_PATH = File.join(File.dirname(__FILE__),
|
9
|
+
'../lib/nexpose_thycotic/config/nexpose_thycotic.config')
|
10
|
+
config_path = File.expand_path(CONFIG_PATH)
|
11
|
+
|
12
|
+
# Setup CLI Options
|
13
|
+
GemOptions.create_parser
|
14
|
+
.with_banner_and_options('nexpose_thycotic')
|
15
|
+
.with_configuration_encryption([config_path])
|
16
|
+
.with_help_and_version('Nexpose Thycotic', NexposeThycotic::VERSION)
|
17
|
+
.parse
|
18
|
+
|
19
|
+
settings = ConfigParser.get_config(config_path)
|
20
|
+
|
21
|
+
thycotic_options = NexposeThycotic.set_variables(settings[:thycotic_options])
|
22
|
+
nexpose_options = NexposeThycotic.set_variables(settings[:nexpose_options])
|
23
|
+
options = settings[:options]
|
24
|
+
|
25
|
+
vault_options = { url: thycotic_options[:thycotic_url],
|
26
|
+
username: thycotic_options[:thycotic_username],
|
27
|
+
password: thycotic_options[:thycotic_password] }
|
28
|
+
|
29
|
+
nexpose_options = { nexpose_ip: nexpose_options[:nexpose_url],
|
30
|
+
nexpose_username: nexpose_options[:nexpose_username],
|
31
|
+
nexpose_password: nexpose_options[:nexpose_password],
|
32
|
+
nexpose_port: nexpose_options[:nexpose_port],
|
33
|
+
sites: options[:sites],
|
34
|
+
logging_enabled: options[:logging_enabled],
|
35
|
+
log_level: options[:log_level],
|
36
|
+
log_console: options[:log_console] }
|
37
|
+
NexposeThycotic.update_credentials(vault_options, nexpose_options)
|
38
|
+
|
data/lib/nexpose_thycotic.rb
CHANGED
@@ -1,39 +1,68 @@
|
|
1
1
|
require "nexpose_thycotic/version"
|
2
2
|
require 'nexpose_thycotic/operations'
|
3
|
-
require 'nexpose_thycotic/nx_logger'
|
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
|
-
NxLogger.
|
8
|
+
log = NexposeThycotic::NxLogger.instance
|
9
|
+
log.setup_statistics_collection(NexposeThycotic::VENDOR,
|
10
|
+
NexposeThycotic::PRODUCT,
|
11
|
+
NexposeThycotic::VERSION)
|
12
|
+
log.setup_logging(true, nexpose_options[:log_level], nexpose_options[:log_console])
|
13
|
+
log.log_message('Starting integration.')
|
14
|
+
|
9
15
|
ss = ThycoticOperations.new(vault_options[:url])
|
10
|
-
|
16
|
+
log.log_message("Logging to Thycotic at #{vault_options[:url]}")
|
11
17
|
token = ss.authenticate(vault_options[:username], vault_options[:password])
|
12
|
-
|
13
|
-
|
18
|
+
|
19
|
+
@nx = NexposeOperations.new(nexpose_options[:nexpose_ip],
|
20
|
+
nexpose_options[:nexpose_username],
|
21
|
+
nexpose_options[:nexpose_password],
|
22
|
+
nexpose_options[:nexpose_port])
|
23
|
+
log.log_message('Processing sites')
|
14
24
|
|
15
25
|
nexpose_options[:sites].each do |site_id|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
26
|
+
log.log_debug_message("Processing site #{site_id}")
|
27
|
+
ips = @nx.get_ips_from_site(site_id)
|
28
|
+
site_credentials = []
|
29
|
+
|
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)
|
48
|
+
end
|
33
49
|
end
|
50
|
+
@nx.save_site(site_id, site_credentials)
|
51
|
+
log.log_message("Finished processing #{site_id}")
|
34
52
|
end
|
35
|
-
|
36
|
-
|
53
|
+
end
|
54
|
+
|
55
|
+
def self.set_variables(options)
|
56
|
+
settings = {}
|
57
|
+
options.each_key do |key|
|
58
|
+
value = ENV[key.to_s.upcase]
|
59
|
+
value ||= options[key]
|
60
|
+
if value.nil?
|
61
|
+
log = NexposeThycotic::NxLogger.instance
|
62
|
+
log.info("No configuration value found for #{key}")
|
63
|
+
end
|
64
|
+
settings[key] = value
|
37
65
|
end
|
66
|
+
settings
|
38
67
|
end
|
39
|
-
end
|
68
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
#
|
2
|
+
# Symmetric Encryption for Ruby
|
3
|
+
#
|
4
|
+
---
|
5
|
+
production:
|
6
|
+
# Since the encryption key must NOT be stored along with the
|
7
|
+
# source code, only store the key encryption key here.
|
8
|
+
private_rsa_key:
|
9
|
+
|
10
|
+
# List Symmetric Key Ciphers in the order of current / newest first
|
11
|
+
ciphers:
|
12
|
+
-
|
13
|
+
# Name of the file containing the encrypted key and iv.
|
14
|
+
key_filename: <absolute/path/to/filename>.key
|
15
|
+
iv_filename: <absolute/path/to/filename>.iv
|
16
|
+
|
17
|
+
cipher: aes-256-cbc
|
18
|
+
encoding: base64strict
|
19
|
+
version: 1
|
20
|
+
always_add_header: true
|
21
|
+
|
@@ -0,0 +1,34 @@
|
|
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
|
+
:options:
|
7
|
+
# (M) Enables logging to the log directory.
|
8
|
+
:logging_enabled: true
|
9
|
+
# (M) Sets the log level threshold for output.
|
10
|
+
:log_level: info
|
11
|
+
# (M) Enables logging output to console
|
12
|
+
:log_console: true
|
13
|
+
# Filters to specific sites one per line, leave empty to generate for all sites.
|
14
|
+
:sites:
|
15
|
+
- '1'
|
16
|
+
:nexpose_options:
|
17
|
+
# (M) Nexpose IP address
|
18
|
+
:nexpose_url: 127.0.0.1
|
19
|
+
# (M) Nexpose username
|
20
|
+
:nexpose_username: nxadmin
|
21
|
+
# (M) Nexpose password
|
22
|
+
:nexpose_password: nxadmin
|
23
|
+
# (M) The port Nexpose listens on. Default is 3780
|
24
|
+
:nexpose_port: 3780
|
25
|
+
:thycotic_options:
|
26
|
+
# (M) Thycotic Instance IP address with WSDL
|
27
|
+
:thycotic_url: https://127.0.0.1/SecretServer/webservices/sswebservice.asmx?wsdl
|
28
|
+
# (M) The Thycotic username
|
29
|
+
:thycotic_username: user
|
30
|
+
# (M) The password for the above user
|
31
|
+
:thycotic_password: password
|
32
|
+
:encryption_options:
|
33
|
+
# (M) Path to the encryption.config file
|
34
|
+
:directory: ../../config/encryption.config
|
@@ -7,7 +7,9 @@ module NexposeThycotic
|
|
7
7
|
def initialize(ip, user, pass, port = 3780)
|
8
8
|
@nsc = Connection.new(ip, user, pass, port)
|
9
9
|
@nsc.login
|
10
|
+
NexposeThycotic::NxLogger.instance.on_connect(ip, port, @nsc.session_id, "{}")
|
10
11
|
end
|
12
|
+
|
11
13
|
def get_ips_from_site(site_id)
|
12
14
|
site = Site.load(@nsc, site_id)
|
13
15
|
site.assets
|
@@ -40,7 +42,9 @@ module NexposeThycotic
|
|
40
42
|
attr_accessor :client
|
41
43
|
def initialize(url = nil)
|
42
44
|
# log: true, log_level: :info
|
43
|
-
@client = Savon.client(
|
45
|
+
@client = Savon.client(
|
46
|
+
wsdl: url,
|
47
|
+
ssl_verify_mode: :none)
|
44
48
|
end
|
45
49
|
|
46
50
|
def operations
|
@@ -0,0 +1,141 @@
|
|
1
|
+
require 'erb'
|
2
|
+
require 'yaml'
|
3
|
+
require 'fileutils'
|
4
|
+
require 'symmetric-encryption'
|
5
|
+
|
6
|
+
class ConfigParser
|
7
|
+
ENCRYPTED_FORMAT = '<%%= SymmetricEncryption.try_decrypt "%s" %%>'
|
8
|
+
PLACEHOLDER = '<absolute/path/to/filename>'
|
9
|
+
# The environment to use, defined within the encryption config
|
10
|
+
STANZA = 'production'
|
11
|
+
# The line width of the YAML file before line-wrapping occurs
|
12
|
+
WIDTH = 120
|
13
|
+
|
14
|
+
# Encrypts a configuration file and returns the unencrypted hash.
|
15
|
+
def self.get_config(config_path, enc_path=nil)
|
16
|
+
# Try to load a path from the provided config
|
17
|
+
custom_enc_path = get_enc_directory(config_path)
|
18
|
+
enc_path = custom_enc_path unless custom_enc_path.nil?
|
19
|
+
|
20
|
+
enc_path = File.expand_path(enc_path, __FILE__)
|
21
|
+
config_path = File.expand_path(config_path)
|
22
|
+
|
23
|
+
|
24
|
+
generate_keys(enc_path, config_path)
|
25
|
+
encrypt_config(enc_path, config_path)
|
26
|
+
decrypt_config(enc_path, config_path)
|
27
|
+
end
|
28
|
+
|
29
|
+
# Writes the YAML to file with custom formatting options
|
30
|
+
def self.save_config(config_details, config_path)
|
31
|
+
yaml = config_details.to_yaml(line_width: WIDTH)
|
32
|
+
File.open(config_path, 'w') {|f| f.write yaml }
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.encrypt_field(value)
|
36
|
+
encrypted_value = SymmetricEncryption.encrypt value
|
37
|
+
ENCRYPTED_FORMAT % encrypted_value
|
38
|
+
end
|
39
|
+
|
40
|
+
# Retrieves the custom directory of the encryption config
|
41
|
+
def self.get_enc_directory(config_path)
|
42
|
+
settings = YAML.load_file(config_path)
|
43
|
+
return nil if settings[:encryption_options].nil?
|
44
|
+
|
45
|
+
enc_dir = settings[:encryption_options][:directory]
|
46
|
+
return nil if (enc_dir.nil? || enc_dir == '')
|
47
|
+
|
48
|
+
File.expand_path(enc_dir, __FILE__)
|
49
|
+
end
|
50
|
+
|
51
|
+
# Generates the RSA key, associated files and directories.
|
52
|
+
def self.generate_keys(enc_path, config_path)
|
53
|
+
settings = YAML.load_file(enc_path)
|
54
|
+
key = settings[STANZA]['private_rsa_key']
|
55
|
+
|
56
|
+
# Recognise an existing key
|
57
|
+
return unless (key.nil? || key == '')
|
58
|
+
|
59
|
+
# Generate a new RSA key and store the details
|
60
|
+
new_rsa_key = SymmetricEncryption::KeyEncryptionKey.generate
|
61
|
+
settings[STANZA]['private_rsa_key'] = new_rsa_key
|
62
|
+
save_config(settings, enc_path)
|
63
|
+
|
64
|
+
# Populate the placeholder values within the config
|
65
|
+
populate_ciphers(enc_path, config_path)
|
66
|
+
|
67
|
+
# Need to create a folder (specified by the user) to store the key files
|
68
|
+
dir = File.dirname(settings[STANZA]['ciphers'].first['key_filename'])
|
69
|
+
|
70
|
+
begin
|
71
|
+
unless File.directory?(dir) || PLACEHOLDER.include?(dir)
|
72
|
+
puts "Creating folder: #{dir}"
|
73
|
+
FileUtils::mkdir_p dir
|
74
|
+
end
|
75
|
+
rescue Exception => e
|
76
|
+
msg = "Unable to create the folders used to store encryption details.\n"\
|
77
|
+
'Please ensure the user has permissions to create folders in the ' \
|
78
|
+
"path specified in the encryption config: #{enc_path}\n"
|
79
|
+
handle_error(msg, e)
|
80
|
+
end
|
81
|
+
|
82
|
+
SymmetricEncryption.generate_symmetric_key_files(enc_path, STANZA)
|
83
|
+
end
|
84
|
+
|
85
|
+
# Replace placeholder values for the key and iv file paths,
|
86
|
+
# placing them in the config folder by default.
|
87
|
+
def self.populate_ciphers(enc_path, config_path)
|
88
|
+
settings = YAML.load_file(enc_path)
|
89
|
+
ciphers = settings[STANZA]['ciphers'].first
|
90
|
+
config_folder = File.dirname(config_path)
|
91
|
+
config_name = File.basename(config_path, File.extname(config_path))
|
92
|
+
|
93
|
+
%w(key iv).each do |file|
|
94
|
+
label = "#{file}_filename"
|
95
|
+
file_path = ciphers[label]
|
96
|
+
next unless file_path.include? PLACEHOLDER
|
97
|
+
|
98
|
+
filename = ".#{config_name}.#{file}"
|
99
|
+
ciphers[label] = File.join(config_folder, filename)
|
100
|
+
end
|
101
|
+
|
102
|
+
save_config(settings, enc_path)
|
103
|
+
end
|
104
|
+
|
105
|
+
def self.encrypt_config(enc_path, config_path)
|
106
|
+
SymmetricEncryption.load!(enc_path, STANZA)
|
107
|
+
|
108
|
+
# Read the config in as an array of strings
|
109
|
+
f = File.open(config_path)
|
110
|
+
config_lines = f.readlines
|
111
|
+
f.close
|
112
|
+
|
113
|
+
# Define the regex that can find relevant fields
|
114
|
+
regex = /^(?<label>\s*:?\w*(passw|pwd|user|usr)\w*:?\s)(?<value>.*)$/
|
115
|
+
|
116
|
+
# Line by line, write the line to file, encrypting sensitive fields
|
117
|
+
File.open(config_path, 'w+') do |f|
|
118
|
+
config_lines.each do |l|
|
119
|
+
matches = l.match(regex)
|
120
|
+
|
121
|
+
# Encrypt fields with username/password labels that are in plaintext
|
122
|
+
unless matches.nil? || matches['value'].include?('SymmetricEncryption')
|
123
|
+
l = "#{matches['label']}#{encrypt_field(matches['value'])}"
|
124
|
+
end
|
125
|
+
|
126
|
+
f.puts l
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
# Returns a hash containing the decrypted details from a config file.
|
132
|
+
def self.decrypt_config(enc_path, config_path)
|
133
|
+
SymmetricEncryption.load!(enc_path, STANZA)
|
134
|
+
return YAML.load(ERB.new(File.new(config_path).read).result)
|
135
|
+
end
|
136
|
+
|
137
|
+
def self.handle_error(message, error)
|
138
|
+
puts message
|
139
|
+
raise error
|
140
|
+
end
|
141
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
|
3
|
+
class GemOptions
|
4
|
+
|
5
|
+
@parser
|
6
|
+
|
7
|
+
def self.create_parser
|
8
|
+
@parser = OptionParser.new
|
9
|
+
self
|
10
|
+
end
|
11
|
+
|
12
|
+
# How the gem is used e.g 'nexpose ticketing jira [options]'
|
13
|
+
def self.with_banner(gem_usage_string)
|
14
|
+
@parser.banner = "Usage: #{gem_usage_string} [options]"
|
15
|
+
@parser.separator ''
|
16
|
+
self
|
17
|
+
end
|
18
|
+
|
19
|
+
# Header for options list
|
20
|
+
def self.with_options
|
21
|
+
@parser.separator 'Options:'
|
22
|
+
self
|
23
|
+
end
|
24
|
+
|
25
|
+
# Creates banner and options
|
26
|
+
def self.with_banner_and_options(gem_usage_string)
|
27
|
+
with_banner(gem_usage_string)
|
28
|
+
with_options
|
29
|
+
self
|
30
|
+
end
|
31
|
+
|
32
|
+
# For setting encryption switch. Can be set to work with two configurations
|
33
|
+
# Config_paths is an array
|
34
|
+
def self.with_configuration_encryption(config_paths, enc_path = nil)
|
35
|
+
@parser.on('-e',
|
36
|
+
'--encrypt_config',
|
37
|
+
'Encrypt the configuration file(s) without running the gem') do |e|
|
38
|
+
ConfigParser.get_config(config_paths.first, enc_path) unless enc_path.nil?
|
39
|
+
ConfigParser.get_config(config_paths.last)
|
40
|
+
puts "\nConfiguration File(s) Encrypted"
|
41
|
+
exit
|
42
|
+
end
|
43
|
+
self
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.with_help
|
47
|
+
@parser.on_tail('-h', '--help', 'Show this message') do |h|
|
48
|
+
puts @parser
|
49
|
+
exit
|
50
|
+
end
|
51
|
+
self
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.with_version(gem, version)
|
55
|
+
@parser.on_tail('--version', 'Version Information') do |v|
|
56
|
+
puts "#{gem} #{version}"
|
57
|
+
exit
|
58
|
+
end
|
59
|
+
self
|
60
|
+
end
|
61
|
+
|
62
|
+
def self.with_help_and_version(gem, version)
|
63
|
+
with_help
|
64
|
+
with_version(gem, version)
|
65
|
+
self
|
66
|
+
end
|
67
|
+
|
68
|
+
# Method to allow integrations to create own options, with both short and long
|
69
|
+
# switches and description.
|
70
|
+
# Handler is the block to run when option is called.
|
71
|
+
def self.with_other_option(short_switch, long_switch, description, &handler)
|
72
|
+
@parser.on("-#{short_switch}", "--#{long_switch}", description) do |opt|
|
73
|
+
handler.call
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
# Method to allow integrations to create own options, with only one size of
|
78
|
+
# switch and description.
|
79
|
+
# '-' for short switches and '--' for long switches is required.
|
80
|
+
# Handler is the block to run when option is called.
|
81
|
+
def self.with_single_switch_option(identifier, switch, description, &handler)
|
82
|
+
@parser.on("#{identifier}#{switch}", description) do |opt|
|
83
|
+
handler.call
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
# Parses the options to make them available
|
88
|
+
def self.parse
|
89
|
+
@parser.parse!
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,166 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'json'
|
3
|
+
require 'net/http'
|
4
|
+
require 'singleton'
|
5
|
+
|
6
|
+
module NexposeThycotic
|
7
|
+
class NxLogger
|
8
|
+
include Singleton
|
9
|
+
LOG_PATH = "../logs/rapid7_%s.log"
|
10
|
+
KEY_FORMAT = "external.integration.%s"
|
11
|
+
PRODUCT_FORMAT = "%s_%s"
|
12
|
+
|
13
|
+
DEFAULT_LOG = 'integration'
|
14
|
+
PRODUCT_RANGE = 4..30
|
15
|
+
KEY_RANGE = 3..15
|
16
|
+
|
17
|
+
ENDPOINT = '/data/external/statistic/'
|
18
|
+
|
19
|
+
def initialize()
|
20
|
+
create_calls
|
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
|
+
begin
|
27
|
+
@statistic_key = get_statistic_key vendor
|
28
|
+
@product = get_product product_name, gem_version
|
29
|
+
rescue => e
|
30
|
+
#Continue
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def setup_logging(enabled, log_level = 'info', stdout=false)
|
35
|
+
@stdout = stdout
|
36
|
+
|
37
|
+
log_message('Logging disabled.') unless enabled || @log.nil?
|
38
|
+
@enabled = enabled
|
39
|
+
return unless @enabled
|
40
|
+
|
41
|
+
@logger_file = get_log_path @product
|
42
|
+
|
43
|
+
require 'logger'
|
44
|
+
directory = File.dirname(@logger_file)
|
45
|
+
FileUtils.mkdir_p(directory) unless File.directory?(directory)
|
46
|
+
io = IO.for_fd(IO.sysopen(@logger_file, 'a'), 'a')
|
47
|
+
io.autoclose = false
|
48
|
+
io.sync = true
|
49
|
+
@log = Logger.new(io, 'weekly')
|
50
|
+
@log.level = if log_level.to_s.casecmp('info') == 0
|
51
|
+
Logger::INFO
|
52
|
+
else
|
53
|
+
Logger::DEBUG
|
54
|
+
end
|
55
|
+
log_message("Logging enabled at level <#{log_level}>")
|
56
|
+
end
|
57
|
+
|
58
|
+
def create_calls
|
59
|
+
levels = [:info, :debug, :error, :warn]
|
60
|
+
levels.each do |level|
|
61
|
+
method_name =
|
62
|
+
define_singleton_method("log_#{level.to_s}_message") do |message|
|
63
|
+
puts message if @stdout
|
64
|
+
@log.send(level, message) unless !@enabled || @log.nil?
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def log_message(message)
|
70
|
+
log_info_message message
|
71
|
+
end
|
72
|
+
|
73
|
+
def log_stat_message(message)
|
74
|
+
end
|
75
|
+
|
76
|
+
def get_log_path(product)
|
77
|
+
product.downcase! unless product.nil?
|
78
|
+
File.join(File.dirname(__FILE__), LOG_PATH % (product || DEFAULT_LOG))
|
79
|
+
end
|
80
|
+
|
81
|
+
def get_statistic_key(vendor)
|
82
|
+
if vendor.nil? || vendor.length < KEY_RANGE.min
|
83
|
+
log_stat_message("Vendor length is below minimum of <#{KEY_RANGE}>")
|
84
|
+
return nil
|
85
|
+
end
|
86
|
+
|
87
|
+
vendor.gsub!('-', '_')
|
88
|
+
vendor.slice! vendor.rindex('_') until vendor.count('_') <= 1
|
89
|
+
|
90
|
+
vendor.delete! "^A-Za-z0-9\_"
|
91
|
+
|
92
|
+
KEY_FORMAT % vendor[0...KEY_RANGE.max].downcase
|
93
|
+
end
|
94
|
+
|
95
|
+
def get_product(product, version)
|
96
|
+
return nil if ((product.nil? || product.empty?) ||
|
97
|
+
(version.nil? || version.empty?))
|
98
|
+
|
99
|
+
product.gsub!('-', '_')
|
100
|
+
product.slice! product.rindex('_') until product.count('_') <= 1
|
101
|
+
|
102
|
+
product.delete! "^A-Za-z0-9\_"
|
103
|
+
version.delete! "^A-Za-z0-9\.\-"
|
104
|
+
|
105
|
+
product = (PRODUCT_FORMAT % [product, version])[0...PRODUCT_RANGE.max]
|
106
|
+
|
107
|
+
product.slice! product.rindex(/[A-Z0-9]/i)+1..-1
|
108
|
+
|
109
|
+
if product.length < PRODUCT_RANGE.min
|
110
|
+
log_stat_message("Product length below minimum <#{PRODUCT_RANGE.min}>.")
|
111
|
+
return nil
|
112
|
+
end
|
113
|
+
product.downcase
|
114
|
+
end
|
115
|
+
|
116
|
+
def generate_payload(statistic_value='')
|
117
|
+
product_name, separator, version = @product.to_s.rpartition('_')
|
118
|
+
payload_value = {'version' => version}.to_json
|
119
|
+
|
120
|
+
payload = {'statistic-key' => @statistic_key.to_s,
|
121
|
+
'statistic-value' => payload_value,
|
122
|
+
'product' => product_name}
|
123
|
+
JSON.generate(payload)
|
124
|
+
end
|
125
|
+
|
126
|
+
def send(nexpose_address, nexpose_port, session_id, payload)
|
127
|
+
header = {'Content-Type' => 'application/json',
|
128
|
+
'nexposeCCSessionID' => session_id,
|
129
|
+
'Cookie' => "nexposeCCSessionID=#{session_id}"}
|
130
|
+
req = Net::HTTP::Put.new(ENDPOINT, header)
|
131
|
+
req.body = payload
|
132
|
+
http_instance = Net::HTTP.new(nexpose_address, nexpose_port)
|
133
|
+
http_instance.use_ssl = true
|
134
|
+
http_instance.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
135
|
+
response = http_instance.start { |http| http.request(req) }
|
136
|
+
log_stat_message "Received code #{response.code} from Nexpose console."
|
137
|
+
log_stat_message "Received message #{response.msg} from Nexpose console."
|
138
|
+
log_stat_message 'Finished sending statistics data to Nexpose.'
|
139
|
+
|
140
|
+
response.code
|
141
|
+
end
|
142
|
+
|
143
|
+
def on_connect(nexpose_address, nexpose_port, session_id, value)
|
144
|
+
log_stat_message 'Sending statistics data to Nexpose'
|
145
|
+
|
146
|
+
if @product.nil? || @statistic_key.nil?
|
147
|
+
log_stat_message('Invalid product name and/or statistics key.')
|
148
|
+
log_stat_message('Statistics collection not enabled.')
|
149
|
+
return
|
150
|
+
end
|
151
|
+
|
152
|
+
begin
|
153
|
+
payload = generate_payload value
|
154
|
+
send(nexpose_address, nexpose_port, session_id, payload)
|
155
|
+
rescue => e
|
156
|
+
#Let the program continue
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
#Used by net library for debugging
|
161
|
+
def <<(value)
|
162
|
+
log_debug_message(value)
|
163
|
+
end
|
164
|
+
|
165
|
+
end
|
166
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: nexpose_thycotic
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
|
-
- Damian Finol
|
7
|
+
- Damian Finol, Adam Robinson
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2017-05-30 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -72,20 +72,40 @@ dependencies:
|
|
72
72
|
requirements:
|
73
73
|
- - "~>"
|
74
74
|
- !ruby/object:Gem::Version
|
75
|
-
version: 0.
|
75
|
+
version: '0.9'
|
76
76
|
type: :runtime
|
77
77
|
prerelease: false
|
78
78
|
version_requirements: !ruby/object:Gem::Requirement
|
79
79
|
requirements:
|
80
80
|
- - "~>"
|
81
81
|
- !ruby/object:Gem::Version
|
82
|
-
version: 0.
|
83
|
-
|
82
|
+
version: '0.9'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: symmetric-encryption
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '3.9'
|
90
|
+
- - ">="
|
91
|
+
- !ruby/object:Gem::Version
|
92
|
+
version: 3.9.0
|
93
|
+
type: :runtime
|
94
|
+
prerelease: false
|
95
|
+
version_requirements: !ruby/object:Gem::Requirement
|
96
|
+
requirements:
|
97
|
+
- - "~>"
|
98
|
+
- !ruby/object:Gem::Version
|
99
|
+
version: '3.9'
|
100
|
+
- - ">="
|
101
|
+
- !ruby/object:Gem::Version
|
102
|
+
version: 3.9.0
|
103
|
+
description: This gem allows Nexpose users to pull credentials from Thycotic SecretServer
|
84
104
|
into Nexpose
|
85
105
|
email:
|
86
|
-
-
|
106
|
+
- support@rapid7.com
|
87
107
|
executables:
|
88
|
-
-
|
108
|
+
- nexpose_thycotic
|
89
109
|
extensions: []
|
90
110
|
extra_rdoc_files: []
|
91
111
|
files:
|
@@ -94,13 +114,15 @@ files:
|
|
94
114
|
- LICENSE.txt
|
95
115
|
- README.md
|
96
116
|
- Rakefile
|
97
|
-
- bin/
|
117
|
+
- bin/nexpose_thycotic
|
98
118
|
- lib/nexpose_thycotic.rb
|
99
|
-
- lib/nexpose_thycotic/
|
119
|
+
- lib/nexpose_thycotic/config/encryption.config
|
120
|
+
- lib/nexpose_thycotic/config/nexpose_thycotic.config
|
100
121
|
- lib/nexpose_thycotic/operations.rb
|
122
|
+
- lib/nexpose_thycotic/utilities/config_parser.rb
|
123
|
+
- lib/nexpose_thycotic/utilities/gem_options.rb
|
124
|
+
- lib/nexpose_thycotic/utilities/nx_logger.rb
|
101
125
|
- lib/nexpose_thycotic/version.rb
|
102
|
-
- nexpose_thycotic-0.0.3.gem
|
103
|
-
- nexpose_thycotic.gemspec
|
104
126
|
homepage: http://www.rapid7.com/
|
105
127
|
licenses:
|
106
128
|
- MIT
|
@@ -121,7 +143,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
121
143
|
version: '0'
|
122
144
|
requirements: []
|
123
145
|
rubyforge_project:
|
124
|
-
rubygems_version: 2.
|
146
|
+
rubygems_version: 2.5.1
|
125
147
|
signing_key:
|
126
148
|
specification_version: 4
|
127
149
|
summary: Nexpose Thycotic Gem Integration
|
data/bin/nx_thycotic.rb
DELETED
@@ -1,31 +0,0 @@
|
|
1
|
-
#!/usr/bin/env ruby
|
2
|
-
# Please refer to the configuration documentation for instructions on how to run this gem
|
3
|
-
# Do NOT run this gem without proper pre-configuration.
|
4
|
-
require 'nexpose_thycotic'
|
5
|
-
|
6
|
-
# Sites to collect credentials on, separated by commas.
|
7
|
-
sites = [ 7 ]
|
8
|
-
# Thycotic SecretServer URL.
|
9
|
-
# Set an Environment variable called THYCOTIC_URL to your sswebservice install
|
10
|
-
# IE: http://127.0.0.1/SecretServer/webservices/sswebservice.asmx?wsdl
|
11
|
-
thycotic_url = ENV['THYCOTIC_URL']
|
12
|
-
# Thycotic username.
|
13
|
-
thycotic_username = ENV['THYCOTIC_USER']
|
14
|
-
# Thycotic password
|
15
|
-
thycotic_password = ENV['THYCOTIC_PASS']
|
16
|
-
|
17
|
-
|
18
|
-
### NEXPOSE CONFIGURATION ###
|
19
|
-
# Nexpose IP Address
|
20
|
-
nexpose_ip = ENV['NEXPOSE_URL']
|
21
|
-
# Nexpose username
|
22
|
-
nexpose_username = ENV['NEXPOSE_USER']
|
23
|
-
# Nexpose password
|
24
|
-
nexpose_password = ENV['NEXPOSE_PASS']
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
### DO NOT EDIT AFTER THIS LINE ###
|
29
|
-
vault_options = { url: thycotic_url, username: thycotic_username, password: thycotic_password }
|
30
|
-
nexpose_options = { nexpose_ip: nexpose_ip, nexpose_username: nexpose_username, nexpose_password: nexpose_password, sites: sites }
|
31
|
-
NexposeThycotic.update_credentials(vault_options, nexpose_options)
|
@@ -1,19 +0,0 @@
|
|
1
|
-
module NexposeThycotic
|
2
|
-
module NxLogger
|
3
|
-
require 'logger'
|
4
|
-
LOGGER_FILE = File.join(File.dirname(__FILE__), '/log/nx_thycotic.log')
|
5
|
-
directory = File.dirname(LOGGER_FILE)
|
6
|
-
FileUtils.mkdir_p(directory) unless File.directory?(directory)
|
7
|
-
@log = Logger.new(LOGGER_FILE, 'monthly')
|
8
|
-
@log.level = Logger::INFO
|
9
|
-
|
10
|
-
def self.log_message(message, level)
|
11
|
-
case level
|
12
|
-
when 'info' then @log.info(message)
|
13
|
-
when 'debug' then @log.debug(message)
|
14
|
-
when 'error' then @log.error(message)
|
15
|
-
when 'warn' then @log.warn(message)
|
16
|
-
end
|
17
|
-
end
|
18
|
-
end
|
19
|
-
end
|
data/nexpose_thycotic-0.0.3.gem
DELETED
Binary file
|
data/nexpose_thycotic.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_thycotic/version'
|
5
|
-
|
6
|
-
Gem::Specification.new do |spec|
|
7
|
-
spec.name = "nexpose_thycotic"
|
8
|
-
spec.version = NexposeThycotic::VERSION
|
9
|
-
spec.authors = ["Damian Finol"]
|
10
|
-
spec.email = ["damian_finol@rapid7.com"]
|
11
|
-
spec.summary = %q{Nexpose Thycotic Gem Integration}
|
12
|
-
spec.description = %q{This gem allows Nexpose users to poll credentials from Thycotic SecretServer into 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
|
-
|
22
|
-
spec.add_development_dependency "bundler", "~> 1.5"
|
23
|
-
spec.add_development_dependency "rake"
|
24
|
-
spec.add_runtime_dependency "savon"
|
25
|
-
spec.add_runtime_dependency "rubyntlm"
|
26
|
-
spec.add_runtime_dependency 'nexpose', '~> 0.8.0'
|
27
|
-
end
|