nexpose_thycotic 0.0.4 → 0.1.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/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
|