]> gitweb.fluxo.info Git - leap/leap_cli.git/commitdiff
cleaned up the code a bit by adding SshKey class.
authorelijah <elijah@riseup.net>
Thu, 25 Oct 2012 07:49:10 +0000 (00:49 -0700)
committerelijah <elijah@riseup.net>
Thu, 25 Oct 2012 07:49:10 +0000 (00:49 -0700)
lib/leap_cli.rb
lib/leap_cli/commands/node.rb
lib/leap_cli/commands/user.rb
lib/leap_cli/ssh_key.rb [new file with mode: 0644]

index a88a8adc3f3ad5d5314c5d202cf99d4a6586415b..4dccec256dbdf7d0d7b206d72dd9af77e7317361 100644 (file)
@@ -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'
index 46c8fb6d2b89d982cd7dfad7c9e6f2468b2713af..b9640a8ca2749dce6bd42ff7eeece1d53ce5c5cd 100644 (file)
@@ -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
index a7bf848ad7b32bb43608ec6ab8e213a6954777d5..5f7702a84ddfa5682737e1d57e56d160b2d62edc 100644 (file)
@@ -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 (file)
index 0000000..daa8bf0
--- /dev/null
@@ -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