summaryrefslogtreecommitdiff
path: root/lib/leap_cli/ssh
diff options
context:
space:
mode:
authorelijah <elijah@riseup.net>2016-07-21 00:55:12 -0700
committerelijah <elijah@riseup.net>2016-08-23 13:37:34 -0700
commit205b61dfe721e6d88fc06b050a0497eeb35f4e02 (patch)
tree518b5799f56d9e224d7ca2d85b3d29ef0c01b3c6 /lib/leap_cli/ssh
parent6fab56fb40256fb2e541ee3ad61490f03254d38e (diff)
added 'leap vm' command
Diffstat (limited to 'lib/leap_cli/ssh')
-rw-r--r--lib/leap_cli/ssh/backend.rb6
-rw-r--r--lib/leap_cli/ssh/key.rb123
-rw-r--r--lib/leap_cli/ssh/options.rb3
-rw-r--r--lib/leap_cli/ssh/remote_command.rb6
-rw-r--r--lib/leap_cli/ssh/scripts.rb15
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)