diff options
| author | elijah <elijah@riseup.net> | 2012-10-21 16:17:05 -0700 | 
|---|---|---|
| committer | elijah <elijah@riseup.net> | 2012-10-21 16:17:05 -0700 | 
| commit | 94dd3b4d99a67679adcc3a9921f9e1fa78783cba (patch) | |
| tree | 537e0a290801c2783ab24eb5c932c44c0c7f7c86 | |
| parent | 338b3d97b8cb0f5fca0339514ffdfaf30cc65498 (diff) | |
added command init-node
| -rw-r--r-- | lib/leap_cli/commands/bootstrap.rb | 131 | ||||
| -rw-r--r-- | lib/leap_cli/commands/clean.rb | 16 | ||||
| -rw-r--r-- | lib/leap_cli/commands/compile.rb | 11 | ||||
| -rw-r--r-- | lib/leap_cli/commands/user.rb | 7 | ||||
| -rw-r--r-- | lib/leap_cli/commands/util.rb | 103 | ||||
| -rw-r--r-- | lib/leap_cli/config/manager.rb | 7 | ||||
| -rw-r--r-- | lib/leap_cli/log.rb | 3 | ||||
| -rw-r--r-- | lib/leap_cli/util.rb | 90 | 
8 files changed, 253 insertions, 115 deletions
| diff --git a/lib/leap_cli/commands/bootstrap.rb b/lib/leap_cli/commands/bootstrap.rb new file mode 100644 index 0000000..11188fb --- /dev/null +++ b/lib/leap_cli/commands/bootstrap.rb @@ -0,0 +1,131 @@ +require 'net/ssh/known_hosts' +require 'tempfile' + +module LeapCli; module Commands + +  #desc '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 +  command :'init-node' do |c| +    c.action do |global_options,options,args| +      node_name = args.first +      node = manager.node(node_name) +      assert!(node, "Node '#{node_name}' not found.") +      progress("Pinging #{node.name}") +      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.") +      install_public_host_key(node) +    end +  end + +  desc 'not yet implemented' +  command :'rename-node' do |c| +    c.action do |global_options,options,args| +    end +  end + +  desc 'not yet implemented' +  command :'rm-node' do |c| +    c.action do |global_options,options,args| +    end +  end + +  # +  # saves the public ssh host key for node into the provider directory. +  # +  # see `man sshd` for the format of known_hosts +  # +  def install_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) +    if key_in_known_hosts?(public_key, [node.name, node.ip_address, node.domain.name]) +      progress("Public ssh host key for #{node.name} is already trusted (key found in 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) +      if !agree("Is this correct? ") +        bail! +      else +        puts +        # we write the file without ipaddress or hostname, because these might change later, but we want to keep the same key. +        write_file!([:node_ssh_pub_key, node.name], [key_type, public_key].join(' ')) +        update_known_hosts +      end +    end + +  end + +  def get_public_key_for_ip(address) +    assert_bin!('ssh-keyscan') +    output = assert_run! "ssh-keyscan -t rsa #{address}", "Could not get the public host key. Maybe sshd is not running?" +    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 +  # +  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 +  end + +  # +  # generates the known_hosts file. +  # +  # we do a 'late' binding on the hostnames and ip part of the ssh pub key record in order to allow +  # for the possibility that the hostnames or ip has changed in the node configuration. +  # +  def update_known_hosts +    buffer = StringIO.new +    manager.nodes.values.each do |node| +      hostnames = [node.name, node.domain.internal, node.domain.full, node.ip_address].join(',') +      pub_key = read_file([:node_ssh_pub_key,node.name]) +      if pub_key +        buffer << [hostnames, pub_key].join(' ') +      end +    end +    write_file!(:known_hosts, buffer.string) +  end + +end; end
\ No newline at end of file diff --git a/lib/leap_cli/commands/clean.rb b/lib/leap_cli/commands/clean.rb new file mode 100644 index 0000000..ed9c901 --- /dev/null +++ b/lib/leap_cli/commands/clean.rb @@ -0,0 +1,16 @@ +module LeapCli +  module Commands + +    desc 'Removes all files generated with the "compile" command' +    command :clean do |c| +      c.action do |global_options,options,args| +        Dir.glob(named_path(:hiera, '*')).each do |file| +          remove_file! file +        end +        remove_file! named_path(:authorized_keys) +        remove_file! named_path(:known_hosts) +      end +    end + +  end +end
\ No newline at end of file diff --git a/lib/leap_cli/commands/compile.rb b/lib/leap_cli/commands/compile.rb index 3e9d42d..429d1c5 100644 --- a/lib/leap_cli/commands/compile.rb +++ b/lib/leap_cli/commands/compile.rb @@ -1,3 +1,4 @@ +  module LeapCli    module Commands @@ -7,7 +8,17 @@ module LeapCli          manager.load(Path.provider)          ensure_dir(Path.hiera)          manager.export(Path.hiera) +        update_authorized_keys +        update_known_hosts +      end +    end + +    def update_authorized_keys +      buffer = StringIO.new +      Dir.glob(named_path(:user_ssh, '*')).each do |keyfile| +        buffer << File.read(keyfile)        end +      write_file!(:authorized_keys, buffer.string)      end    end diff --git a/lib/leap_cli/commands/user.rb b/lib/leap_cli/commands/user.rb index af59074..00c4b62 100644 --- a/lib/leap_cli/commands/user.rb +++ b/lib/leap_cli/commands/user.rb @@ -13,7 +13,7 @@ require 'gpgme'  module LeapCli    module Commands -    desc 'adds a new trusted sysadmin' +    desc 'Adds a new trusted sysadmin'      arg_name '<username>', :optional => false, :multiple => false      command :'add-user' do |c| @@ -47,11 +47,12 @@ module LeapCli          assert!(pgp_pub_key, 'Sorry, could not find OpenPGP public key.')          if ssh_pub_key -          write_file!(:user_ssh, username, ssh_pub_key) +          write_file!([:user_ssh, username], ssh_pub_key)          end          if pgp_pub_key -          write_file!(:user_pgp, username, pgp_pub_key) +          write_file!([:user_pgp, username], pgp_pub_key)          end +        end      end diff --git a/lib/leap_cli/commands/util.rb b/lib/leap_cli/commands/util.rb index ad4f01c..b5a102f 100644 --- a/lib/leap_cli/commands/util.rb +++ b/lib/leap_cli/commands/util.rb @@ -2,11 +2,12 @@ module LeapCli    module Commands      extend self      extend LeapCli::Util -#     # -#     # keeps prompting the user for a numbered choice, until they pick a good one or bail out. -#     # -#     # block is yielded and is responsible for rendering the choices. -#     # + +    # +    # keeps prompting the user for a numbered choice, until they pick a good one or bail out. +    # +    # block is yielded and is responsible for rendering the choices. +    #      def numbered_choice_menu(msg, items, &block)        while true          say("\n" + msg + ':') @@ -28,98 +29,6 @@ module LeapCli        end      end -#     # -#     # read a file, exit if the file doesn't exist. -#     # -#     def read_file!(file_path) -#       if !File.exists?(file_path) -#         bail!("File '%s' does not exist." % file_path) -#       else -#         File.readfile(file_path) -#       end -#     end - -#     ## -#     ## LOGGING -#     ## - -#     def log0(message=nil, &block) -#       if message -#         puts message -#       elsif block -#         puts yield(block) -#       end -#     end - -#     def log1(message=nil, &block) -#       if LeapCli.log_level > 0 -#         if message -#           puts message -#         elsif block -#           puts yield(block) -#         end -#       end -#     end - -#     def log2(message=nil, &block) -#       if LeapCli.log_level > 1 -#         if message -#           puts message -#         elsif block -#           puts yield(block) -#         end -#       end -#     end - -#     def progress(message) -#       log1(" * " + message) -#     end - -#     ## -#     ## QUITTING -#     ## - -#     # -#     # quit and print help -#     # -#     def help!(message=nil) -#       ENV['GLI_DEBUG'] = "false" -#       help_now!(message) -#       #say("ERROR: " + message) -#     end - -#     # -#     # quit with a message that we are bailing out. -#     # -#     def bail!(message="") -#       say(message) -#       say("Bailing out.") -#       raise SystemExit.new -#       #ENV['GLI_DEBUG'] = "false" -#       #exit_now!(message) -#     end - -#     # -#     # quit with no message -#     # -#     def quit!(message='') -#       say(message) -#       raise SystemExit.new -#     end - -#     # -#     # bails out with message if assertion is false. -#     # -#     def assert!(boolean, message) -#       bail!(message) unless boolean -#     end - -#     # -#     # assert that the command is available -#     # -#     def assert_bin!(cmd_name) -#       assert! `which #{cmd_name}`.strip.any?, "Sorry, bailing out, the command '%s' is not installed." % cmd_name -#     end    end  end diff --git a/lib/leap_cli/config/manager.rb b/lib/leap_cli/config/manager.rb index b35251a..432ba0b 100644 --- a/lib/leap_cli/config/manager.rb +++ b/lib/leap_cli/config/manager.rb @@ -85,6 +85,13 @@ module LeapCli          return node_list        end +      # +      # returns a single Config::Object that corresponds to a Node. +      # +      def node(name) +        nodes[name] +      end +        private        def load_all_json(pattern, config_type = :class) diff --git a/lib/leap_cli/log.rb b/lib/leap_cli/log.rb index ac35eae..58f1a1c 100644 --- a/lib/leap_cli/log.rb +++ b/lib/leap_cli/log.rb @@ -46,3 +46,6 @@ def progress(message)    log1(" * " + message)  end +def progress2(message) +  log2(" * " + message) +end diff --git a/lib/leap_cli/util.rb b/lib/leap_cli/util.rb index 67fca8d..6095b2b 100644 --- a/lib/leap_cli/util.rb +++ b/lib/leap_cli/util.rb @@ -1,6 +1,14 @@  require 'md5'  module LeapCli + +  class FileMissing < Exception +    attr_reader :file_path +    def initialize(file_path) +      @file_path = file_path +    end +  end +    module Util      extend self @@ -50,6 +58,18 @@ module LeapCli        assert! `which #{cmd_name}`.strip.any?, "Sorry, bailing out, the command '%s' is not installed." % cmd_name      end +    # +    # assert that the command is run without an error. +    # if successful, return output. +    # +    def assert_run!(cmd, message) +      log2(" * run: #{cmd}") +      cmd = cmd + " 2>&1" +      output = `#{cmd}` +      assert!($?.success?, message) +      return output +    end +      ##      ## FILES AND DIRECTORIES      ## @@ -67,7 +87,7 @@ module LeapCli      end      def progress_nochange(path) -      progress 'no change %s' % relative_path(path) +      progress2 'no change %s' % relative_path(path)      end      def progress_removed(path) @@ -93,23 +113,43 @@ module LeapCli      NAMED_PATHS = {        :user_ssh => 'users/#{arg}/#{arg}_ssh.pub', -      :user_pgp => 'users/#{arg}/#{arg}_pgp.pub' +      :user_pgp => 'users/#{arg}/#{arg}_pgp.pub', +      :hiera => 'hiera/#{arg}.yaml', +      :node_ssh_pub_key => 'files/nodes/#{arg}/#{arg}_ssh_key.pub', +      :known_hosts => 'files/ssh/known_hosts', +      :authorized_keys => 'files/ssh/authorized_keys'      } -    # -    # read a file, exit if the file doesn't exist. -    # -    def read_file!(file_path) -      if !File.exists?(file_path) -        bail!("File '%s' does not exist." % file_path) -      else -        File.readfile(file_path) +    def read_file!(*args) +      begin +        try_to_read_file!(*args) +      rescue FileMissing => exc +        bail!("File '%s' does not exist." % exc.file_path) +      end +    end + +    def read_file(*args) +      begin +        try_to_read_file!(*args) +      rescue FileMissing => exc +        return nil        end      end +    # +    # Three ways to call: +    # +    # - write_file!(file_path, file_contents) +    # - write_file!(named_path, file_contents) +    # - write_file!(named_path, file_contents, argument)  -- deprecated +    # - write_file!([named_path, argument], file_contents) +    # +    #      def write_file!(*args)        if args.first.is_a? Symbol          write_named_file!(*args) +      elsif args.first.is_a? Array +        write_named_file!(args.first[0], args.last, args.first[1])        else          write_to_path!(*args)        end @@ -123,15 +163,17 @@ module LeapCli      end      # -    # saves a named file +    # saves a named file.      # -    def write_named_file!(name, arg, contents) -      assert!(NAMED_PATHS[name], "Error, I don't know the path for #{arg}") +    def write_named_file!(name, contents, arg=nil) +      fullpath = named_path(name, arg) +      write_to_path!(fullpath, contents) +    end +    def named_path(name, arg=nil) +      assert!(NAMED_PATHS[name], "Error, I don't know the path for :#{name} (with argument '#{arg}')")        filename = eval('"' + NAMED_PATHS[name] + '"')        fullpath = Path.provider + '/' + filename - -      write_to_path!(fullpath, contents)      end      def write_to_path!(filepath, contents) @@ -166,6 +208,24 @@ module LeapCli        end      end +    # +    # trys to read a file, raise exception if the file doesn't exist. +    # +    def try_to_read_file!(*args) +      if args.first.is_a? Symbol +        file_path = named_path(args.first) +      elsif args.first.is_a? Array +        file_path = named_path(*args.first) +      else +        file_path = args.first +      end +      if !File.exists?(file_path) +        raise FileMissing.new(file_path) +      else +        File.read(file_path) +      end +    end +    end  end | 
