Skip to content

Security Deployment Checklist

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


Quick Checklist

Use this checklist to verify security before EVERY production deployment.


✅ Authentication & Authorization

  • [ ] Cesivi:Authentication:AcceptAllCredentials is false in appsettings.Production.json
  • [ ] Cesivi:Authentication:AllowAnonymous is false
  • [ ] Cesivi:Authentication:EnableBasic is false (prevents plaintext passwords)
  • [ ] At least one secure authentication method enabled (NTLM or JWT)
  • [ ] Environment variable check will throw exception if AcceptAllCredentials=true in Production
  • [ ] All API controllers have [Authorize] attribute (or documented reason if [AllowAnonymous])

Verification Command:

# Check appsettings.Production.json
cat appsettings.Production.json | grep -A 5 "Authentication"

# Should output:
# "AcceptAllCredentials": false,
# "AllowAnonymous": false,
# "EnableBasic": false


✅ Network Security

  • [ ] HTTPS enabled (Cesivi:UseHttps: true)
  • [ ] Valid TLS certificate configured (not self-signed)
  • [ ] TLS 1.2 or 1.3 minimum (check Kestrel configuration)
  • [ ] HTTP redirects to HTTPS (or HTTP disabled entirely)
  • [ ] Reverse proxy configured (nginx, IIS, Azure Application Gateway, etc.)
  • [ ] CORS policy configured properly (not AllowAnyOrigin())

Verification Command:

# Test HTTPS endpoint
curl -I https://your-domain.com/health

# Should return 200 OK with security headers


✅ Rate Limiting & Request Limits

  • [ ] Rate limiting enabled (IpRateLimiting:EnableEndpointRateLimiting: true)
  • [ ] Production rate limits configured (check appsettings.Production.json)
  • [ ] General rate limit: ≤ 5 requests/second per IP
  • [ ] API rate limit: ≤ 120 requests/minute per IP for /_api/*
  • [ ] POST rate limit: ≤ 60 requests/minute per IP for POST operations
  • [ ] Health endpoints whitelisted (no rate limiting for /health, /ready, /live)
  • [ ] Request size limit configured (Cesivi:RequestLimits:MaxRequestBodySize)
  • [ ] CSOM batch size limit configured (Cesivi:RequestLimits:MaxCsomBatchSize)
  • [ ] File upload size limit appropriate (Cesivi:RequestLimits:MaxFileUploadSize)

Verification Command:

# Test rate limiting (should return 429 after limit exceeded)
for i in {1..20}; do curl -I https://your-domain.com/_api/web; done

# Should see 429 Too Many Requests after configured limit


✅ Secrets & Configuration

  • [ ] FormDigest:SharedSecret changed from default value
  • [ ] SharedSecret is at least 32 characters (recommend 64+)
  • [ ] Redis connection string stored in environment variable (not appsettings.json)
  • [ ] Database connection strings stored in environment variables
  • [ ] TLS certificate password stored in environment variable (if applicable)
  • [ ] .env file added to .gitignore (never commit secrets to git)
  • [ ] No secrets visible in appsettings.Production.json (only placeholders like REPLACE_WITH_...)

Verification Command:

# Check for secrets in configuration file (should return REPLACE_WITH_...)
cat appsettings.Production.json | grep -i "secret\|password\|connectionstring"

# Verify environment variables are set
echo $CESIVI_FORMDIGEST__SHAREDSECRET
echo $CESIVI_DISTRIBUTEDSTATE__CONNECTIONSTRING


✅ Infrastructure

  • [ ] Redis used for distributed state (DistributedState:Provider: "Redis")
  • [ ] Redis connection uses SSL/TLS (ssl=true in connection string)
  • [ ] Redis requires password authentication
  • [ ] Database backups configured and tested (restore tested within last 30 days)
  • [ ] Data encrypted at rest (TDE for SQL Server, encrypted file system, etc.)
  • [ ] Log files stored outside application directory (prevent unauthorized access)
  • [ ] Separate storage for uploaded files (not in web root)

Verification Command:

# Test Redis connection (should require password)
redis-cli -h your-redis-host ping
# Should return: (error) NOAUTH Authentication required.

# Test Redis with password
redis-cli -h your-redis-host -a YOUR_PASSWORD ping
# Should return: PONG


✅ Monitoring & Logging

  • [ ] Centralized logging configured (ELK, Splunk, Azure Monitor, CloudWatch)
  • [ ] Security alerts configured (failed logins, rate limits, errors)
  • [ ] Log level set to Warning or Information in production (not Debug)
  • [ ] Log rotation configured (prevent disk fill)
  • [ ] No secrets logged (check log output for passwords, tokens, API keys)
  • [ ] Structured logging enabled (JSON format for log aggregation)
  • [ ] Health check endpoints exposed and monitored (/health, /ready, /live)
  • [ ] Prometheus metrics exported (/metrics endpoint)
  • [ ] Grafana dashboards configured (if using Prometheus)

Verification Command:

# Check health endpoints
curl https://your-domain.com/health
curl https://your-domain.com/ready
curl https://your-domain.com/live

# All should return 200 OK with health status


✅ Security Headers

  • [ ] HSTS header enabled (verify with browser dev tools or curl)
  • [ ] Content-Security-Policy header present
  • [ ] X-Frame-Options set to SAMEORIGIN
  • [ ] X-Content-Type-Options set to nosniff
  • [ ] X-XSS-Protection set to 1; mode=block
  • [ ] Referrer-Policy set to strict-origin-when-cross-origin
  • [ ] Server header removed (not advertising ASP.NET Core version)
  • [ ] X-Powered-By header removed

Verification Command:

# Check security headers
curl -I https://your-domain.com | grep -i "strict-transport\|content-security\|x-frame\|x-content-type\|referrer-policy"

# Expected output:
# strict-transport-security: max-age=31536000; includeSubDomains; preload
# content-security-policy: default-src 'self'; ...
# x-frame-options: SAMEORIGIN
# x-content-type-options: nosniff
# referrer-policy: strict-origin-when-cross-origin


✅ Container Security (Docker/Kubernetes)

  • [ ] Non-root user configured in Dockerfile (USER cesivi)
  • [ ] Read-only root filesystem where possible
  • [ ] Security context configured (drop all capabilities, add only NET_BIND_SERVICE)
  • [ ] Resource limits configured (CPU: 2, Memory: 2Gi)
  • [ ] Health checks configured (liveness, readiness probes)
  • [ ] Kubernetes secrets used for sensitive data (not ConfigMaps)
  • [ ] Network policies configured (restrict ingress/egress)
  • [ ] Pod Security Standards enforced (Restricted or Baseline)
  • [ ] Vulnerability scanning passed (Trivy, Clair, Snyk)
  • [ ] Image pulled from trusted registry with image signature verification

Verification Commands:

# Docker: Check user in running container
docker exec <container-id> whoami
# Should return: cesivi (not root)

# Kubernetes: Check pod security context
kubectl get pod <pod-name> -o jsonpath='{.spec.securityContext}' --namespace=cesivi

# Scan Docker image for vulnerabilities
trivy image cesivi:1.0


✅ Testing

  • [ ] All unit tests passing (server tests: 293/293)
  • [ ] All integration tests passing (REST/SOAP tests: 383/403 or better)
  • [ ] Security test suite run and passing
  • [ ] Vulnerability scan completed (dotnet list package --vulnerable)
  • [ ] No critical or high vulnerabilities found
  • [ ] Rate limiting tested (verified 429 responses after limit exceeded)
  • [ ] Authentication tested (verified access denied without valid credentials)
  • [ ] Authorization tested (verified users cannot access unauthorized resources)
  • [ ] HTTPS tested (verified TLS certificate valid and accepted by browsers)
  • [ ] Load testing completed (verified server handles expected traffic)

Verification Commands:

# Run tests
dotnet test --verbosity quiet

# Check for vulnerable packages
dotnet list package --vulnerable

# Should output: No vulnerable packages found


✅ Compliance & Documentation

  • [ ] SECURITY.md updated with vulnerability reporting contact
  • [ ] Security best practices documented (SECURITY_BEST_PRACTICES.md)
  • [ ] Deployment documentation updated with production settings
  • [ ] Runbooks created for common security incidents
  • [ ] Incident response plan documented
  • [ ] Data retention policy documented
  • [ ] Privacy policy updated (if handling user data)
  • [ ] Terms of service updated (if applicable)

Environment-Specific Checklists

Development Environment

Allowed (for convenience): - ✅ AcceptAllCredentials: true - ✅ AllowAnonymous: true - ✅ EnableBasic: true - ✅ Self-signed certificates - ✅ InMemory distributed state provider - ✅ Verbose logging (Debug level)

Still Required: - ⚠️ Secrets in environment variables (good habit) - ⚠️ Regular package updates - ⚠️ HTTPS enabled (even with self-signed cert)


Staging Environment

Should mirror production: - ⛔ AcceptAllCredentials: false - ⛔ AllowAnonymous: false - ⛔ EnableBasic: false - ✅ Valid TLS certificate (or staging cert) - ✅ Redis distributed state provider - ✅ Production-level logging (Warning/Information) - ✅ All production security features enabled

Purpose: Test production configuration before deploying to production


Production Environment

All checklist items above MUST be completed

Additional requirements: - ✅ Change management process followed - ✅ Rollback plan documented and tested - ✅ Stakeholders notified of deployment - ✅ Maintenance window scheduled (if needed) - ✅ Monitoring dashboards checked before/after deployment - ✅ Post-deployment smoke tests completed


Common Pitfalls to Avoid

❌ Don't:

  1. Copy development appsettings.json to production (contains insecure defaults)
  2. Commit .env files to git (contains production secrets)
  3. Use default FormDigest secret (easy to guess)
  4. Disable HTTPS "temporarily" (becomes permanent)
  5. Skip testing after configuration changes (catch issues early)
  6. Use InMemory provider in multi-server production (session loss on pod restart)
  7. Ignore vulnerability scan warnings (small issues become big problems)
  8. Forget to rotate secrets (compromised secrets stay compromised)

✅ Do:

  1. Use appsettings.Production.json (hardened defaults)
  2. Store secrets in environment variables or vault (never in git)
  3. Generate strong random secrets (64+ characters)
  4. Enforce HTTPS at reverse proxy level (defense in depth)
  5. Test in staging before production (catch configuration errors)
  6. Use Redis for production (stateless pods, better scalability)
  7. Update packages regularly (monthly security updates)
  8. Implement secret rotation (every 90 days minimum)

Sign-Off

Before deploying to production, confirm:

Deployment Date: ___

Deployed By: ___

Reviewed By: ___

Checklist Completed: - [ ] All items above checked and verified - [ ] Known issues documented (if any) - [ ] Rollback plan tested - [ ] Monitoring confirmed operational

Signature: ___


Post-Deployment Verification

After deploying to production, verify:

  • [ ] Server started successfully (check logs)
  • [ ] Health endpoints responding (/health, /ready, /live)
  • [ ] Authentication working (test with valid and invalid credentials)
  • [ ] Rate limiting working (test by exceeding limits)
  • [ ] Security headers present (check with curl or browser)
  • [ ] Metrics being collected (check Prometheus/Grafana)
  • [ ] Logs being aggregated (check ELK/Splunk/CloudWatch)
  • [ ] No errors in logs (check for startup errors)
  • [ ] Sample API call succeeds (verify functionality)
  • [ ] No secrets in logs (check log output)

Emergency Rollback

If security issues are found after deployment:

  1. Immediately rollback to previous version
  2. Document the issue in incident report
  3. Fix the security issue in development/staging
  4. Re-test thoroughly before redeploying
  5. Post-mortem (why did checklist miss this?)

Additional Resources


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


Changelog

  • 2026-01-18: Initial version (PLAN-148 Phase 2.3)