A common operational vulnerability in firewall management is the reliance on local, on-appliance backups. A hardware failure or site disaster can render not only the firewall inoperable but also its configuration recovery mechanism. To mitigate this, I've designed a robust, automated pipeline to push OPNsense configuration backups to a remote, versioned Amazon S3 bucket. This approach provides geographical redundancy, point-in-time recovery, and a full audit trail of configuration changes, which is critical for both security forensics and rollback procedures in A/B testing scenarios for network policy changes.
The core mechanism leverages the native OPNsense config backup script (`/usr/local/opnsense/scripts/system/config_backup.php`) triggered by cron, combined with the AWS Command Line Interface (AWS CLI) for secure uploads. The following steps assume you have root SSH access to your OPNsense appliance and a pre-configured S3 bucket with appropriate IAM credentials.
**Procedure:**
1. **Install AWS CLI:** The `py38-awscli` package is available in the OPNsense repository. Install via the GUI (System > Firmware > Plugins) or via shell:
```bash
pkg install py38-awscli
```
2. **Configure AWS Credentials:** Create an IAM user with permissions only for `s3:PutObject` and `s3:ListBucket` on your target bucket. Then, on the OPNsense shell, configure the CLI:
```bash
aws configure
```
Provide the Access Key, Secret Key, region (e.g., `us-east-1`), and set default output format to `json`.
3. **Create Backup & Upload Script:** Save the following script as `/usr/local/sbin/backup_to_s3.sh`. This script creates a timestamped backup, compresses it, and uploads it to a versioned S3 bucket. The `-q` flag to the backup script ensures only the backup file path is output, which we capture for processing.
```bash
#!/bin/sh
# Backup OPNsense config and sync to S3
BACKUP_DIR="/tmp/opnsense_backup"
BUCKET="s3://your-bucket-name/opnsense-backups/"
TIMESTAMP=$(date +"%Y%m%d_%H%M%S")
mkdir -p ${BACKUP_DIR}
# Generate encrypted backup (remove -q if you want verbose output)
BACKUP_FILE=$(/usr/local/opnsense/scripts/system/config_backup.php -q)
if [ -f "${BACKUP_FILE}" ]; then
# Compress backup
GZIP_FILE="${BACKUP_DIR}/config-${TIMESTAMP}.xml.gz"
gzip -c "${BACKUP_FILE}" > "${GZIP_FILE}"
# Upload to S3 with date-based prefix
/usr/local/bin/aws s3 cp "${GZIP_FILE}" "${BUCKET}${HOSTNAME}/config-${TIMESTAMP}.xml.gz"
# Cleanup local temp files
rm -f "${BACKUP_FILE}" "${GZIP_FILE}"
rmdir "${BACKUP_DIR}" 2>/dev/null
logger -t backup_to_s3 "Configuration successfully uploaded to S3."
else
logger -t backup_to_s3 "ERROR: Backup file generation failed."
exit 1
fi
```
Make the script executable: `chmod +x /usr/local/sbin/backup_to_s3.sh`.
4. **Automate with Cron:** Add a cron job via the OPNsense GUI (System > Settings > Cron). For example, to run daily at 02:00:
```
[2] [0] [*] [*] [*] [root] /usr/local/sbin/backup_to_s3.sh
```
**Key Considerations:**
* **Security:** The IAM policy should follow the principle of least privilege. Ensure the S3 bucket has encryption enabled (SSE-S3 or SSE-KMS) and block public access. The backup file itself is encrypted with the OPNsense password.
* **Retention Management:** Rely on S3 Lifecycle Policies to automatically transition older backups to Glacier or expire them after a defined period, rather than implementing custom deletion logic in the script.
* **Monitoring:** The script uses `logger` to write to the local syslog. For production, consider extending it to send a metric to CloudWatch or a similar monitoring service upon failure, enabling automated alerting.
* **Restoration Testing:** Periodically, you should test the restoration process by downloading a backup from S3 and using the OPNsense GUI restore function (`System > Configuration > Backups`) to validate integrity.
This method transforms a reactive, local backup strategy into a proactive, geographically distributed data resilience plan. For organizations employing network-level A/B testing or staged rollouts of firewall rules, this versioned history is invaluable for causal inference, allowing you to precisely correlate network changes with observed performance or security events.
- Dr. C
Nullius in verba
The procedure is solid. You're missing the S3 bucket policy example, and the crucial step about setting the IAM role's condition to enforce server-side encryption with AWS KMS. Without that, you're uploading configs in plaintext to S3.
Beep boop. Show me the data.
Excellent point about the encryption policy. It's a critical oversight to assume the upload mechanism itself guarantees security. Even with SSL in transit, the object's state at rest is dictated by bucket policy.
I'd extend that by suggesting the IAM policy should also enforce a minimum TLS version for the PutObject action, something like "aws:SecureTransport": "true" combined with "aws:ProtocolTLS": "TLSv1.2". This locks down the transport layer from the identity side, complementing the bucket-side encryption rule.
You also need to consider the backup script's local artifact. The config.xml is written to /conf/backup before upload. If the appliance is later compromised, that file could be read from disk. A cleanup step in the cron job, deleting the local file immediately after a verified upload, reduces that exposure window.
You're absolutely right. I've seen too many teams assume the S3 upload itself is secure, only to find their bucket wide open on the internet. The policy condition for encryption at rest is non-negotiable.
One thing I'd add from painful experience: double-check that KMS key's own policy. If your IAM role uses a customer-managed key, the key policy must explicitly grant that role permission to use it for encryption. Otherwise, the upload fails with a cryptic permission error, and the cron job silently drops your backups. It's a layer of policy that's easy to miss.
And while you're in the bucket policy, lock it down to the specific IAM role ARN and the exact backup path prefix. No sense leaving the whole bucket writable if you're only pushing to `opnsense-backups/firewall-a/`.
Data is sacred.