Reverse Proxy Configuration ============================ In production deployments, Inmor runs behind a reverse proxy that handles TLS termination. This is the recommended setup as it: * Provides TLS/HTTPS encryption for external clients * Allows the internal services to communicate over HTTP * Enables load balancing and high availability * Simplifies certificate management Architecture ------------ .. code-block:: text Internet │ ▼ ┌────────────────────────────────────────┐ │ Reverse Proxy │ │ (nginx/Apache/Caddy) │ │ │ │ *.federation.example.com:443 (TLS) │ └────────────────────────────────────────┘ │ │ ▼ ▼ ┌──────────────┐ ┌──────────────┐ │ Trust Anchor │ │ Admin Portal │ │ :8080 (HTTP) │ │ :8000 (HTTP) │ └──────────────┘ └──────────────┘ The reverse proxy terminates TLS and forwards requests to the internal HTTP services. .. _securing-admin-api: Securing the Admin API ---------------------- .. danger:: **The Admin API MUST be protected in production.** The Admin API provides unrestricted management access to the Trust Anchor. An attacker with access could: * Issue fraudulent trust marks to malicious entities * Revoke legitimate trust marks, disrupting federation operations * Add rogue subordinates to the federation * Modify or destroy the Trust Anchor configuration **At minimum, protect the Admin API with HTTP Basic Authentication.** Basic Authentication Setup ^^^^^^^^^^^^^^^^^^^^^^^^^^ **nginx** 1. Create a password file:: sudo apt install apache2-utils # For htpasswd sudo htpasswd -c /etc/nginx/.htpasswd admin 2. Add to your server block:: auth_basic "Admin API"; auth_basic_user_file /etc/nginx/.htpasswd; **Apache (httpd)** 1. Create a password file:: sudo htpasswd -c /etc/httpd/.htpasswd admin 2. Add to your VirtualHost:: AuthType Basic AuthName "Admin API" AuthUserFile /etc/httpd/.htpasswd Require valid-user **Caddy** Use the ``basicauth`` directive in your Caddyfile:: admin.federation.example.com { basicauth { admin $2a$14$... # Use: caddy hash-password } reverse_proxy localhost:8000 } Generate password hash with: ``caddy hash-password`` nginx Configuration ------------------- Complete Setup ^^^^^^^^^^^^^^ Create ``/etc/nginx/sites-available/inmor.conf``: .. code-block:: nginx # Trust Anchor - Federation endpoints (public) server { listen 443 ssl http2; server_name federation.example.com; ssl_certificate /etc/letsencrypt/live/federation.example.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/federation.example.com/privkey.pem; # Modern TLS configuration ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256; ssl_prefer_server_ciphers off; # Security headers add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; # Proxy to Trust Anchor location / { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } } # Admin Portal - Management API (protected) server { listen 443 ssl http2; server_name admin.federation.example.com; ssl_certificate /etc/letsencrypt/live/admin.federation.example.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/admin.federation.example.com/privkey.pem; ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256; ssl_prefer_server_ciphers off; # REQUIRED: Basic authentication auth_basic "Admin API"; auth_basic_user_file /etc/nginx/.htpasswd; # Optional: IP allowlist (additional layer) # allow 10.0.0.0/8; # allow 192.168.0.0/16; # deny all; location / { proxy_pass http://127.0.0.1:8000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } } # Redirect HTTP to HTTPS server { listen 80; server_name federation.example.com admin.federation.example.com; return 301 https://$server_name$request_uri; } Enable and test:: sudo ln -s /etc/nginx/sites-available/inmor.conf /etc/nginx/sites-enabled/ sudo nginx -t sudo systemctl reload nginx Single Domain Setup (nginx) ^^^^^^^^^^^^^^^^^^^^^^^^^^^ If you prefer a single domain with path-based routing: .. code-block:: nginx server { listen 443 ssl http2; server_name federation.example.com; ssl_certificate /etc/letsencrypt/live/federation.example.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/federation.example.com/privkey.pem; # Trust Anchor endpoints (public) location /.well-known/openid-federation { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Forwarded-Proto $scheme; } location ~ ^/(fetch|list|resolve|trust_mark|trust_mark_list|trust_mark_status|historical_keys|collection)$ { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Forwarded-Proto $scheme; } # Admin API (protected with basic auth) location /api/ { auth_basic "Admin API"; auth_basic_user_file /etc/nginx/.htpasswd; proxy_pass http://127.0.0.1:8000; proxy_set_header Host $host; proxy_set_header X-Forwarded-Proto $scheme; } # Default - Trust Anchor location / { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Forwarded-Proto $scheme; } } Rate Limiting (nginx) ^^^^^^^^^^^^^^^^^^^^^ Add rate limiting for public endpoints: .. code-block:: nginx # In http block limit_req_zone $binary_remote_addr zone=federation:10m rate=10r/s; # In server/location block location / { limit_req zone=federation burst=20 nodelay; proxy_pass http://127.0.0.1:8080; } Apache (httpd) Configuration ----------------------------- Prerequisites ^^^^^^^^^^^^^ Enable required modules:: sudo a2enmod proxy proxy_http ssl headers rewrite Complete Setup ^^^^^^^^^^^^^^ Create ``/etc/apache2/sites-available/inmor.conf`` (Debian/Ubuntu) or ``/etc/httpd/conf.d/inmor.conf`` (RHEL/CentOS): .. code-block:: apache # Trust Anchor - Federation endpoints (public) ServerName federation.example.com SSLEngine on SSLCertificateFile /etc/letsencrypt/live/federation.example.com/fullchain.pem SSLCertificateKeyFile /etc/letsencrypt/live/federation.example.com/privkey.pem # Modern TLS configuration SSLProtocol all -SSLv3 -TLSv1 -TLSv1.1 SSLCipherSuite ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256 # Security headers Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains" # Proxy to Trust Anchor ProxyPreserveHost On ProxyPass / http://127.0.0.1:8080/ ProxyPassReverse / http://127.0.0.1:8080/ RequestHeader set X-Forwarded-Proto "https" # Admin Portal - Management API (protected) ServerName admin.federation.example.com SSLEngine on SSLCertificateFile /etc/letsencrypt/live/admin.federation.example.com/fullchain.pem SSLCertificateKeyFile /etc/letsencrypt/live/admin.federation.example.com/privkey.pem SSLProtocol all -SSLv3 -TLSv1 -TLSv1.1 SSLCipherSuite ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256 # Proxy to Admin Portal ProxyPreserveHost On ProxyPass / http://127.0.0.1:8000/ ProxyPassReverse / http://127.0.0.1:8000/ RequestHeader set X-Forwarded-Proto "https" # REQUIRED: Basic authentication AuthType Basic AuthName "Admin API" AuthUserFile /etc/httpd/.htpasswd Require valid-user # Optional: IP allowlist (additional layer) # # Require ip 10.0.0.0/8 192.168.0.0/16 # # Redirect HTTP to HTTPS ServerName federation.example.com ServerAlias admin.federation.example.com Redirect permanent / https://federation.example.com/ Enable and test:: # Debian/Ubuntu sudo a2ensite inmor.conf sudo apache2ctl configtest sudo systemctl reload apache2 # RHEL/CentOS sudo httpd -t sudo systemctl reload httpd Single Domain Setup (Apache) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. code-block:: apache ServerName federation.example.com SSLEngine on SSLCertificateFile /etc/letsencrypt/live/federation.example.com/fullchain.pem SSLCertificateKeyFile /etc/letsencrypt/live/federation.example.com/privkey.pem # Trust Anchor endpoints (public) ProxyPass /.well-known/openid-federation http://127.0.0.1:8080/.well-known/openid-federation ProxyPassReverse /.well-known/openid-federation http://127.0.0.1:8080/.well-known/openid-federation ProxyPassMatch ^/(fetch|list|resolve|trust_mark|trust_mark_list|trust_mark_status|historical_keys|collection)$ http://127.0.0.1:8080/$1 ProxyPassReverse / http://127.0.0.1:8080/ # Admin API (protected with basic auth) AuthType Basic AuthName "Admin API" AuthUserFile /etc/httpd/.htpasswd Require valid-user ProxyPass http://127.0.0.1:8000/api/ ProxyPassReverse http://127.0.0.1:8000/api/ # Default - Trust Anchor ProxyPass / http://127.0.0.1:8080/ ProxyPassReverse / http://127.0.0.1:8080/ RequestHeader set X-Forwarded-Proto "https" Rate Limiting (Apache) ^^^^^^^^^^^^^^^^^^^^^^ Use mod_ratelimit or mod_qos:: # Enable module sudo a2enmod ratelimit # In VirtualHost SetOutputFilter RATE_LIMIT SetEnv rate-limit 500 Caddy Configuration ------------------- Caddy provides automatic HTTPS with Let's Encrypt by default. Complete Setup ^^^^^^^^^^^^^^ Create ``/etc/caddy/Caddyfile``: .. code-block:: text # Trust Anchor - Federation endpoints (public) federation.example.com { reverse_proxy localhost:8080 header Strict-Transport-Security "max-age=31536000; includeSubDomains" } # Admin Portal - Management API (protected) admin.federation.example.com { # REQUIRED: Basic authentication basicauth { admin $2a$14$Zkx19XLiW6VYouLHR5NmfOFU0z2GTNmpkT/5qqR7hx4IjWJPDhjvG } # Optional: IP allowlist (additional layer) # @allowed remote_ip 10.0.0.0/8 192.168.0.0/16 # handle @allowed { # reverse_proxy localhost:8000 # } # respond 403 reverse_proxy localhost:8000 } Generate password hash:: caddy hash-password Enable and run:: sudo systemctl enable caddy sudo systemctl start caddy # Or reload after config changes sudo systemctl reload caddy Single Domain Setup (Caddy) ^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. code-block:: text federation.example.com { # Trust Anchor endpoints (public) handle /.well-known/openid-federation { reverse_proxy localhost:8080 } handle_path /fetch { reverse_proxy localhost:8080 } handle_path /list { reverse_proxy localhost:8080 } handle_path /resolve { reverse_proxy localhost:8080 } handle_path /trust_mark* { reverse_proxy localhost:8080 } handle_path /historical_keys { reverse_proxy localhost:8080 } # Admin API (protected with basic auth) handle /api/* { basicauth { admin $2a$14$... } reverse_proxy localhost:8000 } # Default - Trust Anchor handle { reverse_proxy localhost:8080 } } Rate Limiting (Caddy) ^^^^^^^^^^^^^^^^^^^^^ Use the rate_limit directive (requires caddy-ratelimit plugin):: federation.example.com { rate_limit { zone dynamic { key {remote_host} events 10 window 1s } } reverse_proxy localhost:8080 } TLS Certificate Management -------------------------- Let's Encrypt with Certbot (nginx) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ :: # Install certbot sudo apt install certbot python3-certbot-nginx # Obtain certificates sudo certbot --nginx -d federation.example.com -d admin.federation.example.com # Auto-renewal is configured automatically sudo systemctl status certbot.timer Let's Encrypt with Certbot (Apache) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ :: # Install certbot sudo apt install certbot python3-certbot-apache # Obtain certificates sudo certbot --apache -d federation.example.com -d admin.federation.example.com Let's Encrypt with Caddy ^^^^^^^^^^^^^^^^^^^^^^^^ Caddy handles TLS certificates automatically. No additional configuration needed. Certificates are obtained and renewed automatically when Caddy starts. Manual Certificate Installation ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ For manually obtained certificates:: # Create certificate directory sudo mkdir -p /etc/ssl/inmor # Copy certificates sudo cp fullchain.pem /etc/ssl/inmor/ sudo cp privkey.pem /etc/ssl/inmor/ sudo chmod 600 /etc/ssl/inmor/privkey.pem Security Best Practices ----------------------- 1. **Always Protect Admin API** Never expose the Admin API without authentication. Use Basic Auth at minimum, consider IP allowlisting or VPN for additional security. 2. **Use Strong TLS Configuration** * Disable TLS 1.0 and 1.1 * Use modern cipher suites * Enable HSTS 3. **Enable Logging** **nginx**:: access_log /var/log/nginx/inmor-access.log; error_log /var/log/nginx/inmor-error.log; **Apache**:: ErrorLog ${APACHE_LOG_DIR}/inmor-error.log CustomLog ${APACHE_LOG_DIR}/inmor-access.log combined **Caddy**:: federation.example.com { log { output file /var/log/caddy/inmor-access.log } reverse_proxy localhost:8080 } 4. **Rate Limiting** Implement rate limiting on public endpoints to prevent abuse. 5. **Regular Updates** Keep your reverse proxy software updated for security patches. Updating Django Settings ------------------------ When running behind a reverse proxy, update ``localsettings.py``:: # REQUIRED: Disable Django's HTTPS redirect — the proxy handles it. # Without this, POST requests get 301-redirected and lose their body. SECURE_SSL_REDIRECT = False # REQUIRED: Trust the X-Forwarded-Proto header from the proxy so Django # knows the original request was HTTPS (needed for CSRF, secure cookies). SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https') USE_X_FORWARDED_HOST = True # Set the canonical domain TA_DOMAIN = 'https://federation.example.com' TRUSTMARK_PROVIDER = 'https://federation.example.com' # Allow the proxy host ALLOWED_HOSTS = ['federation.example.com', 'admin.federation.example.com', 'localhost'] Testing the Setup ----------------- 1. Test the Trust Anchor:: curl https://federation.example.com/.well-known/openid-federation 2. Test trust chain resolution:: curl "https://federation.example.com/resolve?sub=https://example-rp.com&trust_anchor=https://federation.example.com" 3. Test Admin API with authentication:: curl -u admin:password https://admin.federation.example.com/api/v1/trustmarktypes