dtk-node-agent 0.7.7 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -13
- data/README.md +21 -4
- data/bin/dtk-node-agent +17 -0
- data/lib/config/install.config +2 -2
- data/lib/dtk-node-agent/installer.rb +30 -25
- data/lib/dtk-node-agent/version.rb +18 -1
- metadata +23 -110
- data/mcollective_additions/debian.mcollective.init +0 -92
- data/mcollective_additions/plugins/README.md +0 -1
- data/mcollective_additions/plugins/v1.2/agent/discovery.rb +0 -39
- data/mcollective_additions/plugins/v1.2/agent/get_log_fragment.ddl +0 -15
- data/mcollective_additions/plugins/v1.2/agent/get_log_fragment.rb +0 -79
- data/mcollective_additions/plugins/v1.2/agent/git_access.ddl +0 -9
- data/mcollective_additions/plugins/v1.2/agent/git_access.rb +0 -79
- data/mcollective_additions/plugins/v1.2/agent/netstat.ddl +0 -9
- data/mcollective_additions/plugins/v1.2/agent/netstat.rb +0 -34
- data/mcollective_additions/plugins/v1.2/agent/puppet_apply.ddl +0 -9
- data/mcollective_additions/plugins/v1.2/agent/puppet_apply.rb +0 -630
- data/mcollective_additions/plugins/v1.2/agent/rpcutil.ddl +0 -204
- data/mcollective_additions/plugins/v1.2/agent/rpcutil.rb +0 -101
- data/mcollective_additions/plugins/v1.2/facts/pbuilder_facts.rb +0 -35
- data/mcollective_additions/plugins/v2.2/agent/action_agent.ddl +0 -9
- data/mcollective_additions/plugins/v2.2/agent/action_agent.rb +0 -47
- data/mcollective_additions/plugins/v2.2/agent/dev_manager.ddl +0 -9
- data/mcollective_additions/plugins/v2.2/agent/dev_manager.rb +0 -111
- data/mcollective_additions/plugins/v2.2/agent/discovery.rb +0 -39
- data/mcollective_additions/plugins/v2.2/agent/dtk_node_agent_git_client.rb +0 -94
- data/mcollective_additions/plugins/v2.2/agent/execute_tests.ddl +0 -9
- data/mcollective_additions/plugins/v2.2/agent/execute_tests.rb +0 -111
- data/mcollective_additions/plugins/v2.2/agent/execute_tests_v2.ddl +0 -9
- data/mcollective_additions/plugins/v2.2/agent/execute_tests_v2.rb +0 -131
- data/mcollective_additions/plugins/v2.2/agent/get_log_fragment.ddl +0 -15
- data/mcollective_additions/plugins/v2.2/agent/get_log_fragment.rb +0 -79
- data/mcollective_additions/plugins/v2.2/agent/git_access.ddl +0 -9
- data/mcollective_additions/plugins/v2.2/agent/git_access.rb +0 -61
- data/mcollective_additions/plugins/v2.2/agent/netstat.ddl +0 -9
- data/mcollective_additions/plugins/v2.2/agent/netstat.rb +0 -34
- data/mcollective_additions/plugins/v2.2/agent/ps.ddl +0 -9
- data/mcollective_additions/plugins/v2.2/agent/ps.rb +0 -37
- data/mcollective_additions/plugins/v2.2/agent/puppet_apply.ddl +0 -9
- data/mcollective_additions/plugins/v2.2/agent/puppet_apply.rb +0 -818
- data/mcollective_additions/plugins/v2.2/agent/puppet_cancel.ddl +0 -10
- data/mcollective_additions/plugins/v2.2/agent/puppet_cancel.rb +0 -78
- data/mcollective_additions/plugins/v2.2/agent/rpcutil.ddl +0 -204
- data/mcollective_additions/plugins/v2.2/agent/rpcutil.rb +0 -101
- data/mcollective_additions/plugins/v2.2/agent/ssh_agent.ddl +0 -13
- data/mcollective_additions/plugins/v2.2/agent/ssh_agent.rb +0 -97
- data/mcollective_additions/plugins/v2.2/agent/sync_agent_code.ddl +0 -10
- data/mcollective_additions/plugins/v2.2/agent/sync_agent_code.rb +0 -85
- data/mcollective_additions/plugins/v2.2/agent/tail.ddl +0 -11
- data/mcollective_additions/plugins/v2.2/agent/tail.rb +0 -67
- data/mcollective_additions/plugins/v2.2/audit/logfile.rb +0 -26
- data/mcollective_additions/plugins/v2.2/connector/r8stomp.rb +0 -238
- data/mcollective_additions/plugins/v2.2/connector/stomp.rb +0 -349
- data/mcollective_additions/plugins/v2.2/connector/stomp_em.rb +0 -191
- data/mcollective_additions/plugins/v2.2/data/agent_data.ddl +0 -22
- data/mcollective_additions/plugins/v2.2/data/agent_data.rb +0 -17
- data/mcollective_additions/plugins/v2.2/data/collective_data.ddl +0 -20
- data/mcollective_additions/plugins/v2.2/data/collective_data.rb +0 -9
- data/mcollective_additions/plugins/v2.2/data/fact_data.ddl +0 -28
- data/mcollective_additions/plugins/v2.2/data/fact_data.rb +0 -55
- data/mcollective_additions/plugins/v2.2/data/fstat_data.ddl +0 -89
- data/mcollective_additions/plugins/v2.2/data/fstat_data.rb +0 -56
- data/mcollective_additions/plugins/v2.2/discovery/flatfile.ddl +0 -11
- data/mcollective_additions/plugins/v2.2/discovery/flatfile.rb +0 -48
- data/mcollective_additions/plugins/v2.2/discovery/mc.ddl +0 -11
- data/mcollective_additions/plugins/v2.2/discovery/mc.rb +0 -30
- data/mcollective_additions/plugins/v2.2/discovery/stdin.ddl +0 -11
- data/mcollective_additions/plugins/v2.2/discovery/stdin.rb +0 -66
- data/mcollective_additions/plugins/v2.2/facts/pbuilder_facts.rb +0 -37
- data/mcollective_additions/plugins/v2.2/facts/yaml_facts.rb +0 -61
- data/mcollective_additions/plugins/v2.2/registration/agentlist.rb +0 -10
- data/mcollective_additions/plugins/v2.2/security/sshkey.ddl +0 -9
- data/mcollective_additions/plugins/v2.2/security/sshkey.rb +0 -362
- data/mcollective_additions/plugins/v2.2/util/puppetrunner.rb +0 -36
- data/mcollective_additions/plugins/v2.2/validator/array_validator.ddl +0 -7
- data/mcollective_additions/plugins/v2.2/validator/array_validator.rb +0 -9
- data/mcollective_additions/plugins/v2.2/validator/ipv4address_validator.ddl +0 -7
- data/mcollective_additions/plugins/v2.2/validator/ipv4address_validator.rb +0 -16
- data/mcollective_additions/plugins/v2.2/validator/ipv6address_validator.ddl +0 -7
- data/mcollective_additions/plugins/v2.2/validator/ipv6address_validator.rb +0 -16
- data/mcollective_additions/plugins/v2.2/validator/length_validator.ddl +0 -7
- data/mcollective_additions/plugins/v2.2/validator/length_validator.rb +0 -11
- data/mcollective_additions/plugins/v2.2/validator/regex_validator.ddl +0 -7
- data/mcollective_additions/plugins/v2.2/validator/regex_validator.rb +0 -9
- data/mcollective_additions/plugins/v2.2/validator/shellsafe_validator.ddl +0 -7
- data/mcollective_additions/plugins/v2.2/validator/shellsafe_validator.rb +0 -13
- data/mcollective_additions/plugins/v2.2/validator/typecheck_validator.ddl +0 -7
- data/mcollective_additions/plugins/v2.2/validator/typecheck_validator.rb +0 -28
- data/mcollective_additions/redhat.mcollective.init +0 -139
- data/mcollective_additions/redhat.mcollective.service +0 -14
- data/mcollective_additions/server.cfg +0 -22
- data/src/etc/logrotate.d/mcollective +0 -10
- data/src/etc/mcollective.default +0 -6
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
metadata :name => "flatfile",
|
|
2
|
-
:description => "Flatfile based discovery for node identities",
|
|
3
|
-
:author => "R.I.Pienaar <rip@devco.net>",
|
|
4
|
-
:license => "ASL 2.0",
|
|
5
|
-
:version => "0.1",
|
|
6
|
-
:url => "http://marionette-collective.org/",
|
|
7
|
-
:timeout => 0
|
|
8
|
-
|
|
9
|
-
discovery do
|
|
10
|
-
capabilities :identity
|
|
11
|
-
end
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
# discovers against a flatfile instead of the traditional network discovery
|
|
2
|
-
# the flat file must have a node name per line which should match identities
|
|
3
|
-
# as configured
|
|
4
|
-
module MCollective
|
|
5
|
-
class Discovery
|
|
6
|
-
class Flatfile
|
|
7
|
-
def self.discover(filter, timeout, limit=0, client=nil)
|
|
8
|
-
unless client.options[:discovery_options].empty?
|
|
9
|
-
file = client.options[:discovery_options].first
|
|
10
|
-
else
|
|
11
|
-
raise "The flatfile discovery method needs a path to a text file"
|
|
12
|
-
end
|
|
13
|
-
|
|
14
|
-
raise "Cannot read the file %s specified as discovery source" % file unless File.readable?(file)
|
|
15
|
-
|
|
16
|
-
discovered = []
|
|
17
|
-
hosts = []
|
|
18
|
-
|
|
19
|
-
File.readlines(file).each do |host|
|
|
20
|
-
host = host.chomp.strip
|
|
21
|
-
if host.empty? || host.match(/^#/)
|
|
22
|
-
next
|
|
23
|
-
end
|
|
24
|
-
raise 'Identities can only match /^[\w\.\-]+$/' unless host.match(/^[\w\.\-]+$/)
|
|
25
|
-
hosts << host
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
# this plugin only supports identity filters, do regex matches etc against
|
|
29
|
-
# the list found in the flatfile
|
|
30
|
-
if !(filter["identity"].empty?)
|
|
31
|
-
filter["identity"].each do |identity|
|
|
32
|
-
identity = Regexp.new(identity.gsub("\/", "")) if identity.match("^/")
|
|
33
|
-
|
|
34
|
-
if identity.is_a?(Regexp)
|
|
35
|
-
discovered = hosts.grep(identity)
|
|
36
|
-
elsif hosts.include?(identity)
|
|
37
|
-
discovered << identity
|
|
38
|
-
end
|
|
39
|
-
end
|
|
40
|
-
else
|
|
41
|
-
discovered = hosts
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
discovered
|
|
45
|
-
end
|
|
46
|
-
end
|
|
47
|
-
end
|
|
48
|
-
end
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
metadata :name => "mc",
|
|
2
|
-
:description => "MCollective Broadcast based discovery",
|
|
3
|
-
:author => "R.I.Pienaar <rip@devco.net>",
|
|
4
|
-
:license => "ASL 2.0",
|
|
5
|
-
:version => "0.1",
|
|
6
|
-
:url => "http://marionette-collective.org/",
|
|
7
|
-
:timeout => 2
|
|
8
|
-
|
|
9
|
-
discovery do
|
|
10
|
-
capabilities [:classes, :facts, :identity, :agents, :compound]
|
|
11
|
-
end
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
module MCollective
|
|
2
|
-
class Discovery
|
|
3
|
-
class Mc
|
|
4
|
-
def self.discover(filter, timeout, limit, client)
|
|
5
|
-
begin
|
|
6
|
-
hosts = []
|
|
7
|
-
Timeout.timeout(timeout) do
|
|
8
|
-
reqid = client.sendreq("ping", "discovery", filter)
|
|
9
|
-
Log.debug("Waiting #{timeout} seconds for discovery replies to request #{reqid}")
|
|
10
|
-
|
|
11
|
-
loop do
|
|
12
|
-
reply = client.receive(reqid)
|
|
13
|
-
Log.debug("Got discovery reply from #{reply.payload[:senderid]}")
|
|
14
|
-
hosts << reply.payload[:senderid]
|
|
15
|
-
|
|
16
|
-
return hosts if limit > 0 && hosts.size == limit
|
|
17
|
-
end
|
|
18
|
-
end
|
|
19
|
-
rescue Timeout::Error => e
|
|
20
|
-
rescue Exception => e
|
|
21
|
-
raise
|
|
22
|
-
ensure
|
|
23
|
-
client.unsubscribe("discovery", :reply)
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
hosts
|
|
27
|
-
end
|
|
28
|
-
end
|
|
29
|
-
end
|
|
30
|
-
end
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
metadata :name => "stdin",
|
|
2
|
-
:description => "STDIN based discovery for node identities",
|
|
3
|
-
:author => "Tomas Doran <bobtfish@bobtfish.net.net>",
|
|
4
|
-
:license => "ASL 2.0",
|
|
5
|
-
:version => "0.1",
|
|
6
|
-
:url => "http://marionette-collective.org/",
|
|
7
|
-
:timeout => 0
|
|
8
|
-
|
|
9
|
-
discovery do
|
|
10
|
-
capabilities :identity
|
|
11
|
-
end
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
# discovers against stdin instead of the traditional network discovery
|
|
2
|
-
# the input must be a flat file with a node name per line which should match identities as configured,
|
|
3
|
-
# or it should be a json string as output by the -j option of mco rpc
|
|
4
|
-
require 'mcollective/rpc/helpers'
|
|
5
|
-
|
|
6
|
-
module MCollective
|
|
7
|
-
class Discovery
|
|
8
|
-
class Stdin
|
|
9
|
-
def self.discover(filter, timeout, limit=0, client=nil)
|
|
10
|
-
unless client.options[:discovery_options].empty?
|
|
11
|
-
type = client.options[:discovery_options].first.downcase
|
|
12
|
-
else
|
|
13
|
-
type = 'auto'
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
discovered = []
|
|
17
|
-
|
|
18
|
-
file = STDIN.read
|
|
19
|
-
|
|
20
|
-
if file =~ /^\s*$/
|
|
21
|
-
raise("data piped on STDIN contained only whitespace - could not discover hosts from it.")
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
if type == 'auto'
|
|
25
|
-
if file =~ /^\s*\[/
|
|
26
|
-
type = 'json'
|
|
27
|
-
else
|
|
28
|
-
type = 'text'
|
|
29
|
-
end
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
if type == 'json'
|
|
33
|
-
hosts = MCollective::RPC::Helpers.extract_hosts_from_json(file)
|
|
34
|
-
elsif type == 'text'
|
|
35
|
-
hosts = file.split("\n")
|
|
36
|
-
else
|
|
37
|
-
raise("stdin discovery plugin only knows the types auto/text/json, not \"#{type}\"")
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
hosts.map do |host|
|
|
41
|
-
raise 'Identities can only match /\w\.\-/' unless host.match(/^[\w\.\-]+$/)
|
|
42
|
-
host
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
# this plugin only supports identity filters, do regex matches etc against
|
|
46
|
-
# the list found in the flatfile
|
|
47
|
-
unless filter["identity"].empty?
|
|
48
|
-
filter["identity"].each do |identity|
|
|
49
|
-
identity = Regexp.new(identity.gsub("\/", "")) if identity.match("^/")
|
|
50
|
-
|
|
51
|
-
if identity.is_a?(Regexp)
|
|
52
|
-
discovered = hosts.grep(identity)
|
|
53
|
-
elsif hosts.include?(identity)
|
|
54
|
-
discovered << identity
|
|
55
|
-
end
|
|
56
|
-
end
|
|
57
|
-
else
|
|
58
|
-
discovered = hosts
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
discovered
|
|
62
|
-
end
|
|
63
|
-
end
|
|
64
|
-
end
|
|
65
|
-
end
|
|
66
|
-
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
require 'open-uri'
|
|
2
|
-
require 'timeout'
|
|
3
|
-
require 'yaml'
|
|
4
|
-
|
|
5
|
-
module MCollective
|
|
6
|
-
module Facts
|
|
7
|
-
# A factsource for pbuilder
|
|
8
|
-
class Pbuilder_facts < Base
|
|
9
|
-
|
|
10
|
-
def load_facts_from_source
|
|
11
|
-
# ret = {"pbuilderid" => get_pbuilderid()}
|
|
12
|
-
ret = {}
|
|
13
|
-
yaml_file = '/etc/mcollective/facts.yaml'
|
|
14
|
-
if File.exists?(yaml_file)
|
|
15
|
-
yaml_facts = YAML.load_file(yaml_file)
|
|
16
|
-
ret.merge!(yaml_facts)
|
|
17
|
-
end
|
|
18
|
-
ret.merge!("pbuilderid" => get_pbuilderid()) unless ret.keys.include?('pbuilderid')
|
|
19
|
-
ret
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
def get_pbuilderid()
|
|
23
|
-
ret = nil
|
|
24
|
-
begin
|
|
25
|
-
addr = "169.254.169.254"
|
|
26
|
-
wait_sec = 2
|
|
27
|
-
Timeout::timeout(wait_sec) {open("http://#{addr}:80/")}
|
|
28
|
-
ret = OpenURI.open_uri("http://#{addr}/2008-02-01/meta-data/instance-id").read
|
|
29
|
-
rescue Timeout::Error
|
|
30
|
-
rescue
|
|
31
|
-
#TODO: unexpected; write to log what error is
|
|
32
|
-
end
|
|
33
|
-
ret
|
|
34
|
-
end
|
|
35
|
-
end
|
|
36
|
-
end
|
|
37
|
-
end
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
module MCollective
|
|
2
|
-
module Facts
|
|
3
|
-
require 'yaml'
|
|
4
|
-
|
|
5
|
-
# A factsource that reads a hash of facts from a YAML file
|
|
6
|
-
#
|
|
7
|
-
# Multiple files can be specified seperated with a : in the
|
|
8
|
-
# config file, they will be merged with later files overriding
|
|
9
|
-
# earlier ones in the list.
|
|
10
|
-
class Yaml_facts<Base
|
|
11
|
-
def initialize
|
|
12
|
-
@yaml_file_mtimes = {}
|
|
13
|
-
|
|
14
|
-
super
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
def load_facts_from_source
|
|
18
|
-
config = Config.instance
|
|
19
|
-
|
|
20
|
-
fact_files = config.pluginconf["yaml"].split(File::PATH_SEPARATOR)
|
|
21
|
-
facts = {}
|
|
22
|
-
|
|
23
|
-
fact_files.each do |file|
|
|
24
|
-
begin
|
|
25
|
-
if File.exist?(file)
|
|
26
|
-
facts.merge!(YAML.load_file(file))
|
|
27
|
-
else
|
|
28
|
-
raise("Can't find YAML file to load: #{file}")
|
|
29
|
-
end
|
|
30
|
-
rescue Exception => e
|
|
31
|
-
Log.error("Failed to load yaml facts from #{file}: #{e.class}: #{e}")
|
|
32
|
-
end
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
facts
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
# force fact reloads when the mtime on the yaml file change
|
|
39
|
-
def force_reload?
|
|
40
|
-
config = Config.instance
|
|
41
|
-
|
|
42
|
-
fact_files = config.pluginconf["yaml"].split(File::PATH_SEPARATOR)
|
|
43
|
-
|
|
44
|
-
fact_files.each do |file|
|
|
45
|
-
@yaml_file_mtimes[file] ||= File.stat(file).mtime
|
|
46
|
-
mtime = File.stat(file).mtime
|
|
47
|
-
|
|
48
|
-
if mtime > @yaml_file_mtimes[file]
|
|
49
|
-
@yaml_file_mtimes[file] = mtime
|
|
50
|
-
|
|
51
|
-
Log.debug("Forcing fact reload due to age of #{file}")
|
|
52
|
-
|
|
53
|
-
return true
|
|
54
|
-
end
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
false
|
|
58
|
-
end
|
|
59
|
-
end
|
|
60
|
-
end
|
|
61
|
-
end
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
metadata :name => 'sshkey',
|
|
2
|
-
:description => 'Security Plugin that uses ssh keys for signing',
|
|
3
|
-
:author => 'Pieter Loubser <pieter.loubser@puppetlabs.com>',
|
|
4
|
-
:license => 'ASL 2.0',
|
|
5
|
-
:version => '0.4',
|
|
6
|
-
:url => 'http://projects.puppetlabs.com/projects/mcollective-plugins/wiki',
|
|
7
|
-
:timeout => 10
|
|
8
|
-
|
|
9
|
-
requires :mcollective => '2.2.1'
|
|
@@ -1,362 +0,0 @@
|
|
|
1
|
-
module MCollective
|
|
2
|
-
module Security
|
|
3
|
-
# Configuration
|
|
4
|
-
#
|
|
5
|
-
# Client:
|
|
6
|
-
# client.private_key : A private key used to sign requests with - defaults to ssh-agent
|
|
7
|
-
# client.known_hosts : The known_hosts file to use - defaults to /home/callerid/.ssh/known_hosts
|
|
8
|
-
# client.send_key : Send the client's public key with the request - doesn not send the key by default.
|
|
9
|
-
# To send a key specify the key file to send.
|
|
10
|
-
#
|
|
11
|
-
# Server:
|
|
12
|
-
# server.private_key : The private key used to sign replies with - Defaults to /etc/ssh/ssh_host_rsa_key * required
|
|
13
|
-
# server.authorized_keys : The authorized_keys file to use - defaults to the caller's authorized_keys file in his home directory
|
|
14
|
-
# server.send_key : Send the server's public key with the request - does not send the key by default.
|
|
15
|
-
# To send a key specify the key file to send.
|
|
16
|
-
#
|
|
17
|
-
# Shared:
|
|
18
|
-
# (client|server).publickey_dir : Directory to store received keys - defaults to none
|
|
19
|
-
# (client|server).learn_public_keys : Allow writing public keys to publickey_dir - defaults to not sending.
|
|
20
|
-
# (client|server).overwrite_stored_keys : Overwrite received keys - defaults to false
|
|
21
|
-
class Sshkey < Base
|
|
22
|
-
gem 'sshkeyauth', '>= 0.0.4'
|
|
23
|
-
|
|
24
|
-
require 'ssh/key/signer'
|
|
25
|
-
require 'ssh/key/verifier'
|
|
26
|
-
require 'etc'
|
|
27
|
-
|
|
28
|
-
def decodemsg(msg)
|
|
29
|
-
body = Marshal.load(msg.payload)
|
|
30
|
-
|
|
31
|
-
if validrequest?(body)
|
|
32
|
-
body[:body] = Marshal.load(body[:body])
|
|
33
|
-
return body
|
|
34
|
-
else
|
|
35
|
-
nil
|
|
36
|
-
end
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
def encodereply(sender, msg, requestid, requestcallerid=nil)
|
|
40
|
-
serialized_msg = Marshal.dump(msg)
|
|
41
|
-
reply = create_reply(requestid, sender, serialized_msg)
|
|
42
|
-
reply[:serialized_data] = Marshal.dump(create_hash_fields(serialized_msg, reply[:msgtime], requestid))
|
|
43
|
-
reply[:hash] = makehash(reply[:serialized_data])
|
|
44
|
-
|
|
45
|
-
if server_key = lookup_config_option('send_key')
|
|
46
|
-
if File.exists?(server_key)
|
|
47
|
-
reply[:public_key] = load_key(server_key)
|
|
48
|
-
else
|
|
49
|
-
raise("Cannot create reply. sshkey.server.send_key set but key '%s' does not exist." % server_key)
|
|
50
|
-
end
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
Marshal.dump(reply)
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
def encoderequest(sender, msg, requestid, filter, target_agent, target_collective, ttl=60)
|
|
57
|
-
serialized_msg = Marshal.dump(msg)
|
|
58
|
-
req = create_request(requestid, filter, serialized_msg, @initiated_by, target_agent, target_collective, ttl)
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
if client_key = lookup_config_option('send_key')
|
|
62
|
-
if File.exists?(client_key)
|
|
63
|
-
req[:public_key] = load_key(client_key)
|
|
64
|
-
else
|
|
65
|
-
raise("Cannot create request. sshkey.client.send_key set but key '%s' does not exist." % client_key)
|
|
66
|
-
end
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
req[:serialized_data] = Marshal.dump(create_hash_fields(serialized_msg, req[:msgtime], requestid, ttl, callerid))
|
|
70
|
-
req[:hash] = makehash(req[:serialized_data])
|
|
71
|
-
|
|
72
|
-
Marshal.dump(req)
|
|
73
|
-
end
|
|
74
|
-
|
|
75
|
-
def validrequest?(req)
|
|
76
|
-
# Check if verification keys are correctly configured
|
|
77
|
-
valid_configuration?
|
|
78
|
-
# Check if key should be written to disk and write it
|
|
79
|
-
write_key_to_disk(req[:public_key], (req[:callerid] || req[:senderid]).split('=')[-1] ) if req[:public_key]
|
|
80
|
-
|
|
81
|
-
if @initiated_by == :client
|
|
82
|
-
Log.debug('Validating reply from node %s' % req[:senderid])
|
|
83
|
-
verifier = client_verifier(req[:senderid])
|
|
84
|
-
else
|
|
85
|
-
Log.debug('Validating request from client %s' % req[:callerid])
|
|
86
|
-
verifier = node_verifier(req[:callerid], (req[:agent] == 'registration'), req[:public_key])
|
|
87
|
-
end
|
|
88
|
-
|
|
89
|
-
signatures = Marshal.load(req[:hash])
|
|
90
|
-
|
|
91
|
-
if verifier.verify?(signatures, req[:serialized_data])
|
|
92
|
-
@stats.validated
|
|
93
|
-
return true
|
|
94
|
-
else
|
|
95
|
-
@stats.unvalidated
|
|
96
|
-
Log.debug('Received an invalid signature in message.')
|
|
97
|
-
raise SecurityValidationFailed
|
|
98
|
-
end
|
|
99
|
-
end
|
|
100
|
-
|
|
101
|
-
def callerid
|
|
102
|
-
'sshkey=%s' % Etc.getlogin
|
|
103
|
-
end
|
|
104
|
-
|
|
105
|
-
private
|
|
106
|
-
|
|
107
|
-
# Checks that publickey_dir and known_hosts|authorized_keys are not set at the same time.
|
|
108
|
-
def valid_configuration?
|
|
109
|
-
if @initiated_by == :client
|
|
110
|
-
if lookup_config_option('publickey_dir') && lookup_config_option('known_hosts')
|
|
111
|
-
raise('Both publickey_dir and known_hosts are defined in client config. Cannot lookup public key')
|
|
112
|
-
end
|
|
113
|
-
elsif @initiated_by == :node
|
|
114
|
-
if lookup_config_option('publickey_dir') && lookup_config_option('authorized_keys')
|
|
115
|
-
raise('Both publickey_dir and authorized_keys are defiend in server config. Cannot lookup public key')
|
|
116
|
-
end
|
|
117
|
-
end
|
|
118
|
-
end
|
|
119
|
-
|
|
120
|
-
# Checks if the attached public key needs to be stored locally
|
|
121
|
-
# Overwriting is disabled by default
|
|
122
|
-
# - The publickey_directory config option needs to be set before
|
|
123
|
-
# the file will be written.
|
|
124
|
-
# - The directory must exist before writing.
|
|
125
|
-
# - The learn_public_keys configuration option must be enabled.
|
|
126
|
-
def write_key_to_disk(key, identity)
|
|
127
|
-
|
|
128
|
-
# Writing is disabled. Don't bother checking any other states.
|
|
129
|
-
return unless lookup_config_option('learn_public_keys') =~ /^1|y/
|
|
130
|
-
|
|
131
|
-
publickey_dir = lookup_config_option('publickey_dir')
|
|
132
|
-
|
|
133
|
-
unless publickey_dir
|
|
134
|
-
Log.info("Public key sent with request but no publickey_dir defined in configuration. Not writing key to disk.")
|
|
135
|
-
return
|
|
136
|
-
end
|
|
137
|
-
|
|
138
|
-
if File.directory?(publickey_dir)
|
|
139
|
-
if File.exists?(old_keyfile = File.join(publickey_dir, "#{identity}_pub.pem"))
|
|
140
|
-
old_key = File.read(old_keyfile).chomp
|
|
141
|
-
|
|
142
|
-
unless old_key == key
|
|
143
|
-
unless lookup_config_option('overwrite_stored_keys', 'n') =~ /^1|y/
|
|
144
|
-
Log.warn("Public key sent from '%s' does not match the stored key. Not overwriting." % identity)
|
|
145
|
-
else
|
|
146
|
-
Log.warn("Public key sent from '%s' does not match the stored key. Overwriting." % identity)
|
|
147
|
-
File.open(File.join(publickey_dir, "#{identity}_pub.pem"), 'w') { |f| f.puts key }
|
|
148
|
-
end
|
|
149
|
-
end
|
|
150
|
-
else
|
|
151
|
-
Log.debug("Discovered a new public key for '%s'. Writing to '%s'" % [identity, publickey_dir])
|
|
152
|
-
File.open(File.join(publickey_dir, "#{identity}_pub.pem"), 'w') { |f| f.puts key }
|
|
153
|
-
end
|
|
154
|
-
else
|
|
155
|
-
raise("Cannot write public key to '%s'. Directory does not exist." % publickey_dir)
|
|
156
|
-
end
|
|
157
|
-
end
|
|
158
|
-
|
|
159
|
-
# Fetches the correct configuration option for a client or a server
|
|
160
|
-
def lookup_config_option(opt, default = nil)
|
|
161
|
-
if @initiated_by == :client
|
|
162
|
-
result = @config.pluginconf.fetch("sshkey.client.#{opt}", default)
|
|
163
|
-
|
|
164
|
-
if result && ["authorized_keys", "private_key", "send_key", "publickey_dir", "known_hosts"].include?(opt)
|
|
165
|
-
return File.expand_path(result)
|
|
166
|
-
else
|
|
167
|
-
return result
|
|
168
|
-
end
|
|
169
|
-
elsif @initiated_by == :node
|
|
170
|
-
return @config.pluginconf.fetch("sshkey.server.#{opt}", default)
|
|
171
|
-
end
|
|
172
|
-
end
|
|
173
|
-
|
|
174
|
-
# Creates a hash of the fields used to sign a message
|
|
175
|
-
# Response messages use the msg, msgtime and requestid fields.
|
|
176
|
-
# Request messages use the same fields as response, but include
|
|
177
|
-
# ttl and callerid.
|
|
178
|
-
def create_hash_fields(msg, msgtime, requestid, ttl = nil, callerid = nil)
|
|
179
|
-
map = {:msg => msg,
|
|
180
|
-
:msgtime => msgtime,
|
|
181
|
-
:requestid => requestid}
|
|
182
|
-
|
|
183
|
-
# Check if this is a server hash
|
|
184
|
-
return map if (ttl == nil && callerid == nil)
|
|
185
|
-
|
|
186
|
-
map[:ttl] = ttl
|
|
187
|
-
map[:callerid] = callerid
|
|
188
|
-
|
|
189
|
-
map
|
|
190
|
-
end
|
|
191
|
-
|
|
192
|
-
# Adds a key to a signer object and disables ssh-agent
|
|
193
|
-
def add_key_to_signer(signer, key)
|
|
194
|
-
signer.add_key_file(key)
|
|
195
|
-
signer.use_agent = false
|
|
196
|
-
end
|
|
197
|
-
|
|
198
|
-
# Creates a signed hash of fields using the node's private key
|
|
199
|
-
def makehash(data)
|
|
200
|
-
signer = SSH::Key::Signer.new
|
|
201
|
-
|
|
202
|
-
# Check if the client is signing its request with a predefined
|
|
203
|
-
# private key. If this is the case, disable ssh-agent.
|
|
204
|
-
if @initiated_by == :client && (private_key = lookup_config_option('private_key'))
|
|
205
|
-
unless File.exists?(private_key)
|
|
206
|
-
raise("Cannot sign request - private key not found: '%s'" % private_key)
|
|
207
|
-
else
|
|
208
|
-
add_key_to_signer(signer, private_key)
|
|
209
|
-
end
|
|
210
|
-
elsif @initiated_by == :node
|
|
211
|
-
if private_key = lookup_config_option('private_key')
|
|
212
|
-
add_key_to_signer(signer, private_key)
|
|
213
|
-
else
|
|
214
|
-
# First try and default to ssh_host_dsa_key
|
|
215
|
-
if File.exists?(private_key = '/etc/ssh/ssh_host_dsa_key')
|
|
216
|
-
add_key_to_signer(signer, private_key)
|
|
217
|
-
# If that fails, try ssh_host_rsa_key
|
|
218
|
-
elsif File.exists?(private_key = '/etc/ssh/ssh_host_rsa_key')
|
|
219
|
-
add_key_to_signer(signer, private_key)
|
|
220
|
-
else
|
|
221
|
-
raise("Cannot sign reply - private key not found: 's'" % private_key)
|
|
222
|
-
end
|
|
223
|
-
end
|
|
224
|
-
end
|
|
225
|
-
|
|
226
|
-
# Default to using ssh-agent for key signing
|
|
227
|
-
signatures = signer.sign(data).collect { |s| s.signature }
|
|
228
|
-
Marshal.dump(signatures)
|
|
229
|
-
end
|
|
230
|
-
|
|
231
|
-
#Returns the contents of a key file on disk
|
|
232
|
-
def load_key(key)
|
|
233
|
-
if File.exists?(key)
|
|
234
|
-
return File.read(key).strip
|
|
235
|
-
else
|
|
236
|
-
nil
|
|
237
|
-
end
|
|
238
|
-
end
|
|
239
|
-
|
|
240
|
-
# Looks for a specific key in a known hosts file
|
|
241
|
-
def find_key_in_known_hosts(hostname, known_hosts)
|
|
242
|
-
key = nil
|
|
243
|
-
search_for = /^#{hostname}/
|
|
244
|
-
|
|
245
|
-
if File.exists?(known_hosts)
|
|
246
|
-
File.read(known_hosts).each_line do |line|
|
|
247
|
-
fields = line.split
|
|
248
|
-
fields[0].split(',').each do |maybehost|
|
|
249
|
-
if maybehost =~ search_for
|
|
250
|
-
fields = line.split
|
|
251
|
-
key = fields[-2] << ' ' << fields[-1]
|
|
252
|
-
break
|
|
253
|
-
end
|
|
254
|
-
end
|
|
255
|
-
break unless key.nil?
|
|
256
|
-
end
|
|
257
|
-
end
|
|
258
|
-
|
|
259
|
-
unless key
|
|
260
|
-
Log.warn("Could not find a key for host '%s' in file '%s'" % [hostname, known_hosts])
|
|
261
|
-
raise SecurityValidationFailed
|
|
262
|
-
end
|
|
263
|
-
|
|
264
|
-
key
|
|
265
|
-
end
|
|
266
|
-
|
|
267
|
-
# Create a client verifier object which uses the correct public key
|
|
268
|
-
def client_verifier(senderid)
|
|
269
|
-
verifier = SSH::Key::Verifier.new(senderid)
|
|
270
|
-
verifier.use_authorized_keys = false
|
|
271
|
-
|
|
272
|
-
if publickey_dir = lookup_config_option('publickey_dir')
|
|
273
|
-
Log.debug("Using public key directory: '%s'" % publickey_dir)
|
|
274
|
-
verifier.add_public_key_data(find_shared_public_key(publickey_dir, senderid))
|
|
275
|
-
|
|
276
|
-
elsif (known_hosts = lookup_config_option('known_hosts'))
|
|
277
|
-
Log.debug("Using custom known_hosts file: '%s'" % known_hosts)
|
|
278
|
-
verifier.add_public_key_data(find_key_in_known_hosts(senderid, known_hosts))
|
|
279
|
-
|
|
280
|
-
elsif (authorized_keys = lookup_config_option('authorized_keys'))
|
|
281
|
-
Log.debug("Found custom authorized_keys file: '%s'" % authorized_keys)
|
|
282
|
-
verifier.authorized_keys_file = authorized_keys
|
|
283
|
-
verifier.use_authorized_keys = true
|
|
284
|
-
|
|
285
|
-
else
|
|
286
|
-
begin
|
|
287
|
-
user = Etc.getlogin
|
|
288
|
-
known_hosts = File.join(Etc.getpwnam(user).dir, '.ssh', 'known_hosts')
|
|
289
|
-
Log.debug("Using default known_hosts file for user '%s': ''" % [user, known_hosts])
|
|
290
|
-
verifier.add_public_key_data(find_key_in_known_hosts(senderid, "%s" % known_hosts))
|
|
291
|
-
rescue => e
|
|
292
|
-
raise("Cannot find known_hosts file for user '%s': '%s'" % [user, known_hosts])
|
|
293
|
-
end
|
|
294
|
-
end
|
|
295
|
-
|
|
296
|
-
verifier.use_agent = false
|
|
297
|
-
|
|
298
|
-
verifier
|
|
299
|
-
end
|
|
300
|
-
|
|
301
|
-
# Looks for a public key in a shared directory
|
|
302
|
-
def find_shared_public_key(dir, id)
|
|
303
|
-
unless File.directory?(dir)
|
|
304
|
-
raise("Cannot read shared public key directory: '%s'" % dir)
|
|
305
|
-
end
|
|
306
|
-
|
|
307
|
-
if File.exists?(key_file = File.join(dir, "#{id}_pub.pem"))
|
|
308
|
-
return File.read(key_file)
|
|
309
|
-
else
|
|
310
|
-
Log.warn("Cannot find public key for id '%s': '%s'" % [id, File.join(dir, "#{id}_pub.pem")])
|
|
311
|
-
raise SecurityValidationFailed
|
|
312
|
-
end
|
|
313
|
-
end
|
|
314
|
-
|
|
315
|
-
# Create a node verifier object which uses the correct public key
|
|
316
|
-
def node_verifier(callerid, registration = false, pubkey = nil)
|
|
317
|
-
user = callerid.split('=')[-1]
|
|
318
|
-
verifier = SSH::Key::Verifier.new(user)
|
|
319
|
-
verifier.use_agent = false
|
|
320
|
-
|
|
321
|
-
# Here we deal with the special case where a registration message
|
|
322
|
-
# is being validated. send_key has to be defined in the configuration.
|
|
323
|
-
# TODO : This is a stop gap measure we should remove when we fix
|
|
324
|
-
# registration
|
|
325
|
-
if registration && pubkey
|
|
326
|
-
Log.debug("Found registration message. Using sender's public key")
|
|
327
|
-
verifier.add_public_key_data(pubkey)
|
|
328
|
-
verifier.use_authorized_keys = false
|
|
329
|
-
|
|
330
|
-
elsif registration && !pubkey
|
|
331
|
-
Log.warn("Cannot verify registration request. Server did not send its public key")
|
|
332
|
-
raise SecurityValidationFailed
|
|
333
|
-
|
|
334
|
-
elsif publickey_dir = lookup_config_option('publickey_dir')
|
|
335
|
-
if File.directory?(publickey_dir)
|
|
336
|
-
Log.debug("Found shared public key directory: '%s'" % publickey_dir)
|
|
337
|
-
verifier.add_public_key_data(find_shared_public_key(publickey_dir, user))
|
|
338
|
-
verifier.use_authorized_keys = false
|
|
339
|
-
else
|
|
340
|
-
raise("Public key directory '%s' does not exist" % publickey_dir)
|
|
341
|
-
end
|
|
342
|
-
|
|
343
|
-
elsif (authorized_keys = lookup_config_option('authorized_keys'))
|
|
344
|
-
authorized_keys = authorized_keys.sub('%u') { |c| user }
|
|
345
|
-
Log.debug("Found custom authorized_keys file: '%s'" % authorized_keys)
|
|
346
|
-
verifier.authorized_keys_file = authorized_keys
|
|
347
|
-
|
|
348
|
-
else
|
|
349
|
-
begin
|
|
350
|
-
authorized_keys = File.join(Etc.getpwnam(user).dir, '.ssh', 'authorized_keys')
|
|
351
|
-
Log.debug("No authorized_keys file or publickey_dir specified. Using '%s'" % authorized_keys)
|
|
352
|
-
verifier.authorized_keys_file = authorized_keys
|
|
353
|
-
rescue => e
|
|
354
|
-
raise("Cannot find authorized_keys file for user '%s': '%s'" % [user, authorized_keys])
|
|
355
|
-
end
|
|
356
|
-
end
|
|
357
|
-
|
|
358
|
-
verifier
|
|
359
|
-
end
|
|
360
|
-
end
|
|
361
|
-
end
|
|
362
|
-
end
|