Authentication problems using auth method 'remote' and driver 'public'

I am trying to use remote authentication using a reverse proxy that authenticates users and set X-AUTH-USERNAME header.

I am already quite far with the setup, but there seems to be somekind of a ‘deep’ internal issue.


Current setup

Opennebula 6.6
Sunstone (no firedge)
Debian 11

Steps to reproduce:

Setup a Opennebula instance with a reverse proxy in front of the sunstone-gui, that will pass X-AUTH-USERNAME header (the dashes will be converted to lowercase automatically and HTTP will be added in the front).

In /etc/one/sunstone-server.conf set :auth: remote

Create a new user:

oneuser create testuser testuser --driver public

NB! password needs to match the username and it needs to be plain text.

Current results:

Currently the header is set and do_auth method in RemoteCloudAuth.rb returns the username correctly.

But the backend returns a 500.

Expected results:

Backend should auth the user, because everything is done according to docs.

  1. username in HTTP header
  2. password is the same as username
  3. user has public driver
  4. remote auth in conf is applied

More reading on the same topic with log outputs: Setting REMOTE-USER or X-AUTH-USERNAME does not work. · Issue #6252 · OpenNebula/one · GitHub

I set the logging in RemoteCloudAuth.rb to return me the headers using env.

logger.info{env.to_h.to_yaml}

This is the result when clicking on ‘login’ in the Opennebula Sunstone UI.

Wed Jun 28 17:12:01 2023 [I]: Login token:serveradmin:serveradmin:REDACTED
Wed Jun 28 17:12:01 2023 [I]: Login token:serveradmin:serveradmin:REDACTED
Wed Jun 28 17:12:01 2023 [I]: Updating user pool cache.
Wed Jun 28 17:12:01 2023 [I]: --- &6
SERVER_SOFTWARE: thin 1.8.1 codename Infinite Smoothie
SERVER_NAME: REDACTED
rack.input: &1 !ruby/object:StringIO {}
rack.version:
- 1
- 0
rack.errors: !ruby/object:IO {}
rack.multithread: true
rack.multiprocess: false
rack.run_once: false
REQUEST_METHOD: POST
REQUEST_PATH: "/login"
PATH_INFO: "/login"
REQUEST_URI: "/login"
HTTP_VERSION: HTTP/1.1
HTTP_X_AUTH_USERNAME: testuser
HTTP_HOST: REDACTED
HTTP_CONNECTION: close
HTTP_X_REAL_IP: REDACTED
HTTP_X_FORWARDED_FOR: REDACTED
HTTP_X_FORWARDED_PORT: '443'
HTTP_X_FORWARDED_PROTO: https
HTTP_SEC_CH_UA: ''
HTTP_ACCEPT: "*/*"
HTTP_X_REQUESTED_WITH: XMLHttpRequest
HTTP_SEC_CH_UA_MOBILE: "?0"
HTTP_USER_AGENT: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML,
  like Gecko) Chrome/114.0.5735.134 Safari/537.36
HTTP_SEC_CH_UA_PLATFORM: '""'
HTTP_ORIGIN: https://REDACTED
HTTP_SEC_FETCH_SITE: same-origin
HTTP_SEC_FETCH_MODE: cors
HTTP_SEC_FETCH_DEST: empty
HTTP_REFERER: https://REDACTED/
HTTP_ACCEPT_ENCODING: gzip, deflate
HTTP_ACCEPT_LANGUAGE: en-US,en;q=0.9
CONTENT_LENGTH: '37'
CONTENT_TYPE: application/x-www-form-urlencoded; charset=UTF-8
GATEWAY_INTERFACE: CGI/1.2
SERVER_PORT: '80'
QUERY_STRING: ''
SERVER_PROTOCOL: HTTP/1.1
rack.url_scheme: http
SCRIPT_NAME: ''
REMOTE_ADDR: REDACTED
async.callback: !ruby/object:Method {}
async.close: !ruby/object:EventMachine::DefaultDeferrable {}
rack.tempfiles: []
rack.request.form_hash:
  remember: 'false'
  two_factor_auth_token: ''
rack.request.form_vars: remember=false&two_factor_auth_token=
rack.request.form_input: *1
rack.logger: !ruby/object:Rack::NullLogger
  app: !ruby/object:Rack::Protection::FrameOptions
    app: !ruby/object:Rack::Protection::HttpOrigin
      app: !ruby/object:Rack::Protection::IPSpoofing
        app: !ruby/object:Rack::Protection::JsonCsrf
          app: !ruby/object:Rack::Protection::PathTraversal
            app: !ruby/object:Rack::Protection::XSSHeader
              app: &5 !ruby/object:Rack::Session::Pool
                app: !ruby/object:Rack::Deflater
                  app: !ruby/object:Rack::CommonLogger
                    app: !ruby/object:Sinatra::Application
                      default_layout: :layout
                      preferred_extension: 
                      app: 
                      template_cache: !ruby/object:Tilt::Cache
                        cache: {}
                      pinned_response: 
                    logger: !ruby/object:CloudLogger::CloudLogger
                      level: 0
                      progname: 
                      default_formatter: !ruby/object:Logger::Formatter
                        datetime_format: 
                      formatter: !ruby/object:Proc {}
                      logdev: !ruby/object:Logger::LogDevice
                        shift_period_suffix: "%Y%m%d"
                        shift_size: 1048576
                        shift_age: 0
                        filename: "/var/log/one/sunstone.log"
                        dev: !ruby/object:File {}
                        binmode: false
                        mon_data: !ruby/object:Monitor {}
                        mon_data_owner_object_id: 760
                  condition: 
                  compressible_types: 
                  sync: true
                default_options:
                  :path: "/"
                  :domain: 
                  :expire_after: 
                  :secure: false
                  :httponly: true
                  :defer: false
                  :renew: false
                  :sidbits: 128
                  :secure_random: &2 !ruby/module 'SecureRandom'
                  :drop: false
                key: sunstone
                cookie_only: true
                same_site: 
                sidbits: 128
                sid_secure: *2
                sid_length: 32
                pool: {}
                mutex: !ruby/object:Thread::Mutex {}
              options:
                :reaction: :drop_session
                :logging: true
                :message: Forbidden
                :encryptor: &3 !ruby/class 'Digest::SHA1'
                :session_key: rack.session
                :status: 403
                :allow_empty_referrer: true
                :report_key: protection.failed
                :html_types: &4
                - text/html
                - application/xhtml
                - text/xml
                - application/xml
                :xss_mode: :block
                :nosniff: true
                :img_src: "'self' data:"
                :font_src: "'self'"
                :without_session: true
            options:
              :reaction: :drop_session
              :logging: true
              :message: Forbidden
              :encryptor: *3
              :session_key: rack.session
              :status: 403
              :allow_empty_referrer: true
              :report_key: protection.failed
              :html_types: *4
              :img_src: "'self' data:"
              :font_src: "'self'"
              :without_session: true
          options:
            :reaction: :drop_session
            :logging: true
            :message: Forbidden
            :encryptor: *3
            :session_key: rack.session
            :status: 403
            :allow_empty_referrer: true
            :report_key: protection.failed
            :html_types: *4
            :allow_if: 
            :img_src: "'self' data:"
            :font_src: "'self'"
            :without_session: true
        options:
          :reaction: :drop_session
          :logging: true
          :message: Forbidden
          :encryptor: *3
          :session_key: rack.session
          :status: 403
          :allow_empty_referrer: true
          :report_key: protection.failed
          :html_types: *4
          :img_src: "'self' data:"
          :font_src: "'self'"
          :without_session: true
      options:
        :reaction: :drop_session
        :logging: true
        :message: Forbidden
        :encryptor: *3
        :session_key: rack.session
        :status: 403
        :allow_empty_referrer: true
        :report_key: protection.failed
        :html_types: *4
        :allow_if: 
        :img_src: "'self' data:"
        :font_src: "'self'"
        :without_session: true
    options:
      :reaction: :drop_session
      :logging: true
      :message: Forbidden
      :encryptor: *3
      :session_key: rack.session
      :status: 403
      :allow_empty_referrer: true
      :report_key: protection.failed
      :html_types: *4
      :frame_options: :sameorigin
      :img_src: "'self' data:"
      :font_src: "'self'"
      :without_session: true
rack.session: !ruby/object:Rack::Session::Abstract::PersistedSecure::SecureSessionHash
  store: *5
  req: !ruby/object:Rack::Request
    params: 
    env: *6
  loaded: false
  data: {}
  id: 
  exists: 
rack.session.options:
  :path: "/"
  :domain: 
  :expire_after: 
  :secure: false
  :httponly: true
  :defer: false
  :renew: false
  :sidbits: 128
  :secure_random: *2
  :drop: false
sinatra.commonlogger: true
rack.request.query_string: ''
rack.request.query_hash: {}
rack.request.cookie_hash: {}
sinatra.route: POST /login

Wed Jun 28 17:12:01 2023 [I]: Login token:serveradmin:testuser:REDACTED
Wed Jun 28 17:12:01 2023 [I]: Login token:serveradmin:testuser:REDACTED
Wed Jun 28 17:12:01 2023 [I]: Login token:serveradmin:serveradmin:REDACTED
Wed Jun 28 17:12:01 2023 [I]: Login token:serveradmin:serveradmin:REDACTED
Wed Jun 28 17:12:01 2023 [I]: REDACTED - - [28/Jun/2023:17:12:01 +0300] "POST /login HTTP/1.1" 500 - 0.4157

I have also found out from /usr/lib/one/sunstone/sunstone-server.rb , that Authorization header is required even if should be while using remote auth as only x-auth-username header is used.

I was also able to debug the $cloudauth object.

In /usr/lib/one/sunstone/sunstone.rb

    def build_session
        begin
            logger.debug{"Request env: #{request.env.to_json} "}
            logger.debug{"Params: #{params} "}
            logger.debug{"#{$cloud_auth.to_yaml}"}
            result = $cloud_auth.auth(request.env, params)
            logger.debug{"Auth result for session: #{result}"}
        rescue StandardError => e
            logger.debug{"Standard error: #{e.to_s}"}
            logger.error { e.message }
            return [500, '']
        end

This is the result of $cloud_auth.to_yaml

This result is which fails with 500 unable to auth user.

:webauthn_rpname: OpenNebula Cloud
  :webauthn_timeout: 60000
  :vnc_proxy_port: 29876
  :vnc_proxy_support_wss: false
  :vnc_proxy_cert: 
  :vnc_proxy_key: 
  :vnc_proxy_ipv6: false
  :vnc_request_password: false
  :allow_vnc_federation: false
  :keep_me_logged: true
  :lang: en_US
  :table_order: desc
  :mode: mixed
  :get_extended_vm_info: false
  :get_extended_vm_monitoring: false
  :paginate: "[[6, 12, 36, 72], [6, 12, 36, 72]]"
  :leases:
    suspend:
      time: "+1209600"
      color: "#000000"
      warning:
        time: "-86400"
        color: "#085aef"
    terminate:
      time: "+1209600"
      color: "#e1ef08"
      warning:
        time: "-86400"
        color: "#ef2808"
  :disable_guacamole_info_header: false
  :threshold_min: 0
  :threshold_low: 33
  :threshold_high: 66
  :support_fs:
  - ext4
  - ext3
  - ext2
  - xfs
  :oneflow_server: http://127.0.0.1:2474/
  :routes:
  - oneflow
  - vcenter
  - support
  - nsx
  :webauthn_avail: true
  :session_expire_time: 3600
  :debug_level: 3
  :use_user_pool_cache: true
  :webauthn_algorithms:
  - ES256
  - PS256
  - RS256
logger: !ruby/object:CloudLogger::CloudLogger
  level: 0
  progname: 
  default_formatter: !ruby/object:Logger::Formatter
    datetime_format: 
  formatter: !ruby/object:Proc {}
  logdev: !ruby/object:Logger::LogDevice
    shift_period_suffix: "%Y%m%d"
    shift_size: 1048576
    shift_age: 0
    filename: "/var/log/one/sunstone.log"
    dev: !ruby/object:File {}
    binmode: false
    mon_data: !ruby/object:Monitor {}
    mon_data_owner_object_id: 760
lock: !ruby/object:Thread::Mutex {}
expire_delta: 1800
expire_margin: 300
token_expiration_time: 1688031655
upool_expiration_time: 0
server_auth: !ruby/object:OpenNebula::ServerCipherAuth
  srv_user: serveradmin
  srv_passwd: redacted
  key: redacted
  iv: redacted
  cipher: !ruby/object:OpenSSL::Cipher {}

OK I figured something out. But I need some help with what to do.

The problem was Webauthn and HTTP_AUTHORIZATION header.

I dont want to use Webauthn, but for some reason it tried to get a 2FA code or whatever.


I modified the ruby file /usr/lib/one/sunstone/sunstone-server.rb

begin
    require "SunstoneWebAuthn"
    webauthn_avail = true
rescue LoadError
    webauthn_avail = false
end
require "SunstoneWebAuthn"

I modified the ruby file /etc/one/sunstone-server.conf

# Under webauthn I added conf option
:webauthn_avail: false

Now I could enable/disable it via conf option.

It turns out webauthn was enabled and with the remote driver it wants to get a 2fa token even though I dont have it enabled and it returned 403 to the CloudAuth method as env .

I guess some deeper insepction needs to be done, how to make the remote bypass webauthn or fix the implementation.


The HTTP_AUTHORIZATION header seems to bork something.

This block from 567-589 needs to be DISABLED if auth type is remote. Because the only requirement currently for remote auth passtrhough is REMOTE_USER header or HTTP_X_AUTH_USERNAME.

Also this code is different in CE edition of opennebula 6.6 that comes from bullseye repo.

Currently I got it to work and I get the correct user when logging in.

I copied the code from previous post. The current master branch code for sunstone-server.rb L567 until the end of the block and replaced it with the one that comes from bullseye repo.

In my Reverse-proxy that does the authentication, the headers are set as following:

# Ansible managed
-- Set logging to INFO
local openidc = require("resty.openidc")
openidc.set_logging(nil, { DEBUG = ngx.INFO })
local opts = {
    redirect_uri = "REDACTED/redirect_uri",
    discovery = "REDACTED/.well-known/openid-configuration",
    client_id = "REDACTED",
    client_secret = "REDACTED",
    introspection_endpoint_auth_method = "client_secret_basic",
    redirect_uri_scheme = "https",
    logout_path = "/logout",
    redirect_after_logout_uri = "REDACTED",
    redirect_after_logout_with_id_token_hint = true,
    renew_access_token_on_expiry = true,
    revoke_tokens_on_logout = true,
    session_contents = {id_token=true}
}
-- call introspect for OAuth 2.0 Bearer Access Token validation
local res, err = openidc.authenticate(opts)
if err then
    ngx.status = 403
    ngx.say(err)
    ngx.log(ngx.ERR, err)
    ngx.exit(ngx.HTTP_FORBIDDEN)
end
ngx.req.set_header("X-AUTH-USERNAME", res.id_token.preferred_username)
ngx.req.set_header("Authorization", "Basic " .. res.id_token.preferred_username)
ngx.req.set_header("X-USER", res.id_token.sub)

The most important parts are the headers X-AUTH-USERNAME for REMOTE authenticaton method and the Authorization “Basic” for the ruby code that does matching for http_authorization.

I also have webauthn_avail = false as previously mentioned.

glad you got it working!

not sure where the problem is, but in our tests and in several other users, there is no need to comment or move code to make this work. Leaving this here for reference in case others find it useful.

1 Like

I had this issue again. Documenting a little better this time

  1. I modified the ruby file /usr/lib/one/sunstone/sunstone-server.rb

Removed:

# removed
begin
    require "SunstoneWebAuthn"
    webauthn_avail = true
rescue LoadError
    webauthn_avail = false
end
# added
require "SunstoneWebAuthn"

#removed
$conf[:webauthn_avail] = webauthn_avail
# we can now set webauthn_avail via conf option, not automagically.

With 6.6.1.1 Update I had to modify some other things.

I got .downcase error on fireedge endpoint with nilclass.

So I needed to specify fireedge private and public endpoint, eventhough I am not using those.

# From Ansible Run
 # FireEdge
 ################################################################################
 
-#:private_fireedge_endpoint: http://localhost:2616
-#:public_fireedge_endpoint: http://localhost:2616
+
+:private_fireedge_endpoint: http://localhost:2616
+:public_fireedge_endpoint: http://localhost:2616

The error came from here:

I would like to add a “me too”. While migrating my master node from CentOS 8 stream to AlmaLinux 9, I also migrated from 6.6.0 to 6.6.1.1, and Sunstone refused to start with the above error. Removing the “.downcase” call from index.erb fixed this. Should I create a github issue?

--- /usr/lib/one/sunstone/views/index.erb.6.6.1.1	2024-01-17 20:30:35.728187988 +0100
+++ /usr/lib/one/sunstone/views/index.erb	2024-01-17 20:28:11.835725666 +0100
@@ -64,7 +64,7 @@
           'mapped_ips' : '<%= $conf[:mapped_ips] ? $conf[:mapped_ips] : false %>',
           'get_extended_vm_info': '<%= $conf[:get_extended_vm_info] ? $conf[:get_extended_vm_info] : false %>',
           'get_extended_vm_monitoring': '<%= $conf[:get_extended_vm_monitoring] ? $conf[:get_extended_vm_monitoring] : false %>',
-          'public_fireedge_endpoint': '<%= $conf[:public_fireedge_endpoint].downcase %>',
+          'public_fireedge_endpoint': '<%= $conf[:public_fireedge_endpoint] %>',
         },
         'view' : view,
         'available_views' : available_views,

-Yenya

The recommended way to disable VNC through FireEdge is to use an empty string, please read the second note here.

Aha. Thanks for the info!