# DevSecOps & Security Best Practices ## Security Principles ### Defense in Depth - **Multiple layers of security controls** - Network security, application security, data security - No single point of failure ### Least Privilege - **Minimum permissions necessary** - Regular access reviews - Time-bound elevated access ### Zero Trust - **Never trust, always verify** - Verify every request regardless of origin - Micro-segmentation and strict access controls ## Secrets Management ### ❌ Never Do This ```yaml # NEVER hardcode secrets! apiVersion: v1 kind: Pod metadata: name: bad-example spec: containers: - name: app env: - name: DATABASE_PASSWORD value: "SuperSecret123!" # 🚨 NEVER DO THIS - name: API_KEY value: "sk-abc123def456" # 🚨 NEVER DO THIS ``` ### ✅ Use External Secrets Operator ```yaml # External Secrets with AWS Secrets Manager apiVersion: external-secrets.io/v1beta1 kind: ExternalSecret metadata: name: app-secrets namespace: production spec: refreshInterval: 1h secretStoreRef: name: aws-secrets-manager kind: SecretStore target: name: app-secrets creationPolicy: Owner data: - secretKey: database-password remoteRef: key: prod/myapp/database-password - secretKey: api-key remoteRef: key: prod/myapp/api-key ``` ### ✅ Use Sealed Secrets ```bash # Install kubeseal brew install kubeseal # Create sealed secret kubectl create secret generic myapp-secrets \ --from-literal=db-password='secret123' \ --dry-run=client -o yaml | \ kubeseal -o yaml > sealed-secret.yaml # Apply sealed secret (safe to commit to Git) kubectl apply -f sealed-secret.yaml ``` ### AWS Secrets Manager with IRSA ```yaml apiVersion: v1 kind: ServiceAccount metadata: name: myapp namespace: production annotations: eks.amazonaws.com/role-arn: arn:aws:iam::123456789012:role/myapp-secrets-role --- # Application retrieves secrets using AWS SDK # No credentials in code or config! ``` ## Container Security ### Secure Dockerfile ```dockerfile # Use specific version tags, not 'latest' FROM node:20.10.0-alpine3.19 AS builder # Run as non-root user RUN addgroup -g 1000 nodejs && \ adduser -u 1000 -G nodejs -s /bin/sh -D nodejs WORKDIR /app # Copy and install dependencies COPY --chown=nodejs:nodejs package*.json ./ RUN npm ci --only=production && \ npm cache clean --force # Copy application code COPY --chown=nodejs:nodejs . . # Build application RUN npm run build # Production stage FROM node:20.10.0-alpine3.19 # Install dumb-init for signal handling RUN apk add --no-cache dumb-init # Create non-root user RUN addgroup -g 1000 nodejs && \ adduser -u 1000 -G nodejs -s /bin/sh -D nodejs WORKDIR /app # Copy from builder COPY --from=builder --chown=nodejs:nodejs /app/dist ./dist COPY --from=builder --chown=nodejs:nodejs /app/node_modules ./node_modules COPY --from=builder --chown=nodejs:nodejs /app/package*.json ./ # Switch to non-root user USER nodejs # Use dumb-init ENTRYPOINT ["dumb-init", "--"] # Run application CMD ["node", "dist/index.js"] # Expose port EXPOSE 8080 # Add labels LABEL org.opencontainers.image.source="https://github.com/myorg/myapp" \ org.opencontainers.image.version="1.0.0" \ org.opencontainers.image.vendor="MyOrg" ``` ### Security Scanning #### Trivy (Container Vulnerability Scanner) ```bash # Scan Docker image trivy image myapp:latest # Scan with severity filter trivy image --severity HIGH,CRITICAL myapp:latest # Scan filesystem trivy fs --security-checks vuln,config /path/to/project # Output to JSON trivy image -f json -o results.json myapp:latest ``` #### Grype (Vulnerability Scanner) ```bash # Scan image grype myapp:latest # Scan with specific severity grype myapp:latest --fail-on high # Output SARIF for GitHub grype myapp:latest -o sarif > results.sarif ``` ### Pod Security Standards ```yaml # Pod Security Admission apiVersion: v1 kind: Namespace metadata: name: production labels: pod-security.kubernetes.io/enforce: restricted pod-security.kubernetes.io/audit: restricted pod-security.kubernetes.io/warn: restricted --- # Secure Pod Configuration apiVersion: v1 kind: Pod metadata: name: secure-pod namespace: production spec: securityContext: runAsNonRoot: true runAsUser: 1000 fsGroup: 2000 seccompProfile: type: RuntimeDefault containers: - name: app image: myapp:1.0.0 securityContext: allowPrivilegeEscalation: false readOnlyRootFilesystem: true runAsNonRoot: true runAsUser: 1000 capabilities: drop: - ALL add: - NET_BIND_SERVICE # Only if needed resources: limits: memory: "512Mi" cpu: "500m" requests: memory: "256Mi" cpu: "250m" volumeMounts: - name: tmp mountPath: /tmp - name: cache mountPath: /var/cache volumes: - name: tmp emptyDir: {} - name: cache emptyDir: {} ``` ## Network Security ### Network Policies ```yaml # Default deny all ingress apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: default-deny-ingress namespace: production spec: podSelector: {} policyTypes: - Ingress --- # Allow specific ingress apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: app-allow-ingress namespace: production spec: podSelector: matchLabels: app: myapp policyTypes: - Ingress - Egress ingress: # Allow from ingress controller - from: - namespaceSelector: matchLabels: name: ingress-nginx ports: - protocol: TCP port: 8080 egress: # Allow DNS - to: - namespaceSelector: matchLabels: name: kube-system - podSelector: matchLabels: k8s-app: kube-dns ports: - protocol: UDP port: 53 # Allow database - to: - podSelector: matchLabels: app: postgres ports: - protocol: TCP port: 5432 # Allow HTTPS egress - to: - namespaceSelector: {} ports: - protocol: TCP port: 443 ``` ### AWS Security Groups (Terraform) ```hcl # Application Security Group resource "aws_security_group" "app" { name = "${var.name_prefix}-app-sg" description = "Security group for application" vpc_id = var.vpc_id # Allow inbound from ALB only ingress { from_port = 8080 to_port = 8080 protocol = "tcp" security_groups = [aws_security_group.alb.id] description = "HTTP from ALB" } # Allow outbound to database egress { from_port = 5432 to_port = 5432 protocol = "tcp" security_groups = [aws_security_group.database.id] description = "PostgreSQL to database" } # Allow HTTPS egress egress { from_port = 443 to_port = 443 protocol = "tcp" cidr_blocks = ["0.0.0.0/0"] description = "HTTPS to internet" } tags = var.tags } ``` ## IAM & RBAC ### Kubernetes RBAC ```yaml # Least privilege service account apiVersion: v1 kind: ServiceAccount metadata: name: myapp namespace: production --- # Role with minimal permissions apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: name: myapp-role namespace: production rules: # Only read ConfigMaps and Secrets - apiGroups: [""] resources: ["configmaps", "secrets"] verbs: ["get", "list"] resourceNames: ["myapp-config", "myapp-secrets"] # Read own pod information - apiGroups: [""] resources: ["pods"] verbs: ["get"] --- # Bind role to service account apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: name: myapp-rolebinding namespace: production subjects: - kind: ServiceAccount name: myapp namespace: production roleRef: kind: Role name: myapp-role apiGroup: rbac.authorization.k8s.io ``` ### AWS IAM Policy (Terraform) ```hcl # Least privilege IAM policy data "aws_iam_policy_document" "app" { # Allow reading specific secrets statement { actions = [ "secretsmanager:GetSecretValue", "secretsmanager:DescribeSecret", ] resources = [ "arn:aws:secretsmanager:${var.region}:${data.aws_caller_identity.current.account_id}:secret:prod/myapp/*" ] } # Allow writing to specific S3 bucket statement { actions = [ "s3:PutObject", "s3:GetObject", ] resources = [ "${aws_s3_bucket.app.arn}/*" ] } # Allow publishing to specific SNS topic statement { actions = [ "sns:Publish", ] resources = [ aws_sns_topic.app_notifications.arn ] } } resource "aws_iam_policy" "app" { name = "${var.name_prefix}-app-policy" policy = data.aws_iam_policy_document.app.json } ``` ## Compliance & Policy as Code ### OPA (Open Policy Agent) ```rego # Deny pods running as root package kubernetes.admission deny[msg] { input.request.kind.kind == "Pod" input.request.object.spec.securityContext.runAsNonRoot != true msg := "Pods must not run as root" } # Require resource limits deny[msg] { input.request.kind.kind == "Pod" container := input.request.object.spec.containers[_] not container.resources.limits msg := sprintf("Container %v must specify resource limits", [container.name]) } # Deny latest tag deny[msg] { input.request.kind.kind == "Pod" container := input.request.object.spec.containers[_] endswith(container.image, ":latest") msg := sprintf("Container %v uses 'latest' tag", [container.name]) } # Require specific labels deny[msg] { input.request.kind.kind == "Pod" required_labels := ["app", "version", "environment"] label := required_labels[_] not input.request.object.metadata.labels[label] msg := sprintf("Missing required label: %v", [label]) } ``` ### Terraform Sentinel Policy ```hcl # sentinel.hcl policy "require-tags" { enforcement_level = "hard-mandatory" } policy "restrict-instance-type" { enforcement_level = "soft-mandatory" } # require-tags.sentinel import "tfplan/v2" as tfplan required_tags = ["Environment", "Owner", "CostCenter"] main = rule { all tfplan.resource_changes as _, rc { rc.mode is "managed" and rc.type in ["aws_instance", "aws_db_instance", "aws_s3_bucket"] and rc.change.actions contains "create" implies all required_tags as tag { rc.change.after.tags contains tag } } } ``` ## Security Scanning in CI/CD ### GitHub Actions Security Workflow ```yaml name: Security Scan on: [push, pull_request] jobs: secret-scan: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: fetch-depth: 0 - name: TruffleHog Secret Scan uses: trufflesecurity/trufflehog@main with: path: ./ base: ${{ github.event.repository.default_branch }} head: HEAD container-scan: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Build image run: docker build -t myapp:${{ github.sha }} . - name: Run Trivy scan uses: aquasecurity/trivy-action@master with: image-ref: myapp:${{ github.sha }} format: 'sarif' output: 'trivy-results.sarif' severity: 'CRITICAL,HIGH' - name: Upload to GitHub Security uses: github/codeql-action/upload-sarif@v2 with: sarif_file: 'trivy-results.sarif' terraform-scan: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Run Checkov uses: bridgecrewio/checkov-action@master with: directory: terraform/ framework: terraform soft_fail: false sast-scan: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Run Semgrep uses: returntocorp/semgrep-action@v1 with: config: auto ``` ## Encryption ### At Rest ```hcl # S3 bucket encryption resource "aws_s3_bucket_server_side_encryption_configuration" "app" { bucket = aws_s3_bucket.app.id rule { apply_server_side_encryption_by_default { sse_algorithm = "aws:kms" kms_master_key_id = aws_kms_key.s3.arn } bucket_key_enabled = true } } # RDS encryption resource "aws_db_instance" "app" { storage_encrypted = true kms_key_id = aws_kms_key.rds.arn # ... other configuration } # EBS encryption resource "aws_ebs_volume" "app" { encrypted = true kms_key_id = aws_kms_key.ebs.arn # ... other configuration } ``` ### In Transit ```yaml # Enforce TLS in Ingress apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: app annotations: nginx.ingress.kubernetes.io/ssl-redirect: "true" nginx.ingress.kubernetes.io/force-ssl-redirect: "true" nginx.ingress.kubernetes.io/backend-protocol: "HTTPS" spec: tls: - hosts: - app.example.com secretName: app-tls ``` ## Security Best Practices Checklist - [ ] **Never hardcode secrets** - Use secret managers - [ ] **Run containers as non-root** - Set securityContext - [ ] **Use specific image tags** - Never use 'latest' - [ ] **Scan images for vulnerabilities** - Use Trivy/Grype - [ ] **Enable Pod Security Standards** - Use restricted profile - [ ] **Implement network policies** - Default deny, allow specific - [ ] **Use RBAC** - Least privilege access - [ ] **Encrypt data at rest** - KMS for all data stores - [ ] **Enforce TLS** - All traffic encrypted in transit - [ ] **Scan IaC for issues** - Checkov, tfsec in CI/CD - [ ] **Rotate credentials regularly** - Automate rotation - [ ] **Audit and log everything** - CloudTrail, audit logs - [ ] **Implement policy as code** - OPA, Sentinel - [ ] **Regular security reviews** - Penetration testing, audits - [ ] **Keep dependencies updated** - Renovate, Dependabot --- ## Security Incident Response 1. **Detect**: Automated alerting on security events 2. **Contain**: Isolate affected systems 3. **Eradicate**: Remove threat and vulnerabilities 4. **Recover**: Restore services securely 5. **Learn**: Post-incident review and improvements