From d54b7626e743e5413f721a2bb1ad83b33de3d55c Mon Sep 17 00:00:00 2001 From: Alisa Date: Fri, 17 Jul 2026 22:12:06 +0300 Subject: [PATCH] feat(lab11): hardened nginx reverse proxy + TLS 1.3 + security headers --- labs/lab11/reverse-proxy/nginx.conf | 28 ++------- submissions/lab11.md | 93 +++++++++++++++++++++++++++++ 2 files changed, 98 insertions(+), 23 deletions(-) create mode 100644 submissions/lab11.md diff --git a/labs/lab11/reverse-proxy/nginx.conf b/labs/lab11/reverse-proxy/nginx.conf index b90f6c476..257dfd473 100644 --- a/labs/lab11/reverse-proxy/nginx.conf +++ b/labs/lab11/reverse-proxy/nginx.conf @@ -11,7 +11,6 @@ http { server_tokens off; gzip off; - # Security-focused logs log_format security '$remote_addr - $remote_user [$time_local] ' '"$request" $status $body_bytes_sent ' '"$http_referer" "$http_user_agent" ' @@ -20,20 +19,16 @@ http { access_log /var/log/nginx/access.log security; error_log /var/log/nginx/error.log warn; - # Upstream app upstream juice { server juice:3000; keepalive 32; } - # Rate limit zone for login - # ~10 req/min per IP, burst of 5 limit_req_zone $binary_remote_addr zone=login:10m rate=10r/m; limit_req_status 429; map $http_upgrade $connection_upgrade { default upgrade; '' close; } - # Common proxy settings proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; @@ -41,13 +36,11 @@ http { proxy_http_version 1.1; proxy_set_header Connection $connection_upgrade; proxy_set_header Upgrade $http_upgrade; - # Prevent upstream TLS BREACH vector by disabling compression from upstream proxy_set_header Accept-Encoding ""; proxy_read_timeout 30s; proxy_send_timeout 30s; proxy_connect_timeout 5s; proxy_hide_header X-Powered-By; - # Hide upstream headers to avoid duplicates and enforce policy at the proxy proxy_hide_header X-Frame-Options; proxy_hide_header X-Content-Type-Options; proxy_hide_header Referrer-Policy; @@ -58,13 +51,11 @@ http { proxy_hide_header Content-Security-Policy-Report-Only; proxy_hide_header Access-Control-Allow-Origin; - # HTTP server (redirect to HTTPS) server { listen 8080; listen [::]:8080; server_name _; - # Core headers (also on redirects) add_header X-Frame-Options "DENY" always; add_header X-Content-Type-Options "nosniff" always; add_header Referrer-Policy "strict-origin-when-cross-origin" always; @@ -76,7 +67,6 @@ http { return 308 https://$host:8443$request_uri; } - # HTTPS server server { listen 8443 ssl; listen [::]:8443 ssl; @@ -85,18 +75,11 @@ http { ssl_certificate /etc/nginx/certs/localhost.crt; ssl_certificate_key /etc/nginx/certs/localhost.key; - ssl_session_timeout 10m; + ssl_session_timeout 1d; ssl_session_cache shared:SSL:10m; - ssl_protocols TLSv1.2 TLSv1.3; - ssl_ciphers "TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256:EECDH+AESGCM:EDH+AESGCM"; - ssl_prefer_server_ciphers on; - ssl_stapling off; - # If using a publicly-trusted certificate, you may enable OCSP stapling: - # ssl_stapling on; - # ssl_stapling_verify on; - # resolver 1.1.1.1 8.8.8.8 valid=300s; - # resolver_timeout 5s; - # ssl_trusted_certificate /etc/ssl/certs/ca-certificates.crt; + ssl_session_tickets off; + ssl_protocols TLSv1.3; + ssl_prefer_server_ciphers off; client_max_body_size 2m; client_body_timeout 10s; @@ -104,8 +87,7 @@ http { keepalive_timeout 10s; send_timeout 10s; - # Security headers (include HSTS here only) - add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always; + add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always; add_header X-Frame-Options "DENY" always; add_header X-Content-Type-Options "nosniff" always; add_header Referrer-Policy "strict-origin-when-cross-origin" always; diff --git a/submissions/lab11.md b/submissions/lab11.md new file mode 100644 index 000000000..a9b62804e --- /dev/null +++ b/submissions/lab11.md @@ -0,0 +1,93 @@ +# Lab 11 — BONUS — Submission + +## Task 1: TLS + Security Headers + +### nginx.conf (paste the SSL + header sections only — not the whole file) +```nginx +server { + listen 8443 ssl; + http2 on; + ssl_certificate /etc/nginx/certs/localhost.crt; + ssl_certificate_key /etc/nginx/certs/localhost.key; + ssl_session_timeout 1d; + ssl_session_cache shared:SSL:10m; + ssl_session_tickets off; + ssl_protocols TLSv1.3; + ssl_prefer_server_ciphers off; + + add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always; + add_header X-Frame-Options "DENY" always; + add_header X-Content-Type-Options "nosniff" always; + add_header Referrer-Policy "strict-origin-when-cross-origin" always; + add_header Permissions-Policy "camera=(), geolocation=(), microphone=()" always; + add_header Content-Security-Policy-Report-Only "default-src 'self'; img-src 'self' data:; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'" always; +} +``` + +### A. HTTPS redirect proof +``` +HTTP/1.1 308 Permanent Redirect +Location: https://localhost:8443/ +``` + +### B. TLS 1.3 proof +``` +CONNECTED(00000003) +Certificate chain + 0 s:CN = localhost +``` + +### C. Security headers proof (all 6 present) +``` +strict-transport-security: max-age=63072000; includeSubDomains; preload +x-frame-options: DENY +x-content-type-options: nosniff +referrer-policy: strict-origin-when-cross-origin +permissions-policy: camera=(), geolocation=(), microphone=() +cross-origin-opener-policy: same-origin +cross-origin-resource-policy: same-origin +content-security-policy-report-only: default-src 'self'; img-src 'self' data:; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline' +``` + +### What each header defends against (1 sentence each) +- HSTS: informs browsers that the host should only be accessed using HTTPS, and that any future attempts to access it using HTTP should automatically be upgraded to HTTPS +- X-Content-Type-Options: nosniff: prevents XSS-attacks where user-uploaded content is executed as an HTML document, even if the browser has specified that it should be treated as plain text. +- X-Frame-Options: DENY: sites can use this to avoid clickjacking attacks and some cross-site leaks, by ensuring that their content is not embedded into other sites. +- Referrer-Policy: controls how much referrer information (sent with the Referer header) should be included with requests, preventing data leakage via URL. +- Permissions-Policy: disables browser features that could be abused by injected scripts. +- Content-Security-Policy: helps guard against cross-site scripting attacks. + +## Task 2: Production Posture + +### Rate limit proof +| HTTP code | Count out of 60 | +|-----------|----------------:| +| 200 | 0 | +| 429 | 54 | +| 5xx | 6 | + +### Timeout enforced +``` +(Connection closed by nginx — client_header_timeout triggered, no response sent) +``` + +### Cipher hardening +``` +Server Temp Key: X25519, 253 bits +New, TLSv1.3, Cipher is TLS_AES_256_GCM_SHA384 +``` + +### Cert rotation runbook (7 steps) +1. **Detect expiry**: `openssl x509 -enddate -noout -in file.pem`. +2. **Order new cert**: generate new key and CSR, submit to CA. +3. **Validate**: Check new cert with `openssl x509 -text -noout` and verify CN, SAN, expiry, chain +4. **Atomic swap**: Swap cert files on disk, reload nginx gracefully, active connections stay open. +5. **Verify**: Run curl to confirm HTTPS works, then check the new cert's expiry with openssl. +6. **Rollback plan**: Keep old cert and key backup. If issues appeared, restore files and run `nginx -s reload`. +7. **Audit**: Log rotation date, new cert fingerprint, update CMDB/inventory. + +### What OCSP stapling buys you (2-3 sentences, reference Reading 11) +Why is OCSP stapling useful for production but not for a self-signed lab cert? + +The OCSP stapling mechanism allows Nginx to obtain certificate revocation status information from a Certificate Authority (CA) and transmit it to clients directly during the TLS handshake. This enhances privacy and improves connection speed by eliminating unnecessary data exchange. Since contacting a CA is not required for self-signed certificates used in test environments, OCSP is not applicable in such cases - this information is provided solely for preparing the system for a production environment. +