Container run-time defense techniques

Runtime protections go beyond what ARMORRED delivers inside the container image. They are enforced by the container runtime, orchestrator, and host kernel at the time of execution. While ARMORRED images are hardened at build time, deploying them without runtime security controls leaves significant attack surface exposed.

This section documents the runtime security controls that complement ARMORRED's build-time hardening. These controls follow the CIS Docker Benchmark1 and CIS Kubernetes Benchmark2 recommendations.

Linux Capabilities

Linux capabilities decompose the monolithic root privilege into discrete units. Instead of granting a container full root access, specific capabilities can be granted or revoked individually.

By default, container runtimes grant a set of capabilities that most applications do not need:

CapabilityPurposeTypical Requirement
CAP_CHOWNChange file ownershipRarely needed at runtime
CAP_DAC_OVERRIDEBypass file permission checksRarely needed
CAP_FSETIDSet file capabilitiesRarely needed
CAP_FOWNERBypass ownership checksRarely needed
CAP_MKNODCreate special filesNot needed
CAP_NET_RAWUse raw socketsNot needed for most apps
CAP_SETGIDSet group IDNeeded for user switching
CAP_SETUIDSet user IDNeeded for user switching
CAP_SETFCAPSet file capabilitiesNot needed
CAP_SETPCAPTransfer capabilitiesNot needed
CAP_NET_BIND_SERVICEBind to ports < 1024Only if binding privileged ports
CAP_SYS_CHROOTUse chrootRarely needed
CAP_KILLSend signalsNeeded for process management
CAP_AUDIT_WRITEWrite to audit logRarely needed

Drop All, Add Back Only What Is Needed

The recommended approach is to drop all capabilities and explicitly add only those the application requires:


$ podman run -d \
--cap-drop=ALL \
--cap-add=NET_BIND_SERVICE \
-p 80:8080 \
ghcr.io/armorred/nginx:1.26-hardened

ARMORRED images run as non-root (UID 65534) by default and listen on port 8080, so most deployments do not need NET_BIND_SERVICE either:


$ podman run -d \
--cap-drop=ALL \
--tmpfs /tmp:rw \
--tmpfs /var/log/nginx:rw \
-p 8080:8080 \
ghcr.io/armorred/nginx:1.26-hardened

Verify capabilities of a running container:


$ podman exec nginx cat /proc/1/status | grep Cap
CapInh: 0000000000000000
CapPrm: 0000000000000000
CapEff: 0000000000000000
CapBnd: 0000000000000000
CapAmb: 0000000000000000

A value of all zeros confirms no capabilities are granted.

No New Privileges

The --security-opt=no-new-privileges flag prevents processes inside the container from gaining additional privileges through setuid binaries, capability inheritance, or other escalation mechanisms:


$ podman run -d \
--cap-drop=ALL \
--security-opt=no-new-privileges \
--tmpfs /tmp:rw \
--tmpfs /var/log/nginx:rw \
-p 8080:8080 \
ghcr.io/armorred/nginx:1.26-hardened

This flag sets the PR_SET_NO_NEW_PRIVS bit on the process, which is inherited by all child processes. Once set, it cannot be unset. Combined with ARMORRED's non-root execution, this closes privilege escalation paths.

Seccomp Profiles

Seccomp (Secure Computing Mode) restricts the system calls a container can invoke. The default seccomp profile blocks approximately 44 system calls, but a tighter profile can be applied based on the application's actual requirements.

Podman and Docker ship with a default profile. For production, create a custom profile that allows only the system calls your application uses:


$ podman run -d \
--cap-drop=ALL \
--security-opt=no-new-privileges \
--security-opt seccomp=/path/to/nginx-seccomp.json \
--tmpfs /tmp:rw \
--tmpfs /var/log/nginx:rw \
-p 8080:8080 \
ghcr.io/armorred/nginx:1.26-hardened

To discover which system calls an application uses, trace it with strace in a test environment and build a profile from the results:


$ strace -c -f podman run --rm ghcr.io/armorred/nginx:1.26-hardened \
nginx -t 2>&1 | tail -20

The OCI runtime specification defines the seccomp profile format. Tools like oci-seccomp-bpf-hook can automatically generate profiles from container execution traces.

AppArmor and SELinux

Mandatory Access Control (MAC) systems provide an additional layer of confinement beyond capabilities and seccomp:

SystemEnforcementUse Case
AppArmorPath-based access controlUbuntu, Debian, SUSE
SELinuxLabel-based access controlRHEL, CentOS, Fedora

Both systems confine containers to specific filesystem paths, network access patterns, and capability usage. The default container profiles are a reasonable starting point, and custom profiles can further restrict access.

For podman with SELinux:


$ podman run -d \
--security-opt label=type:container_t \
ghcr.io/armorred/nginx:1.26-hardened

For podman with AppArmor:


$ podman run -d \
--security-opt apparmor=podman-default \
ghcr.io/armorred/nginx:1.26-hardened

Kubernetes Pod Security

When deploying ARMORRED images in Kubernetes, configure the pod security context to enforce runtime constraints:

Recommended Security Context

HTTP probes are the recommended health check pattern for ARMORRED images. They work regardless of whether a shell is present in the image:

apiVersion: v1
kind: Pod
metadata:
  name: nginx-hardened
spec:
  securityContext:
    runAsNonRoot: true
    runAsUser: 65534
    runAsGroup: 65534
    fsGroup: 65534
    seccompProfile:
      type: RuntimeDefault
  containers:
    - name: nginx
      image: ghcr.io/armorred/nginx:1.26-hardened
      ports:
        - containerPort: 8080
      securityContext:
        allowPrivilegeEscalation: false
        readOnlyRootFilesystem: true
        capabilities:
          drop:
            - ALL
      livenessProbe:
        httpGet:
          path: /health
          port: 8080
        initialDelaySeconds: 5
        periodSeconds: 10
      readinessProbe:
        httpGet:
          path: /health
          port: 8080
        initialDelaySeconds: 3
        periodSeconds: 5
      volumeMounts:
        - name: tmp
          mountPath: /tmp
        - name: logs
          mountPath: /var/log/nginx
  volumes:
    - name: tmp
      emptyDir:
        sizeLimit: 64Mi
    - name: logs
      emptyDir:
        sizeLimit: 128Mi

Security Context Fields Explained

FieldValuePurpose
runAsNonRoottrueReject containers that attempt to run as root
runAsUser65534Run as nobody user
readOnlyRootFilesystemtrueMount root filesystem read-only
allowPrivilegeEscalationfalsePrevent gaining privileges through setuid
capabilities.dropALLRemove all Linux capabilities
seccompProfile.typeRuntimeDefaultApply the runtime's default seccomp profile

Locked Tier Deployment

The locked tier uses the same security context pattern. The only manifest change is the image tag:

apiVersion: v1
kind: Pod
metadata:
  name: nginx-locked
spec:
  securityContext:
    runAsNonRoot: true
    runAsUser: 65534
    seccompProfile:
      type: RuntimeDefault
  containers:
    - name: nginx
      image: ghcr.io/armorred/nginx:1.26-locked
      ports:
        - containerPort: 8080
      securityContext:
        allowPrivilegeEscalation: false
        readOnlyRootFilesystem: true
        capabilities:
          drop:
            - ALL
      livenessProbe:
        httpGet:
          path: /health
          port: 8080
        initialDelaySeconds: 5
        periodSeconds: 10
      readinessProbe:
        httpGet:
          path: /health
          port: 8080
        initialDelaySeconds: 3
        periodSeconds: 5
      volumeMounts:
        - name: tmp
          mountPath: /tmp
        - name: logs
          mountPath: /var/log/nginx
  volumes:
    - name: tmp
      emptyDir:
        sizeLimit: 64Mi
    - name: logs
      emptyDir:
        sizeLimit: 128Mi

Pod Security Standards

Kubernetes Pod Security Standards define three levels of restriction.3 ARMORRED images are compatible with the restricted level (the most secure):

StandardLevelARMORRED Compatibility
PrivilegedUnrestrictedCompatible
BaselinePrevents known privilege escalationsCompatible
RestrictedHeavily restricted, current hardening best practicesCompatible

Apply the restricted standard at the namespace level:

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

Validate Image Signatures Before Deployment

Verifying image signatures at runtime prevents deploying tampered or unauthorized images. ARMORRED images are signed with a project-owned ECDSA P-256 key pair. The public key is published at https://armorred.org/cosign.pub.

Verify before deployment:


$ cosign verify --key https://armorred.org/cosign.pub \
ghcr.io/armorred/nginx:1.26-hardened

Verification for ghcr.io/armorred/nginx:1.26-hardened --
The following checks were performed on each of these signatures:
- The cosign claims were validated
- The signatures were verified against the specified public key

Automated Verification in Kubernetes

Use admission controllers to enforce signature verification before pods are created:

  • Sigstore Policy Controller: Validates cosign signatures at admission time
  • Kyverno: Policy engine with image verification capabilities
  • OPA Gatekeeper: General-purpose admission controller with signature policies

Example Kyverno policy for ARMORRED image verification:

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: verify-armorred-images
spec:
  validationFailureAction: Enforce
  rules:
    - name: verify-signature
      match:
        any:
          - resources:
              kinds:
                - Pod
      verifyImages:
        - imageReferences:
            - "ghcr.io/armorred/*"
          attestors:
            - entries:
                - keys:
                    publicKeys: |
                      -----BEGIN PUBLIC KEY-----
                      MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE7vpEsQTwL/8XDx0KU6+jU64okQB1
                      A0sMNk5I6GNdZoNCJzJMyOj3jkuGm3TPDE6TtqWlbkScX0EcHv5U0kJqgg==
                      -----END PUBLIC KEY-----

This policy rejects any pod using an ARMORRED image that does not have a valid signature from the ARMORRED project key.

Runtime Protection Summary

Combining ARMORRED's build-time hardening with runtime controls provides comprehensive defense-in-depth:

LayerProtectionControls
Build timeBinary hardeningPIE, RELRO, NX, stack canaries, SafeStack, CFI
Build timeContainer hardeningFROM scratch, minimal components, non-root, no shell
RuntimePrivilege restrictionDrop ALL capabilities, no-new-privileges
RuntimeSystem call filteringSeccomp profiles
RuntimeMandatory access controlAppArmor or SELinux
RuntimeFilesystem integrityRead-only root filesystem
RuntimeSupply chain verificationCosign signature validation
RuntimePolicy enforcementAdmission controllers, Pod Security Standards

Each layer operates independently, so a failure at one level does not compromise the entire system. This defense-in-depth approach follows the security architecture described in the documentation home.

References


1

CIS Docker Benchmark - Center for Internet Security. https://www.cisecurity.org/benchmark/docker

2

CIS Kubernetes Benchmark - Center for Internet Security. https://www.cisecurity.org/benchmark/kubernetes