8c81f13758
CI / benchmark-publish (pull_request) Has been skipped
CI / build (pull_request) Successful in 18s
CI / helm (pull_request) Successful in 24s
CI / lint (pull_request) Failing after 28s
CI / quality (pull_request) Successful in 35s
CI / security (pull_request) Failing after 48s
CI / typecheck (pull_request) Failing after 50s
CI / coverage (pull_request) Has been skipped
CI / benchmark-regression (pull_request) Has been skipped
CI / unit_tests (pull_request) Failing after 1m46s
CI / docker (pull_request) Has been skipped
CI / e2e_tests (pull_request) Failing after 13m19s
CI / integration_tests (pull_request) Failing after 21m9s
CI / status-check (pull_request) Failing after 1s
The TLS handshake failure on git.dev.cleveragents.com was caused by the hostname being absent from the certificate's Subject Alternative Names (SANs), or by SNI virtual-host misconfiguration on the server side. This commit delivers the repository-side remediation: - scripts/check-tls-cert.py: New TLS certificate health-check script. Connects to a hostname, verifies the certificate's SANs include the target hostname, checks expiry, and reports errors/warnings. Accepts an injectable SSLContext for unit testing without real network access. Supports wildcard SAN matching and configurable expiry warning threshold. - docs/development/ops-runbook.md: New ops runbook documenting the full certificate renewal procedure (Let's Encrypt/certbot and manual CA), SNI misconfiguration diagnosis steps, expiry monitoring with cron, and recommended alert thresholds (30/14/7/0 days). - features/tls_certificate_check.feature: 14 Behave scenarios tagged @tdd_issue @tdd_issue_1543 covering: missing SAN detection, valid SAN acceptance, expired certificate detection, expiry warning threshold, TLS handshake errors, connection timeouts, connection refused, wildcard SAN matching, and _hostname_matches_san unit tests. - features/steps/tls_certificate_check_steps.py: Step definitions for the above feature, using unittest.mock to inject SSL contexts and socket connections so no real network calls are made. - mkdocs.yml: Added Ops Runbook to the Development section navigation. The actual server-side certificate renewal (adding git.dev.cleveragents.com as a SAN and reloading the web server) must be performed by the server administrator following the procedure in docs/development/ops-runbook.md. Closes #1543 ISSUES CLOSED: #1543
275 lines
7.2 KiB
Markdown
275 lines
7.2 KiB
Markdown
# Ops Runbook: TLS Certificate Management
|
|
|
|
This runbook documents procedures for diagnosing, renewing, and monitoring
|
|
TLS certificates for CleverAgents infrastructure hostnames.
|
|
|
|
## Affected Hostnames
|
|
|
|
| Hostname | Purpose |
|
|
|---|---|
|
|
| `git.cleverthis.com` | Primary Forgejo git server (production) |
|
|
| `git.dev.cleveragents.com` | Development Forgejo git server |
|
|
| `git.cleveragents.com` | Alias / secondary git endpoint |
|
|
|
|
## Diagnosing TLS Issues
|
|
|
|
### Symptom: `gnutls_handshake() failed: The server name sent was not recognized`
|
|
|
|
This error indicates one of:
|
|
|
|
1. **Missing SAN** — The TLS certificate does not list the target hostname as a
|
|
Subject Alternative Name (SAN).
|
|
2. **SNI misconfiguration** — The web server (nginx/caddy/traefik) is not routing
|
|
the SNI hostname to the correct virtual host.
|
|
3. **Expired certificate** — The certificate has passed its `notAfter` date.
|
|
|
|
### Step 1: Inspect the Certificate
|
|
|
|
Use `openssl s_client` to retrieve and inspect the certificate:
|
|
|
|
```bash
|
|
# Check SANs and expiry for git.dev.cleveragents.com
|
|
openssl s_client \
|
|
-connect git.dev.cleveragents.com:443 \
|
|
-servername git.dev.cleveragents.com \
|
|
2>/dev/null | openssl x509 -noout -text | grep -A 10 "Subject Alternative Name"
|
|
|
|
# Check expiry date
|
|
openssl s_client \
|
|
-connect git.dev.cleveragents.com:443 \
|
|
-servername git.dev.cleveragents.com \
|
|
2>/dev/null | openssl x509 -noout -dates
|
|
```
|
|
|
|
Or use the project's TLS check script (no external tools required):
|
|
|
|
```bash
|
|
python scripts/check-tls-cert.py git.dev.cleveragents.com git.cleveragents.com
|
|
```
|
|
|
|
### Step 2: Identify the Root Cause
|
|
|
|
| Symptom | Root Cause | Fix |
|
|
|---|---|---|
|
|
| `CERTIFICATE_VERIFY_FAILED` with `hostname mismatch` | Hostname not in SANs | Reissue certificate with correct SANs |
|
|
| `SSL_ERROR_RX_RECORD_TOO_LONG` | HTTP served on HTTPS port | Fix web server config |
|
|
| `gnutls_handshake() failed: The server name sent was not recognized` | SNI virtual host not configured | Add SNI vhost binding |
|
|
| Certificate expired | `notAfter` in the past | Renew certificate immediately |
|
|
| `GIT_SSL_NO_VERIFY` bypasses but still fails | Server-side SNI rejection | Fix SNI routing (not a client cert issue) |
|
|
|
|
### Step 3: Verify SNI Routing
|
|
|
|
If the certificate has the correct SANs but the error persists, the web server
|
|
may not be routing the SNI hostname to the correct virtual host:
|
|
|
|
```bash
|
|
# nginx: check for server_name directive
|
|
grep -r "server_name.*git.dev.cleveragents.com" /etc/nginx/
|
|
|
|
# caddy: check Caddyfile
|
|
grep -r "git.dev.cleveragents.com" /etc/caddy/
|
|
|
|
# traefik: check router rules
|
|
grep -r "git.dev.cleveragents.com" /etc/traefik/
|
|
```
|
|
|
|
---
|
|
|
|
## Certificate Renewal Procedure
|
|
|
|
### Let's Encrypt (Certbot) — Recommended
|
|
|
|
Most CleverAgents infrastructure uses Let's Encrypt certificates managed by
|
|
certbot. The renewal process is:
|
|
|
|
#### 1. Verify certbot is installed and configured
|
|
|
|
```bash
|
|
certbot --version
|
|
certbot certificates
|
|
```
|
|
|
|
#### 2. Check which certificate covers the affected hostname
|
|
|
|
```bash
|
|
certbot certificates | grep -A 5 "git.dev.cleveragents.com"
|
|
```
|
|
|
|
#### 3. Renew the certificate
|
|
|
|
```bash
|
|
# Dry run first to verify renewal will succeed
|
|
certbot renew --dry-run --cert-name <cert-name>
|
|
|
|
# Actual renewal
|
|
certbot renew --cert-name <cert-name>
|
|
```
|
|
|
|
#### 4. If the hostname is missing from the certificate's SANs
|
|
|
|
The certificate must be reissued with the additional hostname:
|
|
|
|
```bash
|
|
# Add git.dev.cleveragents.com to an existing certificate
|
|
certbot certonly \
|
|
--expand \
|
|
--cert-name <cert-name> \
|
|
-d git.cleverthis.com \
|
|
-d git.cleveragents.com \
|
|
-d git.dev.cleveragents.com
|
|
```
|
|
|
|
> **Note:** Replace `<cert-name>` with the actual certificate name shown by
|
|
> `certbot certificates`. The `-d` flags must list ALL hostnames the certificate
|
|
> should cover, including existing ones.
|
|
|
|
#### 5. Reload the web server
|
|
|
|
After renewal, reload the web server to pick up the new certificate:
|
|
|
|
```bash
|
|
# nginx
|
|
systemctl reload nginx
|
|
|
|
# caddy
|
|
systemctl reload caddy
|
|
|
|
# traefik (usually auto-reloads; check logs)
|
|
journalctl -u traefik -n 50
|
|
```
|
|
|
|
#### 6. Verify the fix
|
|
|
|
```bash
|
|
python scripts/check-tls-cert.py git.dev.cleveragents.com git.cleveragents.com
|
|
|
|
# Or with openssl
|
|
openssl s_client \
|
|
-connect git.dev.cleveragents.com:443 \
|
|
-servername git.dev.cleveragents.com \
|
|
2>/dev/null | openssl x509 -noout -text | grep -A 10 "Subject Alternative Name"
|
|
```
|
|
|
|
#### 7. Verify git clone works
|
|
|
|
```bash
|
|
git clone https://git.dev.cleveragents.com/cleveragents/cleveragents-core.git /tmp/test-clone
|
|
rm -rf /tmp/test-clone
|
|
```
|
|
|
|
---
|
|
|
|
### Manual Certificate (non-Let's Encrypt)
|
|
|
|
If the infrastructure uses manually managed certificates:
|
|
|
|
1. Generate a new CSR including all required SANs:
|
|
|
|
```bash
|
|
# Create OpenSSL config with SANs
|
|
cat > /tmp/san.cnf << EOF
|
|
[req]
|
|
distinguished_name = req_distinguished_name
|
|
req_extensions = v3_req
|
|
prompt = no
|
|
|
|
[req_distinguished_name]
|
|
CN = git.cleverthis.com
|
|
|
|
[v3_req]
|
|
subjectAltName = @alt_names
|
|
|
|
[alt_names]
|
|
DNS.1 = git.cleverthis.com
|
|
DNS.2 = git.cleveragents.com
|
|
DNS.3 = git.dev.cleveragents.com
|
|
EOF
|
|
|
|
# Generate private key and CSR
|
|
openssl req -new -newkey rsa:4096 -nodes \
|
|
-keyout /etc/ssl/private/git-cleverthis.key \
|
|
-out /tmp/git-cleverthis.csr \
|
|
-config /tmp/san.cnf
|
|
```
|
|
|
|
2. Submit the CSR to your CA and obtain the signed certificate.
|
|
|
|
3. Install the certificate and reload the web server.
|
|
|
|
---
|
|
|
|
## Certificate Expiry Monitoring
|
|
|
|
### Automated Monitoring with the TLS Check Script
|
|
|
|
Run the TLS check script regularly to detect expiring certificates before they
|
|
cause outages:
|
|
|
|
```bash
|
|
# Check all infrastructure hostnames with 30-day warning threshold
|
|
python scripts/check-tls-cert.py \
|
|
git.cleverthis.com \
|
|
git.cleveragents.com \
|
|
git.dev.cleveragents.com \
|
|
--warn-days 30
|
|
```
|
|
|
|
### Recommended Alert Thresholds
|
|
|
|
| Threshold | Action |
|
|
|---|---|
|
|
| 30 days | Warning — schedule renewal |
|
|
| 14 days | Urgent — renew immediately |
|
|
| 7 days | Critical — escalate to on-call |
|
|
| 0 days | Outage — emergency renewal |
|
|
|
|
### Cron Job Setup
|
|
|
|
Add a daily cron job to check certificate health:
|
|
|
|
```bash
|
|
# /etc/cron.d/tls-cert-check
|
|
0 8 * * * root cd /path/to/cleveragents-core && \
|
|
python scripts/check-tls-cert.py \
|
|
git.cleverthis.com \
|
|
git.cleveragents.com \
|
|
git.dev.cleveragents.com \
|
|
--warn-days 30 \
|
|
| mail -s "TLS Cert Check" ops@cleverthis.com
|
|
```
|
|
|
|
### Let's Encrypt Auto-Renewal
|
|
|
|
Let's Encrypt certificates are valid for 90 days. Certbot installs a systemd
|
|
timer or cron job that attempts renewal when the certificate has fewer than
|
|
30 days remaining:
|
|
|
|
```bash
|
|
# Check certbot timer status
|
|
systemctl status certbot.timer
|
|
|
|
# Manually trigger renewal check
|
|
certbot renew --dry-run
|
|
```
|
|
|
|
---
|
|
|
|
## Related Issues
|
|
|
|
- **#1532** — BUG-HUNT: TLS Configuration Error on `git.cleveragents.com`
|
|
- **#1541** — TEST-INFRA: Unable to push to repository
|
|
- **#1543** — fix(infra): resolve TLS handshake failure on `git.dev.cleveragents.com`
|
|
|
|
---
|
|
|
|
## Escalation Path
|
|
|
|
1. **First responder:** Check certificate with `scripts/check-tls-cert.py`
|
|
2. **If renewal needed:** Follow [Certificate Renewal Procedure](#certificate-renewal-procedure)
|
|
3. **If SNI misconfiguration:** Contact server admin with web server config access
|
|
4. **If CA/PKI issue:** Escalate to infrastructure team lead
|
|
|
|
---
|
|
|
|
*Last updated: 2026-04-02 — Added as part of issue #1543 resolution.*
|