diff --git a/src/cloud/common/CloudAuth.rb b/src/cloud/common/CloudAuth.rb new file mode 100644 index 0000000000..0dae4b76d9 --- /dev/null +++ b/src/cloud/common/CloudAuth.rb @@ -0,0 +1,37 @@ +class CloudAuth + AUTH_MODULES = { + "basic" => 'BasicCloudAuth', + "ec2" => 'EC2CloudAuth', + "x509" => 'X509CloudAuth' + } + + attr_reader :client, :token + + def initialize(conf) + @xmlrpc = conf[:one_xmlrpc] + + if AUTH_MODULES.include?(conf[:auth]) + require 'CloudAuth/' + AUTH_MODULES[conf[:auth]] + extend Kernel.const_get(AUTH_MODULES[conf[:auth]]) + else + raise "Auth module not specified" + end + end + + protected + + def get_password(username) + @oneadmin_client ||= OpenNebula::Client.new(nil, @xmlrpc) + + if @user_pool.nil? + @user_pool ||= OpenNebula::UserPool.new(@oneadmin_client) + + rc = @user_pool.info + if OpenNebula.is_error?(rc) + raise rc.message + end + end + + return @user_pool["USER[NAME=\"#{username}\"]/PASSWORD"] + end +end \ No newline at end of file diff --git a/src/cloud/common/CloudAuth/BasicCloudAuth.rb b/src/cloud/common/CloudAuth/BasicCloudAuth.rb new file mode 100644 index 0000000000..c4d53e8c03 --- /dev/null +++ b/src/cloud/common/CloudAuth/BasicCloudAuth.rb @@ -0,0 +1,20 @@ +module BasicCloudAuth + def auth(env, params={}) + auth = Rack::Auth::Basic::Request.new(env) + + if auth.provided? && auth.basic? + username, password = auth.credentials + + one_pass = get_password(username) + if one_pass && one_pass == password + @token = "#{username}:#{password}" + @client = Client.new(@token, @xmlrpc, false) + return nil + else + return "Authentication failure" + end + else + return "Basic auth not provided" + end + end +end \ No newline at end of file diff --git a/src/cloud/common/CloudAuth/EC2CloudAuth.rb b/src/cloud/common/CloudAuth/EC2CloudAuth.rb new file mode 100644 index 0000000000..2842d8c940 --- /dev/null +++ b/src/cloud/common/CloudAuth/EC2CloudAuth.rb @@ -0,0 +1,76 @@ +module EC2CloudAuth + def auth(env, params={}) + username = params['AWSAccessKeyId'] + one_pass = get_password(username) + return "Invalid credentials" unless one_pass + + signature = case params['SignatureVersion'] + when "1" then signature_v1(params.clone,one_pass) + when "2" then signature_v2(params.clone,one_pass,env,true,false) + end + + if params['Signature'] != signature + if params['SignatureVersion']=="2" + signature = signature_v2(params.clone,one_pass,env,false,false) + if params['Signature'] != signature + return "Invalid Credentials" + end + else + return "Invalid Credentials" + end + end + + @token = "#{username}:#{one_pass}" + @client = Client.new(@token, @xmlrpc, false) + return nil + end + + private + + # Calculates signature version 1 + def signature_v1(params, secret_key, digest='sha1') + params.delete('Signature') + req_desc = params.sort {|x,y| x[0].downcase <=> y[0].downcase}.to_s + + digest_generator = OpenSSL::Digest::Digest.new(digest) + digest = OpenSSL::HMAC.digest(digest_generator, secret_key, req_desc) + b64sig = Base64.b64encode(digest) + return b64sig.strip + end + + # Calculates signature version 2 + def signature_v2(params, secret_key, env, include_port=true, urlencode=true) + params.delete('Signature') + params.delete('file') + + server_host = params.delete(:econe_host) + server_port = params.delete(:econe_port) + if include_port + server_str = "#{server_host}:#{server_port}" + else + server_str = server_host + end + + canonical_str = AWS.canonical_string( + params, + server_str, + env['REQUEST_METHOD']) + + # Use the correct signature strength + sha_strength = case params['SignatureMethod'] + when "HmacSHA1" then 'sha1' + when "HmacSHA256" then 'sha256' + else 'sha1' + end + + digest = OpenSSL::Digest::Digest.new(sha_strength) + hmac = OpenSSL::HMAC.digest(digest, secret_key, canonical_str) + b64hmac = Base64.encode64(hmac).gsub("\n","") + + if urlencode + return CGI::escape(b64hmac) + else + return b64hmac + end + end +end \ No newline at end of file diff --git a/src/cloud/common/CloudAuth/X509CloudAuth.rb b/src/cloud/common/CloudAuth/X509CloudAuth.rb new file mode 100644 index 0000000000..6488b7999e --- /dev/null +++ b/src/cloud/common/CloudAuth/X509CloudAuth.rb @@ -0,0 +1,87 @@ +module X509CloudAuth + # TBD Adapt to the new CloudAuth system + + # Gets the username associated with a password + # password:: _String_ the password + # [return] _Hash_ with the username + def get_username(password) + @user_pool.info + #STDERR.puts 'the password is ' + password + #STDERR.puts @user_pool["User[PASSWORD=\"#{password}\"]"] + username = @user_pool["User[PASSWORD=\"#{password}\"]/NAME"] + return username if (username != nil) + + # Check if the DN is part of a |-separted multi-DN password + user_elts = Array.new + @user_pool.each {|e| user_elts << e['PASSWORD']} + multiple_users = user_elts.select {|e| e=~ /\|/ } + matched = nil + multiple_users.each do |e| + e.to_s.split('|').each do |w| + if (w == password) + matched=e + break + end + end + break if matched + end + if matched + password = matched.to_s + end + puts("The password is " + password) + return @user_pool["USER[PASSWORD=\"#{password}\"]/NAME"] + end + + def auth(env, params={}) + failed = 'Authentication failed. ' + # For https, the web service should be set to include the user cert in the environment. + cert_line = env['HTTP_SSL_CLIENT_CERT'] + cert_line = nil if cert_line == '(null)' # For Apache mod_ssl + + # Use the https credentials for authentication + require 'server_auth' + while cert_line + begin + cert_array=cert_line.scan(/([^\s]*)\s/) + cert_array = cert_array[2..-3] + cert_array.unshift('-----BEGIN CERTIFICATE-----') + cert_array.push('-----END CERTIFICATE-----') + cert_pem = cert_array.join("\n") + cert = OpenSSL::X509::Certificate.new(cert_pem) + rescue + raise failed + "Could not create X509 certificate from " + cert_line + end + + # Password should be DN with whitespace removed. + subjectname = cert.subject.to_s.delete("\s") + begin + username = get_username(subjectname) + rescue + username = nil + end + + break if username + + chain_dn = (!chain_dn ? "" : chain_dn) + "\n" + subjectname + chain_index = !chain_index ? 0 : chain_index + 1 + cert_line = env["HTTP_SSL_CLIENT_CERT_CHAIN_#{chain_index}"] + cert_line = nil if cert_line == '(null)' # For Apache mod_ssl + end + + if !cert_line + msg = "" + msg << failed + msg << "Username not found in certificate chain " + msg << chain_dn + raise msg + end + + auth = ServerAuth.new + + login = auth.login_token(username, subjectname, 300) + + STDERR.puts login + + return one_client_user("dummy", login) + end +end \ No newline at end of file diff --git a/src/cloud/common/CloudServer.rb b/src/cloud/common/CloudServer.rb index 070ae9d59d..b5abc10ca2 100755 --- a/src/cloud/common/CloudServer.rb +++ b/src/cloud/common/CloudServer.rb @@ -14,9 +14,8 @@ # limitations under the License. # #--------------------------------------------------------------------------- # -require 'Configuration' require 'OpenNebula' -require 'pp' +require 'CloudAuth' ############################################################################## # This class represents a generic Cloud Server using the OpenNebula Cloud @@ -28,115 +27,39 @@ class CloudServer # Public attributes ########################################################################## attr_reader :config - attr_reader :one_client # Initializes the Cloud server based on a config file # config_file:: _String_ for the server. MUST include the following # variables: - # USER - # PASSWORD + # AUTH # VM_TYPE - # IMAGE_DIR - # DATABASE - def initialize(config_file) - + # XMLRPC + def initialize(config) # --- Load the Cloud Server configuration file --- + @config = config + @cloud_auth = CloudAuth.new(@config) + end - @config = Configuration.new(config_file) + def authenticate(env, params={}) + @cloud_auth.auth(env, params) + end - if @config[:vm_type] == nil - raise "No VM_TYPE defined." - end - - @instance_types = Hash.new - - if @config[:vm_type].kind_of?(Array) - @config[:vm_type].each {|type| - @instance_types[type['NAME']]=type - } - else - @instance_types[@config[:vm_type]['NAME']]=@config[:vm_type] - end - - # --- Start an OpenNebula Session --- - - @one_client = Client.new(nil,@config[:one_xmlrpc]) - @user_pool = UserPool.new(@one_client) + def client + @cloud_auth.client end # # Prints the configuration of the server # - def print_configuration + def self.print_configuration(config) puts "--------------------------------------" puts " Server configuration " puts "--------------------------------------" - pp @config + pp config - puts "--------------------------------------" - puts " Registered Instance Types " - puts "--------------------------------------" - pp @instance_types + STDOUT.flush end - ########################################################################### - # USER and OpenNebula Session Methods - ########################################################################### - - # Generates an OpenNebula Session for the given user - # user:: _Hash_ the user information - # [return] an OpenNebula client session - def one_client_user(name, password) - client = Client.new("dummy:dummy") - if name=="dummy" - #STDERR.puts "#{password}" - client.one_auth = "#{password}" - else - client.one_auth = "#{name}:#{password}" - end - - return client - end - - # Gets the data associated with a user - # name:: _String_ the name of the user - # [return] _Hash_ with the user data - def get_user_password(name) - @user_pool.info - return @user_pool["USER[NAME=\"#{name}\"]/PASSWORD"] - end - - # Gets the username associated with a password - # password:: _String_ the password - # [return] _Hash_ with the username - def get_username(password) - @user_pool.info - #STDERR.puts 'the password is ' + password - #STDERR.puts @user_pool["User[PASSWORD=\"#{password}\"]"] - username = @user_pool["User[PASSWORD=\"#{password}\"]/NAME"] - return username if (username != nil) - - # Check if the DN is part of a |-separted multi-DN password - user_elts = Array.new - @user_pool.each {|e| user_elts << e['PASSWORD']} - multiple_users = user_elts.select {|e| e=~ /\|/ } - matched = nil - multiple_users.each do |e| - e.to_s.split('|').each do |w| - if (w == password) - matched=e - break - end - end - break if matched - end - if matched - password = matched.to_s - end - puts("The password is " + password) - return @user_pool["USER[PASSWORD=\"#{password}\"]/NAME"] - end - # Finds out if a port is available on ip # ip:: _String_ IP address where the port to check is # port:: _String_ port to find out whether is open @@ -157,5 +80,22 @@ class CloudServer return false end -end + def self.get_instance_types(config) + if config[:vm_type] == nil + raise "No VM_TYPE defined." + end + + instance_types = Hash.new + + if config[:vm_type].kind_of?(Array) + config[:vm_type].each {|type| + instance_types[type['NAME']]=type + } + else + instance_types[config[:vm_type]['NAME']]=config[:vm_type] + end + + instance_types + end +end