diff --git a/README.rdoc b/README.rdoc index 4de3f85..d01d6aa 100644 --- a/README.rdoc +++ b/README.rdoc @@ -127,6 +127,52 @@ One of: p rset.env.first.select { |env| env =~ /RYE/ } # => ["RYE=Forty Creek"] +== EXAMPLE 5 -- ERROR HANDLING + + rbox = Rye::Box.new('localhost', :safe => false) + + # Rye follows the standard convention of taking exception to non-zero + # exit code. It raises a Rye::CommandError. In this case, rye.test is + # not found by the ls command. + begin + rbox.ls('rye.test') + rescue Rye::CommandError => ex + puts ex.exit_code # => 1 + puts ex.stderr # => ls: rye.test: No such file or directory + end + + # The Rye:Rap response objects also give you the STDOUT and STDERR + # content separately. Here we redirect STDOUT to STDERR, so this + # will return nothing: + puts rbox.uname('-a 1>&2').stdout # => + + # It all went to STDERR: + puts rbox.uname('-a 1>&2').stderr # => Darwin ryehost 9.6.0 ... + + # There were no actual error so the exit code should be 0. + puts rbox.uname('-a 1>&2').exit_code # => 0 + +== EXAMPLE 6 -- rye + +Rye comes with a command-line utility called rye. + + # Prints the paths to your private keys + $ rye keys + + # Prints the server host keys (suitable for ~/.ssh/known_hosts) + $ rye hostkey HOST1 HOST2 + + # Add your public keys to authorized_keys and authorized_keys2 + # on a remote machine + $ rye authorize HOST1 HOST2 + +More info: + + $ rye -h + $ rye COMMAND -h + $ rye show-commands + + == About Safe-Mode In safe-mode: @@ -154,6 +200,12 @@ Rye permits only a limited number of system commands to be run. This default whi * highline * drydock +== Known Issues + +This list will grow. If you find one let me know! + +* Rye doesn't read the ~/.ssh/config file yet + == Thanks * Solutious Incorporated (http://solutious.com) for all the orange juice. @@ -169,11 +221,6 @@ Rye permits only a limited number of system commands to be run. This default whi * http://delano.github.com/rye * http://www.youtube.com/watch?v=_StUVh6ENuw -== Related Reading - -[OpenSSH key creation HOWTO] https://wiki.systemsx.ch/display/ITDOC/OpenSSH+key+creation+HOWTO -[OpenSSH key management, Part 1] http://www.ibm.com/developerworks/library/l-keyc.html -[OpenSSL command-line HOWTO] http://madboa.com/geek/openssl/ == Credits * Delano Mandelbaum (delano@solutious.com) diff --git a/bin/rye b/bin/rye index e16609e..bbdb92e 100755 --- a/bin/rye +++ b/bin/rye @@ -39,9 +39,9 @@ argv :hostname command :authorize do |obj| raise "You must specify a host" unless obj.argv.hostname - highline = HighLine.new + highline = HighLine.new # Used for password prompt - opts = { :safe => false, :debug => STDOUT } + opts = { :debug => nil } opts[:user] = obj.option.user if obj.option.user obj.argv.each do |hostname| @@ -62,14 +62,14 @@ command :authorize do |obj| # exist on the remote machine or we need to supply a password. rescue Net::SSH::AuthenticationFailed => ex retried += 1 - opts[:password] = highline.ask("Password: ") { |q| q.echo = '*' } + opts[:password] = highline.ask("Password: ") { |q| q.echo = '' } retry unless retried > 3 rescue Net::SSH::AuthenticationFailed STDERR.puts "Authentication failed." exit 1 end - p rbox.authorize_keys + puts "Added public keys for: ", rbox.authorize_keys puts end diff --git a/bin/try b/bin/try index 0630c60..4f104ed 100755 --- a/bin/try +++ b/bin/try @@ -114,7 +114,7 @@ rset.add_boxes(rbox, 'localhost') # Add boxes as hostnames or objects # Calling methods on Rye::Set objects is very similar to calling them on # Rye::Box objects. In fact, it's identical: p rset.uptime # => [[14:19:02 up 32 days, 19:35 ...], [14:19:02 up 30 days, 01:35]] -p rset['/etc'].ls # => [['file1', 'file2', ...], ['life1', 'life2', ...]] +p rset['/usr'].ls # => [['bin', 'etc', ...], ['bin', 'etc', ...]] # Like Rye::Box, the response value is a Rye::Rap object containing the # responses from each box. Each response is itself a Rye::Rap object. @@ -136,16 +136,36 @@ puts unames.first.box == rbox # => true rset.add_env(:RYE, "Forty Creek") p rset.env.first.select { |env| env =~ /RYE/ } # => ["RYE=Forty Creek"] -__END__ puts %Q( # ------------------------------------------------------------------ # EXAMPLE 5 -- ERROR HANDLING #) -puts rbox.ls('rye.test') # => "" -puts rbox.ls('rye.test').stderr # => ls: rye.test: No such file or directory +rbox = Rye::Box.new('localhost', :safe => false) + +# Rye follows the standard convention of taking exception to non-zero +# exit code. It raises a Rye::CommandError. In this case, rye.test is +# not found by the ls command. +begin + rbox.ls('rye.test') +rescue Rye::CommandError => ex + puts ex.exit_code # => 1 + puts ex.stderr # => ls: rye.test: No such file or directory +end + +# The Rye:Rap response objects also give you the STDOUT and STDERR +# content separately. Here we redirect STDOUT to STDERR, so this +# will return nothing: +puts rbox.uname('-a 1>&2').stdout # => + +# It all went to STDERR: +puts rbox.uname('-a 1>&2').stderr # => Darwin ryehost 9.6.0 ... + +# There were no actual error so the exit code should be 0. +puts rbox.uname('-a 1>&2').exit_code # => 0 + + + -puts rbox.touch('rye.test') # => "" -puts rbox.rm('rye.test') # => "" diff --git a/lib/rye.rb b/lib/rye.rb index e94e863..9891d8b 100644 --- a/lib/rye.rb +++ b/lib/rye.rb @@ -47,7 +47,6 @@ class NoHost < RuntimeError; end class NotConnected < RuntimeError; end class CommandNotFound < RuntimeError; end class CommandError < RuntimeError - attr_reader :rap # * +rap+ a Rye::Rap object def initialize(rap) @rap = rap @@ -55,6 +54,9 @@ def initialize(rap) def message "(code: %s) %s" % [@rap.exit_code, @rap.stderr.join($/)] end + def stderr; @rap.stderr if @rap; end + def stdout; @rap.stdout if @rap; end + def exit_code; @rap.exit_code if @rap; end end # Reload Rye dynamically. Useful with irb. @@ -84,7 +86,8 @@ def find_private_keys(path) next if File.directory?(file) pk = nil begin - pk = load_private_key(file) + tmp = Rye::Key.from_file(file) + pk = tmp if tmp.private? rescue OpenSSL::PKey::PKeyError end !pk.nil? diff --git a/lib/rye/box.rb b/lib/rye/box.rb index 185e259..bbfe11c 100644 --- a/lib/rye/box.rb +++ b/lib/rye/box.rb @@ -191,9 +191,13 @@ def host_key # Copy the local public keys (as specified by Rye.keys) to # this box into ~/.ssh/authorized_keys and ~/.ssh/authorized_keys2. - # Returns an Array of the private keys files used to generate the public keys. + # Returns an Array of the private keys files used to generate the public keys. + # + # NOTE: authorize_keys disables safe-mode for this box while it runs. + # def authorize_keys added_keys = [] + opts[:safe] = false Rye.keys.each do |key| path = key[2] debug "# Public key for #{path}" @@ -204,6 +208,7 @@ def authorize_keys self.chmod('-R', '0600', '.ssh') added_keys << path end + opts[:safe] = true added_keys end diff --git a/lib/rye/key.rb b/lib/rye/key.rb index 0973ed0..010618e 100644 --- a/lib/rye/key.rb +++ b/lib/rye/key.rb @@ -31,45 +31,6 @@ def self.generate_pkey(authtype="RSA", bits=1024) klass = authtype.upcase == "RSA" ? OpenSSL::PKey::RSA : OpenSSL::PKey::DSA pk = klass.new(bits) end - def self.calc_mode pbit - # permission bit - mode = Array.new(10, '-') - mt = pbit & 0170000 - # S_IFMT - case mt - # S_IFDIR - when 00040000 - mode[0] = 'd' - # S_IFBLK - when 0060000 - mode[0] = 'b' - # S_IFCHR - when 0020000 - mode[0] = 'c' - # S_IFLNK - when 0120000 - mode[0] = 'l' - # S_IFFIFO - when 0010000 - mode[0] = 'p' - # S_IFSOCK - when 0140000 - mode[0] = 's' - end - u = pbit & 00700 - g = pbit & 00070 - o = pbit & 00007 - mode[1] = 'r' if u & 00400 != 0 - mode[2] = 'w' if u & 00200 != 0 - mode[3] = 'x' if u & 00100 != 0 - mode[4] = 'r' if g & 00040 != 0 - mode[5] = 'w' if g & 00020 != 0 - mode[6] = 'x' if g & 00010 != 0 - mode[7] = 'r' if o & 00004 != 0 - mode[8] = 'w' if o & 00002 != 0 - mode[9] = 'x' if o & 00001 != 0 - mode.join('') - end def self.from_file(path) raise BadFile, path unless File.exists?(path || '') diff --git a/try/keys.rb b/try/keys.rb index 1d2f4f1..f1f87e0 100755 --- a/try/keys.rb +++ b/try/keys.rb @@ -94,3 +94,46 @@ clear_text = private_key.private_decrypt( cipher_text ) puts "\ndecoded text:\n#{clear_text}\n\n" + +__END__ + +# outputs: -rw-r--r-- +def self.calc_mode pbit + # permission bit + mode = Array.new(10, '-') + mt = pbit & 0170000 + # S_IFMT + case mt + # S_IFDIR + when 00040000 + mode[0] = 'd' + # S_IFBLK + when 0060000 + mode[0] = 'b' + # S_IFCHR + when 0020000 + mode[0] = 'c' + # S_IFLNK + when 0120000 + mode[0] = 'l' + # S_IFFIFO + when 0010000 + mode[0] = 'p' + # S_IFSOCK + when 0140000 + mode[0] = 's' + end + u = pbit & 00700 + g = pbit & 00070 + o = pbit & 00007 + mode[1] = 'r' if u & 00400 != 0 + mode[2] = 'w' if u & 00200 != 0 + mode[3] = 'x' if u & 00100 != 0 + mode[4] = 'r' if g & 00040 != 0 + mode[5] = 'w' if g & 00020 != 0 + mode[6] = 'x' if g & 00010 != 0 + mode[7] = 'r' if o & 00004 != 0 + mode[8] = 'w' if o & 00002 != 0 + mode[9] = 'x' if o & 00001 != 0 + mode.join('') +end