dtk-node-agent 0.5.10
Sign up to get free protection for your applications and to get access to all the features.
- 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
|