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:
parent
eb2bdc3b05
commit
f97ef6be7b
37
src/cloud/common/CloudAuth.rb
Normal file
37
src/cloud/common/CloudAuth.rb
Normal 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
|
20
src/cloud/common/CloudAuth/BasicCloudAuth.rb
Normal file
20
src/cloud/common/CloudAuth/BasicCloudAuth.rb
Normal 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
|
76
src/cloud/common/CloudAuth/EC2CloudAuth.rb
Normal file
76
src/cloud/common/CloudAuth/EC2CloudAuth.rb
Normal 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
|
87
src/cloud/common/CloudAuth/X509CloudAuth.rb
Normal file
87
src/cloud/common/CloudAuth/X509CloudAuth.rb
Normal 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
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user