Skip to content

Commit 87c67da

Browse files
committed
2.0.3 Added new inspections:
- Kubernetes Security Standards: AppArmor is disable or override - Kubernetes Security Standards: HostPort is used
1 parent 88cfea5 commit 87c67da

17 files changed

Lines changed: 244 additions & 5 deletions

File tree

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@
77
### Changed
88
- Performed refactoring of kubernetes inspections
99

10+
### Added
11+
- Kubernetes Security Standards: AppArmor is disable or override
12+
- Kubernetes Security Standards: HostPort is used
13+
1014
## [2.0.2] 21-06-2025
1115

1216
### Added

README.md

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,11 +40,14 @@ You can find more information about detected problems:
4040
- **Kubernetes**: Implementing rules to align with the NSA Kubernetes Hardening Guide.
4141
- **and more**: Expanding support for other IaC tools and formats to comprehensively protect and optimize your infrastructure configurations.
4242

43+
## References
44+
45+
- [Trivy checks](https://github.com/aquasecurity/trivy-checks/tree/main) – entry point for Docker rules.
46+
- [Hadolint](https://github.com/hadolint/hadolint) – source of additional Docker rules.
47+
- [Pod Security Standards](https://kubernetes.io/docs/concepts/security/pod-security-standards/) – entry point for Kubernetes rules.
48+
- [Kubescape Rego library](https://github.com/kubescape/regolibrary) – source of Kubernetes rules.
49+
4350
## Thanks
4451

4552
- My mother, who supported me every step of the way and who is no longer with us.
46-
- [Trivy-checks](https://github.com/aquasecurity/trivy-checks/tree/main) for a good source of rules.
47-
- [Hadolint](https://github.com/hadolint/hadolint) for yet another docker rule set.
48-
- [Pod Security Standards](https://kubernetes.io/docs/concepts/security/pod-security-standards/)
49-
- [Kubescape regorules](https://github.com/kubescape/regolibrary) for a good source of Kubernetes rules.
5053
<!-- Plugin description end -->
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.kubernetes.quickfix.ReplaceValueToRuntimeDefaultQuickFix
10+
import dev.protsenko.securityLinter.utils.YamlPath
11+
import org.jetbrains.yaml.psi.YAMLDocument
12+
import org.jetbrains.yaml.psi.YAMLScalar
13+
14+
class AppArmorOverrideInspection : LocalInspectionTool() {
15+
16+
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor {
17+
return object : BaseKubernetesVisitor() {
18+
override fun analyze(specPrefix: String, document: YAMLDocument) {
19+
val appArmorSearchPath = "$specPrefix$APP_ARMOR_PROFILE_TYPE"
20+
val appArmorProfileType = YamlPath.findByYamlPath(
21+
appArmorSearchPath,
22+
document
23+
) as? YAMLScalar
24+
25+
highlightIfProblem(appArmorProfileType)
26+
27+
val containers = containers(specPrefix, document)
28+
for (container in containers) {
29+
val appArmorProfileType = YamlPath.findByYamlPath(
30+
appArmorSearchPath,
31+
container
32+
) as? YAMLScalar ?: continue
33+
34+
highlightIfProblem(appArmorProfileType)
35+
}
36+
}
37+
38+
//TODO: verify metadata (in docs in beta version)
39+
private fun highlightIfProblem(appArmorProfileType: YAMLScalar?) {
40+
if (appArmorProfileType != null && appArmorProfileType.textValue !in allowedProfiles) {
41+
val descriptor = HtmlProblemDescriptor(
42+
appArmorProfileType,
43+
SecurityPluginBundle.message("kube007.documentation"),
44+
SecurityPluginBundle.message("kube007.apparmor-disabled-or-override"),
45+
ProblemHighlightType.ERROR, arrayOf(
46+
ReplaceValueToRuntimeDefaultQuickFix(
47+
SecurityPluginBundle.message("kube007.qf.fix-value"),
48+
)
49+
)
50+
)
51+
52+
holder.registerProblem(descriptor)
53+
}
54+
}
55+
}
56+
}
57+
}
58+
59+
private const val APP_ARMOR_PROFILE_TYPE = "spec.securityContext.appArmorProfile.type"
60+
private val allowedProfiles = setOf("RuntimeDefault", "Localhost")
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
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 HostPortsInspection : LocalInspectionTool() {
15+
16+
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor {
17+
return object : BaseKubernetesVisitor() {
18+
override fun analyze(specPrefix: String, document: YAMLDocument) {
19+
val containers = containers(specPrefix, document)
20+
for (container in containers) {
21+
val ports = (YamlPath.findByYamlPath(
22+
"ports",
23+
container
24+
) as? YAMLSequence) ?: continue
25+
26+
for (port in ports.items) {
27+
val portMapping = port.value as? YAMLMapping ?: continue
28+
val hostPort = portMapping.getKeyValueByKey("hostPort") ?: continue
29+
val portValue = hostPort.valueText
30+
//FTI: allow list and removing whole yaml node
31+
if (portValue != "0"){
32+
val descriptor = HtmlProblemDescriptor(
33+
port,
34+
SecurityPluginBundle.message("kube006.documentation"),
35+
SecurityPluginBundle.message("kube006.host-ports"),
36+
ProblemHighlightType.ERROR, emptyArray()
37+
)
38+
39+
holder.registerProblem(descriptor)
40+
}
41+
}
42+
}
43+
}
44+
}
45+
}
46+
}
47+

src/main/kotlin/dev/protsenko/securityLinter/kubernetes/quickfix/ReplaceQuickFixes.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@ class ReplaceValueTo1000QuickFix(@IntentionFamilyName private val message: Strin
99
class ReplaceValueToFalseQuickFix(@IntentionFamilyName private val message: String) : ReplaceValueWithEnumQuickFix(message) {
1010
override val enumValue: ReplacedValueEnum = ReplacedValueEnum.FALSE
1111
}
12+
1213
class ReplaceValueToTrueQuickFix(@IntentionFamilyName private val message: String) : ReplaceValueWithEnumQuickFix(message) {
1314
override val enumValue: ReplacedValueEnum = ReplacedValueEnum.TRUE
1415
}
16+
17+
class ReplaceValueToRuntimeDefaultQuickFix(@IntentionFamilyName private val message: String) : ReplaceValueWithEnumQuickFix(message) {
18+
override val enumValue: ReplacedValueEnum = ReplacedValueEnum.RUNTIME_DEFAULT
19+
}
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package dev.protsenko.securityLinter.kubernetes.quickfix
22

33
enum class ReplacedValueEnum(val value: String) {
4-
TRUE("true"), FALSE("false"), ONE_THOUSAND("1000")
4+
TRUE("true"), FALSE("false"), ONE_THOUSAND("1000"),
5+
RUNTIME_DEFAULT("RuntimeDefault")
56
}

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,5 +40,15 @@
4040
displayName="HostPath Volumes"
4141
groupPathKey="common.group-key" groupKey="common.kubernetes-group-key"
4242
enabledByDefault="true" language="yaml"/>
43+
<localInspection
44+
implementationClass="dev.protsenko.securityLinter.kubernetes.HostPortsInspection"
45+
displayName="HostPorts"
46+
groupPathKey="common.group-key" groupKey="common.kubernetes-group-key"
47+
enabledByDefault="true" language="yaml"/>
48+
<localInspection
49+
implementationClass="dev.protsenko.securityLinter.kubernetes.AppArmorOverrideInspection"
50+
displayName="AppArmor or SELinux override"
51+
groupPathKey="common.group-key" groupKey="common.kubernetes-group-key"
52+
enabledByDefault="true" language="yaml"/>
4353
</extensions>
4454
</idea-plugin>
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<html>
2+
<body>
3+
<p>Detects AppArmor profile disabling or override</p>
4+
<p>Letting a Pod override or turn off the default AppArmor profile weakens one of the last lines of defence between the container and the host kernel, so the Pod Security Baseline policy blocks anything except the safe defaults (RuntimeDefault, or a vetted localhost/… profile).</p>
5+
</body>
6+
</html>
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<html>
2+
<body>
3+
<p>Detects using hostPorts</p>
4+
<p>hostPort maps a container port straight onto the node’s primary IP address. Traffic reaches the pod before it traverses Kubernetes Services or most NetworkPolicy rules, so any bug in the app is now a node-level exposure, not just an in-cluster one.</p>
5+
</body>
6+
</html>

src/main/resources/messages/SecurityPluginBundle.properties

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,15 @@ kube004.qf.fix-value=Set value to false
137137
kube005.documentation=host-path-volumes
138138
kube005.host-path-volumes=Using hostPath volumes is insecure as it gives access to the node's real file system. Consider removing it.
139139

140+
### kube006
141+
kube006.documentation=hostPort-opens-the-nodes-port
142+
kube006.host-ports=HostPort opens the node's port. Remove hostPort definition and/or use Service/Ingress instead.
143+
144+
### kube007
145+
kube007.documentation=apparmor-disabled-or-override
146+
kube007.apparmor-disabled-or-override=AppArmor profile disabled or overridden - container loses kernel protection. Use RuntimeDefault or remove profile type
147+
kube007.qf.fix-value=Set value to RuntimeDefault
148+
140149
# Not implemented
141150
ds029.missing-healthcheck=Missing HEALTHCHECK instruction
142151
ds023.multiple-exposed-port=Port {0} exposed more than one time.

0 commit comments

Comments
 (0)