From e7e96a9fab5984bab8102ed553959f32b1a66c8a Mon Sep 17 00:00:00 2001 From: Daniel Molina Date: Tue, 23 Aug 2011 16:22:04 +0200 Subject: [PATCH 1/4] feature #754: Change initialize parameters for SshAuth --- src/authm_mad/remotes/ssh/authenticate | 8 ++++-- src/authm_mad/remotes/ssh/ssh_auth.rb | 35 +++++++++++++++++++------- 2 files changed, 32 insertions(+), 11 deletions(-) diff --git a/src/authm_mad/remotes/ssh/authenticate b/src/authm_mad/remotes/ssh/authenticate index 0892917738..517d9033d2 100755 --- a/src/authm_mad/remotes/ssh/authenticate +++ b/src/authm_mad/remotes/ssh/authenticate @@ -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) diff --git a/src/authm_mad/remotes/ssh/ssh_auth.rb b/src/authm_mad/remotes/ssh/ssh_auth.rb index f6f9f8f704..85d38f313e 100644 --- a/src/authm_mad/remotes/ssh/ssh_auth.rb +++ b/src/authm_mad/remotes/ssh/ssh_auth.rb @@ -25,21 +25,38 @@ require 'fileutils' # by oneauth command class SshAuth 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 From f0e959c77e108d2a7b541fe76f94585b037507c6 Mon Sep 17 00:00:00 2001 From: Daniel Molina Date: Tue, 23 Aug 2011 16:24:03 +0200 Subject: [PATCH 2/4] feature #754: Add PROXY_PATH constant --- src/authm_mad/remotes/ssh/ssh_auth.rb | 28 ++++--- src/authm_mad/remotes/x509/x509_auth.rb | 56 +++++++------- .../remotes/x509_proxy/x509_proxy_auth.rb | 77 +++++++++---------- 3 files changed, 78 insertions(+), 83 deletions(-) diff --git a/src/authm_mad/remotes/ssh/ssh_auth.rb b/src/authm_mad/remotes/ssh/ssh_auth.rb index 85d38f313e..a0d2915be9 100644 --- a/src/authm_mad/remotes/ssh/ssh_auth.rb +++ b/src/authm_mad/remotes/ssh/ssh_auth.rb @@ -24,6 +24,8 @@ 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 # Initialize SshAuth object @@ -64,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 @@ -99,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" @@ -114,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 diff --git a/src/authm_mad/remotes/x509/x509_auth.rb b/src/authm_mad/remotes/x509/x509_auth.rb index 7b067f151f..7e04c00566 100644 --- a/src/authm_mad/remotes/x509/x509_auth.rb +++ b/src/authm_mad/remotes/x509/x509_auth.rb @@ -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 # @@ -42,7 +45,7 @@ class X509Auth if @options[:key] @key = OpenSSL::PKey::RSA.new(@options[:key]) - end + end end ########################################################################### @@ -53,50 +56,45 @@ class X509Auth # 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 # 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' #Create the x509 proxy time = Time.now.to_i+expire - + text_to_sign = "#{user}:#{@dn}:#{time}" signed_text = encrypt(text_to_sign) - token = "#{signed_text}:#{@cert.to_pem}" + token = "#{signed_text}:#{@cert.to_pem}" token64 = Base64::encode64(token).strip.delete!("\n") proxy="#{user}:x509:#{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_x509" - + 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) @@ -112,22 +110,22 @@ 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 end # Decrypts base 64 encoded data with pub_key (public key) - def decrypt(data) + def decrypt(data) @cert.public_key.public_decrypt(Base64::decode64(data)) - end + end ########################################################################### # Validate the user certificate @@ -138,10 +136,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 @@ -149,24 +147,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 diff --git a/src/authm_mad/remotes/x509_proxy/x509_proxy_auth.rb b/src/authm_mad/remotes/x509_proxy/x509_proxy_auth.rb index 8653788588..2ed8b386b4 100644 --- a/src/authm_mad/remotes/x509_proxy/x509_proxy_auth.rb +++ b/src/authm_mad/remotes/x509_proxy/x509_proxy_auth.rb @@ -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 From 4ae6c2d8f04c98b89dc1ac3cd96079ea0b0aa39b Mon Sep 17 00:00:00 2001 From: Daniel Molina Date: Tue, 23 Aug 2011 16:25:11 +0200 Subject: [PATCH 3/4] feature #754: Options can be defined without short value --- src/cli/command_parser.rb | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/cli/command_parser.rb b/src/cli/command_parser.rb index 357d1082ab..ef59846023 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 From 376965200610cc2d5eed1dd51c196fa6707e8de2 Mon Sep 17 00:00:00 2001 From: Daniel Molina Date: Tue, 23 Aug 2011 16:29:38 +0200 Subject: [PATCH 4/4] feature #754: Add CLI functionality for ssh and x509 --- src/cli/one_helper/oneuser_helper.rb | 61 +++++++++++++++++++++++ src/cli/oneuser | 72 ++++++++++++++++++++++++++-- 2 files changed, 129 insertions(+), 4 deletions(-) diff --git a/src/cli/one_helper/oneuser_helper.rb b/src/cli/one_helper/oneuser_helper.rb index 24f2fd87e1..05255add83 100644 --- a/src/cli/one_helper/oneuser_helper.rb +++ b/src/cli/one_helper/oneuser_helper.rb @@ -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) diff --git a/src/cli/oneuser b/src/cli/oneuser index ffb4346786..2273562ed7 100755 --- a/src/cli/oneuser +++ b/src/cli/oneuser @@ -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