nexpose_paloalto 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
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: []