diff --git a/include/AuthManager.h b/include/AuthManager.h index 2f330c8225..8d4e49990e 100644 --- a/include/AuthManager.h +++ b/include/AuthManager.h @@ -387,17 +387,26 @@ public: /** * Gets the authorization requests in a single string - * @return a space separated list of auth requests. + * @return a space separated list of auth requests, or an empty string if + * no auth requests were added */ string get_auths() { ostringstream oss; + unsigned int i; - for (unsigned int i=0; i @@ -556,7 +564,7 @@ VMM_EXEC_XEN_KVM_POLL="src/vmm_mad/remotes/poll_xen_kvm.rb" VMM_EXEC_GANGLIA_POLL="src/vmm_mad/remotes/poll_ganglia.rb" #------------------------------------------------------------------------------- -# Information Manager Probes, to be installed under $LIB_LOCATION/remotes +# Information Manager Probes, to be installed under $REMOTES_LOCATION/im #------------------------------------------------------------------------------- IM_PROBES_FILES="src/im_mad/remotes/run_probes" @@ -573,6 +581,19 @@ IM_PROBES_KVM_FILES="src/im_mad/remotes/kvm.d/kvm.rb \ IM_PROBES_GANGLIA_FILES="src/im_mad/remotes/ganglia.d/ganglia_probe" +#------------------------------------------------------------------------------- +# Auth Manager drivers to be installed under $REMOTES_LOCATION/auth +#------------------------------------------------------------------------------- + +AUTH_SERVER_FILES="src/authm_mad/remotes/server/authenticate" + +AUTH_X509_FILES="src/authm_mad/remotes/x509/authenticate" + +AUTH_SSH_FILES="src/authm_mad/remotes/ssh/authenticate" + +AUTH_DUMMY_FILES="src/authm_mad/remotes/dummy/authenticate" + +AUTH_PLAIN_FILES="src/authm_mad/remotes/plain/authenticate" #------------------------------------------------------------------------------- # Transfer Manager commands, to be installed under $LIB_LOCATION/tm_commands @@ -682,11 +703,11 @@ TM_LVM_ETC_FILES="src/tm_mad/lvm/tm_lvm.conf \ HM_ETC_FILES="src/hm_mad/hmrc" #------------------------------------------------------------------------------- -# Hook Manager driver config. files, to be installed under $ETC_LOCATION/hm +# Auth Manager drivers config. files, to be installed under $ETC_LOCATION/auth #------------------------------------------------------------------------------- -AUTH_ETC_FILES="src/authm_mad/auth_mad \ - src/authm_mad/auth.conf" +AUTH_ETC_FILES="src/authm_mad/remotes/server/server_auth.conf \ + src/authm_mad/remotes/x509/x509_auth.conf" #------------------------------------------------------------------------------- # Sample files, to be installed under $SHARE_LOCATION/examples diff --git a/src/authm/AuthManager.cc b/src/authm/AuthManager.cc index 86d286b61c..46d079a89f 100644 --- a/src/authm/AuthManager.cc +++ b/src/authm/AuthManager.cc @@ -285,7 +285,8 @@ void AuthManager::authorize_action(AuthRequest * ar) if (authm_md == 0) { - goto error_driver; + ar->message = "Could not find Authorization driver"; + goto error; } // ------------------------------------------------------------------------ @@ -300,15 +301,23 @@ void AuthManager::authorize_action(AuthRequest * ar) auths = ar->get_auths(); - authm_md->authorize(ar->id, ar->uid, auths); + if ( auths.empty() ) + { + ar->message = "Empty authorization string"; + goto error; + } + + authm_md->authorize(ar->id, ar->uid, auths, ar->self_authorize); return; -error_driver: - ar->result = false; - ar->message = "Could not find Authorization driver"; +error: + ar->result = false; ar->notify(); + + return; } + /* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */ diff --git a/src/authm/AuthManagerDriver.cc b/src/authm/AuthManagerDriver.cc index 43a29445f4..ab12138c94 100644 --- a/src/authm/AuthManagerDriver.cc +++ b/src/authm/AuthManagerDriver.cc @@ -25,20 +25,23 @@ /* Driver ASCII Protocol Implementation */ /* ************************************************************************** */ -void AuthManagerDriver::authorize(int oid, int uid, const string& reqs) const +void AuthManagerDriver::authorize(int oid, + int uid, + const string& reqs, + bool acl) const { ostringstream os; - os << "AUTHORIZE " << oid << " " << uid << " " << reqs << endl; + os << "AUTHORIZE " << oid << " " << uid << " " << reqs << " " << acl <trigger(AuthManager::AUTHORIZE,&ar); ar.wait(); - /* if ( ar.result != false ) { @@ -229,6 +228,56 @@ public: //*/ CPPUNIT_ASSERT(ar.result==false); CPPUNIT_ASSERT(ar.message==astr); + + AuthRequest ar1(2, 2); + + string astr1= "VM:VGhpcyBpcyBhIHRlbXBsYXRlCg==:CREATE:-1:0:0 0"; + + ar1.add_auth(AuthRequest::VM, + "This is a template\n", + 0, + AuthRequest::CREATE, + -1, + false); + + am->trigger(AuthManager::AUTHORIZE,&ar1); + ar1.wait(); + /* + if ( ar1.result != false ) + { + cout << endl << "ar.result: " << ar1.result << endl; + } + + if ( ar1.message != astr1 ) + { + cout << endl << "ar.message: " << ar1.message; + cout << endl << "expected: " << astr1 << endl; + } +//*/ + CPPUNIT_ASSERT(ar1.result==false); + CPPUNIT_ASSERT(ar1.message==astr1); + + AuthRequest ar2(2, 2); + + string astr2= "Empty authorization string"; + + + am->trigger(AuthManager::AUTHORIZE,&ar2); + ar2.wait(); +/* + if ( ar1.result != false ) + { + cout << endl << "ar.result: " << ar1.result << endl; + } + + if ( ar1.message != astr1 ) + { + cout << endl << "ar.message: " << ar1.message; + cout << endl << "expected: " << astr1 << endl; + } +//*/ + CPPUNIT_ASSERT(ar2.result==false); + CPPUNIT_ASSERT(ar2.message==astr2); } diff --git a/src/authm_mad/auth.conf b/src/authm_mad/auth.conf deleted file mode 100644 index 8efb6a6678..0000000000 --- a/src/authm_mad/auth.conf +++ /dev/null @@ -1,8 +0,0 @@ -:database: sqlite://auth.db -:authentication: simple -:quota: - :enabled: false - :defaults: - :cpu: 10.0 - :memory: 1048576 - :num_vms: 10 \ No newline at end of file diff --git a/src/authm_mad/auth_mad b/src/authm_mad/auth_mad deleted file mode 100644 index 8b13789179..0000000000 --- a/src/authm_mad/auth_mad +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/authm_mad/one_auth_mad.rb b/src/authm_mad/one_auth_mad.rb index fbc9af163a..c1b9d6cd06 100755 --- a/src/authm_mad/one_auth_mad.rb +++ b/src/authm_mad/one_auth_mad.rb @@ -28,87 +28,130 @@ end $: << RUBY_LIB_LOCATION -require 'pp' -require 'rubygems' +require 'scripts_common' require 'OpenNebulaDriver' -require 'simple_auth' -require 'simple_permissions' -require 'yaml' -require 'sequel' -require 'ssh_auth' +require 'getoptlong' -class AuthorizationManager < OpenNebulaDriver - def initialize +# This is a generic AuthZ/AuthN driver able to manage multiple authentication +# protocols (simultaneosly). It also supports the definition of custom +# authorization methods +class AuthDriver < OpenNebulaDriver + + # Auth Driver Protocol constants + ACTION = { + :authN => "AUTHENTICATE", + :authZ => "AUTHORIZE" + } + + + # Initialize an AuthDriver + # + # @param [String] the authorization method to be used, nil to use the + # built-in ACL engine + def initialize(authZ, nthreads) super( - :concurrency => 15, - :threaded => true + "auth", + :concurrency => nthreads, + :threaded => nthreads > 0, + :local_actions => {ACTION[:authN] => nil, ACTION[:authZ] => nil} ) + + register_action(ACTION[:authN].to_sym, method("authN")) + register_action(ACTION[:authZ].to_sym, method("authZ")) + + if authZ != nil + @authZ_cmd = File.join(@local_scripts_path, authZ) + @authZ_cmd = File.join(@authZ_cmd, ACTION[:authZ].downcase) + else + @authZ_cmd = nil + end + end + + # Authenticate a user based in a string of the form user:secret when using the + # driver secret is protocol:token + # @param [String] the id for this request, used by OpenNebula core + # to identify the request + # @param [String] id of the user, "-1" if not in defined in OpenNebula + # @param [Strgin] user filed of the auth string + # @param [String] password of the user registered in OpenNebula "-" if none + # @param [String] secret filed of the auth string + def authN(request_id, user_id, user, password, secret) + #OpenNebula.log_debug("authN: #{request_id} #{user_id} #{password} #{secret}") + + secret_attr = secret.split(':') + + if secret_attr.length == 1 + protocol = "plain" + else + protocol = secret_attr[0] + secret_attr.shift + end + + #build path for the auth action + #/var/lib/one/remotes/auth//authenticate + authN_path = File.join(@local_scripts_path, protocol) - config_data=File.read(ETC_LOCATION+'/auth/auth.conf') - STDERR.puts(config_data) - @config=YAML::load(config_data) + command = File.join(authN_path,ACTION[:authN].downcase) + command << ' ' << user << ' ' << password << ' ' << secret_attr.join(' ') + + local_action(command, request_id, ACTION[:authN]) + end + + # Authenticate a user based in a string of the form user:secret when using the + # driver secret is protocol:token + # @param [String] the id for this request, used by OpenNebula core + # to identify the request + # @param [String] id of the user, "-1" if not in defined in OpenNebula + # @param [Array] of auth strings, last element is the ACL evaluation of + # the overall request (0 = denied, 1 = granted). Each request is in + # the form: + # OBJECT::OPERATION:OWNER:PUBLIC:ACL_EVAL + def authZ(request_id, user_id, *requests) - STDERR.puts @config.inspect + requests.flatten! + + #OpenNebula.log_debug("authZ: #{request_id} #{user_id} #{requests}") - database_url=@config[:database] - @db=Sequel.connect(database_url) - - # Get authentication driver - begin - driver_prefix=@config[:authentication].capitalize - driver_name="#{driver_prefix}Auth" - driver=Kernel.const_get(driver_name.to_sym) - @authenticate=driver.new + if @authZ_cmd == nil + if requests[-1] == "1" + result = RESULT[:success] + else + result = RESULT[:failure] + end + + send_message(ACTION[:authZ],result,request_id,"-") + else + command = @authZ_cmd.clone + command << ' ' << user_id << ' ' << requests.join(' ') - STDERR.puts "Using '#{driver_prefix}' driver for authentication" - rescue - STDERR.puts "Driver '#{driver_prefix}' not found, "<< - "using SimpleAuth instead" - @authenticate=SimpleAuth.new - end - - @permissions=SimplePermissions.new(@db, OpenNebula::Client.new, - @config) - - register_action(:AUTHENTICATE, method('action_authenticate')) - register_action(:AUTHORIZE, method('action_authorize')) - end - - def action_authenticate(request_id, user_id, user, password, token) - auth=@authenticate.auth(user_id, user, password, token) - if auth==true - send_message('AUTHENTICATE', RESULT[:success], - request_id, 'Successfully authenticated') - else - send_message('AUTHENTICATE', RESULT[:failure], - request_id, auth) - end - end - - def action_authorize(request_id, user_id, *tokens) - begin - auth=@permissions.auth(user_id, tokens.flatten) - rescue Exception => e - auth="Error: #{e}" - end - - if auth==true - send_message('AUTHORIZE', RESULT[:success], - request_id, 'success') - else - send_message('AUTHORIZE', RESULT[:failure], - request_id, auth) + local_action(command, request_id, ACTION[:authZ]) end end end +# Auth Driver Main program +opts = GetoptLong.new( + [ '--threads', '-t', GetoptLong::REQUIRED_ARGUMENT ], + [ '--authz', '-z', GetoptLong::REQUIRED_ARGUMENT ] +) + +threads = 15 +authz = nil + begin - am=AuthorizationManager.new + opts.each do |opt, arg| + case opt + when '--threads' + threads = arg.to_i + when '--authz' + authz = arg + end + end rescue Exception => e - puts "Error: #{e}" exit(-1) end -am.start_driver +auth_driver = AuthDriver.new(authz, threads) +auth_driver.start_driver diff --git a/src/authm_mad/one_usage.rb b/src/authm_mad/one_usage.rb deleted file mode 100644 index 63938aba87..0000000000 --- a/src/authm_mad/one_usage.rb +++ /dev/null @@ -1,90 +0,0 @@ -# -------------------------------------------------------------------------- # -# Copyright 2002-2011, OpenNebula Project Leads (OpenNebula.org) # -# # -# Licensed under the Apache License, Version 2.0 (the "License"); you may # -# not use this file except in compliance with the License. You may obtain # -# a copy of the License at # -# # -# http://www.apache.org/licenses/LICENSE-2.0 # -# # -# Unless required by applicable law or agreed to in writing, software # -# distributed under the License is distributed on an "AS IS" BASIS, # -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # -# See the License for the specific language governing permissions and # -# limitations under the License. # -#--------------------------------------------------------------------------- # - -require 'OpenNebula' - -# This class holds usage information for a virtual machine or -# total usage for a user. Variables inside are cpu and memory -# consumption -class VmUsage - attr_accessor :cpu, :memory, :num_vms - def initialize(cpu, memory, num_vms=0) - @cpu=cpu - @memory=memory - @num_vms=num_vms - end -end - -# This class retrieves and caches vms and its consuption grouped -# by users. 'update_user' method should be called to fill data for -# a user before any calculation is made -class OneUsage - # 'client' is an OpenNebula::Client object used to connect - # to OpenNebula daemon. Ideally it should connect as user 0 - def initialize(client) - @client=client - @users=Hash.new - end - - # Gets information about VMs defined for a user. It caches new - # VMs and takes out from the cache deleted VMs - def update_user(user) - @users[user]=Hash.new if !@users[user] - - vmpool=OpenNebula::VirtualMachinePool.new(@client, user) - vmpool.info - - one_ids=vmpool.map {|vm| vm.id } - vms=@users[user] - user_ids=vms.keys - - deleted_vms=user_ids-one_ids - added_vms=one_ids-user_ids - - deleted_vms.each {|vmid| vms.delete(vmid) } - - added_vms.each do |vmid| - vm=OpenNebula::VirtualMachine.new( - OpenNebula::VirtualMachine.build_xml(vmid), @client) - vm.info - - usage=VmUsage.new(vm['TEMPLATE/CPU'].to_f, - vm['TEMPLATE/MEMORY'].to_i) - vms[vmid.to_i]=usage - end - end - - # Returns the cache of defined VMs for a user. It is a hash with - # VM id as key and VmUsage as value - def vms(user) - vms=@users[user] - @users[user]=vms=Hash.new if !vms - vms - end - - # Returns total consumption by a user into a VmUsage object - def total(user) - usage=VmUsage.new(0.0, 0, 0) - - @users[user].each do |id, vm| - usage.cpu+=vm.cpu - usage.memory+=vm.memory - usage.num_vms+=1 - end if @users[user] - - usage - end -end diff --git a/src/authm_mad/oneauth b/src/authm_mad/oneauth deleted file mode 100755 index 965cdb84cc..0000000000 --- a/src/authm_mad/oneauth +++ /dev/null @@ -1,112 +0,0 @@ -#!/usr/bin/env ruby - -# -------------------------------------------------------------------------- # -# Copyright 2002-2011, OpenNebula Project Leads (OpenNebula.org) # -# # -# Licensed under the Apache License, Version 2.0 (the "License"); you may # -# not use this file except in compliance with the License. You may obtain # -# a copy of the License at # -# # -# http://www.apache.org/licenses/LICENSE-2.0 # -# # -# Unless required by applicable law or agreed to in writing, software # -# distributed under the License is distributed on an "AS IS" BASIS, # -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # -# See the License for the specific language governing permissions and # -# limitations under the License. # -#--------------------------------------------------------------------------- # - -ONE_LOCATION=ENV["ONE_LOCATION"] - -if !ONE_LOCATION - RUBY_LIB_LOCATION="/usr/lib/one/ruby" - ETC_LOCATION="/etc/one/" - VAR_LOCATION="/var/lib/one" -else - RUBY_LIB_LOCATION=ONE_LOCATION+"/lib/ruby" - ETC_LOCATION=ONE_LOCATION+"/etc/" - VAR_LOCATION="#{ONE_LOCATION}/var" -end - -$: << RUBY_LIB_LOCATION -$: << RUBY_LIB_LOCATION+'/cli' - -require 'OpenNebula' - -require 'rubygems' -require 'sequel' -require 'quota' -require 'ssh_auth' -require 'yaml' - -require 'command_parser' -require 'one_helper' - -cmd=CommandParser::CmdParser.new(ARGV) do - usage "oneauth COMMAND [args..]" - - description "This command contains a set of utilities to " << - "manage authorization module." - - set :option, CommandParser::OPTIONS - - set :format, :userid, OpenNebulaHelper.name_to_id_desc("USER") do |arg| - OpenNebulaHelper.name_to_id(arg, "USER") - end - - # Helpers - def get_database - config_data=File.read(ETC_LOCATION+'/auth/auth.conf') - config=YAML::load(config_data) - - database_url=config[:database] - db=Sequel.connect(database_url) - end - - def add_quota(uid, cpu, memory, num_vms=nil) - db=get_database - quota=Quota.new(db, OpenNebula::Client.new) - quota.set(uid.to_i, cpu.to_f, memory.to_i, num_vms) - end - - # Commands - quotaset_desc = <<-EOT.unindent - Sets CPU, MEMORY and NUM_VMs quota for a given user - EOT - - command 'quota-set', quotaset_desc , :userid, :cpu, :memory, :num_vms do - Dir.chdir VAR_LOCATION - begin - add_quota(*args[1..4]) - rescue Exception => e - exit_with_code -1, "Error starting server: #{e}" - end - exit_with_code 0 - end - - login_desc = <<-EOT.unindent - Generates authentication proxy. The last argument specifies - the expiration time in seconds - EOT - - command 'login', login_desc, :userid, :text do - user=args[0] - time=args[1] - pp args - if time - time=time.to_i - else - time=3600 - end - - ssh=SshAuth.new - ssh.login(user, time) - exit_with_code 0 - end - - command 'key', 'Gets public key' do - ssh=SshAuth.new - puts ssh.extract_public_key - exit_with_code 0 - end -end \ No newline at end of file diff --git a/src/authm_mad/quota.rb b/src/authm_mad/quota.rb deleted file mode 100644 index ee6047ebbc..0000000000 --- a/src/authm_mad/quota.rb +++ /dev/null @@ -1,141 +0,0 @@ -# -------------------------------------------------------------------------- # -# Copyright 2002-2011, OpenNebula Project Leads (OpenNebula.org) # -# # -# Licensed under the Apache License, Version 2.0 (the "License"); you may # -# not use this file except in compliance with the License. You may obtain # -# a copy of the License at # -# # -# http://www.apache.org/licenses/LICENSE-2.0 # -# # -# Unless required by applicable law or agreed to in writing, software # -# distributed under the License is distributed on an "AS IS" BASIS, # -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # -# See the License for the specific language governing permissions and # -# limitations under the License. # -#--------------------------------------------------------------------------- # - -require 'one_usage' - -# Quota functionality for auth driver. Stores in database limits for each -# user and using OneUsage is able to retrieve resource usage from -# OpenNebula daemon and check if it is below limits -class Quota - attr_accessor :defaults - - TABLE_NAME=:quotas - - # 'db' is a Sequel database where to store user limits and client - # is OpenNebula::Client used to connect to OpenNebula daemon - def initialize(db, client, conf={}) - @db=db - @client=client - @conf={ - :defaults => { - :cpu => nil, - :memory => nil, - :num_vms => nil - } - }.merge(conf) - - @defaults=@conf[:defaults] - - @usage=OneUsage.new(@client) - - create_table - @table=@db[TABLE_NAME] - end - - # Creates database quota table if it does not exist - def create_table - @db.create_table?(TABLE_NAME) do - primary_key :id - Integer :uid - Float :cpu - Integer :memory - Integer :num_vms - index :uid - end - end - - # Adds new user limits - def set(uid, cpu, memory, num_vms) - data={ - :cpu => cpu, - :memory => memory, - :num_vms => num_vms - } - - quotas=@table.filter(:uid => uid) - - if quotas.first - #STDERR.puts "updating" - quotas.update(data) - else - #STDERR.puts "inserting" - @table.insert(data.merge!(:uid => uid)) - end - end - - # Gets user limits - def get(uid) - limit=@table.filter(:uid => uid).first - if limit - limit - else - @defaults - end - end - - # Checks if the user is below resource limits. If new_vm is defined - # checks if its requirements fit in limits - def check(user, new_vm=nil) - use=@usage.total(user) - use_after=use.clone - user_quota=get(user) - if new_vm - use_after.cpu+=new_vm.cpu.to_f - use_after.memory+=new_vm.memory.to_i - use_after.num_vms+=1 - end - - STDERR.puts [user_quota, use_after, new_vm].inspect - - error_message="" - - if !(!user_quota[:cpu] || use_after.cpu<=user_quota[:cpu]) - error_message<<"Cpu quota exceeded (Quota: #{user_quota[:cpu]}, "+ - "Used: #{use.cpu}" - error_message<<", asked: #{new_vm.cpu.to_f}" if new_vm - error_message<<")." - end - - if !(!user_quota[:memory] || use_after.memory<=user_quota[:memory]) - error_message<<" Memory quota exceeded (Quota: "+ - "#{user_quota[:memory]}, Used: #{use.memory}" - error_message<<", asked: #{new_vm.memory.to_i}" if new_vm - error_message<<")." - end - - if !(!user_quota[:num_vms] || use_after.num_vms<=user_quota[:num_vms]) - error_message<<" Num VMS quota exceeded (Quota: "+ - "#{user_quota[:memory]}, Used: #{use.num_vms})." - end - - if error_message=="" - false - else - error_message.strip - end - end - - # Updates user resource consuption - def update(user) - @usage.update_user(user) - end - - # Get cache for the user - def get_user(user) - @usage.vms(user) - end -end - diff --git a/src/authm_mad/remotes/dummy/authenticate b/src/authm_mad/remotes/dummy/authenticate new file mode 100755 index 0000000000..5ef03d7900 --- /dev/null +++ b/src/authm_mad/remotes/dummy/authenticate @@ -0,0 +1,24 @@ +#!/usr/bin/env bash + +# -------------------------------------------------------------------------- # +# Copyright 2002-2011, OpenNebula Project Leads (OpenNebula.org) # +# # +# Licensed under the Apache License, Version 2.0 (the "License"); you may # +# not use this file except in compliance with the License. You may obtain # +# a copy of the License at # +# # +# http://www.apache.org/licenses/LICENSE-2.0 # +# # +# Unless required by applicable law or agreed to in writing, software # +# distributed under the License is distributed on an "AS IS" BASIS, # +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # +# See the License for the specific language governing permissions and # +# limitations under the License. # +#--------------------------------------------------------------------------- # + +# $1 = username +# $2 = password +echo $* + + + diff --git a/src/authm_mad/simple_auth.rb b/src/authm_mad/remotes/plain/authenticate old mode 100644 new mode 100755 similarity index 63% rename from src/authm_mad/simple_auth.rb rename to src/authm_mad/remotes/plain/authenticate index 749da483cb..98649b57d7 --- a/src/authm_mad/simple_auth.rb +++ b/src/authm_mad/remotes/plain/authenticate @@ -1,3 +1,5 @@ +#!/usr/bin/env ruby + # -------------------------------------------------------------------------- # # Copyright 2002-2011, OpenNebula Project Leads (OpenNebula.org) # # # @@ -14,22 +16,29 @@ # limitations under the License. # #--------------------------------------------------------------------------- # -# Password authentication module. This one just compares stored password -# with the token sent by the client. -class SimpleAuth - # Method called by authentication driver. It should awnser true if - # successful or a string with the error message if failure. All - # parameters are strings extracted from the authorization message. - # - # * user_id: OpenNebula user identifier - # * user: user name - # * password: password stored in OpenNebula dabatase - # * token: password sent by the client trying to connect - def auth(user_id, user, password, token) - auth=(password==token) - auth="Invalid credentials" if auth!=true or token=='-' - auth - end +ONE_LOCATION=ENV["ONE_LOCATION"] + +if !ONE_LOCATION + RUBY_LIB_LOCATION="/usr/lib/one/ruby" + ETC_LOCATION="/etc/one/" +else + RUBY_LIB_LOCATION=ONE_LOCATION+"/lib/ruby" + ETC_LOCATION=ONE_LOCATION+"/etc/" end +$: << RUBY_LIB_LOCATION +require 'scripts_common' + +user = ARGV[0] +pass = ARGV[1] +secret = ARGV[2] + +#OpenNebula.log_debug("Authenticating #{user}, with password #{pass} (#{secret})") + +if pass == secret + exit 0 +else + OpenNebula.error_message "Invalid credentials" + exit -1 +end diff --git a/src/authm_mad/remotes/server/authenticate b/src/authm_mad/remotes/server/authenticate new file mode 100755 index 0000000000..20f0a2ebe8 --- /dev/null +++ b/src/authm_mad/remotes/server/authenticate @@ -0,0 +1,55 @@ +#!/usr/bin/env ruby + +# -------------------------------------------------------------------------- # +# Copyright 2002-2011, OpenNebula Project Leads (OpenNebula.org) # +# # +# Licensed under the Apache License, Version 2.0 (the "License"); you may # +# not use this file except in compliance with the License. You may obtain # +# a copy of the License at # +# # +# http://www.apache.org/licenses/LICENSE-2.0 # +# # +# Unless required by applicable law or agreed to in writing, software # +# distributed under the License is distributed on an "AS IS" BASIS, # +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # +# See the License for the specific language governing permissions and # +# limitations under the License. # +#--------------------------------------------------------------------------- # + +ONE_LOCATION=ENV["ONE_LOCATION"] + +if !ONE_LOCATION + RUBY_LIB_LOCATION="/usr/lib/one/ruby" + ETC_LOCATION="/etc/one/" +else + RUBY_LIB_LOCATION=ONE_LOCATION+"/lib/ruby" + ETC_LOCATION=ONE_LOCATION+"/etc/" +end + +$: << RUBY_LIB_LOCATION + +require 'server_auth' +require 'scripts_common' + +user = ARGV[0] # username as registered in OpenNebula +pass = ARGV[1] # password for this user +secret = ARGV[2] # Base64 encoded secret as obtained from login_token + +#OpenNebula.log_debug("Authenticating #{user}, with password #{pass} (#{secret})") + +begin + server_auth = ServerAuth.new + dsecret = Base64::decode64(secret) + + rc = server_auth.authenticate(user, pass, dsecret) +rescue => e + OpenNebula.error_message e.message + exit -1 +end + +if rc == true + exit 0 +else + OpenNebula.error_message rc + exit -1 +end diff --git a/src/authm_mad/remotes/server/server_auth.conf b/src/authm_mad/remotes/server/server_auth.conf new file mode 100644 index 0000000000..2d48125dcd --- /dev/null +++ b/src/authm_mad/remotes/server/server_auth.conf @@ -0,0 +1,4 @@ +# Path to the certificate used by the OpenNebula Services +# Certificates must be in PEM format +:one_cert: "/etc/one/auth/cert.pem" +:one_key: "/etc/one/auth/pk.pem" diff --git a/src/authm_mad/remotes/server/server_auth.rb b/src/authm_mad/remotes/server/server_auth.rb new file mode 100644 index 0000000000..e4003f3289 --- /dev/null +++ b/src/authm_mad/remotes/server/server_auth.rb @@ -0,0 +1,96 @@ +# -------------------------------------------------------------------------- # +# Copyright 2002-2011, OpenNebula Project Leads (OpenNebula.org) # +# # +# Licensed under the Apache License, Version 2.0 (the "License"); you may # +# not use this file except in compliance with the License. You may obtain # +# a copy of the License at # +# # +# http://www.apache.org/licenses/LICENSE-2.0 # +# # +# Unless required by applicable law or agreed to in writing, software # +# distributed under the License is distributed on an "AS IS" BASIS, # +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # +# See the License for the specific language governing permissions and # +# limitations under the License. # +#--------------------------------------------------------------------------- # + +require 'openssl' +require 'base64' +require 'fileutils' + +require 'x509_auth' + +# Server authentication class. This authmethod can be used by opennebula services +# to let access authenticated users by other means. It is based on x509 server +# certificates +class ServerAuth < X509Auth + ########################################################################### + #Constants with paths to relevant files and defaults + ########################################################################### + + SERVER_AUTH_CONF_PATH = ETC_LOCATION + "/auth/server_auth.conf" + + SERVER_DEFAULTS = { + :one_cert => ETC_LOCATION + "/auth/cert.pem", + :one_key => ETC_LOCATION + "/auth/key.pem" + } + + ########################################################################### + + def initialize() + @options = SERVER_DEFAULTS + + load_options(SERVER_AUTH_CONF_PATH) + + begin + certs = [ File.read(@options[:one_cert]) ] + key = File.read(@options[:one_key]) + + super(:certs_pem => certs, + :key_pem => key) + rescue + raise + end + end + + # Generates a login token in the form: + # user_name:server:user_name:user_pass:time_expires + # - user_name:user_pass:time_expires is encrypted with the server certificate + def login_token(user, user_pass, expire) + + expires = Time.now.to_i+expire + + token_txt = "#{user}:#{user_pass}:#{expires}" + + token = encrypt(token_txt) + token64 = Base64::encode64(token).strip.delete("\n") + + login_out = "#{user}:server:#{token64}" + + login_out + end + + ########################################################################### + # Server side + ########################################################################### + # auth method for auth_mad + def authenticate(user, pass, signed_text) + begin + # Decryption demonstrates that the user posessed the private key. + _user, user_pass, expires = decrypt(signed_text).split(':') + + return "User name missmatch" if user != _user + + return "login token expired" if Time.now.to_i >= expires.to_i + + # Check that the signed password matches one for the user. + if !pass.split('|').include?(user_pass) + return "User password missmatch" + end + + return true + rescue => e + return e.message + end + end +end diff --git a/src/authm_mad/remotes/ssh/authenticate b/src/authm_mad/remotes/ssh/authenticate new file mode 100755 index 0000000000..517d9033d2 --- /dev/null +++ b/src/authm_mad/remotes/ssh/authenticate @@ -0,0 +1,53 @@ +#!/usr/bin/env ruby + +# -------------------------------------------------------------------------- # +# Copyright 2002-2011, OpenNebula Project Leads (OpenNebula.org) # +# # +# Licensed under the Apache License, Version 2.0 (the "License"); you may # +# not use this file except in compliance with the License. You may obtain # +# a copy of the License at # +# # +# http://www.apache.org/licenses/LICENSE-2.0 # +# # +# Unless required by applicable law or agreed to in writing, software # +# distributed under the License is distributed on an "AS IS" BASIS, # +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # +# See the License for the specific language governing permissions and # +# limitations under the License. # +#--------------------------------------------------------------------------- # + +ONE_LOCATION=ENV["ONE_LOCATION"] + +if !ONE_LOCATION + RUBY_LIB_LOCATION="/usr/lib/one/ruby" + ETC_LOCATION="/etc/one/" +else + RUBY_LIB_LOCATION=ONE_LOCATION+"/lib/ruby" + ETC_LOCATION=ONE_LOCATION+"/etc/" +end + +$: << RUBY_LIB_LOCATION + +require 'ssh_auth' +require 'scripts_common' + +user = ARGV[0] +pass = ARGV[1] +secret = ARGV[2] + +#OpenNebula.log_debug("Authenticating #{user}, with password #{pass} (#{secret})") +begin + ssh_auth = SshAuth.new(:public_key=>pass) +rescue Exception => e + OpenNebula.error_message e.message + exit -1 +end + +rc = ssh_auth.authenticate(user,secret) + +if rc == true + exit 0 +else + OpenNebula.error_message rc + exit -1 +end diff --git a/src/authm_mad/remotes/ssh/ssh_auth.rb b/src/authm_mad/remotes/ssh/ssh_auth.rb new file mode 100644 index 0000000000..c26f50da9c --- /dev/null +++ b/src/authm_mad/remotes/ssh/ssh_auth.rb @@ -0,0 +1,131 @@ +# -------------------------------------------------------------------------- # +# Copyright 2002-2011, OpenNebula Project Leads (OpenNebula.org) # +# # +# Licensed under the Apache License, Version 2.0 (the "License"); you may # +# not use this file except in compliance with the License. You may obtain # +# a copy of the License at # +# # +# http://www.apache.org/licenses/LICENSE-2.0 # +# # +# Unless required by applicable law or agreed to in writing, software # +# distributed under the License is distributed on an "AS IS" BASIS, # +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # +# See the License for the specific language governing permissions and # +# limitations under the License. # +#--------------------------------------------------------------------------- # + + +require 'pp' +require 'openssl' +require 'base64' +require 'fileutils' + +# SSH key authentication class. It can be used as a driver for auth_mad +# as auth method is defined. It also holds some helper methods to be used +# by oneauth command +class SshAuth + LOGIN_PATH = ENV['HOME']+'/.one/one_ssh' + + attr_reader :public_key + + # Initialize SshAuth object + # + # @param [Hash] default options for path + # @option options [String] :public_key public key for the user + # @option options [String] :private_key key private key for the user. + def initialize(options={}) + @private_key = nil + @public_key = nil + + if options[:private_key] + begin + @private_key = File.read(options[:private_key]) + rescue Exception => e + raise "Cannot read #{options[:private_key]}" + end + end + + if options[:public_key] + @public_key = options[:public_key] + elsif @private_key != nil + # Init ssh keys using private key. public key is extracted in a + # format compatible with openssl. The public key does not contain + # "---- BEGIN/END RSA PUBLIC KEY ----" and is in a single line + key = OpenSSL::PKey::RSA.new(@private_key) + + @public_key = key.public_key.to_pem.split("\n") + @public_key = @public_key.reject {|l| l.match(/RSA PUBLIC KEY/) }.join('') + end + + if @private_key.nil? && @public_key.nil? + raise "You have to define at least one of the keys" + end + end + + # Creates the login file for ssh authentication at ~/.one/one_ssh. + # By default it is valid for 1 hour but it can be changed to any number + # of seconds with expire parameter (in seconds) + def login(user, expire=3600) + expire ||= 3600 + + # Init proxy file path and creates ~/.one directory if needed + proxy_dir = File.dirname(LOGIN_PATH) + + begin + FileUtils.mkdir_p(proxy_dir) + rescue Errno::EEXIST + end + + # Generate security token + time = Time.now.to_i + expire.to_i + + secret_plain = "#{user}:#{time}" + secret_crypted = encrypt(secret_plain) + + proxy = "#{user}:ssh:#{secret_crypted}" + + file = File.open(LOGIN_PATH, "w") + file.write(proxy) + file.close + + secret_crypted + end + + # Checks the proxy created with the login method + def authenticate(user, token) + begin + token_plain = decrypt(token) + _user, time = token_plain.split(':') + + if user == _user + if Time.now.to_i >= time.to_i + return "ssh proxy expired, login again to renew it" + else + return true + end + else + return "invalid credentials" + end + rescue + return "error" + end + end + + private + + ########################################################################### + # Methods to handle ssh keys + ########################################################################### + # Encrypts data with the private key of the user and returns + # base 64 encoded output in a single line + def encrypt(data) + rsa=OpenSSL::PKey::RSA.new(@private_key) + Base64::encode64(rsa.private_encrypt(data)).gsub!(/\n/, '').strip + end + + # Decrypts base 64 encoded data with pub_key (public key) + def decrypt(data) + rsa=OpenSSL::PKey::RSA.new(Base64::decode64(@public_key)) + rsa.public_decrypt(Base64::decode64(data)) + end +end diff --git a/src/authm_mad/remotes/x509/authenticate b/src/authm_mad/remotes/x509/authenticate new file mode 100755 index 0000000000..bd593ee9a4 --- /dev/null +++ b/src/authm_mad/remotes/x509/authenticate @@ -0,0 +1,60 @@ +#!/usr/bin/env ruby + +# -------------------------------------------------------------------------- # +# Copyright 2002-2011, OpenNebula Project Leads (OpenNebula.org) # +# # +# Licensed under the Apache License, Version 2.0 (the "License"); you may # +# not use this file except in compliance with the License. You may obtain # +# a copy of the License at # +# # +# http://www.apache.org/licenses/LICENSE-2.0 # +# # +# Unless required by applicable law or agreed to in writing, software # +# distributed under the License is distributed on an "AS IS" BASIS, # +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # +# See the License for the specific language governing permissions and # +# limitations under the License. # +#--------------------------------------------------------------------------- # + +ONE_LOCATION=ENV["ONE_LOCATION"] + +if !ONE_LOCATION + RUBY_LIB_LOCATION="/usr/lib/one/ruby" + ETC_LOCATION="/etc/one/" +else + RUBY_LIB_LOCATION=ONE_LOCATION+"/lib/ruby" + ETC_LOCATION=ONE_LOCATION+"/etc/" +end + +$: << RUBY_LIB_LOCATION + +require 'x509_auth' +require 'scripts_common' + +user = ARGV[0] # username as registered in OpenNebula +pass = ARGV[1] # DN registered for this user +secret = ARGV[2] # Base64 encoded text and certificate chain text:cert_0:cert_1:..., certs in pem format + +#OpenNebula.log_debug("Authenticating #{user}, with password #{pass} (#{secret})") + +begin + dsecret = Base64::decode64(secret) + asecret = dsecret.split(':') + + token = asecret[0] + certs = asecret[1..-1] + + x509_auth = X509Auth.new(:certs_pem=>certs) + + rc = x509_auth.authenticate(user, pass, token) +rescue => e + OpenNebula.error_message e.message + exit -1 +end + +if rc == true + exit 0 +else + OpenNebula.error_message rc + exit -1 +end diff --git a/src/authm_mad/remotes/x509/x509_auth.conf b/src/authm_mad/remotes/x509/x509_auth.conf new file mode 100644 index 0000000000..679610ba0c --- /dev/null +++ b/src/authm_mad/remotes/x509/x509_auth.conf @@ -0,0 +1,3 @@ +# Path to the trusted CA directory. It should contain the trusted CA's for +# the server, each CA certificate shoud be name CA_hash.0 +:ca_dir: "/etc/one/auth/certificates" diff --git a/src/authm_mad/remotes/x509/x509_auth.rb b/src/authm_mad/remotes/x509/x509_auth.rb new file mode 100644 index 0000000000..dbb69746d7 --- /dev/null +++ b/src/authm_mad/remotes/x509/x509_auth.rb @@ -0,0 +1,236 @@ +# -------------------------------------------------------------------------- # +# Copyright 2002-2011, OpenNebula Project Leads (OpenNebula.org) # +# # +# Licensed under the Apache License, Version 2.0 (the "License"); you may # +# not use this file except in compliance with the License. You may obtain # +# a copy of the License at # +# # +# http://www.apache.org/licenses/LICENSE-2.0 # +# # +# Unless required by applicable law or agreed to in writing, software # +# distributed under the License is distributed on an "AS IS" BASIS, # +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # +# See the License for the specific language governing permissions and # +# limitations under the License. # +#--------------------------------------------------------------------------- # + +require 'openssl' +require 'base64' +require 'fileutils' +require 'yaml' + +# X509 authentication class. It can be used as a driver for auth_mad +# as auth method is defined. It also holds some helper methods to be used +# by oneauth command +class X509Auth + ########################################################################### + #Constants with paths to relevant files and defaults + ########################################################################### + if !ENV["ONE_LOCATION"] + ETC_LOCATION = "/etc/one" + else + ETC_LOCATION = ENV["ONE_LOCATION"] + "/etc" + end + + LOGIN_PATH = ENV['HOME']+'/.one/one_x509' + + X509_AUTH_CONF_PATH = ETC_LOCATION + "/auth/x509_auth.conf" + + X509_DEFAULTS = { + :ca_dir => ETC_LOCATION + "/auth/certificates" + } + + ########################################################################### + # Initialize x509Auth object + # + # @param [Hash] default options for path + # @option options [String] :certs_pem + # cert chain array in colon-separated pem format + # @option options [String] :key_pem + # key in pem format + # @option options [String] :ca_dir + # directory of trusted CA's. Needed for auth method, not for login. + def initialize(options={}) + @options={ + :certs_pem => nil, + :key_pem => nil, + :ca_dir => X509_DEFAULTS[:ca_dir] + }.merge!(options) + + load_options(X509_AUTH_CONF_PATH) + + @cert_chain = @options[:certs_pem].collect do |cert_pem| + OpenSSL::X509::Certificate.new(cert_pem) + end + + if @options[:key_pem] + @key = OpenSSL::PKey::RSA.new(@options[:key_pem]) + end + end + + ########################################################################### + # Client side + ########################################################################### + + # Creates the login file for x509 authentication at ~/.one/one_x509. + # By default it is valid as long as the certificate is valid. It can + # be changed to any number of seconds with expire parameter (sec.) + def login(user, expire=0) + write_login(login_token(user,expire)) + end + + # Returns the dn of the user certificate + def dn + @cert_chain[0].subject.to_s + end + + # Generates a login token in the form: + # user_name:x509:user_name:time_expires:cert_chain + # - user_name:time_expires is encrypted with the user certificate + # - user_name:time_expires:cert_chain is base64 encoded + def login_token(user, expire) + if expire != 0 + expires = Time.now.to_i + expire.to_i + else + expires = @cert_chain[0].not_after.to_i + end + + text_to_sign = "#{user}:#{expires}" + signed_text = encrypt(text_to_sign) + + certs_pem = @cert_chain.collect{|cert| cert.to_pem}.join(":") + + token = "#{signed_text}:#{certs_pem}" + token64 = Base64::encode64(token).strip.delete("\n") + + login_out = "#{user}:x509:#{token64}" + + login_out + end + + ########################################################################### + # Server side + ########################################################################### + # auth method for auth_mad + def authenticate(user, pass, signed_text) + begin + # Decryption demonstrates that the user posessed the private key. + _user, expires = decrypt(signed_text).split(':') + + return "User name missmatch" if user != _user + + return "x509 proxy expired" if Time.now.to_i >= expires.to_i + + # Some DN in the chain must match a DN in the password + dn_ok = @cert_chain.each do |cert| + break true if pass.split('|').include?(cert.subject.to_s.delete("\s")) + end + + unless dn_ok == true + return "Certificate subject missmatch" + end + + validate + + return true + rescue => e + return e.message + end + end + +private + # Writes a login_txt to the login file as defined in LOGIN_PATH + # constant + def write_login(login_txt) + # Inits login file path and creates ~/.one directory if needed + # Set instance variables + login_dir = File.dirname(LOGIN_PATH) + + begin + FileUtils.mkdir_p(login_dir) + rescue Errno::EEXIST + end + + file = File.open(LOGIN_PATH, "w") + file.write(login_txt) + file.close + end + + # Load class options form a configuration file (yaml syntax) + def load_options(conf_file) + if File.readable?(conf_file) + config = File.read(conf_file) + + @options.merge!(YAML::load(config)) + end + end + + ########################################################################### + # Methods to encrpyt/decrypt keys + ########################################################################### + # Encrypts data with the private key of the user and returns + # base 64 encoded output in a single line + def encrypt(data) + return nil if !@key + Base64::encode64(@key.private_encrypt(data)).delete("\n").strip + end + + # Decrypts base 64 encoded data with pub_key (public key) + def decrypt(data) + @cert_chain[0].public_key.public_decrypt(Base64::decode64(data)) + end + + ########################################################################### + # Validate the user certificate + ########################################################################### + def validate + now = Time.now + failed = "Could not validate user credentials: " + + # Check start time and end time of certificates + @cert_chain.each do |cert| + if cert.not_before > now || cert.not_after < now + raise failed + "Certificate not valid. Current time is " + + now.localtime.to_s + "." + end + end + + begin + # Validate the proxy certifcates + signee = @cert_chain[0] + + @cert_chain[1..-1].each do |cert| + if !((signee.issuer.to_s == cert.subject.to_s) && + (signee.verify(cert.public_key))) + raise failed + signee.subject.to_s + " with issuer " + + signee.issuer.to_s + " was not verified by " + + cert.subject.to_s + "." + end + signee = cert + end + + # Validate the End Entity certificate + if !@options[:ca_dir] + raise failed + "No certifcate authority directory was specified." + end + + begin + ca_hash = signee.issuer.hash.to_s(16) + ca_path = @options[:ca_dir] + '/' + ca_hash + '.0' + + ca_cert = OpenSSL::X509::Certificate.new(File.read(ca_path)) + + if !((signee.issuer.to_s == ca_cert.subject.to_s) && + (signee.verify(ca_cert.public_key))) + raise failed + signee.subject.to_s + " with issuer " + + signee.issuer.to_s + " was not verified by " + + ca_cert.subject.to_s + "." + end + + signee = ca_cert + end while ca_cert.subject.to_s != ca_cert.issuer.to_s + rescue + raise + end + end +end diff --git a/src/authm_mad/simple_permissions.rb b/src/authm_mad/simple_permissions.rb deleted file mode 100644 index 084073b6c5..0000000000 --- a/src/authm_mad/simple_permissions.rb +++ /dev/null @@ -1,112 +0,0 @@ -# -------------------------------------------------------------------------- # -# Copyright 2002-2011, OpenNebula Project Leads (OpenNebula.org) # -# # -# Licensed under the Apache License, Version 2.0 (the "License"); you may # -# not use this file except in compliance with the License. You may obtain # -# a copy of the License at # -# # -# http://www.apache.org/licenses/LICENSE-2.0 # -# # -# Unless required by applicable law or agreed to in writing, software # -# distributed under the License is distributed on an "AS IS" BASIS, # -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # -# See the License for the specific language governing permissions and # -# limitations under the License. # -#--------------------------------------------------------------------------- # - -require 'quota' -require 'base64' - -class SimplePermissions - - def initialize(database, client, conf={}) - @quota=Quota.new(database, client, conf[:quota] || {}) - @quota_enabled=conf[:quota][:enabled] - end - - # Returns message if result is false, true otherwise - def auth_message(result, message) - result ? true : message - end - - # Extracts cpu and memory resources from the VM template sent in - # authorization message - def get_vm_usage(data) - vm_xml=Base64::decode64(data) - vm=OpenNebula::VirtualMachine.new( - OpenNebula::XMLElement.build_xml(vm_xml, 'TEMPLATE'), - OpenNebula::Client.new) - - # Should set more sensible defaults or get driver configuration - cpu=vm['CPU'] - cpu||=1.0 - cpu=cpu.to_f - - memory=vm['MEMORY'] - memory||=64 - memory=memory.to_f - - VmUsage.new(cpu, memory) - end - - # Checks if the quota is enabled, and if it is not exceeded - def check_quota_enabled(uid, object, id, auth_result) - if @quota_enabled and object=='VM' and auth_result - STDERR.puts 'quota enabled' - @quota.update(uid.to_i) - if message=@quota.check(uid.to_i, get_vm_usage(id)) - auth_result=message - end - end - - return auth_result - end - - # Method called by authorization driver - def auth(uid, tokens) - result=true - - tokens.each do |token| - object, id, action, owner, pub=token.split(':') - result=auth_object(uid.to_s, object, id, action, owner, pub) - break result if result!=true - end - - result - end - - # Authorizes each of the tokens. All parameters are strings. Pub - # means public when "1" and private when "0" - def auth_object(uid, object, id, action, owner, pub) - return true if uid=='0' - - auth_result=false - - case action - when 'CREATE' - auth_result=true if %w{VM NET IMAGE TEMPLATE}.include? object - auth_result = check_quota_enabled(uid, object, id, auth_result) - - when 'INSTANTIATE' - auth_result = true if %w{VM}.include? object - auth_result = check_quota_enabled(uid, object, id, auth_result) - - when 'DELETE' - auth_result = (owner == uid) - - when 'USE' - if %w{VM NET IMAGE TEMPLATE}.include? object - auth_result = ((owner == uid) | (pub=='1')) - elsif object == 'HOST' - auth_result=true - end - - when 'MANAGE' - auth_result = (owner == uid) - - when 'INFO' - end - - return auth_result - end -end diff --git a/src/authm_mad/ssh_auth.rb b/src/authm_mad/ssh_auth.rb deleted file mode 100644 index d7ae2e3110..0000000000 --- a/src/authm_mad/ssh_auth.rb +++ /dev/null @@ -1,114 +0,0 @@ -# -------------------------------------------------------------------------- # -# Copyright 2002-2011, OpenNebula Project Leads (OpenNebula.org) # -# # -# Licensed under the Apache License, Version 2.0 (the "License"); you may # -# not use this file except in compliance with the License. You may obtain # -# a copy of the License at # -# # -# http://www.apache.org/licenses/LICENSE-2.0 # -# # -# Unless required by applicable law or agreed to in writing, software # -# distributed under the License is distributed on an "AS IS" BASIS, # -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # -# See the License for the specific language governing permissions and # -# limitations under the License. # -#--------------------------------------------------------------------------- # - - -require 'pp' -require 'openssl' -require 'base64' -require 'fileutils' - -# SSH key authentication class. It can be used as a driver for auth_mad -# as auth method is defined. It also holds some helper methods to be used -# by oneauth command -class SshAuth - # Reads id_rsa file from user .ssh directory - def get_priv_key - path=ENV['HOME']+'/.ssh/id_rsa' - File.read(path) - end - - # Returns an opened file object to ~/.one/one_ssh - def get_proxy_file - proxy_dir=ENV['HOME']+'/.one' - - # Creates ~/.one directory if it does not exist - begin - FileUtils.mkdir_p(proxy_dir) - rescue Errno::EEXIST - end - - File.open(proxy_dir+'/one_ssh', "w") - end - - # Encrypts data with the private key of the user and returns - # base 64 encoded output - def encrypt(data) - rsa=OpenSSL::PKey::RSA.new(get_priv_key) - # base 64 output is joined into a single line as opennebula - # ascii protocol ends messages with newline - Base64::encode64(rsa.private_encrypt(data)).gsub!(/\n/, '').strip - end - - # Decrypts base 64 encoded data with pub_key (public key) - def decrypt(data, pub_key) - rsa=OpenSSL::PKey::RSA.new(Base64::decode64(pub_key)) - rsa.public_decrypt(Base64::decode64(data)) - end - - # Gets public key from user's private key file. ssh private keys are - # stored in a format not compatible with openssl. This method - # will be used by oneauth so users can extrar their public keys - # that the administrator then can add to the database - def extract_public_key - key=OpenSSL::PKey::RSA.new(get_priv_key) - public_key=key.public_key.to_pem.split("\n") - # gets rid of "---- BEGIN/END RSA PUBLIC KEY ----" lines and - # joins resuklt into a single line - public_key.reject {|l| l.match(/RSA PUBLIC KEY/) }.join('') - end - - # Creates the login file for ssh authentication at ~/.one/one_ssh. - # By default it is valid for 1 hour but it can be changed to any number - # of seconds with expire parameter (in seconds) - def login(user, expire=3600) - time=Time.now.to_i+expire - proxy_text="#{user}:#{time}" - proxy_crypted=encrypt(proxy_text) - proxy="#{user}:plain:#{proxy_crypted}" - file=get_proxy_file - file.write(proxy) - file.close - - # Help string - puts "export ONE_AUTH=#{ENV['HOME']}/.one/one_ssh" - - proxy_crypted - end - - # auth method for auth_mad - def auth(user_id, user, password, token) - begin - decrypted=decrypt(token, password) - - username, time=decrypted.split(':') - - pp [username, time] - - if user==username - if Time.now.to_i>=time.to_i - "proxy expired, login again" - else - true - end - else - "invalid credentials" - end - rescue - "error" - end - end - -end diff --git a/src/cli/command_parser.rb b/src/cli/command_parser.rb index ef2c8da8d2..3878d5630e 100755 --- a/src/cli/command_parser.rb +++ b/src/cli/command_parser.rb @@ -166,6 +166,7 @@ EOT extra_options = comm[:options] if comm parse(extra_options) + if comm check_args!(comm_name, comm[:arity], comm[:args_format]) @@ -209,15 +210,23 @@ EOT next else shown_opts << o[:name] - short = o[:short].split(' ').first - printf opt_format, "#{short}, #{o[:large]}", o[:description] + + str = "" + str << o[:short].split(' ').first << ', ' if o[:short] + str << o[:large] + + printf opt_format, str, o[:description] puts end } } @opts.each{ |o| - printf opt_format, "#{o[:short]}, #{o[:large]}", o[:description] + str = "" + str << o[:short] if o[:short] + str << o[:large] + + printf opt_format, str, o[:description] puts } end @@ -292,7 +301,13 @@ EOT merge = @opts merge = @opts + extra_options if extra_options merge.flatten.each do |e| - opts.on(e[:short],e[:large], e[:format],e[:description]) do |o| + args = [] + args << e[:short] if e[:short] + args << e[:large] + args << e[:format] + args << e[:description] + + opts.on(*args) do |o| if e[:proc] e[:proc].call(o, @options) elsif e[:name]=="help" diff --git a/src/cli/one_helper/oneuser_helper.rb b/src/cli/one_helper/oneuser_helper.rb index f98ad75e73..b481f9291b 100644 --- a/src/cli/one_helper/oneuser_helper.rb +++ b/src/cli/one_helper/oneuser_helper.rb @@ -47,6 +47,92 @@ class OneUserHelper < OpenNebulaHelper::OneHelper return 0, password end + def password(options) + if options[:ssh] + require 'ssh_auth' + + options[:key] ||= ENV['HOME']+'/.ssh/id_rsa' + + begin + sshauth = SshAuth.new(:private_key=>options[:key]) + rescue Exception => e + return -1, e.message + end + + return 0, sshauth.public_key + elsif options[:x509] + require 'x509_auth' + + options[:cert] ||= ENV['X509_USER_CERT'] + + begin + cert = [File.read(options[:cert])] + x509auth = X509Auth.new(:certs_pem=>cert) + rescue Exception => e + return -1, e.message + end + + return 0, x509auth.dn + else + return -1, "You have to specify an Auth method or define a password" + end + end + + def login(username, options) + if options[:ssh] + require 'ssh_auth' + + options[:key] ||= ENV['HOME']+'/.ssh/id_rsa' + + begin + auth = SshAuth.new(:private_key=>options[:key]) + rescue Exception => e + return -1, e.message + end + elsif options[:x509] + require 'x509_auth' + + options[:cert] ||= ENV['X509_USER_CERT'] + options[:key] ||= ENV['X509_USER_KEY'] + + begin + certs = [File.read(options[:cert])] + key = File.read(options[:key]) + + auth = X509Auth.new(:certs_pem=>certs, :key_pem=>key) + rescue Exception => e + return -1, e.message + end + elsif options[:x509_proxy] + require 'x509_auth' + + options[:proxy] ||= ENV['X509_PROXY_CERT'] + + begin + proxy = File.read(options[:proxy]) + + rc = proxy.scan(/(-+BEGIN CERTIFICATE-+\n[^-]*\n-+END CERTIFICATE-+)/) + certs = rc.flatten! + + rc = proxy.match(/(-+BEGIN RSA PRIVATE KEY-+\n[^-]*\n-+END RSA PRIVATE KEY-+)/) + + key = rc[1] + + auth = X509Auth.new(:certs_pem=>certs, :key_pem=>key) + rescue => e + return -1, e.message + end + else + return -1, "You have to specify an Auth method" + end + + options[:time] ||= 3600 + + auth.login(username, options[:time]) + + return 0, 'export ONE_AUTH=' << auth.class::LOGIN_PATH + end + private def factory(id=nil) diff --git a/src/cli/oneuser b/src/cli/oneuser index d1ee7fcfe1..a740411d0c 100755 --- a/src/cli/oneuser +++ b/src/cli/oneuser @@ -59,7 +59,56 @@ cmd=CommandParser::CmdParser.new(ARGV) do :description => "Store plain password" } - create_options = [READ_FILE, PLAIN] + SSH={ + :name => "ssh", + :large => "--ssh", + :description => "SSH Auth system" + } + + X509={ + :name => "x509", + :large => "--x509", + :description => "x509 Auth system for x509 certificates" + } + + X509_PROXY={ + :name => "x509_proxy", + :large => "--x509_proxy", + :description => "x509 Auth system based on x509 proxy certificates" + } + + KEY={ + :name => "key", + :short => "-k path_to_private_key_pem", + :large => "--key path_to_private_key_pem", + :format => String, + :description => "Path to the Private Key of the User" + } + + CERT={ + :name => "cert", + :short => "-c path_to_user_cert_pem", + :large => "--cert path_to_user_cert_pem", + :format => String, + :description => "Path to the Certificate of the User" + } + + PROXY={ + :name => "proxy", + :large => "--proxy path_to_user_proxy_pem", + :format => String, + :description => "Path to the user proxy certificate" + } + + TIME={ + :name => "time", + :large => "--time x", + :format => Integer, + :description => "Token duration in hours, (default 1)" + } + + create_options = [READ_FILE, PLAIN, SSH, X509, KEY, CERT] + login_options = [SSH, X509, X509_PROXY, KEY, CERT, PROXY, TIME] ######################################################################## # Formatters for arguments @@ -86,13 +135,44 @@ cmd=CommandParser::CmdParser.new(ARGV) do create_desc = <<-EOT.unindent Creates a new User + Examples: + oneuser create my_user my_password + oneuser create my_user /tmp/mypass -r + oneuser create my_user --ssh --key /tmp/id_rsa + oneuser create my_user --x509 --cert /tmp/my_cert.pem EOT - command :create, create_desc, :username, :password, + command :create, create_desc, :username, [:password, nil], :options=>create_options do - helper.create_resource(options) do |user| - user.allocate(args[0], args[1]) + if options[:ssh] or options[:x509] + rc = helper.password(options) + if rc.first == 0 + pass = rc[1] + else + exit_with_code *rc + end + else + pass = args[1] end + + helper.create_resource(options) do |user| + user.allocate(args[0], pass) + end + end + + login_desc = <<-EOT.unindent + Creates the Login token for authentication + Examples: + oneuser login my_user --ssh --key /tmp/id_rsa --time 72000 + oneuser login my_user --x509 --cert /tmp/my_cert.pem \ + --key /tmp/my_key.pk --time 72000 + oneuser login my_user --x509_proxy --proxy /tmp/my_cert.pem \ + --time 72000 + EOT + + command :login, login_desc, :username, [:password, nil], + :options=>login_options do + helper.login(args[0], options) end delete_desc = <<-EOT.unindent @@ -109,9 +189,21 @@ cmd=CommandParser::CmdParser.new(ARGV) do Changes the given User's password EOT - command :passwd, passwd_desc, :userid, :password do + command :passwd, passwd_desc, :userid, :password, + :options=>create_options do + if options[:ssh] or options[:x509] + rc = helper.password(options) + if rc.first == 0 + pass = rc[1] + else + exit_with_code *rc + end + else + pass = args[1] + end + helper.perform_action(args[0],options,"Password changed") do |user| - user.passwd(args[1]) + user.passwd(pass) end end diff --git a/src/mad/ruby/OpenNebulaDriver.rb b/src/mad/ruby/OpenNebulaDriver.rb index 7b32ad705a..6129795d3a 100644 --- a/src/mad/ruby/OpenNebulaDriver.rb +++ b/src/mad/ruby/OpenNebulaDriver.rb @@ -90,20 +90,22 @@ class OpenNebulaDriver < ActionManager def initialize(directory, options={}) @options={ :concurrency => 10, - :threaded => true, - :retries => 0, + :threaded => true, + :retries => 0, :local_actions => {} }.merge!(options) super(@options[:concurrency], @options[:threaded]) - @retries = @options[:retries] - @send_mutex=Mutex.new - @local_actions=@options[:local_actions] + @retries = @options[:retries] + @local_actions = @options[:local_actions] + + @send_mutex = Mutex.new # set default values @config = read_configuration - @remote_scripts_base_path=@config['SCRIPTS_REMOTE_DIR'] + @remote_scripts_base_path = @config['SCRIPTS_REMOTE_DIR'] + if ENV['ONE_LOCATION'] == nil @local_scripts_base_path = "/var/lib/one/remotes" else @@ -111,8 +113,8 @@ class OpenNebulaDriver < ActionManager end # dummy paths - @remote_scripts_path=File.join(@remote_scripts_base_path, directory) - @local_scripts_path=File.join(@local_scripts_base_path, directory) + @remote_scripts_path = File.join(@remote_scripts_base_path, directory) + @local_scripts_path = File.join(@local_scripts_base_path, directory) register_action(:INIT, method("init")) end diff --git a/src/oca/ruby/OpenNebula.rb b/src/oca/ruby/OpenNebula.rb index c536eefe7a..7303999ce9 100644 --- a/src/oca/ruby/OpenNebula.rb +++ b/src/oca/ruby/OpenNebula.rb @@ -96,19 +96,14 @@ module OpenNebula raise "ONE_AUTH file not present" end - if !one_secret.match(".+:.+") - raise "Authorization file malformed" - end + tokens = one_secret.chomp.split(':') - - one_secret=~/^(.+?):(.+)$/ - user=$1 - password=$2 - - if password.match(/^plain:/) - @one_auth = "#{user}:#{password.split(':').last}" + if tokens.length > 2 + @one_auth = one_secret + elsif tokens.length == 2 + @one_auth = "#{tokens[0]}:#{Digest::SHA1.hexdigest(tokens[1])}" else - @one_auth = "#{user}:#{Digest::SHA1.hexdigest(password)}" + raise "Authorization file malformed" end if endpoint diff --git a/src/oca/ruby/OpenNebula/XMLUtils.rb b/src/oca/ruby/OpenNebula/XMLUtils.rb index 912c2e4561..c71dece114 100644 --- a/src/oca/ruby/OpenNebula/XMLUtils.rb +++ b/src/oca/ruby/OpenNebula/XMLUtils.rb @@ -101,11 +101,11 @@ module OpenNebula element.text end end - - # Gets an array of text from elemenets extracted + + # Gets an array of text from elemenets extracted # using the XPATH expression passed as filter def retrieve_elements(filter) - elements_array = Array.new + elements_array = Array.new if NOKOGIRI @xml.xpath(filter.to_s).each { |pelem| @@ -116,13 +116,13 @@ module OpenNebula elements_array << pelem.text if pelem.text } end - - if elements_array.size == 0 + + if elements_array.size == 0 return nil else return elements_array end - + end # Gets an attribute from an elemenT @@ -164,6 +164,18 @@ module OpenNebula end end + def each_xpath(xpath_str,&block) + if NOKOGIRI + @xml.xpath(xpath_str).each { |pelem| + block.call pelem.text + } + else + @xml.elements.each(xpath_str) { |pelem| + block.call pelem.text + } + end + end + def name @xml.name end @@ -285,7 +297,7 @@ module OpenNebula hash end - + private def attr_to_str(attr) attr.gsub!('"',"\\\"") diff --git a/src/um/UserPool.cc b/src/um/UserPool.cc index 746e898346..60933f7a45 100644 --- a/src/um/UserPool.cc +++ b/src/um/UserPool.cc @@ -253,20 +253,7 @@ bool UserPool::authenticate(const string& session, ar.add_authenticate(username,u_pass,secret); - if ( uid == 0 ) //oneadmin - { - if (ar.plain_authenticate()) - { - user_id = uid; - group_id = gid; - - uname = tuname; - gname = tgname; - - result = true; - } - } - else if (authm == 0) //plain auth + if (authm == 0) //plain auth { if ( user != 0 && ar.plain_authenticate()) //no plain for external users {