Skip to main content
Version: Next

Lua Support

Nauthilus has Lua 5.1 support in all areas of the service. To understand the interfaces, you must first get an idea of what happens with an incoming authentication request.

Authentication workflow

An incoming authentication request first enters the brute_force check. After that it continues with the environment stage. After that has passed, it continues to process the request in a password backend. When the final result for the request was obtained, it passes subject sources.

Subject sources may change the backend result in one or the other way (accepting a formerly rejected message or vice versa). This is especially useful for other remote services that can influence the authentication process.

After all this has finished, it is possible to do some post actions, which are run independent of all other steps in the whole pipeline and therefore can not influence the final result anymore.

Policy-aware action semantics

Nauthilus distinguishes synchronous Lua actions from Lua POST-Actions.

Synchronous Lua actions are configured with action types such as brute_force, lua, tls_encryption, relay_domains, and rbl. In policy-authoritative paths, the selected policy decision dispatches them through the registered obligation auth.obligation.lua_action.dispatch and Nauthilus waits for the action before the request continues.

The triggering check, environment control, or environment source only emits facts. The action runs only when the selected decision includes the dispatch obligation. This keeps reports, observe-mode comparisons, and mutable side effects aligned with the same policy decision.

Lua POST-Actions use the post action type. They run after the request-time decision context is known and must not change the final decision, response marker, response message, or FSM terminal state. In auth.policy authoritative paths, POST-Action enqueueing is requested through the registered obligation auth.obligation.lua_post_action.enqueue. Brute-force counter and learning updates are requested through auth.obligation.brute_force.update.

Observe-mode policy evaluation reports planned custom obligations but never executes them, so it does not dispatch synchronous Lua actions, enqueue custom POST-Actions, or mutate brute-force state.

In the following sequence diagram you can see the processing of the request in more detail.

Additional things to know

When starting the server, it is possible to call an init script, which may be used to register prometheus elements, start connection tracker or define custom redis pools. The latter is interesting, if you prefer using other redis servers for all your custom Lua scripts.

While runtime...

When an incoming authentication request is started, a Lua context is created.

All parts of a request share that common request context. Lua scripts can set arbitrary data in the context and read/delete things from there.

Lua scripts can modify the final log line by adding key-value pairs from each script.


Configuration

For the configuration, please have a look at the configuration file document.


Lua components

Each component does provide a set of global functions, constants, ... and requires a well-defined response from each request.

Every Lua script that has been configured is pre-compiled and kept in memory for future use. To make script changes, you must reload the service.


Lua libraries

Nauthilus does automatically preload Lua modules.

This is the list of modules that are currently available:

Loader nameDescription
nauthilus_mailE-Mail functions
nauthilus_passwordPassword compare and validation functions
nauthilus_redisRedis related functions
nauthilus_miscCountry code and sleep functions
nauthilus_contextGlobal Lua context accross all States in Nauthilus
nauthilus_ldapLDAP related functions
nauthilus_backendBackend related functions
nauthilus_http_requestHTTP request header functions
nauthilus_http_responseHTTP response functions (headers, status, body; environment/subject sources MUST NOT send a body)
nauthilus_policyRequest-local policy attribute emission
nauthilus_i18nPolicy localization helpers and startup catalog overlays
nauthilus_cborCBOR encode/decode helpers
nauthilus_prometheusPrometheus metrics functions
nauthilus_soft_whitelistSoft whitelist functions
nauthilus_brute_forceBrute force prevention functions
nauthilus_dnsDNS related functions
nauthilus_cacheIn-process cache functions
nauthilus_psnetNetwork connection manager functions
nauthilus_opentelemetryOpenTelemetry tracing functions
nauthilus_utilCommon utility functions
glua_cryptogluacrpyto project on Github
glua_httpgluahttp project on Github

In addition to these Nauthilus-specific modules, the gopher-lua-libs collection is automatically preloaded, providing access to modules like json, yaml, time, db, tcp, and more.

Example:

local nauthilus_redis = require("nauthilus_redis")

local nauthilus_builtin_context = require("nauthilus_context")

-- Gopher-Lua-Libs

local crypto = require("crypto")

local db = require("db")

local time = require("time")

-- Glua-Crypto

local crypto = require("glua_crypto") -- provides sha1 and others...

Actions

Whenever a brute froce attack is recognized, actions may be called. The request will wait until all requests have finished. Actions are processed by a central action worker. No results are returned to the regular request, so actions in general do their own logging!

Lua actions run only when the selected policy decision dispatches the corresponding obligation. Synchronous actions wait for the worker to finish.

Environment Sources

Lua environment sources run in the pre-auth phase alongside built-in checks such as geoip, rbl, tls_encryption and relay_domains. They are useful when the environment context itself should decide whether the request may continue. Lua environment sources are executed in parallel starting with v1.8.9. As soon as any environment source has triggered, the request will reject the authentication process after all environment sources have finished, and the results are aggregated.

Version change in 1.8.9

Since version 1.8.9, Lua environment and subject sources are executed in parallel. Nauthilus waits for all scripts to finish and then aggregates their results:

  • Environment sources: triggered=true if any script reports ENVIRONMENT_TRIGGER_YES; abort flag is set if any script requests ENVIRONMENT_ABORT_YES; the first error aborts the whole operation; the first status_message set is used.
  • Subject sources: an action is taken if any subject source requests it; backend_result attributes from multiple subject sources are merged (later keys overwrite earlier ones); remove-attributes are unioned; the first error aborts; the first status_message set is used. Each script runs with its own Lua state and per-script timeout. Do not rely on execution order between scripts.

Lua environment sources can return an abort flag to skip remaining built-in pre-auth checks.

Lua backend

The Lua backend can be used for password checks and request modes such as no-auth, list-accounts, and TOTP-aware flows.

The backend can accept a request or reject it. It has full access to all meta information that are delivered from the incoming request.

Subject Sources

There may exist remote services that may be contacted after the main backend authentication proccess returned its first
result. Think of something like GeoIP service or some IP white/blacklisting. Even a request that might have authenticated correctly may be rejected to a policy violation from such a service. Therefor subject sources have the power to overwrite the result from a backend.

You can also use subject sources to retrieve additional information from databases or LDAP and add additional attributes to the remaining result. This is useful for setups, where Nauthilus may also take the role of a Dovecot proxy. Users may get routed to different mail stores upon successful authentication. For this, you may retrieve the current backend server list with servers that have been checked as being alive by backend health checks and select one for the current client request.

info

Subject sources never affect caching! This is important, because otherwise valid credentials might result in storing them in the negative password cache or vice versa for invalid credentials.

warning

You always have to deal with the "request.authenticated" flag! If you don't care enough, you might acidentially reject legitimate authenticated users or allow bad guys.

Post actions

Post actions are actions, which run after the request hast come to its final result. Its main purpose is to start some automated things like doing statistics stuff, sending messages to operators or anything else that does not require fast instant processing.

As an example have a look at the telegram script. Lua scripts in earlier stages of the process may provide some information by using the Lua context. The telegram script may pick up these information and decide to send out some notifications to an operator channel.


Required functions and constants

Every Lua script must provide a pre-defined Lua function with a request parameter. Concerning the actual script, there is a requried return statement.

Nauthilus will look for these functions and parses the results.


Common request fields for all Lua scripts

The following request fields are supported

NameTypePrecenseAdditional info
debugboolalways-
repeatingboolmaybe-
user_foundboolmaybe-
authenticatedboolmaybe-
no_authboolalwaystrue, if the reuqest is used to retrieve user information
servicestringalwaysNauthilus endpoint like "dovecot" or "nginx"
sessionstringalways-
client_ipstringalways-
client_portstringalways-
client_hoststringmaybe-
client_netstringmaybeAvailable in conjunction with brute-force-actions
client_idstringmaybe-
user_agentstringmaybe-
local_ipstringalways-
local_portstringalways-
usernamestringalways-
accountstringmaybeSubject and post actions
unique_user_idstringmaybeUsed with OIDC subject
display_namestringmaybe-
passwordstringalways-
protocolstringalways-
brute_force_bucketstringmaybeAvailable in conjunction with brute-force-actions
environmentstringmaybeIn actions, if an environment source or control has triggered
status_messagestringalwaysCurrent status message returned to client, if auth request failed
sslstringmaybeHAproxy: %[ssl_fc]
ssl_session_idstringmaybeHAproxy: %[ssl_fc_session_id,hex]
ssl_client_verifystringmaybeHAproxy: %[ssl_c_verify]
ssl_client_dnstringmaybeHAproxy: %{+Q}[ssl_c_s_dn]
ssl_client_cnstringmaybeHAproxy: %{+Q}[ssl_c_s_dn(cn)]
ssl_issuerstringmaybeHAproxy: %{+Q}[ssl_c_i_dn]
ssl_client_not_beforestringmaybeHAproxy: %{+Q}[ssl_c_notbefore]
ssl_client_not_afterstringmaybeHAproxy: %{+Q}[ssl_c_notafter]
ssl_subject_dnstringmaybeHAproxy: %{+Q}[ssl_c_s_dn]
ssl_issuer_dnstringmaybeHAproxy: %{+Q}[ssl_c_i_dn]
ssl_client_subject_dnstringmaybeHAproxy: %{+Q}[ssl_c_s_dn]
ssl_client_issuer_dnstringmaybeHAproxy: %{+Q}[ssl_c_i_dn]
ssl_protocolstringmaybeHAproxy: %[ssl_fc_protocol]
ssl_cipherstringmaybeHAproxy: %[ssl_fc_cipher]
ssl_serialstringmaybeSSL certificate serial number
ssl_fingerprintstringmaybeSSL certificate fingerprint
brute_force_counternumberalwaysCurrent brute force attempt counter
account_fieldstringmaybeThe name of the field that contains the account
methodstringmaybeHTTP method (e.g., "GET", "POST")
oidc_cidstringmaybeOIDC Client ID
saml_entity_idstringmaybeSAML Service Provider Entity ID
grant_typestringmaybeOIDC grant/flow type (e.g. authorization_code, device_code)
oidc_client_namestringmaybeHuman-readable OIDC client name
redirect_uristringmaybeOIDC redirect URI from current flow
mfa_completedboolmaybeWhether MFA was completed in current flow
mfa_methodstringmaybeMFA method used (totp, webauthn, recovery)
requested_scopestablemaybeRequested OIDC scopes as Lua table
user_groupstablemaybeUser group memberships (for example LDAP memberOf)
allowed_client_scopestablemaybeScopes configured on current OIDC client
allowed_client_grant_typestablemaybeGrant types configured on current OIDC client
log_formatstringalwaysConfigured log format ("default" or "json")
log_levelstringalwaysConfigured log level
loggingtablealwaysTable containing log_format and log_level
latencynumberalwaysRequest processing latency
http_statusnumberalwaysHTTP status code returned to the client
redis_prefixstringalwaysConfigured Redis key prefix
note

TLS-related values may be retrieved from Nginx and as a fallback tried to be retrieved from HAproxy headers.

tip

It is always a good idea to check the value of a request field, before using it.

Environment Sources

A Lua environment source script must provide the following function:

---@param request table
---@return boolean, boolean, number
function nauthilus_call_environment(request)
return triggered, abort, result -- See details below
end
important

It must return three values: the trigger state, an abort flag for remaining pre-auth checks, and the script result status.

Constants for the returned result

ConstantMeaningValueCategory
nauthilus_builtin.ENVIRONMENT_TRIGGER_NOThe environment source has not been triggeredfalsetrigger
nauthilus_builtin.ENVIRONMENT_TRIGGER_YESThe environment source has been triggered and the request must be rejectedtruetrigger
nauthilus_builtin.ENVIRONMENT_ABORT_NOProcess other environment controls and sourcesfalseskip_flag
nauthilus_builtin.ENVIRONMENT_ABORT_YESAfter finishing the script, skip all other environment controls and sourcestrueskip_flag
nauthilus_builtin.ENVIRONMENT_RESULT_OKThe script finished without errors0failure_info
nauthilus_builtin.ENVIRONMENT_RESULT_FAILSomething went wrong while executing the script1failure_info

Request fields

Only common request fields are present.

Subject Sources

A Lua subject source script must provide the following function:

---@param request table
---@return boolean, number
function nauthilus_call_subject(request)
if request.authenticated then
-- do something
end

return subject_action, result -- See details below
end
important

It must return two values: the subject action and the script result status.

Constants for the returned result

ConstantMeaningValueCategory
nauthilus_builtin.SUBJECT_ACCEPTThe request must be acceptedfalsesubject_action
nauthilus_builtin.SUBJECT_REJECTThe request has to be rejectedtruesubject_action
nauthilus_builtin.SUBJECT_RESULT_OKThe script finished without errors0subject_info
nauthilus_builtin.SUBJECT_RESULT_FAILSomething went wrong while executing the script1subject_info

Request fields

Only common request fields are present.

Actions (including post)

A Lua action script must provide the following function:

---@param request table
---@return number
function nauthilus_call_action(request)
if request.no_auth then
-- Example post action: Store request information in database
end

return failure_info -- See details below
end
important

Actions must return the script status constant.

Constants for the returned result

ConstantMeaningValueCategory
nauthilus_builtin.ACTION_RESULT_OKThe script finished without errors0failure_info
nauthilus_builtin.ACTION_RESULT_FAILThe script finished with errors1failure_info

Request fields

Only common request fields are available.

Lua Backend

The Lua backend script must provide the following function:

---@param request table
---@return number, userdata
function nauthilus_backend_verify_password(request)
local backend_result_object = backend_result:new()
-- Do something with backend_result_object

return failure_info, backend_result_object -- See details below
end

For user account listing, the following function is required:

---@param request table
------@return number, table
function nauthilus_backend_list_accounts(request)
local accounts = {}

return failure_info, accounts -- See details below
end

If you plan on adding TOTP-keys for your users, you must provide the follwing function:

---@param request table
---@return number
function nauthilus_backend_add_totp(request)
return failure_info -- See details below
end
important

The backend must return the result status constant and a backend result object

Constants for the returned result

ConstantMeaningValueCategory
nauthilus_builtin.BACKEND_RESULT_OKThe script finished without errors0failure_info
nauthilus_builtin.BACKEND_RESULT_FAILThe script finished with errors1failure_info

Request fields

Function nauthilus_backend_verify_password request fields

Only common request fields are used.

Function nauthilus_backend_list_accounts request fields

Only "debug" and "session" from the common requests are available.

Function nauthilus_backend_add_totp request fields

Only "debug" and "session" from the common requests as well as "totp_secret" (string) are available.

Cache flush callback

If auth.backends.lua.backend.default.cache_flush_script_path is configured, Nauthilus executes a dedicated Lua callback during:

  • DELETE /api/v1/cache/flush
  • DELETE /api/v1/cache/flush/async

The script must provide:

---@param request table
---@return table|nil, string|nil
function nauthilus_cache_flush(request)
local additional_keys = {
request.redis_prefix .. ":custom:" .. request.username
}

local account_name = "account-value"

return additional_keys, account_name
end

Return contract:

  • First return value: table (array) with additional Redis keys to delete. Non-string items are ignored.
  • Second return value: optional account name (string). If non-empty, Nauthilus skips account lookup and uses this value.

Request fields

Common request fields are available. For cache flush calls, the following are guaranteed to be set:

  • request.username: user name from the flush request payload
  • request.session: generated request GUID
  • request.redis_prefix: configured Redis prefix (storage.redis.prefix)

UserData object backend_result

The nauthilus_backend_result object can be initialized in the Lua backend and in Lua subject sources. The following methods exist:

backend

NameMeaning
authenticatedSet or get the authentication status
user_foundSet or get the user found flag which indicated, if the backend found the user
account_fieldSet or get the account field name, which must have been added to a list of result attributes
totp_secret_fieldSet or get the TOTP secret field name, which must have been added to the result attributes
totp_recovery_fieldNot yet implemented
unique_user_id_fieldSet or get the unique user id field, which must have been added to the result attributes
display_name_fieldSet or get the display name field, which must have been added to the result attributes
attributesSet or get the result attributes as a Lua table

subject sources

Subject sources only have an "attributes" method. While Lua backends do return a nauthlus_backend_result directly, subject sources can only apply it with a Lua function called "nauthilus_backend.apply_backend_result(backend_result_object)".

Attributes can not overwrite existing attributes!

Example usage for nauthilus_backend_result

local attributes = {}
attributes["account"] = "bob"

local b = nauthilus_backend_result.new()
b:attributes(attributes) -- Add the table
b:account_field("account") -- Attributes contain a key "account" for the account field
b:authenticated(true) -- User is authenticated
b:user_found(true) -- The user was found

Endpoints /api/v1/mail/dovecot, /api/v1/generic/user and /api/v1/generic/json

"attributes" represent a common result store for a backend query. All fields that have been set by *_field methods will be used for further internal processing, while all other attributes will be converted to HTTP-response-headers, which will be sent back to the client application that talked to Nauthilus. These headeres will be prefixed with X-Nauthilus-.

For the generic endpoints, "attributes" will bew returned in the JSON respone.


Additional notes

Nauthilus uses the gopher-lua-libs library in all Lua scripts. Please have a look at their documentation for all the modules that can directly be used in Nauthilus scripts:

gopher-lua-libs on Github