diff options
| author | elijah <elijah@riseup.net> | 2016-07-21 00:55:12 -0700 | 
|---|---|---|
| committer | elijah <elijah@riseup.net> | 2016-08-23 13:37:34 -0700 | 
| commit | 205b61dfe721e6d88fc06b050a0497eeb35f4e02 (patch) | |
| tree | 518b5799f56d9e224d7ca2d85b3d29ef0c01b3c6 /lib/leap_cli/ssh | |
| parent | 6fab56fb40256fb2e541ee3ad61490f03254d38e (diff) | |
added 'leap vm' command
Diffstat (limited to 'lib/leap_cli/ssh')
| -rw-r--r-- | lib/leap_cli/ssh/backend.rb | 6 | ||||
| -rw-r--r-- | lib/leap_cli/ssh/key.rb | 123 | ||||
| -rw-r--r-- | lib/leap_cli/ssh/options.rb | 3 | ||||
| -rw-r--r-- | lib/leap_cli/ssh/remote_command.rb | 6 | ||||
| -rw-r--r-- | lib/leap_cli/ssh/scripts.rb | 15 | 
5 files changed, 147 insertions, 6 deletions
| diff --git a/lib/leap_cli/ssh/backend.rb b/lib/leap_cli/ssh/backend.rb index 80203b61..42e58c15 100644 --- a/lib/leap_cli/ssh/backend.rb +++ b/lib/leap_cli/ssh/backend.rb @@ -46,6 +46,12 @@ module LeapCli          Thread.current["sshkit_backend"] = nil        end +      # if set, all the commands will begin with: +      # sudo -u #{@user} -- sh -c '<command>' +      def set_user(user='root') +        @user = user +      end +        #        # like default capture, but gracefully logs failures for us        # last argument can be an options hash. diff --git a/lib/leap_cli/ssh/key.rb b/lib/leap_cli/ssh/key.rb index ad1ecf15..76223b7e 100644 --- a/lib/leap_cli/ssh/key.rb +++ b/lib/leap_cli/ssh/key.rb @@ -2,12 +2,34 @@  # A wrapper around OpenSSL::PKey::RSA instances to provide a better api for  # dealing with SSH keys.  # -# NOTE: cipher 'ssh-ed25519' not supported yet because we are waiting +# NOTES: +# +# cipher 'ssh-ed25519' not supported yet because we are waiting  # for support in Net::SSH  # +# there are many ways to represent an SSH key, since SSH keys can be of +# a variety of types. +# +# To confuse matters more, there are multiple binary representations. +# So, for example, an RSA key has a native SSH representation +# (two bignums, e followed by n), and a DER representation. +# +# AWS uses fingerprints of the DER representation, but SSH typically reports +# fingerprints of the SSH representation. +# +# Also, SSH public key files are base64 encoded, but with whitespace removed +# so it all goes on one line. +# +# Some useful links: +# +# https://stackoverflow.com/questions/3162155/convert-rsa-public-key-to-rsa-der +# https://net-ssh.github.io/ssh/v2/api/classes/Net/SSH/Buffer.html +# https://serverfault.com/questions/603982/why-does-my-openssh-key-fingerprint-not-match-the-aws-ec2-console-keypair-finger +#  require 'net/ssh'  require 'forwardable' +require 'base64'  module LeapCli    module SSH @@ -72,6 +94,14 @@ module LeapCli          public_key || private_key        end +      def self.my_public_keys +        load_keys_from_paths File.join(ENV['HOME'], '.ssh', '*.pub') +      end + +      def self.provider_public_keys +        load_keys_from_paths Path.named_path([:user_ssh, '*']) +      end +        #        # Picks one key out of an array of keys that we think is the "best",        # based on the order of preference in SUPPORTED_TYPES @@ -127,19 +157,29 @@ module LeapCli          end        end +      private + +      def self.load_keys_from_paths(key_glob) +        keys = [] +        Dir.glob(key_glob).each do |file| +          key = Key.load(file) +          if key && key.public? +            keys << key +          end +        end +        return keys +      end +        ##        ## INSTANCE METHODS        ##        public -      def initialize(rsa_key) -        @key = rsa_key +      def initialize(p_key) +        @key = p_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 @@ -156,6 +196,70 @@ module LeapCli          Key.new(@key.private_key)        end +      def private? +        @key.respond_to?(:private?) ? @key.private? : @key.private_key? +      end + +      def public? +        @key.respond_to?(:public?) ? @key.public? : @key.public_key? +      end + +      # +      # three arguments: +      # +      # - digest: one of md5, sha1, sha256, etc. (default sha256) +      # - encoding: either :hex (default) or :base64 +      # - type: fingerprint type, either :ssh (default) or :der +      # +      # NOTE: +      # +      # * I am not sure how to make a fingerprint for OpenSSL::PKey::EC::Point +      # +      # * AWS reports fingerprints using MD5 digest for uploaded ssh keys, +      #   but SHA1 for keys it created itself. +      # +      # * Also, AWS fingerprints are digests on the DER encoding of the key. +      #   But standard SSH fingerprints are digests of SSH encoding of the key. +      # +      # * Other tools will sometimes display fingerprints in hex and sometimes +      #   in base64. Arrrgh. +      # +      def fingerprint(type: :ssh, digest: :sha256, encoding: :hex) +        require 'digest' + +        digest = digest.to_s.upcase +        digester = case digest +          when "MD5" then Digest::MD5.new +          when "SHA1" then Digest::SHA1.new +          when "SHA256" then Digest::SHA256.new +          when "SHA384" then Digest::SHA384.new +          when "SHA512" then Digest::SHA512.new +          else raise ArgumentError, "digest #{digest} is unknown" +        end + +        keymatter = nil +        if type == :der && @key.respond_to?(:to_der) +          keymatter = @key.to_der +        else +          keymatter = self.raw_key.to_s +        end + +        fp = nil +        if encoding == :hex +          fp = digester.hexdigest(keymatter) +        elsif encoding == :base64 +          fp = Base64.encode64(digester.digest(keymatter)).sub(/=$/, '') +        else +          raise ArgumentError, "encoding #{encoding} not understood" +        end + +        if digest == "MD5" && encoding == :hex +          return fp.scan(/../).join(':') +        else +          return fp +        end +      end +        #        # not sure if this will always work, but is seems to for now.        # @@ -175,10 +279,17 @@ module LeapCli          self.type + " " + self.key        end +      # +      # base64 encoding of the key, with spaces removed. +      #        def key          [Net::SSH::Buffer.from(:key, @key).to_s].pack("m*").gsub(/\s/, "")        end +      def raw_key +        Net::SSH::Buffer.from(:key, @key) +      end +        def ==(other_key)          return false if other_key.nil?          return false if self.class != other_key.class diff --git a/lib/leap_cli/ssh/options.rb b/lib/leap_cli/ssh/options.rb index b8266d11..7bc06564 100644 --- a/lib/leap_cli/ssh/options.rb +++ b/lib/leap_cli/ssh/options.rb @@ -46,6 +46,9 @@ module LeapCli          if args[:auth_methods]            ssh_options[:auth_methods] = args[:auth_methods]          end +        if args[:user] +          ssh_options[:user] = args[:user] +        end          return ssh_options        end diff --git a/lib/leap_cli/ssh/remote_command.rb b/lib/leap_cli/ssh/remote_command.rb index 7195405e..0e9f2d55 100644 --- a/lib/leap_cli/ssh/remote_command.rb +++ b/lib/leap_cli/ssh/remote_command.rb @@ -38,6 +38,7 @@ module LeapCli      #  :port -- ssh port      #  :ip   -- ssh ip      #  :auth_methods -- e.g. ["pubkey", "password"] +    #  :user -- default 'root'      #      def self.remote_command(nodes, options={}, &block)        CustomCoordinator.new( @@ -47,6 +48,11 @@ module LeapCli          )        ).each do |ssh, host|          LeapCli.log 2, "ssh options for #{host.hostname}: #{host.ssh_options.inspect}" +        if host.user != 'root' +          # if the ssh user is not root, we want to make the ssh commands +          # switch to root before they are run: +          ssh.set_user('root') +        end          yield ssh, host        end      end diff --git a/lib/leap_cli/ssh/scripts.rb b/lib/leap_cli/ssh/scripts.rb index a15a9edd..9fef6240 100644 --- a/lib/leap_cli/ssh/scripts.rb +++ b/lib/leap_cli/ssh/scripts.rb @@ -119,6 +119,21 @@ module LeapCli          end        end +      # +      # AWS debian images only allow you to login as admin. This is done with a +      # custom command in /root/.ssh/authorized_keys, instead of by modifying +      # /etc/ssh/sshd_config. +      # +      # We need to be able to ssh as root for scp and rsync to work. +      # +      # This command is run as 'admin', with a sudo wrapper. In order for the +      # sudo to work, the command must be specified as separate arguments with +      # no spaces (that is how ssh-kit works). +      # +      def allow_root_ssh +        ssh.execute 'cp', '/home/admin/.ssh/authorized_keys', '/root/.ssh/authorized_keys' +      end +        private        def flagize(hsh) | 
