Access Control
Stalwart provides a flexible access control mechanism for the HTTP server. Rules can restrict access by IP address, resource path, method name, listener identity, and other request attributes, so that sensitive services can be exposed only to the clients and listeners that require them.
Configuration
Access rules are carried by the allowedEndpoints expression on the Http singleton (found in the WebUI under Settings › Network › HTTP › General, Settings › Network › HTTP › Security). The expression is evaluated for each incoming request; it must return an HTTP status code. A result of 200 permits the request, while any other value denies it and the server responds with the returned status. By default the expression evaluates to 200, allowing every endpoint.
Examples
Restriction by IP address
The following rule restricts access to /api/* to requests originating from 127.0.0.1:
{
"allowedEndpoints": {
"match": [
{"if": "starts_with(url_path, '/api') && remote_ip != '127.0.0.1'", "then": "404"}
],
"else": "200"
}
}
Restriction by IP range
The following rule allows public access to /robots.txt and /.well-known/*; all other requests are denied unless they originate from the 192.168.1.* network.
{
"allowedEndpoints": {
"match": [
{"if": "starts_with(remote_ip, '192.168.1.') || contains(['robots.txt', '.well-known'], split(url_path, '/')[1])", "then": "200"}
],
"else": "400"
}
}
Restriction by listener
Two HTTP listeners can be defined on the NetworkListener object (found in the WebUI under Settings › Network › Listeners): a private-http listener bound to localhost, and a public-http listener bound to all interfaces. HTTP requests arriving through private-http are unrestricted, while requests coming from public-http are only allowed for the /jmap, /robots.txt, and /.well-known/* endpoints.
The rule on the Http singleton:
{
"allowedEndpoints": {
"match": [
{"if": "listener == 'private-http' || contains(['jmap', 'robots.txt', '.well-known'], split(url_path, '/')[1])", "then": "200"}
],
"else": "404"
}
}
Restriction by endpoint and method
The following rule disables JMAP access unless the request is an OPTIONS request:
{
"allowedEndpoints": {
"match": [
{"if": "!starts_with(url, '/jmap') || method == 'OPTIONS'", "then": "200"}
],
"else": "400"
}
}