1
0
mirror of https://github.com/OpenNebula/one.git synced 2025-03-22 18:50:08 +03:00

feature #795: Add new Cloud Auth system

This commit is contained in:
Daniel Molina 2011-09-20 18:24:51 +02:00
parent eb2bdc3b05
commit f97ef6be7b
5 changed files with 253 additions and 93 deletions

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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