leap_cli 1.2.5

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 (72) hide show
  1. data/bin/leap +81 -0
  2. data/lib/core_ext/boolean.rb +14 -0
  3. data/lib/core_ext/hash.rb +35 -0
  4. data/lib/core_ext/json.rb +42 -0
  5. data/lib/core_ext/nil.rb +5 -0
  6. data/lib/core_ext/string.rb +14 -0
  7. data/lib/leap/platform.rb +52 -0
  8. data/lib/leap_cli/commands/ca.rb +430 -0
  9. data/lib/leap_cli/commands/clean.rb +16 -0
  10. data/lib/leap_cli/commands/compile.rb +134 -0
  11. data/lib/leap_cli/commands/deploy.rb +172 -0
  12. data/lib/leap_cli/commands/facts.rb +93 -0
  13. data/lib/leap_cli/commands/inspect.rb +140 -0
  14. data/lib/leap_cli/commands/list.rb +122 -0
  15. data/lib/leap_cli/commands/new.rb +126 -0
  16. data/lib/leap_cli/commands/node.rb +272 -0
  17. data/lib/leap_cli/commands/pre.rb +99 -0
  18. data/lib/leap_cli/commands/shell.rb +67 -0
  19. data/lib/leap_cli/commands/test.rb +55 -0
  20. data/lib/leap_cli/commands/user.rb +140 -0
  21. data/lib/leap_cli/commands/util.rb +50 -0
  22. data/lib/leap_cli/commands/vagrant.rb +201 -0
  23. data/lib/leap_cli/config/macros.rb +369 -0
  24. data/lib/leap_cli/config/manager.rb +369 -0
  25. data/lib/leap_cli/config/node.rb +37 -0
  26. data/lib/leap_cli/config/object.rb +336 -0
  27. data/lib/leap_cli/config/object_list.rb +174 -0
  28. data/lib/leap_cli/config/secrets.rb +43 -0
  29. data/lib/leap_cli/config/tag.rb +18 -0
  30. data/lib/leap_cli/constants.rb +7 -0
  31. data/lib/leap_cli/leapfile.rb +97 -0
  32. data/lib/leap_cli/load_paths.rb +15 -0
  33. data/lib/leap_cli/log.rb +166 -0
  34. data/lib/leap_cli/logger.rb +216 -0
  35. data/lib/leap_cli/markdown_document_listener.rb +134 -0
  36. data/lib/leap_cli/path.rb +84 -0
  37. data/lib/leap_cli/remote/leap_plugin.rb +204 -0
  38. data/lib/leap_cli/remote/puppet_plugin.rb +66 -0
  39. data/lib/leap_cli/remote/rsync_plugin.rb +35 -0
  40. data/lib/leap_cli/remote/tasks.rb +36 -0
  41. data/lib/leap_cli/requirements.rb +19 -0
  42. data/lib/leap_cli/ssh_key.rb +130 -0
  43. data/lib/leap_cli/util/remote_command.rb +110 -0
  44. data/lib/leap_cli/util/secret.rb +54 -0
  45. data/lib/leap_cli/util/x509.rb +32 -0
  46. data/lib/leap_cli/util.rb +431 -0
  47. data/lib/leap_cli/version.rb +9 -0
  48. data/lib/leap_cli.rb +46 -0
  49. data/lib/lib_ext/capistrano_connections.rb +16 -0
  50. data/lib/lib_ext/gli.rb +52 -0
  51. data/lib/lib_ext/markdown_document_listener.rb +122 -0
  52. data/vendor/certificate_authority/lib/certificate_authority/certificate.rb +200 -0
  53. data/vendor/certificate_authority/lib/certificate_authority/certificate_revocation_list.rb +77 -0
  54. data/vendor/certificate_authority/lib/certificate_authority/distinguished_name.rb +97 -0
  55. data/vendor/certificate_authority/lib/certificate_authority/extensions.rb +266 -0
  56. data/vendor/certificate_authority/lib/certificate_authority/key_material.rb +148 -0
  57. data/vendor/certificate_authority/lib/certificate_authority/ocsp_handler.rb +144 -0
  58. data/vendor/certificate_authority/lib/certificate_authority/pkcs11_key_material.rb +65 -0
  59. data/vendor/certificate_authority/lib/certificate_authority/revocable.rb +14 -0
  60. data/vendor/certificate_authority/lib/certificate_authority/serial_number.rb +10 -0
  61. data/vendor/certificate_authority/lib/certificate_authority/signing_entity.rb +16 -0
  62. data/vendor/certificate_authority/lib/certificate_authority/signing_request.rb +56 -0
  63. data/vendor/certificate_authority/lib/certificate_authority.rb +21 -0
  64. data/vendor/rsync_command/lib/rsync_command/ssh_options.rb +159 -0
  65. data/vendor/rsync_command/lib/rsync_command/thread_pool.rb +36 -0
  66. data/vendor/rsync_command/lib/rsync_command/version.rb +3 -0
  67. data/vendor/rsync_command/lib/rsync_command.rb +96 -0
  68. data/vendor/rsync_command/test/rsync_test.rb +74 -0
  69. data/vendor/rsync_command/test/ssh_options_test.rb +61 -0
  70. data/vendor/vagrant_ssh_keys/vagrant.key +27 -0
  71. data/vendor/vagrant_ssh_keys/vagrant.pub +1 -0
  72. metadata +345 -0
@@ -0,0 +1,204 @@
1
+ #
2
+ # these methods are made available in capistrano tasks as 'leap.method_name'
3
+ # (see RemoteCommand::new_capistrano)
4
+ #
5
+
6
+ module LeapCli; module Remote; module LeapPlugin
7
+
8
+ def required_packages
9
+ "puppet ruby-hiera-puppet rsync lsb-release locales"
10
+ end
11
+
12
+ def log(*args, &block)
13
+ LeapCli::Util::log(*args, &block)
14
+ end
15
+
16
+ #
17
+ # creates directories that are owned by root and 700 permissions
18
+ #
19
+ def mkdirs(*dirs)
20
+ raise ArgumentError.new('illegal dir name') if dirs.grep(/[\' ]/).any?
21
+ run dirs.collect{|dir| "mkdir -m 700 -p #{dir}; "}.join
22
+ end
23
+
24
+ #
25
+ # echos "ok" if the node has been initialized and the required packages are installed, bails out otherwise.
26
+ #
27
+ def assert_initialized
28
+ begin
29
+ test_initialized_file = "test -f #{INITIALIZED_FILE}"
30
+ check_required_packages = "! dpkg-query -W --showformat='${Status}\n' #{required_packages} 2>&1 | grep -q -E '(deinstall|no packages)'"
31
+ run "#{test_initialized_file} && #{check_required_packages} && echo ok"
32
+ rescue Capistrano::CommandError => exc
33
+ LeapCli::Util.bail! do
34
+ exc.hosts.each do |host|
35
+ LeapCli::Util.log :error, "running deploy: node not initialized. Run 'leap node init #{host}'", :host => host
36
+ end
37
+ end
38
+ end
39
+ end
40
+
41
+ #
42
+ # bails out the deploy if the file /etc/leap/no-deploy exists.
43
+ # This kind of sucks, because it would be better to skip over nodes that have no-deploy set instead
44
+ # halting the entire deploy. As far as I know, with capistrano, there is no way to close one of the
45
+ # ssh connections in the pool and make sure it gets no further commands.
46
+ #
47
+ def check_for_no_deploy
48
+ begin
49
+ run "test ! -f /etc/leap/no-deploy"
50
+ rescue Capistrano::CommandError => exc
51
+ LeapCli::Util.bail! do
52
+ exc.hosts.each do |host|
53
+ LeapCli::Util.log "Can't continue because file /etc/leap/no-deploy exists", :host => host
54
+ end
55
+ end
56
+ end
57
+ end
58
+
59
+ def mark_initialized
60
+ run "touch #{INITIALIZED_FILE}"
61
+ end
62
+
63
+ #
64
+ # This is a hairy ugly hack, exactly the kind of stuff that makes ruby
65
+ # dangerous and too much fun for its own good.
66
+ #
67
+ # In most places, we run remote ssh without a current 'task'. This works fine,
68
+ # except that in a few places, the behavior of capistrano ssh is controlled by
69
+ # the options of the current task.
70
+ #
71
+ # We don't want to create an actual current task, because tasks are no fun
72
+ # and can't take arguments or return values. So, when we need to configure
73
+ # things that can only be configured in a task, we use this handy hack to
74
+ # fake the current task.
75
+ #
76
+ # This is NOT thread safe, but could be made to be so with some extra work.
77
+ #
78
+ def with_task(name)
79
+ task = @config.tasks[name]
80
+ @config.class.send(:alias_method, :original_current_task, :current_task)
81
+ @config.class.send(:define_method, :current_task, Proc.new(){ task })
82
+ begin
83
+ yield
84
+ ensure
85
+ @config.class.send(:remove_method, :current_task)
86
+ @config.class.send(:alias_method, :current_task, :original_current_task)
87
+ end
88
+ end
89
+
90
+ #
91
+ # similar to run(cmd, &block), but with:
92
+ #
93
+ # * exit codes
94
+ # * stdout and stderr are combined
95
+ #
96
+ def stream(cmd, &block)
97
+ command = '%s 2>&1; echo "exitcode=$?"' % cmd
98
+ run(command) do |channel, stream, data|
99
+ exitcode = nil
100
+ if data =~ /exitcode=(\d+)\n/
101
+ exitcode = $1.to_i
102
+ data.sub!(/exitcode=(\d+)\n/,'')
103
+ end
104
+ yield({:host => channel[:host], :data => data, :exitcode => exitcode})
105
+ end
106
+ end
107
+
108
+ #
109
+ # like stream, but capture all the output before returning
110
+ #
111
+ def capture(cmd, &block)
112
+ command = '%s 2>&1; echo "exitcode=$?" 2>&1;' % cmd
113
+ host_data = {}
114
+ run(command) do |channel, stream, data|
115
+ host_data[channel[:host]] ||= ""
116
+ if data =~ /exitcode=(\d+)\n/
117
+ exitcode = $1.to_i
118
+ data.sub!(/exitcode=(\d+)\n/,'')
119
+ host_data[channel[:host]] += data
120
+ yield({:host => channel[:host], :data => host_data[channel[:host]], :exitcode => exitcode})
121
+ else
122
+ host_data[channel[:host]] += data
123
+ end
124
+ end
125
+ end
126
+
127
+ #
128
+ # Run a command, with a nice status report and progress indicator.
129
+ # Only successful results are returned, errors are printed.
130
+ #
131
+ # For each successful run on each host, block is yielded with a hash like so:
132
+ #
133
+ # {:host => 'bluejay', :exitcode => 0, :data => 'shell output'}
134
+ #
135
+ def run_with_progress(cmd, &block)
136
+ ssh_failures = []
137
+ exitcode_failures = []
138
+ succeeded = []
139
+ task = LeapCli.log_level > 1 ? :standard_task : :skip_errors_task
140
+ with_task(task) do
141
+ log :querying, 'facts' do
142
+ progress " "
143
+ call_on_failure do |host|
144
+ ssh_failures << host
145
+ progress 'F'
146
+ end
147
+ capture(cmd) do |response|
148
+ if response[:exitcode] == 0
149
+ progress '.'
150
+ yield response
151
+ else
152
+ exitcode_failures << response
153
+ progress 'F'
154
+ end
155
+ end
156
+ end
157
+ end
158
+ puts "done"
159
+ if ssh_failures.any?
160
+ log :failed, 'to connect to nodes: ' + ssh_failures.join(' ')
161
+ end
162
+ if exitcode_failures.any?
163
+ log :failed, 'to run successfully:' do
164
+ exitcode_failures.each do |response|
165
+ log "[%s] exit %s - %s" % [response[:host], response[:exitcode], response[:data].strip]
166
+ end
167
+ end
168
+ end
169
+ rescue Capistrano::RemoteError => err
170
+ log :error, err.to_s
171
+ end
172
+
173
+ private
174
+
175
+ def progress(str='.')
176
+ $stdout.print str; $stdout.flush;
177
+ end
178
+
179
+ #def mkdir(dir)
180
+ # run "mkdir -p #{dir}"
181
+ #end
182
+
183
+ #def chown_root(dir)
184
+ # run "chown root -R #{dir} && chmod -R ag-rwx,u+rwX #{dir}"
185
+ #end
186
+
187
+ #def logrun(cmd)
188
+ # @streamer ||= LeapCli::Remote::LogStreamer.new
189
+ # run cmd do |channel, stream, data|
190
+ # @streamer.collect_output(channel[:host], data)
191
+ # end
192
+ #end
193
+
194
+ # return_code = nil
195
+ # run "something; echo return code: $?" do |channel, stream, data|
196
+ # if data =~ /return code: (\d+)/
197
+ # return_code = $1.to_i
198
+ # else
199
+ # Capistrano::Configuration.default_io_proc.call(channel, stream, data)
200
+ # end
201
+ # end
202
+ # puts "finished with return code: #{return_code}"
203
+
204
+ end; end; end
@@ -0,0 +1,66 @@
1
+ #
2
+ # these methods are made available in capistrano tasks as 'puppet.method_name'
3
+ # (see RemoteCommand::new_capistrano)
4
+ #
5
+
6
+ module LeapCli; module Remote; module PuppetPlugin
7
+
8
+ def apply(options)
9
+ run "#{PUPPET_DESTINATION}/bin/puppet_command set_hostname apply #{flagize(options)}"
10
+ end
11
+
12
+ private
13
+
14
+ def flagize(hsh)
15
+ hsh.inject([]) {|str, item|
16
+ if item[1] === false
17
+ str
18
+ elsif item[1] === true
19
+ str << "--" + item[0].to_s
20
+ else
21
+ str << "--" + item[0].to_s + " " + item[1].to_s
22
+ end
23
+ }.join(' ')
24
+ end
25
+
26
+ end; end; end
27
+
28
+
29
+ # def puppet(command = :noop)
30
+ # #puppet_cmd = "cd #{puppet_destination} && #{sudo_cmd} #{puppet_command} --modulepath=#{puppet_lib} #{puppet_parameters}"
31
+ # puppet_cmd = "cd #{puppet_destination} && #{sudo_cmd} #{puppet_command} #{puppet_parameters}"
32
+ # flag = command == :noop ? '--noop' : ''
33
+
34
+ # writer = if puppet_stream_output
35
+ # SupplyDrop::Writer::Streaming.new(logger)
36
+ # else
37
+ # SupplyDrop::Writer::Batched.new(logger)
38
+ # end
39
+
40
+ # writer = SupplyDrop::Writer::File.new(writer, puppet_write_to_file) unless puppet_write_to_file.nil?
41
+
42
+ # begin
43
+ # exitcode = nil
44
+ # run "#{puppet_cmd} #{flag}; echo exitcode:$?" do |channel, stream, data|
45
+ # if data =~ /exitcode:(\d+)/
46
+ # exitcode = $1
47
+ # writer.collect_output(channel[:host], "Puppet #{command} complete (#{exitcode_description(exitcode)}).\n")
48
+ # else
49
+ # writer.collect_output(channel[:host], data)
50
+ # end
51
+ # end
52
+ # ensure
53
+ # writer.all_output_collected
54
+ # end
55
+ # end
56
+
57
+ # def exitcode_description(code)
58
+ # case code
59
+ # when "0" then "no changes"
60
+ # when "2" then "changes made"
61
+ # when "4" then "failed"
62
+ # when "6" then "changes and failures"
63
+ # else code
64
+ # end
65
+ # end
66
+
@@ -0,0 +1,35 @@
1
+ #
2
+ # these methods are made available in capistrano tasks as 'rsync.method_name'
3
+ # (see RemoteCommand::new_capistrano)
4
+ #
5
+
6
+ require 'rsync_command'
7
+
8
+ module LeapCli; module Remote; module RsyncPlugin
9
+
10
+ #
11
+ # takes a block, yielded a server, that should return a hash with various rsync options.
12
+ # supported options include:
13
+ #
14
+ # {:source => '', :dest => '', :flags => '', :includes => [], :excludes => []}
15
+ #
16
+ def update
17
+ rsync = RsyncCommand.new(:logger => logger)
18
+ rsync.asynchronously(find_servers) do |server|
19
+ options = yield server
20
+ next unless options
21
+ remote_user = server.user || fetch(:user, ENV['USER'])
22
+ src = options[:source]
23
+ dest = {:user => remote_user, :host => server.host, :path => options[:dest]}
24
+ options[:ssh] = ssh_options.merge(server.options[:ssh_options]||{})
25
+ options[:chdir] ||= Path.provider
26
+ rsync.exec(src, dest, options)
27
+ end
28
+ if rsync.failed?
29
+ LeapCli::Util.bail! do
30
+ LeapCli::Util.log :failed, "to rsync to #{rsync.failures.map{|f|f[:dest][:host]}.join(' ')}"
31
+ end
32
+ end
33
+ end
34
+
35
+ end; end; end
@@ -0,0 +1,36 @@
1
+ #
2
+ # This file is evaluated just the same as a typical capistrano "deploy.rb"
3
+ # For DSL manual, see https://github.com/capistrano/capistrano/wiki
4
+ #
5
+
6
+ MAX_HOSTS = 10
7
+
8
+ task :install_authorized_keys, :max_hosts => MAX_HOSTS do
9
+ leap.log :updating, "authorized_keys" do
10
+ leap.mkdirs '/root/.ssh'
11
+ upload LeapCli::Path.named_path(:authorized_keys), '/root/.ssh/authorized_keys', :mode => '600'
12
+ end
13
+ end
14
+
15
+ task :install_prerequisites, :max_hosts => MAX_HOSTS do
16
+ leap.mkdirs LeapCli::PUPPET_DESTINATION
17
+ leap.log :updating, "package list" do
18
+ run "apt-get update"
19
+ end
20
+ leap.log :installing, "required packages" do
21
+ run "DEBIAN_FRONTEND=noninteractive apt-get -q -y -o DPkg::Options::=--force-confold install #{leap.required_packages}"
22
+ end
23
+ run "echo 'en_US.UTF-8 UTF-8' > /etc/locale.gen; locale-gen"
24
+ leap.mkdirs("/etc/leap", "/srv/leap")
25
+ leap.mark_initialized
26
+ end
27
+
28
+ #
29
+ # just dummies, used to capture task options
30
+ #
31
+
32
+ task :skip_errors_task, :on_error => :continue, :max_hosts => MAX_HOSTS do
33
+ end
34
+
35
+ task :standard_task, :max_hosts => MAX_HOSTS do
36
+ end
@@ -0,0 +1,19 @@
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
@@ -0,0 +1,130 @@
1
+ #
2
+ # A wrapper around OpenSSL::PKey::RSA instances to provide a better api for dealing with SSH keys.
3
+ #
4
+ #
5
+
6
+ require 'net/ssh'
7
+ require 'forwardable'
8
+
9
+ module LeapCli
10
+ class SshKey
11
+ extend Forwardable
12
+
13
+ attr_accessor :filename
14
+ attr_accessor :comment
15
+
16
+ ##
17
+ ## CLASS METHODS
18
+ ##
19
+
20
+ def self.load(arg1, arg2=nil)
21
+ key = nil
22
+ if arg1.is_a? OpenSSL::PKey::RSA
23
+ key = SshKey.new arg1
24
+ elsif arg1.is_a? String
25
+ if arg1 =~ /^ssh-/
26
+ type, data = arg1.split(' ')
27
+ key = SshKey.new load_from_data(data, type)
28
+ elsif File.exists? arg1
29
+ key = SshKey.new load_from_file(arg1)
30
+ key.filename = arg1
31
+ else
32
+ key = SshKey.new load_from_data(arg1, arg2)
33
+ end
34
+ end
35
+ return key
36
+ end
37
+
38
+ def self.load_from_file(filename)
39
+ public_key = nil
40
+ private_key = nil
41
+ begin
42
+ public_key = Net::SSH::KeyFactory.load_public_key(filename)
43
+ rescue NotImplementedError, Net::SSH::Exception, OpenSSL::PKey::PKeyError
44
+ begin
45
+ private_key = Net::SSH::KeyFactory.load_private_key(filename)
46
+ rescue NotImplementedError, Net::SSH::Exception, OpenSSL::PKey::PKeyError
47
+ end
48
+ end
49
+ public_key || private_key
50
+ end
51
+
52
+ def self.load_from_data(data, type='ssh-rsa')
53
+ public_key = nil
54
+ private_key = nil
55
+ begin
56
+ public_key = Net::SSH::KeyFactory.load_data_public_key("#{type} #{data}")
57
+ rescue NotImplementedError, Net::SSH::Exception, OpenSSL::PKey::PKeyError
58
+ begin
59
+ private_key = Net::SSH::KeyFactory.load_data_private_key("#{type} #{data}")
60
+ rescue NotImplementedError, Net::SSH::Exception, OpenSSL::PKey::PKeyError
61
+ end
62
+ end
63
+ public_key || private_key
64
+ end
65
+
66
+ ##
67
+ ## INSTANCE METHODS
68
+ ##
69
+
70
+ public
71
+
72
+ def initialize(rsa_key)
73
+ @key = rsa_key
74
+ end
75
+
76
+ def_delegator :@key, :fingerprint, :fingerprint
77
+ def_delegator :@key, :public?, :public?
78
+ def_delegator :@key, :private?, :private?
79
+ def_delegator :@key, :ssh_type, :type
80
+ def_delegator :@key, :public_encrypt, :public_encrypt
81
+ def_delegator :@key, :public_decrypt, :public_decrypt
82
+ def_delegator :@key, :private_encrypt, :private_encrypt
83
+ def_delegator :@key, :private_decrypt, :private_decrypt
84
+ def_delegator :@key, :params, :params
85
+ def_delegator :@key, :to_text, :to_text
86
+
87
+ def public_key
88
+ SshKey.new(@key.public_key)
89
+ end
90
+
91
+ def private_key
92
+ SshKey.new(@key.private_key)
93
+ end
94
+
95
+ #
96
+ # not sure if this will always work, but is seems to for now.
97
+ #
98
+ def bits
99
+ Net::SSH::Buffer.from(:key, @key).to_s.split("\001\000").last.size * 8
100
+ end
101
+
102
+ def summary
103
+ "%s %s %s (%s)" % [self.type, self.bits, self.fingerprint, self.filename || self.comment || '']
104
+ end
105
+
106
+ def to_s
107
+ self.type + " " + self.key
108
+ end
109
+
110
+ def key
111
+ [Net::SSH::Buffer.from(:key, @key).to_s].pack("m*").gsub(/\s/, "")
112
+ end
113
+
114
+ def ==(other_key)
115
+ return false if other_key.nil?
116
+ return false if self.class != other_key.class
117
+ return self.to_text == other_key.to_text
118
+ end
119
+
120
+ def in_known_hosts?(*identifiers)
121
+ identifiers.each do |identifier|
122
+ Net::SSH::KnownHosts.search_for(identifier).each do |key|
123
+ return true if self == key
124
+ end
125
+ end
126
+ return false
127
+ end
128
+
129
+ end
130
+ end
@@ -0,0 +1,110 @@
1
+ module LeapCli; module Util; module RemoteCommand
2
+ extend self
3
+
4
+ #
5
+ # FYI
6
+ # Capistrano::Logger::IMPORTANT = 0
7
+ # Capistrano::Logger::INFO = 1
8
+ # Capistrano::Logger::DEBUG = 2
9
+ # Capistrano::Logger::TRACE = 3
10
+ #
11
+ def ssh_connect(nodes, options={}, &block)
12
+ options ||= {}
13
+ node_list = parse_node_list(nodes)
14
+
15
+ cap = new_capistrano
16
+ cap.logger = LeapCli::Logger.new(:level => LeapCli.log_level)
17
+ user = options[:user] || 'root'
18
+ cap.set :user, user
19
+ cap.set :ssh_options, ssh_options # ssh options common to all nodes
20
+ cap.set :use_sudo, false # we may want to change this in the future
21
+
22
+ # Allow password authentication when we are bootstraping a single node
23
+ # (and key authentication fails).
24
+ if options[:bootstrap] && node_list.size == 1
25
+ hostname = node_list.values.first.name
26
+ if options[:echo]
27
+ cap.set(:password) { ask "Root SSH password for #{user}@#{hostname}> " }
28
+ else
29
+ cap.set(:password) { Capistrano::CLI.password_prompt " * Typed password will be hidden (use --echo to make it visible)\nRoot SSH password for #{user}@#{hostname}> " }
30
+ end
31
+ end
32
+
33
+ node_list.each do |name, node|
34
+ cap.server node.name, :dummy_arg, node_options(node, options[:ssh_options])
35
+ end
36
+
37
+ yield cap
38
+ end
39
+
40
+ private
41
+
42
+ #
43
+ # For available options, see http://net-ssh.github.com/net-ssh/classes/Net/SSH.html#method-c-start
44
+ #
45
+ def ssh_options
46
+ {
47
+ :config => false,
48
+ :global_known_hosts_file => path(:known_hosts),
49
+ :user_known_hosts_file => '/dev/null',
50
+ :paranoid => true
51
+ }
52
+ end
53
+
54
+ #
55
+ # For notes on advanced ways to set server-specific options, see
56
+ # http://railsware.com/blog/2011/11/02/advanced-server-definitions-in-capistrano/
57
+ #
58
+ # if, in the future, we want to do per-node password options, it would be done like so:
59
+ #
60
+ # password_proc = Proc.new {Capistrano::CLI.password_prompt "Root SSH password for #{node.name}"}
61
+ # return {:password => password_proc}
62
+ #
63
+ def node_options(node, ssh_options_override=nil)
64
+ ssh_options_override ||= {}
65
+ {
66
+ :ssh_options => {
67
+ # :host_key_alias => node.name, << incompatible with ports in known_hosts
68
+ :host_name => node.ip_address,
69
+ :port => node.ssh.port
70
+ }.merge(contingent_ssh_options_for_node(node)).merge(ssh_options_override)
71
+ }
72
+ end
73
+
74
+ def new_capistrano
75
+ # load once the library files
76
+ @capistrano_enabled ||= begin
77
+ require 'capistrano'
78
+ require 'capistrano/cli'
79
+ require 'lib_ext/capistrano_connections'
80
+ require 'leap_cli/remote/leap_plugin'
81
+ require 'leap_cli/remote/puppet_plugin'
82
+ require 'leap_cli/remote/rsync_plugin'
83
+ Capistrano.plugin :leap, LeapCli::Remote::LeapPlugin
84
+ Capistrano.plugin :puppet, LeapCli::Remote::PuppetPlugin
85
+ Capistrano.plugin :rsync, LeapCli::Remote::RsyncPlugin
86
+ true
87
+ end
88
+
89
+ # create capistrano instance
90
+ cap = Capistrano::Configuration.new
91
+
92
+ # add tasks to capistrano instance
93
+ cap.load File.dirname(__FILE__) + '/../remote/tasks.rb'
94
+
95
+ return cap
96
+ end
97
+
98
+ def contingent_ssh_options_for_node(node)
99
+ opts = {}
100
+ if node.vagrant?
101
+ opts[:keys] = [vagrant_ssh_key_file]
102
+ opts[:paranoid] = false # we skip host checking for vagrant nodes, because fingerprint is different for everyone.
103
+ if LeapCli::log_level <= 1
104
+ opts[:verbose] = :error # suppress all the warnings about adding host keys to known_hosts, since it is not actually doing that.
105
+ end
106
+ end
107
+ return opts
108
+ end
109
+
110
+ end; end; end
@@ -0,0 +1,54 @@
1
+ #
2
+ # A simple secret generator
3
+ #
4
+ # Uses OpenSSL random number generator instead of Ruby's rand function
5
+ #
6
+ require 'openssl'
7
+
8
+ module LeapCli; module Util
9
+ class Secret
10
+ CHARS = ('A'..'Z').to_a + ('a'..'z').to_a + ('0'..'9').to_a + "_".split(//u) - "io01lO".split(//u)
11
+ HEX = (0..9).to_a + ('a'..'f').to_a
12
+
13
+ #
14
+ # generate a secret with with no ambiguous characters.
15
+ #
16
+ # +length+ is in chars
17
+ #
18
+ # Only alphanumerics are allowed, in order to make these passwords work
19
+ # for REST url calls and to allow you to easily copy and paste them.
20
+ #
21
+ def self.generate(length = 16)
22
+ seed
23
+ OpenSSL::Random.random_bytes(length).bytes.to_a.collect { |byte|
24
+ CHARS[ byte % CHARS.length ]
25
+ }.join
26
+ end
27
+
28
+ #
29
+ # generates a hex secret, instead of an alphanumeric on.
30
+ #
31
+ # length is in bits
32
+ #
33
+ def self.generate_hex(length = 128)
34
+ seed
35
+ OpenSSL::Random.random_bytes(length/4).bytes.to_a.collect { |byte|
36
+ HEX[ byte % HEX.length ]
37
+ }.join
38
+ end
39
+
40
+ private
41
+
42
+ def self.seed
43
+ @pid ||= 0
44
+ pid = $$
45
+ if @pid != pid
46
+ now = Time.now
47
+ ary = [now.to_i, now.nsec, @pid, pid]
48
+ OpenSSL::Random.seed(ary.to_s)
49
+ @pid = pid
50
+ end
51
+ end
52
+
53
+ end
54
+ end; end
@@ -0,0 +1,32 @@
1
+ require 'openssl'
2
+ require 'certificate_authority'
3
+ require 'digest'
4
+ require 'digest/md5'
5
+ require 'digest/sha1'
6
+
7
+ module LeapCli; module X509
8
+ extend self
9
+
10
+ #
11
+ # returns a fingerprint of a x509 certificate
12
+ #
13
+ def fingerprint(digest, cert_file)
14
+ if cert_file.is_a? String
15
+ cert = OpenSSL::X509::Certificate.new(Util.read_file!(cert_file))
16
+ elsif cert_file.is_a? OpenSSL::X509::Certificate
17
+ cert = cert_file
18
+ elsif cert_file.is_a? CertificateAuthority::Certificate
19
+ cert = cert_file.openssl_body
20
+ end
21
+ digester = case digest
22
+ when "MD5" then Digest::MD5.new
23
+ when "SHA1" then Digest::SHA1.new
24
+ when "SHA256" then Digest::SHA256.new
25
+ when "SHA384" then Digest::SHA384.new
26
+ when "SHA512" then Digest::SHA512.new
27
+ end
28
+ digester.hexdigest(cert.to_der)
29
+ end
30
+
31
+
32
+ end; end