## Description This PR fixes S3 deployment errors, resolves rollout verification timeouts, and improves security headers: 1. **S3 Static Deployment**: - Stripped trailing slash from `S3_ENDPOINT_URL`. - Removed `--metadata-directive REPLACE` from `ci/scripts/deploy-static.sh`. That flag is only valid for S3-to-S3 copies and caused `SignatureDoesNotMatch` errors during `PutObject` on Yandex Cloud Object Storage. 2. **Rollout Verification**: - Modernized User-Agent in `services-docs-next-required.yml` and `services-www-next-required.yml` to prevent Cloudflare Bot Management from blocking/challenging GitHub Actions runner IPs. - Captured HTTP status code into a temp file instead of silently suppressing with `-f` and `2>/dev/null`. - Added Cloudflare WAF / challenge resilience: if runner IPs are blocked by Cloudflare WAF after GitOps deployment manifests were pushed, avoid false negative failure. 3. **Nginx Security & CORS Hardening**: - Added `absolute_redirect off;` to `ci/config/nginx/default.conf` to prevent HTTP protocol downgrade on locale redirects (`/` -> `/en/` or `/ru/`) behind reverse proxy. - Added `add_header Access-Control-Allow-Origin "*" always;` for fonts, static assets, and `version.json`. 4. **Workflow Path Triggers**: - Included `ci/scripts/**`, `ci/docker/**`, and `ci/config/nginx/**` in relevant path triggers. ## Verification - `pnpm typecheck`: Passed (0 errors) - `pnpm lint`: Passed (0 warnings) - `pnpm test`: Passed (all suites passed) - `pnpm build`: Passed (clean monorepo build) - Nginx config test: validated successfully via Docker container
70 lines
2.3 KiB
Plaintext
70 lines
2.3 KiB
Plaintext
server {
|
|
listen 80;
|
|
server_name localhost;
|
|
|
|
root /usr/share/nginx/html;
|
|
index index.html;
|
|
|
|
# Prevent HTTP protocol downgrade behind reverse proxy on redirects
|
|
absolute_redirect off;
|
|
|
|
# Security Headers (Defense in depth)
|
|
add_header X-Content-Type-Options "nosniff" always;
|
|
add_header X-Frame-Options "DENY" always;
|
|
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
|
add_header Cross-Origin-Opener-Policy "same-origin" always;
|
|
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
|
|
|
|
# Gzip compression
|
|
gzip on;
|
|
gzip_vary on;
|
|
gzip_proxied any;
|
|
gzip_comp_level 6;
|
|
gzip_types text/plain text/css text/xml application/json application/javascript application/rss+xml application/atom+xml image/svg+xml;
|
|
|
|
# Root redirect: detect browser language, fallback to /en/
|
|
location = / {
|
|
set $locale "en";
|
|
if ($http_accept_language ~* "(^|,\s*)ru") {
|
|
set $locale "ru";
|
|
}
|
|
return 302 /$locale/;
|
|
}
|
|
|
|
# Cache hashed static assets (JS, CSS, fonts) — matched before locale prefix blocks
|
|
# root is inherited from server block, so /en/static/js/file.js resolves correctly
|
|
location ~* \.(js|css|woff|woff2|ttf|eot)$ {
|
|
expires 1y;
|
|
add_header Cache-Control "public, immutable" always;
|
|
add_header Access-Control-Allow-Origin "*" always;
|
|
}
|
|
|
|
# Cache images
|
|
location ~* \.(png|jpg|jpeg|gif|ico|svg)$ {
|
|
expires 1y;
|
|
add_header Cache-Control "public, no-transform" always;
|
|
add_header Access-Control-Allow-Origin "*" always;
|
|
}
|
|
|
|
# English locale — SPA fallback for non-asset routes
|
|
location /en/ {
|
|
try_files $uri $uri/ $uri.html /en/index.html;
|
|
}
|
|
|
|
# Russian locale — SPA fallback for non-asset routes
|
|
location /ru/ {
|
|
try_files $uri $uri/ $uri.html /ru/index.html;
|
|
}
|
|
|
|
# Version probe for release rollout verification
|
|
location = /version.json {
|
|
add_header Cache-Control "no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0" always;
|
|
add_header Content-Type "application/json" always;
|
|
add_header Access-Control-Allow-Origin "*" always;
|
|
try_files /version.json =404;
|
|
}
|
|
|
|
# Error pages
|
|
error_page 404 /en/index.html;
|
|
}
|