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

feature #795: Adapt EC2 to the new Cloud Auth system

This commit is contained in:
Daniel Molina 2011-09-20 18:25:23 +02:00
parent f97ef6be7b
commit afdf9d23c5
3 changed files with 64 additions and 201 deletions

View File

@ -24,5 +24,7 @@ PORT=4567
# SSL proxy that serves the API (set if is being used)
#SSL_SERVER=fqdm.of.the.server
AUTH=ec2
# VM types allowed and its template file (inside templates directory)
VM_TYPE=[NAME=m1.small, TEMPLATE=m1.small.erb]

View File

@ -15,7 +15,6 @@
#--------------------------------------------------------------------------- #
require 'rubygems'
require 'sinatra'
require 'erb'
require 'time'
require 'AWS'
@ -62,118 +61,26 @@ class EC2QueryServer < CloudServer
###########################################################################
def initialize(config_file,template,views)
super(config_file)
@config.add_configuration_value("TEMPLATE_LOCATION",template)
@config.add_configuration_value("VIEWS",views)
if @config[:ssl_server]
@server_host=@config[:ssl_server]
else
@server_host=@config[:server]
end
@server_port=@config[:port]
print_configuration
def initialize(config)
super(config)
end
###########################################################################
# Authentication functions
###########################################################################
# EC2 protocol authentication function
# params:: of the request
# [return] true if authenticated
def authenticate(params,env)
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
if cert_line == nil
# Use the secret key for authentication.
password = get_user_password(params['AWSAccessKeyId'])
return nil if !password
def authenticate(env, params)
econe_host = @config[:ssl_server]
econe_host ||= @config[:server]
signature = case params['SignatureVersion']
when "1" then signature_version_1(params.clone, password)
when "2" then signature_version_2(params,
password,
env,
true,
false)
end
econe_port = @config[:port]
if params['Signature']==signature
return one_client_user(params['AWSAccessKeyId'], password)
else
if params['SignatureVersion']=="2"
signature = signature_version_2(params,
password,
env,
false,
false)
if params['Signature']==signature
return one_client_user(params['AWSAccessKeyId'], password)
end
end
end
return nil
else
# 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
raise failed + "Username not found in certificate chain " + chain_dn
end
auth = ServerAuth.new
login = auth.login_token(username, subjectname, 300)
STDERR.puts login
return one_client_user("dummy", login)
end
params.merge!({:econe_host => econe_host, :econe_port => econe_port})
super(env, params)
end
###########################################################################
# Repository Interface
###########################################################################
def upload_image(params, one_client)
image = ImageEC2.new(Image.build_xml, one_client, params['file'])
def upload_image(params)
image = ImageEC2.new(Image.build_xml, self.client, params['file'])
template = image.to_one_template
if OpenNebula.is_error?(template)
@ -191,11 +98,11 @@ class EC2QueryServer < CloudServer
return response.result(binding), 200
end
def register_image(params, one_client)
def register_image(params)
# Get the Image ID
tmp, img=params['ImageLocation'].split('-')
image = Image.new(Image.build_xml(img.to_i), one_client)
image = Image.new(Image.build_xml(img.to_i), self.client)
# Enable the new Image
rc = image.info
@ -211,9 +118,9 @@ class EC2QueryServer < CloudServer
return response.result(binding), 200
end
def describe_images(params, one_client)
def describe_images(params)
user_flag = OpenNebula::Pool::INFO_GROUP
impool = ImagePool.new(one_client, user_flag)
impool = ImagePool.new(self.client, user_flag)
impool.info
erb_version = params['Version']
@ -226,11 +133,11 @@ class EC2QueryServer < CloudServer
# Instance Interface
###########################################################################
def run_instances(params, one_client)
def run_instances(params)
# Get the instance type and path
if params['InstanceType'] != nil
instance_type_name = params['InstanceType']
instance_type = @instance_types[instance_type_name]
instance_type = @config[:instance_types][instance_type_name]
if instance_type != nil
path = @config[:template_location] + "/#{instance_type['TEMPLATE']}"
@ -252,7 +159,7 @@ class EC2QueryServer < CloudServer
template_text = template.result(binding)
# Start the VM.
vm = VirtualMachine.new(VirtualMachine.build_xml, one_client)
vm = VirtualMachine.new(VirtualMachine.build_xml, self.client)
rc = vm.allocate(template_text)
if OpenNebula::is_error?(rc)
@ -270,26 +177,26 @@ class EC2QueryServer < CloudServer
return response.result(binding), 200
end
def describe_instances(params, one_client)
def describe_instances(params)
user_flag = OpenNebula::Pool::INFO_MINE
vmpool = VirtualMachinePool.new(one_client, user_flag)
vmpool = VirtualMachinePool.new(self.client, user_flag)
vmpool.info
erb_version = params['Version']
erb_user_name = params['AWSAccessKeyId']
response = ERB.new(File.read(@config[:views]+"/describe_instances.erb"))
return response.result(binding), 200
end
def terminate_instances(params, one_client)
def terminate_instances(params)
# Get the VM ID
vmid=params['InstanceId.1']
vmid=params['InstanceId.01'] if !vmid
tmp, vmid=vmid.split('-') if vmid[0]==?i
vm = VirtualMachine.new(VirtualMachine.build_xml(vmid),one_client)
vm = VirtualMachine.new(VirtualMachine.build_xml(vmid),self.client)
rc = vm.info
return OpenNebula::Error.new('Unsupported'),400 if OpenNebula::is_error?(rc)
@ -308,55 +215,6 @@ class EC2QueryServer < CloudServer
return response.result(binding), 200
end
private
# Calculates signature version 1
def signature_version_1(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_version_2(params, secret_key, env, includeport=true, urlencode=true)
signature_params = params.reject { |key,value|
key=='Signature' or key=='file' }
if includeport
server_str = @server_host + ':' + @server_port
else
server_str = @server_host
end
canonical_str = AWS.canonical_string(signature_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)
b64hmac =
Base64.encode64(
OpenSSL::HMAC.digest(digest, secret_key, canonical_str)).gsub("\n","")
if urlencode
return CGI::escape(b64hmac)
else
return b64hmac
end
end
###########################################################################
# Helper functions
###########################################################################

View File

@ -42,19 +42,28 @@ require 'rubygems'
require 'sinatra'
require 'EC2QueryServer'
require 'Configuration'
include OpenNebula
begin
$econe_server = EC2QueryServer.new(CONFIGURATION_FILE,
TEMPLATE_LOCATION, VIEWS_LOCATION)
config = Configuration.new(CONFIGURATION_FILE)
config.add_configuration_value("TEMPLATE_LOCATION", TEMPLATE_LOCATION)
config.add_configuration_value("VIEWS", VIEWS_LOCATION)
instance_types = CloudServer.get_instance_types(config)
config.add_configuration_value("INSTANCE_TYPES", instance_types)
CloudServer.print_configuration(config)
set :config, config
rescue Exception => e
puts "Error starting server: #{e}"
exit(-1)
end
if CloudServer.is_port_open?($econe_server.config[:server],
$econe_server.config[:port])
if CloudServer.is_port_open?(settings.config[:server],
settings.config[:port])
puts "Port busy, please shutdown the service or move econe server port."
exit
end
@ -62,23 +71,18 @@ end
##############################################################################
# Sinatra Configuration
##############################################################################
set :host, $econe_server.config[:server]
set :port, $econe_server.config[:port]
set :host, settings.config[:server]
set :port, settings.config[:port]
##############################################################################
# Actions
##############################################################################
before do
#STDERR.puts Time.now.strftime("%d/%b/%Y %H:%M:%S")
begin
@client = $econe_server.authenticate(params,env)
rescue => e
#STDERR.puts Time.now.strftime("%d/%b/%Y %H:%M:%S") + ' Exception in authentication. ' + e.to_s
@client = nil
end
if @client.nil?
@econe_server = EC2QueryServer.new(settings.config)
result = @econe_server.authenticate(request.env, params)
if result
# Add a log message
error 400, error_xml("AuthFailure", 0)
end
end
@ -86,7 +90,7 @@ end
helpers do
def error_xml(code,id)
message = ''
case code
when 'AuthFailure'
message = 'User not authorized'
@ -94,45 +98,44 @@ helpers do
message = 'Specified AMI ID does not exist'
when 'Unsupported'
message = 'The instance type or feature is not supported in your requested Availability Zone.'
else
else
message = code
end
xml = "<Response><Errors><Error><Code>"+
code +
"</Code><Message>" +
message +
"</Message></Error></Errors><RequestID>" +
id.to_s +
code +
"</Code><Message>" +
message +
"</Message></Error></Errors><RequestID>" +
id.to_s +
"</RequestID></Response>"
return xml
end
return xml
end
end
post '/' do
do_http_request(params, @client)
do_http_request(params)
end
get '/' do
do_http_request(params, @client)
do_http_request(params)
end
def do_http_request(params, client)
def do_http_request(params)
case params['Action']
when 'UploadImage'
result,rc = $econe_server.upload_image(params, client)
result,rc = @econe_server.upload_image(params)
when 'RegisterImage'
result,rc = $econe_server.register_image(params, client)
result,rc = @econe_server.register_image(params)
when 'DescribeImages'
result,rc = $econe_server.describe_images(params, client)
result,rc = @econe_server.describe_images(params)
when 'RunInstances'
result,rc = $econe_server.run_instances(params, client)
result,rc = @econe_server.run_instances(params)
when 'DescribeInstances'
result,rc = $econe_server.describe_instances(params, client)
result,rc = @econe_server.describe_instances(params)
when 'TerminateInstances'
result,rc = $econe_server.terminate_instances(params, client)
result,rc = @econe_server.terminate_instances(params)
end
if OpenNebula::is_error?(result)
@ -141,5 +144,5 @@ def do_http_request(params, client)
headers['Content-Type'] = 'application/xml'
result
result
end