Security Best Practices
Overview
Security is built into every layer of the DineTogether infrastructure. This guide covers security best practices and configurations.
Container Security
Run as Non-Root User
# Dockerfile
FROM node:18-alpine
# Create non-root user
RUN addgroup -g 1001 -S nodejs
RUN adduser -S nodejs -u 1001
# Change ownership
WORKDIR /app
COPY --chown=nodejs:nodejs . .
# Switch to non-root user
USER nodejs
EXPOSE 3000
CMD ["node", "server.js"]
Security Context in Kubernetes
# In docker-compose.yml labels or K8s manifest
securityContext:
runAsNonRoot: true
runAsUser: 1001
runAsGroup: 1001
fsGroup: 1001
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop:
- ALL
Secrets Management
Never Commit Secrets
Use GitHub Secrets
Kubernetes Secrets
# Create secrets
kubectl create secret generic app-secrets \
--from-literal=database-url='postgresql://...' \
--from-literal=api-key='...' \
--namespace=test-staging
# Use in deployment
envFrom:
- secretRef:
name: app-secrets
Network Security
Internal vs External Services
# Public service (with ingress network)
services:
frontend:
networks:
- default
- ingress # Public access
# Internal service (no ingress)
database:
networks:
- default # Internal only
HTTPS Only
# Automatic HTTPS redirect
metadata:
annotations:
traefik.ingress.kubernetes.io/router.entrypoints: websecure
traefik.ingress.kubernetes.io/router.tls: "true"
Image Security
Use Specific Tags
Scan Images
# In GitHub Actions
- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@master
with:
image-ref: ghcr.io/${{ github.repository }}:${{ github.sha }}
format: 'sarif'
output: 'trivy-results.sarif'
Private Registry
- All images stored in private GHCR
- Authentication required to pull
- Automatic cleanup of old images
Access Control
GitHub Repository
# Protect main branch
gh api repos/{owner}/{repo}/branches/main/protection \
--method PUT \
--field enforce_admins=true \
--field required_status_checks='{
"strict": true,
"contexts": ["build", "test"]
}'
Kubernetes RBAC
# ServiceAccount with limited permissions
apiVersion: v1
kind: ServiceAccount
metadata:
name: app-service-account
namespace: test-staging
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: app-role
namespace: test-staging
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list"]
Application Security
Environment Variables
// Validate required environment variables
const required = ['DATABASE_URL', 'JWT_SECRET', 'API_KEY'];
for (const varName of required) {
if (!process.env[varName]) {
throw new Error(`Missing required environment variable: ${varName}`);
}
}
Input Validation
// Always validate and sanitize input
app.use(express.json({ limit: '10mb' }));
app.use(helmet());
app.use(cors({
origin: process.env.ALLOWED_ORIGINS?.split(',') || false
}));
Rate Limiting
# Traefik middleware
apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
name: rate-limit
spec:
rateLimit:
average: 100
period: 1m
burst: 50
SSL/TLS Configuration
Strong Ciphers Only
# Traefik TLS options
apiVersion: traefik.containo.us/v1alpha1
kind: TLSOption
metadata:
name: default
spec:
minVersion: VersionTLS12
cipherSuites:
- TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
- TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
- TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305
Security Headers
# Traefik middleware
apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
name: security-headers
spec:
headers:
frameDeny: true
contentTypeNosniff: true
browserXssFilter: true
stsSeconds: 31536000
stsIncludeSubdomains: true
stsPreload: true
customFrameOptionsValue: "SAMEORIGIN"
customResponseHeaders:
X-Content-Type-Options: "nosniff"
X-Frame-Options: "DENY"
X-XSS-Protection: "1; mode=block"
Referrer-Policy: "strict-origin-when-cross-origin"
Permissions-Policy: "geolocation=(), microphone=(), camera=()"
Monitoring & Auditing
Log Security Events
// Log authentication attempts
app.post('/login', (req, res) => {
const { email } = req.body;
console.log(JSON.stringify({
event: 'login_attempt',
email: email,
ip: req.ip,
timestamp: new Date().toISOString()
}));
});
Monitor Suspicious Activity
# Check for failed auth attempts
kubectl logs deployment/api -n test-staging | grep "login_failed" | tail -20
# Monitor pod exec attempts
kubectl get events --all-namespaces | grep "exec"
Database Security
Connection Security
Separate Users
-- Create read-only user for analytics
CREATE USER analytics_user WITH PASSWORD 'secure_password';
GRANT SELECT ON ALL TABLES IN SCHEMA public TO analytics_user;
-- Create app user with limited permissions
CREATE USER app_user WITH PASSWORD 'secure_password';
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO app_user;
CI/CD Security
Minimal Token Permissions
Secure Workflows
# Pin action versions
uses: actions/checkout@v4.1.0 # Not @v4
# Verify actions
uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v4.1.0
Incident Response
Quick Actions
# Isolate compromised pod
kubectl delete pod <pod-name> -n test-staging
# Revoke access
kubectl delete secret ghcr-secret -n test-staging
# Rotate secrets
gh secret set DEPLOY_TOKEN --repo dine-together/myapp
Investigation
# Check recent changes
kubectl get events -n test-staging --sort-by='.lastTimestamp'
# Audit logs
kubectl logs deployment/api -n test-staging --since=1h | grep -E "(error|unauthorized|forbidden)"
# Check modified files
kubectl exec <pod-name> -n test-staging -- find / -mtime -1 -type f 2>/dev/null
Security Checklist
Development
- [ ] No hardcoded secrets
- [ ] Dependencies up to date
- [ ] Input validation implemented
- [ ] Error messages don't leak info
- [ ] Logging doesn't include sensitive data
Docker
- [ ] Using specific base image tags
- [ ] Running as non-root user
- [ ] Minimal image (alpine preferred)
- [ ] No unnecessary packages
- [ ] Multi-stage builds
Kubernetes
- [ ] Security context configured
- [ ] Resource limits set
- [ ] Network policies defined
- [ ] Secrets properly managed
- [ ] RBAC configured
Application
- [ ] HTTPS only
- [ ] Security headers configured
- [ ] Rate limiting enabled
- [ ] CORS properly configured
- [ ] Authentication required
Common Vulnerabilities
Exposed Secrets
Wrong:
Right:
SQL Injection
Wrong:
Right:
const query = 'SELECT * FROM users WHERE email = $1';
const result = await db.query(query, [email]);
Path Traversal
Wrong:
Right:
app.get('/file/:name', (req, res) => {
const safeName = path.basename(req.params.name);
const filePath = path.join(UPLOAD_DIR, safeName);
res.sendFile(filePath);
});
Compliance
Data Protection
- Encryption at Rest
- Database encryption
- Volume encryption
-
Backup encryption
-
Encryption in Transit
- TLS 1.2+ only
- Certificate validation
-
Secure protocols
-
Access Control
- Principle of least privilege
- Regular access reviews
- Audit logging
Next Steps
- Security Scanning
- Set up vulnerability scanning
- Implement SAST/DAST
-
Regular penetration testing
-
Monitoring
- Security event monitoring
- Anomaly detection
-
Alert configuration
-
Training
- Security best practices
- Incident response procedures
- Regular security updates