diff options
| -rw-r--r-- | lib/leap_cli.rb | 1 | ||||
| -rw-r--r-- | lib/leap_cli/commands/node.rb | 79 | ||||
| -rw-r--r-- | lib/leap_cli/commands/user.rb | 28 | ||||
| -rw-r--r-- | lib/leap_cli/ssh_key.rb | 128 | 
4 files changed, 161 insertions, 75 deletions
| diff --git a/lib/leap_cli.rb b/lib/leap_cli.rb index a88a8ad..4dccec2 100644 --- a/lib/leap_cli.rb +++ b/lib/leap_cli.rb @@ -9,6 +9,7 @@ require 'leap_cli/init'  require 'leap_cli/path'  require 'leap_cli/util'  require 'leap_cli/log' +require 'leap_cli/ssh_key'  require 'leap_cli/config/object'  require 'leap_cli/config/object_list'  require 'leap_cli/config/manager' diff --git a/lib/leap_cli/commands/node.rb b/lib/leap_cli/commands/node.rb index 46c8fb6..b9640a8 100644 --- a/lib/leap_cli/commands/node.rb +++ b/lib/leap_cli/commands/node.rb @@ -7,11 +7,11 @@ module LeapCli; module Commands    ## COMMANDS    ## -  #desc 'Create a new configuration for a node' -  #command :'new-node' do |c| -  #  c.action do |global_options,options,args| -  #  end -  #end +  desc 'not yet implemented... Create a new configuration for a node' +  command :'new-node' do |c| +    c.action do |global_options,options,args| +    end +  end    desc 'Bootstraps a node, setting up ssh keys and installing prerequisites'    arg_name '<node-name>', :optional => false, :multiple => false @@ -84,28 +84,27 @@ module LeapCli; module Commands    #    def save_public_host_key(node)      progress("Fetching public SSH host key for #{node.name}") -    public_key, key_type = get_public_key_for_ip(node.ip_address) +    public_key = get_public_key_for_ip(node.ip_address)      pub_key_path = Path.named_path([:node_ssh_pub_key, node.name])      if Path.exists?(pub_key_path) -      if file_content_equals?(pub_key_path, node_pub_key_contents(key_type, public_key)) +      if public_key == SshKey.load_from_file(pub_key_path)          progress("Public SSH host key for #{node.name} has not changed")        else          bail!("WARNING: The public SSH host key we just fetched for #{node.name} doesn't match what we have saved previously. Remove the file #{pub_key_path} if you really want to change it.")        end -    elsif key_in_known_hosts?(public_key, [node.name, node.ip_address, node.domain.name]) +    elsif public_key.in_known_hosts?(node.name, node.ip_address, node.domain.name)        progress("Public SSH host key for #{node.name} is trusted (key found in your ~/.ssh/known_hosts)")      else -      fingerprint, bits = ssh_key_fingerprint(key_type, public_key)        puts        say("This is the SSH host key you got back from node \"#{node.name}\"") -      say("Type        -- #{bits} bit #{key_type.upcase}") -      say("Fingerprint -- " + fingerprint) -      say("Public Key  -- " + public_key) +      say("Type        -- #{public_key.bits} bit #{public_key.type.upcase}") +      say("Fingerprint -- " + public_key.fingerprint) +      say("Public Key  -- " + public_key.key)        if !agree("Is this correct? ")          bail!        else          puts -        write_file!([:node_ssh_pub_key, node.name], node_pub_key_contents(key_type, public_key)) +        write_file!([:node_ssh_pub_key, node.name], public_key.to_s)        end      end    end @@ -116,49 +115,7 @@ module LeapCli; module Commands      line = output.split("\n").grep(/^[^#]/).first      assert! line, "Got zero host keys back!"      ip, key_type, public_key = line.split(' ') -    return [public_key, key_type] -  end - -  # -  # returns true if the particular host_key is found in a "known_hosts" file installed for the current user or on this machine. -  # -  # - host_key: string of ssh public host key -  # - identifiers: an array of identifers (which could be an ip address or hostname) -  # -  def key_in_known_hosts?(host_key, identifiers) -    identifiers.each do |identifier| -      Net::SSH::KnownHosts.search_for(identifier).each do |key| -        # i am not sure what format ssh keys are in, but key.to_pem returns something different than we need. -        # this little bit of magic code will encode correctly. I think the format is base64 encoding of bits, exponent, and modulus. -        key_string = [Net::SSH::Buffer.from(:key, key).to_s].pack("m*").gsub(/\s/, "") -        return true if key_string == host_key -      end -    end -    return false -  end - -  # -  # gets a fingerprint for a key string -  # -  # i think this could better be done this way: -  # blob = Net::SSH::Buffer.from(:key, key).to_s -  # fingerprint = OpenSSL::Digest::MD5.hexdigest(blob).scan(/../).join(":") -  # -  def ssh_key_fingerprint(type, key) -    assert_bin!('ssh-keygen') -    file = Tempfile.new('leap_cli_public_key_') -    begin -      file.write(type) -      file.write(" ") -      file.write(key) -      file.close -      output = assert_run!("ssh-keygen -l -f #{file.path}", "Failed to run ssh-keygen on public key.") -      bits, fingerprint, filename, key_type = output.split(' ') -      return [fingerprint, bits] -    ensure -      file.close -      file.unlink -    end +    return SshKey.load(public_key, key_type)    end    def ping_node(node) @@ -166,14 +123,4 @@ module LeapCli; module Commands      assert_run!("ping -W 1 -c 1 #{node.ip_address}", "Could not ping #{node.name} (address #{node.ip_address}). Try again, we only send a single ping.")    end -  # -  # returns a string that can be used for the contents of the files/nodes/x/x_ssh_key.pub file -  # -  # We write the file without ipaddress or hostname, because these might change later. -  # The ip and host is added at when compiling the combined known_hosts file. -  # -  def node_pub_key_contents(key_type, public_key) -    [key_type, public_key].join(' ') -  end -  end; end
\ No newline at end of file diff --git a/lib/leap_cli/commands/user.rb b/lib/leap_cli/commands/user.rb index a7bf848..5f7702a 100644 --- a/lib/leap_cli/commands/user.rb +++ b/lib/leap_cli/commands/user.rb @@ -40,12 +40,11 @@ module LeapCli          if options[:self]            username ||= `whoami`.strip -          ssh_pub_key ||= pick_ssh_key +          ssh_pub_key ||= pick_ssh_key.to_s            pgp_pub_key ||= pick_pgp_key          end          assert!(ssh_pub_key, 'Sorry, could not find SSH public key.') -        #assert!(pgp_pub_key, 'Sorry, could not find OpenPGP public key.')          if ssh_pub_key            write_file!([:user_ssh, username], ssh_pub_key) @@ -61,19 +60,30 @@ module LeapCli      # let the the user choose among the ssh public keys that we encounter, or just pick the key if there is only one.      #      def pick_ssh_key -      assert_bin! 'ssh-add' -      ssh_fingerprints = `ssh-add -l`.split("\n").compact -      assert! ssh_fingerprints.any?, 'Sorry, could not find any SSH public key for you. Have you run ssh-keygen?' +      ssh_keys = [] +      Dir.glob("#{ENV['HOME']}/.ssh/*.pub").each do |keyfile| +        ssh_keys << SshKey.load(keyfile) +      end + +      if `which ssh-add && ssh-add -L`.strip.any? +        `ssh-add -L`.split("\n").compact.each do |line| +          key = SshKey.load(line) +          key.comment = 'ssh-agent' +          ssh_keys << key unless ssh_keys.include?(key) +        end +      end +      ssh_keys.compact! + +      assert! ssh_keys.any?, 'Sorry, could not find any SSH public key for you. Have you run ssh-keygen?' -      if ssh_fingerprints.length > 1 -        key_index = numbered_choice_menu('Choose your SSH public key', ssh_fingerprints) do |key, i| -          say("#{i+1}.  #{key}") +      if ssh_keys.length > 1 +        key_index = numbered_choice_menu('Choose your SSH public key', ssh_keys.collect(&:summary)) do |line, i| +          say("#{i+1}. #{line}")          end        else          key_index = 0        end -      ssh_keys = `ssh-add -L`.split("\n").compact        return ssh_keys[key_index]      end diff --git a/lib/leap_cli/ssh_key.rb b/lib/leap_cli/ssh_key.rb new file mode 100644 index 0000000..daa8bf0 --- /dev/null +++ b/lib/leap_cli/ssh_key.rb @@ -0,0 +1,128 @@ +# +# A wrapper around OpenSSL::PKey::RSA instances to provide a better api for dealing with SSH keys. +# +# + +require 'net/ssh' +require 'forwardable' + +module LeapCli +  class SshKey +    extend Forwardable + +    attr_accessor :filename +    attr_accessor :comment + +    ## +    ## CLASS METHODS +    ## + +    def self.load(arg1, arg2=nil) +      key = nil +      if arg1.is_a? OpenSSL::PKey::RSA +        key = SshKey.new arg1 +      elsif arg1.is_a? String +        if arg1 =~ /^ssh-/ +          type, data = arg1.split(' ') +          key = SshKey.new load_from_data(data, type) +        elsif File.exists? arg1 +          key = SshKey.new load_from_file(arg1) +          key.filename = arg1 +        else +          key = SshKey.new load_from_data(arg1, arg2) +        end +      end +      return key +    end + +    def self.load_from_file(filename) +      public_key = nil +      private_key = nil +      begin +        public_key = Net::SSH::KeyFactory.load_public_key(filename) +      rescue NotImplementedError, Net::SSH::Exception, OpenSSL::PKey::PKeyError +        begin +          private_key = Net::SSH::KeyFactory.load_private_key(filename) +        rescue NotImplementedError, Net::SSH::Exception, OpenSSL::PKey::PKeyError +        end +      end +      public_key || private_key +    end + +    def self.load_from_data(data, type='ssh-rsa') +      public_key = nil +      private_key = nil +      begin +        public_key = Net::SSH::KeyFactory.load_data_public_key("#{type} #{data}") +      rescue NotImplementedError, Net::SSH::Exception, OpenSSL::PKey::PKeyError +        begin +          private_key = Net::SSH::KeyFactory.load_data_private_key("#{type} #{data}") +        rescue NotImplementedError, Net::SSH::Exception, OpenSSL::PKey::PKeyError +        end +      end +      public_key || private_key +    end + +    ## +    ## INSTANCE METHODS +    ## + +    public + +    def initialize(rsa_key) +      @key = rsa_key +    end + +    def_delegator :@key, :fingerprint, :fingerprint +    def_delegator :@key, :public?, :public? +    def_delegator :@key, :private?, :private? +    def_delegator :@key, :ssh_type, :type +    def_delegator :@key, :public_encrypt, :public_encrypt +    def_delegator :@key, :public_decrypt, :public_decrypt +    def_delegator :@key, :private_encrypt, :private_encrypt +    def_delegator :@key, :private_decrypt, :private_decrypt +    def_delegator :@key, :params, :params + +    def public_key +      SshKey.new(@key.public_key) +    end + +    def private_key +      SshKey.new(@key.private_key) +    end + +    # +    # not sure if this will always work, but is seems to for now. +    # +    def bits +      Net::SSH::Buffer.from(:key, @key).to_s.split("\001\000").last.size * 8 +    end + +    def summary +      "%s %s %s (%s)" % [self.type, self.bits, self.fingerprint, self.filename || self.comment || ''] +    end + +    def to_s +      self.type + " " + self.key +    end + +    def key +      [Net::SSH::Buffer.from(:key, @key).to_s].pack("m*").gsub(/\s/, "") +    end + +    def ==(other_key) +      return false if other_key.nil? +      self.params == other_key.params +    end + +    def in_known_hosts?(*identifiers) +      identifiers.each do |identifier| +        Net::SSH::KnownHosts.search_for(identifier).each do |key| +          return true if self == key +        end +      end +      return false +    end + +  end +end | 
