- 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>
677 lines
16 KiB
Markdown
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)
|