foreman_probing 0.0.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 +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
|