feat: initial OpenCode configuration with custom commands and skills
This commit is contained in:
350
skill/mqtts-developer/setup-mqtts-acme.md
Normal file
350
skill/mqtts-developer/setup-mqtts-acme.md
Normal file
@@ -0,0 +1,350 @@
|
||||
# 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`
|
||||
Reference in New Issue
Block a user