Security Guide
Security Guide
Section titled “Security Guide”This guide covers security best practices for deploying and operating Bifrost in production environments.
TLS/HTTPS Configuration
Section titled “TLS/HTTPS Configuration”Enabling TLS on Listeners
Section titled “Enabling TLS on Listeners”server: http: listen: ":8443" tls: enabled: true cert_file: "/etc/bifrost/certs/server.crt" key_file: "/etc/bifrost/certs/server.key"Generating Self-Signed Certificates
Section titled “Generating Self-Signed Certificates”For testing only:
# Generate private keyopenssl genrsa -out server.key 4096
# Generate certificateopenssl req -new -x509 -sha256 -key server.key -out server.crt -days 365 \ -subj "/CN=bifrost.example.com"Using Let’s Encrypt
Section titled “Using Let’s Encrypt”For production, use certificates from Let’s Encrypt:
# Install certbotsudo apt install certbot
# Obtain certificatesudo certbot certonly --standalone -d bifrost.example.com
# Certificate location# /etc/letsencrypt/live/bifrost.example.com/fullchain.pem# /etc/letsencrypt/live/bifrost.example.com/privkey.pemserver: http: tls: enabled: true cert_file: "/etc/letsencrypt/live/bifrost.example.com/fullchain.pem" key_file: "/etc/letsencrypt/live/bifrost.example.com/privkey.pem"Certificate Permissions
Section titled “Certificate Permissions”# Secure key filechmod 600 /etc/bifrost/certs/server.keychown bifrost:bifrost /etc/bifrost/certs/server.keyAuthentication
Section titled “Authentication”Choosing a Provider Type
Section titled “Choosing a Provider Type”Authentication is configured as an ordered list under auth.providers[], each
entry naming a plugin type. (The legacy auth.mode key is rejected at
startup — see the Authentication Guide.)
type |
Use Case | Security Level |
|---|---|---|
none |
Internal networks only | Low |
native |
Small deployments | Medium |
system |
Unix/PAM integration | Medium-High — needs a custom build on Linux, see below |
apikey |
Service-to-service access | Medium-High |
ldap |
Enterprise/AD integration | High |
oauth |
SSO / bearer-token validation | High |
jwt |
Signed-token validation (JWKS or pinned key) | High |
kerberos |
Active Directory SSO (with auth.negotiate) |
High |
mtls |
Certificate / smart-card authentication | High |
totp, hotp, mfa_wrapper |
Second factor on top of a primary provider | High |
ntlm |
Non-functional — rejects every login | n/a |
Warning:
system(PAM) fails closed in the stock builds. On Linux the PAM backend is compiled in only withCGO_ENABLED=1and thepambuild tag. The release binaries and the Docker image are built without both, sotype: systemrejects every login there (it logs a warning and returns false). Build from source withCGO_ENABLED=1 go build -tags pam ./...(needs libpam headers) if you need it, or usenative/ldap/oauthinstead. macOS usesdscl -authonlyand needs no special build; Windows is not supported.
Warning:
ntlmcan never succeed. Bifrost has no credential source against which to verify an NTLM Type 3 response, so the plugin rejects every attempt by design rather than accepting unverified credentials. Usekerberosfor Windows domain SSO.
Native Authentication
Section titled “Native Authentication”Generate secure password hashes using bcrypt:
# Using htpasswd (Apache utils)htpasswd -nbBC 12 "" "your-password" | cut -d: -f2
# Using Pythonpython3 -c "import bcrypt; print(bcrypt.hashpw(b'your-password', bcrypt.gensalt(rounds=12)).decode())"Warning: Password Hash Security
- Always use bcrypt cost factor of 12 or higher
- Never store plaintext passwords in config files
- Rotate passwords regularly
auth: providers: - name: native type: native enabled: true priority: 1 config: users: - username: admin password_hash: "$2a$12$LQv3c1yqBWVHxkd0LHAkCOYz6TtxMQJqhN8/X4.gS6T9I2P9z8K2G"LDAP Security
Section titled “LDAP Security”auth: providers: - name: ldap type: ldap enabled: true priority: 1 config: url: "ldaps://ldap.example.com:636" # Use LDAPS base_dn: "dc=example,dc=com" tls: true insecure_skip_verify: false # Always verify in production bind_dn: "cn=bifrost-svc,ou=services,dc=example,dc=com" bind_password: "${LDAP_BIND_PASSWORD}" # Use environment variable group_lookup_fail_closed: true # Reject the login if the group lookup failsurl and base_dn are both required — the provider refuses to start without
them.
Tip: LDAP Best Practices
- Use a dedicated service account with minimal permissions
- Store bind password in environment variable
- Use LDAPS (port 636) instead of StartTLS
- Verify server certificates
OAuth/OIDC Security
Section titled “OAuth/OIDC Security”auth: providers: - name: oauth type: oauth enabled: true priority: 1 config: client_id: "${OAUTH_CLIENT_ID}" client_secret: "${OAUTH_CLIENT_SECRET}" # Never commit to git issuer_url: "https://auth.example.com" scopes: - openid - profileBifrost validates a bearer token the client already holds (supplied as the proxy
password), so there is no browser redirect and no redirect_url setting. Obtain
the token from your identity provider over TLS and keep the proxy listener on
HTTPS so the token is never sent in the clear.
API Security
Section titled “API Security”API Token Authentication
Section titled “API Token Authentication”Always set an API token for production:
api: enabled: true listen: ":7082" token: "${BIFROST_API_TOKEN}"Generate a secure token:
# Generate random tokenopenssl rand -hex 32Using the API Token
Section titled “Using the API Token”# Header authentication (recommended)curl -H "Authorization: Bearer your-token" http://localhost:7082/api/v1/status
# Query parameter (less secure, avoid in production)curl "http://localhost:7082/api/v1/status?token=your-token"Dashboard sign-in and session cookies
Section titled “Dashboard sign-in and session cookies”The web dashboard authenticates with the same api.token, but it does not keep
the token in the browser when a session store is configured. Signing in POSTs
the token once to /api/v1/login, which returns an HttpOnly session cookie:
api: enabled: true token: "${BIFROST_API_TOKEN}"
session: store: memory # or redis duration: "12h"With that block present:
- The token is sent once and not written to
localStorage. The cookie is HttpOnly, so injected script cannot read it — a stored bearer token can be read by any XSS on the page. - The cookie also authenticates the WebSocket handshake, so no credential is
placed in a URL. A browser cannot set headers on a WS upgrade, which is why
the
?token=form exists at all. - Sign out (in the dashboard’s credential dialog) calls
/api/v1/logout, which destroys the session server-side rather than only clearing browser state. - When the session expires the dashboard prompts for the token again instead of showing failures on every page.
Without a session: block, /api/v1/login answers 503 and the dashboard
falls back to storing the token in localStorage and sending
Authorization: Bearer. That still works, and the dialog says plainly that it
is the weaker posture. Configuring a session store is the recommended
production setup.
Restrict API Access
Section titled “Restrict API Access”Bind the API to localhost if only local access is needed:
api: listen: "127.0.0.1:7082" # Only localhostNetwork Security
Section titled “Network Security”Firewall Configuration
Section titled “Firewall Configuration”Linux (UFW)
Section titled “Linux (UFW)”# Allow proxy ports from specific networkssudo ufw allow from 10.0.0.0/8 to any port 8080 proto tcpsudo ufw allow from 10.0.0.0/8 to any port 1080 proto tcp
# Allow Web UI from admin network onlysudo ufw allow from 192.168.1.0/24 to any port 8081 proto tcp
# Block API from external accesssudo ufw deny 8082/tcpLinux (iptables)
Section titled “Linux (iptables)”# Allow proxy from internal networkiptables -A INPUT -p tcp --dport 8080 -s 10.0.0.0/8 -j ACCEPTiptables -A INPUT -p tcp --dport 8080 -j DROPIP Access Control
Section titled “IP Access Control”Restrict which client IPs may use the proxy with two CIDR lists,
whitelist and blacklist:
access_control: # If non-empty, ONLY IPs matching one of these CIDRs are allowed. # Leave empty to allow all IPs that are not blacklisted. whitelist: - "10.0.0.0/8" - "192.168.0.0/16" # IPs matching any of these CIDRs are always denied. blacklist: - "203.0.113.0/24"Evaluation order:
- The blacklist is checked first — a match is always denied (blacklist takes precedence over the whitelist).
- If the whitelist is non-empty, the IP must match an entry in it; otherwise the request is denied.
- If the whitelist is empty, any IP not on the blacklist is allowed.
Both lists accept individual addresses (e.g. 192.168.1.10) as well as
CIDR ranges. Access control is enforced only when at least one list is
non-empty, and changes are hot-reloaded.
There is no enabled, default_action, or rules key — the presence of
entries in the lists is what activates enforcement.
Rate Limiting
Section titled “Rate Limiting”Protect against abuse with rate limiting:
rate_limit: enabled: true requests_per_second: 100 burst_size: 200 per_ip: true per_user: trueBandwidth Throttling
Section titled “Bandwidth Throttling”Prevent bandwidth abuse:
rate_limit: bandwidth: enabled: true upload: "10Mbps" download: "100Mbps"Secrets Management
Section titled “Secrets Management”Environment Variables
Section titled “Environment Variables”Never commit secrets to version control. Use environment variables:
# config.yamlauth: providers: - name: ldap type: ldap enabled: true priority: 1 config: url: "ldaps://ldap.example.com:636" base_dn: "dc=example,dc=com" bind_dn: "cn=bifrost-svc,ou=services,dc=example,dc=com" bind_password: "${LDAP_PASSWORD}"
api: token: "${API_TOKEN}"# Set environment variablesexport LDAP_PASSWORD="secret"export API_TOKEN="your-secure-token"
# Run with environmentbifrost-server -c config.yamlSystemd Environment Files
Section titled “Systemd Environment Files”Create /etc/bifrost/env:
LDAP_PASSWORD=secretAPI_TOKEN=your-secure-tokenAdd to service file:
[Service]EnvironmentFile=/etc/bifrost/envSecure the file:
chmod 600 /etc/bifrost/envchown root:bifrost /etc/bifrost/envDocker Secrets
Section titled “Docker Secrets”# docker-compose.ymlservices: bifrost-server: environment: - API_TOKEN_FILE=/run/secrets/api_token secrets: - api_token
secrets: api_token: file: ./secrets/api_token.txtLogging Security
Section titled “Logging Security”Sensitive Data
Section titled “Sensitive Data”Bifrost automatically redacts sensitive data from logs, but review your configuration:
logging: level: info # Avoid 'debug' in production format: jsonWarning: Log Review
- Regularly review logs for sensitive data leaks
- Don’t log full request/response bodies in production
- Secure log files with appropriate permissions
Log File Permissions
Section titled “Log File Permissions”chmod 640 /var/log/bifrost/*.logchown bifrost:bifrost /var/log/bifrost/*.logSecurity Checklist
Section titled “Security Checklist”Pre-Production
Section titled “Pre-Production”- TLS enabled on all public listeners
- Valid certificates (not self-signed)
- Authentication enabled
- API token set
- Secrets in environment variables (not config)
- Config file permissions restricted (600)
- Firewall rules configured
- Rate limiting enabled
Regular Maintenance
Section titled “Regular Maintenance”- Rotate API tokens quarterly
- Update TLS certificates before expiry
- Review access logs for anomalies
- Update to latest Bifrost version
- Audit user accounts
- Test backup and recovery procedures
Incident Response
Section titled “Incident Response”- Document API endpoints and access
- Have a process for revoking tokens
- Know how to disable authentication temporarily
- Have backup configurations ready
- Know how to review logs quickly
Security Vulnerabilities
Section titled “Security Vulnerabilities”Reporting
Section titled “Reporting”If you discover a security vulnerability, please report it responsibly:
- Do not open a public GitHub issue
- Email the maintainers directly
- Include:
- Description of the vulnerability
- Steps to reproduce
- Potential impact
- Suggested fix (if any)
Updates
Section titled “Updates”Subscribe to releases to stay informed about security updates:
# Watch the repository on GitHub# Or check releases periodicallycurl -s https://api.github.com/repos/rennerdo30/bifrost-proxy/releases/latest | jq -r .tag_name