feat: Enhance security and validation in backend
- Added helmet for security headers and configured content security policy - Implemented CORS with a whitelist for allowed origins - Introduced express-validator for input validation in API endpoints - Set request size limits to prevent DoS attacks - Added global error handling and 404 response - Updated TypeScript configuration to use node16 module resolution - Improved Docker Compose configuration for security and resource limits - Created a comprehensive .env.example for environment configuration - Implemented automated security scans in CI/CD with Trivy - Added cleanup script for debugging ports - Established a detailed security policy document
This commit is contained in:
79
Dockerfile
79
Dockerfile
@@ -52,13 +52,21 @@ RUN apk add --no-cache \
|
||||
curl \
|
||||
tzdata \
|
||||
tini \
|
||||
su-exec \
|
||||
shadow \
|
||||
&& rm -rf /var/cache/apk/*
|
||||
|
||||
# Arbeitsverzeichnisse erstellen
|
||||
RUN mkdir -p /var/www/html \
|
||||
# Non-root User erstellen (UID/GID 1000 für Kompatibilität)
|
||||
RUN addgroup -g 1000 appuser \
|
||||
&& adduser -D -u 1000 -G appuser appuser \
|
||||
&& mkdir -p /var/www/html \
|
||||
&& mkdir -p /app/backend/dist \
|
||||
&& mkdir -p /etc/supervisor.d \
|
||||
&& mkdir -p /data
|
||||
&& mkdir -p /data \
|
||||
&& mkdir -p /var/log/nginx \
|
||||
&& mkdir -p /var/lib/nginx \
|
||||
&& mkdir -p /var/tmp/nginx \
|
||||
&& mkdir -p /run/nginx
|
||||
|
||||
# Frontend Build von Builder Stage kopieren
|
||||
COPY --from=frontend-builder /app/frontend/build /var/www/html
|
||||
@@ -67,10 +75,10 @@ COPY --from=frontend-builder /app/frontend/build /var/www/html
|
||||
COPY --from=backend-builder /app/backend/dist /app/backend/dist
|
||||
COPY --from=backend-builder /app/backend/node_modules /app/backend/node_modules
|
||||
|
||||
# Nginx Konfiguration erstellen
|
||||
# Nginx Konfiguration erstellen (non-root optimiert)
|
||||
RUN printf 'worker_processes auto;\n\
|
||||
error_log /var/log/nginx/error.log warn;\n\
|
||||
pid /var/run/nginx.pid;\n\
|
||||
pid /run/nginx/nginx.pid;\n\
|
||||
\n\
|
||||
events {\n\
|
||||
worker_connections 1024;\n\
|
||||
@@ -81,6 +89,13 @@ events {\n\
|
||||
http {\n\
|
||||
include /etc/nginx/mime.types;\n\
|
||||
default_type application/octet-stream;\n\
|
||||
\n\
|
||||
# Temp-Pfade für non-root\n\
|
||||
client_body_temp_path /var/tmp/nginx/client_body;\n\
|
||||
proxy_temp_path /var/tmp/nginx/proxy;\n\
|
||||
fastcgi_temp_path /var/tmp/nginx/fastcgi;\n\
|
||||
uwsgi_temp_path /var/tmp/nginx/uwsgi;\n\
|
||||
scgi_temp_path /var/tmp/nginx/scgi;\n\
|
||||
\n\
|
||||
# Logging\n\
|
||||
log_format main '\''$remote_addr - $remote_user [$time_local] "$request" '\''\n\
|
||||
@@ -96,19 +111,24 @@ http {\n\
|
||||
gzip on;\n\
|
||||
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;\n\
|
||||
\n\
|
||||
# Security Headers\n\
|
||||
# Moderne Security Headers\n\
|
||||
add_header X-Frame-Options "SAMEORIGIN" always;\n\
|
||||
add_header X-Content-Type-Options "nosniff" always;\n\
|
||||
add_header X-XSS-Protection "1; mode=block" always;\n\
|
||||
add_header Referrer-Policy "strict-origin-when-cross-origin" always;\n\
|
||||
add_header Content-Security-Policy "default-src '\''self'\''; script-src '\''self'\'' '\''unsafe-inline'\''; style-src '\''self'\'' '\''unsafe-inline'\''; img-src '\''self'\'' data: https:; font-src '\''self'\'' data:; connect-src '\''self'\'' https:; frame-ancestors '\''self'\''; base-uri '\''self'\''; form-action '\''self'\'';" always;\n\
|
||||
add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;\n\
|
||||
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;\n\
|
||||
\n\
|
||||
# Request-Limits gegen DoS\n\
|
||||
client_max_body_size 50M;\n\
|
||||
client_body_timeout 60s;\n\
|
||||
client_header_timeout 60s;\n\
|
||||
client_body_buffer_size 128k;\n\
|
||||
large_client_header_buffers 4 16k;\n\
|
||||
\n\
|
||||
server {\n\
|
||||
listen 80;\n\
|
||||
listen 8080;\n\
|
||||
server_name _;\n\
|
||||
\n\
|
||||
client_max_body_size 50M;\n\
|
||||
client_body_timeout 60s;\n\
|
||||
client_header_timeout 60s;\n\
|
||||
\n\
|
||||
# Health Check Endpoint\n\
|
||||
location /health {\n\
|
||||
@@ -147,16 +167,22 @@ http {\n\
|
||||
proxy_connect_timeout 60s;\n\
|
||||
proxy_send_timeout 60s;\n\
|
||||
proxy_read_timeout 60s;\n\
|
||||
\n\
|
||||
# Buffer-Limits\n\
|
||||
proxy_buffer_size 4k;\n\
|
||||
proxy_buffers 8 4k;\n\
|
||||
proxy_busy_buffers_size 8k;\n\
|
||||
}\n\
|
||||
}\n\
|
||||
}' > /etc/nginx/nginx.conf
|
||||
|
||||
# Supervisor Konfiguration erstellen
|
||||
# Supervisor Konfiguration erstellen (non-root)
|
||||
RUN printf '[supervisord]\n\
|
||||
nodaemon=true\n\
|
||||
loglevel=info\n\
|
||||
logfile=/dev/stdout\n\
|
||||
logfile_maxbytes=0\n\
|
||||
user=appuser\n\
|
||||
\n\
|
||||
[program:nginx]\n\
|
||||
command=/usr/sbin/nginx -g "daemon off;"\n\
|
||||
@@ -167,6 +193,7 @@ stdout_logfile_maxbytes=0\n\
|
||||
stderr_logfile=/dev/stderr\n\
|
||||
stderr_logfile_maxbytes=0\n\
|
||||
priority=10\n\
|
||||
user=appuser\n\
|
||||
\n\
|
||||
[program:backend]\n\
|
||||
command=node /app/backend/dist/index.js\n\
|
||||
@@ -179,12 +206,12 @@ stdout_logfile_maxbytes=0\n\
|
||||
stderr_logfile=/dev/stderr\n\
|
||||
stderr_logfile_maxbytes=0\n\
|
||||
priority=20\n\
|
||||
user=node\n' > /etc/supervisor.d/supervisord.ini
|
||||
user=appuser\n' > /etc/supervisor.d/supervisord.ini
|
||||
|
||||
# Health Check Script erstellen
|
||||
RUN printf '#!/bin/sh\n\
|
||||
# Health check for both nginx and backend\n\
|
||||
nginx_status=$(curl -f -s http://localhost/health > /dev/null && echo "ok" || echo "fail")\n\
|
||||
nginx_status=$(curl -f -s http://localhost:8080/health > /dev/null && echo "ok" || echo "fail")\n\
|
||||
backend_status=$(curl -f -s http://localhost:3001/api/health > /dev/null && echo "ok" || echo "fail")\n\
|
||||
\n\
|
||||
if [ "$nginx_status" = "ok" ] && [ "$backend_status" = "ok" ]; then\n\
|
||||
@@ -194,9 +221,17 @@ else\n\
|
||||
exit 1\n\
|
||||
fi' > /usr/local/bin/healthcheck.sh && chmod +x /usr/local/bin/healthcheck.sh
|
||||
|
||||
# Berechtigungen setzen
|
||||
RUN chown -R node:node /data /app/backend
|
||||
RUN chown nginx:nginx /var/www/html
|
||||
# Berechtigungen setzen für non-root User
|
||||
RUN chown -R appuser:appuser \
|
||||
/data \
|
||||
/app/backend \
|
||||
/var/www/html \
|
||||
/var/log/nginx \
|
||||
/var/lib/nginx \
|
||||
/var/tmp/nginx \
|
||||
/run/nginx \
|
||||
/etc/nginx \
|
||||
/usr/local/bin/healthcheck.sh
|
||||
|
||||
# Health Check konfigurieren
|
||||
HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \
|
||||
@@ -205,6 +240,12 @@ HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \
|
||||
# Arbeitsverzeichnis setzen
|
||||
WORKDIR /app
|
||||
|
||||
# Exponiere Port 8080 (non-root) statt 80
|
||||
EXPOSE 8080
|
||||
|
||||
# Wechsle zu non-root User
|
||||
USER appuser
|
||||
|
||||
# Startup mit Tini für proper signal handling
|
||||
ENTRYPOINT ["/sbin/tini", "--"]
|
||||
CMD ["/usr/bin/supervisord", "-c", "/etc/supervisor.d/supervisord.ini"]
|
||||
@@ -216,7 +257,7 @@ LABEL traefik.enable=true
|
||||
LABEL traefik.http.routers.app.rule="Host(\`your-domain.com\`)"
|
||||
LABEL traefik.http.routers.app.entrypoints=websecure
|
||||
LABEL traefik.http.routers.app.tls.certresolver=letsencrypt
|
||||
LABEL traefik.http.services.app.loadbalancer.server.port=80
|
||||
LABEL traefik.http.services.app.loadbalancer.server.port=8080
|
||||
|
||||
# =============================================================================
|
||||
# Metadata Labels
|
||||
|
||||
Reference in New Issue
Block a user