Skip to content
Open
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"id": "781e75f7-bf43-4ab7-bae7-a08ad6607af3",
"queryName": "Beta - ENV Instruction Contains Secret",
"severity": "HIGH",
"category": "Secret Management",
"descriptionText": "Dockerfile ENV instructions should not store secrets, passwords, API keys or tokens as plaintext values. These values are embedded in every image layer and visible in 'docker inspect' output, risking credential exposure. Use build-time secrets (BuildKit) or a secrets manager instead.",
"descriptionUrl": "https://docs.docker.com/build/building/secrets/",
"platform": "Dockerfile",
"descriptionID": "7abccf53",
"cloudProvider": "common",
"cwe": "798",
"riskScore": "8.5",
"experimental": "true"
}
34 changes: 34 additions & 0 deletions assets/queries/dockerfile/env_instruction_has_secret/query.rego
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package Cx

secret_env_patterns := {
"password", "passwd", "pwd", "secret", "api_key", "apikey",
"token", "private_key", "auth_key", "access_key", "secret_key",
"encryption_key", "db_pass", "database_password", "app_secret"
}

CxPolicy[result] {
resource := input.document[i].command[name][_]
resource.Cmd == "env"

env_entry := resource.Value[_]
parts := split(env_entry, "=")
count(parts) >= 2

env_key := lower(parts[0])
env_val := concat("=", array.slice(parts, 1, count(parts)))

contains(env_key, secret_env_patterns[_])
env_val != ""
not startswith(env_val, "$")
not startswith(env_val, "\${")
not env_val == "changeme"
not env_val == "placeholder"

result := {
"documentId": input.document[i].id,
"searchKey": sprintf("FROM={{%s}}.ENV {{%s}}", [name, parts[0]]),
"issueType": "IncorrectValue",
"keyExpectedValue": sprintf("ENV '%s' should not contain a hardcoded secret value; use BuildKit secrets or a runtime secrets manager", [parts[0]]),
"keyActualValue": sprintf("ENV '%s' appears to contain a hardcoded secret value", [parts[0]]),
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
FROM python:3.11-slim
ENV APP_SECRET=${APP_SECRET}
ENV DATABASE_PASSWORD=$DB_PASS
ENV LOG_LEVEL=info
ENV PORT=8080
RUN pip install flask
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
FROM python:3.11-slim
ENV APP_SECRET=my_super_secret_value
ENV DATABASE_PASSWORD=s3cr3t123
ENV API_KEY=AKIAIOSFODNN7EXAMPLE
RUN pip install flask
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
[
{
"queryName": "Beta - ENV Instruction Contains Secret",
"severity": "HIGH",
"line": 2,
"fileName": "positive.dockerfile"
},
{
"queryName": "Beta - ENV Instruction Contains Secret",
"severity": "HIGH",
"line": 3,
"fileName": "positive.dockerfile"
},
{
"queryName": "Beta - ENV Instruction Contains Secret",
"severity": "HIGH",
"line": 4,
"fileName": "positive.dockerfile"
}
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"id": "ab2bf277-cd73-451c-8a34-70ea5c7ee258",
"queryName": "Beta - Container Seccomp Profile Not Set",
"severity": "MEDIUM",
"category": "Insecure Configurations",
"descriptionText": "Containers should have a seccomp profile set to 'RuntimeDefault' or 'Localhost' in their security context. Without it, the container has an unconfined syscall profile, increasing the attack surface if a container breakout occurs.",
"descriptionUrl": "https://kubernetes.io/docs/tutorials/security/seccomp/",
"platform": "Kubernetes",
"descriptionID": "30a7f7cc",
"cloudProvider": "common",
"cwe": "250",
"riskScore": "5.5",
"experimental": "true"
}
37 changes: 37 additions & 0 deletions assets/queries/k8s/container_seccomp_profile_not_set/query.rego
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package Cx

import data.generic.k8s as k8sLib
import data.generic.common as common_lib

valid_types := {"RuntimeDefault", "Localhost"}

CxPolicy[result] {
document := input.document[i]
specInfo := k8sLib.getSpecInfo(document)
metadata := document.metadata
types := {"initContainers", "containers"}
container := specInfo.spec[types[x]][j]

not has_valid_seccomp(container, specInfo.spec)

result := {
"documentId": document.id,
"resourceType": document.kind,
"resourceName": metadata.name,
"searchKey": sprintf("metadata.name={{%s}}.%s.%s.name={{%s}}.securityContext", [metadata.name, specInfo.path, types[x], container.name]),
"issueType": "MissingAttribute",
"keyExpectedValue": sprintf("Container '%s' should have seccompProfile.type set to 'RuntimeDefault' or 'Localhost'", [container.name]),
"keyActualValue": sprintf("Container '%s' does not have a valid seccompProfile configured", [container.name]),
"searchLine": common_lib.build_search_line(split(specInfo.path, "."), [types[x], j, "securityContext"]),
}
}

# seccomp defined at container level
has_valid_seccomp(container, _) {
container.securityContext.seccompProfile.type == valid_types[_]
}

# seccomp defined at pod spec level
has_valid_seccomp(_, spec) {
spec.securityContext.seccompProfile.type == valid_types[_]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
apiVersion: v1
kind: Pod
metadata:
name: negative-pod-container-level
spec:
containers:
- name: app
image: nginx:1.21
securityContext:
seccompProfile:
type: RuntimeDefault
runAsNonRoot: true
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
apiVersion: v1
kind: Pod
metadata:
name: negative-pod-pod-level
spec:
securityContext:
seccompProfile:
type: RuntimeDefault
containers:
- name: app
image: nginx:1.21
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
apiVersion: v1
kind: Pod
metadata:
name: positive-pod
spec:
containers:
- name: app
image: nginx:1.21
securityContext:
runAsNonRoot: true
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[
{
"queryName": "Beta - Container Seccomp Profile Not Set",
"severity": "MEDIUM",
"line": 7,
"fileName": "positive1.yaml"
}
]
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ kind: Deployment
metadata:
name: web-server
spec:
replicas: 3
selector:
matchLabels:
app: web-store
replicas: 3
template:
metadata:
labels:
Expand All @@ -21,7 +21,7 @@ spec:
operator: In
values:
- web-store
topologyKey: "kubernetes.io/hostname"
topologyKey: kubernetes.io/hostname
containers:
- name: web-app
image: nginx:1.16-alpine
- image: nginx:1.16-alpine
name: web-app
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: label-mismatch
spec:
replicas: 3
selector:
matchLabels:
app: web-store
replicas: 3
template:
metadata:
labels:
Expand All @@ -18,25 +19,25 @@ spec:
- labelSelector:
matchLabels:
app: web-store
topologyKey: "kubernetes.io/hostname"
topologyKey: kubernetes.io/hostname
containers:
- name: web-app
image: nginx:1.16-alpine
- image: nginx:1.16-alpine
name: web-app
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: no-affinity
spec:
replicas: 3
selector:
matchLabels:
app: web-store
replicas: 3
template:
metadata:
labels:
app: web-store
spec:
containers:
- name: web-app
image: nginx:1.16-alpine
- image: nginx:1.16-alpine
name: web-app
14 changes: 14 additions & 0 deletions assets/queries/k8s/ingress_without_tls/metadata.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"id": "c81c6c42-6c17-48c4-bde6-ea242e76f275",
"queryName": "Beta - Ingress Without TLS",
"severity": "HIGH",
"category": "Networking and Firewall",
"descriptionText": "Kubernetes Ingress resources should configure TLS to encrypt traffic between clients and the ingress controller. Without a 'tls' block, traffic is served over plain HTTP, exposing sensitive data in transit.",
"descriptionUrl": "https://kubernetes.io/docs/concepts/services-networking/ingress/#tls",
"platform": "Kubernetes",
"descriptionID": "0dcbf4a2",
"cloudProvider": "common",
"cwe": "319",
"riskScore": "7.5",
"experimental": "true"
}
40 changes: 40 additions & 0 deletions assets/queries/k8s/ingress_without_tls/query.rego
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package Cx

import data.generic.common as common_lib

CxPolicy[result] {
document := input.document[i]
document.kind == "Ingress"
metadata := document.metadata
not common_lib.valid_key(document.spec, "tls")

result := {
"documentId": document.id,
"resourceType": document.kind,
"resourceName": metadata.name,
"searchKey": sprintf("metadata.name={{%s}}.spec", [metadata.name]),
"issueType": "MissingAttribute",
"keyExpectedValue": sprintf("Ingress '%s' should have 'spec.tls' configured", [metadata.name]),
"keyActualValue": sprintf("Ingress '%s' does not have 'spec.tls' configured; traffic is served over HTTP", [metadata.name]),
"searchLine": common_lib.build_search_line(["spec"], []),
}
}

CxPolicy[result] {
document := input.document[i]
document.kind == "Ingress"
metadata := document.metadata
tls_entries := document.spec.tls
count(tls_entries) == 0

result := {
"documentId": document.id,
"resourceType": document.kind,
"resourceName": metadata.name,
"searchKey": sprintf("metadata.name={{%s}}.spec.tls", [metadata.name]),
"issueType": "IncorrectValue",
"keyExpectedValue": sprintf("Ingress '%s' should have at least one TLS entry in 'spec.tls'", [metadata.name]),
"keyActualValue": sprintf("Ingress '%s' has an empty 'spec.tls' list", [metadata.name]),
"searchLine": common_lib.build_search_line(["spec", "tls"], []),
}
}
20 changes: 20 additions & 0 deletions assets/queries/k8s/ingress_without_tls/test/negative1.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: negative-ingress
spec:
tls:
- hosts:
- example.com
secretName: example-tls
rules:
- host: example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: my-service
port:
number: 80
16 changes: 16 additions & 0 deletions assets/queries/k8s/ingress_without_tls/test/positive1.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: positive-ingress
spec:
rules:
- host: example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: my-service
port:
number: 80
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[
{
"queryName": "Beta - Ingress Without TLS",
"severity": "HIGH",
"line": 5,
"fileName": "positive1.yaml"
}
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"id": "f979bf87-34fd-4bab-a5b1-530268a581d5",
"queryName": "Beta - Backup Vault Without KMS Key",
"severity": "MEDIUM",
"category": "Encryption",
"descriptionText": "AWS Backup Vaults should be encrypted using a customer-managed KMS key. Without a 'kms_key_arn', backups are protected only by the default AWS-managed key, offering no additional control over key rotation or access.",
"descriptionUrl": "https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/backup_vault#kms_key_arn",
"platform": "Terraform",
"descriptionID": "6df8a84c",
"cloudProvider": "aws",
"cwe": "311",
"riskScore": "5.5",
"experimental": "true"
}
Loading
Loading