Latest Updates done, before integrating

This commit is contained in:
2026-04-12 10:13:53 +02:00
parent db46fcf0c6
commit 5354e34055
19 changed files with 820 additions and 41 deletions

12
infra/ca/.gitignore vendored Normal file
View File

@@ -0,0 +1,12 @@
# Private keys and device certs — NEVER commit
*.key
*.p12
*.csr
*.srl
# ca.crt and server.crt are public — safe to commit if desired,
# but kept out by default so the repo doesn't become a cert store.
# Uncomment the next two lines to allow committing public certs:
# !ca.crt
# !server.crt
*.crt

94
infra/ca/enroll-iphone.md Normal file
View File

@@ -0,0 +1,94 @@
# iPhone Enrollment Guide
Two steps: first install the CA as a trusted root, then install your personal client cert.
Both must be done before Safari can reach the EMS.
---
## Step 1 — Install the CA cert (trust anchor)
Do this once. It makes your iPhone trust the EMS server cert and accept the client cert.
### On your Mac / Linux machine (in the `infra/ca/` directory):
```bash
# Serve ca.crt temporarily on the LAN
python3 -m http.server 8080
```
### On the iPhone (Safari — not Chrome):
1. Open **Safari** and navigate to `http://192.168.x.x:8080/ca.crt`
(replace with your Mac's LAN IP — check with `ifconfig | grep 192.168`)
2. Safari shows: *"This website is trying to download a configuration profile. Do you want to allow this?"***Allow**
3. Open **Settings** → you will see a banner: **Profile Downloaded** → tap it → **Install**
4. Enter your iPhone passcode if prompted → **Install** (top right) → **Install** again to confirm
5. Go to **Settings → General → About → Certificate Trust Settings**
6. Under *"Enable Full Trust For Root Certificates"*, toggle **EMS Private CA****Continue**
The CA is now trusted. You can stop the Python server.
---
## Step 2 — Install the client cert
Do this once per device. Generate the cert first if you haven't:
```bash
cd infra/ca
./issue-client-cert.sh lutz-iphone
```
Then transfer `lutz-iphone.p12` to the iPhone. The easiest ways:
**AirDrop (recommended):**
1. On Mac: right-click `lutz-iphone.p12` → Share → AirDrop → select your iPhone
2. On iPhone: tap Accept
3. Tap the received file → **Settings** opens automatically
4. **Settings → Profile Downloaded****Install** → enter PKCS12 password → **Install**
**Alternatively via Files / Mail / Notes:**
- Share the `.p12` file to yourself via any app, then tap it to trigger profile installation.
---
## Step 3 — Test
1. Open **Safari** on the iPhone
2. Navigate to `https://ems.famfi.dyndns.org:9443`
3. Safari will prompt: *"ems.famfi.dyndns.org" wants to use "lutz-iphone EMS"***Continue**
4. The EMS login page should appear — no certificate warning
---
## Revoking a device
There is no CRL/OCSP for this private CA (not needed for a home setup).
To revoke a device:
1. On the iPhone: **Settings → General → VPN & Device Management** → select the EMS profile → **Remove**
2. Generate a new CA (`gen-ca.sh`) and re-enroll all remaining devices, OR
regenerate only the server cert and client certs — a revoked client cert is still technically valid
until the Traefik config is updated to exclude it by CN.
For a home setup with 1-2 devices, deleting the profile from the device is sufficient protection.
---
## Cert renewal (annually)
Server and client certs are valid for 825 days (~2.25 years). When they approach expiry:
```bash
cd infra/ca
./gen-server-cert.sh # new server cert
scp server.crt server.key user@synology:/etc/ems/certs/
ssh user@synology "cd /opt/ems && docker compose restart traefik"
./issue-client-cert.sh lutz-iphone # new client cert — repeat enrollment Step 2
```
The CA itself is valid for 10 years. Its expiry date:
```bash
openssl x509 -noout -dates -in infra/ca/ca.crt
```

40
infra/ca/gen-ca.sh Executable file
View File

@@ -0,0 +1,40 @@
#!/usr/bin/env bash
# gen-ca.sh — Generate the EMS root CA (one-time operation).
#
# Outputs: ca.key (KEEP SECRET) and ca.crt (distribute to all devices).
# ca.key must be backed up encrypted and kept off the Synology.
# Losing ca.key means all devices must re-enroll after generating a new CA.
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cd "$SCRIPT_DIR"
if [[ -f ca.key ]]; then
echo "ERROR: ca.key already exists."
echo "Delete it explicitly if you intend to replace the CA."
echo "WARNING: a new CA invalidates ALL existing server and client certs — every device must re-enroll."
exit 1
fi
echo "Generating EMS root CA (RSA-4096, 10 years)..."
openssl req -x509 -newkey rsa:4096 -sha256 \
-days 3650 \
-keyout ca.key \
-out ca.crt \
-nodes \
-subj "/CN=EMS Private CA/O=FamFi/C=DE" \
-addext "basicConstraints=critical,CA:TRUE,pathlen:0" \
-addext "keyUsage=critical,keyCertSign,cRLSign" \
-addext "subjectKeyIdentifier=hash"
chmod 600 ca.key
echo ""
echo "Done."
echo " ca.crt — distribute to devices (install as trusted root)"
echo " ca.key — KEEP SECRET: store in encrypted backup, remove from Synology after cert issuance"
echo ""
echo "Next steps:"
echo " ./gen-server-cert.sh # server cert for Traefik"
echo " ./issue-client-cert.sh <name> # one per device"

47
infra/ca/gen-server-cert.sh Executable file
View File

@@ -0,0 +1,47 @@
#!/usr/bin/env bash
# gen-server-cert.sh — Generate the Traefik server cert for ems.famfi.dyndns.org.
#
# Signed by the private CA. Once ca.crt is trusted on a device,
# this cert is accepted without warnings.
# Renew annually (before 825-day expiry) by re-running this script.
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cd "$SCRIPT_DIR"
DOMAIN="${1:-ems.famfi.dyndns.org}"
if [[ ! -f ca.key ]]; then
echo "ERROR: ca.key not found. Run gen-ca.sh first."
exit 1
fi
echo "Generating server cert for ${DOMAIN}..."
openssl req -newkey rsa:2048 -nodes \
-keyout server.key \
-out server.csr \
-subj "/CN=${DOMAIN}/O=FamFi/C=DE"
openssl x509 -req \
-in server.csr \
-CA ca.crt \
-CAkey ca.key \
-CAcreateserial \
-out server.crt \
-days 825 \
-sha256 \
-extfile <(printf "basicConstraints=CA:FALSE\nkeyUsage=critical,digitalSignature,keyEncipherment\nextendedKeyUsage=serverAuth\nsubjectAltName=DNS:%s\nsubjectKeyIdentifier=hash\nauthorityKeyIdentifier=keyid" "$DOMAIN")
chmod 600 server.key
rm -f server.csr
echo ""
echo "Done: server.crt + server.key for ${DOMAIN}"
echo ""
echo "Deploy certs to Synology (run from infra/ca/):"
echo " ssh user@synology 'mkdir -p /etc/ems/certs'"
echo " scp ca.crt server.crt server.key user@synology:/etc/ems/certs/"
echo " ssh user@synology 'chmod 600 /etc/ems/certs/server.key'"
echo ""
echo "Traefik will pick up the new cert on next container restart."

57
infra/ca/issue-client-cert.sh Executable file
View File

@@ -0,0 +1,57 @@
#!/usr/bin/env bash
# issue-client-cert.sh — Issue a client cert for one device.
#
# Usage: ./issue-client-cert.sh <device-name>
# Example: ./issue-client-cert.sh lutz-iphone
#
# Outputs <device-name>.p12 — install on the device after enrolling ca.crt.
# See enroll-iphone.md for the full iPhone enrollment flow.
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cd "$SCRIPT_DIR"
NAME="${1:?Usage: $0 <device-name> e.g.: $0 lutz-iphone}"
if [[ ! -f ca.key ]]; then
echo "ERROR: ca.key not found. Run gen-ca.sh first."
exit 1
fi
if [[ -f "${NAME}.p12" ]]; then
echo "WARNING: ${NAME}.p12 already exists. Overwriting."
fi
echo "Issuing client cert for: ${NAME}"
echo "You will be prompted for a PKCS12 export password."
echo "Use a strong password — you will need it during iPhone installation."
echo ""
openssl req -newkey rsa:2048 -nodes \
-keyout "${NAME}.key" \
-out "${NAME}.csr" \
-subj "/CN=${NAME}/O=FamFi/C=DE"
openssl x509 -req \
-in "${NAME}.csr" \
-CA ca.crt \
-CAkey ca.key \
-CAcreateserial \
-out "${NAME}.crt" \
-days 825 \
-sha256 \
-extfile <(printf "basicConstraints=CA:FALSE\nkeyUsage=critical,digitalSignature\nextendedKeyUsage=clientAuth\nsubjectKeyIdentifier=hash\nauthorityKeyIdentifier=keyid")
openssl pkcs12 -export \
-out "${NAME}.p12" \
-inkey "${NAME}.key" \
-in "${NAME}.crt" \
-certfile ca.crt \
-name "${NAME} EMS"
chmod 600 "${NAME}.key" "${NAME}.p12"
rm -f "${NAME}.csr" "${NAME}.crt"
echo ""
echo "Done: ${NAME}.p12"
echo "AirDrop to iPhone, then follow enroll-iphone.md."

37
infra/traefik/dynamic.yml Normal file
View File

@@ -0,0 +1,37 @@
# Traefik dynamic configuration — hot-reloaded by Traefik on change.
#
# TLS: private CA server cert + mandatory client cert (mTLS).
# Any connection without a valid client cert signed by ca.crt is rejected
# at the TLS handshake — before any HTTP reaches EMS.
tls:
certificates:
- certFile: /certs/server.crt
keyFile: /certs/server.key
options:
mtls:
clientAuth:
caFiles:
- /certs/ca.crt
clientAuthType: RequireAndVerifyClientCert
# Minimum TLS 1.2; prefer 1.3
minVersion: VersionTLS12
sniStrict: true
http:
routers:
ems:
rule: "Host(`ems.famfi.dyndns.org`)"
entryPoints:
- websecure
tls:
options: mtls
service: ems
services:
ems:
loadBalancer:
servers:
- url: "http://ems:9099"
passHostHeader: true

19
infra/traefik/traefik.yml Normal file
View File

@@ -0,0 +1,19 @@
# Traefik static configuration
# Handles TLS termination on port 9443 with private CA mTLS.
# Dynamic routing config is in dynamic.yml (hot-reloaded on change).
entryPoints:
websecure:
address: ":9443"
providers:
file:
filename: /etc/traefik/dynamic.yml
watch: true # reload dynamic.yml without container restart
log:
level: INFO
accessLog:
filePath: "/dev/stdout"
format: common