Securing /admin URLs
The following are examples for securing /admin URLs as described in the security page.
These are generic examples on how to protect /admin using common web servers. Ensure you test your final implementation to verify that it works.
Apache Example
The following uses a very basic Apache virtual host configuration which works with IXP Manager. We add the new <Location ...> clause to restrict access to /admin/.
<VirtualHost *:443>
ServerName portal.example.net
ServerAdmin webmaster@localhost
DocumentRoot /srv/ixpmanager/public
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
<Directory /srv/ixpmanager/public>
Options FollowSymLinks
AllowOverride None
Require all granted
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} -s [OR]
RewriteCond %{REQUEST_FILENAME} -l [OR]
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule ^.*$ - [NC,L]
RewriteRule ^.*$ /index.php [NC,L]
</Directory>
# This clause is the new element that restricts access to /admin
<Location "/admin/">
# Use a custom error page so as not to confuse legitimate users
ErrorDocument 403 /403-admin.html
<RequireAny>
Require ip 127.0.0.1/8
Require ip 192.0.2.0/28
Require ip 192.0.2.128/27
Require ip 2001:db8:0:100::/64
Require ip 2001:db8:0:101::/64
</RequireAny>
</Location>
ErrorLog ${APACHE_LOG_DIR}/error.log
CustomLog ${APACHE_LOG_DIR}/access.log combined
### SSL config
Include /etc/letsencrypt/options-ssl-apache.conf
SSLCertificateFile /etc/letsencrypt/live/portal.example.net/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/portal.example.net/privkey.pem
</VirtualHost>
<VirtualHost *:80>
ServerName portal.example.net
RewriteCond %{SERVER_NAME} =portal.example.net
RewriteRule ^ https://%{SERVER_NAME}%{REQUEST_URI} [END,NE,R=permanent]
</VirtualHost>
Nginx Example
server {
listen 80;
listen [::]:80;
server_name portal.example.net;
rewrite ^ https://portal.example.net$request_uri? permanent;
}
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name portal.example.net;
ssl_certificate /etc/letsencrypt/live/portal.example.net/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/portal.example.net/privkey.pem;
root /srv/ixpmanager/public;
index index.php;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location /admin/ {
allow 127.0.0.0/8;
allow 192.0.2.0/28;
allow 192.0.2.128/27;
allow 2001:db8:0:100::/64;
allow 2001:db8:0:101::/64;
deny all;
# Use a custom error page so as not to confuse legitimate users
error_page 403 /403-admin.html;
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
include snippets/fastcgi-php.conf;
# With php7.0-fpm:
fastcgi_pass unix:/run/php/php8.4-fpm.sock;
}
}
Varnish Example
1. Define an ACL
acl adminaccess {
# Known safe IP addresses
"127.0.0.0/8";
"192.0.2.0/28";
"192.0.2.128/27";
"2001:db8:0:100::/64";
"2001:db8:0:101::/64";
}
2. Secure access
sub vcl_recv {
# ....
# assume IXP manager is served from portal.example.net:
if (req.http.host == "portal.example.net") {
# Do not allow crawlers access the looking glass:
if (req.url ~ "^/lg/?" && req.http.User-Agent ~ "(?i)(googlebot|ClaudeBot|Amazonbot|DataForSeoBot|AhrefsBot|DotBot|PetalBot|bingbot|SemrushBot|msnbot|YandexBot|GPTBot|SeznamBot)" ) {
return (synth (403, "No bot crawling please"));
}
# point this to your IXP Manager server
set req.backend_hint = ixpmanager;
# secured URLs
if (req.url ~ "^/admin/?" ) {
# Allow if from known safe IP addresses
if (req.http.X-Forwarded-Proto != "https" && client.ip ~ mgmtaccess) {
return(pipe);
} else if (req.http.X-Forwarded-Proto == "https" && std.ip(req.http.X-Real-IP,"0.0.0.0") ~ mgmtaccess) {
return(pipe);
}
# Otherwise, send our custom 403
set req.http.X-Override-Status = "403";
set req.url = "/403-admin.html";
return (hash);
}
# otherwise:
return(pipe);
}
3. Send the 403 in the response
sub vcl_backend_response {
...
# If we set an override status in recv(), carry it through:
if (bereq.http.X-Override-Status) {
# Make the delivered status whatever we want, regardless of backend’s status
set beresp.status = std.integer(bereq.http.X-Override-Status, 200);
# Caching policy for this special response
set beresp.ttl = 0s;
set beresp.uncacheable = true; // deliver but don't store
return (deliver);
}
}