Rocky 9 - Apache calls LuaHookFixups called before OIDC authn

Pretty squarely not OOD’s fault here, but still an issue:

I’ve been chasing an odd behavior with an OOD deployment with mod_auth_openidc on a Rocky 9 host: the parts of OOD that require authentication will immediately redirect to the “no mapping” page. The behavior on CentOS 7 + rh-httpd24 is to redirect the client to the OIDC provider’s logon page.

Under the hood, this is caused by Apache calling the LuaHookFixups hook before authentication (which appears to contradict the mod_lua documentation). I’ve confirmed that this is the case by modifying the pun_proxy.lua script.

Has anyone else seen this behavior? The Apache packages are:
httpd-2.4.57-8.el9.x86_64
mod_auth_openidc-2.4.9.4-4.el9.x86_64

A fix is to exit the handler with apache2.DECLINED if there’s no authenticated username present in the ‘r’ dictionary, allowing the redirect to OIDC authentication to proceed. However, I’m not sure this is a safe fix. If any such modified lua handler is in an authorization role such that apache2.DECLINED (“process the request as if the handler wasn’t there”) yields access to a resource, this would be a huge mistake to apply.

Any insight or guidance from the dev team?

Hi and welcome!

I’ll need a minute to look this over. It seems like apache should hit the OIDC handlers before the LuaHookFixups - as you’ve indicated.

I’ll have to build a container and replicate to figure this out. Maybe extra debug logs could indicate why this is the case, but I believe the order in which things are defined in an apache Location is important. I.e., authentication directives are defined first and LuaHookFixups defined last.

I’ll take a look and report back.

I’m having trouble replicating. If I set LogLevel to trace5 - I see it goes from rewriting to authentication.

[Fri Jun 07 14:02:06.702171 2024] [rewrite:trace4] [pid 49:tid 208] mod_rewrite.c(493): [client 10.0.2.100:55384] 10.0.2.100 - - [localhost/sid#5f72be6f6280][ri
d#7bb4c4006c30/initial] RewriteCond: input='/var/www/ood/public/maintenance/index.html' pattern='-f' => matched
[Fri Jun 07 14:02:06.702176 2024] [rewrite:trace4] [pid 49:tid 208] mod_rewrite.c(493): [client 10.0.2.100:55384] 10.0.2.100 - - [localhost/sid#5f72be6f6280][ri
d#7bb4c4006c30/initial] RewriteCond: input='/etc/ood/maintenance.enable' pattern='-f' => not-matched
[Fri Jun 07 14:02:06.702178 2024] [rewrite:trace1] [pid 49:tid 208] mod_rewrite.c(493): [client 10.0.2.100:55384] 10.0.2.100 - - [localhost/sid#5f72be6f6280][ri
d#7bb4c4006c30/initial] pass through /pun/sys/dashboard
[Fri Jun 07 14:02:06.702180 2024] [proxy:trace2] [pid 49:tid 208] mod_proxy.c(884): [client 10.0.2.100:55384] AH03461: attempting to match URI path '/pun/sys/da
shboard' against prefix '/dex' for proxying
[Fri Jun 07 14:02:06.702220 2024] [authz_core:debug] [pid 49:tid 208] mod_authz_core.c(815): [client 10.0.2.100:55384] AH01626: authorization result of Require 
valid-user : denied (no authenticated user yet)
[Fri Jun 07 14:02:06.702222 2024] [authz_core:debug] [pid 49:tid 208] mod_authz_core.c(815): [client 10.0.2.100:55384] AH01626: authorization result of <Require
Any>: denied (no authenticated user yet)
[Fri Jun 07 14:02:06.702237 2024] [auth_openidc:debug] [pid 49:tid 208] src/mod_auth_openidc.c(3943): [client 10.0.2.100:55384] oidc_check_user_id: incoming req
uest: "/pun/sys/dashboard?(null)", ap_is_initial_req(r)=1
[Fri Jun 07 14:02:06.702244 2024] [auth_openidc:debug] [pid 49:tid 208] src/util.c(2539): [client 10.0.2.100:55384] oidc_util_hdr_in_get: Host=localhost:8080
[Fri Jun 07 14:02:06.702246 2024] [auth_openidc:debug] [pid 49:tid 208] src/util.c(2539): [client 10.0.2.100:55384] oidc_util_hdr_in_get: Host=localhost:8080
[Fri Jun 07 14:02:06.702248 2024] [auth_openidc:debug] [pid 49:tid 208] src/util.c(663): [client 10.0.2.100:55384] oidc_get_redirect_uri: determined absolute re
direct uri: http://localhost:8080/oidc
[root@4e45995e1036 ~]# rpm -q httpd
httpd-2.4.57-8.el9.x86_64
[root@4e45995e1036 ~]# rpm -q mod_auth_openidc
mod_auth_openidc-2.4.9.4-4.el9.x86_64
[root@4e45995e1036 ~]# rpm -q mod_lua
mod_lua-2.4.57-8.el9.x86_64

I tested on the nightly, but should be pretty close to 3.1. I wonder if there’s some configuration that’s throwing this off? Can you share your ood_portal.yml (obfuscating anything you need to).

Thanks for digging in. Do you happen to have the next 15 or so lines after the bottom of the log/trace?

Here’s mine:

[Fri Jun 07 10:53:43.346465 2024] [auth_openidc:debug] [pid 199702:tid 199792] src/util.c(2539): [client X:38748] oidc_util_hdr_in_get: Host=Y
[Fri Jun 07 10:53:43.346474 2024] [auth_openidc:debug] [pid 199702:tid 199792] src/util.c(2539): [client X:38748] oidc_util_hdr_in_get: Host=Y
[Fri Jun 07 10:53:43.346483 2024] [auth_openidc:debug] [pid 199702:tid 199792] src/util.c(663): [client X:38748] oidc_get_redirect_uri: determined absolute redirect uri: https://Y/oidc  <---- end of log example above
[Fri Jun 07 10:53:43.346494 2024] [auth_openidc:debug] [pid 199702:tid 199792] src/util.c(1406): [client X:38748] oidc_util_request_matches_url: comparing "/pun/sys/dashboard"=="/oidc"
[Fri Jun 07 10:53:43.346509 2024] [auth_openidc:debug] [pid 199702:tid 199792] src/util.c(2539): [client X:38748] oidc_util_hdr_in_get: Accept=text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
[Fri Jun 07 10:53:43.346520 2024] [auth_openidc:debug] [pid 199702:tid 199792] src/util.c(2539): [client X:38748] oidc_util_hdr_in_get: Host=Y
[Fri Jun 07 10:53:43.346529 2024] [auth_openidc:debug] [pid 199702:tid 199792] src/util.c(2539): [client X:38748] oidc_util_hdr_in_get: Host=Y
[Fri Jun 07 10:53:43.346538 2024] [auth_openidc:debug] [pid 199702:tid 199792] src/util.c(644): [client X:38748] oidc_get_current_url: current URL 'https://Y/pun/sys/dashboard'
[Fri Jun 07 10:53:43.346547 2024] [auth_openidc:debug] [pid 199702:tid 199792] src/mod_auth_openidc.c(2279): [client X:38748] oidc_authenticate_user: enter
[Fri Jun 07 10:53:43.346570 2024] [auth_openidc:debug] [pid 199702:tid 199792] src/mod_auth_openidc.c(2290): [client X:38748] oidc_authenticate_user: defer discovery to the content handler
[Fri Jun 07 10:53:43.346584 2024] [authz_core:debug] [pid 199702:tid 199792] mod_authz_core.c(815): [client X:38748] AH01626: authorization result of Require valid-user : granted <---- wait, what?! we're not done with OIDC yet!
[Fri Jun 07 10:53:43.346594 2024] [authz_core:debug] [pid 199702:tid 199792] mod_authz_core.c(815): [client X:38748] AH01626: authorization result of <RequireAny>: granted
[Fri Jun 07 10:53:43.346626 2024] [lua:trace2] [pid 199702:tid 199792] mod_lua.c(207): [client X:38748] AH02313: request hook details: scope: once, file: /opt/ood/mod_ood_proxy/lib/pun_proxy.lua, func: pun_proxy_handler
[Fri Jun 07 10:53:43.347549 2024] [lua:debug] [pid 199702:tid 199792] lua_request.c(1902): [client X:38748] AH01488: request_rec->dispatching user -> string
[Fri Jun 07 10:53:43.347586 2024] [lua:debug] [pid 199702:tid 199792] lua_request.c(1902): [client X:38748] AH01488: request_rec->dispatching user -> string
[Fri Jun 07 10:53:43.347599 2024] [lua:trace4] [pid 199702:tid 199792] mod_lua.c(730): [client X:38748] Lua hook pun_proxy.lua:pun_proxy_handler for phase fixups returned -1   <---- I patched the pun_proxy.lua to return apache2.DECLINED if no user is set.

Here’s the ood_portal.yml with comments removed:

$ cat /etc/ood/config/ood_portal.yml | grep -vP '\s*#' | grep -v '^$'
---
oidc_uri: '/oidc'
auth:
  - 'AuthType openid-connect'
  - 'Require valid-user'
  - 'OIDCDiscoverURL /oidc-discovery/globus'
logout_redirect: '/oidc?logout=/public/logged_out.html'
map_fail_uri: '/public/map_error.html'
ssl:
  - SSLCertificateFile /Z/host.pem
  - SSLCertificateChainFile /Z/chain.pem
  - SSLCertificateKeyFile /Z/host.privkey
  - SSLCipherSuite 'ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES128-GCM-SHA256'
node_uri: '/node'
rnode_uri: '/rnode'
host_regex: '[\w.-]+\.BB\.CC'
public_root: /etc/ood/public
user_map_cmd: '/AA'
user_env: 'OIDC_access_token'

And for completeness, the rendered ood-portal.conf and oidc config:

$ sudo cat /etc/httpd/conf.d/ood-portal.conf | grep -Pv '^\s*#' | grep -v '^$'
<VirtualHost *:80>
  RewriteEngine On
  RewriteRule ^(.*) https://Y:443$1 [R=301,NE,L]
</VirtualHost>
<VirtualHost *:443>
  ErrorLog  "logs/Y_error_ssl.log"
  CustomLog "logs/Y_access_ssl.log" combined
  RewriteEngine On
  RewriteCond /etc/ood/public/maintenance/index.html -f
  RewriteCond /etc/ood/maintenance.enable -f
  RewriteCond %{REQUEST_URI} !/public/maintenance/.*$
  RewriteRule ^.*$ /public/maintenance/index.html [R=302,L]
  Header always set Content-Security-Policy "frame-ancestors https://Y;"
  Header always set Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"
  SSLEngine On
  SSLCertificateFile /Z/host.pem
  SSLCertificateChainFile /Z/chain.pem
  SSLCertificateKeyFile /Z/host.privkey
  SSLCipherSuite 'ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES128-GCM-SHA256'
  LuaRoot "/opt/ood/mod_ood_proxy/lib"
  LogLevel trace5  <---- modified for troubleshooting
  LuaHookLog logger.lua logger
  SetEnv OOD_USER_MAP_CMD "/AA"
  SetEnv OOD_USER_ENV "OIDC_access_token"
  SetEnv OOD_MAP_FAIL_URI "/public/map_error.html"
  SetEnv OOD_PUN_STAGE_CMD "sudo /opt/ood/nginx_stage/sbin/nginx_stage"
  Alias "/public" "/etc/ood/public"
  <Directory "/etc/ood/public">
    Options FollowSymLinks
    AllowOverride None
    Require all granted
  </Directory>
  <LocationMatch "^/node/(?<host>[\w.-]+\.BB\.CC)/(?<port>\d+)">
    AuthType openid-connect
    Require valid-user
    OIDCDiscoverURL /oidc-discovery/globus
    Header edit Location "^[^/]+//[^/]+" ""
    Header edit* Set-Cookie ";\s*(?i)Domain[^;]*" ""
    Header edit* Set-Cookie ";\s*(?i)Path[^;]*" ""
    Header edit  Set-Cookie "^([^;]+)" "$1; Path=/node/%{MATCH_HOST}e/%{MATCH_PORT}e"
    LuaHookFixups node_proxy.lua node_proxy_handler
  </LocationMatch>
  <LocationMatch "^/rnode/(?<host>[\w.-]+\.sdsc\.edu)/(?<port>\d+)(?<uri>/.*|)">
    AuthType openid-connect
    Require valid-user
    OIDCDiscoverURL /oidc-discovery/globus
    Header edit Location "^([^/]+//[^/]+)|(?=/)|^([\./]{1,}(?<!/))" "/rnode/%{MATCH_HOST}e/%{MATCH_PORT}e"
    Header edit* Set-Cookie ";\s*(?i)Domain[^;]*" ""
    Header edit* Set-Cookie ";\s*(?i)Path[^;]*" ""
    Header edit  Set-Cookie "^([^;]+)" "$1; Path=/rnode/%{MATCH_HOST}e/%{MATCH_PORT}e"
    LuaHookFixups node_proxy.lua node_proxy_handler
  </LocationMatch>
  SetEnv OOD_PUN_URI "/pun"
  <Location "/pun">
    AuthType openid-connect
    Require valid-user
    OIDCDiscoverURL /oidc-discovery/globus
    ProxyPassReverse "http://localhost/pun"
    Header edit* Set-Cookie ";\s*(?i)Domain[^;]*" ""
    Header edit* Set-Cookie ";\s*(?i)Path\s*=(?-i)(?!\s*/pun)[^;]*" "; Path=/pun"
    SetEnv OOD_PUN_SOCKET_ROOT "/var/run/ondemand-nginx"
    SetEnv OOD_PUN_MAX_RETRIES "5"
    LuaHookFixups pun_proxy.lua pun_proxy_handler
  </Location>
  SetEnv OOD_NGINX_URI "/nginx"
  <Location "/nginx">
    AuthType openid-connect
    Require valid-user
    OIDCDiscoverURL /oidc-discovery/globus
    LuaHookFixups nginx.lua nginx_handler
  </Location>
  RedirectMatch ^/$ "/pun/sys/dashboard"
  Redirect "/logout" "/oidc?logout=/public/logged_out.html"
  <Location "/oidc">
    AuthType openid-connect
    Require valid-user
    OIDCDiscoverURL /oidc-discovery/globus
  </Location>
  <Location "/oidc-discovery/globus">
   RewriteEngine On
   RewriteCond "%{QUERY_STRING}" "(.*)"
   RewriteRule "." /oidc?%1&iss=https\%3a\%2f\%2fauth.globus.org [R=302,L,NE]
  </Location>
  <Directory "/etc/ood/public/maintenance">
    RewriteCond /etc/ood/maintenance.enable !-f
    ReWriteRule ^.*$ /
    RewriteCond %{REQUEST_URI} !/public/maintenance/.*$
    RewriteRule ^.*$ /public/maintenance/index.html [R=503,L]
    ErrorDocument 503 /public/maintenance/index.html
  </Directory>
</VirtualHost>
$ sudo  cat /etc/httpd/conf.d/auth_openidc.conf | grep -vP '\s*#' | grep -v '^$'
OIDCRedirectURI      /oidc
OIDCCryptoPassphrase "exec:/usr/bin/openssl rand -hex 32"
OIDCMetaDataDir      /Z/oidc
OIDCPassClaimsAs environment
OIDCSessionMaxDuration 28800
OIDCSessionInactivityTimeout 28800
OIDCSessionType server-cache
OIDCCacheType shm
OIDCStripCookies mod_auth_openidc_session mod_auth_openidc_session_chunks mod_auth_openidc_session_0 mod_auth_openidc_session_1
OIDCCookieHTTPOnly On
OIDCCookieSameSite On

I’m guessing my next stop is to dig in to mod_auth_openidc to see how this happened:

[Fri Jun 07 10:53:43.346547 2024] [auth_openidc:debug] [pid 199702:tid 199792] src/mod_auth_openidc.c(2279): [client X:38748] oidc_authenticate_user: enter
[Fri Jun 07 10:53:43.346570 2024] [auth_openidc:debug] [pid 199702:tid 199792] src/mod_auth_openidc.c(2290): [client X:38748] oidc_authenticate_user: defer discovery to the content handler
[Fri Jun 07 10:53:43.346584 2024] [authz_core:debug] [pid 199702:tid 199792] mod_authz_core.c(815): [client X:38748] AH01626: authorization result of Require valid-user : granted <---- wait, what?! we're not done with OIDC yet!

Note that the above httpd config uses OIDCMetaDataDir, both to support multiple IdPs as well as keep secrets separate from the config. This might be relevant…

Sure, here you are.

[Fri Jun 07 14:02:06.702180 2024] [proxy:trace2] [pid 49:tid 208] mod_proxy.c(884): [client 10.0.2.100:55384] AH03461: attempting to match URI path '/pun/sys/dashboard' against prefix '/dex'
 for proxying
[Fri Jun 07 14:02:06.702220 2024] [authz_core:debug] [pid 49:tid 208] mod_authz_core.c(815): [client 10.0.2.100:55384] AH01626: authorization result of Require 
valid-user : denied (no authenticated user yet)
[Fri Jun 07 14:02:06.702222 2024] [authz_core:debug] [pid 49:tid 208] mod_authz_core.c(815): [client 10.0.2.100:55384] AH01626: authorization result of <RequireAny>: denied (no authenticated user yet)
[Fri Jun 07 14:02:06.702237 2024] [auth_openidc:debug] [pid 49:tid 208] src/mod_auth_openidc.c(3943): [client 10.0.2.100:55384] oidc_check_user_id: incoming request: "/pun/sys/dashboard?(null)", ap_is_initial_req(r)=1
[Fri Jun 07 14:02:06.702244 2024] [auth_openidc:debug] [pid 49:tid 208] src/util.c(2539): [client 10.0.2.100:55384] oidc_util_hdr_in_get: Host=localhost:8080
[Fri Jun 07 14:02:06.702246 2024] [auth_openidc:debug] [pid 49:tid 208] src/util.c(2539): [client 10.0.2.100:55384] oidc_util_hdr_in_get: Host=localhost:8080
[Fri Jun 07 14:02:06.702248 2024] [auth_openidc:debug] [pid 49:tid 208] src/util.c(663): [client 10.0.2.100:55384] oidc_get_redirect_uri: determined absolute redirect uri: http://localhost:8080/oidc
[Fri Jun 07 14:02:06.702250 2024] [auth_openidc:debug] [pid 49:tid 208] src/util.c(1243): [client 10.0.2.100:55384] oidc_util_get_cookie: returning "mod_auth_openidc_session" = <null>
[Fri Jun 07 14:02:06.702252 2024] [auth_openidc:debug] [pid 49:tid 208] src/util.c(2539): [client 10.0.2.100:55384] oidc_util_hdr_in_get: Host=localhost:8080
[Fri Jun 07 14:02:06.702253 2024] [auth_openidc:debug] [pid 49:tid 208] src/util.c(2539): [client 10.0.2.100:55384] oidc_util_hdr_in_get: Host=localhost:8080
[Fri Jun 07 14:02:06.702255 2024] [auth_openidc:debug] [pid 49:tid 208] src/util.c(663): [client 10.0.2.100:55384] oidc_get_redirect_uri: determined absolute redirect uri: http://localhost:8080/oidc
[Fri Jun 07 14:02:06.702257 2024] [auth_openidc:debug] [pid 49:tid 208] src/util.c(1406): [client 10.0.2.100:55384] oidc_util_request_matches_url: comparing "/pun/sys/dashboard"=="/oidc"
[Fri Jun 07 14:02:06.702259 2024] [auth_openidc:debug] [pid 49:tid 208] src/util.c(2539): [client 10.0.2.100:55384] oidc_util_hdr_in_get: Accept=text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
[Fri Jun 07 14:02:06.702261 2024] [auth_openidc:debug] [pid 49:tid 208] src/util.c(2539): [client 10.0.2.100:55384] oidc_util_hdr_in_get: Host=localhost:8080
[Fri Jun 07 14:02:06.702264 2024] [auth_openidc:debug] [pid 49:tid 208] src/util.c(2539): [client 10.0.2.100:55384] oidc_util_hdr_in_get: Host=localhost:8080
[Fri Jun 07 14:02:06.702266 2024] [auth_openidc:debug] [pid 49:tid 208] src/util.c(644): [client 10.0.2.100:55384] oidc_get_current_url: current URL 'http://localhost:8080/pun/sys/dashboard'
[Fri Jun 07 14:02:06.702267 2024] [auth_openidc:debug] [pid 49:tid 208] src/mod_auth_openidc.c(2279): [client 10.0.2.100:55384] oidc_authenticate_user: enter
[Fri Jun 07 14:02:06.702270 2024] [auth_openidc:debug] [pid 49:tid 208] src/cache/common.c(317): [client 10.0.2.100:55384] oidc_cache_get: enter: http://localhost:8080/dex/.well-known/openid-configuration (section=p, decrypt=0, type=shm)
[Fri Jun 07 14:02:06.702499 2024] [auth_openidc:debug] [pid 49:tid 208] src/cache/common.c(352): [client 10.0.2.100:55384] oidc_cache_get: cache miss from shm cache backend for key http://localhost:8080/dex/.well-known/openid-configuration
[Fri Jun 07 14:02:06.702505 2024] [auth_openidc:debug] [pid 49:tid 208] src/util.c(775): [client 10.0.2.100:55384] oidc_util_http_query_encoded_url: url=http://localhost:8080/dex/.well-known/openid-configuration
[Fri Jun 07 14:02:06.702507 2024] [auth_openidc:debug] [pid 49:tid 208] src/util.c(814): [client 10.0.2.100:55384] oidc_util_http_call: url=http://localhost:8080/dex/.well-known/openid-configuration, data=(null), content_type=(null), basic_auth=null, bearer_token=(null), ssl_validate_server=1, timeout=5, outgoing_proxy=(null), pass_cookies=0, ssl_cert=(null), ssl_key=(null), ssl_key_pwd=(null)
[Fri Jun 07 14:02:06.703213 2024] [http2:trace1] [pid 52:tid 194] h2_c1.c(218): [client 127.0.0.1:55686] h2_h2, process_conn
[Fri Jun 07 14:02:06.703246 2024] [http2:trace1] [pid 52:tid 194] h2_c1.c(223): [client 127.0.0.1:55686] h2_h2, process_conn, new connection using protocol 'http/1.1', direct=0, tls acceptable=1
[Fri Jun 07 14:02:06.703250 2024] [http2:trace1] [pid 52:tid 194] h2_c1.c(282): [client 127.0.0.1:55686] h2_h2, declined
[Fri Jun 07 14:02:06.703298 2024] [core:trace5] [pid 52:tid 194] protocol.c(713): [client 127.0.0.1:55686] Request received from client: GET /dex/.well-known/openid-configuration HTTP/1.1

I wonder if it’s this that’s throwing you off. Does the default REMOTE_USER not work for you? Is it possible that it’s using the current/old OIDC_access_token the user is passing to access the site?

Totally different behavior. Thanks for the confirmation.

After a little digging, it’s actually mod_auth_openidc, which returns success if OIDCMetaDataDir is in play.

From mod_auth_openidc.c:

// TODO: should we use an explicit redirect to the discovery endpoint (maybe a "discovery" param to the redirect_uri)?
                if (c->metadata_dir != NULL) {
                        /*
                         * Will be handled in the content handler; avoid:
                         * No authentication done but request not allowed without authentication
                         * by setting r->user
                         */
                        oidc_debug(r, "defer discovery to the content handler");
                        oidc_request_state_set(r, OIDC_REQUEST_STATE_KEY_DISCOVERY, "");
                        r->user = "";
                        return OK;
                }

There’s the satisfied “Require valid-user”, which then causes pun_proxy_lua to get called… since there is a valid user according to the return value from mod_auth_openidc.

I just tried using a more complex directive to require some other artifact in addition to valid-user (not-null username, one of the OIDC_ env variables set), but that just produces a “Forbidden” response.

So I guess, until the TODO is taken care of, and/or a more appropriate return value is set, the lua handlers need to accommodate the case that they’ll be called with no username in a pre-authentication context.

Here’s the patch I’m using right now for pun_proxy, node_proxy, and nginx:

function pun_proxy_handler(r)
  -- early-exit if no username since this handler may be called pre-authn
  if r.user == nil or r.user == '' then
    return apache2.DECLINED
  end