We're trying to run a lua script to hook into fixups phase of a request in an apache server app, but we keep getting the following:
"[Thu Aug 28 09:55:37.099004 2025] [lua:crit] [pid <<pid>>] [client <<IP>>] <redacted>: lua: Unable to find function fetch_token_hook in /var/www/html/<<app domain>>/lua/fetch_token.lua, referer: https://<<app domain>>/?<<query parameters>>"
This how is the apache config file is currently defined, only including the relevant details:
## Vhost docroot
DocumentRoot "/var/www/html/<<app domain>>"
## Directories, there should at least be a declaration for /var/www/html/<<app domain>>
<Directory "/var/www/html/<<app domain>>">
AllowOverride None
Require all granted
</Directory>
## Custom fragment
# Lua config
LoadModule lua_module modules/mod_lua.so
LuaHookFixups /var/www/html/<<app domain>>/lua/fetch_token.lua fetch_token_hook
<Files "*.lua">
SetHandler lua-script
</Files>
And this is what the lua script looks like:
local http = require "socket.http"
local ltn12 = require "ltn12"
local cjson = require "cjson"
local token_endpoint = "<redacted>"
local client_id = "<redacted>"
local client_secret = "<redacted>"
local scope = "<redacted>"
local cached_token = nil
local expires_at = 0
local function get_new_token(r)
local body = string.format(
"grant_type=client_credentials&client_id=%s&client_secret=%s&scope=%s",
client_id, client_secret, scope:gsub(" ", "%%20"))
local resp_chunks = {}
local _, code = http.request{
url = token_endpoint,
method = "POST",
headers = {
["Content-Type"] = "application/x-www-form-urlencoded",
["Content-Length"] = #body
},
source = ltn12.source.string(body),
sink = ltn12.sink.table(resp_chunks)
}
if code ~= 200 then
r:err("[lua] token endpoint returned status "..tostring(code))
return nil
end
local token_json = table.concat(resp_chunks)
local t = cjson.decode(token_json)
if not t.access_token or not t.expires_in then
r:err("[lua] token endpoint did not return access_token / expires_in")
return nil
end
cached_token = t.access_token
expires_at = os.time() + tonumber(t.expires_in)
return cached_token
end
function fetch_token_hook(r)
file = io.open("/var/www/html/<<app domain>>/lua/lualog.txt","a")
file:write("Start of log \n")
file:write(r.uri)
file:write("\n <End of log>")
file:close()
if not r.uri:match("^<<proxy path>>") then
return apache2.DECLINED
end
if (not cached_token) or (os.time() > expires_at - 30) then
if not get_new_token(r) then
return apache2.HTTP_INTERNAL_SERVER_ERROR
end
end
r.headers_in["Authorization"] = "Bearer "..cached_token
return apache2.DECLINED
end
We've double and triple checked the path and the naming and we've been able to get this exact setup running locally, so we know it's not typo. At this point we're almost convinced it's some kind of security issue, but we can't see any holes there either. These are the permissions granted to the fetch_token.lua file:
"-rwxr-xr-x. 1 <<user>> www 1834 Aug 29 09:33 fetch_token.lua"
We've also tried:
- Tried incorrect lua script path (still says it can't find the method, might be an indication that directive can't find the script with even the correct path)
- Tried removing LoadModule lua_module modules/mod_lua.so (As expected, gives an error indicating lua isn't available)
- Added file tags for loading script (No difference)
- Tried simplifying lua script (no difference)
- Tried running lua script manually (It works)