nexpose_paloalto 0.1.1

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 14c752bd0571d6cb5572c01e07eef1ee27178c72
4
+ data.tar.gz: a1f4eca110f7765547a98e6b571d3e0c1bdd80b8
5
+ SHA512:
6
+ metadata.gz: 6bebc678b2e03d9b01720fb84362f6f97927b5a44e464d3c49170304a68abca80ba114bbc09492145a6bcd99d38e2d23e9dc2ae6e3598925e641e0801b400763
7
+ data.tar.gz: ddb4b6a8a0e1456d46dd2b1b0f098892946f520cd6c428e0f39bbbb23fa88a76b2c382ad2f0eaa9947b1590f19129e99ed68d9ed2dcd8a7e8a261dcb4404db6c
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in nexpose_paloalto.gemspec
4
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,27 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ nexpose_paloalto (0.1.0)
5
+ curb (~> 0.8.7)
6
+ nexpose (~> 0.9)
7
+ nokogiri (~> 1.6)
8
+
9
+ GEM
10
+ remote: https://rubygems.org/
11
+ specs:
12
+ curb (0.8.7)
13
+ mini_portile (0.6.1)
14
+ nexpose (0.9.8)
15
+ rex (= 2.0.7)
16
+ nokogiri (1.6.4.1)
17
+ mini_portile (~> 0.6.0)
18
+ rake (10.3.2)
19
+ rex (2.0.7)
20
+
21
+ PLATFORMS
22
+ ruby
23
+
24
+ DEPENDENCIES
25
+ bundler (~> 1.8)
26
+ nexpose_paloalto!
27
+ rake (~> 10.0)
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2015 Rapid7
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,63 @@
1
+ # Paloalto
2
+
3
+ Nexpose -> Palo Alto integration Gem.
4
+
5
+ With this Gem an integration between Nexpose Dynamic Asset Groups and Palo Alto's PAN TAGs. By using this integration,
6
+ dynamic asset groups could be setup in Nexpose that correspond to groups in PAN with applicable policies.
7
+
8
+ For example:
9
+ * Create a DAG in Nexpose which affects a particular vulnerability (Heartbleed).
10
+ * Define a policy in PAN to block SSL (mitigates temporarily Heartbleed).
11
+ * Run this Gem with the Hearbleed DAG in the configuration.
12
+ * All assets identified by Nexpose will be registered in PAN.
13
+ * Apply PAN Heartbleed Policy on the created group.
14
+
15
+ ## Installation
16
+
17
+ Add this line to your application's Gemfile:
18
+
19
+ ```ruby
20
+ gem 'paloalto'
21
+ ```
22
+
23
+ And then execute:
24
+
25
+ $ bundle
26
+
27
+ Or install it yourself as:
28
+
29
+ $ gem install paloalto
30
+
31
+ ## Usage
32
+
33
+ Follow these steps once installed:
34
+
35
+ * Modify the file nexpose_paloalto.rb under the bin folder, and add the DAGs to report on.
36
+
37
+ * Add the following Environment variables, with your respective information:
38
+
39
+ NEXPOSE_URL
40
+
41
+ NEXPOSE_USERNAME
42
+
43
+ NEXPOSE_PASSWORD
44
+
45
+
46
+ PAN_URL
47
+
48
+ PAN_USERNAME
49
+
50
+ PAN_PASSWORD
51
+
52
+
53
+ For Linux systems, make sure they are added to the current environment where the gem is run.
54
+
55
+ For Windows systems, make sure they are on the Environment Variables section in your Control Panel.
56
+
57
+ * Run the command 'nexpose_paloalto' under the bin folder.
58
+
59
+ * (Optional) Review the log file under the logs folder in the Gem path.
60
+
61
+
62
+ For any support requests, please email integrations_support@rapid7.com with a description of the issue and any logs
63
+ available.
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+
@@ -0,0 +1,37 @@
1
+ require 'paloalto'
2
+
3
+ # Obtain Nexpose settings from Environment Variables.
4
+ nexpose_settings = Hash.new
5
+ raise 'Must configure nexpose settings before starting' if ENV['NEXPOSE_URL'].nil? || ENV['NEXPOSE_USERNAME'].nil? || ENV['NEXPOSE_PASSWORD'].nil?
6
+ nexpose_settings[:nexpose_url] = ENV['NEXPOSE_URL']
7
+ nexpose_settings[:nexpose_username] = ENV['NEXPOSE_USERNAME']
8
+ nexpose_settings[:nexpose_password] = ENV['NEXPOSE_PASSWORD']
9
+
10
+ # Obtain PAN's info.
11
+ pan_settings = Hash.new
12
+ raise 'Must configure Palo Alto settings before starting' if ENV['PAN_URL'].nil? || ENV['PAN_USERNAME'].nil? || ENV['PAN_PASSWORD'].nil?
13
+ pan_settings[:pan_url] = ENV['PAN_URL']
14
+ pan_settings[:pan_username] = ENV['PAN_USERNAME']
15
+ pan_settings[:pan_password] = ENV['PAN_PASSWORD']
16
+
17
+ # Nexpose options
18
+ # Dynamic Asset Groups to use, separated by commas:
19
+ # dag = [1, 2]
20
+ dag = []
21
+
22
+ # Sites to use. We recommend using DAGs, separated by commas:
23
+ # sites = [1, 2]
24
+ sites = []
25
+
26
+ # Report Time_out. Leave default value of 10800.
27
+ report_timeout = 10800
28
+
29
+ # Do not edit below this line.
30
+ # Check that everything is in place before we start it.
31
+ raise 'Must configure a site or a dag before starting' if dag.empty? && sites.empty?
32
+ nexpose_settings[:dag] = dag
33
+ nexpose_settings[:sites] = sites
34
+ nexpose_settings[:timeout] = report_timeout
35
+
36
+ # Start integration with all the parameters.
37
+ Paloalto.start_integration(nexpose_settings, pan_settings)
data/lib/paloalto.rb ADDED
@@ -0,0 +1,147 @@
1
+ require 'paloalto/version'
2
+ require 'paloalto/nexpose_helper'
3
+ require 'paloalto/ngfw'
4
+ require 'paloalto/nx_logger'
5
+
6
+ module Paloalto
7
+ def self.start_integration(nexpose_settings, pan_settings)
8
+ # Asset query.
9
+ asset_query = "select asset_id, da.ip_address, string_agg(DISTINCT '<' || dt.tag_name, '>') || '>' as tags
10
+ from dim_site_asset
11
+ LEFT OUTER JOIN dim_asset da USING (asset_id)
12
+ LEFT OUTER JOIN dim_tag_asset dta using (asset_id)
13
+ LEFT OUTER JOIN dim_tag dt using (tag_id)
14
+ GROUP BY asset_id, da.ip_address"
15
+
16
+ nexpose_url = nexpose_settings[:nexpose_url]
17
+ nexpose_username = nexpose_settings[:nexpose_username]
18
+ nexpose_password = nexpose_settings[:nexpose_password]
19
+
20
+ pan_url = pan_settings[:pan_url]
21
+ pan_username = pan_settings[:pan_username]
22
+ pan_password = pan_settings[:pan_password]
23
+
24
+ report_timeout = nexpose_settings[:timeout]
25
+
26
+ #Setup logging
27
+ @log = Paloalto::NXLogger.new
28
+
29
+ #Nexpose sites and DAGs to import. Uses Site Id and DAG ID e.g. 'sites = [1,2,3,4]'. Leave as nil to run on all sites and DAGs the user has access to or
30
+ # set as an empty array e.g. 'dags=[]' to not run on any sites/dags.
31
+ sites = nexpose_settings[:sites]
32
+ dags = nexpose_settings[:dag]
33
+
34
+ @log.log_message("Running with user configured site IDs <#{sites}> and dynamic asset group IDs #{dags}.")
35
+
36
+ # Log in to nexpose.
37
+ nsc = Paloalto::NexposeHelper.login(nexpose_url, nexpose_username, nexpose_password)
38
+
39
+ #Gather the sites
40
+ all_sites = nsc.sites
41
+ all_sites.delete_if {|site| !(sites.include? site.id)} unless sites.nil?
42
+ all_sites_names = []
43
+ all_sites.each { |site| (all_sites_names ||= []) << site.name.to_s }
44
+
45
+ # Handle any Nexpose Dynamic Asset Groups
46
+ # These will have a single tag (The Nexpose group name) with a Dynamic group name the same.
47
+ nexpose_dag_query_results = Paloalto::NexposeHelper.generate_dag_asset_groups({timeout: report_timeout}, nsc)
48
+ all_nexpose_dag_details = Paloalto::NexposeHelper.parse_dag_details(nexpose_dag_query_results)
49
+ all_nexpose_dag_details.delete_if {|dag_details| !(dags.include? dag_details[0].to_i)} unless dags.nil?
50
+
51
+ @log.log_message("User has access to the following site IDs <#{all_sites.each {|site| site.id}}> and dynamic asset group IDs #{all_nexpose_dag_details.each {|dag| dag[0]}}.")
52
+
53
+ # Login to PAN.
54
+ pan_key = Paloalto::Ngfw.login(pan_url, pan_username, pan_password)
55
+
56
+ #Get the device config
57
+ device_config_xml = Paloalto::Ngfw.retrieve_device_config(pan_url, pan_key)
58
+
59
+ #Get the device names
60
+ device_name = Paloalto::Ngfw.parse_device_name(device_config_xml)
61
+ vsys_name = Paloalto::Ngfw.parse_vsys_name(device_config_xml)
62
+
63
+ @log.log_message("Found device configuration. Name <#{device_name}> and vsys <#{vsys_name}>.")
64
+
65
+ #Get this device's config
66
+ vsys_config = Paloalto::Ngfw.retrieve_device_config(pan_url, pan_key, device_name, vsys_name)
67
+
68
+ #Get the existing tags
69
+ existing_tags = Paloalto::Ngfw.parse_existing_tags(vsys_config)
70
+
71
+ #Gather the tags we want to create
72
+ wanted_tags = []
73
+ all_sites_names.each {|site_name| wanted_tags << site_name.gsub(/[()]/, "")}
74
+ wanted_tags << 'Nexpose'
75
+ all_nexpose_dag_details.each {|details| wanted_tags << details[1].gsub(/[()']/, "")}
76
+
77
+ #Find which new tags need to be created
78
+ tags_to_create = wanted_tags - existing_tags
79
+
80
+ @log.log_message("New tags to be created <#{tags_to_create}>.")
81
+
82
+ #Find the existing DAGs
83
+ existing_dags = Paloalto::Ngfw.parse_existing_dags(vsys_config)
84
+
85
+ #Gather the dags we want to create
86
+ wanted_dags = []
87
+ all_sites_names.each {|site_name| wanted_dags << site_name.gsub(/[()]/, "")}
88
+ wanted_dags << 'Nexpose'
89
+ all_nexpose_dag_details.each {|details| wanted_dags << details[1].gsub(/[()']/, "")}
90
+
91
+ #Find which new dags need to be created
92
+ dags_to_create = wanted_dags - existing_dags
93
+
94
+ @log.log_message("New DAGs to be created <#{dags_to_create}>.")
95
+
96
+ #Create the new tags
97
+ @log.log_message("Creating tags...")
98
+ tags_element=''
99
+ tags_to_create.each {|tag_to_create| tags_element << Paloalto::Ngfw.generate_tag_xml(tag_to_create, 'color3', "Nexpose tag for asset grouping: #{tag_to_create}")}
100
+ response = Paloalto::Ngfw.create_tags(pan_url, pan_key, device_name, vsys_name, tags_element) unless tags_element.empty?
101
+
102
+ #Create the new dags
103
+ @log.log_message("Creating DAGs...")
104
+ dags_element=''
105
+ dags_to_create.each {|dag_to_create| dags_element << Paloalto::Ngfw.generate_dag_xml(dag_to_create, "'Nexpose' AND '#{dag_to_create}'", dag_to_create)}
106
+ Paloalto::Ngfw.create_dags(pan_url, pan_key, device_name, vsys_name, dags_element) unless dags_element.empty?
107
+
108
+ @log.log_message('Committing the changes...')
109
+
110
+ #Commit the changes
111
+ response = Paloalto::Ngfw.commit(pan_url, pan_key)
112
+
113
+ @log.log_message("Commit response <#{response}>")
114
+
115
+ all_sites.each do |site|
116
+ #Fetch this site's details
117
+ @log.log_message("Getting asset details for site <#{site.id}>.")
118
+ report_output = Paloalto::NexposeHelper.generate_report({timeout: report_timeout, site: site.id.to_s, query: asset_query}, nsc)
119
+ asset_details = Paloalto::NexposeHelper.parse_asset_query_details(report_output, site.name)
120
+
121
+ @log.log_message("Unregistering assets for site <#{site.id}>.")
122
+ # Unregister devices. Note have to do this because any "add" updates will append instead of overwriting.
123
+ Paloalto::Ngfw.unregister_devices(pan_url, pan_key, asset_details, site.id)
124
+
125
+ @log.log_message("Registering asset details for site <#{site.id}>.")
126
+ # Register the new devices
127
+ Paloalto::Ngfw.register_devices(pan_url, pan_key, asset_details, site.id)
128
+
129
+ end
130
+
131
+ #Repeat for Nexpose DAGs
132
+ all_nexpose_dag_details.each do |dag_details|
133
+ #Fetch this DAG's details
134
+ @log.log_message("Getting DAG details details for DAG <#{dag_details[0]}>.")
135
+ report_output = Paloalto::NexposeHelper.generate_report({timeout: report_timeout, query: Paloalto::NexposeHelper.generate_dag_assets_query(dag_details[0])}, nsc)
136
+ dag_parsed_details = Paloalto::NexposeHelper.parse_dag_query_details(report_output, dag_details[1].gsub(/[()']/, ""))
137
+
138
+ @log.log_message("Registering asset details for DAG <#{dag_details[0]}>.")
139
+ #Add devices. Note we do not remove these devices first as it will clean wipe the site configuration
140
+ Paloalto::Ngfw.register_devices(pan_url, pan_key, dag_parsed_details, "dag_#{dag_details[0]}")
141
+ end
142
+
143
+ @log.log_message('Exiting..')
144
+ end
145
+
146
+
147
+ end
@@ -0,0 +1,104 @@
1
+ module Paloalto
2
+ module NexposeHelper
3
+ require 'nexpose'
4
+ require 'csv'
5
+ require 'paloalto/nx_logger'
6
+
7
+ # Logs in to Nexpose using the url, username and password.
8
+ def self.login(url=nil, username=nil, password=nil)
9
+ raise 'Nexpose connection must be set in environment variables.' if url.nil? || username.nil? || password.nil?
10
+ nsc = Nexpose::Connection.new(url, username, password)
11
+ nsc.login
12
+ nsc
13
+ end
14
+
15
+ # Generates the ReportConfig with the parameters necessary.
16
+ # Report Params is a hash with the following hashes set:
17
+ # 'site' = Site to report on.
18
+ # 'query' = SQL Query.
19
+ # 'timeout' = Timeout in ms.
20
+ # 'filename' = File name to save to disk.
21
+ def self.generate_report(report_params=nil, nsc=nil)
22
+ raise 'Report options must be set in the config file.' if report_params.nil? || nsc.nil?
23
+ report_config = Nexpose::AdhocReportConfig.new(nil, 'sql')
24
+ report_config.add_filter('site', report_params[:site]) unless report_params[:site].nil?
25
+ report_config.add_filter('version', '1.2.0')
26
+ report_config.add_filter('query', report_params[:query])
27
+ report_output = report_config.generate(nsc, report_params[:timeout])
28
+ return report_output
29
+ end
30
+
31
+ def self.generate_dag_asset_groups(report_params=nil, nsc=nil)
32
+ report_params[:query] = 'select asset_group_id, name from dim_asset_group'
33
+ return generate_report(report_params, nsc)
34
+ end
35
+
36
+ # Parses a generated asset query.
37
+ # Returns an array of arrays with an entry containing an assets IP and Nexpose TAGs
38
+ def self.parse_asset_query_details(report_output, site_name)
39
+ raise 'Need to pass asset query report output to parse!' if report_output.nil?
40
+ csv_output = CSV.parse(report_output.chomp, headers: :first_row)
41
+ asset_details =[]
42
+ csv_output.each do |row|
43
+ ip_details = []
44
+ ip_details << row[1].to_s
45
+ ip_details << "#{row[2].to_s}<Nexpose><#{site_name}>"
46
+ asset_details << ip_details
47
+ end
48
+ return asset_details
49
+ end
50
+
51
+ # Parses a generated dag query.
52
+ # Returns an array of arrays with an entry containg an assets IP and Nexpose DAG name.
53
+ def self.parse_dag_query_details(report_output, dag_name)
54
+ raise 'Need to pass dag query report output to parse!' if report_output.nil?
55
+ csv_output = CSV.parse(report_output.chomp, headers: :first_row)
56
+ asset_details =[]
57
+ csv_output.each do |row|
58
+ ip_details = []
59
+ ip_details << row[1].to_s
60
+ ip_details << "<Nexpose><#{dag_name}>"
61
+ asset_details << ip_details
62
+ end
63
+ return asset_details
64
+ end
65
+
66
+ def self.parse_dag_names(report_output)
67
+ csv_output = CSV.parse(report_output.chomp, headers: :first_row)
68
+ nexpose_group_names = []
69
+ csv_output.each do |row|
70
+ nexpose_group_names << row[1].to_s
71
+ end
72
+ return nexpose_group_names
73
+ end
74
+
75
+ def self.parse_dag_group_ids(report_output)
76
+ csv_output = CSV.parse(report_output.chomp, headers: :first_row)
77
+ nexpose_group_ids = []
78
+ csv_output.each do |row|
79
+ nexpose_group_ids << row[0].to_s
80
+ end
81
+ return nexpose_group_ids
82
+ end
83
+
84
+ def self.parse_dag_details(report_output)
85
+ csv_output = CSV.parse(report_output.chomp, headers: :first_row)
86
+ nexpose_group_details = []
87
+ csv_output.each do |row|
88
+ row_details = []
89
+ row_details << row[0].to_s
90
+ row_details << row[1].to_s
91
+ nexpose_group_details << row_details
92
+ end
93
+ return nexpose_group_details
94
+ end
95
+
96
+ def self.generate_dag_assets_query(nexpose_dag_id)
97
+ return "select asset_id, da.ip_address, dag.name
98
+ from dim_asset_group_asset daga
99
+ LEFT OUTER JOIN dim_asset da USING (asset_id)
100
+ LEFT OUTER JOIN dim_asset_group dag USING (asset_group_id)
101
+ where asset_group_id = #{nexpose_dag_id}"
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,268 @@
1
+ module Paloalto
2
+ module Ngfw
3
+ require 'curb'
4
+ require 'nokogiri'
5
+ require 'fileutils'
6
+
7
+ #Curls a request to a specified URL.
8
+ #Returns Nokogiri::XML::Document
9
+ def self.curlRequest(url)
10
+ raise 'Need a URL to curl a request' if url.nil?
11
+
12
+ log_message("Sending request to URL")
13
+
14
+ curlReq = Curl::Easy.new(url)
15
+ curlReq.multipart_form_post = true
16
+ curlReq.ssl_verify_peer = false
17
+ curlReq.ssl_verify_host = false
18
+ curlReq.perform
19
+
20
+ log_message("Request response <#{curlReq.body}>")
21
+
22
+ return Nokogiri::XML(curlReq.body)
23
+ end
24
+
25
+ def self.curlRequestWithFile(url, filename)
26
+ raise 'Need a URL and a file to curl a request' if url.nil? || filename.nil?
27
+
28
+ log_message("Sending request to URL <#{url}> with file <#{filename}>")
29
+
30
+ curlReq = Curl::Easy.new(url)
31
+ curlReq.multipart_form_post = true
32
+ curlReq.ssl_verify_peer = false
33
+ curlReq.ssl_verify_host = false
34
+ curlReq.http_post(Curl::PostField.file("fileupload",filename))
35
+
36
+ log_message("Request response <#{curlReq.body}>")
37
+
38
+ return Nokogiri::XML(curlReq.body)
39
+ end
40
+
41
+ # Logs a message
42
+ def self.log_message(message)
43
+ require 'logger'
44
+ logger_file = File.join(File.dirname(__FILE__), './logs/rapid7_palo_alto.log')
45
+ directory = File.dirname(logger_file)
46
+ FileUtils.mkdir_p(directory) unless File.directory?(directory)
47
+ @log = Logger.new(logger_file, 'monthly')
48
+ @log.level = Logger::INFO
49
+ @log.info(message)
50
+ end
51
+
52
+ # Performs login using URL, pan_username, pan_password.
53
+ # Returns Nokogiri::XML::Element
54
+ # TODO: Handle invalid login.
55
+ def self.login(url=nil, pan_username=nil, pan_password=nil)
56
+ raise 'URL, Username and Password must be set in environment variables.' if url.nil? || pan_username.nil? || pan_password.nil?
57
+
58
+ url="https://#{url}/api/?type=keygen&user=#{pan_username}&password=#{pan_password}"
59
+ response_xml = curlRequest(url)
60
+ return response_xml.at_xpath('//key').child
61
+ end
62
+
63
+ # Commits a candidate configuration.
64
+ # TODO: Handle invalid command/key. Validate before commit and check commit job status.
65
+ def self.commit(pan_address=nil, key=nil)
66
+ raise 'Need to login first.' if key.nil?
67
+ raise 'Need to define a PAN URL.' if pan_address.nil?
68
+
69
+ url="https://#{pan_address}/api/?type=commit&key=#{key}&cmd=<commit></commit>"
70
+ response_xml = curlRequest(url)
71
+
72
+ #Get job id
73
+ job_id=response_xml.at_xpath('//job').child
74
+ url = "https://#{pan_address}/api/?type=op&key=#{key}&cmd=<show><jobs><id>#{job_id}</id></jobs></show>"
75
+ 6.times do
76
+ response = curlRequest(url)
77
+ complete = response.at_xpath('/response[@status="success"]/result/job/result')
78
+ if complete.nil?
79
+ log_message("Commit response <#{response}>")
80
+ raise 'Error trying to parse commit response! See log for details.'
81
+ end
82
+ complete = complete.child
83
+ log_message("Current commit status <#{complete}>")
84
+ (complete.to_s == 'OK') ? break : sleep(20)
85
+ end
86
+ return response_xml
87
+ end
88
+
89
+ # Queries the Firewall for it's configuration (device and vsys names etc)
90
+ # Returns Nokogiri::XML::Document
91
+ def self.retrieve_device_config(pan_address=nil, key=nil, device_name=nil,vsys_name=nil)
92
+ raise 'Need to login first.' if key.nil?
93
+ raise 'Need to define a PAN URL.' if pan_address.nil?
94
+
95
+ if (!device_name.nil?)
96
+ if(!vsys_name.nil?)
97
+ url="https://#{pan_address}/api/?type=config&action=show&key=#{key}&xpath=/config/devices/entry[@name='#{device_name}']/vsys/entry[@name='#{vsys_name}']"
98
+ else
99
+ url="https://#{pan_address}/api/?type=config&action=show&key=#{key}&xpath=/config/devices/entry[@name='#{device_name}']"
100
+ end
101
+ else
102
+ url="https://#{pan_address}/api/?type=config&action=show&key=#{key}&xpath=/config/devices"
103
+ end
104
+ response_xml = curlRequest(url)
105
+ return response_xml
106
+ end
107
+
108
+ # Parse the device config and return the device name.
109
+ # Returns device name
110
+ def self.parse_device_name(device_config_xml=nil)
111
+ raise 'Need to pass device config to parse device name!' if device_config_xml.nil?
112
+ return device_config_xml.at_xpath('/response/result/devices/entry').values.first
113
+ end
114
+
115
+ # Parse the device config and return the vsys name.
116
+ # Returns device vsys name
117
+ def self.parse_vsys_name(device_config_xml=nil)
118
+ raise 'Need to pass device config to parse vsys name!' if device_config_xml.nil?
119
+ return device_config_xml.at_xpath('/response/result/devices/entry/vsys/entry').values.first
120
+ end
121
+
122
+ # Parse the device config and return the existing tags.
123
+ # Returns an array containing existing tags
124
+ def self.parse_existing_tags(device_config_xml=nil)
125
+ raise 'Need to pass device config to parse existing tags!' if device_config_xml.nil?
126
+ existing_tags=[]
127
+ device_config_xml.xpath('//tag/entry').each {|tag| existing_tags << tag.values}
128
+ return existing_tags
129
+ end
130
+
131
+ # Parse the device config and return the existing DAGs.
132
+ # Returns an array containing existing DAGs
133
+ def self.parse_existing_dags(device_config_xml=nil)
134
+ raise 'Need to pass device config to parse existing DAGs!' if device_config_xml.nil?
135
+ existing_dags=[]
136
+ device_config_xml.xpath('//address-group/entry').each {|tag| existing_dags << tag.values if tag.to_s.include?("<dynamic")}
137
+ return existing_dags
138
+ end
139
+
140
+ # Generates PAN API formatted XML for adding a TAG.
141
+ # Returns PAN API formatted XML String ready for sending
142
+ def self.generate_tag_xml(tag_name, tag_colour, tag_comments)
143
+ return URI.escape("<entry name=\"#{tag_name}\"><color>#{tag_colour}</color><comments>#{tag_comments}</comments></entry>")
144
+ end
145
+
146
+ # Generates PAN API formatted XML for adding a DAG.
147
+ # Returns PAN API formatted XML String ready for sending
148
+ def self.generate_dag_xml(dag_name, dag_filter, dag_tag_member)
149
+ return URI.escape("<entry name=\"#{dag_name}\"><dynamic><filter>#{dag_filter}</filter></dynamic><tag><member>#{dag_tag_member}</member></tag></entry>")
150
+ end
151
+
152
+ # Posts a XML formatted file to PAN.
153
+ # Options is a hash with:
154
+ # 'pan_address' : PAN URL.
155
+ # 'filename' : File with XML to post.
156
+ # TODO: Handle error codes.
157
+ def self.post_dag_file(pan_address = nil, filename = nil, key=nil)
158
+ raise 'Options and key must be set' if pan_address.nil? || key.nil? || filename.nil?
159
+
160
+ c = Curl::Easy.new("https://#{pan_address}/api/?type=user-id&key=#{key}&action=set")
161
+ c.multipart_form_post = true
162
+ c.ssl_verify_peer = false
163
+ c.ssl_verify_host = false
164
+ c.http_post(Curl::PostField.file('thing[file]', filename))
165
+ end
166
+
167
+ def self.create_tags(pan_address=nil, key=nil, device_name, vsys_name, tags_element)
168
+ raise 'Options and key must be set' if pan_address.nil? || key.nil? || device_name.nil? || vsys_name.nil? || tags_element.nil?
169
+ url="https://#{pan_address}/api/?type=config&action=set&key=#{key}&xpath=/config/devices/entry[@name='#{device_name}']/vsys/entry[@name='#{vsys_name}']/tag&element=#{tags_element}"
170
+ return curlRequest(url)
171
+ end
172
+
173
+ def self.create_dags(pan_address=nil, key=nil, device_name=nil, vsys_name=nil, tags_element=nil)
174
+ raise 'Options and key must be set' if pan_address.nil? || key.nil? || device_name.nil? || vsys_name.nil? || tags_element.nil?
175
+ url="https://#{pan_address}/api/?type=config&action=set&key=#{key}&xpath=/config/devices/entry[@name='#{device_name}']/vsys/entry[@name='#{vsys_name}']/address-group&element=#{tags_element}"
176
+ return curlRequest(url)
177
+ end
178
+
179
+ def self.generate_add_asset_xml(ip_addresses_details = [])
180
+ raise 'Need asset details to generate add asset xml.' if ip_addresses_details.nil?
181
+
182
+ xml = ''
183
+ ip_addresses_details.each do |ip_address, tags_string|
184
+ xml << "<entry ip=\"#{ip_address}\"><tag>"
185
+ tags_string.split(/<(.*?)>/).each do |tag|
186
+ xml << "<member>#{tag}</member>" unless tag.empty?
187
+ end
188
+ xml << "</tag></entry>"
189
+ end
190
+ #return URI.escape(xml)
191
+ return xml
192
+ end
193
+
194
+ def self.generate_remove_asset_xml(ip_addresses_details = [])
195
+ raise 'Need asset details to generate remove asset xml.' if ip_addresses_details.nil?
196
+
197
+ xml = ''
198
+ ip_addresses_details.each do |ip_address|
199
+ xml << "<entry ip=\"#{ip_address}\"></entry>"
200
+ end
201
+ return xml
202
+ end
203
+
204
+ def self.unregister_devices(pan_address=nil, key=nil, asset_details=nil, site_id=nil)
205
+ raise 'Options and key must be set' if pan_address.nil? || key.nil? || asset_details.nil? || site_id.nil?
206
+
207
+ #Extract a list of IPs from the asset details
208
+ ip_list=[]
209
+ asset_details.each {|ip_addess, tags| ip_list << ip_addess}
210
+
211
+ #Createa folder to hold the files
212
+ folder = "./paloalto/logs"
213
+ FileUtils.mkdir_p(folder) unless File.directory?(folder)
214
+
215
+ remove_file_list=[]
216
+ xml="<uid-message><version>1.0</version><type>update</type><payload><unregister>#{generate_remove_asset_xml(ip_list)}</unregister></payload></uid-message>"
217
+ remove_file = File.open("#{folder}/#{site_id}_remove.xml", 'w')
218
+ remove_file.puts(xml)
219
+ remove_file_list << remove_file.path
220
+ remove_file.close
221
+
222
+ remove_file_list.each do |filename|
223
+ curlRequestWithFile("https://#{pan_address}/api/?type=user-id&action=set&key=#{key}&client=wget&file-name=\"#{filename}\"", filename)
224
+ end
225
+ end
226
+
227
+ def self.register_devices(pan_address=nil, key=nil, asset_details=nil, site_id=nil)
228
+ raise 'Options and key must be set' if pan_address.nil? || key.nil? || asset_details.nil? || site_id.nil?
229
+
230
+ #Createa folder to hold the files
231
+ folder = "./paloalto/logs"
232
+ FileUtils.mkdir_p(folder) unless File.directory?(folder)
233
+
234
+ add_file_list=[]
235
+ #Add devices
236
+ xml="<uid-message><version>1.0</version><type>update</type><payload><register>#{generate_add_asset_xml(asset_details)}</register></payload></uid-message>"
237
+ add_file =File.open("#{folder}/#{site_id}_add.xml", 'w')
238
+ add_file.puts(xml)
239
+ add_file_list << add_file.path
240
+ add_file.close
241
+
242
+ add_file_list.each do |filename|
243
+ curlRequestWithFile("https://#{pan_address}/api/?type=user-id&action=set&key=#{key}&client=wget&&file-name=\"#{filename}\"", filename)
244
+ end
245
+ end
246
+
247
+ def self.register_dag_devices(pan_address=nil, key=nil, dag_parsed_details=nil)
248
+ raise 'Options and key must be set' if pan_address.nil? || key.nil? || dag_parsed_details.nil?
249
+
250
+ #Createa folder to hold the files
251
+ folder = "./paloalto/logs"
252
+ FileUtils.mkdir_p(folder) unless File.directory?(folder)
253
+
254
+ add_file_list=[]
255
+ #Add devices
256
+ xml="<uid-message><version>1.0</version><type>update</type><payload><register>#{generate_add_asset_xml(asset_details)}</register></payload></uid-message>"
257
+ add_file =File.open("#{folder}/dag_#{dag_details[0]}_add.xml", 'w')
258
+ add_file.puts(xml)
259
+ add_file_list << add_file.path
260
+ add_file.close
261
+
262
+ add_file_list.each do |filename|
263
+ puts curlRequestWithFile("https://#{pan_address}/api/?type=user-id&action=set&key=#{key}&client=wget&&file-name=\"#{filename}\"", filename)
264
+ end
265
+ end
266
+
267
+ end
268
+ end
@@ -0,0 +1,27 @@
1
+ module Paloalto
2
+ class NXLogger
3
+ LOGGER_FILE = File.join(File.dirname(__FILE__), './logs/rapid7_palo_alto.log')
4
+
5
+ attr_accessor :options
6
+
7
+ def initialize
8
+ setup_logging()
9
+ end
10
+
11
+ def setup_logging(enabled = true)
12
+ if enabled
13
+ require 'logger'
14
+ directory = File.dirname(LOGGER_FILE)
15
+ FileUtils.mkdir_p(directory) unless File.directory?(directory)
16
+ @log = Logger.new(LOGGER_FILE, 'monthly')
17
+ @log.level = Logger::INFO
18
+ log_message('Logging enabled for helper.')
19
+ end
20
+ end
21
+
22
+ # Logs a message
23
+ def log_message(message)
24
+ @log.info(message)
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,3 @@
1
+ module Paloalto
2
+ VERSION = "0.1.1"
3
+ end
@@ -0,0 +1,28 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'paloalto/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "nexpose_paloalto"
8
+ spec.version = Paloalto::VERSION
9
+ spec.authors = ['Damian Finol', 'JJ Cassidy']
10
+ spec.email = ['integrations_support@rapid7.com']
11
+
12
+ spec.summary = 'Nexpose Palo Alto Gem Integration'
13
+ spec.description = 'This Gem allows usage of Nexpose Dynamic Asset groups with Palo Alto TAGs.'
14
+ spec.homepage = "http://www.rapid7.com"
15
+ spec.license = "MIT"
16
+
17
+ spec.files = Dir['[A-Z]*'] + Dir['lib/**/*'] + Dir['bin/**']
18
+ spec.bindir = "exe"
19
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
20
+ spec.require_paths = ["lib"]
21
+
22
+ spec.add_development_dependency "bundler", "~> 1.8"
23
+ spec.add_development_dependency "rake", "~> 10.0"
24
+ spec.add_runtime_dependency 'nexpose', "~> 0.9"
25
+ spec.add_runtime_dependency 'curb', "~> 0.8.7"
26
+ spec.add_runtime_dependency 'nokogiri', "~> 1.6"
27
+ spec.required_ruby_version = '~> 2.0'
28
+ end
metadata ADDED
@@ -0,0 +1,128 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: nexpose_paloalto
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ platform: ruby
6
+ authors:
7
+ - Damian Finol
8
+ - JJ Cassidy
9
+ autorequire:
10
+ bindir: exe
11
+ cert_chain: []
12
+ date: 2015-04-01 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: bundler
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - "~>"
19
+ - !ruby/object:Gem::Version
20
+ version: '1.8'
21
+ type: :development
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - "~>"
26
+ - !ruby/object:Gem::Version
27
+ version: '1.8'
28
+ - !ruby/object:Gem::Dependency
29
+ name: rake
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - "~>"
33
+ - !ruby/object:Gem::Version
34
+ version: '10.0'
35
+ type: :development
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - "~>"
40
+ - !ruby/object:Gem::Version
41
+ version: '10.0'
42
+ - !ruby/object:Gem::Dependency
43
+ name: nexpose
44
+ requirement: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - "~>"
47
+ - !ruby/object:Gem::Version
48
+ version: '0.9'
49
+ type: :runtime
50
+ prerelease: false
51
+ version_requirements: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - "~>"
54
+ - !ruby/object:Gem::Version
55
+ version: '0.9'
56
+ - !ruby/object:Gem::Dependency
57
+ name: curb
58
+ requirement: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - "~>"
61
+ - !ruby/object:Gem::Version
62
+ version: 0.8.7
63
+ type: :runtime
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - "~>"
68
+ - !ruby/object:Gem::Version
69
+ version: 0.8.7
70
+ - !ruby/object:Gem::Dependency
71
+ name: nokogiri
72
+ requirement: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - "~>"
75
+ - !ruby/object:Gem::Version
76
+ version: '1.6'
77
+ type: :runtime
78
+ prerelease: false
79
+ version_requirements: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - "~>"
82
+ - !ruby/object:Gem::Version
83
+ version: '1.6'
84
+ description: This Gem allows usage of Nexpose Dynamic Asset groups with Palo Alto
85
+ TAGs.
86
+ email:
87
+ - integrations_support@rapid7.com
88
+ executables: []
89
+ extensions: []
90
+ extra_rdoc_files: []
91
+ files:
92
+ - Gemfile
93
+ - Gemfile.lock
94
+ - LICENSE.txt
95
+ - README.md
96
+ - Rakefile
97
+ - lib/bin/nexpose_paloalto.rb
98
+ - lib/paloalto.rb
99
+ - lib/paloalto/nexpose_helper.rb
100
+ - lib/paloalto/ngfw.rb
101
+ - lib/paloalto/nx_logger.rb
102
+ - lib/paloalto/version.rb
103
+ - nexpose_paloalto.gemspec
104
+ homepage: http://www.rapid7.com
105
+ licenses:
106
+ - MIT
107
+ metadata: {}
108
+ post_install_message:
109
+ rdoc_options: []
110
+ require_paths:
111
+ - lib
112
+ required_ruby_version: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - "~>"
115
+ - !ruby/object:Gem::Version
116
+ version: '2.0'
117
+ required_rubygems_version: !ruby/object:Gem::Requirement
118
+ requirements:
119
+ - - ">="
120
+ - !ruby/object:Gem::Version
121
+ version: '0'
122
+ requirements: []
123
+ rubyforge_project:
124
+ rubygems_version: 2.4.4
125
+ signing_key:
126
+ specification_version: 4
127
+ summary: Nexpose Palo Alto Gem Integration
128
+ test_files: []