Skip to content

Commit 147aee4

Browse files
committed
2.0.8
- [Disallowed volume type](https://protsenko.dev/infrastructure-security/disallowed-volume-type/) inspection (Pod Security Standards - Restricted)
1 parent e87f097 commit 147aee4

10 files changed

Lines changed: 210 additions & 1 deletion

File tree

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@
22

33
# Cloud (IaC) Security Changelog
44

5+
## [2.0.8] 09-08-2025
6+
7+
### Added
8+
- Kubernetes Security Standards: [Disallowed volume type](https://protsenko.dev/infrastructure-security/disallowed-volume-type/)
9+
510
## [2.0.7] 03-08-2025
611

712
### Changed

gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
pluginGroup = dev.protsenko.securityLinter
22
pluginName = Cloud (IaC) Security
33
pluginRepositoryUrl = https://github.com/NordCoderd/cloud-security-plugin
4-
pluginVersion = 2.0.7
4+
pluginVersion = 2.0.8
55

66
# Supported build number ranges and IntelliJ Platform versions -> https://plugins.jetbrains.com/docs/intellij/build-number-ranges.html
77
pluginSinceBuild = 231
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package dev.protsenko.securityLinter.kubernetes
2+
3+
import com.intellij.codeInspection.LocalInspectionTool
4+
import com.intellij.codeInspection.ProblemHighlightType
5+
import com.intellij.codeInspection.ProblemsHolder
6+
import com.intellij.psi.PsiElementVisitor
7+
import dev.protsenko.securityLinter.core.HtmlProblemDescriptor
8+
import dev.protsenko.securityLinter.core.SecurityPluginBundle
9+
import dev.protsenko.securityLinter.utils.YamlPath
10+
import org.jetbrains.yaml.psi.YAMLDocument
11+
import org.jetbrains.yaml.psi.YAMLMapping
12+
import org.jetbrains.yaml.psi.YAMLSequence
13+
14+
class DisallowedVolumeType : LocalInspectionTool() {
15+
override fun buildVisitor(
16+
holder: ProblemsHolder,
17+
isOnTheFly: Boolean,
18+
): PsiElementVisitor {
19+
return object : BaseKubernetesVisitor() {
20+
override fun analyze(
21+
specPrefix: String,
22+
document: YAMLDocument,
23+
) {
24+
val specVolumes =
25+
YamlPath.findByYamlPath("${specPrefix}spec.volumes", document) as? YAMLSequence ?: return
26+
27+
for (volume in specVolumes.items) {
28+
val volumeValue = volume.value as? YAMLMapping ?: continue
29+
val prohibitedValues =
30+
volumeValue
31+
.keyValues
32+
.filter {
33+
if (it.value !is YAMLMapping) return@filter false
34+
if (it.keyText !in allowedVolumeTypes) {
35+
return@filter true
36+
}
37+
return@filter false
38+
}
39+
40+
if (prohibitedValues.isNotEmpty()) {
41+
prohibitedValues.forEach {
42+
val descriptor =
43+
HtmlProblemDescriptor(
44+
it,
45+
SecurityPluginBundle.message("kube012.documentation"),
46+
SecurityPluginBundle.message("kube012.problem-text"),
47+
ProblemHighlightType.ERROR,
48+
emptyArray(),
49+
)
50+
holder.registerProblem(descriptor)
51+
}
52+
}
53+
}
54+
}
55+
}
56+
}
57+
}
58+
59+
private val allowedVolumeTypes =
60+
setOf("configMap", "csi", "downwardAPI", "emptyDir", "ephemeral", "persistentVolumeClaim", "projected", "secret")

src/main/resources/META-INF/dev.protsenko.security-linter-yaml.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,5 +70,10 @@
7070
displayName="Insecure systctls"
7171
groupPathKey="common.group-key" groupKey="common.kubernetes-group-key"
7272
enabledByDefault="true" language="yaml"/>
73+
<localInspection
74+
implementationClass="dev.protsenko.securityLinter.kubernetes.DisallowedVolumeType"
75+
displayName="Disallowed volume types"
76+
groupPathKey="common.group-key" groupKey="common.kubernetes-group-key"
77+
enabledByDefault="true" language="yaml"/>
7378
</extensions>
7479
</idea-plugin>
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<html>
2+
<body>
3+
<p>Detects disallowed volume types in Pods (violates Pod Security Restricted).</p>
4+
<p>Certain volume types (e.g., <code>hostPath</code> and legacy in-tree plugins) can expose the node filesystem or bypass pod isolation, leading to privilege escalation and node compromise.</p>
5+
<p><b>Allowed under Restricted:</b> <code>configMap</code>, <code>csi</code>, <code>downwardAPI</code>, <code>emptyDir</code>, <code>ephemeral</code>, <code>persistentVolumeClaim</code>, <code>projected</code>, <code>secret</code>.</p>
6+
<p><b>How to fix:</b> Replace with Kubernetes data volumes (ConfigMap/Secret/DownwardAPI/Projected) or storage via PVC/CSI. Prefer read-only mounts where possible and avoid mounting host paths or runtime sockets.</p>
7+
</body>
8+
</html>

src/main/resources/messages/SecurityPluginBundle.properties

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,10 @@ kube010.qf.fix-value=Set the type to 'RuntimeDefault'
165165
kube011.documentation=insecure-sysctls
166166
kube011.problem-text=Sysctls can disable security mechanisms or affect all containers on a host, and should be disallowed except for an allowed "safe" subset.
167167

168+
## kube012
169+
kube012.documentation=disallowed-volume-type
170+
kube012.problem-text=This volume type may expose the node or bypass pod isolation. Use Kubernetes data volumes (ConfigMap/Secret/DownwardAPI/Projected) or storage via PVC/CSI instead.
171+
168172
# Not implemented
169173
ds029.missing-healthcheck=Missing HEALTHCHECK instruction
170174
ds023.multiple-exposed-port=Port {0} exposed more than one time.
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package dev.protsenko.securityLinter.kubernetes
2+
3+
import com.intellij.codeInspection.LocalInspectionTool
4+
import dev.protsenko.securityLinter.core.KubernetesHighlightingBaseTest
5+
6+
class KUBE012DisallowedVolumeType(
7+
override val ruleFolderName: String = "KUBE012",
8+
override val targetFileName: String = "pod.yaml",
9+
override val targetInspection: LocalInspectionTool = DisallowedVolumeType(),
10+
override val customFiles: Set<String> =
11+
setOf(
12+
"cronjob.yaml.denied",
13+
),
14+
) : KubernetesHighlightingBaseTest()
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
apiVersion: batch/v1
2+
kind: CronJob
3+
metadata:
4+
name: hostlog-audit
5+
labels:
6+
app: hostlog-audit
7+
spec:
8+
schedule: "*/5 * * * *" # every 5 minutes
9+
successfulJobsHistoryLimit: 3
10+
failedJobsHistoryLimit: 1
11+
jobTemplate:
12+
spec:
13+
template:
14+
metadata:
15+
labels:
16+
app: hostlog-audit
17+
spec:
18+
restartPolicy: OnFailure
19+
volumes:
20+
- name: host-logs
21+
<error descr="This volume type may expose the node or bypass pod isolation. Use Kubernetes data volumes (ConfigMap/Secret/DownwardAPI/Projected) or storage via PVC/CSI instead.">hostPath:
22+
path: /var/log
23+
type: Directory</error>
24+
containers:
25+
- name: log-dumper
26+
image: busybox:1.36.1
27+
command: ["/bin/sh", "-c"]
28+
args:
29+
- tail -n 20 /host/var/log/dmesg
30+
securityContext:
31+
allowPrivilegeEscalation: false
32+
privileged: false
33+
readOnlyRootFilesystem: true
34+
runAsNonRoot: true
35+
volumeMounts:
36+
- name: host-logs
37+
mountPath: /host/var/log
38+
readOnly: true
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
apiVersion: apps/v1
2+
kind: Deployment
3+
metadata:
4+
name: safe-deployment
5+
spec:
6+
replicas: 1
7+
selector:
8+
matchLabels:
9+
app: safe
10+
template:
11+
metadata:
12+
labels:
13+
app: safe
14+
spec:
15+
containers:
16+
- name: app
17+
image: alpine
18+
command: ["sh", "-c", "sleep 3600"]
19+
volumeMounts:
20+
- name: app-config
21+
mountPath: /etc/app/config
22+
readOnly: true
23+
- name: db-creds
24+
mountPath: /var/run/secrets/db
25+
readOnly: true
26+
- name: cache
27+
mountPath: /var/cache/app
28+
- name: data
29+
mountPath: /var/lib/app
30+
volumes:
31+
- name: app-config
32+
configMap:
33+
name: app-config
34+
- name: db-creds
35+
secret:
36+
secretName: db-creds
37+
- name: cache
38+
emptyDir:
39+
sizeLimit: 256Mi
40+
- name: data
41+
persistentVolumeClaim:
42+
claimName: app-data # Backed by an approved StorageClass/CSI driver
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
apiVersion: apps/v1
2+
kind: Deployment
3+
metadata:
4+
name: risky-deployment
5+
spec:
6+
replicas: 1
7+
selector:
8+
matchLabels:
9+
app: risky
10+
template:
11+
metadata:
12+
labels:
13+
app: risky
14+
spec:
15+
containers:
16+
- name: app
17+
image: alpine
18+
command: ["sh", "-c", "sleep 3600"]
19+
volumeMounts:
20+
- name: docker-sock
21+
mountPath: /var/run/docker.sock
22+
- name: host-etc
23+
mountPath: /host/etc
24+
readOnly: true
25+
volumes:
26+
- name: docker-sock
27+
<error descr="This volume type may expose the node or bypass pod isolation. Use Kubernetes data volumes (ConfigMap/Secret/DownwardAPI/Projected) or storage via PVC/CSI instead.">hostPath:
28+
path: /var/run/docker.sock # Direct access to runtime socket
29+
type: Socket</error>
30+
- name: host-etc
31+
<error descr="This volume type may expose the node or bypass pod isolation. Use Kubernetes data volumes (ConfigMap/Secret/DownwardAPI/Projected) or storage via PVC/CSI instead.">hostPath:
32+
path: /etc # Arbitrary host filesystem access
33+
type: Directory</error>

0 commit comments

Comments
 (0)