Skip to content

Security: mvalancy/CyberPower-PDU

Security

docs/security.md

Security Hardening

Docs: Getting Started | Configuration | API Reference | Architecture | MQTT Topics | SNMP OIDs | Multi-PDU | Security | Troubleshooting | Features

This guide covers how to harden each component of the CyberPower PDU Bridge stack for production use. The defaults are designed for easy setup -- anonymous MQTT, default SNMP communities, plain-text passwords -- but you should lock these down before deploying in a production environment.


Overview

The stack has four attack surfaces, each with its own hardening steps:

graph LR
    subgraph Network ["Your Network"]
        PDU["PDU<br/><small>SNMP v2c</small>"]
        Bridge["Bridge<br/><small>:8080 HTTP</small>"]
        MQTT["Mosquitto<br/><small>:1883 MQTT</small>"]
        InfluxDB["InfluxDB<br/><small>:8086 HTTP</small>"]
    end

    Attacker["Attacker"] -.->|"1. SNMP"| PDU
    Attacker -.->|"2. MQTT"| MQTT
    Attacker -.->|"3. HTTP"| Bridge
    Attacker -.->|"4. HTTP"| InfluxDB

    style Network fill:#0f172a,stroke:#475569,color:#f8fafc
    style PDU fill:#1a1a2e,stroke:#f59e0b,color:#e2e4e9
    style Bridge fill:#1a1a2e,stroke:#0ea5e9,color:#e2e4e9
    style MQTT fill:#1a1a2e,stroke:#10b981,color:#e2e4e9
    style InfluxDB fill:#1a1a2e,stroke:#ec4899,color:#e2e4e9
    style Attacker fill:#7f1d1d,stroke:#ef4444,color:#fef2f2
Loading

1. SNMP Hardening

SNMP v2c (the version used by CyberPower PDUs) sends community strings in plain text over the network. Anyone who can capture network traffic between the bridge and the PDU can read the community strings and gain full control of the PDU.

Change default community strings

The defaults (public for read, private for write) are well-known and should always be changed.

  1. On the PDU: Log into the PDU's web management interface and change the SNMP community strings under Network Management > SNMP. Consult your PDU's manual for the exact steps.

  2. In your .env file:

PDU_COMMUNITY_READ=my-read-community-2026
PDU_COMMUNITY_WRITE=my-write-community-2026
  1. In pdus.json (multi-PDU):
{
  "pdus": [
    {
      "device_id": "rack1-pdu",
      "host": "192.168.20.177",
      "community_read": "my-read-community-2026",
      "community_write": "my-write-community-2026"
    }
  ]
}

Use SNMPv3 if available

Some newer CyberPower PDU models support SNMPv3, which adds authentication and encryption. If your PDU supports it, configuring SNMPv3 eliminates the plain-text community string problem entirely. Check your PDU's firmware version and documentation.

Note: The current bridge uses SNMPv2c exclusively. SNMPv3 support would require changes to the snmp_client.py module.

Network isolation

Since SNMP v2c cannot be encrypted, the best mitigation is to isolate SNMP traffic:

  • Place PDUs on a dedicated management VLAN
  • Use firewall rules to restrict SNMP access to only the bridge host
  • Never expose SNMP ports (161/UDP) to the internet or untrusted networks

2. MQTT Hardening

By default, Mosquitto allows anonymous connections with no authentication or encryption. Anyone on the network can subscribe to all PDU data or publish commands to control outlets.

Enable MQTT authentication

  1. Create a password file on the host:
# Create the first MQTT user (interactive password prompt)
./start --mqtt-passwd pdu-bridge

# Add additional users
./start --mqtt-passwd dashboard-user
  1. Update mosquitto.conf (mosquitto/mosquitto.conf):
listener 1883
protocol mqtt

listener 9001
protocol websockets

# Disable anonymous access
allow_anonymous false

# Password file
password_file /mosquitto/config/passwd

persistence true
persistence_location /mosquitto/data/

log_dest stdout
log_type all
  1. Add the password file volume to docker-compose.yml (under the mosquitto service volumes):
volumes:
  - ./mosquitto/mosquitto.conf:/mosquitto/config/mosquitto.conf
  - ./mosquitto/passwd:/mosquitto/config/passwd
  - mosquitto-data:/mosquitto/data
  - mosquitto-log:/mosquitto/log
  1. Configure the bridge to authenticate in .env:
MQTT_USERNAME=pdu-bridge
MQTT_PASSWORD=your-secure-password
  1. Restart the stack:
./start --restart

Enable MQTT TLS

For encrypted MQTT connections, configure Mosquitto with TLS certificates:

  1. Generate or obtain certificates (self-signed example):
mkdir -p mosquitto/certs

# Generate CA key and certificate
openssl req -new -x509 -days 365 -extensions v3_ca \
  -keyout mosquitto/certs/ca.key \
  -out mosquitto/certs/ca.crt \
  -subj "/CN=PDU MQTT CA"

# Generate server key and certificate
openssl genrsa -out mosquitto/certs/server.key 2048
openssl req -new -key mosquitto/certs/server.key \
  -out mosquitto/certs/server.csr \
  -subj "/CN=mosquitto"
openssl x509 -req -in mosquitto/certs/server.csr \
  -CA mosquitto/certs/ca.crt -CAkey mosquitto/certs/ca.key \
  -CAcreateserial -out mosquitto/certs/server.crt -days 365
  1. Update mosquitto.conf:
# Encrypted listener
listener 8883
protocol mqtt
cafile /mosquitto/certs/ca.crt
certfile /mosquitto/certs/server.crt
keyfile /mosquitto/certs/server.key

# Unencrypted listener (for local bridge only, optional)
listener 1883 127.0.0.1
protocol mqtt

allow_anonymous false
password_file /mosquitto/config/passwd
  1. Mount the certs directory in docker-compose.yml.

Access control lists (ACLs)

For fine-grained control, create an ACL file that restricts which users can read/write which topics:

# mosquitto/acl

# Bridge can publish and subscribe to everything
user pdu-bridge
topic readwrite pdu/#
topic readwrite homeassistant/#

# Dashboard user can only read
user dashboard-user
topic read pdu/#

Add to mosquitto.conf:

acl_file /mosquitto/config/acl

3. PDU Default Credentials

CyberPower PDUs ship with factory default credentials (cyber/cyber). The bridge automatically detects this and shows a security banner on the dashboard.

Automatic detection

When the bridge has serial transport (or runs in mock mode), it checks on first poll whether the PDU admin account still uses the factory default password. If so:

  • A security banner appears at the top of the dashboard
  • A security_warning event is logged in the events panel
  • The Manage tab highlights the security section

Changing the PDU password

You can change the PDU password through:

  1. Web dashboard -- Click the security banner or go to Settings > Manage > Security and use the password change form.
  2. REST API:
curl -X POST http://localhost:8080/api/pdu/security/password \
  -H 'Content-Type: application/json' \
  -d '{"account": "admin", "password": "YourNewSecurePassword"}'
  1. PDU serial console -- Connect directly via RS-232 and use the usrcfg set command.

After changing the password, the security banner disappears on the next poll cycle.


4. Web Dashboard / REST API

The bridge has optional built-in authentication via session tokens. By default, the web UI is open (no login required). To enable authentication, set BRIDGE_WEB_PASSWORD in your .env:

BRIDGE_WEB_PASSWORD=your-secure-password

When enabled, the dashboard shows a login overlay and all API endpoints (except /, /api/auth/*, and /api/health) require a valid session token.

Reverse proxy with authentication (alternative)

For more advanced setups, place the bridge behind a reverse proxy (nginx, Caddy, Traefik) that handles authentication and TLS.

nginx example (/etc/nginx/sites-available/pdu):

server {
    listen 443 ssl;
    server_name pdu.example.com;

    ssl_certificate /etc/letsencrypt/live/pdu.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/pdu.example.com/privkey.pem;

    # Basic authentication
    auth_basic "PDU Dashboard";
    auth_basic_user_file /etc/nginx/.htpasswd;

    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;
    }
}

Create the password file:

sudo apt install apache2-utils
sudo htpasswd -c /etc/nginx/.htpasswd admin

Restrict to localhost

If you only access the dashboard from the bridge host itself, bind to localhost only by changing the bridge's web server or using firewall rules:

# Allow only localhost to reach port 8080
sudo iptables -A INPUT -p tcp --dport 8080 -s 127.0.0.1 -j ACCEPT
sudo iptables -A INPUT -p tcp --dport 8080 -j DROP

5. InfluxDB Hardening

The default InfluxDB configuration uses weak credentials that should be changed immediately.

Change default credentials

In your .env file, update these before first run (InfluxDB credentials are set during initial setup and cannot be easily changed afterward):

INFLUXDB_ADMIN_USER=your-admin-user
INFLUXDB_ADMIN_PASSWORD=a-strong-password-here
INFLUXDB_ADMIN_TOKEN=a-random-api-token-here

Generate a random token:

openssl rand -hex 32

Restrict network access

InfluxDB's web UI and API are exposed on port 8086. If you do not need external access:

  1. Remove the port mapping from docker-compose.yml:
influxdb:
  image: influxdb:2.7
  # ports:            # Comment out or remove
  #   - "8086:8086"   # to disable external access

Telegraf can still reach InfluxDB via the Docker network.

  1. Or bind to localhost only:
ports:
  - "127.0.0.1:8086:8086"

6. Network-Level Security

Firewall rules

On the bridge host, restrict incoming connections to only what is needed:

# Allow SSH
sudo ufw allow 22/tcp

# Allow web dashboard (from trusted network only)
sudo ufw allow from 192.168.0.0/16 to any port 8080

# Allow MQTT (from trusted network only)
sudo ufw allow from 192.168.0.0/16 to any port 1883

# Deny InfluxDB from external access
sudo ufw deny 8086

# Enable firewall
sudo ufw enable

Disable unused services

If you do not use InfluxDB and Telegraf, you can remove them from your stack entirely by commenting them out in docker-compose.yml. The bridge works perfectly without them -- historical data is stored in SQLite.

Keep software updated

# Update the bridge code and rebuild
git pull
./start --rebuild

Security Checklist

Item Default Recommended
PDU admin password cyber/cyber (factory) Change via dashboard or API immediately
SNMP community strings public / private Change to unique strings
MQTT authentication Anonymous Enable username/password
MQTT encryption None (plain text) Enable TLS
Web dashboard auth None Set BRIDGE_WEB_PASSWORD or use reverse proxy
InfluxDB credentials admin / changeme123 Change before first run
InfluxDB network Exposed on :8086 Bind to localhost or disable port mapping
Firewall None Restrict to trusted networks
PDU network Shared Dedicated management VLAN

There aren’t any published security advisories