A complete certificate delivery package for the following architecture:
- OpenWrt/DDNS obtains and renews the wildcard certificate
- OpenWrt pushes the cert/key to an in-cluster webhook
- The webhook updates a single central Kubernetes TLS Secret
- A Traefik-side mirror job copies that central Secret into
kube-system - Traefik uses the mirrored Secret as its default TLS certificate
This package is designed for repeatable deployment and later maintenance.
OpenWrt / DDNS
├─ obtains / renews certificate
├─ stores files under /etc/cert
└─ runs push-k3s-cert.sh after cert change
|
v
K3S in-cluster webhook (namespace: infra-tls)
├─ receives cert/key over HTTP POST
└─ writes central TLS Secret
|
v
central Secret
└─ infra-tls/xlserver-cn-tls
|
v
Traefik mirror CronJob (namespace: kube-system)
└─ mirrors central Secret to kube-system/traefik-default-tls
|
v
Traefik TLSStore default
└─ uses kube-system/traefik-default-tls as the default certificate
- Keep certificate issuance outside the cluster
- Keep only one central source Secret in the cluster
- Avoid distributing wildcard private keys to many application namespaces
- Let Traefik terminate TLS centrally
- Make renewal automatic end-to-end
projects/k3s-cert-webhook/
├── README.md
├── OPERATIONS.md
├── install.sh
├── k3s/
│ ├── 00-namespace.yaml
│ ├── 01-serviceaccount-rbac.yaml
│ ├── 02-configmap.yaml
│ ├── 03-deployment.yaml
│ └── 04-service.yaml
├── openwrt/
│ ├── push-k3s-cert.sh
│ └── token-setup.sh
└── traefik/
├── README.md
├── install-traefik-central-tls.sh
├── secret-mirror-rbac.yaml
├── secret-mirror-cronjob.yaml
└── tlsstore-default.yaml
For this environment, the recommended operating path is:
- Deploy the in-cluster webhook with
install.sh - Configure OpenWrt with
openwrt/push-k3s-cert.sh - Apply Traefik resources manually, one by one:
traefik/secret-mirror-rbac.yamltraefik/secret-mirror-cronjob.yamltraefik/tlsstore-default.yaml
- Use
traefik/install-traefik-central-tls.shonly as an optional helper or reference, not as the primary procedure
Namespace: infra-tls
Main resources:
ServiceAccount:cert-webhookDeployment:cert-webhookService:cert-webhookConfigMap: embedded Node.js server
Purpose:
- Accept a webhook POST from OpenWrt
- Validate token and cert/key format
- Upsert
infra-tls/xlserver-cn-tls
Files:
openwrt/push-k3s-cert.shopenwrt/token-setup.sh
Purpose:
- Read
/etc/cert/xlserver.cn.pem - Read
/etc/cert/xlserver.cn.key - Read token from
/etc/cert-sync/webhook_token - Push to the K3S NodePort endpoint
Namespace: kube-system
Resources:
ServiceAccount:traefik-secret-mirrorCronJob:traefik-secret-mirrorTLSStore:default
Purpose:
- Mirror
infra-tls/xlserver-cn-tls - Write
kube-system/traefik-default-tls - Configure Traefik to use the mirrored Secret as the default TLS certificate
| Item | Value |
|---|---|
| Central namespace | infra-tls |
| Central secret | xlserver-cn-tls |
| Webhook path | /sync-cert |
| Webhook NodePort | 30080 |
| Traefik mirrored secret | kube-system/traefik-default-tls |
| Traefik TLSStore | kube-system/default |
cd projects/k3s-cert-webhook
./install.shManual equivalent:
kubectl apply -f k3s/00-namespace.yaml
kubectl apply -f k3s/01-serviceaccount-rbac.yaml
kubectl apply -f k3s/02-configmap.yaml
kubectl apply -f k3s/03-deployment.yaml
kubectl apply -f k3s/04-service.yaml
kubectl -n infra-tls rollout status deploy/cert-webhook- Copy
openwrt/push-k3s-cert.shto OpenWrt - Copy
openwrt/token-setup.shto OpenWrt - Set the actual token value
- Set the actual K3S node URL
- Trigger the script manually once
- Put it into your DDNS / certificate-change hook
You can apply these manually or use the helper scripts/files in traefik/.
Core resources:
traefik/secret-mirror-rbac.yamltraefik/secret-mirror-cronjob.yamltraefik/tlsstore-default.yaml
kubectl -n infra-tls get all
kubectl -n infra-tls logs deploy/cert-webhook -f
curl http://<k3s-node-ip>:30080/healthzkubectl -n infra-tls get secret xlserver-cn-tls
kubectl -n infra-tls get secret xlserver-cn-tls -o jsonpath='{.data.tls\.crt}' \
| base64 -d \
| openssl x509 -noout -subject -issuer -dateskubectl -n kube-system get secret traefik-default-tls
kubectl -n kube-system get secret traefik-default-tls -o jsonpath='{.data.tls\.crt}' \
| base64 -d \
| openssl x509 -noout -subject -issuer -dateskubectl -n kube-system get tlsstore
kubectl -n kube-system describe tlsstore defaultecho | openssl s_client -servername app.xlserver.cn -connect app.xlserver.cn:443 2>/dev/null \
| openssl x509 -noout -subject -issuer -dates- OpenWrt renews the certificate
- OpenWrt pushes cert/key to the in-cluster webhook
- Webhook updates
infra-tls/xlserver-cn-tls - CronJob mirrors it to
kube-system/traefik-default-tls - Traefik serves the renewed certificate
- Wildcard private key is not copied to every application namespace
- Only one central source Secret exists in
infra-tls - Only one Traefik-consumption copy exists in
kube-system - Webhook is token-protected
- OpenWrt token is stored in a file, not hardcoded in the push script
- This package assumes Traefik CRDs use
apiVersion: traefik.io/v1alpha1 - If your environment differs, adjust
traefik/tlsstore-default.yaml - Application Ingresses that explicitly set their own
tls.secretNamewill continue using their own certificate instead of Traefik's default certificate - The Traefik mirror CronJob is intentionally separate from the OpenWrt push step to keep concerns isolated
OPERATIONS.mdfor the full operations handbooktraefik/README.mdfor Traefik-specific notes