lib/fog/joyent/compute.rb in fog-1.11.1 vs lib/fog/joyent/compute.rb in fog-1.12.0

- old
+ new

@@ -1,18 +1,22 @@ require 'fog/joyent' require 'fog/joyent/errors' require 'fog/compute' +require 'net/ssh' module Fog module Compute class Joyent < Fog::Service requires :joyent_username recognizes :joyent_password recognizes :joyent_url + recognizes :joyent_keyname recognizes :joyent_keyfile + recognizes :joyent_keyphrase + recognizes :joyent_version model_path 'fog/joyent/models/compute' request_path 'fog/joyent/requests/compute' request :list_datacenters @@ -69,12 +73,15 @@ request :list_machine_tags request :get_machine_tag request :delete_machine_tag request :delete_all_machine_tags + # Networks + collection :networks + model :network + request :list_networks - class Mock def self.data @data ||= Hash.new do |hash, key| hash[key] = {} end @@ -94,44 +101,42 @@ end end # Mock class Real def initialize(options = {}) + @connection_options = options[:connection_options] || {} @persistent = options[:persistent] || false @joyent_url = options[:joyent_url] || 'https://us-sw-1.api.joyentcloud.com' @joyent_version = options[:joyent_version] || '~6.5' - @joyent_username = options[:joyent_username] unless @joyent_username raise ArgumentError, "options[:joyent_username] required" end if options[:joyent_keyname] && options[:joyent_keyfile] if File.exists?(options[:joyent_keyfile]) @joyent_keyname = options[:joyent_keyname] - @joyent_key = File.read(options[:joyent_keyfile]) + @joyent_keyfile = options[:joyent_keyfile] + @joyent_keyphrase = options[:joyent_keyphrase] - if @joyent_key.lines.first.include?('-----BEGIN DSA PRIVATE KEY-----') - @key = OpenSSL::PKey::DSA.new(@joyent_key) - elsif @joyent_key.lines.first.include?('-----BEGIN RSA PRIVATE KEY-----') - @key = OpenSSL::PKey::RSA.new(@joyent_key) - else - raise ArgumentError, "options[joyent_keyfile] provided must be an RSA or DSA private key" - end + @key_manager = Net::SSH::Authentication::KeyManager.new(nil, { + :keys_only => true, + :passphrase => @joyent_keyphrase + }) + @key_manager.add(@joyent_keyfile) @header_method = method(:header_for_signature_auth) else raise ArgumentError, "options[:joyent_keyfile] provided does not exist." end elsif options[:joyent_password] @joyent_password = options[:joyent_password] - @header_method = method(:header_for_basic_auth) else raise ArgumentError, "Must provide either a joyent_password or joyent_keyname and joyent_keyfile pair" end @@ -140,30 +145,30 @@ @persistent, @connection_options ) end - def request(request = {}) - request[:headers] = { + def request(opts = {}) + opts[:headers] = { "X-Api-Version" => @joyent_version, "Content-Type" => "application/json", "Accept" => "application/json" - }.merge(request[:headers] || {}).merge(@header_method.call) + }.merge(opts[:headers] || {}).merge(@header_method.call) - if request[:body] - request[:body] = Fog::JSON.encode(request[:body]) + if opts[:body] + opts[:body] = Fog::JSON.encode(opts[:body]) end - response = @connection.request(request) + response = @connection.request(opts) if response.headers["Content-Type"] == "application/json" response.body = json_decode(response.body) end - raise_if_error!(request, response) - response + rescue Excon::Errors::Error => e + raise_if_error!(e.request, e.response) end private def json_decode(body) @@ -177,32 +182,44 @@ } end def header_for_signature_auth date = Time.now.utc.httpdate - begin - signature = Base64.encode64(@key.sign("sha256", date)).delete("\r\n") - rescue OpenSSL::PKey::PKeyError => e - if e.message == 'wrong public key type' - puts 'ERROR: Your version of ruby/openssl does not suport DSA key signing' - puts 'see: http://bugs.ruby-lang.org/issues/4734' - puts 'workaround: Please use an RSA key instead' - end - raise + + # Force KeyManager to load the key(s) + @key_manager.each_identity {} + + key = @key_manager.known_identities.keys.first + + sig = if key.kind_of? OpenSSL::PKey::RSA + @key_manager.sign(key, date)[15..-1] + else + key = OpenSSL::PKey::DSA.new(File.read(@joyent_keyfile), @joyent_keyphrase) + key.sign('sha1', date) end + key_id = "/#{@joyent_username}/keys/#{@joyent_keyname}" + key_type = key.class.to_s.split('::').last.downcase.to_sym + unless [:rsa, :dsa].include? key_type + raise Joyent::Errors::Unauthorized.new('Invalid key type -- only rsa or dsa key is supported') + end + + signature = Base64.encode64(sig).delete("\r\n") + { "Date" => date, - "Authorization" => "Signature keyId=\"#{key_id}\",algorithm=\"rsa-sha256\" #{signature}" + "Authorization" => "Signature keyId=\"#{key_id}\",algorithm=\"#{key_type}-sha1\" #{signature}" } + rescue Net::SSH::Authentication::KeyManagerError => e + raise Joyent::Errors::Unauthorized.new('SSH Signing Error: :#{e.message}', e) end def decode_time_attrs(obj) if obj.kind_of?(Hash) - obj["created"] = Time.parse(obj["created"]) if obj["created"] - obj["updated"] = Time.parse(obj["updated"]) if obj["updated"] + obj["created"] = Time.parse(obj["created"]) unless obj["created"].nil? or obj["created"] == '' + obj["updated"] = Time.parse(obj["updated"]) unless obj["updated"].nil? or obj["updated"] == '' elsif obj.kind_of?(Array) obj.map do |o| decode_time_attrs(o) end end @@ -211,30 +228,30 @@ end def raise_if_error!(request, response) case response.status when 401 then - raise Errors::Unauthorized.new('Invalid credentials were used', request, response) + raise Joyent::Errors::Unauthorized.new('Invalid credentials were used', request, response) when 403 then - raise Errors::Forbidden.new('No permissions to the specified resource', request, response) + raise Joyent::Errors::Forbidden.new('No permissions to the specified resource', request, response) when 404 then - raise Errors::NotFound.new('Requested resource was not found', request, response) + raise Joyent::Errors::NotFound.new('Requested resource was not found', request, response) when 405 then - raise Errors::MethodNotAllowed.new('Method not supported for the given resource', request, response) + raise Joyent::Errors::MethodNotAllowed.new('Method not supported for the given resource', request, response) when 406 then - raise Errors::NotAcceptable.new('Try sending a different Accept header', request, response) + raise Joyent::Errors::NotAcceptable.new('Try sending a different Accept header', request, response) when 409 then - raise Errors::Conflict.new('Most likely invalid or missing parameters', request, response) + raise Joyent::Errors::Conflict.new('Most likely invalid or missing parameters', request, response) when 414 then - raise Errors::RequestEntityTooLarge.new('You sent too much data', request, response) + raise Joyent::Errors::RequestEntityTooLarge.new('You sent too much data', request, response) when 415 then - raise Errors::UnsupportedMediaType.new('You encoded your request in a format we don\'t understand', request, response) + raise Joyent::Errors::UnsupportedMediaType.new('You encoded your request in a format we don\'t understand', request, response) when 420 then - raise Errors::PolicyNotForfilled.new('You are sending too many requests', request, response) + raise Joyent::Errors::PolicyNotForfilled.new('You are sending too many requests', request, response) when 449 then - raise Errors::RetryWith.new('Invalid API Version requested; try with a different API Version', request, response) + raise Joyent::Errors::RetryWith.new('Invalid API Version requested; try with a different API Version', request, response) when 503 then - raise Errors::ServiceUnavailable.new('Either there\'s no capacity in this datacenter, or we\'re in a maintenance window', request, response) + raise Joyent::Errors::ServiceUnavailable.new('Either there\'s no capacity in this datacenter, or we\'re in a maintenance window', request, response) end end end # Real end