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

16 KiB

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
  2. Prerequisites
  3. Development Setup
  4. Production Setup
  5. Certificate Management
  6. Security Headers (HSTS)
  7. Testing HTTPS
  8. 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

Step 1: Generate Development Certificate

Windows / macOS / Linux:

# 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:

{
  "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

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

# 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

# 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

{
  "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

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

# 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:

# 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

{
  "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

export KESTREL__CERTIFICATES__DEFAULT__PASSWORD="ProductionSecurePassword"

Or in docker-compose.yml:

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

az keyvault certificate import \
  --vault-name your-keyvault \
  --name mcp-server-cert \
  --file server.pfx \
  --password ProductionSecurePassword

Step 2: Configure appsettings.Production.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

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):

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):

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:

# 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

# 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

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:

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

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 above.


Testing HTTPS

1. Health Check

curl https://mcp.example.com/health

Expected output:

{
  "status": "Healthy",
  "service": "MCP Server",
  "timestamp": "2025-10-19T12:00:00Z"
}

2. MCP Invoke Endpoint

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

# 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

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 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)
# 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
# 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:
    echo | openssl s_client -connect mcp.example.com:443 2>/dev/null | openssl x509 -noout -enddate
    
  2. Renew certificate (see Certificate Management)
  3. Deploy new certificate

Issue: HSTS Not Working

Symptom: Browser still allows HTTP connections.

Solution:

  1. Verify HSTS header is sent:
    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:

Security Best Practices:

Certificate Authorities:


Document Version: 1.0.0 Last Updated: 2025-10-19 Maintained By: Svrnty Development Team Related: Deployment Guide, Security Best Practices