## New Security Documentation Structure
/security/
├── README.md # Security overview & contacts
├── forensics-checklist.md # Incident investigation guide
├── persistence-scan.sh # Quick persistence detector
├── runtime-detector.sh # Mining/suspicious process detector
└── hardening/
├── docker.md # Docker security baseline
├── kubernetes.md # K8s policies (future reference)
└── cloud.md # Hetzner-specific hardening
## Key Components
### Forensics Checklist
- Process analysis commands
- Persistence mechanism detection
- Network connection analysis
- File system inspection
- Authentication audit
- Decision matrix for threat response
### Scripts
- persistence-scan.sh: Cron, systemd, executables, SSH keys
- runtime-detector.sh: Mining process detection with --kill option
### Hardening Guides
- Docker: Secure compose template, Dockerfile best practices
- Kubernetes: NetworkPolicy, PodSecurityStandard, Falco rules
- Cloud: Egress firewall, SSH hardening, fail2ban, monitoring
## Post-Incident Documentation
Based on lessons learned from Incidents #1 and #2 (Jan 2026)
Co-authored-by: Cursor Agent <agent@cursor.sh>
311 lines
7.4 KiB
Markdown
311 lines
7.4 KiB
Markdown
# 🐳 Docker Security Hardening — DAARION
|
||
|
||
**Версія:** 1.0.0
|
||
**Принцип:** Майнінг можливий тільки там, де дозволений **outbound + CPU без контролю**
|
||
|
||
---
|
||
|
||
## 📋 Security Checklist
|
||
|
||
### Must-Have (Обов'язково)
|
||
|
||
| Налаштування | docker-compose | Пояснення |
|
||
|--------------|----------------|-----------|
|
||
| Read-only filesystem | `read_only: true` | Запобігає запису malware |
|
||
| Drop capabilities | `cap_drop: [ALL]` | Мінімальні привілеї |
|
||
| No new privileges | `security_opt: [no-new-privileges:true]` | Блокує privilege escalation |
|
||
| CPU limit | `cpus: '1.0'` | Обмежує crypto mining |
|
||
| Memory limit | `memory: 512M` | Запобігає DoS |
|
||
| Non-root user | `user: "1001:1001"` | Не root в контейнері |
|
||
| No privileged | `privileged: false` | Завжди! |
|
||
|
||
---
|
||
|
||
## 🛡️ Secure docker-compose Template
|
||
|
||
```yaml
|
||
version: '3.8'
|
||
|
||
services:
|
||
secure-service:
|
||
image: your-image:tag
|
||
container_name: secure-service
|
||
|
||
# ============================================
|
||
# SECURITY HARDENING
|
||
# ============================================
|
||
|
||
# 1. Restart policy (use "no" until verified)
|
||
restart: "no" # Change to "unless-stopped" after verification
|
||
|
||
# 2. Network binding (localhost only for internal services)
|
||
ports:
|
||
- "127.0.0.1:8080:8080"
|
||
|
||
# 3. Read-only root filesystem
|
||
read_only: true
|
||
|
||
# 4. Temporary filesystems (for apps that need write)
|
||
tmpfs:
|
||
- /tmp:size=64M,mode=1777
|
||
- /app/cache:size=128M,mode=1777
|
||
|
||
# 5. Drop ALL capabilities
|
||
cap_drop:
|
||
- ALL
|
||
|
||
# 6. Add only what's needed (rarely needed)
|
||
# cap_add:
|
||
# - NET_BIND_SERVICE # Only if port < 1024
|
||
|
||
# 7. Security options
|
||
security_opt:
|
||
- no-new-privileges:true
|
||
|
||
# 8. Resource limits
|
||
deploy:
|
||
resources:
|
||
limits:
|
||
cpus: '1.0'
|
||
memory: 512M
|
||
reservations:
|
||
cpus: '0.25'
|
||
memory: 128M
|
||
|
||
# 9. Process limits
|
||
pids_limit: 100
|
||
|
||
# 10. Disable privileged mode
|
||
privileged: false
|
||
|
||
# 11. Non-root user
|
||
user: "1001:1001"
|
||
|
||
# 12. Health check
|
||
healthcheck:
|
||
test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
|
||
interval: 30s
|
||
timeout: 10s
|
||
retries: 3
|
||
start_period: 10s
|
||
|
||
# 13. Logging limits
|
||
logging:
|
||
driver: "json-file"
|
||
options:
|
||
max-size: "10m"
|
||
max-file: "3"
|
||
|
||
# 14. Labels for audit
|
||
labels:
|
||
- "security.hardened=true"
|
||
- "security.reviewed=2026-01-09"
|
||
|
||
networks:
|
||
default:
|
||
driver: bridge
|
||
driver_opts:
|
||
com.docker.network.bridge.enable_icc: "false" # Disable inter-container communication
|
||
```
|
||
|
||
---
|
||
|
||
## 🔧 Secure Dockerfile Template
|
||
|
||
```dockerfile
|
||
# ============================================
|
||
# Stage 1: Builder
|
||
# ============================================
|
||
FROM node:20-alpine AS builder
|
||
WORKDIR /app
|
||
|
||
# Copy only package files first (cache optimization)
|
||
COPY package*.json ./
|
||
RUN npm ci --only=production --ignore-scripts
|
||
|
||
COPY . .
|
||
RUN npm run build
|
||
|
||
# ============================================
|
||
# Stage 2: Production (minimal)
|
||
# ============================================
|
||
FROM node:20-alpine AS production
|
||
|
||
# Security: Create non-root user
|
||
RUN addgroup -g 1001 -S appgroup && \
|
||
adduser -u 1001 -S appuser -G appgroup
|
||
|
||
WORKDIR /app
|
||
|
||
# Security: Remove unnecessary tools
|
||
RUN apk del --purge wget curl busybox-extras && \
|
||
rm -rf /var/cache/apk/* /tmp/* /var/tmp/*
|
||
|
||
# Copy from builder
|
||
COPY --from=builder --chown=appuser:appgroup /app/dist ./dist
|
||
COPY --from=builder --chown=appuser:appgroup /app/node_modules ./node_modules
|
||
COPY --from=builder --chown=appuser:appgroup /app/package.json ./
|
||
|
||
# Security: Restrictive permissions
|
||
RUN chmod -R 500 /app && \
|
||
chmod 400 /app/package.json
|
||
|
||
# Switch to non-root
|
||
USER appuser
|
||
|
||
# Health check
|
||
HEALTHCHECK --interval=30s --timeout=10s --start-period=10s --retries=3 \
|
||
CMD node -e "require('http').get('http://localhost:8080/health', (r) => process.exit(r.statusCode === 200 ? 0 : 1))"
|
||
|
||
EXPOSE 8080
|
||
CMD ["node", "dist/server.js"]
|
||
```
|
||
|
||
---
|
||
|
||
## 🚫 Anti-Patterns (НЕ робіть)
|
||
|
||
```yaml
|
||
# ❌ NEVER DO THIS:
|
||
|
||
services:
|
||
insecure:
|
||
privileged: true # ❌ Full host access
|
||
|
||
ports:
|
||
- "8080:8080" # ❌ Binds to 0.0.0.0 (public)
|
||
|
||
volumes:
|
||
- /:/host # ❌ Full host filesystem
|
||
- /var/run/docker.sock:/var/run/docker.sock # ❌ Docker escape
|
||
|
||
cap_add:
|
||
- SYS_ADMIN # ❌ Too powerful
|
||
- NET_ADMIN # ❌ Network manipulation
|
||
|
||
restart: unless-stopped # ❌ Without verification
|
||
|
||
# No resource limits # ❌ Crypto mining possible
|
||
# No read_only # ❌ Malware can persist
|
||
# No user specification # ❌ Runs as root
|
||
```
|
||
|
||
---
|
||
|
||
## 📊 Security Scanning
|
||
|
||
### Pre-deployment scan
|
||
|
||
```bash
|
||
# Install Trivy
|
||
brew install trivy # macOS
|
||
apt install trivy # Ubuntu
|
||
|
||
# Scan image for vulnerabilities
|
||
trivy image --severity HIGH,CRITICAL your-image:tag
|
||
|
||
# Scan with detailed output
|
||
trivy image --format json -o scan-report.json your-image:tag
|
||
|
||
# Scan Dockerfile
|
||
trivy config Dockerfile
|
||
```
|
||
|
||
### CI/CD Integration
|
||
|
||
```yaml
|
||
# .github/workflows/security-scan.yml
|
||
name: Security Scan
|
||
|
||
on: [push, pull_request]
|
||
|
||
jobs:
|
||
scan:
|
||
runs-on: ubuntu-latest
|
||
steps:
|
||
- uses: actions/checkout@v3
|
||
|
||
- name: Build image
|
||
run: docker build -t app:${{ github.sha }} .
|
||
|
||
- name: Run Trivy scan
|
||
uses: aquasecurity/trivy-action@master
|
||
with:
|
||
image-ref: 'app:${{ github.sha }}'
|
||
format: 'table'
|
||
exit-code: '1'
|
||
severity: 'HIGH,CRITICAL'
|
||
```
|
||
|
||
---
|
||
|
||
## 🔍 Runtime Monitoring
|
||
|
||
### Container inspection
|
||
|
||
```bash
|
||
# Check container security settings
|
||
docker inspect --format='
|
||
Privileged: {{.HostConfig.Privileged}}
|
||
ReadonlyRootfs: {{.HostConfig.ReadonlyRootfs}}
|
||
User: {{.Config.User}}
|
||
CapDrop: {{.HostConfig.CapDrop}}
|
||
CapAdd: {{.HostConfig.CapAdd}}
|
||
' container_name
|
||
```
|
||
|
||
### Resource monitoring
|
||
|
||
```bash
|
||
# Real-time stats
|
||
docker stats container_name
|
||
|
||
# CPU limit check
|
||
docker inspect --format='{{.HostConfig.NanoCpus}}' container_name
|
||
|
||
# Memory limit check
|
||
docker inspect --format='{{.HostConfig.Memory}}' container_name
|
||
```
|
||
|
||
---
|
||
|
||
## 📝 DAARION-specific Rules
|
||
|
||
### Services that MUST have hardening:
|
||
|
||
| Service | Priority | Notes |
|
||
|---------|----------|-------|
|
||
| daarion-web | 🔴 Critical | Post-incident, see Dockerfile.secure |
|
||
| dagi-gateway | 🔴 Critical | Public-facing |
|
||
| dagi-router | 🟡 High | Core routing |
|
||
| All others | 🟡 High | Apply baseline |
|
||
|
||
### Network rules:
|
||
|
||
```yaml
|
||
# Internal services: bind to localhost
|
||
ports:
|
||
- "127.0.0.1:PORT:PORT"
|
||
|
||
# Public services: use Nginx reverse proxy
|
||
# Never expose directly to 0.0.0.0
|
||
```
|
||
|
||
---
|
||
|
||
## ✅ Verification Checklist
|
||
|
||
Before deploying any container:
|
||
|
||
- [ ] Image scanned with Trivy (no HIGH/CRITICAL)
|
||
- [ ] `read_only: true` set
|
||
- [ ] `cap_drop: [ALL]` set
|
||
- [ ] `security_opt: [no-new-privileges:true]` set
|
||
- [ ] CPU/memory limits set
|
||
- [ ] Non-root user configured
|
||
- [ ] Health check defined
|
||
- [ ] Restart policy is "no" for new deployments
|
||
- [ ] Port bound to 127.0.0.1 (unless public)
|
||
- [ ] No privileged mode
|
||
- [ ] No dangerous volume mounts
|