2026-08-07_13-20-43_CEST
This commit is contained in:
@@ -0,0 +1,396 @@
|
|||||||
|
# CDN Update Script
|
||||||
|
|
||||||
|
This document describes a reference implementation of the `cdn-update.sh` automation script for the Community CDN architecture.
|
||||||
|
|
||||||
|
The script is designed to:
|
||||||
|
|
||||||
|
* Download the signed control file
|
||||||
|
* Verify its signature
|
||||||
|
* Validate JSON syntax
|
||||||
|
* Generate fail2ban configuration
|
||||||
|
* Generate nginx configuration
|
||||||
|
* Select an available origin
|
||||||
|
* Synchronize content using rsync
|
||||||
|
* Export Prometheus metrics
|
||||||
|
* Fail safely when configuration validation fails
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Reference Script
|
||||||
|
|
||||||
|
```bash
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# Configuration
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
BASE="/var/lib/cdn"
|
||||||
|
|
||||||
|
CONFIG_DIR="${BASE}/config"
|
||||||
|
CONTENT_DIR="${BASE}/content"
|
||||||
|
METRICS_DIR="${BASE}/metrics"
|
||||||
|
|
||||||
|
CONTROL_URL="https://control.example.org/hpr.ccdn.settings.json"
|
||||||
|
SIG_URL="https://control.example.org/hpr.ccdn.settings.json.minisig"
|
||||||
|
|
||||||
|
PUBKEY="RWQxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
||||||
|
|
||||||
|
CONTROL_FILE="${CONFIG_DIR}/hpr.ccdn.settings.json"
|
||||||
|
SIG_FILE="${CONFIG_DIR}/hpr.ccdn.settings.json.minisig"
|
||||||
|
|
||||||
|
NGINX_GEN="/etc/nginx/conf.d/cdn-generated.conf"
|
||||||
|
|
||||||
|
FAIL2BAN_JAIL="/etc/fail2ban/jail.d/cdn-generated.local"
|
||||||
|
|
||||||
|
METRICS_FILE="${METRICS_DIR}/cdn.prom"
|
||||||
|
|
||||||
|
TMPDIR="$(mktemp -d)"
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# Metrics helper
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
metric_write() {
|
||||||
|
cat > "${METRICS_FILE}" <<EOF
|
||||||
|
cdn_last_sync_timestamp ${LAST_SYNC_TIMESTAMP:-0}
|
||||||
|
cdn_sync_success ${SYNC_SUCCESS:-0}
|
||||||
|
cdn_sync_duration_seconds ${SYNC_DURATION:-0}
|
||||||
|
cdn_invalid_requests_total ${INVALID_REQUESTS:-0}
|
||||||
|
cdn_active_origin{origin="${ACTIVE_ORIGIN:-none}"} 1
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# Cleanup
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
cleanup() {
|
||||||
|
rm -rf "${TMPDIR}"
|
||||||
|
}
|
||||||
|
|
||||||
|
trap cleanup EXIT
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# Download control file
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
echo "Downloading control file..."
|
||||||
|
|
||||||
|
curl -fsSL \
|
||||||
|
-o "${TMPDIR}/hpr.ccdn.settings.json" \
|
||||||
|
"${CONTROL_URL}"
|
||||||
|
|
||||||
|
curl -fsSL \
|
||||||
|
-o "${TMPDIR}/hpr.ccdn.settings.json.minisig" \
|
||||||
|
"${SIG_URL}"
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# Verify signature
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
echo "Verifying signature..."
|
||||||
|
|
||||||
|
minisign \
|
||||||
|
-Vm "${TMPDIR}/hpr.ccdn.settings.json" \
|
||||||
|
-P "${PUBKEY}" \
|
||||||
|
-x "${TMPDIR}/hpr.ccdn.settings.json.minisig"
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# Validate JSON
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
jq empty "${TMPDIR}/hpr.ccdn.settings.json"
|
||||||
|
|
||||||
|
install -m 0644 \
|
||||||
|
"${TMPDIR}/hpr.ccdn.settings.json" \
|
||||||
|
"${CONTROL_FILE}"
|
||||||
|
|
||||||
|
install -m 0644 \
|
||||||
|
"${TMPDIR}/hpr.ccdn.settings.json.minisig" \
|
||||||
|
"${SIG_FILE}"
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# Load values
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
mapfile -t ORIGINS < <(
|
||||||
|
jq -r '.origins[]' "${CONTROL_FILE}"
|
||||||
|
)
|
||||||
|
|
||||||
|
RSYNC_INTERVAL=$(
|
||||||
|
jq -r '.rsync_interval_hours' "${CONTROL_FILE}"
|
||||||
|
)
|
||||||
|
|
||||||
|
MAXRETRY=$(
|
||||||
|
jq -r '.fail2ban.maxretry' "${CONTROL_FILE}"
|
||||||
|
)
|
||||||
|
|
||||||
|
FINDTIME=$(
|
||||||
|
jq -r '.fail2ban.findtime' "${CONTROL_FILE}"
|
||||||
|
)
|
||||||
|
|
||||||
|
BANTIME=$(
|
||||||
|
jq -r '.fail2ban.bantime' "${CONTROL_FILE}"
|
||||||
|
)
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# Generate fail2ban configuration
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
echo "Generating fail2ban configuration..."
|
||||||
|
|
||||||
|
ADMIN_IPS=$(
|
||||||
|
jq -r '.admin_ips[]?' "${CONTROL_FILE}" \
|
||||||
|
| tr '\n' ' '
|
||||||
|
)
|
||||||
|
|
||||||
|
cat > "${FAIL2BAN_JAIL}" <<EOF
|
||||||
|
[nginx-invalid]
|
||||||
|
enabled = true
|
||||||
|
|
||||||
|
maxretry = ${MAXRETRY}
|
||||||
|
findtime = ${FINDTIME}
|
||||||
|
bantime = ${BANTIME}
|
||||||
|
|
||||||
|
ignoreip = 127.0.0.1 ${ADMIN_IPS}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
systemctl reload fail2ban
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# Select active origin
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
ACTIVE_ORIGIN=""
|
||||||
|
|
||||||
|
for ORIGIN in "${ORIGINS[@]}"
|
||||||
|
do
|
||||||
|
if ssh \
|
||||||
|
-o BatchMode=yes \
|
||||||
|
-o ConnectTimeout=5 \
|
||||||
|
"${ORIGIN}" \
|
||||||
|
true
|
||||||
|
then
|
||||||
|
ACTIVE_ORIGIN="${ORIGIN}"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ -z "${ACTIVE_ORIGIN}" ]
|
||||||
|
then
|
||||||
|
echo "No origin available"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# Generate nginx configuration
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
echo "Generating nginx config..."
|
||||||
|
|
||||||
|
EXT_REGEX=$(
|
||||||
|
jq -r '.allowed_extensions[]' "${CONTROL_FILE}" \
|
||||||
|
| paste -sd'|' -
|
||||||
|
)
|
||||||
|
|
||||||
|
cat > "${NGINX_GEN}" <<EOF
|
||||||
|
autoindex off;
|
||||||
|
|
||||||
|
location ~ ^/eps/hpr[0-9]{4}/hpr[0-9]{4}\.(${EXT_REGEX})\$ {
|
||||||
|
root ${CONTENT_DIR}/public_html;
|
||||||
|
}
|
||||||
|
|
||||||
|
location = /robots.txt {
|
||||||
|
root ${CONTENT_DIR}/public_html;
|
||||||
|
}
|
||||||
|
|
||||||
|
location = /favicon.ico {
|
||||||
|
root ${CONTENT_DIR}/public_html;
|
||||||
|
}
|
||||||
|
|
||||||
|
location / {
|
||||||
|
access_log /var/log/nginx/invalid_requests.log;
|
||||||
|
return 404;
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
nginx -t
|
||||||
|
systemctl reload nginx
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# Rsync
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
echo "Starting rsync..."
|
||||||
|
|
||||||
|
START_TIME=$(date +%s)
|
||||||
|
|
||||||
|
if rsync \
|
||||||
|
-az \
|
||||||
|
--delete-delay \
|
||||||
|
rsyncuser@"${ACTIVE_ORIGIN}":/srv/content/ \
|
||||||
|
"${CONTENT_DIR}/"
|
||||||
|
then
|
||||||
|
|
||||||
|
END_TIME=$(date +%s)
|
||||||
|
|
||||||
|
LAST_SYNC_TIMESTAMP="${END_TIME}"
|
||||||
|
SYNC_DURATION=$((END_TIME - START_TIME))
|
||||||
|
SYNC_SUCCESS=1
|
||||||
|
|
||||||
|
else
|
||||||
|
|
||||||
|
LAST_SYNC_TIMESTAMP=$(date +%s)
|
||||||
|
SYNC_DURATION=0
|
||||||
|
SYNC_SUCCESS=0
|
||||||
|
|
||||||
|
fi
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# Invalid request metric
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
INVALID_REQUESTS=$(
|
||||||
|
wc -l \
|
||||||
|
< /var/log/nginx/invalid_requests.log \
|
||||||
|
|| echo 0
|
||||||
|
)
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# Write metrics
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
metric_write
|
||||||
|
|
||||||
|
echo "Update complete"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Recommended Enhancements
|
||||||
|
|
||||||
|
## Atomic Configuration Updates
|
||||||
|
|
||||||
|
Generate temporary configuration files first and only replace active files after validation succeeds.
|
||||||
|
|
||||||
|
Example workflow:
|
||||||
|
|
||||||
|
1. Generate configuration in a temporary directory.
|
||||||
|
2. Run `nginx -t`.
|
||||||
|
3. Replace production configuration.
|
||||||
|
4. Reload nginx.
|
||||||
|
|
||||||
|
This prevents broken configuration from affecting service availability.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## SSH Host Key Pinning
|
||||||
|
|
||||||
|
Use a dedicated `known_hosts` file.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ssh \
|
||||||
|
-o UserKnownHostsFile=/etc/cdn/known_hosts \
|
||||||
|
-o StrictHostKeyChecking=yes
|
||||||
|
```
|
||||||
|
|
||||||
|
This protects against origin impersonation.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Restrict Synchronized Content
|
||||||
|
|
||||||
|
Limit rsync to approved file types.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
rsync \
|
||||||
|
-az \
|
||||||
|
--delete-delay \
|
||||||
|
--include='*/' \
|
||||||
|
--include='*.mp3' \
|
||||||
|
--include='*.ogg' \
|
||||||
|
--include='*.opus' \
|
||||||
|
--include='*.txt' \
|
||||||
|
--include='*.json' \
|
||||||
|
--exclude='*' \
|
||||||
|
rsyncuser@origin:/srv/content/ \
|
||||||
|
/var/lib/cdn/content/
|
||||||
|
```
|
||||||
|
|
||||||
|
This prevents accidental synchronization of unexpected files.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Cached Control Files
|
||||||
|
|
||||||
|
If the control server is temporarily unavailable:
|
||||||
|
|
||||||
|
* Continue serving content
|
||||||
|
* Continue using the last verified control file
|
||||||
|
* Retry on the next scheduled execution
|
||||||
|
|
||||||
|
Nodes should never accept an unsigned replacement file.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Version-Based Synchronization
|
||||||
|
|
||||||
|
Store the last control file version.
|
||||||
|
|
||||||
|
Only perform a full synchronization when:
|
||||||
|
|
||||||
|
* The control file version changes
|
||||||
|
* `force_full_rsync` is enabled
|
||||||
|
|
||||||
|
This reduces unnecessary origin traffic.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Fail2ban Dynamic Ban Includes
|
||||||
|
|
||||||
|
Generate a separate include file for:
|
||||||
|
|
||||||
|
* Immediate IP bans
|
||||||
|
* Dynamic blocklists
|
||||||
|
|
||||||
|
Avoid rewriting the primary jail configuration on every update.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Example Cron Schedule
|
||||||
|
|
||||||
|
```cron
|
||||||
|
*/5 * * * * /usr/local/bin/cdn-update.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
This provides:
|
||||||
|
|
||||||
|
* Control file refresh every 5 minutes
|
||||||
|
* Automatic failover detection
|
||||||
|
* Automatic configuration updates
|
||||||
|
* Regular synchronization scheduling
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Operational Flow
|
||||||
|
|
||||||
|
1. Download control file.
|
||||||
|
2. Verify minisign signature.
|
||||||
|
3. Validate JSON.
|
||||||
|
4. Generate fail2ban configuration.
|
||||||
|
5. Generate nginx configuration.
|
||||||
|
6. Select active origin.
|
||||||
|
7. Synchronize content.
|
||||||
|
8. Export Prometheus metrics.
|
||||||
|
9. Exit successfully.
|
||||||
|
|
||||||
|
If any validation step fails, the script exits without modifying the running configuration.
|
||||||
|
|
||||||
|
This fail-closed behavior helps ensure that only authenticated, valid configuration changes are applied to CDN nodes.
|
||||||
@@ -152,11 +152,50 @@ Example:
|
|||||||
|
|
||||||
The control file must be signed.
|
The control file must be signed.
|
||||||
|
|
||||||
Recommended:
|
# minisign
|
||||||
|
|
||||||
|
## Admin
|
||||||
|
|
||||||
|
### Key Generation
|
||||||
|
|
||||||
|
```
|
||||||
|
minisign_date="$( \date --universal +%s )"
|
||||||
|
echo "Generating keys with date \"${minisign_date}\""
|
||||||
|
# -G Generate a new key pair
|
||||||
|
# -P <pubkey>
|
||||||
|
Public key, as a base64 string
|
||||||
|
# -s <seckey_file>
|
||||||
|
Secret key file (default: ~/.minisign/minisign.key
|
||||||
|
minisign -G -p hpr.minisign.${minisign_date}.pubkey -s hpr.minisign.${minisign_date}.seckey
|
||||||
|
```
|
||||||
|
### Document Signing
|
||||||
|
|
||||||
|
```
|
||||||
|
# -S Sign files
|
||||||
|
# -m <file>
|
||||||
|
# File to sign/verify
|
||||||
|
# -t <comment>
|
||||||
|
# Add a one-line trusted comment
|
||||||
|
# -x <sig_file>
|
||||||
|
# Signature file (default: <file>.minisig)
|
||||||
|
# -P <pubkey>
|
||||||
|
Public key, as a base64 string
|
||||||
|
|
||||||
|
minisign_date=1785664421
|
||||||
|
minisign -Sm hpr.ccdn.settings.json -x hpr.ccdn.settings.json.minisig -p hpr.minisign.${minisign_date}.pubkey -t 'Last updated on $( \date --universal +%Y-%m-%dT%H:%M:%SZ_%A ) ($( \date --universal +%s )) by $USER'
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Client
|
||||||
|
|
||||||
|
```
|
||||||
|
minisign_date="$( \date --universal +%s )"
|
||||||
|
echo "Generating keys with date \"${minisign_date}\""
|
||||||
|
minisign -G -p hpr.minisign.${minisign_date}.pubkey -s hpr.minisign.${minisign_date}.seckey
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
* minisign
|
|
||||||
* signify
|
|
||||||
* GnuPG
|
|
||||||
|
|
||||||
Nodes must reject unsigned or invalid control files.
|
Nodes must reject unsigned or invalid control files.
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,268 @@
|
|||||||
|
# ccdn_architecture_upgrades.md
|
||||||
|
|
||||||
|
# CCDN Architecture Upgrade Ideas
|
||||||
|
|
||||||
|
This document lists potential improvements to the CCDN architecture that were identified during a design review. None of these are considered essential for the initial release; they are intended as future enhancements once the core system is stable.
|
||||||
|
|
||||||
|
The philosophy remains unchanged:
|
||||||
|
|
||||||
|
* Keep the system simple.
|
||||||
|
* Prefer proven Unix tools.
|
||||||
|
* Avoid unnecessary dependencies.
|
||||||
|
* Preserve the stateless nature of edge nodes.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Atomic Content Deployment
|
||||||
|
|
||||||
|
**Priority:** High
|
||||||
|
|
||||||
|
Instead of synchronising directly into the live content directory, use a staging directory.
|
||||||
|
|
||||||
|
Suggested workflow:
|
||||||
|
|
||||||
|
1. Download the signed control file.
|
||||||
|
2. Verify its signature.
|
||||||
|
3. Synchronise content into a staging directory.
|
||||||
|
4. Perform validation.
|
||||||
|
5. Atomically rename the staging directory into production.
|
||||||
|
6. Reload nginx if required.
|
||||||
|
|
||||||
|
Benefits:
|
||||||
|
|
||||||
|
* Prevents partially updated content from being served.
|
||||||
|
* Makes interrupted synchronisations harmless.
|
||||||
|
* Provides clean rollback behaviour if validation fails.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Content Manifest
|
||||||
|
|
||||||
|
**Priority:** High
|
||||||
|
|
||||||
|
Publish a manifest alongside the content containing:
|
||||||
|
|
||||||
|
* filename
|
||||||
|
* size
|
||||||
|
* SHA-256 checksum
|
||||||
|
* modification time
|
||||||
|
|
||||||
|
After synchronisation, each node can verify downloaded files against the manifest.
|
||||||
|
|
||||||
|
Benefits:
|
||||||
|
|
||||||
|
* Detects storage corruption.
|
||||||
|
* Detects incomplete synchronisations.
|
||||||
|
* Simplifies troubleshooting.
|
||||||
|
* Provides confidence that mirrors contain identical content.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Configuration Versioning
|
||||||
|
|
||||||
|
**Priority:** Medium
|
||||||
|
|
||||||
|
Include a version number or timestamp in the signed control file.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"version": 14
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
or
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"generated": "2026-08-01T12:00:00Z"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Benefits:
|
||||||
|
|
||||||
|
* Easier troubleshooting.
|
||||||
|
* Prevents accidental rollback.
|
||||||
|
* Makes monitoring simpler.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Configuration Expiry
|
||||||
|
|
||||||
|
**Priority:** Medium
|
||||||
|
|
||||||
|
Include an expiry timestamp in the signed control file.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"valid_until": "2026-09-01T00:00:00Z"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Nodes should continue serving existing content if the configuration expires, but generate warnings or alerts so administrators know updates are no longer being received.
|
||||||
|
|
||||||
|
Benefits:
|
||||||
|
|
||||||
|
* Detects long-term communication failures.
|
||||||
|
* Helps identify replay attacks using stale configurations.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Health Endpoint
|
||||||
|
|
||||||
|
**Priority:** Medium
|
||||||
|
|
||||||
|
Expose a small JSON document such as:
|
||||||
|
|
||||||
|
```
|
||||||
|
/health.json
|
||||||
|
```
|
||||||
|
|
||||||
|
Example information:
|
||||||
|
|
||||||
|
* configuration version
|
||||||
|
* last successful synchronisation
|
||||||
|
* current origin
|
||||||
|
* disk usage
|
||||||
|
* software version
|
||||||
|
* node identifier
|
||||||
|
|
||||||
|
Benefits:
|
||||||
|
|
||||||
|
* Simplifies monitoring.
|
||||||
|
* Easy integration with Prometheus or external monitoring.
|
||||||
|
* Useful during troubleshooting.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Disk Space Protection
|
||||||
|
|
||||||
|
**Priority:** Medium
|
||||||
|
|
||||||
|
Before synchronisation, verify sufficient free disk space exists.
|
||||||
|
|
||||||
|
If available space falls below a configured threshold, abort the update and generate an alert.
|
||||||
|
|
||||||
|
Benefits:
|
||||||
|
|
||||||
|
* Prevents failed deployments.
|
||||||
|
* Protects nodes from filling the filesystem.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. SSH Restrictions
|
||||||
|
|
||||||
|
**Priority:** Medium
|
||||||
|
|
||||||
|
Restrict the rsync account so it cannot obtain an interactive shell.
|
||||||
|
|
||||||
|
Possible approaches include:
|
||||||
|
|
||||||
|
* `rrsync`
|
||||||
|
* `ForceCommand`
|
||||||
|
* `command=` restrictions in `authorized_keys`
|
||||||
|
|
||||||
|
Benefits:
|
||||||
|
|
||||||
|
* Reduces the impact of a compromised SSH key.
|
||||||
|
* Limits access strictly to file synchronisation.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Signing Key Rotation
|
||||||
|
|
||||||
|
**Priority:** Low
|
||||||
|
|
||||||
|
Support publishing both the current and next public signing keys.
|
||||||
|
|
||||||
|
This allows new keys to be distributed before they become active.
|
||||||
|
|
||||||
|
Benefits:
|
||||||
|
|
||||||
|
* Simplifies planned key rotation.
|
||||||
|
* Avoids emergency replacement procedures.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. Split Configuration Files
|
||||||
|
|
||||||
|
**Priority:** Low
|
||||||
|
|
||||||
|
If the control file becomes large, consider splitting it into independently signed files.
|
||||||
|
|
||||||
|
For example:
|
||||||
|
|
||||||
|
* origins.json
|
||||||
|
* sync.json
|
||||||
|
* nginx.json
|
||||||
|
* security.json
|
||||||
|
|
||||||
|
Benefits:
|
||||||
|
|
||||||
|
* Easier maintenance.
|
||||||
|
* Smaller updates.
|
||||||
|
* Simpler reviews.
|
||||||
|
|
||||||
|
This is not recommended until the configuration grows significantly.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. Declarative Configuration Only
|
||||||
|
|
||||||
|
**Priority:** Ongoing
|
||||||
|
|
||||||
|
The signed configuration should describe desired system state rather than commands to execute.
|
||||||
|
|
||||||
|
For example:
|
||||||
|
|
||||||
|
Prefer:
|
||||||
|
|
||||||
|
* sync interval
|
||||||
|
* fail2ban settings
|
||||||
|
* nginx options
|
||||||
|
* origin list
|
||||||
|
|
||||||
|
Avoid:
|
||||||
|
|
||||||
|
* arbitrary shell commands
|
||||||
|
* remote script execution
|
||||||
|
|
||||||
|
Benefits:
|
||||||
|
|
||||||
|
* Smaller attack surface.
|
||||||
|
* Easier auditing.
|
||||||
|
* More predictable behaviour.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Future Enhancements
|
||||||
|
|
||||||
|
These ideas are intentionally outside the scope of the first implementation but may become useful if CCDN grows.
|
||||||
|
|
||||||
|
* Delta manifests for large libraries.
|
||||||
|
* Geographic origin selection.
|
||||||
|
* Optional peer-to-peer mirror synchronisation.
|
||||||
|
* Compression for text-based metadata.
|
||||||
|
* Signed content release tags.
|
||||||
|
* Read-only content mounts between updates.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Deliberately Out of Scope
|
||||||
|
|
||||||
|
The following technologies are intentionally excluded from the CCDN design:
|
||||||
|
|
||||||
|
* Kubernetes
|
||||||
|
* Docker Swarm
|
||||||
|
* Redis
|
||||||
|
* PostgreSQL
|
||||||
|
* Elasticsearch
|
||||||
|
* Message queues
|
||||||
|
* Dynamic service discovery
|
||||||
|
* Distributed databases
|
||||||
|
* Complex orchestration systems
|
||||||
|
|
||||||
|
The goal is to keep CCDN easy to understand, easy to operate, and easy to recover by relying on mature Unix tooling rather than additional infrastructure.
|
||||||
@@ -0,0 +1,213 @@
|
|||||||
|
# Community Content Delivery Network Docker nodes
|
||||||
|
|
||||||
|
## Background
|
||||||
|
|
||||||
|
We wish to deploy a Community CDN that is easy to deploy and maintain.
|
||||||
|
|
||||||
|
This can be achieved by providing a Community Managed docker nodes that is regularly updated.
|
||||||
|
|
||||||
|
Changes can be applied to the GitTea, and the mirror nodes should pick up the changes.
|
||||||
|
|
||||||
|
## Removal of node
|
||||||
|
|
||||||
|
- Remove from central DNS
|
||||||
|
- Remove from monitoring
|
||||||
|
- Remove from allow lists
|
||||||
|
- Remove from authorized_keys
|
||||||
|
|
||||||
|
##
|
||||||
|
|
||||||
|
- Addition of new nodes
|
||||||
|
- Removal of old nodes
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Path Validation Strategy
|
||||||
|
|
||||||
|
This is where your design differs from a normal web server.
|
||||||
|
|
||||||
|
You know:
|
||||||
|
|
||||||
|
episode numbers
|
||||||
|
valid extensions
|
||||||
|
exact file list
|
||||||
|
|
||||||
|
Therefore every request can be validated.
|
||||||
|
|
||||||
|
Option 1 (recommended)
|
||||||
|
|
||||||
|
Allow nginx to serve files normally.
|
||||||
|
|
||||||
|
If file does not exist:
|
||||||
|
|
||||||
|
error_page 404 = @invalid_request;
|
||||||
|
|
||||||
|
location @invalid_request {
|
||||||
|
access_log /var/log/nginx/invalid-paths.log;
|
||||||
|
return 404;
|
||||||
|
}
|
||||||
|
|
||||||
|
This scales well even with tens of thousands of files.
|
||||||
|
|
||||||
|
Prevent Directory Browsing
|
||||||
|
|
||||||
|
autoindex off;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
try_files $uri =404;
|
||||||
|
}
|
||||||
|
|
||||||
|
No listings.
|
||||||
|
|
||||||
|
No traversal.
|
||||||
|
|
||||||
|
No guessing directories.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
thank you - I have refined the requirements now so ignore what I said earlier.
|
||||||
|
|
||||||
|
We are building a private community content delivery network, with nodes that serves files that typically change once a day.
|
||||||
|
|
||||||
|
## Hardware Requirements
|
||||||
|
|
||||||
|
Requirements for Hosting
|
||||||
|
|
||||||
|
- 24/7 Home Service
|
||||||
|
- fixed IP address
|
||||||
|
- unlimited bandwidth
|
||||||
|
- fast > 500mb/sec upload
|
||||||
|
- large > 5T of storage
|
||||||
|
- permission from your ISP to run a web server
|
||||||
|
- Contact information know to the Janitors
|
||||||
|
- Optional: UPS
|
||||||
|
|
||||||
|
## Software updates
|
||||||
|
|
||||||
|
|
||||||
|
The nodes should ideally run on both [Docker](https://en.wikipedia.org/wiki/Docker_%28software%29) and [Podman](https://en.wikipedia.org/wiki/Podman)
|
||||||
|
|
||||||
|
It should run Debian `debian:stable-slim`
|
||||||
|
|
||||||
|
All software MUST be running verifiable Free Libre Open Source software with links to the license.
|
||||||
|
|
||||||
|
The nodes should have as little utilities as possible to do it's job and in order to reduce the attack surface.
|
||||||
|
|
||||||
|
The amount of traffic is about 5Mb/sec with a load of 2 TPS
|
||||||
|
|
||||||
|
There will be many nodes, that will come and go over time.
|
||||||
|
|
||||||
|
The nodes will be added to DNS so the load can be shared.
|
||||||
|
|
||||||
|
The origin server requirements will be dictated by the needs of the nodes.
|
||||||
|
|
||||||
|
Nodes will monitor a rss control channel from the origin server for instructions.
|
||||||
|
|
||||||
|
The RSS file will contain a link to a json file.
|
||||||
|
|
||||||
|
The json file will contain
|
||||||
|
|
||||||
|
- the fail2ban settings, list of ip addresses, maxretry, findtime and bantime
|
||||||
|
- force full rsync Boolean flag - false
|
||||||
|
- hours between rsync eg every 3 hours
|
||||||
|
- list of origin servers
|
||||||
|
- list of Admin IP addresses
|
||||||
|
- list of IP addresses to ban immediately
|
||||||
|
- list of useragents to ban immediately
|
||||||
|
|
||||||
|
If the origin server is not available then the nodes should connect to the next origin server in the list.
|
||||||
|
|
||||||
|
nodes will synchronize the files from a origin server using rsync over ssh.
|
||||||
|
|
||||||
|
A full rsync is sufficient to ensure files are copied correctly.
|
||||||
|
|
||||||
|
A full rsync will be done several times a day, eg every 3 hours - from settings
|
||||||
|
|
||||||
|
atomic updates are not required as only new files are added.
|
||||||
|
|
||||||
|
There are thousands of files
|
||||||
|
|
||||||
|
The files will come from a well known directory eg `public_html/eps/hpr${ep_num}/hpr${ep_num}*.${extension}`
|
||||||
|
|
||||||
|
The `ep_num` will be a digit from 0001 to 9999
|
||||||
|
|
||||||
|
The `extension` will be from a well know list from the origin updated perhaps once a year
|
||||||
|
|
||||||
|
nodes will then serve the files using `nginx`
|
||||||
|
|
||||||
|
`nginx` must restrict access to files outside the well known files
|
||||||
|
|
||||||
|
A list of well known files that clients require (favorite icon, robots.txt, etc) will be available from the origin server.
|
||||||
|
|
||||||
|
The files we wish to serve are all Creative Commons Licensed so can be shared.
|
||||||
|
|
||||||
|
Random browsing is not allowed.
|
||||||
|
|
||||||
|
All file paths are known, and are provided by the origin server.
|
||||||
|
|
||||||
|
There should never be any request for a file outside the well known paths.
|
||||||
|
|
||||||
|
Any attempt to access any file outside the known paths should be logged.
|
||||||
|
|
||||||
|
Based on a configuration provided by the origin server, the server will run failtoban
|
||||||
|
|
||||||
|
repeat offenders will be banned for 7 days.
|
||||||
|
|
||||||
|
The Admin IP addresses should never be banned
|
||||||
|
|
||||||
|
traffic should be secured with let's encrypt of equivalent tls transport
|
||||||
|
|
||||||
|
clients can request the files without tls
|
||||||
|
|
||||||
|
The nodes public ip address will be placed on a allow list for ssh
|
||||||
|
|
||||||
|
Monitoring should be available using Prometheus or similar.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
+----------------+
|
||||||
|
| Origin Server A|
|
||||||
|
+--------+-------+
|
||||||
|
|
|
||||||
|
RSS Control Feed
|
||||||
|
|
|
||||||
|
JSON Configuration
|
||||||
|
|
|
||||||
|
+------------------+------------------+
|
||||||
|
| |
|
||||||
|
v v
|
||||||
|
|
||||||
|
+----------------+ +----------------+
|
||||||
|
| Edge Node 1 | | Edge Node 2 |
|
||||||
|
| | | |
|
||||||
|
| nginx | | nginx |
|
||||||
|
| rsync | | rsync |
|
||||||
|
| fail2ban | | fail2ban |
|
||||||
|
| node-agent | | node-agent |
|
||||||
|
| prometheus exp.| | prometheus exp.|
|
||||||
|
+--------+-------+ +--------+-------+
|
||||||
|
| |
|
||||||
|
+----------------+--------------------+
|
||||||
|
|
|
||||||
|
DNS RR
|
||||||
|
|
|
||||||
|
Clients
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
untrusted comment: minisign public key 85F68B90C4D786B9
|
||||||
|
RWS5htfEkIv2hSLt8UJmbUF4MV5P0JX+IcHq354RNF6wgC/bO/M3OxqU
|
||||||
Reference in New Issue
Block a user