leap_cli 1.5.6 → 1.6.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (62) hide show
  1. data/bin/leap +29 -6
  2. data/lib/leap/platform.rb +36 -1
  3. data/lib/leap_cli/commands/ca.rb +97 -20
  4. data/lib/leap_cli/commands/compile.rb +49 -8
  5. data/lib/leap_cli/commands/db.rb +13 -4
  6. data/lib/leap_cli/commands/deploy.rb +138 -29
  7. data/lib/leap_cli/commands/env.rb +76 -0
  8. data/lib/leap_cli/commands/facts.rb +10 -3
  9. data/lib/leap_cli/commands/inspect.rb +2 -2
  10. data/lib/leap_cli/commands/list.rb +10 -10
  11. data/lib/leap_cli/commands/node.rb +7 -132
  12. data/lib/leap_cli/commands/node_init.rb +169 -0
  13. data/lib/leap_cli/commands/pre.rb +4 -27
  14. data/lib/leap_cli/commands/ssh.rb +152 -0
  15. data/lib/leap_cli/commands/test.rb +22 -13
  16. data/lib/leap_cli/commands/user.rb +12 -4
  17. data/lib/leap_cli/commands/vagrant.rb +4 -4
  18. data/lib/leap_cli/config/filter.rb +175 -0
  19. data/lib/leap_cli/config/manager.rb +130 -61
  20. data/lib/leap_cli/config/node.rb +32 -0
  21. data/lib/leap_cli/config/object.rb +69 -44
  22. data/lib/leap_cli/config/object_list.rb +44 -39
  23. data/lib/leap_cli/config/secrets.rb +24 -12
  24. data/lib/leap_cli/config/tag.rb +7 -0
  25. data/lib/{core_ext → leap_cli/core_ext}/boolean.rb +0 -0
  26. data/lib/{core_ext → leap_cli/core_ext}/hash.rb +0 -0
  27. data/lib/{core_ext → leap_cli/core_ext}/json.rb +0 -0
  28. data/lib/{core_ext → leap_cli/core_ext}/nil.rb +0 -0
  29. data/lib/{core_ext → leap_cli/core_ext}/string.rb +0 -0
  30. data/lib/leap_cli/core_ext/yaml.rb +29 -0
  31. data/lib/leap_cli/exceptions.rb +24 -0
  32. data/lib/leap_cli/leapfile.rb +60 -10
  33. data/lib/{lib_ext → leap_cli/lib_ext}/capistrano_connections.rb +0 -0
  34. data/lib/{lib_ext → leap_cli/lib_ext}/gli.rb +0 -0
  35. data/lib/leap_cli/log.rb +1 -1
  36. data/lib/leap_cli/logger.rb +18 -1
  37. data/lib/leap_cli/markdown_document_listener.rb +1 -1
  38. data/lib/leap_cli/override/json.rb +11 -0
  39. data/lib/leap_cli/path.rb +20 -6
  40. data/lib/leap_cli/remote/leap_plugin.rb +2 -2
  41. data/lib/leap_cli/remote/puppet_plugin.rb +1 -1
  42. data/lib/leap_cli/remote/rsync_plugin.rb +1 -1
  43. data/lib/leap_cli/remote/tasks.rb +1 -1
  44. data/lib/leap_cli/ssh_key.rb +63 -1
  45. data/lib/leap_cli/util/remote_command.rb +19 -2
  46. data/lib/leap_cli/util/secret.rb +1 -1
  47. data/lib/leap_cli/util/x509.rb +3 -2
  48. data/lib/leap_cli/util.rb +11 -3
  49. data/lib/leap_cli/version.rb +2 -2
  50. data/lib/leap_cli.rb +24 -14
  51. data/vendor/certificate_authority/lib/certificate_authority/certificate.rb +85 -29
  52. data/vendor/certificate_authority/lib/certificate_authority/distinguished_name.rb +5 -0
  53. data/vendor/certificate_authority/lib/certificate_authority/extensions.rb +406 -41
  54. data/vendor/certificate_authority/lib/certificate_authority/key_material.rb +0 -34
  55. data/vendor/certificate_authority/lib/certificate_authority/serial_number.rb +6 -0
  56. data/vendor/certificate_authority/lib/certificate_authority/signing_request.rb +36 -1
  57. metadata +25 -24
  58. data/lib/leap_cli/commands/shell.rb +0 -89
  59. data/lib/leap_cli/config/macros.rb +0 -430
  60. data/lib/leap_cli/constants.rb +0 -7
  61. data/lib/leap_cli/requirements.rb +0 -19
  62. data/lib/lib_ext/markdown_document_listener.rb +0 -122
@@ -1,430 +0,0 @@
1
- # encoding: utf-8
2
- #
3
- # MACROS
4
- # these are methods available when eval'ing a value in the .json configuration
5
- #
6
- # This module is included in Config::Object
7
- #
8
-
9
- require 'base32'
10
-
11
- module LeapCli; module Config
12
- module Macros
13
- ##
14
- ## NODES
15
- ##
16
-
17
- #
18
- # the list of all the nodes
19
- #
20
- def nodes
21
- global.nodes
22
- end
23
-
24
- #
25
- # grab an environment appropriate provider
26
- #
27
- def provider
28
- global.env(@node.environment).provider
29
- end
30
-
31
- #
32
- # returns a list of nodes that match the same environment
33
- #
34
- # if @node.environment is not set, we return other nodes
35
- # where environment is not set.
36
- #
37
- def nodes_like_me
38
- nodes[:environment => @node.environment]
39
- end
40
-
41
- ##
42
- ## FILES
43
- ##
44
-
45
- class FileMissing < Exception
46
- attr_accessor :path, :options
47
- def initialize(path, options={})
48
- @path = path
49
- @options = options
50
- end
51
- def to_s
52
- @path
53
- end
54
- end
55
-
56
- #
57
- # inserts the contents of a file
58
- #
59
- def file(filename, options={})
60
- if filename.is_a? Symbol
61
- filename = [filename, @node.name]
62
- end
63
- filepath = Path.find_file(filename)
64
- if filepath
65
- if filepath =~ /\.erb$/
66
- ERB.new(File.read(filepath, :encoding => 'UTF-8'), nil, '%<>').result(binding)
67
- else
68
- File.read(filepath, :encoding => 'UTF-8')
69
- end
70
- else
71
- raise FileMissing.new(Path.named_path(filename), options)
72
- ""
73
- end
74
- end
75
-
76
- #
77
- # like #file, but allow missing files
78
- #
79
- def try_file(filename)
80
- return file(filename)
81
- rescue FileMissing
82
- return nil
83
- end
84
-
85
- #
86
- # returns what the file path will be, once the file is rsynced to the server.
87
- # an internal list of discovered file paths is saved, in order to rsync these files when needed.
88
- #
89
- # notes:
90
- #
91
- # * argument 'path' is relative to Path.provider/files or Path.provider_base/files
92
- # * the path returned by this method is absolute
93
- # * the path stored for use later by rsync is relative to Path.provider
94
- # * if the path does not exist locally, but exists in provider_base, then the default file from
95
- # provider_base is copied locally. this is required for rsync to work correctly.
96
- #
97
- def file_path(path)
98
- if path.is_a? Symbol
99
- path = [path, @node.name]
100
- end
101
- actual_path = Path.find_file(path)
102
- if actual_path.nil?
103
- Util::log 2, :skipping, "file_path(\"#{path}\") because there is no such file."
104
- nil
105
- else
106
- if actual_path =~ /^#{Regexp.escape(Path.provider_base)}/
107
- # if file is under Path.provider_base, we must copy the default file to
108
- # to Path.provider in order for rsync to be able to sync the file.
109
- local_provider_path = actual_path.sub(/^#{Regexp.escape(Path.provider_base)}/, Path.provider)
110
- FileUtils.mkdir_p File.dirname(local_provider_path), :mode => 0700
111
- FileUtils.install actual_path, local_provider_path, :mode => 0600
112
- Util.log :created, Path.relative_path(local_provider_path)
113
- actual_path = local_provider_path
114
- end
115
- if File.directory?(actual_path) && actual_path !~ /\/$/
116
- actual_path += '/' # ensure directories end with /, important for building rsync command
117
- end
118
- relative_path = Path.relative_path(actual_path)
119
- @node.file_paths << relative_path
120
- @node.manager.provider.hiera_sync_destination + '/' + relative_path
121
- end
122
- end
123
-
124
- #
125
- # inserts a named secret, generating it if needed.
126
- #
127
- # manager.export_secrets should be called later to capture any newly generated secrets.
128
- #
129
- # +length+ is the character length of the generated password.
130
- #
131
- def secret(name, length=32)
132
- @manager.secrets.set(name, Util::Secret.generate(length), @node[:environment])
133
- end
134
-
135
- # inserts a base32 encoded secret
136
- def base32_secret(name, length=20)
137
- @manager.secrets.set(name, Base32.encode(Util::Secret.generate(length)), @node[:environment])
138
- end
139
-
140
- # Picks a random obfsproxy port from given range
141
- def rand_range(name, range)
142
- @manager.secrets.set(name, rand(range), @node[:environment])
143
- end
144
-
145
- #
146
- # inserts an hexidecimal secret string, generating it if needed.
147
- #
148
- # +bit_length+ is the bits in the secret, (ie length of resulting hex string will be bit_length/4)
149
- #
150
- def hex_secret(name, bit_length=128)
151
- @manager.secrets.set(name, Util::Secret.generate_hex(bit_length), @node[:environment])
152
- end
153
-
154
- #
155
- # return a fingerprint for a x509 certificate
156
- #
157
- def fingerprint(filename)
158
- "SHA256: " + X509.fingerprint("SHA256", Path.named_path(filename))
159
- end
160
-
161
- ##
162
- ## HOSTS
163
- ##
164
-
165
- #
166
- # records the list of hosts that are encountered for this node
167
- #
168
- def hostnames(nodes)
169
- @referenced_nodes ||= ObjectList.new
170
- if nodes.is_a? Config::Object
171
- nodes = ObjectList.new nodes
172
- end
173
- nodes.each_node do |node|
174
- @referenced_nodes[node.name] ||= node
175
- end
176
- return nodes.values.collect {|node| node.domain.name}
177
- end
178
-
179
- #
180
- # Generates entries needed for updating /etc/hosts on a node (as a hash).
181
- #
182
- # Argument `nodes` can be nil or a list of nodes. If nil, only include the
183
- # IPs of the other nodes this @node as has encountered (plus all mx nodes).
184
- #
185
- # Also, for virtual machines, we use the local address if this @node is in
186
- # the same location as the node in question.
187
- #
188
- # We include the ssh public key for each host, so that the hash can also
189
- # be used to generate the /etc/ssh/known_hosts
190
- #
191
- def hosts_file(nodes=nil)
192
- if nodes.nil?
193
- if @referenced_nodes && @referenced_nodes.any?
194
- nodes = @referenced_nodes
195
- nodes = nodes.merge(nodes_like_me[:services => 'mx']) # all nodes always need to communicate with mx nodes.
196
- end
197
- end
198
- return nil unless nodes
199
- hosts = {}
200
- my_location = @node['location'] ? @node['location']['name'] : nil
201
- nodes.each_node do |node|
202
- hosts[node.name] = {'ip_address' => node.ip_address, 'domain_internal' => node.domain.internal, 'domain_full' => node.domain.full}
203
- node_location = node['location'] ? node['location']['name'] : nil
204
- if my_location == node_location
205
- if facts = @node.manager.facts[node.name]
206
- if facts['ec2_public_ipv4']
207
- hosts[node.name]['ip_address'] = facts['ec2_public_ipv4']
208
- end
209
- end
210
- end
211
- host_pub_key = Util::read_file([:node_ssh_pub_key,node.name])
212
- if host_pub_key
213
- hosts[node.name]['host_pub_key'] = host_pub_key
214
- end
215
- end
216
- hosts
217
- end
218
-
219
- ##
220
- ## STUNNEL
221
- ##
222
-
223
- #
224
- # stunnel configuration for the client side.
225
- #
226
- # +node_list+ is a ObjectList of nodes running stunnel servers.
227
- #
228
- # +port+ is the real port of the ultimate service running on the servers
229
- # that the client wants to connect to.
230
- #
231
- # About ths stunnel puppet names:
232
- #
233
- # * accept_port is the port on localhost to which local clients
234
- # can connect. it is auto generated serially.
235
- # * connect_port is the port on the stunnel server to connect to.
236
- # it is auto generated from the +port+ argument.
237
- #
238
- # The network looks like this:
239
- #
240
- # |------ stunnel client ---------------| |--------- stunnel server -----------------------|
241
- # consumer app -> localhost:accept_port -> server:connect_port -> server:port -> service app
242
- #
243
- # generates an entry appropriate to be passed directly to
244
- # create_resources(stunnel::service, hiera('..'), defaults)
245
- #
246
- def stunnel_client(node_list, port, options={})
247
- @next_stunnel_port ||= 4000
248
- hostnames(node_list) # record the hosts
249
- node_list.values.inject(Config::ObjectList.new) do |hsh, node|
250
- if node.name != self.name || options[:include_self]
251
- hsh["#{node.name}_#{port}"] = Config::Object[
252
- 'accept_port', @next_stunnel_port,
253
- 'connect', node.domain.internal,
254
- 'connect_port', stunnel_port(port)
255
- ]
256
- @next_stunnel_port += 1
257
- end
258
- hsh
259
- end
260
- end
261
-
262
- #
263
- # generates a stunnel server entry.
264
- #
265
- # +port+ is the real port targeted service.
266
- #
267
- def stunnel_server(port)
268
- {"accept" => stunnel_port(port), "connect" => "127.0.0.1:#{port}"}
269
- end
270
-
271
- #
272
- # maps a real port to a stunnel port (used as the connect_port in the client config
273
- # and the accept_port in the server config)
274
- #
275
- def stunnel_port(port)
276
- port = port.to_i
277
- if port < 50000
278
- return port + 10000
279
- else
280
- return port - 10000
281
- end
282
- end
283
-
284
- ##
285
- ## HAPROXY
286
- ##
287
-
288
- #
289
- # creates a hash suitable for configuring haproxy. the key is the node name of the server we are proxying to.
290
- #
291
- # * node_list - a hash of nodes for the haproxy servers
292
- # * stunnel_client - contains the mappings to local ports for each server node.
293
- # * non_stunnel_port - in case self is included in node_list, the port to connect to.
294
- #
295
- # 1000 weight is used for nodes in the same location.
296
- # 100 otherwise.
297
- #
298
- def haproxy_servers(node_list, stunnel_clients, non_stunnel_port=nil)
299
- default_weight = 10
300
- local_weight = 100
301
-
302
- # record the hosts_file
303
- hostnames(node_list)
304
-
305
- # create a simple map for node name -> local stunnel accept port
306
- accept_ports = stunnel_clients.inject({}) do |hsh, stunnel_entry|
307
- name = stunnel_entry.first.sub /_[0-9]+$/, ''
308
- hsh[name] = stunnel_entry.last['accept_port']
309
- hsh
310
- end
311
-
312
- # if one the nodes in the node list is ourself, then there will not be a stunnel to it,
313
- # but we need to include it anyway in the haproxy config.
314
- if node_list[self.name] && non_stunnel_port
315
- accept_ports[self.name] = non_stunnel_port
316
- end
317
-
318
- # create the first pass of the servers hash
319
- servers = node_list.values.inject(Config::ObjectList.new) do |hsh, node|
320
- weight = default_weight
321
- if self['location'] && node['location']
322
- if self.location['name'] == node.location['name']
323
- weight = local_weight
324
- end
325
- end
326
- hsh[node.name] = Config::Object[
327
- 'backup', false,
328
- 'host', 'localhost',
329
- 'port', accept_ports[node.name] || 0,
330
- 'weight', weight
331
- ]
332
- hsh
333
- end
334
-
335
- # if there are some local servers, make the others backup
336
- if servers.detect{|k,v| v.weight == local_weight}
337
- servers.each do |k,server|
338
- server['backup'] = server['weight'] == default_weight
339
- end
340
- end
341
-
342
- return servers
343
- end
344
-
345
- ##
346
- ## SSH
347
- ##
348
-
349
- #
350
- # Creates a hash from the ssh key info in users directory, for use in
351
- # updating authorized_keys file. Additionally, the 'monitor' public key is
352
- # included, which is used by the monitor nodes to run particular commands
353
- # remotely.
354
- #
355
- def authorized_keys
356
- hash = {}
357
- keys = Dir.glob(Path.named_path([:user_ssh, '*']))
358
- keys.sort.each do |keyfile|
359
- ssh_type, ssh_key = File.read(keyfile, :encoding => 'UTF-8').strip.split(" ")
360
- name = File.basename(File.dirname(keyfile))
361
- hash[name] = {
362
- "type" => ssh_type,
363
- "key" => ssh_key
364
- }
365
- end
366
- ssh_type, ssh_key = File.read(Path.named_path(:monitor_pub_key), :encoding => 'UTF-8').strip.split(" ")
367
- hash[Leap::Platform.monitor_username] = {
368
- "type" => ssh_type,
369
- "key" => ssh_key
370
- }
371
- hash
372
- end
373
-
374
- #
375
- # this is not currently used, because we put key information in the 'hosts' hash.
376
- # see 'hosts_file()'
377
- #
378
- # def known_hosts_file(nodes=nil)
379
- # if nodes.nil?
380
- # if @referenced_nodes && @referenced_nodes.any?
381
- # nodes = @referenced_nodes
382
- # end
383
- # end
384
- # return nil unless nodes
385
- # entries = []
386
- # nodes.each_node do |node|
387
- # hostnames = [node.name, node.domain.internal, node.domain.full, node.ip_address].join(',')
388
- # pub_key = Util::read_file([:node_ssh_pub_key,node.name])
389
- # if pub_key
390
- # entries << [hostnames, pub_key].join(' ')
391
- # end
392
- # end
393
- # entries.join("\n")
394
- # end
395
-
396
- ##
397
- ## UTILITY
398
- ##
399
-
400
- class AssertionFailed < Exception
401
- attr_accessor :assertion
402
- def initialize(assertion)
403
- @assertion = assertion
404
- end
405
- def to_s
406
- @assertion
407
- end
408
- end
409
-
410
- def assert(assertion)
411
- if instance_eval(assertion)
412
- true
413
- else
414
- raise AssertionFailed.new(assertion)
415
- end
416
- end
417
-
418
- #
419
- # wrap something that might fail in `try`. e.g.
420
- #
421
- # "= try{ nodes[:services => 'tor'].first.ip_address } "
422
- #
423
- def try(&block)
424
- yield
425
- rescue NoMethodError
426
- nil
427
- end
428
-
429
- end
430
- end; end
@@ -1,7 +0,0 @@
1
- module LeapCli
2
-
3
- PUPPET_DESTINATION = '/srv/leap'
4
- INITIALIZED_FILE = "#{PUPPET_DESTINATION}/initialized"
5
- DEFAULT_TAGS = ['leap_base','leap_service']
6
-
7
- end
@@ -1,19 +0,0 @@
1
- # run 'rake update-requirements' to generate this file.
2
- module LeapCli
3
- REQUIREMENTS = [
4
- "provider.ca.name",
5
- "provider.ca.server_certificates.bit_size",
6
- "provider.ca.server_certificates.digest",
7
- "provider.ca.server_certificates.life_span",
8
- "common.x509.use",
9
- "provider.domain",
10
- "provider.name",
11
- "provider.ca.server_certificates.bit_size",
12
- "provider.ca.server_certificates.digest",
13
- "provider.ca.name",
14
- "provider.ca.bit_size",
15
- "provider.ca.life_span",
16
- "provider.ca.client_certificates.unlimited_prefix",
17
- "provider.ca.client_certificates.limited_prefix"
18
- ]
19
- end
@@ -1,122 +0,0 @@
1
- require 'stringio'
2
- require 'gli/commands/help_modules/arg_name_formatter'
3
-
4
- #
5
- # adaption of RdocDocumentListener to use Markdown
6
- # see http://rtomayko.github.com/ronn/ronn-format.7
7
- #
8
-
9
- module GLI
10
- module Commands
11
- class MarkdownDocumentListener
12
-
13
- def initialize(global_options,options,arguments)
14
- @io = STDOUT #File.new(File.basename($0) + ".rdoc",'w')
15
- @nest = ''
16
- @arg_name_formatter = GLI::Commands::HelpModules::ArgNameFormatter.new
17
- end
18
-
19
- def beginning
20
- end
21
-
22
- # Called when processing has completed
23
- def ending
24
- #@io.close
25
- end
26
-
27
- # Gives you the program description
28
- def program_desc(desc)
29
- @io.puts "== #{File.basename($0)} - #{desc}"
30
- @io.puts
31
- end
32
-
33
- def program_long_desc(desc)
34
- @io.puts desc
35
- @io.puts
36
- end
37
-
38
- # Gives you the program version
39
- def version(version)
40
- @io.puts "v#{version}"
41
- @io.puts
42
- end
43
-
44
- def options
45
- if @nest.size == 0
46
- @io.puts "=== Global Options"
47
- else
48
- @io.puts "#{@nest}=== Options"
49
- end
50
- end
51
-
52
- # Gives you a flag in the current context
53
- def flag(name,aliases,desc,long_desc,default_value,arg_name,must_match,type)
54
- invocations = ([name] + Array(aliases)).map { |_| add_dashes(_) }.join('|')
55
- usage = "#{invocations} #{arg_name || 'arg'}"
56
- @io.puts "#{@nest}=== #{usage}"
57
- @io.puts
58
- @io.puts String(desc).strip
59
- @io.puts
60
- @io.puts "[Default Value] #{default_value || 'None'}"
61
- @io.puts "[Must Match] #{must_match.to_s}" unless must_match.nil?
62
- @io.puts String(long_desc).strip
63
- @io.puts
64
- end
65
-
66
- # Gives you a switch in the current context
67
- def switch(name,aliases,desc,long_desc,negetable)
68
- if negetable
69
- name = "[no-]#{name}" if name.to_s.length > 1
70
- aliases = aliases.map { |_| _.to_s.length > 1 ? "[no-]#{_}" : _ }
71
- end
72
- invocations = ([name] + aliases).map { |_| add_dashes(_) }.join('|')
73
- @io.puts "#{@nest}=== #{invocations}"
74
- @io.puts String(desc).strip
75
- @io.puts
76
- @io.puts String(long_desc).strip
77
- @io.puts
78
- end
79
-
80
- def end_options
81
- end
82
-
83
- def commands
84
- @io.puts "#{@nest}=== Commands"
85
- @nest = "#{@nest}="
86
- end
87
-
88
- # Gives you a command in the current context and creates a new context of this command
89
- def command(name,aliases,desc,long_desc,arg_name,arg_options)
90
- @io.puts "#{@nest}=== Command: <tt>#{([name] + aliases).join('|')} #{@arg_name_formatter.format(arg_name,arg_options)}</tt>"
91
- @io.puts String(desc).strip
92
- @io.puts
93
- @io.puts String(long_desc).strip
94
- @nest = "#{@nest}="
95
- end
96
-
97
- # Ends a command, and "pops" you back up one context
98
- def end_command(name)
99
- @nest.gsub!(/=$/,'')
100
- end
101
-
102
- # Gives you the name of the current command in the current context
103
- def default_command(name)
104
- @io.puts "[Default Command] #{name}" unless name.nil?
105
- end
106
-
107
- def end_commands
108
- @nest.gsub!(/=$/,'')
109
- end
110
-
111
- private
112
-
113
- def add_dashes(name)
114
- name = "-#{name}"
115
- name = "-#{name}" if name.length > 2
116
- name
117
- end
118
-
119
-
120
- end
121
- end
122
- end