Latest Updates done, before integrating
This commit is contained in:
12
infra/ca/.gitignore
vendored
Normal file
12
infra/ca/.gitignore
vendored
Normal 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
94
infra/ca/enroll-iphone.md
Normal 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
40
infra/ca/gen-ca.sh
Executable 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
47
infra/ca/gen-server-cert.sh
Executable 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
57
infra/ca/issue-client-cert.sh
Executable 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
37
infra/traefik/dynamic.yml
Normal 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
19
infra/traefik/traefik.yml
Normal 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
|
||||
Reference in New Issue
Block a user