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:
| Capability | Purpose | Typical Requirement |
|---|---|---|
CAP_CHOWN | Change file ownership | Rarely needed at runtime |
CAP_DAC_OVERRIDE | Bypass file permission checks | Rarely needed |
CAP_FSETID | Set file capabilities | Rarely needed |
CAP_FOWNER | Bypass ownership checks | Rarely needed |
CAP_MKNOD | Create special files | Not needed |
CAP_NET_RAW | Use raw sockets | Not needed for most apps |
CAP_SETGID | Set group ID | Needed for user switching |
CAP_SETUID | Set user ID | Needed for user switching |
CAP_SETFCAP | Set file capabilities | Not needed |
CAP_SETPCAP | Transfer capabilities | Not needed |
CAP_NET_BIND_SERVICE | Bind to ports < 1024 | Only if binding privileged ports |
CAP_SYS_CHROOT | Use chroot | Rarely needed |
CAP_KILL | Send signals | Needed for process management |
CAP_AUDIT_WRITE | Write to audit log | Rarely 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:
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:
Verify capabilities of a running container:
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:
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:
To discover which system calls an application uses, trace it with strace in a test environment and build a profile from the results:
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:
| System | Enforcement | Use Case |
|---|---|---|
| AppArmor | Path-based access control | Ubuntu, Debian, SUSE |
| SELinux | Label-based access control | RHEL, 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:
For podman with AppArmor:
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
| Field | Value | Purpose |
|---|---|---|
runAsNonRoot | true | Reject containers that attempt to run as root |
runAsUser | 65534 | Run as nobody user |
readOnlyRootFilesystem | true | Mount root filesystem read-only |
allowPrivilegeEscalation | false | Prevent gaining privileges through setuid |
capabilities.drop | ALL | Remove all Linux capabilities |
seccompProfile.type | RuntimeDefault | Apply 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):
| Standard | Level | ARMORRED Compatibility |
|---|---|---|
| Privileged | Unrestricted | Compatible |
| Baseline | Prevents known privilege escalations | Compatible |
| Restricted | Heavily restricted, current hardening best practices | Compatible |
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:
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:
| Layer | Protection | Controls |
|---|---|---|
| Build time | Binary hardening | PIE, RELRO, NX, stack canaries, SafeStack, CFI |
| Build time | Container hardening | FROM scratch, minimal components, non-root, no shell |
| Runtime | Privilege restriction | Drop ALL capabilities, no-new-privileges |
| Runtime | System call filtering | Seccomp profiles |
| Runtime | Mandatory access control | AppArmor or SELinux |
| Runtime | Filesystem integrity | Read-only root filesystem |
| Runtime | Supply chain verification | Cosign signature validation |
| Runtime | Policy enforcement | Admission 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
CIS Docker Benchmark - Center for Internet Security. https://www.cisecurity.org/benchmark/docker
CIS Kubernetes Benchmark - Center for Internet Security. https://www.cisecurity.org/benchmark/kubernetes
Pod Security Standards - Kubernetes. https://kubernetes.io/docs/concepts/security/pod-security-standards/