- 本地化命令描述(英文→中文) - 删除未使用命令文件 - 新增 summarize-conversation 命令 - 更新 AI 模型配置为 DeepSeek - 新增 agent-browser 技能 - 重构技能目录结构(重命名)
351 lines
10 KiB
Markdown
351 lines
10 KiB
Markdown
# Setup MQTTS with Auto-Renewal Certificate
|
|
|
|
## Description
|
|
Complete workflow to configure MQTTS (MQTT over TLS) for EMQX using acme.sh with automatic certificate renewal. Supports DNS-based certificate validation (Alibaba Cloud DNS API).
|
|
|
|
## Parameters
|
|
- `domain`: MQTT domain name (e.g., mq.example.com)
|
|
- `ca`: Certificate Authority (default: zerossl, options: letsencrypt, zerossl)
|
|
- `dns_provider`: DNS provider (default: dns_ali, supports acme.sh DNS APIs)
|
|
- `emqx_container`: EMQX Docker container name (default: emqx)
|
|
- `cert_dir`: Certificate storage directory (default: /root/certs)
|
|
|
|
## Prerequisites Check
|
|
1. Verify DNS resolution for the domain
|
|
2. Check if acme.sh is installed
|
|
3. Verify DNS API credentials are configured
|
|
4. Check if EMQX container is running
|
|
5. Verify current container configuration
|
|
|
|
## Execution Steps
|
|
|
|
### Phase 1: DNS and Environment Verification
|
|
```bash
|
|
# Check DNS resolution
|
|
dig ${domain} +short
|
|
|
|
# Get server public IP
|
|
curl -s ifconfig.me
|
|
|
|
# Verify acme.sh installation
|
|
~/.acme.sh/acme.sh --version
|
|
|
|
# Check DNS API configuration
|
|
cat ~/.acme.sh/account.conf | grep -E "Ali_Key|Ali_Secret"
|
|
|
|
# Check EMQX container status
|
|
docker ps | grep ${emqx_container}
|
|
```
|
|
|
|
### Phase 2: Certificate Directory Setup
|
|
```bash
|
|
# Create certificate directory structure
|
|
mkdir -p ${cert_dir}/${domain}
|
|
chmod 755 ${cert_dir}
|
|
chmod 700 ${cert_dir}/${domain}
|
|
```
|
|
|
|
### Phase 3: Certificate Issuance
|
|
```bash
|
|
# Issue certificate using DNS validation
|
|
~/.acme.sh/acme.sh --issue \
|
|
--dns ${dns_provider} \
|
|
-d ${domain} \
|
|
--keylength 2048 \
|
|
--server ${ca}
|
|
```
|
|
|
|
### Phase 4: Certificate Installation
|
|
```bash
|
|
# Install certificate with auto-reload
|
|
~/.acme.sh/acme.sh --install-cert \
|
|
-d ${domain} \
|
|
--cert-file ${cert_dir}/${domain}/cert.pem \
|
|
--key-file ${cert_dir}/${domain}/key.pem \
|
|
--fullchain-file ${cert_dir}/${domain}/fullchain.pem \
|
|
--reloadcmd "docker restart ${emqx_container}"
|
|
|
|
# Set proper permissions
|
|
chmod 755 ${cert_dir}/${domain}
|
|
chmod 644 ${cert_dir}/${domain}/cert.pem
|
|
chmod 644 ${cert_dir}/${domain}/key.pem
|
|
chmod 644 ${cert_dir}/${domain}/fullchain.pem
|
|
|
|
# Create CA cert symlink
|
|
ln -sf ${cert_dir}/${domain}/fullchain.pem ${cert_dir}/${domain}/cacert.pem
|
|
```
|
|
|
|
### Phase 5: Extract Public Key
|
|
```bash
|
|
# Extract public key from private key
|
|
openssl rsa -in ${cert_dir}/${domain}/key.pem -pubout -out ${cert_dir}/${domain}/public.pem 2>/dev/null
|
|
chmod 644 ${cert_dir}/${domain}/public.pem
|
|
```
|
|
|
|
### Phase 6: EMQX Container Reconfiguration
|
|
```bash
|
|
# Backup current container configuration
|
|
docker inspect ${emqx_container} > /root/emqx-backup-$(date +%Y%m%d-%H%M%S).json
|
|
|
|
# Get current container ports and volumes
|
|
PORTS=$(docker inspect ${emqx_container} --format '{{range $p, $conf := .NetworkSettings.Ports}}{{if $conf}}-p {{(index $conf 0).HostPort}}:{{$p}} {{end}}{{end}}')
|
|
|
|
# Stop and remove current container
|
|
docker stop ${emqx_container}
|
|
docker rm ${emqx_container}
|
|
|
|
# Recreate container with certificate mount
|
|
docker run -d \
|
|
--name ${emqx_container} \
|
|
--restart unless-stopped \
|
|
-p 1883:1883 \
|
|
-p 8083:8083 \
|
|
-p 8084:8084 \
|
|
-p 8883:8883 \
|
|
-p 18083:18083 \
|
|
-v /root/emqx/data:/opt/emqx/data \
|
|
-v /root/emqx/log:/opt/emqx/log \
|
|
-v ${cert_dir}/${domain}:/opt/emqx/etc/certs:ro \
|
|
emqx/emqx:5.8.8
|
|
```
|
|
|
|
### Phase 7: Verification
|
|
```bash
|
|
# Wait for container to start
|
|
sleep 8
|
|
|
|
# Check container status
|
|
docker ps | grep ${emqx_container}
|
|
|
|
# Verify certificate files in container
|
|
docker exec ${emqx_container} ls -l /opt/emqx/etc/certs/
|
|
|
|
# Check EMQX listeners
|
|
docker exec ${emqx_container} emqx ctl listeners
|
|
|
|
# Test SSL connection
|
|
timeout 10 openssl s_client -connect ${domain}:8883 -servername ${domain} -showcerts 2>/dev/null | openssl x509 -noout -text | grep -E "Subject:|Issuer:|Not Before|Not After|DNS:"
|
|
|
|
# Verify SSL handshake
|
|
timeout 10 openssl s_client -connect ${domain}:8883 -servername ${domain} 2>&1 <<< "Q" | grep -E "Verify return code:|SSL handshake|Protocol|Cipher"
|
|
```
|
|
|
|
### Phase 8: Generate Documentation
|
|
```bash
|
|
# Extract public key fingerprint
|
|
PUB_FP_MD5=$(openssl rsa -in ${cert_dir}/${domain}/key.pem -pubout 2>/dev/null | openssl md5 | awk '{print $2}')
|
|
PUB_FP_SHA256=$(openssl rsa -in ${cert_dir}/${domain}/key.pem -pubout 2>/dev/null | openssl sha256 | awk '{print $2}')
|
|
|
|
# Extract certificate fingerprints
|
|
CERT_FP_MD5=$(openssl x509 -in ${cert_dir}/${domain}/cert.pem -noout -fingerprint -md5 | cut -d= -f2)
|
|
CERT_FP_SHA1=$(openssl x509 -in ${cert_dir}/${domain}/cert.pem -noout -fingerprint -sha1 | cut -d= -f2)
|
|
CERT_FP_SHA256=$(openssl x509 -in ${cert_dir}/${domain}/cert.pem -noout -fingerprint -sha256 | cut -d= -f2)
|
|
|
|
# Generate fingerprints file
|
|
cat > ${cert_dir}/${domain}/fingerprints.txt << EOF
|
|
Certificate and Key Fingerprints
|
|
Generated: $(date)
|
|
|
|
=== Private Key Fingerprint ===
|
|
MD5: ${PUB_FP_MD5}
|
|
SHA256: ${PUB_FP_SHA256}
|
|
|
|
=== Certificate Fingerprint ===
|
|
MD5: ${CERT_FP_MD5}
|
|
SHA1: ${CERT_FP_SHA1}
|
|
SHA256: ${CERT_FP_SHA256}
|
|
|
|
=== Certificate Details ===
|
|
$(openssl x509 -in ${cert_dir}/${domain}/cert.pem -noout -subject -issuer -dates)
|
|
EOF
|
|
|
|
# Generate README
|
|
cat > ${cert_dir}/${domain}/README.txt << 'EOF'
|
|
EMQX MQTTS Certificate Files
|
|
|
|
Domain: ${domain}
|
|
Created: $(date +%Y-%m-%d)
|
|
CA: ${ca}
|
|
Key Length: RSA 2048
|
|
Auto-Renewal: Enabled
|
|
|
|
Files:
|
|
- cert.pem: Server certificate
|
|
- key.pem: Private key (CONFIDENTIAL)
|
|
- public.pem: Public key
|
|
- fullchain.pem: Full certificate chain
|
|
- cacert.pem: CA certificate (symlink)
|
|
- fingerprints.txt: Certificate fingerprints
|
|
- README.txt: This file
|
|
|
|
Client Configuration:
|
|
- PC/Mobile: Use system CA (no files needed)
|
|
- Embedded: Use fullchain.pem
|
|
- Connection: mqtts://${domain}:8883
|
|
|
|
Security:
|
|
⚠️ Keep key.pem secure and confidential
|
|
✓ cert.pem and fullchain.pem are safe to distribute
|
|
EOF
|
|
```
|
|
|
|
### Phase 9: Create Backup Package
|
|
```bash
|
|
# Create compressed backup
|
|
cd ${cert_dir}
|
|
tar czf ${domain}-complete-$(date +%Y%m%d-%H%M%S).tar.gz ${domain}/
|
|
|
|
# Generate checksums
|
|
md5sum ${domain}-complete-*.tar.gz | tail -1 > ${domain}-checksums.txt
|
|
sha256sum ${domain}-complete-*.tar.gz | tail -1 >> ${domain}-checksums.txt
|
|
```
|
|
|
|
### Phase 10: Verify Auto-Renewal
|
|
```bash
|
|
# Check certificate renewal configuration
|
|
~/.acme.sh/acme.sh --info -d ${domain}
|
|
|
|
# List all managed certificates
|
|
~/.acme.sh/acme.sh --list
|
|
|
|
# Verify cron job
|
|
crontab -l | grep acme
|
|
```
|
|
|
|
## Post-Installation Summary
|
|
Display the following information:
|
|
1. Certificate details (domain, validity, CA)
|
|
2. EMQX container status and ports
|
|
3. MQTTS listener status (port 8883)
|
|
4. Public key and certificate fingerprints
|
|
5. Client configuration guide (system CA vs fullchain.pem)
|
|
6. Backup file location and checksums
|
|
7. Auto-renewal schedule
|
|
|
|
## Client Configuration Guide
|
|
|
|
### Option 1: System CA (Recommended for PC/Mobile)
|
|
No files needed. The operating system's built-in CA trust store will verify the certificate automatically.
|
|
|
|
**Python Example:**
|
|
```python
|
|
import paho.mqtt.client as mqtt
|
|
import ssl
|
|
|
|
client = mqtt.Client()
|
|
client.username_pw_set("username", "password")
|
|
client.tls_set(cert_reqs=ssl.CERT_REQUIRED, tls_version=ssl.PROTOCOL_TLSv1_2)
|
|
client.connect("${domain}", 8883, 60)
|
|
```
|
|
|
|
**Node.js Example:**
|
|
```javascript
|
|
const mqtt = require('mqtt');
|
|
const client = mqtt.connect('mqtts://${domain}:8883', {
|
|
username: 'username',
|
|
password: 'password',
|
|
rejectUnauthorized: true
|
|
});
|
|
```
|
|
|
|
### Option 2: fullchain.pem (Recommended for Embedded Devices)
|
|
Distribute `fullchain.pem` to clients that cannot access system CA.
|
|
|
|
**Python Example:**
|
|
```python
|
|
client.tls_set(ca_certs="fullchain.pem", cert_reqs=ssl.CERT_REQUIRED)
|
|
client.connect("${domain}", 8883, 60)
|
|
```
|
|
|
|
**ESP32/Arduino Example:**
|
|
```cpp
|
|
#include <WiFiClientSecure.h>
|
|
#include <PubSubClient.h>
|
|
|
|
const char* root_ca = "..."; // Content from fullchain.pem
|
|
|
|
WiFiClientSecure espClient;
|
|
PubSubClient client(espClient);
|
|
|
|
void setup() {
|
|
espClient.setCACert(root_ca);
|
|
client.setServer("${domain}", 8883);
|
|
client.connect("ESP32", "username", "password");
|
|
}
|
|
```
|
|
|
|
## Troubleshooting
|
|
|
|
### DNS Resolution Issues
|
|
- Verify A record points to server IP
|
|
- Wait 5-10 minutes for DNS propagation
|
|
- Test with: `dig ${domain} +short`
|
|
|
|
### Certificate Validation Failed
|
|
- Check system time is synchronized
|
|
- Update system CA certificates
|
|
- Use fullchain.pem instead of system CA
|
|
|
|
### Container Start Failed
|
|
- Check certificate file permissions
|
|
- Verify volume mount paths
|
|
- Review container logs: `docker logs ${emqx_container}`
|
|
|
|
### SSL Connection Refused
|
|
- Verify port 8883 is open in firewall
|
|
- Check EMQX listener status
|
|
- Test with: `openssl s_client -connect ${domain}:8883`
|
|
|
|
## Maintenance
|
|
|
|
### Certificate Renewal
|
|
Auto-renewal is configured via cron (daily at 00:34). Manual renewal:
|
|
```bash
|
|
~/.acme.sh/acme.sh --renew -d ${domain} --force
|
|
```
|
|
|
|
### Check Certificate Expiry
|
|
```bash
|
|
openssl x509 -in ${cert_dir}/${domain}/cert.pem -noout -dates
|
|
```
|
|
|
|
### Update Client Certificates
|
|
When certificate is renewed, container automatically restarts. No client updates needed unless using public key pinning.
|
|
|
|
## Security Notes
|
|
1. **Private Key (key.pem)**: Keep confidential, never share
|
|
2. **Certificate (cert.pem)**: Safe to distribute to clients
|
|
3. **Full Chain (fullchain.pem)**: Safe to distribute to clients
|
|
4. **Public Key (public.pem)**: Safe to distribute for pinning
|
|
5. **Backup Files**: Store in encrypted storage
|
|
6. **Auto-Renewal**: Enabled, checks daily, renews 60 days before expiry
|
|
|
|
## Output Files
|
|
- `${cert_dir}/${domain}/cert.pem`: Server certificate
|
|
- `${cert_dir}/${domain}/key.pem`: Private key
|
|
- `${cert_dir}/${domain}/public.pem`: Public key
|
|
- `${cert_dir}/${domain}/fullchain.pem`: Full certificate chain
|
|
- `${cert_dir}/${domain}/cacert.pem`: CA certificate (symlink)
|
|
- `${cert_dir}/${domain}/fingerprints.txt`: Fingerprints
|
|
- `${cert_dir}/${domain}/README.txt`: Documentation
|
|
- `${cert_dir}/${domain}-complete-*.tar.gz`: Backup package
|
|
- `${cert_dir}/${domain}-checksums.txt`: Package checksums
|
|
- `/root/emqx-backup-*.json`: Container configuration backup
|
|
|
|
## Success Criteria
|
|
- ✅ DNS resolves correctly
|
|
- ✅ Certificate issued successfully
|
|
- ✅ EMQX container running with certificate mount
|
|
- ✅ SSL listener on port 8883 active
|
|
- ✅ SSL handshake succeeds
|
|
- ✅ Auto-renewal configured
|
|
- ✅ Documentation generated
|
|
- ✅ Backup package created
|
|
|
|
## Related Commands
|
|
- Check certificate info: `openssl x509 -in cert.pem -text -noout`
|
|
- Test MQTTS connection: `mosquitto_pub -h ${domain} -p 8883 --cafile fullchain.pem -t test -m hello`
|
|
- View EMQX logs: `docker logs -f ${emqx_container}`
|
|
- List certificates: `~/.acme.sh/acme.sh --list`
|
|
- Force renewal: `~/.acme.sh/acme.sh --renew -d ${domain} --force`
|