svrnty-mcp-server/docs/deployment/https-setup.md
Svrnty 0c27de4162 refactor: rename OpenHarbor.MCP to Svrnty.MCP across all libraries
- Renamed all directories: OpenHarbor.MCP.* → Svrnty.MCP.*
- Updated all namespaces in 179 C# files
- Renamed 20 .csproj files and 3 .sln files
- Updated 193 documentation references
- Updated 33 references in main CODEX codebase
- Updated Codex.sln with new paths
- Build verified: 0 errors

Preparing for extraction to standalone repositories.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-22 21:04:17 -04:00

677 lines
16 KiB
Markdown

# HTTPS/TLS Setup Guide - Svrnty.MCP.Server
**Purpose**: Production-grade HTTPS/TLS configuration for secure MCP server deployment
**Audience**: DevOps engineers, system administrators
**Last Updated**: 2025-10-19
**Version**: 1.0.0
---
## Table of Contents
1. [Overview](#overview)
2. [Prerequisites](#prerequisites)
3. [Development Setup](#development-setup)
4. [Production Setup](#production-setup)
5. [Certificate Management](#certificate-management)
6. [Security Headers (HSTS)](#security-headers-hsts)
7. [Testing HTTPS](#testing-https)
8. [Troubleshooting](#troubleshooting)
---
## Overview
Svrnty.MCP.Server supports HTTPS/TLS through ASP.NET Core's Kestrel web server. This guide covers configuration for both development and production environments.
**Security Benefits:**
- Encrypted communication (prevents eavesdropping)
- Client/server authentication
- Data integrity verification
- Compliance with security best practices
**Default Behavior:**
- HTTP only (for development ease)
- HTTPS must be explicitly configured
- Uses Kestrel's built-in TLS support
---
## Prerequisites
### Development
- .NET 8.0 SDK
- OpenSSL or dotnet dev-certs tool
- PowerShell (Windows) or bash (Linux/macOS)
### Production
- Valid TLS certificate (from CA or Let's Encrypt)
- Private key file
- Certificate chain (intermediate + root CA certs)
- Reverse proxy (optional): Nginx, Traefik, or Kestrel standalone
---
## Development Setup
### Option 1: ASP.NET Core Development Certificate (Recommended)
**Step 1: Generate Development Certificate**
**Windows / macOS / Linux:**
```bash
# Generate and trust dev certificate
dotnet dev-certs https --trust
# Verify installation
dotnet dev-certs https --check
```
**Step 2: Configure appsettings.Development.json**
Create or update `appsettings.Development.json` in the Server project:
```json
{
"Kestrel": {
"Endpoints": {
"Http": {
"Url": "http://localhost:5050"
},
"Https": {
"Url": "https://localhost:5051",
"Certificate": {
"Subject": "CN=localhost",
"Store": "My",
"Location": "CurrentUser",
"AllowInvalid": true
}
}
}
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}
```
**Step 3: Run Server with HTTPS**
```bash
cd Svrnty.MCP.Server/samples/CodexMcpServer
dotnet run --environment Development
# Server will listen on:
# - HTTP: http://localhost:5050
# - HTTPS: https://localhost:5051
```
**Step 4: Test HTTPS Endpoint**
```bash
# Test health endpoint
curl -k https://localhost:5051/health
# Test MCP invoke endpoint
curl -k -X POST https://localhost:5051/mcp/invoke \
-H "Content-Type: application/json" \
-H "X-API-Key: your-api-key" \
-d '{
"jsonrpc": "2.0",
"method": "tools/list",
"id": "1"
}'
```
> **Note**: `-k` flag skips certificate validation (dev only!)
### Option 2: Custom Development Certificate with OpenSSL
**Step 1: Generate Self-Signed Certificate**
```bash
# Create directory for certs
mkdir -p /home/svrnty/codex/Svrnty.MCP.Server/certs
# Generate private key
openssl genrsa -out certs/localhost.key 2048
# Generate certificate signing request (CSR)
openssl req -new -key certs/localhost.key -out certs/localhost.csr \
-subj "/C=CA/ST=Quebec/L=Montreal/O=Svrnty/CN=localhost"
# Generate self-signed certificate (valid for 365 days)
openssl x509 -req -days 365 -in certs/localhost.csr \
-signkey certs/localhost.key -out certs/localhost.crt
# Convert to PFX format (required by Kestrel)
openssl pkcs12 -export -out certs/localhost.pfx \
-inkey certs/localhost.key -in certs/localhost.crt \
-passout pass:YourSecurePassword
```
**Step 2: Configure appsettings.Development.json**
```json
{
"Kestrel": {
"Endpoints": {
"Https": {
"Url": "https://localhost:5051",
"Certificate": {
"Path": "certs/localhost.pfx",
"Password": "YourSecurePassword"
}
}
}
}
}
```
**Security**: Add `certs/` directory to `.gitignore` to avoid committing private keys.
---
## Production Setup
### Option 1: Certificate from File (Recommended for Kubernetes/Docker)
**Step 1: Obtain Production Certificate**
Use one of:
- **Let's Encrypt** (free, automated renewal)
- **Commercial CA** (DigiCert, Sectigo, etc.)
- **Internal PKI** (corporate certificate authority)
**Step 2: Prepare Certificate Files**
Ensure you have:
- `server.crt` - Server certificate
- `server.key` - Private key
- `ca-bundle.crt` - Intermediate + root CA certificates (optional)
**Step 3: Convert to PFX Format**
```bash
# Combine cert + key + chain into PFX
openssl pkcs12 -export -out server.pfx \
-inkey server.key \
-in server.crt \
-certfile ca-bundle.crt \
-passout pass:ProductionSecurePassword
```
**Step 4: Deploy Certificate**
**Docker/Kubernetes:**
```bash
# Create Kubernetes secret
kubectl create secret generic mcp-server-tls \
--from-file=server.pfx=server.pfx \
--from-literal=password=ProductionSecurePassword
# Mount in deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: mcp-server
spec:
template:
spec:
containers:
- name: mcp-server
volumeMounts:
- name: tls-cert
mountPath: /app/certs
readOnly: true
env:
- name: CERTIFICATE_PASSWORD
valueFrom:
secretKeyRef:
name: mcp-server-tls
key: password
volumes:
- name: tls-cert
secret:
secretName: mcp-server-tls
```
**Step 5: Configure appsettings.Production.json**
```json
{
"Kestrel": {
"Endpoints": {
"Https": {
"Url": "https://*:5051",
"Certificate": {
"Path": "/app/certs/server.pfx",
"Password": "" // Read from environment variable
},
"Protocols": "Http1AndHttp2"
}
}
},
"AllowedHosts": "*"
}
```
**Step 6: Set Environment Variable for Password**
```bash
export KESTREL__CERTIFICATES__DEFAULT__PASSWORD="ProductionSecurePassword"
```
Or in `docker-compose.yml`:
```yaml
services:
mcp-server:
environment:
- KESTREL__CERTIFICATES__DEFAULT__PASSWORD=${CERTIFICATE_PASSWORD}
volumes:
- ./certs/server.pfx:/app/certs/server.pfx:ro
```
### Option 2: Certificate from Azure Key Vault
**Step 1: Store Certificate in Key Vault**
```bash
az keyvault certificate import \
--vault-name your-keyvault \
--name mcp-server-cert \
--file server.pfx \
--password ProductionSecurePassword
```
**Step 2: Configure appsettings.Production.json**
```json
{
"Kestrel": {
"Endpoints": {
"Https": {
"Url": "https://*:5051",
"Certificate": {
"KeyVaultUri": "https://your-keyvault.vault.azure.net/",
"CertificateName": "mcp-server-cert"
}
}
}
}
}
```
**Step 3: Grant Managed Identity Access**
```bash
az keyvault set-policy \
--name your-keyvault \
--object-id <managed-identity-object-id> \
--certificate-permissions get
```
### Option 3: Reverse Proxy (Nginx/Traefik) with TLS Termination
**Best for**: Load balancing, multiple servers, centralized certificate management
**Architecture**:
```
Client → HTTPS → Nginx/Traefik (TLS termination) → HTTP → MCP Server (5050)
```
**Nginx Configuration** (`/etc/nginx/sites-available/mcp-server`):
```nginx
server {
listen 443 ssl http2;
server_name mcp.example.com;
ssl_certificate /etc/nginx/ssl/server.crt;
ssl_certificate_key /etc/nginx/ssl/server.key;
# Modern TLS configuration
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256';
ssl_prefer_server_ciphers off;
# HSTS
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
location / {
proxy_pass http://localhost:5050;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
# Redirect HTTP to HTTPS
server {
listen 80;
server_name mcp.example.com;
return 301 https://$server_name$request_uri;
}
```
**Traefik Configuration** (`docker-compose.yml`):
```yaml
services:
traefik:
image: traefik:v2.10
command:
- "--api.insecure=true"
- "--providers.docker=true"
- "--entrypoints.web.address=:80"
- "--entrypoints.websecure.address=:443"
- "--certificatesresolvers.letsencrypt.acme.tlschallenge=true"
- "--certificatesresolvers.letsencrypt.acme.email=admin@example.com"
- "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json"
ports:
- "80:80"
- "443:443"
volumes:
- "/var/run/docker.sock:/var/run/docker.sock:ro"
- "./letsencrypt:/letsencrypt"
mcp-server:
image: openharbor/mcp-server:latest
labels:
- "traefik.enable=true"
- "traefik.http.routers.mcp.rule=Host(`mcp.example.com`)"
- "traefik.http.routers.mcp.entrypoints=websecure"
- "traefik.http.routers.mcp.tls.certresolver=letsencrypt"
- "traefik.http.services.mcp.loadbalancer.server.port=5050"
```
---
## Certificate Management
### Automatic Renewal with Let's Encrypt
**Using Certbot:**
```bash
# Install certbot
sudo apt-get install certbot
# Obtain certificate
sudo certbot certonly --standalone -d mcp.example.com
# Certificates location: /etc/letsencrypt/live/mcp.example.com/
# - fullchain.pem (certificate + chain)
# - privkey.pem (private key)
# Convert to PFX for Kestrel
sudo openssl pkcs12 -export \
-out /etc/letsencrypt/live/mcp.example.com/server.pfx \
-inkey /etc/letsencrypt/live/mcp.example.com/privkey.pem \
-in /etc/letsencrypt/live/mcp.example.com/fullchain.pem \
-passout pass:YourSecurePassword
# Auto-renewal (certbot creates cron job automatically)
sudo certbot renew --dry-run
```
### Certificate Rotation
**Step 1: Update Certificate File**
Replace `server.pfx` with new certificate.
**Step 2: Reload Without Downtime**
```bash
# Docker
docker exec mcp-server kill -HUP 1
# Kubernetes
kubectl rollout restart deployment/mcp-server
# Systemd
sudo systemctl reload mcp-server
```
**Step 3: Verify New Certificate**
```bash
echo | openssl s_client -connect mcp.example.com:443 -servername mcp.example.com 2>/dev/null | openssl x509 -noout -dates
```
---
## Security Headers (HSTS)
**HTTP Strict Transport Security (HSTS)** forces browsers to use HTTPS.
**Method 1: Kestrel Middleware** (Recommended)
Update `Program.cs` in `CodexMcpServer`:
```csharp
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
// Add HSTS middleware (production only)
if (!app.Environment.IsDevelopment())
{
app.UseHsts();
}
// Redirect HTTP to HTTPS
app.UseHttpsRedirection();
// ... rest of configuration
app.Run();
```
**Method 2: Custom Middleware**
```csharp
app.Use(async (context, next) =>
{
if (!context.Request.IsHttps)
{
var httpsUrl = $"https://{context.Request.Host}{context.Request.Path}{context.Request.QueryString}";
context.Response.Redirect(httpsUrl, permanent: true);
return;
}
// Add HSTS header
context.Response.Headers.Add("Strict-Transport-Security", "max-age=31536000; includeSubDomains");
await next();
});
```
**Method 3: Reverse Proxy (Nginx)**
Already shown in [Option 3](#option-3-reverse-proxy-nginxtraefik-with-tls-termination) above.
---
## Testing HTTPS
### 1. Health Check
```bash
curl https://mcp.example.com/health
```
Expected output:
```json
{
"status": "Healthy",
"service": "MCP Server",
"timestamp": "2025-10-19T12:00:00Z"
}
```
### 2. MCP Invoke Endpoint
```bash
curl -X POST https://mcp.example.com/mcp/invoke \
-H "Content-Type: application/json" \
-H "X-API-Key: your-api-key" \
-d '{
"jsonrpc": "2.0",
"method": "tools/list",
"id": "test-1"
}'
```
### 3. TLS Configuration Check
```bash
# Check TLS version and cipher suite
nmap --script ssl-enum-ciphers -p 443 mcp.example.com
# Check certificate validity
echo | openssl s_client -connect mcp.example.com:443 -servername mcp.example.com 2>/dev/null | openssl x509 -noout -text
```
### 4. HSTS Validation
```bash
curl -I https://mcp.example.com/health | grep -i strict-transport-security
```
Expected output:
```
Strict-Transport-Security: max-age=31536000; includeSubDomains
```
### 5. SSL Labs Test
For public-facing servers, use [SSL Labs](https://www.ssllabs.com/ssltest/) to get an A+ rating.
---
## Troubleshooting
### Issue: "Unable to configure HTTPS endpoint"
**Symptom:**
```
Unhandled exception. System.InvalidOperationException: Unable to configure HTTPS endpoint.
```
**Solution:**
1. Verify certificate file exists at specified path
2. Check file permissions (readable by server process)
3. Verify password is correct
4. Check certificate format (must be PFX for Kestrel)
```bash
# Verify PFX file
openssl pkcs12 -info -in server.pfx -noout
```
### Issue: "The certificate chain was issued by an authority that is not trusted"
**Symptom:**
Clients reject the certificate as untrusted.
**Solution:**
1. Ensure CA bundle is included in PFX
2. Add intermediate certificates to certificate store
3. For development, use `dotnet dev-certs https --trust`
### Issue: "Connection reset" or "SSL handshake failed"
**Symptom:**
Client cannot establish TLS connection.
**Solution:**
1. Check firewall allows port 443
2. Verify TLS protocols match (client vs. server)
3. Check cipher suite compatibility
```bash
# Test with specific TLS version
openssl s_client -connect mcp.example.com:443 -tls1_2
```
### Issue: Certificate Expired
**Symptom:**
```
The remote certificate is invalid according to the validation procedure.
```
**Solution:**
1. Check certificate expiry:
```bash
echo | openssl s_client -connect mcp.example.com:443 2>/dev/null | openssl x509 -noout -enddate
```
2. Renew certificate (see [Certificate Management](#certificate-management))
3. Deploy new certificate
### Issue: HSTS Not Working
**Symptom:**
Browser still allows HTTP connections.
**Solution:**
1. Verify HSTS header is sent:
```bash
curl -I https://mcp.example.com/health | grep Strict-Transport-Security
```
2. Clear browser HSTS cache (Chrome: `chrome://net-internals/#hsts`)
3. Ensure middleware is configured correctly
---
## Production Checklist
Before deploying to production, verify:
- [ ] Valid TLS certificate from trusted CA
- [ ] Certificate includes full chain (intermediate + root)
- [ ] Private key is stored securely (Kubernetes secret, Azure Key Vault, etc.)
- [ ] Certificate password is stored in environment variable (not in config)
- [ ] Certificate expiry monitoring is configured
- [ ] Automatic renewal is set up (Let's Encrypt, certbot)
- [ ] HSTS header is enabled
- [ ] HTTP → HTTPS redirect is configured
- [ ] TLS 1.2+ is enforced (no SSLv3, TLS 1.0, TLS 1.1)
- [ ] Strong cipher suites are configured
- [ ] Certificate Common Name (CN) matches domain
- [ ] Firewall allows port 443
- [ ] Health check endpoint is accessible via HTTPS
- [ ] Load balancer (if used) is configured for TLS passthrough or termination
- [ ] Monitoring/alerting is set up for certificate expiry
---
## References
**ASP.NET Core Documentation:**
- [Configure HTTPS in Kestrel](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/servers/kestrel/endpoints)
- [Enforce HTTPS](https://learn.microsoft.com/en-us/aspnet/core/security/enforcing-ssl)
- [HSTS Middleware](https://learn.microsoft.com/en-us/aspnet/core/security/enforcing-ssl#http-strict-transport-security-protocol-hsts)
**Security Best Practices:**
- [Mozilla SSL Configuration Generator](https://ssl-config.mozilla.org/)
- [OWASP TLS Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Transport_Layer_Protection_Cheat_Sheet.html)
- [SSL Labs Best Practices](https://github.com/ssllabs/research/wiki/SSL-and-TLS-Deployment-Best-Practices)
**Certificate Authorities:**
- [Let's Encrypt](https://letsencrypt.org/) - Free automated certificates
- [DigiCert](https://www.digicert.com/) - Commercial CA
- [Sectigo](https://sectigo.com/) - Commercial CA
---
**Document Version**: 1.0.0
**Last Updated**: 2025-10-19
**Maintained By**: Svrnty Development Team
**Related**: [Deployment Guide](deployment-guide.md), [Security Best Practices](../security/security-guide.md)