aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--lib/leap_cli.rb1
-rw-r--r--lib/leap_cli/commands/node.rb79
-rw-r--r--lib/leap_cli/commands/user.rb28
-rw-r--r--lib/leap_cli/ssh_key.rb128
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