Skip to content

Security Best Practices Guide

Cesivi Server - Production Deployment Security Version: 1.0 Last Updated: 2026-01-18 Plan: PLAN-148 Phase 2.3


Table of Contents

  1. Authentication Configuration
  2. Network Security
  3. Storage Encryption
  4. Secrets Management
  5. Docker Deployment Security
  6. Kubernetes Deployment Security
  7. Regular Updates
  8. Monitoring & Logging
  9. Security Checklist

Authentication Configuration

Development vs. Production

Setting Development Production
AcceptAllCredentials true false (enforced)
AllowAnonymous true false
EnableBasic true false
EnableNTLM true true
EnableJWT true true

Configuration Example (Production)

{
  "Cesivi": {
    "Authentication": {
      "AcceptAllCredentials": false,
      "AllowAnonymous": false,
      "EnableNTLM": true,
      "EnableJWT": true,
      "EnableBasic": false
    }
  }
}

Why Disable Basic Authentication in Production?

Basic authentication sends username and password in Base64 encoding, which is easily decoded. Even over HTTPS, this creates unnecessary risk.

Use instead: - NTLM: Windows integrated authentication (Active Directory) - JWT: Token-based authentication (modern APIs)

AcceptAllCredentials Protection

Cesivi Server will throw an exception at startup if AcceptAllCredentials is enabled in Production environment:

SECURITY ERROR: AcceptAllCredentials cannot be enabled in Production environment.
Set Cesivi:Authentication:AcceptAllCredentials to false in appsettings.Production.json

This prevents accidental deployment with authentication bypass enabled.


Network Security

HTTPS Configuration

⚠️ CRITICAL: Always use HTTPS in production.

Enable HTTPS in appsettings.json

{
  "Cesivi": {
    "UseHttps": true,
    "HttpPort": 80,
    "HttpsPort": 443
  },
  "Kestrel": {
    "Certificate": {
      "Path": "/path/to/certificate.pfx",
      "Password": "YOUR_CERTIFICATE_PASSWORD",
      "AllowInvalid": false
    }
  }
}

TLS Version Recommendations

Minimum TLS version: TLS 1.2 (prefer TLS 1.3)

Configure in Kestrel:

// Program.cs (requires code modification)
builder.WebHost.ConfigureKestrel(options =>
{
    options.ConfigureHttpsDefaults(httpsOptions =>
    {
        httpsOptions.SslProtocols = System.Security.Authentication.SslProtocols.Tls12 |
                                     System.Security.Authentication.SslProtocols.Tls13;
    });
});

Certificate Management

Development: - Use self-signed certificates (generated with dotnet dev-certs https) - Set AllowInvalid: true in Kestrel configuration

Production: - Use certificates from a trusted Certificate Authority (Let's Encrypt, DigiCert, etc.) - Set AllowInvalid: false - Configure automatic certificate renewal (certbot, ACME protocol)

Reverse Proxy Recommendations

Use a reverse proxy (nginx, IIS, Apache) for additional security:

Benefits: - SSL/TLS termination - Rate limiting (additional layer) - DDoS protection - Load balancing - Request filtering - Security headers (additional layer)

Example nginx configuration:

server {
    listen 443 ssl http2;
    server_name mocksharepoint.example.com;

    ssl_certificate /etc/ssl/certs/mocksharepoint.crt;
    ssl_certificate_key /etc/ssl/private/mocksharepoint.key;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers HIGH:!aNULL:!MD5;

    # Security headers (additional to Cesivi's built-in headers)
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;

    # Rate limiting (additional to Cesivi's built-in rate limiting)
    limit_req_zone $binary_remote_addr zone=api:10m rate=100r/m;
    limit_req zone=api burst=20 nodelay;

    location / {
        proxy_pass http://localhost:5000;
        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;

        # Request size limit (matches Cesivi's limit)
        client_max_body_size 100M;
    }
}

Storage Encryption

Data at Rest

File System Storage: - Use encrypted file systems (BitLocker, LUKS, dm-crypt) - Configure in OS/infrastructure layer - No application-level configuration needed

SQL Server: - Enable Transparent Data Encryption (TDE) - Encrypt connection strings in storage

-- Enable TDE on SQL Server
ALTER DATABASE CesiviDB
SET ENCRYPTION ON;

PostgreSQL: - Enable pgcrypto extension for column-level encryption - Use encrypted file system for data directory

Data in Transit

Internal Communication: - Use HTTPS for all API calls - Use encrypted Redis connections (if using distributed state)

Redis Encryption (Production):

{
  "Cesivi": {
    "DistributedState": {
      "Provider": "Redis",
      "ConnectionString": "redis.example.com:6379,password=STRONG_PASSWORD,ssl=true,abortConnect=false"
    }
  }
}

Note: ssl=true enables TLS for Redis connections


Secrets Management

⛔ NEVER Store Secrets in appsettings.json

Bad Practice:

{
  "Cesivi": {
    "DistributedState": {
      "ConnectionString": "localhost:6379,password=MyPassword123"
    }
  }
}

✅ Use Environment Variables

Good Practice:

# Set environment variables
export CESIVI_DISTRIBUTEDSTATE__CONNECTIONSTRING="redis.example.com:6379,password=STRONG_PASSWORD,ssl=true"
export CESIVI_FORMDIGEST__SHAREDSECRET="YOUR_STRONG_SECRET_AT_LEAST_32_CHARACTERS"

appsettings.Production.json:

{
  "Cesivi": {
    "DistributedState": {
      "ConnectionString": "REPLACE_WITH_REDIS_CONNECTION_STRING"
    },
    "FormDigest": {
      "SharedSecret": "REPLACE_WITH_STRONG_SECRET_AT_LEAST_32_CHARACTERS"
    }
  }
}

Secret Rotation Policy

Rotate secrets regularly:

Secret Rotation Frequency
FormDigest SharedSecret Every 90 days
Database passwords Every 90 days
Redis passwords Every 90 days
TLS certificates Before expiry (30 days warning)

Docker Deployment Security

Secure Dockerfile

Best Practices:

# Use official ASP.NET Core runtime image
FROM mcr.microsoft.com/dotnet/aspnet:10.0

# Create non-root user
RUN addgroup --system --gid 1000 cesivi && \
    adduser --system --uid 1000 --ingroup cesivi cesivi

# Set working directory
WORKDIR /app

# Copy published application
COPY --chown=cesivi:cesivi ./publish .

# Use non-root user
USER cesivi

# Expose port (note: use reverse proxy in production, not direct exposure)
EXPOSE 8080

# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s \
  CMD curl --fail http://localhost:8080/health || exit 1

# Run application
ENTRYPOINT ["dotnet", "Cesivi.dll"]

Docker Compose Security

Production docker-compose.yml:

version: '3.8'

services:
  cesivi:
    image: cesivi:1.0
    restart: unless-stopped
    environment:
      - ASPNETCORE_ENVIRONMENT=Production
      - CESIVI_DISTRIBUTEDSTATE__CONNECTIONSTRING=${REDIS_CONNECTION_STRING}
      - CESIVI_FORMDIGEST__SHAREDSECRET=${FORMDIGEST_SECRET}
    volumes:
      - ./data:/app/MockData:rw
    networks:
      - backend
    deploy:
      resources:
        limits:
          cpus: '2'
          memory: 2G
        reservations:
          cpus: '1'
          memory: 1G
    security_opt:
      - no-new-privileges:true
    cap_drop:
      - ALL
    cap_add:
      - NET_BIND_SERVICE

  redis:
    image: redis:7-alpine
    restart: unless-stopped
    command: redis-server --requirepass ${REDIS_PASSWORD} --appendonly yes
    volumes:
      - redis-data:/data
    networks:
      - backend
    security_opt:
      - no-new-privileges:true

networks:
  backend:
    driver: bridge

volumes:
  redis-data:

Environment file (.env):

# CRITICAL: Never commit .env to git!
REDIS_CONNECTION_STRING=redis:6379,password=STRONG_PASSWORD
REDIS_PASSWORD=STRONG_PASSWORD
FORMDIGEST_SECRET=YOUR_STRONG_SECRET_AT_LEAST_32_CHARACTERS

Kubernetes Deployment Security

Secure Deployment

Best Practices:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: cesivi-server
  namespace: cesivi
spec:
  replicas: 3
  selector:
    matchLabels:
      app: cesivi-server
  template:
    metadata:
      labels:
        app: cesivi-server
    spec:
      # Use service account with minimal permissions
      serviceAccountName: cesivi-server

      # Security context for pod
      securityContext:
        runAsNonRoot: true
        runAsUser: 1000
        runAsGroup: 1000
        fsGroup: 1000
        seccompProfile:
          type: RuntimeDefault

      containers:
      - name: cesivi
        image: cesivi:1.0
        imagePullPolicy: Always

        # Security context for container
        securityContext:
          allowPrivilegeEscalation: false
          readOnlyRootFilesystem: true
          capabilities:
            drop:
            - ALL
            add:
            - NET_BIND_SERVICE

        # Resource limits
        resources:
          requests:
            memory: "1Gi"
            cpu: "500m"
          limits:
            memory: "2Gi"
            cpu: "2"

        # Environment variables from secrets
        env:
        - name: ASPNETCORE_ENVIRONMENT
          value: "Production"
        - name: CESIVI_DISTRIBUTEDSTATE__CONNECTIONSTRING
          valueFrom:
            secretKeyRef:
              name: cesivi-secrets
              key: redis-connection-string
        - name: CESIVI_FORMDIGEST__SHAREDSECRET
          valueFrom:
            secretKeyRef:
              name: cesivi-secrets
              key: formdigest-secret

        # Probes
        livenessProbe:
          httpGet:
            path: /health
            port: 8080
          initialDelaySeconds: 30
          periodSeconds: 10
        readinessProbe:
          httpGet:
            path: /ready
            port: 8080
          initialDelaySeconds: 5
          periodSeconds: 5

        # Volumes for writable directories
        volumeMounts:
        - name: data
          mountPath: /app/MockData
        - name: tmp
          mountPath: /tmp

      volumes:
      - name: data
        persistentVolumeClaim:
          claimName: cesivi-data
      - name: tmp
        emptyDir: {}

Kubernetes Secrets

Never commit secrets to git:

# Create secret from environment variables
kubectl create secret generic cesivi-secrets \
  --from-literal=redis-connection-string='redis:6379,password=STRONG_PASSWORD,ssl=true' \
  --from-literal=formdigest-secret='YOUR_STRONG_SECRET_AT_LEAST_32_CHARACTERS' \
  --namespace=cesivi

# Verify secret (don't output to console in production!)
kubectl describe secret cesivi-secrets --namespace=cesivi

Network Policies

Restrict network access:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: cesivi-server-policy
  namespace: cesivi
spec:
  podSelector:
    matchLabels:
      app: cesivi-server
  policyTypes:
  - Ingress
  - Egress
  ingress:
  - from:
    - namespaceSelector:
        matchLabels:
          name: ingress-nginx
    ports:
    - protocol: TCP
      port: 8080
  egress:
  - to:
    - podSelector:
        matchLabels:
          app: redis
    ports:
    - protocol: TCP
      port: 6379
  - to:
    - namespaceSelector: {}
      podSelector:
        matchLabels:
          k8s-app: kube-dns
    ports:
    - protocol: UDP
      port: 53

Regular Updates

Update Policy

Frequency:

Component Check Frequency Update Frequency
Cesivi Server Weekly Monthly or when security updates available
.NET Runtime Weekly Monthly or when security updates available
Docker Base Images Weekly Monthly or when security updates available
NuGet Packages Weekly Monthly or when security updates available
OS Patches Daily Weekly (non-breaking), immediately (critical security)

Vulnerability Scanning

Run regularly:

# Check for vulnerable NuGet packages
dotnet list package --vulnerable

# Update NuGet packages
dotnet restore

# Docker image scanning (example with Trivy)
trivy image cesivi:1.0

# Kubernetes scanning (example with kubesec)
kubesec scan deployment.yaml

Monitoring & Logging

Security Logging

What to log:

✅ Authentication attempts (success and failure) ✅ Authorization failures (denied access) ✅ Rate limit violations (429 responses) ✅ Failed input validation ✅ Admin actions (configuration changes, user management) ✅ Suspicious activity patterns

What NOT to log:

⛔ Passwords or credentials ⛔ Authentication tokens ⛔ Personally Identifiable Information (PII) ⛔ API keys or secrets

Log Aggregation

Recommended:

  • ELK Stack (Elasticsearch, Logstash, Kibana)
  • Splunk
  • Azure Monitor
  • AWS CloudWatch

Security Alerts

Configure alerts for:

  • 10+ failed login attempts in 1 minute (potential brute-force)
  • 100+ rate limit violations in 5 minutes (potential DoS)
  • Unauthorized access attempts (authorization failures)
  • Unexpected error spikes (potential attack)

Security Checklist

Use this checklist before every production deployment:

Authentication & Authorization

  • [ ] AcceptAllCredentials set to false
  • [ ] AllowAnonymous set to false
  • [ ] Basic authentication disabled (EnableBasic: false)
  • [ ] Strong authentication method enabled (NTLM or JWT)
  • [ ] [Authorize] attributes present on all API controllers

Network Security

  • [ ] HTTPS enabled with valid TLS certificate
  • [ ] TLS 1.2 or 1.3 minimum enforced
  • [ ] HTTP redirects to HTTPS (or HTTP disabled)
  • [ ] Reverse proxy configured (nginx, IIS, or cloud load balancer)
  • [ ] CORS policy configured properly (not AllowAll)

Rate Limiting & Request Limits

  • [ ] Rate limiting enabled (IpRateLimiting:EnableEndpointRateLimiting: true)
  • [ ] Production rate limits configured (stricter than development)
  • [ ] Request size limits configured (MaxRequestBodySize, MaxCsomBatchSize)
  • [ ] File upload size limit appropriate (MaxFileUploadSize)

Secrets & Configuration

  • [ ] Secrets stored in environment variables (not appsettings.json)
  • [ ] FormDigest:SharedSecret changed from default
  • [ ] Redis connection string not in appsettings.json
  • [ ] Database connection strings not in appsettings.json
  • [ ] .env file added to .gitignore

Infrastructure

  • [ ] Redis used for distributed state (not InMemory)
  • [ ] Database backups configured and tested
  • [ ] Data encrypted at rest (TDE, encrypted file system)
  • [ ] Data encrypted in transit (HTTPS, Redis SSL)
  • [ ] Kubernetes secrets used (not ConfigMaps) for sensitive data

Monitoring & Logging

  • [ ] Centralized logging configured (ELK, Splunk, CloudWatch)
  • [ ] Security alerts configured (failed logins, rate limits, errors)
  • [ ] Log rotation configured (prevent disk fill)
  • [ ] No secrets logged (passwords, tokens, API keys)
  • [ ] Health check endpoints exposed (/health, /ready, /live)

Security Headers

  • [ ] HSTS header enabled (production + HTTPS)
  • [ ] Content Security Policy configured
  • [ ] X-Frame-Options set to SAMEORIGIN
  • [ ] X-Content-Type-Options set to nosniff
  • [ ] Server header removed

Container Security (if using Docker/K8s)

  • [ ] Non-root user configured in container
  • [ ] Read-only root filesystem (where possible)
  • [ ] Security context configured (drop all capabilities, add only needed)
  • [ ] Resource limits configured (CPU, memory)
  • [ ] Vulnerability scanning passed (Trivy, Clair, Snyk)
  • [ ] Network policies configured (Kubernetes)

Testing

  • [ ] Security test suite run and passing
  • [ ] Penetration testing completed (if applicable)
  • [ ] Vulnerability scan completed (dotnet list package --vulnerable)
  • [ ] Rate limiting tested (verify 429 responses)
  • [ ] Authentication/authorization tested (verify access control)

Additional Resources


Last updated: 2026-01-18 Version: 1.0 Plan: PLAN-148 Phase 2.3