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

Merge branch 'master' of git.opennebula.org:one

This commit is contained in:
Jaime Melis 2011-08-31 18:33:34 +02:00
commit 6683dd64e8
34 changed files with 1174 additions and 741 deletions

View File

@ -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<auths.size(); i++)
if ( auths.empty() )
{
return string();
}
for (i=0; i<auths.size()-1; i++)
{
oss << auths[i] << " ";
}
oss << auths[i];
return oss.str();
};

View File

@ -71,9 +71,11 @@ private:
* "AUTHORIZE OPERATION_ID USER_ID REQUEST1 REQUEST2..."
* @param oid an id to identify the request.
* @param uid the user id.
* @param requests space separated list of requests in the form OP:OBJ:ID
* @param requests space separated list of requests in the form OP:OB:ID
* @param acl is the authorization result using the ACL engine for
* this request
*/
void authorize(int oid, int uid, const string& requests) const;
void authorize(int oid, int uid, const string& requests, bool acl) const;
/**
* Sends an authorization request to the MAD:

View File

@ -222,7 +222,13 @@ VAR_DIRS="$VAR_LOCATION/remotes \
$VAR_LOCATION/remotes/hooks \
$VAR_LOCATION/remotes/hooks/vnm \
$VAR_LOCATION/remotes/image \
$VAR_LOCATION/remotes/image/fs"
$VAR_LOCATION/remotes/image/fs \
$VAR_LOCATION/remotes/auth \
$VAR_LOCATION/remotes/auth/plain \
$VAR_LOCATION/remotes/auth/ssh \
$VAR_LOCATION/remotes/auth/x509 \
$VAR_LOCATION/remotes/auth/server \
$VAR_LOCATION/remotes/auth/dummy"
SUNSTONE_DIRS="$SUNSTONE_LOCATION/models \
$SUNSTONE_LOCATION/models/OpenNebulaJSON \
@ -314,6 +320,11 @@ INSTALL_FILES=(
IM_PROBES_KVM_FILES:$VAR_LOCATION/remotes/im/kvm.d
IM_PROBES_XEN_FILES:$VAR_LOCATION/remotes/im/xen.d
IM_PROBES_GANGLIA_FILES:$VAR_LOCATION/remotes/im/ganglia.d
AUTH_SSH_FILES:$VAR_LOCATION/remotes/auth/ssh
AUTH_X509_FILES:$VAR_LOCATION/remotes/auth/x509
AUTH_SERVER_FILES:$VAR_LOCATION/remotes/auth/server
AUTH_DUMMY_FILES:$VAR_LOCATION/remotes/auth/dummy
AUTH_PLAIN_FILES:$VAR_LOCATION/remotes/auth/plain
VMM_EXEC_KVM_SCRIPTS:$VAR_LOCATION/remotes/vmm/kvm
VMM_EXEC_XEN_SCRIPTS:$VAR_LOCATION/remotes/vmm/xen
VMM_EXEC_XEN_KVM_POLL:$VAR_LOCATION/remotes/vmm/kvm/poll
@ -455,8 +466,7 @@ BIN_FILES="src/nebula/oned \
src/cli/onetemplate \
src/cli/oneacl \
src/onedb/onedb \
share/scripts/one \
src/authm_mad/oneauth"
share/scripts/one"
#-------------------------------------------------------------------------------
# C/C++ OpenNebula API Library & Development files
@ -478,11 +488,9 @@ RUBY_LIB_FILES="src/mad/ruby/ActionManager.rb \
src/mad/ruby/Ganglia.rb \
src/oca/ruby/OpenNebula.rb \
src/tm_mad/TMScript.rb \
src/authm_mad/one_usage.rb \
src/authm_mad/quota.rb \
src/authm_mad/simple_auth.rb \
src/authm_mad/simple_permissions.rb \
src/authm_mad/ssh_auth.rb"
src/authm_mad/remotes/ssh/ssh_auth.rb \
src/authm_mad/remotes/server/server_auth.rb \
src/authm_mad/remotes/x509/x509_auth.rb"
#-----------------------------------------------------------------------------
# MAD Script library files, to be installed under $LIB_LOCATION/<script lang>
@ -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

View File

@ -96,7 +96,10 @@ void AuthRequest::add_auth(Object ob,
// User can show and MANAGE (change passwd) their own information
( uid == ob_id_int && ob == USER &&
( op == INFO || op == MANAGE )
)
) ||
// Users can show their group information
( ob == GROUP && gid == ob_id_int && op == INFO )
)
{
auth = true;
@ -285,7 +288,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 +304,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;
}
/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */

View File

@ -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 <<endl;
write(os);
}
void AuthManagerDriver::authenticate(int oid,
int uid,
const string& username,
const string& password,
const string& session) const
int uid,
const string& username,
const string& password,
const string& session) const
{
ostringstream os;

View File

@ -182,7 +182,7 @@ public:
string astr = "VM:VGhpcyBpcyBhIHRlbXBsYXRlCg==:CREATE:-1:0:0 "
"IMAGE:2:USE:3:0:0 "
"NET:4:DELETE:5:1:0 "
"HOST:6:MANAGE:7:1:0";
"HOST:6:MANAGE:7:1:0 0";
ar.add_auth(AuthRequest::VM,
"This is a template\n",
@ -214,7 +214,6 @@ public:
am->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);
}

View File

@ -1,8 +0,0 @@
:database: sqlite://auth.db
:authentication: simple
:quota:
:enabled: false
:defaults:
:cpu: 10.0
:memory: 1048576
:num_vms: 10

View File

@ -1 +0,0 @@

View File

@ -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/<protocol>/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:<TEMPLATE_64|OBJECT_ID>: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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -92,8 +92,9 @@ cmd=CommandParser::CmdParser.new(ARGV) do
Shows information for the given Group
EOT
command :show, show_desc, :groupid, :options=>OpenNebulaHelper::XML do
helper.show_resource(args[0],options)
command :show, show_desc,[:groupid, nil], :options=>OpenNebulaHelper::XML do
group = args[0] || OpenNebula::Group::SELF
helper.show_resource(group,options)
end
end

View File

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

View File

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

View File

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

View File

@ -27,6 +27,9 @@ module OpenNebula
:delete => "group.delete"
}
# Flag for requesting connected user's group info
SELF = -1
# Creates a Group description with just its identifier
# this method should be used to create plain Group objects.
# +id+ the id of the user

View File

@ -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!('"',"\\\"")

View File

@ -77,6 +77,14 @@ int UserDelete::drop(int oid, PoolObjectSQL * object, string& error_msg)
User * user = static_cast<User *>(object);
int group_id = user->get_gid();
if (oid == 0)
{
error_msg = "oneadmin can not be deleted.";
object->unlock();
return -1;
}
int rc = pool->drop(object, error_msg);
object->unlock();

View File

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