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.
Files changed (62) hide show
  1. checksums.yaml +15 -0
  2. data/Gemfile +4 -0
  3. data/Gemfile.lock +18 -0
  4. data/README.md +42 -0
  5. data/bin/dtk-node-agent +16 -0
  6. data/dtk-node-agent.gemspec +38 -0
  7. data/lib/config/install.config +12 -0
  8. data/lib/dtk-node-agent/installer.rb +142 -0
  9. data/lib/dtk-node-agent/version.rb +3 -0
  10. data/mcollective_additions/plugins/README.md +1 -0
  11. data/mcollective_additions/plugins/v1.2/agent/discovery.rb +39 -0
  12. data/mcollective_additions/plugins/v1.2/agent/get_log_fragment.ddl +15 -0
  13. data/mcollective_additions/plugins/v1.2/agent/get_log_fragment.rb +79 -0
  14. data/mcollective_additions/plugins/v1.2/agent/git_access.ddl +9 -0
  15. data/mcollective_additions/plugins/v1.2/agent/git_access.rb +79 -0
  16. data/mcollective_additions/plugins/v1.2/agent/netstat.ddl +9 -0
  17. data/mcollective_additions/plugins/v1.2/agent/netstat.rb +34 -0
  18. data/mcollective_additions/plugins/v1.2/agent/puppet_apply.ddl +9 -0
  19. data/mcollective_additions/plugins/v1.2/agent/puppet_apply.rb +630 -0
  20. data/mcollective_additions/plugins/v1.2/agent/rpcutil.ddl +204 -0
  21. data/mcollective_additions/plugins/v1.2/agent/rpcutil.rb +101 -0
  22. data/mcollective_additions/plugins/v1.2/facts/pbuilder_facts.rb +35 -0
  23. data/mcollective_additions/plugins/v2.2/agent/dev_manager.ddl +9 -0
  24. data/mcollective_additions/plugins/v2.2/agent/dev_manager.rb +69 -0
  25. data/mcollective_additions/plugins/v2.2/agent/discovery.rb +39 -0
  26. data/mcollective_additions/plugins/v2.2/agent/dtk_node_agent_git_client.rb +94 -0
  27. data/mcollective_additions/plugins/v2.2/agent/execute_tests.ddl +9 -0
  28. data/mcollective_additions/plugins/v2.2/agent/execute_tests.rb +64 -0
  29. data/mcollective_additions/plugins/v2.2/agent/get_log_fragment.ddl +15 -0
  30. data/mcollective_additions/plugins/v2.2/agent/get_log_fragment.rb +79 -0
  31. data/mcollective_additions/plugins/v2.2/agent/git_access.ddl +9 -0
  32. data/mcollective_additions/plugins/v2.2/agent/git_access.rb +72 -0
  33. data/mcollective_additions/plugins/v2.2/agent/netstat.ddl +9 -0
  34. data/mcollective_additions/plugins/v2.2/agent/netstat.rb +34 -0
  35. data/mcollective_additions/plugins/v2.2/agent/ps.ddl +9 -0
  36. data/mcollective_additions/plugins/v2.2/agent/ps.rb +37 -0
  37. data/mcollective_additions/plugins/v2.2/agent/puppet_apply.ddl +9 -0
  38. data/mcollective_additions/plugins/v2.2/agent/puppet_apply.rb +633 -0
  39. data/mcollective_additions/plugins/v2.2/agent/puppet_cancel.ddl +10 -0
  40. data/mcollective_additions/plugins/v2.2/agent/puppet_cancel.rb +78 -0
  41. data/mcollective_additions/plugins/v2.2/agent/rpcutil.ddl +204 -0
  42. data/mcollective_additions/plugins/v2.2/agent/rpcutil.rb +101 -0
  43. data/mcollective_additions/plugins/v2.2/agent/sync_agent_code.ddl +10 -0
  44. data/mcollective_additions/plugins/v2.2/agent/sync_agent_code.rb +85 -0
  45. data/mcollective_additions/plugins/v2.2/agent/tail.ddl +11 -0
  46. data/mcollective_additions/plugins/v2.2/agent/tail.rb +67 -0
  47. data/mcollective_additions/plugins/v2.2/connector/r8stomp.rb +238 -0
  48. data/mcollective_additions/plugins/v2.2/connector/stomp.rb +349 -0
  49. data/mcollective_additions/plugins/v2.2/connector/stomp_em.rb +191 -0
  50. data/mcollective_additions/plugins/v2.2/facts/pbuilder_facts.rb +35 -0
  51. data/mcollective_additions/plugins/v2.2/security/sshkey.ddl +9 -0
  52. data/mcollective_additions/plugins/v2.2/security/sshkey.rb +362 -0
  53. data/mcollective_additions/server.cfg +22 -0
  54. data/puppet_additions/modules/r8/lib/puppet/type/r8_export_file.rb +53 -0
  55. data/puppet_additions/modules/r8/manifests/export_variable.rb +10 -0
  56. data/puppet_additions/puppet_lib_base/puppet/indirector/catalog/r8_storeconfig_backend.rb +48 -0
  57. data/puppet_additions/puppet_lib_base/puppet/indirector/r8_storeconfig_backend.rb +4 -0
  58. data/puppet_additions/puppet_lib_base/puppet/indirector/resource/r8_storeconfig_backend.rb +72 -0
  59. data/src/etc/init.d/ec2-run-user-data +95 -0
  60. data/src/etc/logrotate.d/mcollective +10 -0
  61. data/src/etc/logrotate.d/puppet +7 -0
  62. 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