dtk-node-agent 0.5.10
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 +15 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +18 -0
- data/README.md +42 -0
- data/bin/dtk-node-agent +16 -0
- data/dtk-node-agent.gemspec +38 -0
- data/lib/config/install.config +12 -0
- data/lib/dtk-node-agent/installer.rb +142 -0
- data/lib/dtk-node-agent/version.rb +3 -0
- data/mcollective_additions/plugins/README.md +1 -0
- data/mcollective_additions/plugins/v1.2/agent/discovery.rb +39 -0
- data/mcollective_additions/plugins/v1.2/agent/get_log_fragment.ddl +15 -0
- data/mcollective_additions/plugins/v1.2/agent/get_log_fragment.rb +79 -0
- data/mcollective_additions/plugins/v1.2/agent/git_access.ddl +9 -0
- data/mcollective_additions/plugins/v1.2/agent/git_access.rb +79 -0
- data/mcollective_additions/plugins/v1.2/agent/netstat.ddl +9 -0
- data/mcollective_additions/plugins/v1.2/agent/netstat.rb +34 -0
- data/mcollective_additions/plugins/v1.2/agent/puppet_apply.ddl +9 -0
- data/mcollective_additions/plugins/v1.2/agent/puppet_apply.rb +630 -0
- data/mcollective_additions/plugins/v1.2/agent/rpcutil.ddl +204 -0
- data/mcollective_additions/plugins/v1.2/agent/rpcutil.rb +101 -0
- data/mcollective_additions/plugins/v1.2/facts/pbuilder_facts.rb +35 -0
- data/mcollective_additions/plugins/v2.2/agent/dev_manager.ddl +9 -0
- data/mcollective_additions/plugins/v2.2/agent/dev_manager.rb +69 -0
- data/mcollective_additions/plugins/v2.2/agent/discovery.rb +39 -0
- data/mcollective_additions/plugins/v2.2/agent/dtk_node_agent_git_client.rb +94 -0
- data/mcollective_additions/plugins/v2.2/agent/execute_tests.ddl +9 -0
- data/mcollective_additions/plugins/v2.2/agent/execute_tests.rb +64 -0
- data/mcollective_additions/plugins/v2.2/agent/get_log_fragment.ddl +15 -0
- data/mcollective_additions/plugins/v2.2/agent/get_log_fragment.rb +79 -0
- data/mcollective_additions/plugins/v2.2/agent/git_access.ddl +9 -0
- data/mcollective_additions/plugins/v2.2/agent/git_access.rb +72 -0
- data/mcollective_additions/plugins/v2.2/agent/netstat.ddl +9 -0
- data/mcollective_additions/plugins/v2.2/agent/netstat.rb +34 -0
- data/mcollective_additions/plugins/v2.2/agent/ps.ddl +9 -0
- data/mcollective_additions/plugins/v2.2/agent/ps.rb +37 -0
- data/mcollective_additions/plugins/v2.2/agent/puppet_apply.ddl +9 -0
- data/mcollective_additions/plugins/v2.2/agent/puppet_apply.rb +633 -0
- data/mcollective_additions/plugins/v2.2/agent/puppet_cancel.ddl +10 -0
- data/mcollective_additions/plugins/v2.2/agent/puppet_cancel.rb +78 -0
- data/mcollective_additions/plugins/v2.2/agent/rpcutil.ddl +204 -0
- data/mcollective_additions/plugins/v2.2/agent/rpcutil.rb +101 -0
- data/mcollective_additions/plugins/v2.2/agent/sync_agent_code.ddl +10 -0
- data/mcollective_additions/plugins/v2.2/agent/sync_agent_code.rb +85 -0
- data/mcollective_additions/plugins/v2.2/agent/tail.ddl +11 -0
- data/mcollective_additions/plugins/v2.2/agent/tail.rb +67 -0
- data/mcollective_additions/plugins/v2.2/connector/r8stomp.rb +238 -0
- data/mcollective_additions/plugins/v2.2/connector/stomp.rb +349 -0
- data/mcollective_additions/plugins/v2.2/connector/stomp_em.rb +191 -0
- data/mcollective_additions/plugins/v2.2/facts/pbuilder_facts.rb +35 -0
- data/mcollective_additions/plugins/v2.2/security/sshkey.ddl +9 -0
- data/mcollective_additions/plugins/v2.2/security/sshkey.rb +362 -0
- data/mcollective_additions/server.cfg +22 -0
- data/puppet_additions/modules/r8/lib/puppet/type/r8_export_file.rb +53 -0
- data/puppet_additions/modules/r8/manifests/export_variable.rb +10 -0
- data/puppet_additions/puppet_lib_base/puppet/indirector/catalog/r8_storeconfig_backend.rb +48 -0
- data/puppet_additions/puppet_lib_base/puppet/indirector/r8_storeconfig_backend.rb +4 -0
- data/puppet_additions/puppet_lib_base/puppet/indirector/resource/r8_storeconfig_backend.rb +72 -0
- data/src/etc/init.d/ec2-run-user-data +95 -0
- data/src/etc/logrotate.d/mcollective +10 -0
- data/src/etc/logrotate.d/puppet +7 -0
- metadata +189 -0
@@ -0,0 +1,191 @@
|
|
1
|
+
require 'eventmachine'
|
2
|
+
module MCollective
|
3
|
+
module Connector
|
4
|
+
# Handles sending and receiving messages over the Stomp protocol
|
5
|
+
#
|
6
|
+
# This plugin supports version 1.1 or 1.1.6 and newer of the Stomp rubygem
|
7
|
+
# the versions between those had multi threading issues.
|
8
|
+
#
|
9
|
+
# For all versions you can configure it as follows:
|
10
|
+
#
|
11
|
+
# connector = stomp
|
12
|
+
# plugin.stomp.host = stomp.your.net
|
13
|
+
# plugin.stomp.port = 6163
|
14
|
+
# plugin.stomp.user = you
|
15
|
+
# plugin.stomp.password = secret
|
16
|
+
#
|
17
|
+
# For versions of ActiveMQ that supports message priorities
|
18
|
+
# you can set a priority, this will cause a "priority" header
|
19
|
+
# to be emitted if present:
|
20
|
+
#
|
21
|
+
# plugin.stomp.priority = 4
|
22
|
+
#
|
23
|
+
class Stomp_em<Base
|
24
|
+
#this is effectively a singleton, but not mixin in Singleton beacuse mcollective isstantiates with new
|
25
|
+
#TODO: may look at making singleton and patching with making :new public, recognizing that will only be called once
|
26
|
+
|
27
|
+
module StompClient
|
28
|
+
include EM::Protocols::Stomp
|
29
|
+
def initialize(*args)
|
30
|
+
super(*args)
|
31
|
+
#TODO: if cannot fidn user and log this shoudl be error
|
32
|
+
conn_opts = (args.last.kind_of?(Hash))? args.last : {}
|
33
|
+
@login = conn_opts[:login]
|
34
|
+
@passcode = conn_opts[:passcode]
|
35
|
+
@connected = false
|
36
|
+
end
|
37
|
+
|
38
|
+
def connection_completed
|
39
|
+
connect :login => @login, :passcode => @passcode
|
40
|
+
end
|
41
|
+
|
42
|
+
def receive_msg msg
|
43
|
+
if msg.command == "CONNECTED"
|
44
|
+
pp [:is_connected]
|
45
|
+
@connected = true
|
46
|
+
else
|
47
|
+
Stomp_em.process(msg)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def is_connected?()
|
52
|
+
@connected
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def initialize
|
57
|
+
@config = Config.instance
|
58
|
+
@subscriptions = []
|
59
|
+
@connected = nil
|
60
|
+
@connection = nil
|
61
|
+
end
|
62
|
+
|
63
|
+
def set_context(context)
|
64
|
+
@@decode_context = context[:decode_context]
|
65
|
+
@@multiplexer = context[:multiplexer]
|
66
|
+
end
|
67
|
+
|
68
|
+
def disconnect
|
69
|
+
#TODO: need to write
|
70
|
+
end
|
71
|
+
|
72
|
+
# Connects to the Stomp middleware
|
73
|
+
def connect
|
74
|
+
if @connection
|
75
|
+
Log.debug("Already connection, not re-initializing connection")
|
76
|
+
return
|
77
|
+
end
|
78
|
+
begin
|
79
|
+
host = nil
|
80
|
+
port = nil
|
81
|
+
user = nil
|
82
|
+
password = nil
|
83
|
+
@@base64 = false
|
84
|
+
|
85
|
+
@@base64 = get_bool_option("stomp.base64", false)
|
86
|
+
@@msgpriority = get_option("stomp.priority", 0).to_i
|
87
|
+
|
88
|
+
# Maintain backward compat for older stomps
|
89
|
+
host = get_env_or_option("STOMP_SERVER", "stomp.host")
|
90
|
+
port = get_env_or_option("STOMP_PORT", "stomp.port", 6163).to_i
|
91
|
+
user = get_env_or_option("STOMP_USER", "stomp.user")
|
92
|
+
password = get_env_or_option("STOMP_PASSWORD", "stomp.password")
|
93
|
+
|
94
|
+
#TODO: assume reactor is running already
|
95
|
+
@connection = EM.connect host, port, StompClient, :login => user, :passcode => password
|
96
|
+
Log.debug("Connecting to #{host}:#{port}")
|
97
|
+
rescue Exception => e
|
98
|
+
pp e.backtrace[0..5]
|
99
|
+
raise("Could not connect to Stomp Server: #{e}")
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def wait_until_connected?
|
104
|
+
return if @connected
|
105
|
+
loop do
|
106
|
+
return if @connected = @connection.is_connected?
|
107
|
+
sleep 1
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
|
112
|
+
def self.process(msg)
|
113
|
+
# STOMP puts the payload in the body variable, pass that
|
114
|
+
# into the payload of MCollective::Request and discard all the
|
115
|
+
# other headers etc that stomp provides
|
116
|
+
raw_msg =
|
117
|
+
if @@base64
|
118
|
+
Request.new(SSL.base64_decode(msg.body))
|
119
|
+
else
|
120
|
+
Request.new(msg.body)
|
121
|
+
end
|
122
|
+
msg = @@decode_context.r8_decode_receive(raw_msg)
|
123
|
+
@@multiplexer.process_response(msg,msg[:requestid])
|
124
|
+
end
|
125
|
+
|
126
|
+
#TODO: make automic subscribe_and_send because need subscribe to happen before send does
|
127
|
+
# Subscribe to a topic or queue
|
128
|
+
def subscribe(source)
|
129
|
+
unless @subscriptions.include?(source)
|
130
|
+
EM::defer do
|
131
|
+
Log.debug("Subscribing to #{source}")
|
132
|
+
wait_until_connected?
|
133
|
+
@connection.subscribe(source)
|
134
|
+
@subscriptions << source
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
def subscribe_and_send(source,destination,body,params={})
|
139
|
+
EM::defer do
|
140
|
+
wait_until_connected?
|
141
|
+
unless @subscriptions.include?(source)
|
142
|
+
Log.debug("Subscribing to #{source}")
|
143
|
+
@connection.subscribe(source)
|
144
|
+
@subscriptions << source
|
145
|
+
end
|
146
|
+
@connection.send(destination,body,params)
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
# Subscribe to a topic or queue
|
151
|
+
def unsubscribe(source)
|
152
|
+
#TODO
|
153
|
+
end
|
154
|
+
private
|
155
|
+
def get_env_or_option(env, opt, default=nil)
|
156
|
+
return ENV[env] if ENV.include?(env)
|
157
|
+
return @config.pluginconf[opt] if @config.pluginconf.include?(opt)
|
158
|
+
return default if default
|
159
|
+
|
160
|
+
raise("No #{env} environment or plugin.#{opt} configuration option given")
|
161
|
+
end
|
162
|
+
|
163
|
+
# looks for a config option, accepts an optional default
|
164
|
+
#
|
165
|
+
# raises an exception when it cant find a value anywhere
|
166
|
+
def get_option(opt, default=nil)
|
167
|
+
return @config.pluginconf[opt] if @config.pluginconf.include?(opt)
|
168
|
+
return default if default
|
169
|
+
|
170
|
+
raise("No plugin.#{opt} configuration option given")
|
171
|
+
end
|
172
|
+
|
173
|
+
# gets a boolean option from the config, supports y/n/true/false/1/0
|
174
|
+
def get_bool_option(opt, default)
|
175
|
+
return default unless @config.pluginconf.include?(opt)
|
176
|
+
|
177
|
+
val = @config.pluginconf[opt]
|
178
|
+
|
179
|
+
if val =~ /^1|yes|true/
|
180
|
+
return true
|
181
|
+
elsif val =~ /^0|no|false/
|
182
|
+
return false
|
183
|
+
else
|
184
|
+
return default
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
# vi:tabstop=4:expandtab:ai
|
@@ -0,0 +1,35 @@
|
|
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
|
+
yaml_file = '/etc/mcollective/facts.yaml'
|
13
|
+
if File.exists?(yaml_file)
|
14
|
+
yaml_facts = YAML.load_file(yaml_file)
|
15
|
+
ret.merge!(yaml_facts)
|
16
|
+
end
|
17
|
+
ret
|
18
|
+
end
|
19
|
+
|
20
|
+
def get_pbuilderid()
|
21
|
+
ret = nil
|
22
|
+
begin
|
23
|
+
addr = "169.254.169.254"
|
24
|
+
wait_sec = 2
|
25
|
+
Timeout::timeout(wait_sec) {open("http://#{addr}:80/")}
|
26
|
+
ret = OpenURI.open_uri("http://#{addr}/2008-02-01/meta-data/instance-id").read
|
27
|
+
rescue Timeout::Error
|
28
|
+
rescue
|
29
|
+
#TODO: unexpected; write to log what error is
|
30
|
+
end
|
31
|
+
ret
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,9 @@
|
|
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'
|
@@ -0,0 +1,362 @@
|
|
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
|