authpf-api

module
v0.3.0-rc3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 16, 2026 License: BSD-3-Clause

README ΒΆ

AuthPF-API

A RESTful HTTP API for pf user rules.

Overview

AuthPF-API is a Go-based REST API that provides a secure interface for managing pf user rules on FreeBSD and OpenBSD systems. It allows users to activate and deactivate pf rules through HTTP endpoints with JWT token authentication and fine-grained permission control. The original authpf is "a user shell for authenticating gateways" -- openbsd.org and is based on SSH logins. AuthPF-API is an alternative by using HTTP/S to load/unload pf user rules.

Features

  • πŸ” JWT Authentication - Secure token-based authentication
  • πŸ‘₯ Role-Based Access Control (RBAC) - Fine-grained permission management
  • ⏱️ Automatic Expiration - Rules automatically expire after configured timeout
  • πŸ”„ Scheduled Cleanup - Periodic cleanup of expired rules
  • πŸ—οΈ Cross-Platform Build - Support for FreeBSD and OpenBSD on multiple architectures
  • πŸ§‘β€πŸ’Ό Runs as User - Runs as Non-Root User - Elevator tool (sudo/doas) support for running pfctl commands
  • 🎚️ PF Macros - User defined pf macro support
  • πŸ“‹ PF Table Support - Automatically track active user IPs in a pf table (global or per-user)

Supported Platforms

Operating Systems
  • FreeBSD
  • OpenBSD
  • macOS
  • Other pf based OS with anchor support
Architectures
  • amd64 (x86-64)
  • arm64 (ARM 64-bit)

Requirements

  • Go 1.24 or higher
  • pfctl binary (usually pre-installed on BSD systems)
  • Optional: sudo or doas for privilege escalation

Installation

From Source
# Clone the repository
git clone https://github.com/scd-systems/authpf-api.git
cd authpf-api

# Build for current system
make build

# Or build for all platforms
make build-all

# Run the application
./build/authpf-api-freebsd-amd64 -foreground
Using Make
# Show available targets
make help

# Build for specific platform
make build GOOS=freebsd GOARCH=amd64

# Run tests
make test

# Generate coverage report
make coverage-html

Configuration

The application is configured via a YAML configuration file. By default, it uses /usr/local/etc/authpf-api-config.yaml.

Configuration Parameters
Defaults Section
Parameter Description Required
defaults.pfctlBinary Path to the pfctl binary executable (e.g., /sbin/pfctl). Must be accessible by the user running the API. Yes
AuthPF Section
Parameter Description Required
authpf.timeout Set the maximum timeout for authpf anchors (e.g., 30m, 1h). Defines how long the pf rules will be active until the scheduler removes it again. Yes
authpf.userRulesRootfolder Root directory where user-specific rule files are stored (e.g., /etc/authpf/users). Each user gets a subdirectory here. Yes
authpf.userRulesFile Filename for user rules within the userRulesRootfolder (e.g., authpf.rules). This file is loaded when a user activates their anchors. Yes
authpf.anchorName Name of the PF anchor to use for rule management (e.g., authpf). Used to organize and manage rules within the packet filter. Yes
authpf.flushFilter List of flush targets for pfctl command (nat, queue, ethernet, rules, info, Sources, Reset). Specifies which rule types to clear when flushing. Yes
authpf.onStartup Specifies the startup anchor loading. Possible Values are (import, importflush). Import just loads existing anchors. Value importflush clears the anchors after importing them from pf. Default none No
authpf.onShutdown Remove all activated user rules when the api server shuts down. Default none No
authpf.pfTable Name of a global pf table to track active user IPs. When set, the user's IP is added to this table on anchor activation and removed on deactivation. The table must exist in pf.conf before starting authpf-api. Leave empty to disable. No
Server Section
Parameter Description Required
server.bind IP address to bind the server to (e.g., 127.0.0.1 for localhost only). Use 0.0.0.0 to listen on all interfaces. Yes
server.port Port number for the HTTP/HTTPS server (e.g., 8080). Ensure the port is not already in use and firewall allows access. Yes
server.ssl.certificate Path to SSL certificate file (leave empty to disable SSL). Required for HTTPS connections. No
server.ssl.key Path to SSL private key file (e.g., key.pem). Must match the certificate and be readable by the server process. No
server.jwtSecret JWT secret key for token signing - MUST be changed before production deployment. Use a strong, random value for security. If not set, a random secret will be generated. No
server.jwtTokenTimeout JWT token timeout in hours (default: 8 hours if not set). Determines how long authentication tokens remain valid. No
server.elevatorMode Elevator mode for privilege escalation (none, sudo, or doas). Required when running server as non-root user (recommended). No
server.logfile Path to the server logfile (e.g., /var/log/authpf-api.log). Ensure the directory exists and is writable by the server process. Yes
RBAC Section
Roles
Parameter Description
rbac.roles.<name>.permissions List of permission strings assigned to this role (e.g., activate_own_rules, view_other_rules). See RBAC - Permissions for all available values.
Users
Parameter Description Required
rbac.users.<name>.password Bcrypt password hash for this user. Generate with ./authpf-api -gen-user-password. Yes
rbac.users.<name>.role Role assigned to this user. Must match a key defined under rbac.roles. Yes
rbac.users.<name>.userId Numeric user ID used as the pf anchor suffix (e.g., authpf/username(1001)). Defaults to 0 if not set. No
rbac.users.<name>.userIp Pin a static IP address passed as the user_ip macro to pfctl. If omitted, the remote client IP of the HTTP request is used. No
rbac.users.<name>.userRulesFile Override the global authpf.userRulesFile for this specific user. No
rbac.users.<name>.macros Map of arbitrary key/value pairs passed as additional -D key=value arguments to pfctl. Must also be declared in the user's pf rules file. No
rbac.users.<name>.pfTable Override the global authpf.pfTable for this specific user. If set, this user's IP is tracked in this table instead of the global one. The table must exist in pf.conf. No
Environment Variables
# Configuration file path
export CONFIG_FILE=/path/to/config.yaml

# Log level (debug, info, warn, error)
export LOG_LEVEL=info
Command-Line Flags
# Show version and exit
./authpf-api -version

# Log to stdout instead of logfile
./authpf-api -foreground

# Generate User Password (bcrypted)
./authpf-api -gen-user-password

API Endpoints

Authentication
Login
POST /login
Content-Type: application/json

{
  "username": "authpf-user1",
  "password": "SHA256"
}

Response (200 OK):

{
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}
AuthPF Rules

All endpoints require JWT authentication via Authorization: Bearer <token> header.

Activate Rule
POST /api/v1/authpf/activate?timeout=30m
Authorization: Bearer <token>
Content-Type: application/json

Response (201 Created):

{
  "status": "activated",
  "user": "authpf-user1",
  "msg": "authpf anchor is being loaded"
}
Deactivate Rule
DELETE /api/v1/authpf/activate
Authorization: Bearer <token>
Content-Type: application/json

{}

Response (202 Accepted):

{
  "status": "queued",
  "user": "authpf-user1",
  "msg": "authpf anchor is being unloaded"
}
Get Status of all activated rules
GET /api/v1/authpf/all
Authorization: Bearer <token>

Response (200 OK):

{
  "authpf-user1": {
    "username": "authpf-user1",
    "timeout": "30m",
    "client_ip": "192.168.1.100",
    "expireat": "2026-01-07T22:00:00Z"
  }
}
Delete All Rules (Admin Only)
DELETE /api/v1/authpf/all
Authorization: Bearer <token>

Response (200 OK):

{
  "status": "cleared"
}
Query Parameters:
Parameter Description
authpf_username Activate/Deactivate the authpf rules from another user (requires activate_other_rules/deactivate_other_rules permissions in role)
timeout Modify the authpf expire timeout (default 30m)

Example:

POST /api/v1/authpf/activate?authpf_username=othername
Authorization: Bearer <token>

RBAC - Permissions

Available Permissions
Permission Description
activate_own_rules Allow user to activate authpf rules
activate_other_rules Allow user to activate rules from other users
deactivate_own_rules Allow user to deactivate their own rules
deactivate_other_rules Allow user to deactivate rules from other users
view_own_rules Allow user to view their own rules status
view_other_rules Allow user to view rules status from other users

User IP and Macro Settings

User IP (userIp)

By default, authpf-api uses the remote client IP address of the HTTP request as the user_ip macro value passed to pfctl when loading a user's anchor rules. This allows pf rules to dynamically reference the connecting client's IP.

For scenarios where the client IP is dynamic, behind a NAT, or should be overridden for security reasons, you can pin a static IP per user via the userIp field in the RBAC user configuration:

rbac:
  users:
    authpf-user1:
      role: user
      userId: 1001
      userIp: 192.168.0.10   # fixed IP β€” client IP from the request is ignored

When userIp is set, the configured value is always passed as -D user_ip=<value> to pfctl, regardless of the actual source IP of the HTTP request. If userIp is omitted, the real client IP is used automatically.

Note: During anchor import on startup (onStartup: import / importflush), the user_ip cannot be recovered from pf and is set to NaN/imported. This has no functional impact β€” deactivation works without a valid IP.


Macro Settings (macros)

In addition to the built-in user_ip and user_id macros, authpf-api supports arbitrary user-defined macros per user. These are passed as additional -D key=value arguments to pfctl when the user's anchor is loaded, making them available inside the user's pf rules file.

rbac:
  users:
    authpf-user1:
      role: user
      userId: 1001
      macros:
        server_1_port: 22
        server_1_addr: 10.127.2.1

The above configuration results in the following pfctl invocation (simplified):

pfctl -a authpf/authpf-user1(1001) \
  -D user_ip=<client_ip> \
  -D user_id=1001 \
  -D server_1_port=22 \
  -D server_1_addr=10.127.2.1 \
  -f /etc/authpf/users/authpf-user1/authpf.rules

The macros must also be declared and used inside the corresponding authpf.rules file of the user:

server_1_port = $server_1_port
server_1_addr = $server_1_addr

pass in proto tcp from $user_ip to $server_1_addr port $server_1_port

Important: When using elevatorMode: sudo, the sudoers Cmnd_Alias must be updated to allow the additional -D arguments. Use a broad regex pattern to cover all macro key/value pairs:

/sbin/pfctl ^-a authpf/([a-zA-Z0-9_-]+)(\([0-9]+\))? -D [a-zA-Z0-9_-]+=[a-zA-Z0-9_.+-]+ -f /etc/authpf/users/[a-zA-Z0-9_-]+/authpf.rules$

PF Table Support

authpf-api can automatically track active user IPs in a pf table β€” similar to the original authpf(8) behaviour. This allows pf rules to reference the set of currently authenticated users by table name.

How it works
  • When a user's anchor is activated, their user_ip is added to the configured pf table via pfctl -t <table> -T add <ip>.
  • When a user's anchor is deactivated, their IP is removed via pfctl -t <table> -T delete <ip>.
  • On startup, authpf-api verifies that all configured tables exist.
  • A global table applies to all users. A per-user table overrides the global one for that specific user.
Priority
user.pfTable (if set)  β†’  authpf.pfTable (global, if set)  β†’  no table management
Configuration

Global table (applies to all users without a per-user override):

authpf:
  pfTable: authpf_users

Per-user table (overrides the global table for this user):

rbac:
  users:
    authpf-user1:
      role: user
      userId: 1001
      pfTable: custom_table_user1   # overrides authpf.pfTable
Required pf.conf entry

The table must be declared as persist in /etc/pf.conf before starting authpf-api:

table <authpf_users> persist

Without persist, pf will remove the table when it becomes empty, causing subsequent pfctl -T add calls to fail.

Error behaviour
Operation On failure
Add IP on activate Fatal β€” HTTP 500 returned, anchor is not activated
Remove IP on deactivate Warn only β€” logged, anchor is still flushed
Remove IP on shutdown / deactivate-all Warn only β€” logged, all anchors are still flushed
Table existence check on startup Fatal β€” server refuses to start
Elevator (sudo) β€” additional rules

When using elevatorMode: sudo, the sudoers Cmnd_Alias must include the table management commands:

/sbin/pfctl ^-t [a-zA-Z0-9_]+ -T add [0-9a-zA-Z:.]+$, \
/sbin/pfctl ^-t [a-zA-Z0-9_]+ -T delete [0-9a-zA-Z:.]+$, \
/sbin/pfctl ^-t [a-zA-Z0-9_]+ -T show$

See the full updated sudo example in the Elevator Setup section below.


Setup PF

Before you can use authpf-api, please verify that the authpf anchors are set up in pf.conf

/etc/pf.conf

nat-anchor "authpf/*"
rdr-anchor "authpf/*"
binat-anchor "authpf/*"
anchor "authpf/*"

Verify also that the anchor name is the same as in the config file (authpf.anchorName: authpf)

Setup SSL for AuthPF-API

Create a Self-Signed CA Root and SSL Certificate

Create CA Root Key

openssl ecparam -genkey -name secp384r1 | openssl ec -aes256 -out rootCA.key

Self-Sign CA Root

openssl req -x509 -new -nodes -key rootCA.key -sha256 -days 3650 -out rootCA.crt

Create a Server Certificate and sign with the CA Root certificate

openssl ecparam -genkey -name prime256v1 -noout -out mydomain.com.key
openssl req -new -key mydomain.com.key -out mydomain.com.csr

cat > ./mydomain.com.ext << _EOF
authorityKeyIdentifier=keyid,issuer
basicConstraints=CA:FALSE
keyUsage = digitalSignature, keyEncipherment
extendedKeyUsage = serverAuth
subjectAltName = @alt_names

[alt_names]
DNS.1 = mydomain.com
DNS.2 = www.mydomain.com
_EOF

openssl x509 -req -in mydomain.com.csr -CA rootCA.crt -CAkey rootCA.key -CAcreateserial -out mydomain.com.crt -days 397 -sha256 -extfile mydomain.com.ext
Configure authpf-api.conf
server:
  ssl: 
    certificate: mydomain.com.crt
    key: mydomain.com.key

Restart authpf-api.

Copy the rootCA.crt to the clients to verify the server.

Elevator Setup

When running authpf-api as non-root user, an elevator setup is required. AuthPF-API currently supports sudo and doas.

Configure Sudo

Sudoers File:

  Cmnd_Alias AUTHPF_API_COMMANDS = /sbin/pfctl -sA, \
      /sbin/pfctl ^-a authpf/([a-zA-Z0-9_-]+)(\([0-9]+\))? -D user_ip=[0-9.]+ -D user_id=[0-9]+ -f /etc/authpf/users/[a-zA-Z0-9_-]+/authpf.rules$, \
      /sbin/pfctl ^-a authpf/([a-zA-Z0-9_-]+)(\([0-9]+\))? -F nat$, \
      /sbin/pfctl ^-a authpf/([a-zA-Z0-9_-]+)(\([0-9]+\))? -F rules$, \
      /sbin/pfctl ^-a authpf/([a-zA-Z0-9_-]+)(\([0-9]+\))? -F queue$, \
      /sbin/pfctl ^-a authpf/([a-zA-Z0-9_-]+)(\([0-9]+\))? -F states$, \
      /sbin/pfctl ^-a authpf/([a-zA-Z0-9_-]+)(\([0-9]+\))? -F Tables$, \
      /sbin/pfctl ^-a authpf/([a-zA-Z0-9_-]+)(\([0-9]+\))? -F all$, \
      /sbin/pfctl ^-t [a-zA-Z0-9_]+ -T add [0-9a-fA-F:.]+$, \
      /sbin/pfctl ^-t [a-zA-Z0-9_]+ -T delete [0-9a-fA-F:.]+$, \
      /sbin/pfctl ^-t [a-zA-Z0-9_]+ -T show$
  %_authpf-api ALL=(root)  NOPASSWD: AUTHPF_API_COMMANDS

Note: The three pf table rules are only required when authpf.pfTable or any rbac.users.<name>.pfTable is configured. They cover IPv4 and IPv6 addresses for add/delete, and the startup existence check for show.

Configure authpf-api.conf

server:
  elevatorMode: sudo
Configure Doas

doas.conf:

permit nopass :authpf as root cmd /sbin/pfctl

Configure authpf-api.conf

server:
  elevatorMode: doas
Doas Security Considerations
  • The doas setup is similar to sudo, but with some restrictions. Doas does not support regular expressions for command arguments yet. A solution can be to use the pfctl_wrapper (found under scripts/).

  • Why not use flush all (pfctl -a "authpf/user" -Fa) and run each filter flushing separately? The flush all under FreeBSD 15.0 RELEASE results in an error (pfctl: Operation not supported by device) with ExitCode 1. It's related to a NETLINK change: https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=291981

Import existing Anchors

Authpf-api is able to import existing authpf anchors. Three modes are configurable:

  • none
  • import
  • importflush

If no import is required, set to none or empty. The "import" mode will parse the output of pfctl -sA and import all existing anchors (authpf/NAME(USERID)). Mode "importflush" will do the same as import, but will remove/flush the anchors directly after importing.

Configure import
authpf:
  onStartup: import # valid modes: none, importflush
Limitations
  1. It is currently not possible to detect the UserIP macro value. The UserIP will be set to "NaN/imported". It does not have any effect. Deactivation works without IP Address.
  2. The Expire datetime will be calculated by the authpf.timeout and current server time.

Development

Running Tests
# Run all tests
make test

# Run tests with verbose output
make test-verbose

# Generate coverage report
make coverage

# Generate HTML coverage report
make coverage-html
Code Quality
# Format code
make fmt

# Run linter
make lint

# Run go vet
make vet
Building
# Build for current system
make build

# Build for FreeBSD
make build-freebsd

# Build for OpenBSD
make build-openbsd

# Build for all platforms
make build-all

Logging

The application uses structured logging with zerolog. Logs can be output to:

  1. Logfile (default) - Configured in server.logfile
  2. Stdout - Use -foreground flag

User Password Generation

The authpf-api uses bcrypt and sha256 in combination for password hashing. Use the -gen-user-password flag to generate a bcrypt hash for a new user password. In the first step, the password input by -gen-user-password will be hashed by sha256 and afterwards hashed again with bcrypt. The authentication against the API is only accepting the user password as a sha256 hash.

Interactive Mode

Generate a password hash interactively (password input is hidden):

./authpf-api -gen-user-password
Enter password:
$2a$10$N9qo8uLOic.......
Piped Mode

Generate a password hash by piping the password:

echo -n "your-password" | ./authpf-api -gen-user-password
$2a$10$N9qo8uLOickgx2ZM........
Adding Users to Configuration

Copy the generated hash and add it to your configuration file (/usr/local/etc/authpf-api.conf):

rbac:
  users:
    authpf-userX:
      password: $2a$10$abcdefghijklmnopqrstuvwxyz1234567890ABCDEFGHIJKLMNOPQR
      role: user
    authpf-admin:
      password: $2a$10$abcdefghijklmnopqrstuvwxyz1234567890ABCDEFGHIJKLMNOPQR
      role: admin
User Management with authpf-api-cli

For comprehensive user and rule management, use the authpf-api-cli tool. Refer to the authpf-api-cli documentation for detailed instructions on managing users, roles, and permissions.

Troubleshooting

Connection Refused
  • Verify server is running: ps aux | grep authpf-api
  • Check bind address and port in configuration
  • Ensure firewall allows connections
Authentication Failed
  • Verify username and password are correct
  • Check user exists in configuration
  • Verify password hash is correct
Rule Not Loading
  • Check pfctl binary path in configuration
  • Verify user has permission to run pfctl
  • Check authpf rules file exists and is readable
  • Review logs for detailed error messages
Permission Denied
  • Verify user role has required permission
  • Check RBAC configuration
  • Review logs for permission errors

Contributing

Contributions are welcome!

Please ensure:

  1. Code follows Go conventions
  2. All tests pass: make test
  3. Code is formatted: make fmt
  4. Linter passes: make lint
  5. Commit messages are descriptive

License

See LICENSE file for details.

Support

For issues, questions, or suggestions, please open an issue in the repository.

Directories ΒΆ

Path Synopsis
internal
api
pkg

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL