Skip to content

Security Hardening

Thomas Mangin edited this page Nov 15, 2025 · 5 revisions

Security Hardening ExaBGP

Best practices for securing ExaBGP deployments

πŸ”’ Defense in depth - multiple layers of security protect your BGP infrastructure


Table of Contents


Overview

Version-Specific Security Improvements

βœ… ExaBGP 5.0.0 Security Fix: TOCTOU (Time-of-Check-Time-of-Use) vulnerability fixed in configuration parser. Upgrading to 5.0.0 is recommended for security-conscious deployments.

ExaBGP 5.0.0 Security Enhancements:

  • βœ… TOCTOU Fix: Fixed race condition in configuration file parsing that could allow unauthorized config modifications
  • βœ… Configuration Reload Fix: Improved API process state consistency during reload operations (#1340)
  • βœ… Python 3.8+ Only: Removed Python 2 support (Python 2 has known security vulnerabilities and no updates)

Recommendation: Upgrade to ExaBGP 5.0.0 for latest security fixes. See 4.x β†’ 5.0.0 Migration Guide.


Security Principles

ExaBGP security follows these principles:

  1. Authentication: Verify BGP peer identity
  2. Authorization: Control what peers can do
  3. Isolation: Separate privileges and processes
  4. Monitoring: Detect suspicious activity
  5. Defense in Depth: Multiple security layers

Attack Surface

Where ExaBGP can be attacked:

Component               Attack Vectors
-----------------------------------------
BGP sessions            Spoofing, MITM, session hijacking
API processes           Code injection, privilege escalation
Configuration files     Unauthorized modification
Log files              Information disclosure
Network interfaces      Eavesdropping, packet injection
Process memory          Buffer overflows (unlikely in Python)

Threat Model

Threat Categories

1. Network Threats

  • BGP session hijacking
  • TCP connection spoofing
  • Man-in-the-middle attacks
  • Route injection/hijacking
  • Denial of service

2. Local Threats

  • Privilege escalation
  • Configuration tampering
  • API process exploitation
  • Log file manipulation
  • Resource exhaustion

3. Application Threats

  • Malicious API processes
  • Code injection
  • Dependency vulnerabilities
  • Configuration errors

Risk Assessment

High Risk:

  • Unauthenticated BGP sessions
  • Running as root
  • World-writable configuration files
  • No firewall rules
  • Unvalidated route announcements

Medium Risk:

  • Weak MD5 passwords
  • Verbose logging with sensitive data
  • Shared user accounts
  • No audit trail

Low Risk:

  • Outdated ExaBGP version (rare vulnerabilities)
  • Default configuration paths

BGP Authentication

MD5 Authentication (TCP MD5)

Configure MD5 authentication:

# ExaBGP configuration
neighbor 192.168.1.1 {
    router-id 192.168.1.2;
    local-address 192.168.1.2;
    local-as 65001;
    peer-as 65000;

    # MD5 password (RFC 2385)
    md5-password "SuperSecretPassword123!";

    family {
        ipv4 unicast;
    }
}

Corresponding router configuration (Cisco):

router bgp 65000
 neighbor 192.168.1.2 remote-as 65001
 neighbor 192.168.1.2 password SuperSecretPassword123!

Corresponding router configuration (Juniper):

protocols {
    bgp {
        group exabgp {
            neighbor 192.168.1.2 {
                authentication-key "SuperSecretPassword123!";
            }
        }
    }
}

Password Management

Best practices:

1. Use strong passwords:

# Generate strong password (20 characters)
openssl rand -base64 20

# Example output:
# VXk3dGF3Zm9zNHMxMmRhc2Rhc2Q=

2. Store passwords securely:

# Don't commit passwords to Git!
# Use environment variables or secrets management

# .gitignore
*.secret
secrets/

# Store in separate file
cat > /etc/exabgp/secrets/peer-password.txt <<EOF
VXk3dGF3Zm9zNHMxMmRhc2Rhc2Q=
EOF

chmod 600 /etc/exabgp/secrets/peer-password.txt
chown exabgp:exabgp /etc/exabgp/secrets/peer-password.txt

3. Reference password from file:

#!/bin/bash
# Generate config with password from secret file

PASSWORD=$(cat /etc/exabgp/secrets/peer-password.txt)

cat > /etc/exabgp/exabgp.conf <<EOF
neighbor 192.168.1.1 {
    md5-password "${PASSWORD}";
}
EOF

TCP-AO (TCP Authentication Option)

More secure alternative to MD5 (RFC 5925):

Note: TCP-AO support depends on OS kernel version (Linux 5.12+)

# Check if kernel supports TCP-AO
grep TCP_AO /boot/config-$(uname -r)

Configuration (when supported):

neighbor 192.168.1.1 {
    # TCP-AO is more secure than MD5
    # Check ExaBGP documentation for current support status
    tcp-ao {
        key-id 1;
        recv-id 1;
        algorithm hmac-sha-256;
        password "StrongPassword123!";
    }
}

TTL Security (GTSM)

Generalized TTL Security Mechanism (RFC 5082):

Protects against remote attacks by requiring TTL=255 (directly connected peers).

ExaBGP configuration:

neighbor 192.168.1.1 {
    router-id 192.168.1.2;
    local-as 65001;
    peer-as 65000;

    # TTL must be 255 (directly connected)
    ttl-security 255;
}

Router configuration (Cisco):

router bgp 65000
 neighbor 192.168.1.2 ttl-security hops 1

Router configuration (Juniper):

protocols {
    bgp {
        group exabgp {
            neighbor 192.168.1.2 {
                multihop {
                    ttl 255;
                }
            }
        }
    }
}

Process Isolation

Run as Dedicated User

Never run ExaBGP as root!

Create dedicated user:

# Create exabgp user (no login shell)
useradd -r -s /bin/false -d /var/lib/exabgp exabgp

# Create directories
mkdir -p /etc/exabgp /var/log/exabgp /var/lib/exabgp

# Set ownership
chown -R exabgp:exabgp /etc/exabgp /var/log/exabgp /var/lib/exabgp

# Set permissions
chmod 750 /etc/exabgp
chmod 750 /var/log/exabgp
chmod 750 /var/lib/exabgp

Systemd Service Hardening

Secure systemd service configuration:

# /etc/systemd/system/exabgp.service
[Unit]
Description=ExaBGP
After=network.target
Documentation=https://github.com/Exa-Networks/exabgp

[Service]
Type=simple
ExecStart=/usr/local/bin/exabgp /etc/exabgp/exabgp.conf

# Run as dedicated user
User=exabgp
Group=exabgp

# Security hardening
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/var/log/exabgp
ProtectKernelTunables=true
ProtectKernelModules=true
ProtectControlGroups=true
RestrictRealtime=true
RestrictSUIDSGID=true
LockPersonality=true
MemoryDenyWriteExecute=true

# Restrict capabilities (CAP_NET_BIND_SERVICE not needed if not binding <1024)
CapabilityBoundingSet=
AmbientCapabilities=

# Restrict network access
RestrictAddressFamilies=AF_INET AF_INET6

# Resource limits
LimitNOFILE=4096
LimitNPROC=64
TasksMax=128

# Restart policy
Restart=always
RestartSec=10

# Logging
StandardOutput=append:/var/log/exabgp/exabgp.log
StandardError=append:/var/log/exabgp/exabgp.log
SyslogIdentifier=exabgp

[Install]
WantedBy=multi-user.target

Reload and restart:

systemctl daemon-reload
systemctl restart exabgp
systemctl status exabgp

AppArmor Profile

Create AppArmor profile for additional isolation:

# /etc/apparmor.d/usr.local.bin.exabgp
#include <tunables/global>

/usr/local/bin/exabgp {
  #include <abstractions/base>
  #include <abstractions/python>
  #include <abstractions/nameservice>

  # ExaBGP binary
  /usr/local/bin/exabgp r,
  /usr/bin/python3* ix,

  # Configuration files
  /etc/exabgp/** r,

  # API processes
  /etc/exabgp/processes/*.py r,
  /usr/local/bin/healthcheck.py r,

  # Logs
  /var/log/exabgp/** w,

  # PID file
  /var/run/exabgp.pid w,

  # Network
  network inet stream,
  network inet6 stream,

  # Deny everything else
  deny /** w,
}

Enable AppArmor profile:

# Load profile
apparmor_parser -r /etc/apparmor.d/usr.local.bin.exabgp

# Enable enforcement
aa-enforce /usr/local/bin/exabgp

# Check status
aa-status | grep exabgp

User Permissions

File Permissions

Secure file permissions:

# Configuration files (read-only for exabgp user)
chmod 640 /etc/exabgp/exabgp.conf
chown root:exabgp /etc/exabgp/exabgp.conf

# API processes (read-only, executable)
chmod 750 /etc/exabgp/processes/
chmod 750 /etc/exabgp/processes/*.py
chown root:exabgp /etc/exabgp/processes/*.py

# Log directory (writable by exabgp)
chmod 750 /var/log/exabgp
chown exabgp:exabgp /var/log/exabgp

# Secrets (read-only by exabgp only)
chmod 600 /etc/exabgp/secrets/
chown exabgp:exabgp /etc/exabgp/secrets/

Verify permissions:

#!/bin/bash
# Check ExaBGP file permissions

echo "=== ExaBGP Security Audit ==="
echo

echo "Configuration files:"
ls -la /etc/exabgp/*.conf

echo
echo "API processes:"
ls -la /etc/exabgp/processes/

echo
echo "Log files:"
ls -la /var/log/exabgp/

echo
echo "Secrets:"
ls -la /etc/exabgp/secrets/

echo
echo "Process user:"
ps aux | grep exabgp | grep -v grep

Sudo Access (if needed)

Minimal sudo access for API processes:

# /etc/sudoers.d/exabgp

# Allow exabgp to reload specific services (no password)
exabgp ALL=(ALL) NOPASSWD: /bin/systemctl reload nginx
exabgp ALL=(ALL) NOPASSWD: /bin/systemctl reload haproxy

# Allow IP address management
exabgp ALL=(ALL) NOPASSWD: /sbin/ip addr add *
exabgp ALL=(ALL) NOPASSWD: /sbin/ip addr del *

# Deny everything else (explicit)
exabgp ALL=(ALL) !/bin/bash
exabgp ALL=(ALL) !/bin/sh
exabgp ALL=(ALL) !/usr/bin/su

Test sudo permissions:

# As exabgp user
sudo -l

Firewall Rules

Iptables Rules

Restrict BGP access to known peers:

#!/bin/bash
# ExaBGP firewall rules

# BGP peers (update with your peer IPs)
BGP_PEERS=(
    "192.168.1.1"
    "192.168.1.2"
    "2001:db8::1"
)

# Flush existing BGP rules
iptables -D INPUT -p tcp --dport 179 -j ACCEPT 2>/dev/null
ip6tables -D INPUT -p tcp --dport 179 -j ACCEPT 2>/dev/null

# Allow established connections
iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
ip6tables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT

# Allow BGP from known peers only
for peer in "${BGP_PEERS[@]}"; do
    if [[ "$peer" =~ : ]]; then
        # IPv6
        ip6tables -A INPUT -p tcp -s "$peer" --dport 179 -j ACCEPT
    else
        # IPv4
        iptables -A INPUT -p tcp -s "$peer" --dport 179 -j ACCEPT
    fi
done

# Drop all other BGP traffic
iptables -A INPUT -p tcp --dport 179 -j DROP
ip6tables -A INPUT -p tcp --dport 179 -j DROP

# Log dropped BGP packets
iptables -A INPUT -p tcp --dport 179 -j LOG --log-prefix "BGP-DROP: "
ip6tables -A INPUT -p tcp --dport 179 -j LOG --log-prefix "BGP-DROP: "

# Save rules
iptables-save > /etc/iptables/rules.v4
ip6tables-save > /etc/iptables/rules.v6

Nftables Rules (Modern Alternative)

Modern firewall with nftables:

#!/usr/sbin/nft -f

# /etc/nftables.conf

table inet filter {
    # Define BGP peers
    set bgp_peers_v4 {
        type ipv4_addr
        elements = { 192.168.1.1, 192.168.1.2 }
    }

    set bgp_peers_v6 {
        type ipv6_addr
        elements = { 2001:db8::1, 2001:db8::2 }
    }

    chain input {
        type filter hook input priority 0; policy drop;

        # Allow established connections
        ct state established,related accept

        # Allow loopback
        iif lo accept

        # Allow BGP from known peers
        tcp dport 179 ip saddr @bgp_peers_v4 accept
        tcp dport 179 ip6 saddr @bgp_peers_v6 accept

        # Log and drop other BGP
        tcp dport 179 log prefix "BGP-DROP: " drop
    }
}

Apply rules:

nft -f /etc/nftables.conf
systemctl enable nftables

Rate Limiting

Protect against BGP connection floods:

# Iptables rate limiting
iptables -A INPUT -p tcp --dport 179 -m state --state NEW \
    -m recent --set --name BGP

iptables -A INPUT -p tcp --dport 179 -m state --state NEW \
    -m recent --update --seconds 60 --hitcount 10 --name BGP \
    -j DROP

Nftables rate limiting:

table inet filter {
    chain input {
        # Limit BGP connections to 10/minute
        tcp dport 179 ct state new \
            limit rate 10/minute accept
    }
}

Route Filtering

Prefix Filtering

Filter announced routes in API process:

#!/usr/bin/env python3
"""
Secure route announcer with prefix validation
"""
import sys
import ipaddress

# Whitelist of allowed prefixes
ALLOWED_PREFIXES = [
    ipaddress.ip_network('100.10.0.0/16'),
    ipaddress.ip_network('203.0.113.0/24'),
]

def is_prefix_allowed(prefix_str):
    """Check if prefix is in whitelist"""
    try:
        prefix = ipaddress.ip_network(prefix_str)

        for allowed in ALLOWED_PREFIXES:
            if prefix.subnet_of(allowed):
                return True

        return False
    except:
        return False

def announce_route(prefix, **attributes):
    """Announce route only if allowed"""
    if not is_prefix_allowed(prefix):
        sys.stderr.write(f"SECURITY: Blocked announcement of {prefix}\n")
        return

    # Build announcement
    cmd = f"announce route {prefix}"

    if 'next_hop' in attributes:
        cmd += f" next-hop {attributes['next_hop']}"

    sys.stdout.write(cmd + "\n")
    sys.stdout.flush()

    sys.stderr.write(f"ALLOWED: Announced {prefix}\n")

# Example usage
announce_route('100.10.0.100/32', next_hop='self')
announce_route('1.1.1.1/32', next_hop='self')  # Blocked!

Attribute Validation

Validate BGP attributes:

#!/usr/bin/env python3
"""
Validate BGP attributes before announcing
"""
import sys

def validate_announcement(prefix, next_hop, med=None, as_path=None):
    """Validate BGP announcement parameters"""

    # Check prefix format
    if not is_valid_prefix(prefix):
        sys.stderr.write(f"SECURITY: Invalid prefix format: {prefix}\n")
        return False

    # Check next-hop
    if next_hop != 'self' and not is_valid_ip(next_hop):
        sys.stderr.write(f"SECURITY: Invalid next-hop: {next_hop}\n")
        return False

    # Check MED (0-4294967295)
    if med is not None:
        if not (0 <= med <= 4294967295):
            sys.stderr.write(f"SECURITY: Invalid MED: {med}\n")
            return False

    # Check AS-PATH (no private ASNs if peering with public internet)
    if as_path:
        if any(64512 <= asn <= 65535 for asn in as_path):
            sys.stderr.write(f"SECURITY: Private ASN in AS-PATH\n")
            return False

    return True

def is_valid_prefix(prefix):
    """Validate prefix format"""
    import ipaddress
    try:
        ipaddress.ip_network(prefix)
        return True
    except:
        return False

def is_valid_ip(ip):
    """Validate IP address"""
    import ipaddress
    try:
        ipaddress.ip_address(ip)
        return True
    except:
        return False

Router-Side Filtering

Always filter on routers too (defense in depth):

Cisco prefix-list:

! Only accept specific prefixes from ExaBGP
ip prefix-list FROM-EXABGP permit 100.10.0.0/16 le 32
ip prefix-list FROM-EXABGP permit 203.0.113.0/24 le 32
ip prefix-list FROM-EXABGP deny 0.0.0.0/0 le 32

router bgp 65000
 neighbor 192.168.1.2 prefix-list FROM-EXABGP in

Juniper prefix-list:

policy-options {
    prefix-list from-exabgp {
        100.10.0.0/16;
        203.0.113.0/24;
    }
    policy-statement accept-from-exabgp {
        term 1 {
            from {
                prefix-list from-exabgp;
            }
            then accept;
        }
        term default {
            then reject;
        }
    }
}

protocols {
    bgp {
        group exabgp {
            import accept-from-exabgp;
        }
    }
}

API Security

Input Validation

Never trust external input:

#!/usr/bin/env python3
"""
Secure API process with input validation
"""
import sys
import re
import ipaddress

def sanitize_input(data):
    """Sanitize external input"""
    # Remove dangerous characters
    data = re.sub(r'[;&|`$\n]', '', data)
    return data

def check_service_health(url):
    """Check service with validated URL"""
    import urllib.parse

    # Validate URL
    parsed = urllib.parse.urlparse(url)

    # Only allow http/https
    if parsed.scheme not in ['http', 'https']:
        sys.stderr.write(f"SECURITY: Invalid URL scheme: {parsed.scheme}\n")
        return False

    # Only allow localhost
    if parsed.hostname not in ['localhost', '127.0.0.1', '::1']:
        sys.stderr.write(f"SECURITY: Invalid hostname: {parsed.hostname}\n")
        return False

    # Now safe to check
    import urllib.request
    try:
        response = urllib.request.urlopen(url, timeout=5)
        return response.status == 200
    except:
        return False

# Safe usage
if check_service_health('http://localhost/health'):
    sys.stdout.write("announce route 100.10.0.100/32 next-hop self\n")
    sys.stdout.flush()

Avoid Code Injection

Don't use eval(), exec(), or shell=True:

# ❌ DANGEROUS: Code injection vulnerability
import subprocess
route = input("Enter route: ")  # User could enter: "1.1.1.1; rm -rf /"
subprocess.run(f"ip route add {route}", shell=True)  # DON'T DO THIS!

# βœ… SAFE: No shell injection possible
import subprocess
route = input("Enter route: ")
# Validate input first
if is_valid_route(route):
    subprocess.run(['ip', 'route', 'add', route], shell=False)

API Process Sandboxing

Use subprocess timeouts and resource limits:

#!/usr/bin/env python3
"""
Safely execute health checks with resource limits
"""
import subprocess
import resource

def check_service_with_limits():
    """Execute health check with resource limits"""
    def set_limits():
        # Set resource limits for child process
        resource.setrlimit(resource.RLIMIT_CPU, (5, 5))      # 5 sec CPU
        resource.setrlimit(resource.RLIMIT_AS, (50*1024*1024, 50*1024*1024))  # 50 MB memory
        resource.setrlimit(resource.RLIMIT_NPROC, (0, 0))    # No new processes

    try:
        result = subprocess.run(
            ['/usr/local/bin/healthcheck.sh'],
            timeout=10,           # 10 second timeout
            preexec_fn=set_limits,  # Apply resource limits
            capture_output=True
        )
        return result.returncode == 0
    except subprocess.TimeoutExpired:
        sys.stderr.write("SECURITY: Health check timeout\n")
        return False
    except Exception as e:
        sys.stderr.write(f"SECURITY: Health check error: {e}\n")
        return False

Audit Logging

Enable Comprehensive Logging

Log security-relevant events:

#!/usr/bin/env python3
"""
API process with security audit logging
"""
import sys
import syslog
import datetime

def audit_log(event, level=syslog.LOG_INFO):
    """Write to syslog for audit trail"""
    syslog.openlog('exabgp-security', syslog.LOG_PID, syslog.LOG_DAEMON)
    syslog.syslog(level, event)
    syslog.closelog()

    # Also write to stderr for ExaBGP logs
    timestamp = datetime.datetime.now().isoformat()
    sys.stderr.write(f"[{timestamp}] AUDIT: {event}\n")

# Log security events
audit_log("API process started", syslog.LOG_INFO)
audit_log("Route announced: 100.10.0.100/32", syslog.LOG_INFO)
audit_log("Blocked invalid prefix: 1.1.1.1/32", syslog.LOG_WARNING)
audit_log("Health check failed 3 times", syslog.LOG_ERR)

Log Rotation with Retention

Configure secure log rotation:

# /etc/logrotate.d/exabgp
/var/log/exabgp/*.log {
    daily
    rotate 90              # Keep 90 days for audit
    compress
    delaycompress
    missingok
    notifempty
    create 0640 exabgp exabgp
    sharedscripts
    postrotate
        systemctl reload exabgp > /dev/null 2>&1 || true
    endscript
}

Centralized Logging

Send logs to SIEM for analysis:

# /etc/rsyslog.d/exabgp.conf

# Send ExaBGP logs to central syslog server
if $programname == 'exabgp' or $programname == 'exabgp-security' then {
    action(type="omfwd"
           target="siem.example.com"
           port="514"
           protocol="tcp")
}

Attack Mitigation

BGP Session Hijacking Prevention

Prevent session hijacking:

  1. Use MD5 authentication (minimum)
  2. Use TTL security (GTSM)
  3. Filter BGP traffic (firewall)
  4. Monitor for unexpected sessions

Detection script:

#!/usr/bin/env python3
"""
Detect unexpected BGP sessions
"""
import subprocess
import sys

EXPECTED_PEERS = ['192.168.1.1', '192.168.1.2']

def check_bgp_sessions():
    """Check for unexpected BGP connections"""
    result = subprocess.run(
        ['ss', '-tan', 'sport', '=', ':179', 'or', 'dport', '=', ':179'],
        capture_output=True,
        text=True
    )

    for line in result.stdout.split('\n'):
        if 'ESTAB' in line:
            # Parse connection
            parts = line.split()
            if len(parts) >= 5:
                remote = parts[4].split(':')[0]

                if remote not in EXPECTED_PEERS:
                    # ALERT: Unexpected BGP session!
                    msg = f"SECURITY ALERT: Unexpected BGP session from {remote}"
                    sys.stderr.write(msg + "\n")

                    # Send alert
                    send_alert(msg)

if __name__ == '__main__':
    check_bgp_sessions()

Route Hijacking Detection

Monitor for suspicious route announcements:

#!/usr/bin/env python3
"""
Monitor for route hijacking attempts
"""
import sys
import ipaddress

# Bogon prefixes (should never be announced)
BOGONS = [
    ipaddress.ip_network('0.0.0.0/8'),
    ipaddress.ip_network('10.0.0.0/8'),
    ipaddress.ip_network('127.0.0.0/8'),
    ipaddress.ip_network('169.254.0.0/16'),
    ipaddress.ip_network('172.16.0.0/12'),
    ipaddress.ip_network('192.168.0.0/16'),
    ipaddress.ip_network('224.0.0.0/4'),
    ipaddress.ip_network('240.0.0.0/4'),
]

def is_bogon(prefix_str):
    """Check if prefix is a bogon"""
    try:
        prefix = ipaddress.ip_network(prefix_str)
        for bogon in BOGONS:
            if prefix.subnet_of(bogon):
                return True
        return False
    except:
        return False

# Monitor announcements
for line in sys.stdin:
    if 'announce route' in line:
        # Extract prefix
        parts = line.split()
        if len(parts) >= 3:
            prefix = parts[2]

            if is_bogon(prefix):
                sys.stderr.write(f"SECURITY ALERT: Bogon announcement blocked: {prefix}\n")
                # Don't forward this announcement
                continue

    # Forward legitimate announcements
    sys.stdout.write(line)
    sys.stdout.flush()

Resource Exhaustion Prevention

Prevent DoS via resource exhaustion:

#!/usr/bin/env python3
"""
Rate-limit route announcements to prevent DoS
"""
import sys
import time
from collections import deque

class RateLimiter:
    def __init__(self, max_rate=100, window=1.0):
        """
        max_rate: Maximum announcements per window
        window: Time window in seconds
        """
        self.max_rate = max_rate
        self.window = window
        self.events = deque()

    def allow(self):
        """Check if action is allowed"""
        now = time.time()

        # Remove old events
        while self.events and now - self.events[0] > self.window:
            self.events.popleft()

        # Check rate
        if len(self.events) < self.max_rate:
            self.events.append(now)
            return True
        else:
            return False

limiter = RateLimiter(max_rate=100, window=1.0)

def announce_route(prefix):
    """Rate-limited route announcement"""
    if limiter.allow():
        sys.stdout.write(f"announce route {prefix} next-hop self\n")
        sys.stdout.flush()
    else:
        sys.stderr.write(f"SECURITY: Rate limit exceeded for {prefix}\n")

Security Checklist

Deployment Security Checklist

Before deploying to production:

Authentication & Authorization:

  • MD5 or TCP-AO authentication enabled
  • Strong passwords (20+ characters)
  • Passwords stored securely (not in Git)
  • TTL security (GTSM) enabled if applicable

Process Isolation:

  • Running as dedicated user (not root)
  • Systemd hardening enabled
  • AppArmor/SELinux profile configured
  • Resource limits set

File Permissions:

  • Config files mode 640 or stricter
  • API processes owned by root, executable by exabgp
  • Log directory writable by exabgp only
  • Secrets directory mode 600

Firewall:

  • BGP traffic restricted to known peers
  • Rate limiting enabled
  • Logging enabled for dropped packets

Route Security:

  • Prefix filtering in API processes
  • Attribute validation enabled
  • Router-side filtering configured
  • Bogon filtering enabled

Monitoring:

  • Audit logging enabled
  • Logs rotated with retention
  • SIEM integration configured
  • Alerts for security events
  • Unexpected session detection

API Security:

  • Input validation on all external data
  • No shell=True in subprocess calls
  • No eval()/exec() usage
  • Resource limits on API processes
  • Rate limiting enabled

Regular Security Audits

Perform regular audits:

#!/bin/bash
# ExaBGP security audit script

echo "=== ExaBGP Security Audit ==="
echo "Date: $(date)"
echo

# Check process user
echo "1. Process User:"
ps aux | grep exabgp | grep -v grep | awk '{print $1}'
if ps aux | grep exabgp | grep -v grep | grep -q root; then
    echo "  ❌ WARNING: Running as root!"
else
    echo "  βœ… OK: Not running as root"
fi
echo

# Check file permissions
echo "2. File Permissions:"
config_perms=$(stat -c %a /etc/exabgp/exabgp.conf 2>/dev/null)
if [ "$config_perms" -le 640 ]; then
    echo "  βœ… OK: Config permissions: $config_perms"
else
    echo "  ❌ WARNING: Config too permissive: $config_perms"
fi
echo

# Check BGP authentication
echo "3. BGP Authentication:"
if grep -q "md5-password" /etc/exabgp/exabgp.conf; then
    echo "  βœ… OK: MD5 authentication enabled"
else
    echo "  ❌ WARNING: No MD5 authentication"
fi
echo

# Check firewall
echo "4. Firewall Rules:"
if iptables -L -n | grep -q "dpt:179"; then
    echo "  βœ… OK: BGP firewall rules present"
else
    echo "  ❌ WARNING: No BGP firewall rules"
fi
echo

# Check for secrets in Git
echo "5. Secrets in Git:"
if git -C /etc/exabgp rev-parse 2>/dev/null; then
    if git -C /etc/exabgp grep -q "md5-password"; then
        echo "  ❌ WARNING: Passwords in Git!"
    else
        echo "  βœ… OK: No passwords in Git"
    fi
fi
echo

echo "=== Audit Complete ==="

Next Steps

Learn More

Security Resources


Need security help? Join our Slack community β†’


πŸ‘» Ghost written by Claude (Anthropic AI)

Clone this wiki locally