foreman_probing 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE +619 -0
- data/README.md +102 -0
- data/Rakefile +47 -0
- data/app/controllers/foreman_probing/api/v2/scans_controller.rb +66 -0
- data/app/controllers/foreman_probing/hosts_controller.rb +14 -0
- data/app/controllers/foreman_probing/scans_controller.rb +47 -0
- data/app/helpers/concerns/foreman_probing/hosts_helper_extensions.rb +9 -0
- data/app/helpers/concerns/foreman_probing/subnet_helper_extensions.rb +7 -0
- data/app/helpers/foreman_probing/scans_helper.rb +42 -0
- data/app/lib/actions/create_subnets.rb +27 -0
- data/app/lib/actions/import_host_facts.rb +85 -0
- data/app/lib/actions/perform_scan.rb +26 -0
- data/app/lib/actions/process_host.rb +18 -0
- data/app/lib/actions/process_scan.rb +21 -0
- data/app/lib/actions/scan_host.rb +35 -0
- data/app/lib/actions/update_probing_facet.rb +44 -0
- data/app/models/concerns/foreman_probing/foreman_tasks_task_extensions.rb +9 -0
- data/app/models/concerns/foreman_probing/host_extensions.rb +10 -0
- data/app/models/concerns/foreman_probing/subnet_extensions.rb +21 -0
- data/app/models/foreman_probing/fact_name.rb +11 -0
- data/app/models/foreman_probing/port.rb +21 -0
- data/app/models/foreman_probing/probing_facet.rb +27 -0
- data/app/models/foreman_probing/scan.rb +42 -0
- data/app/models/foreman_probing/scan_composer.rb +75 -0
- data/app/models/foreman_probing/scan_host.rb +8 -0
- data/app/models/foreman_probing/service.rb +14 -0
- data/app/models/foreman_probing/targeting/direct.rb +50 -0
- data/app/models/foreman_probing/targeting/search.rb +23 -0
- data/app/models/foreman_probing/targeting/subnet.rb +17 -0
- data/app/models/foreman_probing/targeting/subnet_discovery.rb +12 -0
- data/app/models/foreman_probing/targeting.rb +12 -0
- data/app/overrides/dashboard/index/sample_override.html.erb.deface +4 -0
- data/app/overrides/open_ports_tab.rb +14 -0
- data/app/services/foreman_probing/fact_parser.rb +47 -0
- data/app/services/foreman_probing/structured_fact_importer.rb +20 -0
- data/app/views/dashboard/_foreman_probing_widget.html.erb +2 -0
- data/app/views/foreman_probing/api/v2/scans/base.json.rabl +3 -0
- data/app/views/foreman_probing/api/v2/scans/index.json.rabl +3 -0
- data/app/views/foreman_probing/api/v2/scans/show.json.rabl +3 -0
- data/app/views/foreman_probing/hosts/_open_ports.html.erb +19 -0
- data/app/views/foreman_probing/hosts/hosts/new_action.html.erb +1 -0
- data/app/views/foreman_probing/hosts/new_action.html.erb +1 -0
- data/app/views/foreman_probing/layouts/layouts/new_layout.html.erb +0 -0
- data/app/views/foreman_probing/layouts/new_layout.html.erb +0 -0
- data/app/views/foreman_probing/probing_facets/_open_ports_tab_content.html.erb +6 -0
- data/app/views/foreman_probing/probing_facets/_open_ports_tab_title.html.erb +4 -0
- data/app/views/foreman_probing/scans/index.html.erb +31 -0
- data/app/views/foreman_probing/scans/new.html.erb +51 -0
- data/app/views/foreman_probing/scans/show.html.erb +41 -0
- data/app/views/foreman_probing/scans/show.js.erb +0 -0
- data/app/views/templates/ssh/add_public_keys.erb +22 -0
- data/app/views/templates/ssh/register_content_host.erb +34 -0
- data/app/views/templates/ssh/register_puppet.erb +19 -0
- data/config/routes.rb +28 -0
- data/db/migrate/20170401154201_create_foreman_probing_facets.rb +9 -0
- data/db/migrate/20170401154714_create_foreman_probing_ports.rb +13 -0
- data/db/migrate/20170401154726_create_foreman_probing_services.rb +10 -0
- data/db/migrate/20170620180935_create_foreman_probing_scans.rb +15 -0
- data/db/migrate/20170701190511_create_foreman_probing_targetings.rb +9 -0
- data/db/seeds.d/70-job_templates.rb +7 -0
- data/lib/foreman_probing/engine.rb +124 -0
- data/lib/foreman_probing/version.rb +3 -0
- data/lib/foreman_probing.rb +5 -0
- data/locale/Makefile +60 -0
- data/locale/en/foreman_probing.po +19 -0
- data/locale/foreman_probing.pot +19 -0
- data/locale/gemspec.rb +2 -0
- data/test/factories/foreman_probing_factories.rb +5 -0
- data/test/test_plugin_helper.rb +6 -0
- data/test/unit/foreman_probing_test.rb +11 -0
- metadata +159 -0
data/README.md
ADDED
@@ -0,0 +1,102 @@
|
|
1
|
+
# ForemanProbing
|
2
|
+
A plugin enabling Foreman to detect machines on the network and make the show up
|
3
|
+
as Hosts.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
The installation is split into four parts. General prerequisites and installing
|
7
|
+
Foreman, Smart Proxy and Smart Proxy Dynflow Core plugins
|
8
|
+
|
9
|
+
### Prerequisites
|
10
|
+
1. Install nmap
|
11
|
+
2. This assumes the following directory structure
|
12
|
+
|
13
|
+
```
|
14
|
+
$PROJECT_ROOT
|
15
|
+
+- foreman
|
16
|
+
+- foreman_probing
|
17
|
+
+- smart-proxy-probing
|
18
|
+
+- smart_proxy_dynflow
|
19
|
+
`- smart-proxy
|
20
|
+
```
|
21
|
+
|
22
|
+
### Installing the Foreman plugin
|
23
|
+
1. Clone this repository
|
24
|
+
2. Tell Foreman to load the gem
|
25
|
+
|
26
|
+
```shell
|
27
|
+
# In Foreman's checkout
|
28
|
+
cat <<-END > bundler.d/foreman_probing.rb
|
29
|
+
gem 'foreman_probing', :path => '../foreman_probing'
|
30
|
+
END
|
31
|
+
```
|
32
|
+
|
33
|
+
3. Run `bundle install`
|
34
|
+
4. Run the migrations and seeds
|
35
|
+
|
36
|
+
```shell
|
37
|
+
bundle exec rake db:migrate
|
38
|
+
bundle exec rake db:seed
|
39
|
+
```
|
40
|
+
|
41
|
+
### Installing the Smart Proxy plugin
|
42
|
+
1. Clone the `smart-proxy-probing` repository
|
43
|
+
2. Enable the plugin
|
44
|
+
```shell
|
45
|
+
# In smart-proxy checkout
|
46
|
+
cat <<-END > config/settings.d/probing.yml
|
47
|
+
---
|
48
|
+
:enabled: true
|
49
|
+
END
|
50
|
+
|
51
|
+
cat <<-END > bundler.d/smart-proxy-probing.rb
|
52
|
+
gem 'smart-proxy-probing', :path => '../smart-proxy-probing'
|
53
|
+
END
|
54
|
+
```
|
55
|
+
3. Run `bundle install`
|
56
|
+
|
57
|
+
### Installing the Smart Proxy Dynflow Core plugin
|
58
|
+
1. Clone this repository
|
59
|
+
2. Enable the plugin
|
60
|
+
|
61
|
+
```shell
|
62
|
+
# in smart_proxy_dynflow
|
63
|
+
cat <<-END > bundler.d/foreman_probing_core.rb
|
64
|
+
gem 'foreman_probing_core', :path => '../foreman_probing'
|
65
|
+
END
|
66
|
+
```
|
67
|
+
|
68
|
+
## Usage
|
69
|
+
1. Navigate to Monitor > Network scans
|
70
|
+
2. Click Run Scan
|
71
|
+
3. Fill out the form
|
72
|
+
4. Run the scan
|
73
|
+
5. Wait for it to finish
|
74
|
+
6. Observe Hosts created for the scanned machines
|
75
|
+
7. Use Remote Execution or Ansible to manage the hosts
|
76
|
+
8. (optional) Use Remote Execution or Ansible to deploy agents (Puppet, Katello,
|
77
|
+
Chef, Salt minion...)
|
78
|
+
|
79
|
+
## Ansible roles
|
80
|
+
Some of the roles need `foreman_url` parameter to be set. It would be probably best to set this parameter as global and be done with it.
|
81
|
+
|
82
|
+
## Contributing
|
83
|
+
|
84
|
+
Fork and send a Pull Request. Thanks!
|
85
|
+
|
86
|
+
## Copyright
|
87
|
+
|
88
|
+
Copyright (c) *2017* *Adam Ruzicka*
|
89
|
+
|
90
|
+
This program is free software: you can redistribute it and/or modify
|
91
|
+
it under the terms of the GNU General Public License as published by
|
92
|
+
the Free Software Foundation, either version 3 of the License, or
|
93
|
+
(at your option) any later version.
|
94
|
+
|
95
|
+
This program is distributed in the hope that it will be useful,
|
96
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
97
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
98
|
+
GNU General Public License for more details.
|
99
|
+
|
100
|
+
You should have received a copy of the GNU General Public License
|
101
|
+
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
102
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
#!/usr/bin/env rake
|
2
|
+
begin
|
3
|
+
require 'bundler/setup'
|
4
|
+
rescue LoadError
|
5
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
6
|
+
end
|
7
|
+
begin
|
8
|
+
require 'rdoc/task'
|
9
|
+
rescue LoadError
|
10
|
+
require 'rdoc/rdoc'
|
11
|
+
require 'rake/rdoctask'
|
12
|
+
RDoc::Task = Rake::RDocTask
|
13
|
+
end
|
14
|
+
|
15
|
+
RDoc::Task.new(:rdoc) do |rdoc|
|
16
|
+
rdoc.rdoc_dir = 'rdoc'
|
17
|
+
rdoc.title = 'ForemanProbing'
|
18
|
+
rdoc.options << '--line-numbers'
|
19
|
+
rdoc.rdoc_files.include('README.rdoc')
|
20
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
21
|
+
end
|
22
|
+
|
23
|
+
APP_RAKEFILE = File.expand_path('../test/dummy/Rakefile', __FILE__)
|
24
|
+
|
25
|
+
Bundler::GemHelper.install_tasks
|
26
|
+
|
27
|
+
require 'rake/testtask'
|
28
|
+
|
29
|
+
Rake::TestTask.new(:test) do |t|
|
30
|
+
t.libs << 'lib'
|
31
|
+
t.libs << 'test'
|
32
|
+
t.pattern = 'test/**/*_test.rb'
|
33
|
+
t.verbose = false
|
34
|
+
end
|
35
|
+
|
36
|
+
task default: :test
|
37
|
+
|
38
|
+
begin
|
39
|
+
require 'rubocop/rake_task'
|
40
|
+
RuboCop::RakeTask.new
|
41
|
+
rescue => _
|
42
|
+
puts 'Rubocop not loaded.'
|
43
|
+
end
|
44
|
+
|
45
|
+
task :default do
|
46
|
+
Rake::Task['rubocop'].execute
|
47
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
module ForemanProbing
|
2
|
+
module Api::V2
|
3
|
+
class ScansController < ::Api::V2::BaseController
|
4
|
+
include ::Api::Version2
|
5
|
+
include ::Foreman::Renderer
|
6
|
+
|
7
|
+
resource_description do
|
8
|
+
resource_id 'foreman_probing'
|
9
|
+
api_version 'v2'
|
10
|
+
api_base_url '/foreman_probing/api'
|
11
|
+
end
|
12
|
+
|
13
|
+
before_action :find_resource, :only => %w{show rerun}
|
14
|
+
|
15
|
+
def resource_class
|
16
|
+
ForemanProbing::Scan
|
17
|
+
end
|
18
|
+
|
19
|
+
api :GET, '/scans', N_('List scans')
|
20
|
+
param_group :search_and_pagination, ::Api::V2::BaseController
|
21
|
+
def index
|
22
|
+
@scans = resource_scope.order(:id => 'desc').paginate(:page => params[:page])
|
23
|
+
end
|
24
|
+
|
25
|
+
api :POST, '/scans', N_('Create a scan')
|
26
|
+
param :targeting_type, String, :required => true,
|
27
|
+
:desc => N_('Type of targeting, one of %{options}') % { :options => ForemanProbing::ScanComposer::TARGETING_TYPES.join(', ') }
|
28
|
+
param :scan_type, String, :required => true, :desc => N_('Type of the scan, one of %{options}') % { :options => %w{TCP UDP ICMP}.join(', ') }
|
29
|
+
param :ports, String, :desc => N_('The ports to probe')
|
30
|
+
param :proxy_id, :identifier, :required => true, :desc => N_('The smart proxy to run the scan from')
|
31
|
+
param :direct, String, :desc => N_('Comma separated list of IPv4 addresses, subnets or ranges')
|
32
|
+
param :subnet_id, :identifier, :desc => N_('ID of subnet to scan')
|
33
|
+
param :search_query, String, :desc => N_('Scan hosts matching the search query')
|
34
|
+
def create
|
35
|
+
@composer = ScanComposer.new_from_params(params[:foreman_probing_scan])
|
36
|
+
@scan = @composer.compose!
|
37
|
+
@scan.save!
|
38
|
+
task = ForemanTasks.async_task(::Actions::ForemanProbing::PerformScan,
|
39
|
+
@scan,
|
40
|
+
@scan.ports)
|
41
|
+
@scan.task = task
|
42
|
+
@scan.save!
|
43
|
+
set_auto_refresh
|
44
|
+
redirect_to @scan
|
45
|
+
end
|
46
|
+
|
47
|
+
api :POST, '/scans/:id', N_('Rerun scan')
|
48
|
+
param :id, :identifier, :required => true, :desc => N_('ID of scan to rerun')
|
49
|
+
def rerun
|
50
|
+
composer = ScanComposer.new_from_scan(ForemanProbing::Scan.find(params['id']))
|
51
|
+
@scan = composer.compose!
|
52
|
+
@scan.save!
|
53
|
+
task = ForemanTasks.async_task(::Actions::ForemanProbing::PerformScan,
|
54
|
+
@scan,
|
55
|
+
@scan.ports)
|
56
|
+
@scan.task = task
|
57
|
+
@scan.save!
|
58
|
+
render :action => 'show'
|
59
|
+
end
|
60
|
+
|
61
|
+
api :GET, '/scans/:id', N_('Show scan')
|
62
|
+
param :id, :identifier, :required => true, :desc => N_('ID of scan to show')
|
63
|
+
def show; end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module ForemanProbing
|
2
|
+
# Example: Plugin's HostsController inherits from Foreman's HostsController
|
3
|
+
class HostsController < ::HostsController
|
4
|
+
before_action :find_resource, :only => :open_ports
|
5
|
+
|
6
|
+
def open_ports
|
7
|
+
render :partial => 'open_ports'
|
8
|
+
end
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
define_action_permission ['open_ports'], :view
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module ForemanProbing
|
2
|
+
class ScansController < ::ApplicationController
|
3
|
+
|
4
|
+
def index
|
5
|
+
@scans = resource_base.order(:id => 'desc').paginate(:page => params[:page])
|
6
|
+
end
|
7
|
+
|
8
|
+
def new
|
9
|
+
@scan = ForemanProbing::Scan.new
|
10
|
+
@scan.target_kind = params.fetch(:target_kind, 'direct')
|
11
|
+
@scan.search_query = params[:search_query]
|
12
|
+
@scan.targeting = ::ForemanProbing::Targeting.new
|
13
|
+
end
|
14
|
+
|
15
|
+
def create
|
16
|
+
@composer = ScanComposer.new_from_params(params[:foreman_probing_scan])
|
17
|
+
@scan = @composer.compose!
|
18
|
+
@scan.save!
|
19
|
+
task = ForemanTasks.async_task(::Actions::ForemanProbing::PerformScan,
|
20
|
+
@scan,
|
21
|
+
@scan.ports)
|
22
|
+
@scan.task = task
|
23
|
+
@scan.save!
|
24
|
+
set_auto_refresh
|
25
|
+
redirect_to @scan
|
26
|
+
end
|
27
|
+
|
28
|
+
def rerun
|
29
|
+
composer = ScanComposer.new_from_scan(ForemanProbing::Scan.find(params['id']))
|
30
|
+
@scan = composer.compose!
|
31
|
+
|
32
|
+
render :action => 'new'
|
33
|
+
end
|
34
|
+
|
35
|
+
def show
|
36
|
+
@scan = ::ForemanProbing::Scan.find(params['id'])
|
37
|
+
set_auto_refresh
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def set_auto_refresh
|
43
|
+
@auto_refresh = @scan.task.try(:pending?)
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
module ForemanProbing
|
2
|
+
module HostsHelperExtensions
|
3
|
+
def host_title_actions(*args)
|
4
|
+
title_actions(button_group(link_to(_('Probe'), new_foreman_probing_scan_path(:search_query => "name = #{args.first.name}", :target_kind => 'host'), :id => :run_button, :class => 'btn btn-default')))
|
5
|
+
|
6
|
+
super(*args)
|
7
|
+
end
|
8
|
+
end
|
9
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module ForemanProbing
|
2
|
+
module ScansHelper
|
3
|
+
def scan_task_buttons(scan)
|
4
|
+
task = scan.task
|
5
|
+
task_authorizer = Authorizer.new(User.current, :collection => [task])
|
6
|
+
buttons = []
|
7
|
+
buttons << link_to(_('Rerun'), rerun_foreman_probing_scan_path(scan), :class => 'btn btn-default', :title => _('Refresh this page'))
|
8
|
+
buttons << link_to(_('Refresh'), {}, :class => 'btn btn-default', :title => _('Refresh this page'))
|
9
|
+
# if authorized_for(hash_for_new_job_invocation_path)
|
10
|
+
# buttons << link_to(_('Rerun'), rerun_job_invocation_path(:id => job_invocation.id),
|
11
|
+
# :class => 'btn btn-default',
|
12
|
+
# :title => _('Rerun the job'))
|
13
|
+
# end
|
14
|
+
if authorized_for(:permission => :view_foreman_tasks, :auth_object => task, :authorizer => task_authorizer)
|
15
|
+
buttons << link_to(_('Task'), foreman_tasks_task_path(task),
|
16
|
+
:class => 'btn btn-default',
|
17
|
+
:title => _('See the task details'))
|
18
|
+
end
|
19
|
+
if authorized_for(:permission => :edit_foreman_tasks, :auth_object => task, :authorizer => task_authorizer)
|
20
|
+
buttons << link_to(_('Cancel Task'), cancel_foreman_tasks_task_path(task),
|
21
|
+
:class => 'btn btn-danger',
|
22
|
+
:title => _('Try to cancel the task'),
|
23
|
+
:disabled => !task.cancellable?,
|
24
|
+
:method => :post)
|
25
|
+
end
|
26
|
+
return buttons
|
27
|
+
end
|
28
|
+
|
29
|
+
def targeting_label(targeting)
|
30
|
+
case targeting
|
31
|
+
when ::ForemanProbing::Targeting::Search
|
32
|
+
"with search query '#{targeting.raw_targets}'"
|
33
|
+
when ::ForemanProbing::Targeting::Direct
|
34
|
+
"direct '#{targeting.raw_targets}'"
|
35
|
+
when ::ForemanProbing::Targeting::SubnetDiscovery
|
36
|
+
'subnet discovery'
|
37
|
+
when ::ForemanProbing::Targeting::Subnet
|
38
|
+
"subnet #{targeting.subnet.name}"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Actions
|
2
|
+
module ForemanProbing
|
3
|
+
class CreateSubnets < Actions::EntryAction
|
4
|
+
middleware.use Actions::Middleware::KeepCurrentUser
|
5
|
+
def run
|
6
|
+
proxy = SmartProxy.find(input[:proxy_id])
|
7
|
+
output[:subnet_ids] = input[:scan][:proxy_output][:local_addresses].map do |str, hash|
|
8
|
+
network_addr = IPAddr.new(hash[:addr]).mask(hash[:cidr]).to_s
|
9
|
+
subnet = Subnet.where(:network => network_addr, :mask => hash[:netmask]).first
|
10
|
+
if subnet.nil?
|
11
|
+
subnet = Subnet.new
|
12
|
+
subnet.network = network_addr
|
13
|
+
subnet.mask = hash[:netmask]
|
14
|
+
subnet.name = "#{str} at #{proxy.name}"
|
15
|
+
subnet.location_ids = proxy.location_ids
|
16
|
+
subnet.organization_ids = proxy.organization_ids
|
17
|
+
subnet.save!
|
18
|
+
end
|
19
|
+
proxy.subnets << subnet
|
20
|
+
proxy.save!
|
21
|
+
subnet.id
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
module Actions
|
2
|
+
module ForemanProbing
|
3
|
+
class ImportHostFacts < ::Dynflow::Action
|
4
|
+
middleware.use Actions::Middleware::KeepCurrentUser
|
5
|
+
def run
|
6
|
+
facts = input[:facts]
|
7
|
+
# If we're not scanning an already existing host and it is down, we don't want to import it to Foreman
|
8
|
+
unless (input[:options][:host_id].nil? && facts.fetch(:status, {})[:state] == 'down')
|
9
|
+
host = determine_host(facts)
|
10
|
+
facts = try_match_interface_names(host, facts)
|
11
|
+
facts.delete(:hostnames)
|
12
|
+
::User.as :admin do
|
13
|
+
state = host.import_facts(facts)
|
14
|
+
output[:state] = state
|
15
|
+
output[:facts] = facts
|
16
|
+
end
|
17
|
+
try_set_subnet!(host)
|
18
|
+
host.smart_proxy_ids << input[:proxy_id]
|
19
|
+
output[:host_id] = host.id
|
20
|
+
output[:hostname] = host.name
|
21
|
+
scan = ::ForemanProbing::Scan.find(input[:scan_id])
|
22
|
+
host.organization_id ||= scan.organization_ids.first
|
23
|
+
host.location_id ||= scan.location_ids.first
|
24
|
+
host.scans << scan
|
25
|
+
host.save!
|
26
|
+
end
|
27
|
+
rescue ::Foreman::Exception => e
|
28
|
+
# This error is what is thrown by Host#ImportHostAndFacts when
|
29
|
+
# the Host is in the build state. This can be refactored once
|
30
|
+
# issue #3959 is fixed.
|
31
|
+
raise e unless e.code == 'ERF51-9911'
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def try_match_interface_names(host, facts)
|
37
|
+
names = host.interfaces.where(:mac => facts.fetch(:addresses, {}).fetch(:hwaddr, {}).keys).map(&:identifier)
|
38
|
+
if names.count != facts[:addresses][:ipv4].keys.count
|
39
|
+
names = facts[:addresses][:ipv4].keys.count.times.map { |i| "unknown#{i}" }
|
40
|
+
end
|
41
|
+
names.each_with_index do |name, i|
|
42
|
+
[:ipv4, :ipv6, :hwaddr].each do |kind|
|
43
|
+
addr = facts[:addresses].fetch(kind, {}).keys[i]
|
44
|
+
facts[:addresses].fetch(kind, {}).fetch(addr, {})[:identifier] = name
|
45
|
+
end
|
46
|
+
end
|
47
|
+
facts
|
48
|
+
end
|
49
|
+
|
50
|
+
def try_set_subnet!(host)
|
51
|
+
return unless host.subnet.nil? # We don't want to redefine already set subnet
|
52
|
+
subnet = if input[:subnet_id]
|
53
|
+
Subnet.find(input[:subnet_id])
|
54
|
+
else
|
55
|
+
Subnet.all.find { |subnet| subnet.ipaddr.include? host.ip } # Try to find a defined subnet
|
56
|
+
end
|
57
|
+
if subnet
|
58
|
+
host.location_id = subnet.location_ids.first
|
59
|
+
host.organization_id = subnet.organization_ids.first
|
60
|
+
host.subnet = subnet
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def determine_host(facts)
|
65
|
+
macs = facts[:addresses].fetch(:hwaddr, {}).keys
|
66
|
+
unless macs.empty?
|
67
|
+
ifaces = ::Nic::Managed.where(:mac => macs)
|
68
|
+
return ifaces.first.host unless ifaces.empty?
|
69
|
+
end
|
70
|
+
Host::Managed.import_host(determine_hostname(facts).dup)
|
71
|
+
end
|
72
|
+
|
73
|
+
def determine_hostname(facts)
|
74
|
+
# Try to use first of its hostnames, fallback to some of its addresses
|
75
|
+
if !facts[:hostnames].empty?
|
76
|
+
facts[:hostnames].first[:name]
|
77
|
+
else
|
78
|
+
name = facts[:addresses].map { |_kind, values| values.keys }.flatten.first
|
79
|
+
raise 'Cannot determine host name' if name.nil?
|
80
|
+
name
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Actions
|
2
|
+
module ForemanProbing
|
3
|
+
class PerformScan < Actions::EntryAction
|
4
|
+
include ::Actions::Helpers::WithContinuousOutput
|
5
|
+
include ::Actions::Helpers::WithDelegatedAction
|
6
|
+
|
7
|
+
# middleware.do_not_use Dynflow::Middleware::Common::Transaction
|
8
|
+
middleware.use Actions::Middleware::KeepCurrentUser
|
9
|
+
|
10
|
+
def plan(scan, ports, options = {})
|
11
|
+
options[:subnet_discovery] = true if scan.targeting.is_a? ::ForemanProbing::Targeting::SubnetDiscovery
|
12
|
+
scanned = plan_delegated_action(scan.smart_proxy, 'ForemanProbingCore::Actions::UseProbe',
|
13
|
+
:targets => scan.targeting.targets,
|
14
|
+
:scan_type => scan.scan_type,
|
15
|
+
:ports => ports,
|
16
|
+
:options => options)
|
17
|
+
plan_action(CreateSubnets, :proxy_id => scan.smart_proxy_id, :scan => scanned.output) if scan.targeting.is_a? ::ForemanProbing::Targeting::SubnetDiscovery
|
18
|
+
plan_action(::Actions::ForemanProbing::ProcessScan,
|
19
|
+
:scan_id => scan.id,
|
20
|
+
:scan => scanned.output,
|
21
|
+
:proxy_id => scan.smart_proxy_id,
|
22
|
+
:options => options)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Actions
|
2
|
+
module ForemanProbing
|
3
|
+
class ProcessHost < Actions::EntryAction
|
4
|
+
middleware.use Actions::Middleware::KeepCurrentUser
|
5
|
+
def plan(input)
|
6
|
+
sequence do
|
7
|
+
parsed_scan = plan_action(::Actions::ForemanProbing::ImportHostFacts,
|
8
|
+
:scan_id => input[:scan_id],
|
9
|
+
:facts => input[:facts],
|
10
|
+
:proxy_id => input[:proxy_id],
|
11
|
+
:options => input[:options])
|
12
|
+
plan_action(::Actions::ForemanProbing::UpdateProbingFacet, :parsed_scan => parsed_scan.output)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Actions
|
2
|
+
module ForemanProbing
|
3
|
+
class ProcessScan < Actions::ActionWithSubPlans
|
4
|
+
|
5
|
+
def plan(*args)
|
6
|
+
plan_self(*args)
|
7
|
+
end
|
8
|
+
|
9
|
+
def create_sub_plans
|
10
|
+
input[:scan][:proxy_output][:facts].map do |report|
|
11
|
+
trigger(::Actions::ForemanProbing::ProcessHost,
|
12
|
+
:scan_id => input[:scan_id],
|
13
|
+
:facts => report,
|
14
|
+
:proxy_id => input[:proxy_id],
|
15
|
+
:options => input[:options] || {})
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Actions
|
2
|
+
module ForemanProbing
|
3
|
+
class ScanHost < Actions::EntryAction
|
4
|
+
middleware.use Actions::Middleware::KeepCurrentUser
|
5
|
+
include ::Actions::Helpers::WithDelegatedAction
|
6
|
+
|
7
|
+
middleware.do_not_use Dynflow::Middleware::Common::Transaction
|
8
|
+
|
9
|
+
def plan(host, probes, proxy_selector = ::ForemanProbingProxySelector.new, port_overrides = {})
|
10
|
+
action_subject(host, :probes => probes.map(&:to_s))
|
11
|
+
|
12
|
+
hostname = find_ip_or_hostname(host)
|
13
|
+
proxy = proxy_selector.determine_proxy(host)
|
14
|
+
plan_delegated_action(proxy, ForemanProbingCore::Actions::UseProbe, hostname, probes, port_overrides)
|
15
|
+
plan_self
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def find_ip_or_hostname(host)
|
21
|
+
%w(execution primary provision).each do |flag|
|
22
|
+
interface = host.send(flag + '_interface')
|
23
|
+
return interface.ip if interface && interface.ip.present?
|
24
|
+
end
|
25
|
+
|
26
|
+
host.interfaces.each do |interface|
|
27
|
+
return interface.ip unless interface.ip.blank?
|
28
|
+
end
|
29
|
+
|
30
|
+
return host.fqdn
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module Actions
|
2
|
+
module ForemanProbing
|
3
|
+
class UpdateProbingFacet < Actions::EntryAction
|
4
|
+
|
5
|
+
middleware.use Actions::Middleware::KeepCurrentUser
|
6
|
+
|
7
|
+
def run
|
8
|
+
data = input[:parsed_scan]
|
9
|
+
if data.key? :host_id
|
10
|
+
host = Host.find(data[:host_id])
|
11
|
+
host.probing_facet ||= ::ForemanProbing::ProbingFacet.new
|
12
|
+
host.probing_facet.status = data['facts'].fetch('status', {}).fetch('state', 'down')
|
13
|
+
|
14
|
+
%w(tcp udp).each do |protocol|
|
15
|
+
update_ports protocol, host.probing_facet
|
16
|
+
end
|
17
|
+
|
18
|
+
host.probing_facet.save!
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def rescue_strategy
|
23
|
+
Dynflow::Action::Rescue::Skip
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def update_ports(protocol, facet)
|
29
|
+
protocol_base = input[:parsed_scan]['facts'].fetch(:ports, {}).fetch(protocol, {})
|
30
|
+
up, down = protocol_base.partition { |number, value| value['state'] == 'open' }
|
31
|
+
.map { |part| part.map(&:first).map(&:to_i) }
|
32
|
+
known = facet.scanned_ports.where(:protocol => protocol).pluck(:number)
|
33
|
+
to_create = up - known
|
34
|
+
to_update = known & up
|
35
|
+
to_remove = known & down
|
36
|
+
facet.scanned_ports.where(:protocol => protocol, :number => to_remove).delete_all
|
37
|
+
facet.scanned_ports.where(:protocol => protocol, :number => to_update).each(&:touch)
|
38
|
+
to_create.each do |number|
|
39
|
+
facet.scanned_ports << ::ForemanProbing::Port.new(:protocol => protocol, :number => number, :state => protocol_base[number.to_s]['state'])
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
module ForemanProbing
|
2
|
+
module HostExtensions
|
3
|
+
def self.prepended(base)
|
4
|
+
base.instance_eval do
|
5
|
+
has_many :scan_hosts, :class_name => '::ForemanProbing::ScanHost', :foreign_key => :host_id
|
6
|
+
has_many :scans, :through => :scan_hosts, :class_name => '::ForemanProbing::Scan'
|
7
|
+
end
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module ForemanProbing
|
2
|
+
module SubnetExtensions
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
# execute callbacks
|
7
|
+
has_many :probing_proxies, :dependent => :destroy
|
8
|
+
end
|
9
|
+
|
10
|
+
# create or overwrite instance methods...
|
11
|
+
def probe!(probe)
|
12
|
+
|
13
|
+
end
|
14
|
+
|
15
|
+
module ClassMethods
|
16
|
+
# create or overwrite class methods...
|
17
|
+
def class_method_name
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
module ForemanProbing
|
2
|
+
class FactName < ::FactName
|
3
|
+
# Define the class that fact names that come from Ansible should have
|
4
|
+
# It allows us to filter facts by origin, and also to display the origin
|
5
|
+
# in the fact values table (/fact_values)
|
6
|
+
def origin
|
7
|
+
'foreman_probing/Network Scan'
|
8
|
+
end
|
9
|
+
|
10
|
+
end
|
11
|
+
end
|