Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 63 additions & 2 deletions docs/generated/checks.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,24 @@ verbs:
- ^watch$
- ^*$
```
## automount-service-account-token

**Enabled by default**: No

**Description**: Pod spec does not explicitly set automountServiceAccountToken: false. If the workload does not call the Kubernetes API, consider disabling automatic token mounting to reduce attack surface. Note: this checks the pod spec only — a ServiceAccount-level setting may also control this behavior.

**Remediation**: Set automountServiceAccountToken: false in the pod spec if the workload does not need to interact with the Kubernetes API.

**Template**: [cel-expression](templates.md#cel)

**Parameters**:

```yaml
check: |
(!has(object.spec.template.spec.automountServiceAccountToken) ||
object.spec.template.spec.automountServiceAccountToken == true)
? 'service account token is auto-mounted — set automountServiceAccountToken: false if not needed' : ''
```
## cluster-admin-role-binding

**Enabled by default**: No
Expand Down Expand Up @@ -255,9 +273,9 @@ forbiddenServiceTypes:

**Enabled by default**: Yes

**Description**: Alert on pods/deployment-likes with sharing host's network namespace
**Description**: Alert on pods/deployment-likes sharing the host's network namespace. Pods with hostNetwork: true bypass service mesh mTLS, silently disabling encryption for inter-service traffic. They also gain access to network-level attacks against other pods on the same node.

**Remediation**: Ensure the host's network namespace is not shared.
**Remediation**: Ensure the host's network namespace is not shared. If host networking is required, be aware that service mesh mTLS is bypassed and consider alternative encryption for inter-service communication.

**Template**: [host-network](templates.md#host-network)
## host-pid
Expand All @@ -284,6 +302,26 @@ forbiddenServiceTypes:
```yaml
minReplicas: 3
```
## init-container-security-gap

**Enabled by default**: No

**Description**: Init container does not set runAsNonRoot: true while the pod-level securityContext does. Init containers can run as root even when main containers are hardened via the pod-level security context.

**Remediation**: Set runAsNonRoot: true in each init container's securityContext to match the pod-level restriction.

**Template**: [cel-expression](templates.md#cel)

**Parameters**:

```yaml
check: |
has(object.spec.template.spec.securityContext) && has(object.spec.template.spec.securityContext.runAsNonRoot) && object.spec.template.spec.securityContext.runAsNonRoot == true && has(object.spec.template.spec.initContainers) && object.spec.template.spec.initContainers.exists(c,
!has(c.securityContext) ||
!has(c.securityContext.runAsNonRoot) ||
c.securityContext.runAsNonRoot != true
) ? 'init container may run as root despite pod-level runAsNonRoot — apply securityContext to init containers' : ''
```
## invalid-target-ports

**Enabled by default**: Yes
Expand Down Expand Up @@ -524,6 +562,29 @@ acceptedPriorityClassNames:
**Remediation**: Ensure privileged ports [0, 1024] are not mapped within containers.

**Template**: [privileged-ports](templates.md#privileged-ports)
## projected-token-long-expiration

**Enabled by default**: No

**Description**: Projected service account token volume with expirationSeconds greater than 24 hours. Long-lived tokens extracted from compromised pods remain valid for extended periods.

**Remediation**: Set expirationSeconds to 3600 (1 hour) or less unless the workload specifically requires longer token lifetimes.

**Template**: [cel-expression](templates.md#cel)

**Parameters**:

```yaml
check: |
has(object.spec.template.spec.volumes) && object.spec.template.spec.volumes.exists(v,
has(v.projected) &&
v.projected.sources.exists(s,
has(s.serviceAccountToken) &&
has(s.serviceAccountToken.expirationSeconds) &&
s.serviceAccountToken.expirationSeconds > 86400
)
) ? 'projected SA token with expirationSeconds > 24h — stolen tokens persist' : ''
```
## read-secret-from-env-var

**Enabled by default**: No
Expand Down
36 changes: 36 additions & 0 deletions e2etests/bats-tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,18 @@ get_value_from() {
[[ "${count}" == "1" ]]
}

@test "automount-service-account-token" {
tmp="tests/checks/automount-service-account-token.yml"
cmd="${KUBE_LINTER_BIN} lint --include automount-service-account-token --do-not-auto-add-defaults --format json ${tmp}"
run ${cmd}

print_info "${status}" "${output}" "${cmd}" "${tmp}"
[ "$status" -eq 1 ]

count=$(get_value_from "${lines[0]}" '.Reports | length')
[[ "${count}" == "2" ]]
}

@test "cluster-admin-role-binding" {
tmp="tests/checks/cluster-admin-role-binding.yml"
cmd="${KUBE_LINTER_BIN} lint --include cluster-admin-role-binding --do-not-auto-add-defaults --format json ${tmp}"
Expand Down Expand Up @@ -435,6 +447,18 @@ get_value_from() {
[[ "${count}" == "1" ]]
}

@test "init-container-security-gap" {
tmp="tests/checks/init-container-security-gap.yml"
cmd="${KUBE_LINTER_BIN} lint --include init-container-security-gap --do-not-auto-add-defaults --format json ${tmp}"
run ${cmd}

print_info "${status}" "${output}" "${cmd}" "${tmp}"
[ "$status" -eq 1 ]

count=$(get_value_from "${lines[0]}" '.Reports | length')
[[ "${count}" == "1" ]]
}

@test "invalid-target-ports" {
tmp="tests/checks/invalid-target-ports.yaml"
cmd="${KUBE_LINTER_BIN} lint --include invalid-target-ports --do-not-auto-add-defaults --format json ${tmp}"
Expand Down Expand Up @@ -815,6 +839,18 @@ get_value_from() {
[[ "${count}" == "2" ]]
}

@test "projected-token-long-expiration" {
tmp="tests/checks/projected-token-long-expiration.yml"
cmd="${KUBE_LINTER_BIN} lint --include projected-token-long-expiration --do-not-auto-add-defaults --format json ${tmp}"
run ${cmd}

print_info "${status}" "${output}" "${cmd}" "${tmp}"
[ "$status" -eq 1 ]

count=$(get_value_from "${lines[0]}" '.Reports | length')
[[ "${count}" == "1" ]]
}

@test "read-secret-from-env-var" {
tmp="tests/checks/read-secret-from-env-var.yml"
cmd="${KUBE_LINTER_BIN} lint --include read-secret-from-env-var --do-not-auto-add-defaults --format json ${tmp}"
Expand Down
19 changes: 19 additions & 0 deletions pkg/builtinchecks/yamls/automount-service-account-token.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
name: automount-service-account-token
description: >-
Pod spec does not explicitly set automountServiceAccountToken: false.
If the workload does not call the Kubernetes API, consider disabling
automatic token mounting to reduce attack surface. Note: this checks
the pod spec only — a ServiceAccount-level setting may also control
this behavior.
remediation: >-
Set automountServiceAccountToken: false in the pod spec if the workload
does not need to interact with the Kubernetes API.
scope:
objectKinds:
- DeploymentLike
template: cel-expression
params:
check: >
(!has(object.spec.template.spec.automountServiceAccountToken) ||
object.spec.template.spec.automountServiceAccountToken == true)
? 'service account token is auto-mounted — set automountServiceAccountToken: false if not needed' : ''
Comment thread
coderabbitai[bot] marked this conversation as resolved.
11 changes: 9 additions & 2 deletions pkg/builtinchecks/yamls/hostnetwork.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
name: "host-network"
description: "Alert on pods/deployment-likes with sharing host's network namespace"
remediation: "Ensure the host's network namespace is not shared."
description: >-
Alert on pods/deployment-likes sharing the host's network namespace.
Pods with hostNetwork: true bypass service mesh mTLS, silently disabling
encryption for inter-service traffic. They also gain access to
network-level attacks against other pods on the same node.
remediation: >-
Ensure the host's network namespace is not shared. If host networking
is required, be aware that service mesh mTLS is bypassed and consider
alternative encryption for inter-service communication.
scope:
objectKinds:
- DeploymentLike
Expand Down
23 changes: 23 additions & 0 deletions pkg/builtinchecks/yamls/init-container-security-gap.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
name: init-container-security-gap
description: >-
Init container does not set runAsNonRoot: true while the pod-level
securityContext does. Init containers can run as root even when main
containers are hardened via the pod-level security context.
remediation: >-
Set runAsNonRoot: true in each init container's securityContext to match
the pod-level restriction.
scope:
objectKinds:
- DeploymentLike
template: cel-expression
params:
check: >
has(object.spec.template.spec.securityContext) &&
has(object.spec.template.spec.securityContext.runAsNonRoot) &&
object.spec.template.spec.securityContext.runAsNonRoot == true &&
has(object.spec.template.spec.initContainers) &&
object.spec.template.spec.initContainers.exists(c,
!has(c.securityContext) ||
!has(c.securityContext.runAsNonRoot) ||
c.securityContext.runAsNonRoot != true
) ? 'init container may run as root despite pod-level runAsNonRoot — apply securityContext to init containers' : ''
23 changes: 23 additions & 0 deletions pkg/builtinchecks/yamls/projected-token-long-expiration.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
name: projected-token-long-expiration
description: >-
Projected service account token volume with expirationSeconds greater
than 24 hours. Long-lived tokens extracted from compromised pods remain
valid for extended periods.
remediation: >-
Set expirationSeconds to 3600 (1 hour) or less unless the workload
specifically requires longer token lifetimes.
scope:
objectKinds:
- DeploymentLike
template: cel-expression
params:
check: >
has(object.spec.template.spec.volumes) &&
object.spec.template.spec.volumes.exists(v,
has(v.projected) &&
v.projected.sources.exists(s,
has(s.serviceAccountToken) &&
has(s.serviceAccountToken.expirationSeconds) &&
s.serviceAccountToken.expirationSeconds > 86400
)
) ? 'projected SA token with expirationSeconds > 24h — stolen tokens persist' : ''
35 changes: 35 additions & 0 deletions tests/checks/automount-service-account-token.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: dont-fire
spec:
template:
spec:
automountServiceAccountToken: false
containers:
- name: app
image: app:v1
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: fire-default-mount
spec:
template:
spec:
containers:
- name: app
image: app:v1
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: fire-explicit-true
spec:
template:
spec:
automountServiceAccountToken: true
containers:
- name: app
image: app:v1
47 changes: 47 additions & 0 deletions tests/checks/init-container-security-gap.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: dont-fire-no-init
spec:
template:
spec:
securityContext:
runAsNonRoot: true
containers:
- name: app
image: app:v1
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: dont-fire-init-hardened
spec:
template:
spec:
securityContext:
runAsNonRoot: true
initContainers:
- name: init
image: init:v1
securityContext:
runAsNonRoot: true
containers:
- name: app
image: app:v1
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: fire-init-no-security-context
spec:
template:
spec:
securityContext:
runAsNonRoot: true
initContainers:
- name: init
image: init:v1
containers:
- name: app
image: app:v1
36 changes: 36 additions & 0 deletions tests/checks/projected-token-long-expiration.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: dont-fire-short-token
spec:
template:
spec:
containers:
- name: app
image: app:v1
volumes:
- name: token
projected:
sources:
- serviceAccountToken:
expirationSeconds: 3600
path: token
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: fire-long-token
spec:
template:
spec:
containers:
- name: app
image: app:v1
volumes:
- name: token
projected:
sources:
- serviceAccountToken:
expirationSeconds: 604800
path: token
Loading