diff options
Diffstat (limited to 'lib/leap_cli/commands')
-rw-r--r-- | lib/leap_cli/commands/ca.rb | 106 | ||||
-rw-r--r-- | lib/leap_cli/commands/compile.rb | 11 | ||||
-rw-r--r-- | lib/leap_cli/commands/deploy.rb | 163 | ||||
-rw-r--r-- | lib/leap_cli/commands/env.rb | 53 | ||||
-rw-r--r-- | lib/leap_cli/commands/facts.rb | 4 | ||||
-rw-r--r-- | lib/leap_cli/commands/inspect.rb | 2 | ||||
-rw-r--r-- | lib/leap_cli/commands/list.rb | 18 | ||||
-rw-r--r-- | lib/leap_cli/commands/node.rb | 44 | ||||
-rw-r--r-- | lib/leap_cli/commands/pre.rb | 29 | ||||
-rw-r--r-- | lib/leap_cli/commands/shell.rb | 17 | ||||
-rw-r--r-- | lib/leap_cli/commands/test.rb | 4 | ||||
-rw-r--r-- | lib/leap_cli/commands/user.rb | 16 | ||||
-rw-r--r-- | lib/leap_cli/commands/vagrant.rb | 2 |
13 files changed, 362 insertions, 107 deletions
diff --git a/lib/leap_cli/commands/ca.rb b/lib/leap_cli/commands/ca.rb index 46e3494..579e305 100644 --- a/lib/leap_cli/commands/ca.rb +++ b/lib/leap_cli/commands/ca.rb @@ -1,6 +1,6 @@ -require 'openssl' -require 'certificate_authority' -require 'date' +autoload :OpenSSL, 'openssl' +autoload :CertificateAuthority, 'certificate_authority' +autoload :Date, 'date' require 'digest/md5' module LeapCli; module Commands @@ -36,6 +36,7 @@ module LeapCli; module Commands nodes = manager.filter!(args) nodes.each_node do |node| + warn_if_commercial_cert_will_soon_expire(node) if !node.x509.use remove_file!([:node_x509_key, node.name]) remove_file!([:node_x509_cert, node.name]) @@ -81,9 +82,19 @@ module LeapCli; module Commands # http://www.redkestrel.co.uk/Articles/CSR.html # cert.desc "Creates a CSR for use in buying a commercial X.509 certificate." - cert.long_desc "Unless specified, the CSR is created for the provider's primary domain. The properties used for this CSR come from `provider.ca.server_certificates`." + cert.long_desc "Unless specified, the CSR is created for the provider's primary domain. "+ + "The properties used for this CSR come from `provider.ca.server_certificates`, "+ + "but may be overridden here." cert.command :csr do |csr| csr.flag 'domain', :arg_name => 'DOMAIN', :desc => 'Specify what domain to create the CSR for.' + csr.flag ['organization', 'O'], :arg_name => 'ORGANIZATION', :desc => "Override default O in distinguished name." + csr.flag ['unit', 'OU'], :arg_name => 'UNIT', :desc => "Set OU in distinguished name." + csr.flag 'email', :arg_name => 'EMAIL', :desc => "Set emailAddress in distinguished name." + csr.flag ['locality', 'L'], :arg_name => 'LOCALITY', :desc => "Set L in distinguished name." + csr.flag ['state', 'ST'], :arg_name => 'STATE', :desc => "Set ST in distinguished name." + csr.flag ['country', 'C'], :arg_name => 'COUNTRY', :desc => "Set C in distinguished name." + csr.flag :bits, :arg_name => 'BITS', :desc => "Override default certificate bit length" + csr.flag :digest, :arg_name => 'DIGEST', :desc => "Override default signature digest" csr.action do |global_options,options,args| assert_config! 'provider.domain' assert_config! 'provider.name' @@ -97,24 +108,25 @@ module LeapCli; module Commands # RSA key keypair = CertificateAuthority::MemoryKeyMaterial.new - log :generating, "%s bit RSA key" % server_certificates.bit_size do - keypair.generate_key(server_certificates.bit_size) + bit_size = (options[:bits] || server_certificates.bit_size).to_i + log :generating, "%s bit RSA key" % bit_size do + keypair.generate_key(bit_size) write_file! [:commercial_key, domain], keypair.private_key.to_pem end # CSR dn = CertificateAuthority::DistinguishedName.new - csr = CertificateAuthority::SigningRequest.new - dn.common_name = domain - dn.organization = provider.name[provider.default_language] - dn.country = server_certificates['country'] # optional - dn.state = server_certificates['state'] # optional - dn.locality = server_certificates['locality'] # optional - - log :generating, "CSR with commonName => '%s', organization => '%s'" % [dn.common_name, dn.organization] do - csr.distinguished_name = dn - csr.key_material = keypair - csr.digest = server_certificates.digest + dn.common_name = domain + dn.organization = options[:organization] || provider.name[provider.default_language] + dn.ou = options[:organizational_unit] # optional + dn.email_address = options[:email] # optional + dn.country = options[:country] || server_certificates['country'] # optional + dn.state = options[:state] || server_certificates['state'] # optional + dn.locality = options[:locality] || server_certificates['locality'] # optional + + digest = options[:digest] || server_certificates.digest + log :generating, "CSR with #{digest} digest and #{print_dn(dn)}" do + csr = create_csr(dn, keypair, digest) request = csr.to_x509_csr write_file! [:commercial_csr, domain], csr.to_pem end @@ -191,7 +203,7 @@ module LeapCli; module Commands return true else cert = load_certificate_file([:node_x509_cert, node.name]) - if cert.not_after < months_from_yesterday(1) + if cert.not_after < months_from_yesterday(2) log :updating, "cert for node '#{node.name}' because it will expire soon" return true end @@ -222,6 +234,18 @@ module LeapCli; module Commands return false end + def warn_if_commercial_cert_will_soon_expire(node) + dns_names_for_node(node).each do |domain| + if file_exists?([:commercial_cert, domain]) + cert = load_certificate_file([:commercial_cert, domain]) + if cert.not_after < months_from_yesterday(2) + log :warning, "the commercial certificate '#{Path.relative_path([:commercial_cert, domain])}' will expire soon. "+ + "You should renew it with `leap cert csr --domain #{domain}`." + end + end + end + end + def generate_cert_for_node(node) return if node.x509.use == false @@ -262,6 +286,43 @@ module LeapCli; module Commands yield cert.key_material.private_key.to_pem, cert.to_pem end + # + # creates a CSR and returns it. + # with the correct extReq attribute so that the CA + # doens't generate certs with extensions we don't want. + # + def create_csr(dn, keypair, digest) + csr = CertificateAuthority::SigningRequest.new + csr.distinguished_name = dn + csr.key_material = keypair + csr.digest = digest + + # define extensions manually (library doesn't support setting these on CSRs) + extensions = [] + extensions << CertificateAuthority::Extensions::BasicConstraints.new.tap {|basic| + basic.ca = false + } + extensions << CertificateAuthority::Extensions::KeyUsage.new.tap {|keyusage| + keyusage.usage = ["digitalSignature", "keyEncipherment"] + } + extensions << CertificateAuthority::Extensions::ExtendedKeyUsage.new.tap {|extkeyusage| + extkeyusage.usage = [ "serverAuth"] + } + + # convert extensions to attribute 'extReq' + # aka "Requested Extensions" + factory = OpenSSL::X509::ExtensionFactory.new + attrval = OpenSSL::ASN1::Set([OpenSSL::ASN1::Sequence( + extensions.map{|e| factory.create_ext(e.openssl_identifier, e.to_s, e.critical)} + )]) + attrs = [ + OpenSSL::X509::Attribute.new("extReq", attrval), + ] + csr.attributes = attrs + + return csr + end + def ca_root @ca_root ||= begin load_certificate_file(:ca_cert, :ca_key) @@ -406,6 +467,15 @@ module LeapCli; module Commands cert_serial_number(domain_name).to_s(36) end + # prints CertificateAuthority::DistinguishedName fields + def print_dn(dn) + fields = {} + [:common_name, :locality, :state, :country, :organization, :organizational_unit, :email_address].each do |attr| + fields[attr] = dn.send(attr) if dn.send(attr) + end + fields.inspect + end + ## ## TIME HELPERS ## diff --git a/lib/leap_cli/commands/compile.rb b/lib/leap_cli/commands/compile.rb index 63c2047..644ce2a 100644 --- a/lib/leap_cli/commands/compile.rb +++ b/lib/leap_cli/commands/compile.rb @@ -5,9 +5,18 @@ module LeapCli desc "Compile generated files." command :compile do |c| c.desc 'Compiles node configuration files into hiera files used for deployment.' + c.arg_name 'ENVIRONMENT', :optional => true c.command :all do |all| all.action do |global_options,options,args| - compile_hiera_files + environment = args.first + if !LeapCli.leapfile.environment.nil? && !environment.nil? && environment != LeapCli.leapfile.environment + bail! "You cannot specify an ENVIRONMENT argument while the environment is pinned." + end + if environment && manager.environment_names.include?(environment) + compile_hiera_files(manager.filter([environment])) + else + compile_hiera_files(manager.filter) + end end end diff --git a/lib/leap_cli/commands/deploy.rb b/lib/leap_cli/commands/deploy.rb index 814407f..6589837 100644 --- a/lib/leap_cli/commands/deploy.rb +++ b/lib/leap_cli/commands/deploy.rb @@ -17,9 +17,12 @@ module LeapCli # --force c.switch :force, :desc => 'Deploy even if there is a lockfile.', :negatable => false + # --dev + c.switch :dev, :desc => "Development mode: don't run 'git submodule update' before deploy.", :negatable => false + # --tags c.flag :tags, :desc => 'Specify tags to pass through to puppet (overriding the default).', - :default_value => DEFAULT_TAGS.join(','), :arg_name => 'TAG[,TAG]' + :arg_name => 'TAG[,TAG]' c.flag :port, :desc => 'Override the default SSH port.', :arg_name => 'PORT' @@ -28,9 +31,12 @@ module LeapCli :arg_name => 'IPADDRESS' c.action do |global,options,args| - init_submodules - nodes = filter_deploy_nodes(args) + if options[:dev] != true + init_submodules + end + + nodes = manager.filter!(args) if nodes.size > 1 say "Deploying to these nodes: #{nodes.keys.join(', ')}" if !global[:yes] && !agree("Continue? ") @@ -38,7 +44,16 @@ module LeapCli end end - compile_hiera_files + environments = nodes.field('environment').uniq + if environments.empty? + environments = [nil] + end + environments.each do |env| + check_platform_pinning(env) + end + # compile hiera files for all the nodes in every environment that is + # being deployed and only those environments. + compile_hiera_files(manager.filter(environments)) ssh_connect(nodes, connect_options(options)) do |ssh| ssh.leap.log :checking, 'node' do @@ -58,39 +73,128 @@ module LeapCli end end end + end end private + # + # The currently activated provider.json could have loaded some pinning + # information for the platform. If this is the case, refuse to deploy + # if there is a mismatch. + # + # For example: + # + # "platform": { + # "branch": "develop" + # "version": "1.0..99" + # "commit": "e1d6280e0a8c565b7fb1a4ed3969ea6fea31a5e2..HEAD" + # } + # + def check_platform_pinning(environment) + provider = manager.env(environment).provider + return unless provider['platform'] + + if environment.nil? || environment == 'default' + provider_json = 'provider.json' + else + provider_json = 'provider.' + environment + '.json' + end + + # can we have json schema verification already? + unless provider.platform.is_a? Hash + bail!('`platform` attribute in #{provider_json} must be a hash (was %s).' % provider.platform.inspect) + end + + # check version + if provider.platform['version'] + if !Leap::Platform.version_in_range?(provider.platform.version) + say("The platform is pinned to a version range of '#{provider.platform.version}' "+ + "by the `platform.version` property in #{provider_json}, but the platform "+ + "(#{Path.platform}) has version #{Leap::Platform.version}.") + quit!("OK. Bye.") unless agree("Do you really want to deploy from the wrong version? ") + end + end + + # check branch + if provider.platform['branch'] + if !is_git_directory?(Path.platform) + say("The platform is pinned to a particular branch by the `platform.branch` property "+ + "in #{provider_json}, but the platform directory (#{Path.platform}) is not a git repository.") + quit!("OK. Bye.") unless agree("Do you really want to deploy anyway? ") + end + unless provider.platform.branch == current_git_branch(Path.platform) + say("The platform is pinned to branch '#{provider.platform.branch}' by the `platform.branch` property "+ + "in #{provider_json}, but the current branch is '#{current_git_branch(Path.platform)}' " + + "(for directory '#{Path.platform}')") + quit!("OK. Bye.") unless agree("Do you really want to deploy from the wrong branch? ") + end + end + + # check commit + if provider.platform['commit'] + if !is_git_directory?(Path.platform) + say("The platform is pinned to a particular commit range by the `platform.commit` property "+ + "in #{provider_json}, but the platform directory (#{Path.platform}) is not a git repository.") + quit!("OK. Bye.") unless agree("Do you really want to deploy anyway? ") + end + current_commit = current_git_commit(Path.platform) + Dir.chdir(Path.platform) do + commit_range = assert_run!("git log --pretty='format:%H' '#{provider.platform.commit}'", + "The platform is pinned to a particular commit range by the `platform.commit` property "+ + "in #{provider_json}, but git was not able to find commits in the range specified "+ + "(#{provider.platform.commit}).") + commit_range = commit_range.split("\n") + if !commit_range.include?(current_commit) && + provider.platform.commit.split('..').first != current_commit + say("The platform is pinned via the `platform.commit` property in #{provider_json} " + + "to a commit in the range #{provider.platform.commit}, but the current HEAD " + + "(#{current_commit}) is not in that range.") + quit!("OK. Bye.") unless agree("Do you really want to deploy from the wrong commit? ") + end + end + end + end + def sync_hiera_config(ssh) - dest_dir = provider.hiera_sync_destination ssh.rsync.update do |server| node = manager.node(server.host) hiera_file = Path.relative_path([:hiera, node.name]) - ssh.leap.log hiera_file + ' -> ' + node.name + ':' + dest_dir + '/hiera.yaml' + ssh.leap.log hiera_file + ' -> ' + node.name + ':' + Leap::Platform.hiera_path { :source => hiera_file, - :dest => dest_dir + '/hiera.yaml', + :dest => Leap::Platform.hiera_path, :flags => "-rltp --chmod=u+rX,go-rwx" } end end + # + # sync various support files. + # def sync_support_files(ssh) - dest_dir = provider.hiera_sync_destination + dest_dir = Leap::Platform.files_dir + source_files = [] + if Path.defined?(:custom_puppet_dir) && file_exists?(:custom_puppet_dir) + source_files += [:custom_puppet_dir, :custom_puppet_modules_dir, :custom_puppet_manifests_dir].collect{|path| + Path.relative_path(path, Path.provider) + '/' # rsync needs trailing slash + } + ensure_dir :custom_puppet_modules_dir + end ssh.rsync.update do |server| node = manager.node(server.host) files_to_sync = node.file_paths.collect {|path| Path.relative_path(path, Path.provider) } + files_to_sync += source_files if files_to_sync.any? ssh.leap.log(files_to_sync.join(', ') + ' -> ' + node.name + ':' + dest_dir) { - :chdir => Path.provider, + :chdir => Path.named_path(:files_dir), :source => ".", :dest => dest_dir, :excludes => "*", - :includes => calculate_includes_from_files(files_to_sync), - :flags => "-rltp --chmod=u+rX,go-rwx --relative --delete --delete-excluded --filter='protect hiera.yaml' --copy-links" + :includes => calculate_includes_from_files(files_to_sync, '/files'), + :flags => "-rltp --chmod=u+rX,go-rwx --relative --delete --delete-excluded --copy-links" } else nil @@ -100,9 +204,9 @@ module LeapCli def sync_puppet_files(ssh) ssh.rsync.update do |server| - ssh.leap.log(Path.platform + '/[bin,tests,puppet] -> ' + server.host + ':' + LeapCli::PUPPET_DESTINATION) + ssh.leap.log(Path.platform + '/[bin,tests,puppet] -> ' + server.host + ':' + Leap::Platform.leap_dir) { - :dest => LeapCli::PUPPET_DESTINATION, + :dest => Leap::Platform.leap_dir, :source => '.', :chdir => Path.platform, :excludes => '*', @@ -112,7 +216,12 @@ module LeapCli end end + # + # ensure submodules are up to date, if the platform is a git + # repository. + # def init_submodules + return unless is_git_directory?(Path.platform) Dir.chdir Path.platform do assert_run! "git submodule sync" statuses = assert_run! "git submodule status" @@ -126,11 +235,17 @@ module LeapCli end end - def calculate_includes_from_files(files) + # + # converts an array of file paths into an array + # suitable for --include of rsync + # + # if set, `prefix` is stripped off. + # + def calculate_includes_from_files(files, prefix=nil) return nil unless files and files.any? # prepend '/' (kind of like ^ for rsync) - includes = files.collect {|file| '/' + file} + includes = files.collect {|file| file =~ /^\// ? file : '/' + file } # include all sub files of specified directories includes.size.times do |i| @@ -148,6 +263,10 @@ module LeapCli end end + if prefix + includes.map! {|path| path.sub(/^#{Regexp.escape(prefix)}\//, '/')} + end + return includes end @@ -155,23 +274,11 @@ module LeapCli if options[:tags] tags = options[:tags].split(',') else - tags = LeapCli::DEFAULT_TAGS.dup + tags = Leap::Platform.default_puppet_tags.dup end tags << 'leap_slow' unless options[:fast] tags.join(',') end - # - # for safety, we allow production deploys to be turned off in the Leapfile. - # - def filter_deploy_nodes(filter) - nodes = manager.filter!(filter) - if !leapfile.allow_production_deploy - nodes = nodes[:environment => "!production"] - assert! nodes.any?, "Skipping deploy because @allow_production_deploy is disabled." - end - nodes - end - end end diff --git a/lib/leap_cli/commands/env.rb b/lib/leap_cli/commands/env.rb new file mode 100644 index 0000000..b2f585d --- /dev/null +++ b/lib/leap_cli/commands/env.rb @@ -0,0 +1,53 @@ +module LeapCli + module Commands + + desc "Manipulate and query environment information." + long_desc "The 'environment' node property can be used to isolate sets of nodes into entirely separate environments. "+ + "A node in one environment will never interact with a node from another environment. "+ + "Environment pinning works by modifying your ~/.leaprc file and is dependent on the "+ + "absolute file path of your provider directory (pins don't apply if you move the directory)" + command :env do |c| + c.desc "List the available environments. The pinned environment, if any, will be marked with '*'." + c.command :ls do |ls| + ls.action do |global_options, options, args| + envs = ["default"] + manager.environment_names.compact.sort + envs.each do |env| + if env + if LeapCli.leapfile.environment == env + puts "* #{env}" + else + puts " #{env}" + end + end + end + end + end + + c.desc 'Pin the environment to ENVIRONMENT. All subsequent commands will only apply to nodes in this environment.' + c.arg_name 'ENVIRONMENT' + c.command :pin do |pin| + pin.action do |global_options,options,args| + environment = args.first + if environment == 'default' || + (environment && manager.environment_names.include?(environment)) + LeapCli.leapfile.set('environment', environment) + log 0, :saved, "~/.leaprc with environment set to #{environment}." + end + end + end + + c.desc "Unpin the environment. All subsequent commands will apply to all nodes." + c.command :unpin do |unpin| + unpin.action do |global_options, options, args| + LeapCli.leapfile.unset('environment') + log 0, :saved, "~/.leaprc, removing environment property." + end + end + + c.default_command :ls + end + + protected + + end +end
\ No newline at end of file diff --git a/lib/leap_cli/commands/facts.rb b/lib/leap_cli/commands/facts.rb index d607086..65eda61 100644 --- a/lib/leap_cli/commands/facts.rb +++ b/lib/leap_cli/commands/facts.rb @@ -91,7 +91,9 @@ module LeapCli; module Commands end end end - overwrite_existing = args.empty? + # only overwrite the entire facts file if and only if we are gathering facts + # for all nodes in all environments. + overwrite_existing = args.empty? && LeapCli.leapfile.environment.nil? update_facts_file(new_facts, overwrite_existing) end diff --git a/lib/leap_cli/commands/inspect.rb b/lib/leap_cli/commands/inspect.rb index 746a80c..e8f5caf 100644 --- a/lib/leap_cli/commands/inspect.rb +++ b/lib/leap_cli/commands/inspect.rb @@ -109,7 +109,7 @@ module LeapCli; module Commands if options[:base] inspect_json manager.base_provider elsif arg =~ /provider\.(.*)\.json/ - inspect_json manager.providers[$1] + inspect_json manager.env($1).provider else inspect_json manager.provider end diff --git a/lib/leap_cli/commands/list.rb b/lib/leap_cli/commands/list.rb index 5b84113..b8d7739 100644 --- a/lib/leap_cli/commands/list.rb +++ b/lib/leap_cli/commands/list.rb @@ -15,24 +15,25 @@ module LeapCli; module Commands c.flag 'print', :desc => 'What attributes to print (optional)' c.switch 'disabled', :desc => 'Include disabled nodes in the list.', :negatable => false c.action do |global_options,options,args| + # don't rely on default manager(), because we want to pass custom options to load() + manager = LeapCli::Config::Manager.new if global_options[:color] colors = ['cyan', 'white'] else colors = [nil, nil] end puts - if options['disabled'] - manager.load(:include_disabled => true) # reload, with disabled nodes - end + manager.load(:include_disabled => options['disabled'], :continue_on_error => true) if options['print'] print_node_properties(manager.filter(args), options['print']) else if args.any? NodeTable.new(manager.filter(args), colors).run else - TagTable.new('SERVICES', manager.services, colors).run - TagTable.new('TAGS', manager.tags, colors).run - NodeTable.new(manager.nodes, colors).run + environment = LeapCli.leapfile.environment || '_all_' + TagTable.new('SERVICES', manager.env(environment).services, colors).run + TagTable.new('TAGS', manager.env(environment).tags, colors).run + NodeTable.new(manager.filter(), colors).run end end end @@ -41,11 +42,9 @@ module LeapCli; module Commands private def self.print_node_properties(nodes, properties) - node_list = manager.nodes properties = properties.split(',') max_width = nodes.keys.inject(0) {|max,i| [i.size,max].max} nodes.each_node do |node| - node.evaluate value = properties.collect{|prop| if node[prop].nil? "null" @@ -68,7 +67,7 @@ module LeapCli; module Commands @colors = colors end def run - tags = @tag_list.keys.sort + tags = @tag_list.keys.select{|tag| tag !~ /^_/}.sort # sorted list of tags, excluding _partials max_width = [20, (tags+[@heading]).inject(0) {|max,i| [i.size,max].max}].max table :border => false do row :color => @colors[0] do @@ -76,6 +75,7 @@ module LeapCli; module Commands column "NODES", :width => HighLine::SystemExtensions.terminal_size.first - max_width - 2, :padding => 2 end tags.each do |tag| + next if @tag_list[tag].node_list.empty? row :color => @colors[1] do column tag column @tag_list[tag].node_list.keys.sort.join(', ') diff --git a/lib/leap_cli/commands/node.rb b/lib/leap_cli/commands/node.rb index 304d86b..f1e1cf8 100644 --- a/lib/leap_cli/commands/node.rb +++ b/lib/leap_cli/commands/node.rb @@ -1,6 +1,4 @@ -require 'net/ssh/known_hosts' -require 'tempfile' -require 'ipaddr' +autoload :IPAddr, 'ipaddr' module LeapCli; module Commands @@ -194,18 +192,40 @@ module LeapCli; module Commands end end + # + # get the public host key for a host. + # return SshKey object representation of the key. + # + # Only supports ecdsa or rsa host keys. ecdsa is preferred if both are available. + # def get_public_key_for_ip(address, port=22) assert_bin!('ssh-keyscan') - output = assert_run! "ssh-keyscan -p #{port} -t ecdsa #{address}", "Could not get the public host key from #{address}:#{port}. Maybe sshd is not running?" - line = output.split("\n").grep(/^[^#]/).first - if line =~ /No route to host/ - bail! :failed, 'ssh-keyscan: no route to %s' % address - elsif line =~ /no hostkey alg/ - bail! :failed, 'ssh-keyscan: no hostkey alg (must be missing an ecdsa public host key)' + output = assert_run! "ssh-keyscan -p #{port} #{address}", "Could not get the public host key from #{address}:#{port}. Maybe sshd is not running?" + if output.empty? + bail! :failed, "ssh-keyscan returned empty output." + end + + # key arrays [ip, key_type, public_key] + rsa_key = nil + ecdsa_key = nil + + lines = output.split("\n").grep(/^[^#]/) + lines.each do |line| + if line =~ /No route to host/ + bail! :failed, 'ssh-keyscan: no route to %s' % address + elsif line =~ / ssh-rsa / + rsa_key = line.split(' ') + elsif line =~ / ecdsa-sha2-nistp256 / + ecdsa_key = line.split(' ') + end + end + + if rsa_key.nil? && ecdsa_key.nil? + bail! "ssh-keyscan got zero host keys back! Output was: #{output}" + else + key = ecdsa_key || rsa_key + return SshKey.load(key[2], key[1]) end - assert! line, "Got zero host keys back!" - ip, key_type, public_key = line.split(' ') - return SshKey.load(public_key, key_type) end def is_node_alive(node, options) diff --git a/lib/leap_cli/commands/pre.rb b/lib/leap_cli/commands/pre.rb index 4b62b5b..7a64c15 100644 --- a/lib/leap_cli/commands/pre.rb +++ b/lib/leap_cli/commands/pre.rb @@ -21,7 +21,7 @@ module LeapCli; module Commands switch :yes, :negatable => false desc 'Enable debugging library (leap_cli development only)' - switch :debug, :negatable => false + switch [:d, :debug], :negatable => false desc 'Disable colors in output' default_value true @@ -31,12 +31,7 @@ module LeapCli; module Commands # # set verbosity # - LeapCli.log_level = global[:verbose].to_i - if LeapCli.log_level > 1 - ENV['GLI_DEBUG'] = "true" - else - ENV['GLI_DEBUG'] = "false" - end + LeapCli.set_log_level(global[:verbose].to_i) # # load Leapfile @@ -53,13 +48,6 @@ module LeapCli; module Commands bail! { log :missing, "platform directory '#{Path.platform}'" } end - if LeapCli.leapfile.platform_branch && LeapCli::Util.is_git_directory?(Path.platform) - branch = LeapCli::Util.current_git_branch(Path.platform) - if branch != LeapCli.leapfile.platform_branch - bail! "Wrong branch for #{Path.platform}. Was '#{branch}', should be '#{LeapCli.leapfile.platform_branch}'. Edit Leapfile to disable this check." - end - end - # # set log file # @@ -68,18 +56,7 @@ module LeapCli; module Commands log_version LeapCli.log_in_color = global[:color] - # - # load all the nodes everything - # - manager - - # - # check requirements - # - REQUIREMENTS.each do |key| - assert_config! key - end - + true end private diff --git a/lib/leap_cli/commands/shell.rb b/lib/leap_cli/commands/shell.rb index 2ccb3de..2138e9d 100644 --- a/lib/leap_cli/commands/shell.rb +++ b/lib/leap_cli/commands/shell.rb @@ -3,8 +3,10 @@ module LeapCli; module Commands desc 'Log in to the specified node with an interactive shell.' arg_name 'NAME' #, :optional => false, :multiple => false command :ssh do |c| + c.flag 'ssh', :desc => "Pass through raw options to ssh (e.g. --ssh '-F ~/sshconfig')" + c.flag 'port', :desc => 'Override ssh port for remote host' c.action do |global_options,options,args| - exec_ssh(:ssh, args) + exec_ssh(:ssh, options, args) end end @@ -12,7 +14,7 @@ module LeapCli; module Commands arg_name 'NAME' command :mosh do |c| c.action do |global_options,options,args| - exec_ssh(:mosh, args) + exec_ssh(:mosh, options, args) end end @@ -44,8 +46,9 @@ module LeapCli; module Commands private - def exec_ssh(cmd, args) + def exec_ssh(cmd, cli_options, args) node = get_node_from_args(args, :include_disabled => true) + port = node.ssh.port options = [ "-o 'HostName=#{node.ip_address}'", # "-o 'HostKeyAlias=#{node.name}'", << oddly incompatible with ports in known_hosts file, so we must not use this or non-standard ports break. @@ -65,7 +68,13 @@ module LeapCli; module Commands elsif LeapCli.log_level >= 2 options << "-v" end - ssh = "ssh -l #{username} -p #{node.ssh.port} #{options.join(' ')}" + if cli_options[:port] + port = cli_options[:port] + end + if cli_options[:ssh] + options << cli_options[:ssh] + end + ssh = "ssh -l #{username} -p #{port} #{options.join(' ')}" if cmd == :ssh command = "#{ssh} #{node.domain.full}" elsif cmd == :mosh diff --git a/lib/leap_cli/commands/test.rb b/lib/leap_cli/commands/test.rb index 2584a69..2f146b7 100644 --- a/lib/leap_cli/commands/test.rb +++ b/lib/leap_cli/commands/test.rb @@ -33,9 +33,9 @@ module LeapCli; module Commands def test_cmd(options) if options[:continue] - "#{PUPPET_DESTINATION}/bin/run_tests --continue" + "#{Leap::Platform.leap_dir}/bin/run_tests --continue" else - "#{PUPPET_DESTINATION}/bin/run_tests" + "#{Leap::Platform.leap_dir}/bin/run_tests" end end diff --git a/lib/leap_cli/commands/user.rb b/lib/leap_cli/commands/user.rb index d7c21db..6c33878 100644 --- a/lib/leap_cli/commands/user.rb +++ b/lib/leap_cli/commands/user.rb @@ -1,4 +1,3 @@ -require 'gpgme' # # perhaps we want to verify that the key files are actually the key files we expect. @@ -75,8 +74,10 @@ module LeapCli if `which ssh-add`.strip.any? `ssh-add -L 2> /dev/null`.split("\n").compact.each do |line| key = SshKey.load(line) - key.comment = 'ssh-agent' - ssh_keys << key unless ssh_keys.include?(key) + if key + key.comment = 'ssh-agent' + ssh_keys << key unless ssh_keys.include?(key) + end end end ssh_keys.compact! @@ -98,13 +99,20 @@ module LeapCli # let the the user choose among the gpg public keys that we encounter, or just pick the key if there is only one. # def pick_pgp_key + begin + return unless `which gpg`.strip.any? + require 'gpgme' + rescue LoadError + return + end + secret_keys = GPGME::Key.find(:secret) if secret_keys.empty? log "Skipping OpenPGP setup because I could not find any OpenPGP keys for you" return nil end - assert_bin! 'gpg' + secret_keys.select!{|key| !key.expired} if secret_keys.length > 1 key_index = numbered_choice_menu('Choose your OpenPGP public key', secret_keys) do |key, i| diff --git a/lib/leap_cli/commands/vagrant.rb b/lib/leap_cli/commands/vagrant.rb index 5219161..41fda03 100644 --- a/lib/leap_cli/commands/vagrant.rb +++ b/lib/leap_cli/commands/vagrant.rb @@ -1,4 +1,4 @@ -require 'ipaddr' +autoload :IPAddr, 'ipaddr' require 'fileutils' module LeapCli; module Commands |