mirror of
https://github.com/OpenNebula/one.git
synced 2025-03-22 18:50:08 +03:00
Merge branch 'feature-754' of git://git.opennebula.org/one into x509-2
Conflicts: src/authm_mad/remotes/x509/x509_auth.rb
This commit is contained in:
commit
fd9813477e
@ -36,8 +36,12 @@ pass = ARGV[1]
|
||||
secret = ARGV[2]
|
||||
|
||||
#OpenNebula.log_debug("Authenticating #{user}, with password #{pass} (#{secret})")
|
||||
|
||||
ssh_auth = SshAuth.new(pass)
|
||||
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)
|
||||
|
||||
|
@ -24,22 +24,41 @@ require 'fileutils'
|
||||
# as auth method is defined. It also holds some helper methods to be used
|
||||
# by oneauth command
|
||||
class SshAuth
|
||||
PROXY_PATH = ENV['HOME']+'/.one/one_ssh'
|
||||
|
||||
attr_reader :public_key
|
||||
|
||||
def initialize(pub_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
|
||||
@private_key = File.read(ENV['HOME']+'/.ssh/id_rsa')
|
||||
# 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 pub_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('')
|
||||
else
|
||||
@public_key = pub_key
|
||||
end
|
||||
|
||||
if @private_key.nil? && @public_key.nil?
|
||||
raise "You have to define at least one of the keys"
|
||||
end
|
||||
end
|
||||
|
||||
@ -47,33 +66,28 @@ class SshAuth
|
||||
# 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=ENV['HOME']+'/.one'
|
||||
|
||||
proxy_dir = File.dirname(PROXY_PATH)
|
||||
|
||||
begin
|
||||
FileUtils.mkdir_p(proxy_dir)
|
||||
rescue Errno::EEXIST
|
||||
end
|
||||
|
||||
proxy_path = proxy_dir + '/one_ssh'
|
||||
|
||||
# Generate security token
|
||||
time = Time.now.to_i + expire
|
||||
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(proxy_path, "w")
|
||||
|
||||
file = File.open(PROXY_PATH, "w")
|
||||
file.write(proxy)
|
||||
|
||||
file.close
|
||||
|
||||
# Help string
|
||||
puts "export ONE_AUTH=#{ENV['HOME']}/.one/one_ssh"
|
||||
|
||||
|
||||
secret_crypted
|
||||
end
|
||||
|
||||
@ -82,7 +96,7 @@ class SshAuth
|
||||
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"
|
||||
@ -97,12 +111,13 @@ class SshAuth
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
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
|
||||
# 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
|
||||
|
@ -22,6 +22,9 @@ require 'fileutils'
|
||||
# as auth method is defined. It also holds some helper methods to be used
|
||||
# by oneauth command
|
||||
class X509Auth
|
||||
PROXY_PATH = ENV['HOME']+'/.one/one_x509'
|
||||
|
||||
attr_reader :dn
|
||||
|
||||
# Initialize x509Auth object
|
||||
#
|
||||
@ -92,16 +95,17 @@ class X509Auth
|
||||
token64
|
||||
end
|
||||
|
||||
|
||||
###########################################################################
|
||||
# Server side
|
||||
###########################################################################
|
||||
# auth method for auth_mad
|
||||
def authenticate(user, pass, token)
|
||||
def authenticate(user, pass, token)
|
||||
begin
|
||||
validate
|
||||
|
||||
plain = decrypt(token)
|
||||
|
||||
|
||||
_user, subject, time_expire = plain.split(':')
|
||||
|
||||
if (user != _user)
|
||||
@ -117,13 +121,13 @@ class X509Auth
|
||||
return e.message
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
###########################################################################
|
||||
# Methods to encrpyt/decrypt keys
|
||||
###########################################################################
|
||||
# Encrypts data with the private key of the user and returns
|
||||
# base 64 encoded output in a single line
|
||||
# base 64 encoded output in a single line
|
||||
def encrypt(data)
|
||||
return nil if !@key
|
||||
Base64::encode64(@key.private_encrypt(data)).delete!("\n").strip
|
||||
@ -143,10 +147,10 @@ private
|
||||
|
||||
# Check start time and end time of certificate
|
||||
if @cert.not_before > now || @cert.not_after < now
|
||||
raise failed + "Certificate not valid. Current time is " +
|
||||
raise failed + "Certificate not valid. Current time is " +
|
||||
now.localtime.to_s + "."
|
||||
end
|
||||
|
||||
|
||||
# Check the rest of the certificate chain if specified
|
||||
if !@options[:ca_dir]
|
||||
return
|
||||
@ -154,24 +158,24 @@ private
|
||||
|
||||
begin
|
||||
signee = @cert
|
||||
|
||||
|
||||
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) &&
|
||||
|
||||
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 " +
|
||||
raise failed + signee.subject.to_s + " with issuer " +
|
||||
signee.issuer.to_s + " was not verified by " +
|
||||
ca.subject.to_s + "."
|
||||
end
|
||||
|
||||
signee = ca_cert
|
||||
end while ca_cert.subject.to_s != ca_cert.issuer.to_s
|
||||
rescue
|
||||
raise
|
||||
raise
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -18,19 +18,20 @@ require 'openssl'
|
||||
require 'base64'
|
||||
require 'fileutils'
|
||||
|
||||
# Authentication class based on x509 proxy certificate.
|
||||
# Authentication class based on x509 proxy certificate.
|
||||
class X509ProxyAuth
|
||||
PROXY_PATH = ENV['HOME']+'/.one/one_x509_proxy'
|
||||
|
||||
# Initialize x509ProxyAuth object
|
||||
#
|
||||
# @param [Hash] default options for path
|
||||
# @option options [String] :proxy ($X509_PROXY_CERT)
|
||||
# @option options [String] :proxy ($X509_PROXY_CERT)
|
||||
# proxy cert for the user
|
||||
# @option options [String] :proxy_cert (nil)
|
||||
# @option options [String] :proxy_cert (nil)
|
||||
# public cert of a user proxy
|
||||
# @option options [String] :user_cert (nil)
|
||||
# @option options [String] :user_cert (nil)
|
||||
# user cert, used to generate the proxy
|
||||
# @option options [String] :ca_dir (/etc/grid-security/certificates)
|
||||
# @option options [String] :ca_dir (/etc/grid-security/certificates)
|
||||
# trusted CA directory. If nil it will not be used to verify
|
||||
# certificates
|
||||
def initialize(options={})
|
||||
@ -53,33 +54,33 @@ class X509ProxyAuth
|
||||
|
||||
proxy_cert_txt = rc[0]
|
||||
user_cert_txt = rc[1]
|
||||
|
||||
|
||||
rc = proxy.match(/-+BEGIN RSA PRIVATE KEY-+\n([^-]*)\n-+END RSA PRIVATE KEY-+/)
|
||||
|
||||
proxy_key_txt = rc[1]
|
||||
end
|
||||
|
||||
|
||||
if !proxy_cert_txt || !user_cert_txt
|
||||
raise "Can not get user or proxy certificates"
|
||||
end
|
||||
|
||||
|
||||
@proxy_cert = OpenSSL::X509::Certificate.new(proxy_cert_txt)
|
||||
@user_cert = OpenSSL::X509::Certificate.new(user_cert_txt)
|
||||
@dn = @user_cert.subject.to_s
|
||||
|
||||
|
||||
if proxy_ket_txt
|
||||
@poxy_key = OpenSSL::PKey::RSA.new(proxy_key_txt)
|
||||
end
|
||||
|
||||
# Load configuration file
|
||||
#@auth_conf_path = ETC_LOCATION+'/auth/auth.conf'
|
||||
|
||||
|
||||
#if File.readable?(@auth_conf_path)
|
||||
# config = File.read(@auth_conf_path)
|
||||
# config = YAML::load(config_data)
|
||||
|
||||
# @options.merge!(config)
|
||||
#end
|
||||
#end
|
||||
end
|
||||
|
||||
###########################################################################
|
||||
@ -90,47 +91,45 @@ class X509ProxyAuth
|
||||
def login(user)
|
||||
# Init proxy file path and creates ~/.one directory if needed
|
||||
# Set instance variables
|
||||
proxy_dir=ENV['HOME']+'/.one'
|
||||
|
||||
proxy_dir=File.dirname(PROXY_PATH)
|
||||
|
||||
begin
|
||||
FileUtils.mkdir_p(proxy_dir)
|
||||
rescue Errno::EEXIST
|
||||
end
|
||||
|
||||
one_proxy_path = proxy_dir + '/one_x509_proxy'
|
||||
|
||||
|
||||
#Generate token for authentication
|
||||
text_to_sign = "#{user}:#{@dn}"
|
||||
signed_text = encrypt(text_to_sign)
|
||||
|
||||
token = "#{signed_text}:#{@proxy_cert.to_pem}:#{@user_cert.to_pem}"
|
||||
token = "#{signed_text}:#{@proxy_cert.to_pem}:#{@user_cert.to_pem}"
|
||||
token64 = Base64::encode64(token).strip.delete!("\n")
|
||||
|
||||
proxy="#{user}:grid:#{token64}"
|
||||
|
||||
file = File.open(one_proxy_path, "w")
|
||||
file = File.open(PROXY_PATH, "w")
|
||||
|
||||
file.write(proxy)
|
||||
|
||||
|
||||
file.close
|
||||
|
||||
|
||||
# Help string
|
||||
puts "export ONE_AUTH=#{ENV['HOME']}/.one/one_grid"
|
||||
|
||||
puts "export ONE_AUTH=#{ENV['HOME']}/.one/one_x509_proxy"
|
||||
|
||||
token64
|
||||
end
|
||||
|
||||
|
||||
###########################################################################
|
||||
# Server side
|
||||
###########################################################################
|
||||
|
||||
# auth method for auth_mad
|
||||
def authenticate(user, pass, token)
|
||||
def authenticate(user, pass, token)
|
||||
begin
|
||||
validate_chain
|
||||
|
||||
plain = decrypt(token)
|
||||
|
||||
|
||||
_user, subject = plain.split(':')
|
||||
|
||||
if (user != _user)
|
||||
@ -149,14 +148,14 @@ private
|
||||
# Methods to encrpyt/decrypt keys
|
||||
###########################################################################
|
||||
# Encrypts data with the private key of the user and returns
|
||||
# base 64 encoded output in a single line
|
||||
# base 64 encoded output in a single line
|
||||
def encrypt(data)
|
||||
return nil if !@proxy_key
|
||||
Base64::encode64(@proxy_key.private_encrypt(data)).delete!("\n").strip
|
||||
end
|
||||
|
||||
# Decrypts base 64 encoded data with pub_key (public key)
|
||||
def decrypt(data)
|
||||
def decrypt(data)
|
||||
@proxy_cert.public_key.public_decrypt(Base64::decode64(data))
|
||||
end
|
||||
|
||||
@ -169,22 +168,22 @@ private
|
||||
|
||||
# Check start time and end time of proxy
|
||||
if @proxy_cert.not_before > now || @proxy_cert.not_after < now
|
||||
raise failed + "Certificate not valid. Current time is " +
|
||||
raise failed + "Certificate not valid. Current time is " +
|
||||
now.localtime.to_s + "."
|
||||
end
|
||||
|
||||
|
||||
# Check that the issuer of the proxy is the same user as in the user certificate
|
||||
if @proxy_cert.issuer.to_s != @user_cert.subject.to_s
|
||||
raise failed + "Proxy with issuer " + @proxy_cert.issuer.to_s +
|
||||
raise failed + "Proxy with issuer " + @proxy_cert.issuer.to_s +
|
||||
" does not match user " + @dn
|
||||
end
|
||||
|
||||
|
||||
# Check that the user signed the proxy
|
||||
if !@proxy_cert.verify(@user_cert.public_key)
|
||||
raise "Proxy with subject " + @proxy_cert.subject.to_s +
|
||||
raise "Proxy with subject " + @proxy_cert.subject.to_s +
|
||||
" was not verified by " + @dn + "."
|
||||
end
|
||||
|
||||
|
||||
# Check the rest of the certificate chain if specified
|
||||
if !@options[:ca_dir]
|
||||
return
|
||||
@ -192,24 +191,24 @@ private
|
||||
|
||||
begin
|
||||
signee = @user_cert
|
||||
|
||||
|
||||
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) &&
|
||||
|
||||
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 " +
|
||||
raise failed + signee.subject.to_s + " with issuer " +
|
||||
signee.issuer.to_s + " was not verified by " +
|
||||
ca.subject.to_s + "."
|
||||
end
|
||||
|
||||
signee = ca_cert
|
||||
end while ca_cert.subject.to_s != ca_cert.issuer.to_s
|
||||
rescue
|
||||
raise
|
||||
raise
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -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
|
||||
|
@ -47,6 +47,67 @@ 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
|
||||
x509auth = X509Auth.new(:cert=>options[: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
|
||||
auth = X509Auth.new(:cert=>options[:cert], :key=>options[:key])
|
||||
rescue Exception => e
|
||||
return -1, e.message
|
||||
end
|
||||
else
|
||||
return -1, "You have to specify an Auth method"
|
||||
end
|
||||
|
||||
auth.login(username, options[:time])
|
||||
|
||||
return 0, 'export ONE_AUTH=' << auth.class::PROXY_PATH
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def factory(id=nil)
|
||||
|
@ -59,7 +59,42 @@ 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"
|
||||
}
|
||||
|
||||
KEY={
|
||||
:name => "key",
|
||||
:short => "-k private_key",
|
||||
:large => "--key private_key",
|
||||
:format => String,
|
||||
:description => "Path to the Private Key of the User"
|
||||
}
|
||||
|
||||
CERT={
|
||||
:name => "cert",
|
||||
:large => "--cert s",
|
||||
:format => String,
|
||||
:description => "Path to the Certificate of the User"
|
||||
}
|
||||
|
||||
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, KEY, CERT, TIME]
|
||||
|
||||
########################################################################
|
||||
# Formatters for arguments
|
||||
@ -86,13 +121,42 @@ 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 args[1].nil?
|
||||
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
|
||||
EOT
|
||||
|
||||
command :login, login_desc, :username, [:password, nil],
|
||||
:options=>create_options do
|
||||
helper.login(args[0], options)
|
||||
end
|
||||
|
||||
delete_desc = <<-EOT.unindent
|
||||
|
Loading…
x
Reference in New Issue
Block a user