Skip to content

Commit 1882135

Browse files
committed
2.0.0 Kubernetes rules & Rebranding
- Kubernetes: Detect non-root containers to align with the NSA Kubernetes Hardening Guide - Plugin name made more concise - New branded logo featuring the dog “Jessica”
1 parent 80de26a commit 1882135

25 files changed

Lines changed: 657 additions & 52 deletions

CHANGELOG.md

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,18 @@
11
<!-- Keep a Changelog guide -> https://keepachangelog.com -->
22

3-
# Infrastructure as Code (IaC) Security Linter Changelog
3+
# Cloud (IaC) Security Changelog
4+
5+
## [2.0.0] 11-07-2025
6+
7+
This version marks a major step in the plugin’s lifecycle.
8+
In this release, I have begun implementing Kubernetes rules and completed the rebranding.
9+
10+
### Added
11+
- Kubernetes: Detect non-root containers to align with the NSA Kubernetes Hardening Guide
12+
13+
### Updated
14+
- Plugin name made more concise
15+
- New branded logo featuring the dog “Jessica”
416

517
## [1.1.8] 08-06-2025
618

README.md

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,46 @@
1-
# Infrastructure as Code (IaC) Security Linter
1+
# Cloud (IaC) Security
22

33
[![CI](https://github.com/NordCoderd/infrastructure-security/actions/workflows/gradle.yml/badge.svg)](https://github.com/NordCoderd/infrastructure-security/actions/workflows/gradle.yml)
44
[![JetBrains Plugin Version](https://img.shields.io/jetbrains/plugin/v/dev.protsenko.security-linter)](https://plugins.jetbrains.com/plugin/25413-infrastructure-security)
55
[![JetBrains Plugin Downloads](https://img.shields.io/jetbrains/plugin/d/dev.protsenko.security-linter)](https://plugins.jetbrains.com/plugin/25413-infrastructure-security)
66

77
<!-- Plugin description -->
8-
Infrastructure as Code (IaC) Security Linter for JetBrains IDEs (e.g., IntelliJ IDEA, PyCharm, WebStorm, and more).
8+
Cloud (IaC) Security Linter for JetBrains IDEs (e.g., IntelliJ IDEA, PyCharm, WebStorm, and more).
99

10-
Scan Docker and Infrastructure as Code (IaC) files for security vulnerabilities and misconfigurations directly in your JetBrains IDE.
10+
Scan Docker, Kubernetes, and other Infrastructure-as-Code (IaC) files for security vulnerabilities and misconfigurations directly within your JetBrains IDE.
1111

1212
## Why this plugin?
1313

14-
- Seamless integration into IDE without installing external tools.
15-
- Verifies your files on the fly and highlight problems earlier and that make shift left happens.
16-
- Quick-fixes for problems are available for some inspections that could help fix problem faster.
14+
- Seamless integration into the IDE without installing external tools.
15+
- Verifies your files on the fly and highlight problems earlier, and that make shift left happens.
16+
- Quick-fixes for problems are available for some inspections that could help fix problems faster.
1717
- Supports complicated verifications, such as tracking variables and arguments as sources of issues.
1818
- Pure Kotlin implementation, leveraging the power of IDEs.
19-
- Because I made it with love, [read about my experience creating the plugin.](https://protsenko.dev/2025/03/24/how-i-made-docker-linter-for-intellij-idea-and-other-jetbrains-ide/)
2019

2120
## What does the plugin offer?
2221

2322
- **Dockerfile Analysis**: Detect security vulnerabilities and optimize Docker images with over 40 checks.
2423
- **Docker Compose**: Detect security vulnerabilities and misconfigurations.
24+
- **Kubernetes**: Detect security issues to align with the NSA Kubernetes Hardening Guide [wip].
2525
- **Quick Fixes**: Resolve issues faster using built-in quick fixes.
2626

27-
## What problems could find that plugin?
27+
## What problems can the plugin detect?
2828

29-
You could find more information about detected problems:
29+
You can find more information about detected problems:
3030

31-
- [Documentation](https://protsenko.dev/infrastructure-security)
32-
- In IDE popup messages with problem. Problem messages have a link to dedicated article in the documentation.
31+
- [Documentation site](https://protsenko.dev/infrastructure-security)
32+
- [Exciting story about creating the plugin](https://protsenko.dev/2025/03/24/how-i-made-docker-linter-for-intellij-idea-and-other-jetbrains-ide/)
33+
- In-IDE pop-up messages describing each issue, each of which links to a dedicated article in the documentation
3334

3435
## Planned features
3536

36-
- **Extended support for Dockerfile and docker-compose files**
37-
- **Kubernetes Files**: Analyzing Kubernetes YAML files to comply with best practices and security standards.
37+
- **Kubernetes**: Implementing rules to align with the NSA Kubernetes Hardening Guide.
3838
- **and more**: Expanding support for other IaC tools and formats to comprehensively protect and optimize your infrastructure configurations.
3939

40-
Detailed list of planned features are available on [GitHub issues](https://github.com/NordCoderd/infrastructure-security/labels/enhancement)
41-
4240
## Thanks
41+
4342
- My mother, who supported me every step of the way and who is no longer with us.
44-
- [Trivy-checks](https://github.com/aquasecurity/trivy-checks/tree/main) for good source of rules.
43+
- [Trivy-checks](https://github.com/aquasecurity/trivy-checks/tree/main) for a good source of rules.
4544
- [Hadolint](https://github.com/hadolint/hadolint) for yet another docker rule set.
45+
- [Kubescape regorules](https://github.com/kubescape/regolibrary) for a good source of Kubernetes rules.
4646
<!-- Plugin description end -->

gradle.properties

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
pluginGroup = dev.protsenko.securityLinter
2-
pluginName = Infrastructure as Code (IaC) Security Linter
2+
pluginName = Cloud (IaC) Security
33
pluginRepositoryUrl = https://github.com/NordCoderd/infrastructure-security
4-
pluginVersion = 1.1.8
4+
pluginVersion = 2.0.0
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: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
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.PsiElement
7+
import com.intellij.psi.PsiElementVisitor
8+
import com.intellij.psi.PsiFile
9+
import dev.protsenko.securityLinter.core.HtmlProblemDescriptor
10+
import dev.protsenko.securityLinter.core.SecurityPluginBundle
11+
import dev.protsenko.securityLinter.utils.YamlPath
12+
import org.jetbrains.yaml.YAMLUtil
13+
import org.jetbrains.yaml.psi.YAMLFile
14+
import org.jetbrains.yaml.psi.YAMLMapping
15+
import org.jetbrains.yaml.psi.YAMLScalar
16+
import org.jetbrains.yaml.psi.YAMLSequence
17+
18+
class NonRootContainerInspection : LocalInspectionTool() {
19+
20+
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor {
21+
return object : PsiElementVisitor() {
22+
override fun visitFile(file: PsiFile) {
23+
if (file !is YAMLFile) return
24+
25+
val documents = file.documents
26+
27+
for (document in documents) {
28+
val kind = YAMLUtil.getQualifiedKeyInDocument(document, listOf("kind")) ?: return
29+
val kindValue = kind.valueText
30+
if (kindValue !in supportedKinds) return
31+
32+
val specPrefix = evaluateSpecPrefix(kindValue)
33+
34+
val isRunAsNonRoot = YamlPath.findByYamlPath("${specPrefix}spec.$RUN_AS_NON_ROOT", document)
35+
val isAllowedRunAsNonRoot = !highlightIfValueNotTrue(isRunAsNonRoot, holder)
36+
37+
val isRunAsUser = YamlPath.findByYamlPath("${specPrefix}spec.$RUN_AS_USER", document)
38+
val isRunAsGroup = YamlPath.findByYamlPath("${specPrefix}spec.$RUN_AS_GROUP", document)
39+
highlightIfValueZero(isRunAsUser, holder)
40+
highlightIfValueZero(isRunAsGroup, holder)
41+
42+
// container level
43+
var allContainersAreNonRoot = true
44+
45+
for (containerType in containerTypes) {
46+
val containers =
47+
YamlPath.findByYamlPath("${specPrefix}$containerType", document) as? YAMLSequence ?: continue
48+
49+
for (containerItem in containers.items) {
50+
val containerYaml = containerItem.value as? YAMLMapping ?: continue
51+
52+
val isRunAsUser =
53+
YamlPath.findByYamlPath(RUN_AS_USER, containerYaml)
54+
55+
val isRunAsGroup =
56+
YamlPath.findByYamlPath(RUN_AS_GROUP, containerYaml)
57+
58+
highlightIfValueZero(isRunAsUser, holder)
59+
highlightIfValueZero(isRunAsGroup, holder)
60+
61+
val isRunAsNonRootContainer =
62+
YamlPath.findByYamlPath(RUN_AS_NON_ROOT, containerYaml)
63+
64+
//The container fields may be undefined/nil if the
65+
// pod-level spec.securityContext.runAsNonRoot is set to true.
66+
67+
// global and container level isn't set
68+
if (isRunAsNonRoot == null && isRunAsNonRootContainer == null){
69+
allContainersAreNonRoot = false
70+
}
71+
72+
val isRestrictedValue = highlightIfValueNotTrue(isRunAsNonRootContainer, holder)
73+
if (isRestrictedValue) {
74+
allContainersAreNonRoot = false
75+
}
76+
}
77+
}
78+
79+
if (isRunAsNonRoot == null && !allContainersAreNonRoot) {
80+
val descriptor = HtmlProblemDescriptor(
81+
kind,
82+
SecurityPluginBundle.message("kube001.documentation"),
83+
SecurityPluginBundle.message("kube001.non-root-containers"),
84+
ProblemHighlightType.ERROR, emptyArray()
85+
)
86+
87+
holder.registerProblem(descriptor)
88+
}
89+
}
90+
91+
super.visitFile(file)
92+
}
93+
}
94+
}
95+
96+
private fun evaluateSpecPrefix(kind: String): String {
97+
if (kind == "Pod") return ""
98+
if (kind == "CronJob") return "spec.jobTemplate.spec.template."
99+
if (kind in specInTemplateKindTypes) return "spec.template."
100+
return ""
101+
}
102+
103+
private fun highlightIfValueNotTrue(element: PsiElement?, holder: ProblemsHolder): Boolean {
104+
if (element !is YAMLScalar) return false
105+
val isRunAsNonRoot = element.textValue.toBooleanStrictOrNull()
106+
107+
if (isRunAsNonRoot != true) {
108+
val descriptor = HtmlProblemDescriptor(
109+
element,
110+
SecurityPluginBundle.message("kube001.documentation"),
111+
SecurityPluginBundle.message("kube001.non-root-containers"),
112+
ProblemHighlightType.ERROR, emptyArray()
113+
)
114+
115+
holder.registerProblem(descriptor)
116+
return true
117+
}
118+
return false
119+
}
120+
121+
// Running as Non-root user (v1.23+)
122+
private fun highlightIfValueZero(element: PsiElement?, holder: ProblemsHolder) {
123+
if (element !is YAMLScalar) return
124+
val isRunAsUser = element.textValue.toIntOrNull() ?: return
125+
126+
if (isRunAsUser == 0) {
127+
val descriptor = HtmlProblemDescriptor(
128+
element,
129+
SecurityPluginBundle.message("kube001.documentation"),
130+
SecurityPluginBundle.message("kube001.non-root-containers"),
131+
ProblemHighlightType.ERROR, emptyArray()
132+
)
133+
134+
holder.registerProblem(descriptor)
135+
}
136+
}
137+
138+
}
139+
140+
private val specInTemplateKindTypes = setOf("Deployment", "ReplicaSet", "DaemonSet", "StatefulSet", "Job")
141+
private val supportedKinds = specInTemplateKindTypes + setOf("Pod", "CronJob")
142+
143+
private val containerTypes = listOf("spec.containers", "spec.initContainers", "spec.ephemeralContainers")
144+
145+
private const val RUN_AS_NON_ROOT = "securityContext.runAsNonRoot"
146+
private const val RUN_AS_USER = "securityContext.runAsUser"
147+
private const val RUN_AS_GROUP = "securityContext.runAsGroup"
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package dev.protsenko.securityLinter.utils
2+
3+
import com.intellij.psi.PsiElement
4+
import org.jetbrains.yaml.psi.YAMLDocument
5+
import org.jetbrains.yaml.psi.YAMLMapping
6+
import org.jetbrains.yaml.psi.YAMLSequence
7+
8+
object YamlPath {
9+
10+
fun findByYamlPath(path: String, document: YAMLDocument): PsiElement? {
11+
val matchedElement: YAMLMapping = document.children[0] as? YAMLMapping ?: return null
12+
13+
return findByYamlPath(path, matchedElement)
14+
}
15+
16+
fun findByYamlPath(path: String, matchedElement: YAMLMapping): PsiElement? {
17+
val searchTokens = path.split(".")
18+
var startElement: PsiElement? = matchedElement
19+
20+
var countMatches = 0
21+
22+
for (token in searchTokens) {
23+
if (token.endsWith("]")) {
24+
val yamlKey = token.substringBefore("[")
25+
val sequence =
26+
(startElement as? YAMLMapping)?.getKeyValueByKey(yamlKey)?.value as? YAMLSequence ?: break
27+
val index = token.substringAfter("[").substringBefore("]").toIntOrNull() ?: break
28+
29+
startElement = sequence.items.getOrNull(index)?.value as? YAMLMapping
30+
countMatches++
31+
} else {
32+
startElement = (startElement as? YAMLMapping)?.getKeyValueByKey(token)?.value ?: break
33+
countMatches++
34+
}
35+
}
36+
37+
if (countMatches == searchTokens.size) {
38+
return startElement
39+
}
40+
return null
41+
}
42+
}

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

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,13 @@
1212
<extensions defaultExtensionNs="com.intellij">
1313
<localInspection
1414
implementationClass="dev.protsenko.securityLinter.docker_compose.DockerComposeInspection"
15-
displayName="Docker Compose best practices"
15+
displayName="Docker Compose Best Practices"
1616
groupPathKey="common.group-key" groupKey="common.docker-compose-group-key"
1717
enabledByDefault="true" language="yaml"/>
18+
<localInspection
19+
implementationClass="dev.protsenko.securityLinter.kubernetes.NonRootContainerInspection"
20+
displayName="Non Root Containers"
21+
groupPathKey="common.group-key" groupKey="common.kubernetes-group-key"
22+
enabledByDefault="true" language="yaml"/>
1823
</extensions>
1924
</idea-plugin>

src/main/resources/META-INF/plugin.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@
55

66
<!-- Public plugin name should be written in Title Case.
77
Guidelines: https://plugins.jetbrains.com/docs/marketplace/plugin-overview-page.html#plugin-name -->
8-
<name>Infrastructure as Code (IaC) Security Linter</name>
8+
<name>Cloud (IaC) Security</name>
99

1010
<!-- A displayed Vendor name or Organization ID displayed on the Plugins Page. -->
11-
<vendor email="tech@protsenko.dev" url="https://protsenko.dev">Dmitrii Protsenko</vendor>
11+
<vendor email="tech@protsenko.dev" url="https://protsenko.dev">Dmitry Protsenko</vendor>
1212

1313
<!-- Product and plugin compatibility requirements.
1414
Read more: https://plugins.jetbrains.com/docs/intellij/plugin-compatibility.html -->

0 commit comments

Comments
 (0)