Applying DISA STIG Controls to Containers
DISA Security Technical Implementation Guides (STIGs) are the most stringent security hardening standards, required for Department of Defense systems and military contractors. CleanStart supports STIG compliance through additional hardening layers beyond CIS.
STIG vs CIS
Aspect | CIS | STIG |
|---|---|---|
Source | Industry (CIS) | Military (DISA) |
Stringency | Balanced | Maximum |
Controls | 94 | 150+ |
Override | Some flexibility | Minimal |
Environment | General IT | DoD/Military only |
Compliance Required | Industry | DoD Contracts |
CleanStart implements both; STIG is a superset.
STIG Container Controls
GEN000080: Configurable Accounts
# STIG: Lock system accounts, use application accountFROM ubuntu:24.04 # Create application userRUN useradd -m -u 1000 -d /app appuser && \ usermod -L appuser # Lock account (no direct login) # All processes run as appuserUSER appuserGEN001080: Account Lockout Policy
STIG requires implementing account lockout policies at the host level. These policies enforce the use of PAM (Pluggable Authentication Modules) to lock accounts after a specified number of failed login attempts. The pam_tally2.so module is the mechanism for enforcement, which locks an account for a 15-minute duration after 5 failed authentication attempts. This policy applies to all containers since they share the host kernel and authentication system.
GEN002870: Remove Unnecessary Packages
STIG mandates minimal packages in container images. Lightweight base images like Alpine at approximately 5MB should be used instead of full distributions like Ubuntu at approximately 70MB. The number of installed packages should remain minimal—ideally less than 20 packages. Unnecessary packages like apk-tools and ca-certificates should be removed if they are not required for application functionality.
GEN006100: File Permissions
STIG requires strict file permissions on all files in the container. The owner of application files should be the application user rather than root. Directories should have 700 permissions, which grants rwx access to the owner with nothing for group or others. Configuration files should have 600 permissions, which grants rw access to the owner with nothing for group or others. Executable binaries should have 755 permissions, which grants rwx access to the owner and rx access to group and others. This implements the principle of least privilege by ensuring no group or world-readable files exist in the container.
GEN006360: Audit Logging
STIG requires comprehensive audit logging of all file modifications and security-relevant events. The host-level audit daemon should be configured to log all writes and attribute changes on critical configuration files. The auditctl utility specifies watches on sensitive files and directories, recording all modifications. Verification that auditd is running and active ensures audit logs are continuously captured. These logs provide forensic evidence in the event of a security incident and demonstrate compliance with STIG requirements.
GEN006680: SSH Hardening (if SSH in container)
# NOT RECOMMENDED: SSH in containers# Instead use 'docker exec' or 'kubectl exec'# But if required for STIG: RUN apt-get install -y openssh-server && \ mkdir /var/run/sshd && \ echo "PermitRootLogin no" >> /etc/ssh/sshd_config && \ echo "PasswordAuthentication no" >> /etc/ssh/sshd_config && \ echo "X11Forwarding no" >> /etc/ssh/sshd_config && \ ssh-keygen -t ed25519 -N "" -f /etc/ssh/ssh_host_ed25519_keySTIG Application Controls
APP000080: Configuration Management
# Store configuration in read-only volumesFROM alpine:3.19 # Config is provided via ConfigMap/Secret (Kubernetes)RUN mkdir -p /etc/app# /etc/app is mounted read-only from ConfigMap # Application reads from /etc/app but cannot modifyRUN chmod 555 /etc/appAPP000400: Authentication
# Strong authentication only (no hardcoded credentials)FROM alpine:3.19 # Credentials from environment variables or secretsENV DB_PASSWORD="" # Must be set at runtimeENV API_KEY="" # Never log credentialsRUN export DB_PASSWORD=****APP000480: Data Protection
# Encrypt all sensitive dataFROM alpine:3.19 RUN apk add --no-cache openssl # Application must use AES-256 encryption for sensitive dataCOPY app /appRUN chmod 755 /app/bin/encrypt.sh # Encryption helperAPP000500: Error Handling
# No sensitive info in error messagesFROM alpine:3.19 # Application must:# - Not expose stack traces# - Not reveal system paths# - Not reveal database details# - Log errors securely RUN sed -i 's/debug=true/debug=false/' /app/config.confAPP000700: Input Validation
# Application code must validate all inputfrom flask import Flask, requestimport bleach app = Flask(__name__) @app.route('/api/data', methods=['POST'])def handle_data(): # STIG: Validate and sanitize all input data = request.json.get('input', '') # Validate length if len(data) > 1000: return {'error': 'Input too long'}, 400 # Sanitize dangerous characters safe_data = bleach.clean(data) # Validate format if not safe_data.isalnum(): return {'error': 'Invalid characters'}, 400 return {'status': 'ok'}STIG Dockerfile Template
# Dockerfile: STIG-CompliantFROM alpine:3.19 # Minimal base # GEN000080: Non-root userRUN useradd -m -u 1000 -d /app appuser && \ usermod -L appuser # GEN002870: Remove unnecessary packagesRUN apk del apk-tools ca-certificates # GEN006100: Strict permissionsCOPY --chown=appuser:appuser app /appRUN chmod 700 /app && \ find /app -type f -exec chmod 600 {} \; # GEN000840: Disable unnecessary servicesRUN rm -rf /etc/init.d/sshd /etc/init.d/httpd # Configuration (STIG)ENV APP_MODE=productionENV LOG_LEVEL=warn # Minimal loggingENV ENABLE_DEBUG=false # Never enable debug in production # Security labels (STIG requirement)LABEL security.stig="true"LABEL compliance="DoD-Required"LABEL maintainer="security@company.com" # Health check (GEN000700)HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \ CMD /app/bin/healthcheck.sh || exit 1 USER appuserENTRYPOINT ["/app/bin/myapp"]STIG Kubernetes Manifest
apiVersion: v1kind: Podmetadata: name: myapp-stig labels: security.stig: "true"spec: serviceAccountName: myapp-sa securityContext: runAsNonRoot: true runAsUser: 1000 fsGroup: 1000 seLinuxOptions: level: "s0:c123,c456" # STIG level assignment containers: - name: myapp image: myapp:stig-1.0.0 imagePullPolicy: Always # GEN000080: Resource limits resources: limits: cpu: "1" memory: "512Mi" requests: cpu: "100m" memory: "128Mi" # GEN002800: Security context securityContext: privileged: false allowPrivilegeEscalation: false capabilities: drop: - ALL readOnlyRootFilesystem: true # GEN006100: Volumes volumeMounts: - name: app-config mountPath: /etc/app readOnly: true - name: tmp mountPath: /tmp - name: logs mountPath: /var/log # GEN006360: Probes (monitoring) livenessProbe: exec: command: - /app/bin/healthcheck.sh initialDelaySeconds: 10 periodSeconds: 30 failureThreshold: 3 readinessProbe: exec: command: - /app/bin/readiness.sh initialDelaySeconds: 5 periodSeconds: 10 # GEN001080: Node affinity (run on hardened nodes) nodeSelector: node.kubernetes.io/stig-hardened: "true" # GEN002100: Pod security policy affinity: nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution: nodeSelectorTerms: - matchExpressions: - key: security.stig operator: In values: - "true" volumes: - name: app-config configMap: name: myapp-config defaultMode: 0400 # Read-only for owner - name: tmp emptyDir: medium: Memory sizeLimit: 100Mi - name: logs emptyDir: sizeLimit: 1Gi # GEN003150: Audit logging auditAnnotations: kubernetes.io/audit-log: "true" # GEN006500: Network policy # (See networkPolicy in CIS hardening) # GEN006700: No privileged namespace # (Run in non-privileged namespace only)STIG Compliance Verification
# Check image for STIG compliancecleanimg-init --stig-check --image myapp:stig-1.0.0 # Output:# GEN000080: Non-root user - PASS# GEN002870: Minimal packages (15 packages) - PASS# GEN006100: Strict file permissions - PASS# GEN006360: Audit logging capable - PASS# GEN001080: SSH hardening (no SSH - acceptable) - EXEMPTED# Overall STIG Level: 2 (Military-Grade)STIG Levels
STIG compliance is measured across five maturity levels. Level 0 represents non-compliant images that run as root with unnecessary packages and no hardening. Level 1 achieves basic compliance with non-root user, minimal packages, and some hardening measures. Level 2 (Standard) implements STIG controls, strict file permissions, and audit logging—this is production-ready and recommended for most organizations. Level 3 (Enhanced) adds DoD-specific requirements and NSA guidelines. Level 4 (Maximum) achieves the strictest compliance with air-gapped systems, no network access, and full isolation.
CleanStart achieves Level 2 compliance by default, providing a balance between strict security controls and operational practicality suitable for production deployments.
Common STIG Violations
Violation: Running as Root
# BADENTRYPOINT ["/app/bin/myapp"] # Runs as root # GOODRUN useradd -m appuserUSER appuserENTRYPOINT ["/app/bin/myapp"]Violation: Unnecessary Packages
# BADFROM ubuntu:24.04RUN apt-get update && apt-get install -y \ curl wget git vim emacs ssh openssh-server # GOODFROM alpine:3.19RUN apk add --no-cache opensslViolation: Debug Enabled
# BADENV DEBUG=trueENV LOG_LEVEL=debug # GOODENV DEBUG=falseENV LOG_LEVEL=warnDoD Contractor Checklist
DoD contractors must ensure that they use the STIG Dockerfile template, run the STIG compliance check in their CI/CD pipeline, enforce policies via a Kubernetes admission controller, and enable audit logging. They must verify that no hardcoded secrets exist in any images, that encryption is applied for all sensitive data, and that input validation is implemented on all endpoints. Regular STIG re-assessments should be scheduled, and any exceptions must be documented with formal risk acceptance.
See Also
For further reading, see cis-hardening.md for industry standard CIS hardening, openscap-reference.md for automated STIG checking with OpenSCAP, and ../regulatory/fedramp-high.md for government compliance under FedRAMP.
