security-use 0.1.1__py3-none-any.whl → 0.2.9__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. security_use/__init__.py +9 -1
  2. security_use/auth/__init__.py +16 -0
  3. security_use/auth/client.py +223 -0
  4. security_use/auth/config.py +177 -0
  5. security_use/auth/oauth.py +317 -0
  6. security_use/cli.py +699 -34
  7. security_use/compliance/__init__.py +10 -0
  8. security_use/compliance/mapper.py +275 -0
  9. security_use/compliance/models.py +50 -0
  10. security_use/dependency_scanner.py +76 -30
  11. security_use/fixers/iac_fixer.py +173 -95
  12. security_use/iac/rules/azure.py +246 -0
  13. security_use/iac/rules/gcp.py +255 -0
  14. security_use/iac/rules/kubernetes.py +429 -0
  15. security_use/iac/rules/registry.py +56 -0
  16. security_use/parsers/__init__.py +18 -0
  17. security_use/parsers/base.py +2 -0
  18. security_use/parsers/composer.py +101 -0
  19. security_use/parsers/conda.py +97 -0
  20. security_use/parsers/dotnet.py +89 -0
  21. security_use/parsers/gradle.py +90 -0
  22. security_use/parsers/maven.py +108 -0
  23. security_use/parsers/npm.py +196 -0
  24. security_use/parsers/yarn.py +108 -0
  25. security_use/reporter.py +29 -1
  26. security_use/sbom/__init__.py +10 -0
  27. security_use/sbom/generator.py +340 -0
  28. security_use/sbom/models.py +40 -0
  29. security_use/scanner.py +15 -2
  30. security_use/sensor/__init__.py +125 -0
  31. security_use/sensor/alert_queue.py +207 -0
  32. security_use/sensor/config.py +217 -0
  33. security_use/sensor/dashboard_alerter.py +246 -0
  34. security_use/sensor/detector.py +415 -0
  35. security_use/sensor/endpoint_analyzer.py +339 -0
  36. security_use/sensor/middleware.py +521 -0
  37. security_use/sensor/models.py +140 -0
  38. security_use/sensor/webhook.py +227 -0
  39. security_use-0.2.9.dist-info/METADATA +531 -0
  40. security_use-0.2.9.dist-info/RECORD +60 -0
  41. security_use-0.2.9.dist-info/licenses/LICENSE +21 -0
  42. security_use-0.1.1.dist-info/METADATA +0 -92
  43. security_use-0.1.1.dist-info/RECORD +0 -30
  44. {security_use-0.1.1.dist-info → security_use-0.2.9.dist-info}/WHEEL +0 -0
  45. {security_use-0.1.1.dist-info → security_use-0.2.9.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,255 @@
1
+ """GCP security rules for IaC scanning."""
2
+
3
+ from security_use.models import Severity
4
+ from security_use.iac.base import IaCResource
5
+ from security_use.iac.rules.base import Rule, RuleResult
6
+
7
+
8
+ class GCSBucketPublicAccessRule(Rule):
9
+ """Check that GCS buckets do not allow public access."""
10
+
11
+ RULE_ID = "CKV_GCP_5"
12
+ TITLE = "Cloud Storage bucket with public access"
13
+ SEVERITY = Severity.CRITICAL
14
+ DESCRIPTION = (
15
+ "Cloud Storage bucket is publicly accessible. This can expose "
16
+ "sensitive data to unauthorized users."
17
+ )
18
+ REMEDIATION = (
19
+ "Remove allUsers and allAuthenticatedUsers from bucket IAM bindings. "
20
+ "Use uniform bucket-level access."
21
+ )
22
+ RESOURCE_TYPES = [
23
+ "google_storage_bucket",
24
+ "google_storage_bucket_iam_binding",
25
+ "google_storage_bucket_iam_member",
26
+ ]
27
+
28
+ def evaluate(self, resource: IaCResource) -> RuleResult:
29
+ """Check if GCS bucket allows public access."""
30
+ has_public_access = False
31
+
32
+ # Check IAM bindings
33
+ members = resource.get_config("members", default=[])
34
+ member = resource.get_config("member", default="")
35
+
36
+ public_principals = ["allUsers", "allAuthenticatedUsers"]
37
+
38
+ if any(m in members for m in public_principals):
39
+ has_public_access = True
40
+
41
+ if member in public_principals:
42
+ has_public_access = True
43
+
44
+ fix_code = None
45
+ if has_public_access:
46
+ fix_code = "# Remove allUsers and allAuthenticatedUsers from members"
47
+
48
+ return self._create_result(not has_public_access, resource, fix_code)
49
+
50
+
51
+ class GCSBucketEncryptionRule(Rule):
52
+ """Check that GCS buckets have encryption configured."""
53
+
54
+ RULE_ID = "CKV_GCP_6"
55
+ TITLE = "Cloud Storage bucket without customer-managed encryption"
56
+ SEVERITY = Severity.HIGH
57
+ DESCRIPTION = (
58
+ "Cloud Storage bucket does not use customer-managed encryption keys (CMEK). "
59
+ "While GCS encrypts data by default, CMEK provides additional control."
60
+ )
61
+ REMEDIATION = (
62
+ "Configure a Cloud KMS key for bucket encryption."
63
+ )
64
+ RESOURCE_TYPES = ["google_storage_bucket"]
65
+
66
+ def evaluate(self, resource: IaCResource) -> RuleResult:
67
+ """Check if GCS bucket uses CMEK."""
68
+ encryption = resource.get_config("encryption", default={})
69
+ default_kms_key = encryption.get("default_kms_key_name")
70
+
71
+ has_cmek = bool(default_kms_key)
72
+
73
+ fix_code = None
74
+ if not has_cmek:
75
+ fix_code = '''encryption {
76
+ default_kms_key_name = "projects/PROJECT/locations/LOCATION/keyRings/KEYRING/cryptoKeys/KEY"
77
+ }'''
78
+
79
+ return self._create_result(has_cmek, resource, fix_code)
80
+
81
+
82
+ class GCPFirewallOpenIngressRule(Rule):
83
+ """Check that GCP firewall rules don't allow unrestricted ingress."""
84
+
85
+ RULE_ID = "CKV_GCP_2"
86
+ TITLE = "Firewall rule allows unrestricted ingress"
87
+ SEVERITY = Severity.HIGH
88
+ DESCRIPTION = (
89
+ "Firewall rule allows ingress from 0.0.0.0/0 on sensitive ports. "
90
+ "This exposes services to the entire internet."
91
+ )
92
+ REMEDIATION = (
93
+ "Restrict source_ranges to specific IP ranges. "
94
+ "Avoid using 0.0.0.0/0 as the source."
95
+ )
96
+ RESOURCE_TYPES = ["google_compute_firewall"]
97
+
98
+ SENSITIVE_PORTS = ["22", "3389", "3306", "5432", "1433", "27017", "6379"]
99
+
100
+ def evaluate(self, resource: IaCResource) -> RuleResult:
101
+ """Check if firewall rule has open ingress on sensitive ports."""
102
+ has_open_ingress = False
103
+
104
+ direction = resource.get_config("direction", default="INGRESS")
105
+ if direction.upper() != "INGRESS":
106
+ return self._create_result(True, resource)
107
+
108
+ source_ranges = resource.get_config("source_ranges", default=[])
109
+
110
+ if "0.0.0.0/0" not in source_ranges:
111
+ return self._create_result(True, resource)
112
+
113
+ # Check if sensitive ports are exposed
114
+ allow_rules = resource.get_config("allow", default=[])
115
+ if isinstance(allow_rules, list):
116
+ for rule in allow_rules:
117
+ ports = rule.get("ports", [])
118
+ if not ports:
119
+ # No port restriction means all ports
120
+ has_open_ingress = True
121
+ break
122
+ for port in ports:
123
+ if port in self.SENSITIVE_PORTS or "-" in str(port):
124
+ has_open_ingress = True
125
+ break
126
+
127
+ fix_code = None
128
+ if has_open_ingress:
129
+ fix_code = '# Restrict source_ranges to specific IP ranges instead of ["0.0.0.0/0"]'
130
+
131
+ return self._create_result(not has_open_ingress, resource, fix_code)
132
+
133
+
134
+ class GCPCloudSQLEncryptionRule(Rule):
135
+ """Check that Cloud SQL instances have encryption enabled."""
136
+
137
+ RULE_ID = "CKV_GCP_14"
138
+ TITLE = "Cloud SQL instance without customer-managed encryption"
139
+ SEVERITY = Severity.HIGH
140
+ DESCRIPTION = (
141
+ "Cloud SQL instance does not use customer-managed encryption keys (CMEK). "
142
+ "While Cloud SQL encrypts data by default, CMEK provides additional control."
143
+ )
144
+ REMEDIATION = (
145
+ "Configure a Cloud KMS key for Cloud SQL encryption."
146
+ )
147
+ RESOURCE_TYPES = ["google_sql_database_instance"]
148
+
149
+ def evaluate(self, resource: IaCResource) -> RuleResult:
150
+ """Check if Cloud SQL uses CMEK."""
151
+ settings = resource.get_config("settings", default={})
152
+ ip_config = settings.get("ip_configuration", {})
153
+
154
+ # Check for encryption key
155
+ encryption_key = resource.get_config("encryption_key_name")
156
+
157
+ has_cmek = bool(encryption_key)
158
+
159
+ fix_code = None
160
+ if not has_cmek:
161
+ fix_code = 'encryption_key_name = "projects/PROJECT/locations/LOCATION/keyRings/KEYRING/cryptoKeys/KEY"'
162
+
163
+ return self._create_result(has_cmek, resource, fix_code)
164
+
165
+
166
+ class GCPKMSKeyRotationRule(Rule):
167
+ """Check that Cloud KMS keys have rotation configured."""
168
+
169
+ RULE_ID = "CKV_GCP_43"
170
+ TITLE = "KMS key without rotation"
171
+ SEVERITY = Severity.MEDIUM
172
+ DESCRIPTION = (
173
+ "Cloud KMS key does not have automatic rotation configured. "
174
+ "Regular key rotation limits the impact of key compromise."
175
+ )
176
+ REMEDIATION = (
177
+ "Configure automatic key rotation with a rotation period of 90 days or less."
178
+ )
179
+ RESOURCE_TYPES = ["google_kms_crypto_key"]
180
+
181
+ def evaluate(self, resource: IaCResource) -> RuleResult:
182
+ """Check if KMS key has rotation configured."""
183
+ rotation_period = resource.get_config("rotation_period")
184
+
185
+ has_rotation = bool(rotation_period)
186
+
187
+ fix_code = None
188
+ if not has_rotation:
189
+ fix_code = 'rotation_period = "7776000s" # 90 days'
190
+
191
+ return self._create_result(has_rotation, resource, fix_code)
192
+
193
+
194
+ class GCPServiceAccountKeyRule(Rule):
195
+ """Check that service account keys are managed properly."""
196
+
197
+ RULE_ID = "CKV_GCP_41"
198
+ TITLE = "Service account with user-managed keys"
199
+ SEVERITY = Severity.MEDIUM
200
+ DESCRIPTION = (
201
+ "Service account has user-managed keys. User-managed keys are a security "
202
+ "risk as they can be leaked or stolen. Prefer using attached service accounts."
203
+ )
204
+ REMEDIATION = (
205
+ "Use attached service accounts or workload identity instead of user-managed keys."
206
+ )
207
+ RESOURCE_TYPES = ["google_service_account_key"]
208
+
209
+ def evaluate(self, resource: IaCResource) -> RuleResult:
210
+ """Flag user-managed service account keys."""
211
+ # Any google_service_account_key resource is a user-managed key
212
+ fix_code = "# Remove user-managed keys and use workload identity or attached service accounts"
213
+
214
+ return self._create_result(False, resource, fix_code)
215
+
216
+
217
+ class GCPAuditLoggingRule(Rule):
218
+ """Check that audit logging is enabled for GCP projects."""
219
+
220
+ RULE_ID = "CKV_GCP_32"
221
+ TITLE = "Audit logging not enabled"
222
+ SEVERITY = Severity.MEDIUM
223
+ DESCRIPTION = (
224
+ "Audit logging is not enabled for all services. "
225
+ "Audit logs are essential for security monitoring and compliance."
226
+ )
227
+ REMEDIATION = (
228
+ "Enable audit logging for all services using google_project_iam_audit_config."
229
+ )
230
+ RESOURCE_TYPES = ["google_project_iam_audit_config"]
231
+
232
+ def evaluate(self, resource: IaCResource) -> RuleResult:
233
+ """Check if comprehensive audit logging is configured."""
234
+ service = resource.get_config("service", default="")
235
+ audit_log_configs = resource.get_config("audit_log_config", default=[])
236
+
237
+ # Check if auditing all services
238
+ if service == "allServices":
239
+ if isinstance(audit_log_configs, list) and len(audit_log_configs) > 0:
240
+ return self._create_result(True, resource)
241
+
242
+ fix_code = '''resource "google_project_iam_audit_config" "all" {
243
+ service = "allServices"
244
+ audit_log_config {
245
+ log_type = "ADMIN_READ"
246
+ }
247
+ audit_log_config {
248
+ log_type = "DATA_READ"
249
+ }
250
+ audit_log_config {
251
+ log_type = "DATA_WRITE"
252
+ }
253
+ }'''
254
+
255
+ return self._create_result(False, resource, fix_code)
@@ -0,0 +1,429 @@
1
+ """Kubernetes security rules for IaC scanning."""
2
+
3
+ from security_use.models import Severity
4
+ from security_use.iac.base import IaCResource
5
+ from security_use.iac.rules.base import Rule, RuleResult
6
+
7
+
8
+ class K8sRunAsRootRule(Rule):
9
+ """Check that containers don't run as root."""
10
+
11
+ RULE_ID = "CKV_K8S_6"
12
+ TITLE = "Container running as root"
13
+ SEVERITY = Severity.HIGH
14
+ DESCRIPTION = (
15
+ "Container is configured to run as root user. Running as root "
16
+ "increases the risk of container breakout and privilege escalation."
17
+ )
18
+ REMEDIATION = (
19
+ "Set securityContext.runAsNonRoot to true and specify a non-root runAsUser."
20
+ )
21
+ RESOURCE_TYPES = [
22
+ "kubernetes_pod",
23
+ "kubernetes_deployment",
24
+ "kubernetes_stateful_set",
25
+ "kubernetes_daemon_set",
26
+ "kubernetes_job",
27
+ "kubernetes_cron_job",
28
+ "Pod",
29
+ "Deployment",
30
+ "StatefulSet",
31
+ "DaemonSet",
32
+ "Job",
33
+ "CronJob",
34
+ ]
35
+
36
+ def evaluate(self, resource: IaCResource) -> RuleResult:
37
+ """Check if container runs as root."""
38
+ runs_as_root = False
39
+
40
+ # Get pod spec - handle different resource types
41
+ spec = self._get_pod_spec(resource)
42
+ if not spec:
43
+ return self._create_result(True, resource)
44
+
45
+ # Check pod-level security context
46
+ pod_security = spec.get("securityContext", {})
47
+ run_as_non_root = pod_security.get("runAsNonRoot", False)
48
+ run_as_user = pod_security.get("runAsUser")
49
+
50
+ # If pod-level says non-root, we're good
51
+ if run_as_non_root or (run_as_user is not None and run_as_user != 0):
52
+ return self._create_result(True, resource)
53
+
54
+ # Check container-level security context
55
+ containers = spec.get("containers", [])
56
+ for container in containers:
57
+ container_security = container.get("securityContext", {})
58
+ container_run_as_user = container_security.get("runAsUser")
59
+ container_run_as_non_root = container_security.get("runAsNonRoot", False)
60
+
61
+ if container_run_as_user == 0:
62
+ runs_as_root = True
63
+ break
64
+
65
+ if not container_run_as_non_root and run_as_user is None and container_run_as_user is None:
66
+ runs_as_root = True
67
+ break
68
+
69
+ fix_code = None
70
+ if runs_as_root:
71
+ fix_code = '''securityContext:
72
+ runAsNonRoot: true
73
+ runAsUser: 1000'''
74
+
75
+ return self._create_result(not runs_as_root, resource, fix_code)
76
+
77
+ def _get_pod_spec(self, resource: IaCResource) -> dict:
78
+ """Extract pod spec from various resource types."""
79
+ config = resource.config
80
+
81
+ # Direct pod
82
+ if "spec" in config and "containers" in config.get("spec", {}):
83
+ return config["spec"]
84
+
85
+ # Deployment/StatefulSet/etc with template
86
+ spec = config.get("spec", {})
87
+ template = spec.get("template", {})
88
+ if "spec" in template:
89
+ return template["spec"]
90
+
91
+ # Terraform kubernetes_pod
92
+ pod_spec = config.get("spec", [{}])
93
+ if isinstance(pod_spec, list) and pod_spec:
94
+ return pod_spec[0]
95
+
96
+ return {}
97
+
98
+
99
+ class K8sPrivilegedContainerRule(Rule):
100
+ """Check that containers don't run in privileged mode."""
101
+
102
+ RULE_ID = "CKV_K8S_1"
103
+ TITLE = "Privileged container"
104
+ SEVERITY = Severity.CRITICAL
105
+ DESCRIPTION = (
106
+ "Container is running in privileged mode. Privileged containers have "
107
+ "full access to the host and can escape container isolation."
108
+ )
109
+ REMEDIATION = (
110
+ "Set securityContext.privileged to false."
111
+ )
112
+ RESOURCE_TYPES = [
113
+ "kubernetes_pod",
114
+ "kubernetes_deployment",
115
+ "Pod",
116
+ "Deployment",
117
+ "StatefulSet",
118
+ "DaemonSet",
119
+ ]
120
+
121
+ def evaluate(self, resource: IaCResource) -> RuleResult:
122
+ """Check if container runs privileged."""
123
+ is_privileged = False
124
+
125
+ spec = self._get_pod_spec(resource)
126
+ if not spec:
127
+ return self._create_result(True, resource)
128
+
129
+ containers = spec.get("containers", [])
130
+ for container in containers:
131
+ security = container.get("securityContext", {})
132
+ if security.get("privileged", False):
133
+ is_privileged = True
134
+ break
135
+
136
+ fix_code = None
137
+ if is_privileged:
138
+ fix_code = '''securityContext:
139
+ privileged: false'''
140
+
141
+ return self._create_result(not is_privileged, resource, fix_code)
142
+
143
+ def _get_pod_spec(self, resource: IaCResource) -> dict:
144
+ """Extract pod spec from various resource types."""
145
+ config = resource.config
146
+ if "spec" in config and "containers" in config.get("spec", {}):
147
+ return config["spec"]
148
+ spec = config.get("spec", {})
149
+ template = spec.get("template", {})
150
+ if "spec" in template:
151
+ return template["spec"]
152
+ return {}
153
+
154
+
155
+ class K8sResourceLimitsRule(Rule):
156
+ """Check that containers have resource limits defined."""
157
+
158
+ RULE_ID = "CKV_K8S_11"
159
+ TITLE = "Container without resource limits"
160
+ SEVERITY = Severity.MEDIUM
161
+ DESCRIPTION = (
162
+ "Container does not have resource limits defined. Without limits, "
163
+ "a container can consume all available resources on the node."
164
+ )
165
+ REMEDIATION = (
166
+ "Define resources.limits for CPU and memory."
167
+ )
168
+ RESOURCE_TYPES = [
169
+ "kubernetes_pod",
170
+ "kubernetes_deployment",
171
+ "Pod",
172
+ "Deployment",
173
+ "StatefulSet",
174
+ "DaemonSet",
175
+ ]
176
+
177
+ def evaluate(self, resource: IaCResource) -> RuleResult:
178
+ """Check if container has resource limits."""
179
+ has_limits = True
180
+
181
+ spec = self._get_pod_spec(resource)
182
+ if not spec:
183
+ return self._create_result(True, resource)
184
+
185
+ containers = spec.get("containers", [])
186
+ for container in containers:
187
+ resources = container.get("resources", {})
188
+ limits = resources.get("limits", {})
189
+
190
+ if not limits.get("cpu") or not limits.get("memory"):
191
+ has_limits = False
192
+ break
193
+
194
+ fix_code = None
195
+ if not has_limits:
196
+ fix_code = '''resources:
197
+ limits:
198
+ cpu: "500m"
199
+ memory: "512Mi"
200
+ requests:
201
+ cpu: "100m"
202
+ memory: "128Mi"'''
203
+
204
+ return self._create_result(has_limits, resource, fix_code)
205
+
206
+ def _get_pod_spec(self, resource: IaCResource) -> dict:
207
+ """Extract pod spec from various resource types."""
208
+ config = resource.config
209
+ if "spec" in config and "containers" in config.get("spec", {}):
210
+ return config["spec"]
211
+ spec = config.get("spec", {})
212
+ template = spec.get("template", {})
213
+ if "spec" in template:
214
+ return template["spec"]
215
+ return {}
216
+
217
+
218
+ class K8sHostNetworkRule(Rule):
219
+ """Check that pods don't use host network namespace."""
220
+
221
+ RULE_ID = "CKV_K8S_19"
222
+ TITLE = "Pod using host network"
223
+ SEVERITY = Severity.HIGH
224
+ DESCRIPTION = (
225
+ "Pod is configured to use the host network namespace. This allows "
226
+ "the container to access all network interfaces on the host."
227
+ )
228
+ REMEDIATION = (
229
+ "Set hostNetwork to false unless absolutely necessary."
230
+ )
231
+ RESOURCE_TYPES = [
232
+ "kubernetes_pod",
233
+ "kubernetes_deployment",
234
+ "Pod",
235
+ "Deployment",
236
+ "StatefulSet",
237
+ "DaemonSet",
238
+ ]
239
+
240
+ def evaluate(self, resource: IaCResource) -> RuleResult:
241
+ """Check if pod uses host network."""
242
+ spec = self._get_pod_spec(resource)
243
+ if not spec:
244
+ return self._create_result(True, resource)
245
+
246
+ host_network = spec.get("hostNetwork", False)
247
+
248
+ fix_code = None
249
+ if host_network:
250
+ fix_code = "hostNetwork: false"
251
+
252
+ return self._create_result(not host_network, resource, fix_code)
253
+
254
+ def _get_pod_spec(self, resource: IaCResource) -> dict:
255
+ """Extract pod spec from various resource types."""
256
+ config = resource.config
257
+ if "spec" in config and "containers" in config.get("spec", {}):
258
+ return config["spec"]
259
+ spec = config.get("spec", {})
260
+ template = spec.get("template", {})
261
+ if "spec" in template:
262
+ return template["spec"]
263
+ return {}
264
+
265
+
266
+ class K8sSecretsEnvVarsRule(Rule):
267
+ """Check that secrets are not exposed as environment variables."""
268
+
269
+ RULE_ID = "CKV_K8S_35"
270
+ TITLE = "Secrets exposed as environment variables"
271
+ SEVERITY = Severity.MEDIUM
272
+ DESCRIPTION = (
273
+ "Secrets are exposed as environment variables. Environment variables "
274
+ "can be logged or exposed through process listings. Use volume mounts instead."
275
+ )
276
+ REMEDIATION = (
277
+ "Mount secrets as volumes instead of using envFrom with secretRef."
278
+ )
279
+ RESOURCE_TYPES = [
280
+ "kubernetes_pod",
281
+ "kubernetes_deployment",
282
+ "Pod",
283
+ "Deployment",
284
+ "StatefulSet",
285
+ "DaemonSet",
286
+ ]
287
+
288
+ def evaluate(self, resource: IaCResource) -> RuleResult:
289
+ """Check if secrets are exposed as env vars."""
290
+ secrets_in_env = False
291
+
292
+ spec = self._get_pod_spec(resource)
293
+ if not spec:
294
+ return self._create_result(True, resource)
295
+
296
+ containers = spec.get("containers", [])
297
+ for container in containers:
298
+ # Check envFrom with secretRef
299
+ env_from = container.get("envFrom", [])
300
+ for env in env_from:
301
+ if "secretRef" in env:
302
+ secrets_in_env = True
303
+ break
304
+
305
+ # Check individual env vars with secretKeyRef
306
+ env_vars = container.get("env", [])
307
+ for env in env_vars:
308
+ value_from = env.get("valueFrom", {})
309
+ if "secretKeyRef" in value_from:
310
+ secrets_in_env = True
311
+ break
312
+
313
+ if secrets_in_env:
314
+ break
315
+
316
+ fix_code = None
317
+ if secrets_in_env:
318
+ fix_code = '''# Mount secrets as volumes instead
319
+ volumeMounts:
320
+ - name: secret-volume
321
+ mountPath: "/etc/secrets"
322
+ readOnly: true
323
+ volumes:
324
+ - name: secret-volume
325
+ secret:
326
+ secretName: my-secret'''
327
+
328
+ return self._create_result(not secrets_in_env, resource, fix_code)
329
+
330
+ def _get_pod_spec(self, resource: IaCResource) -> dict:
331
+ """Extract pod spec from various resource types."""
332
+ config = resource.config
333
+ if "spec" in config and "containers" in config.get("spec", {}):
334
+ return config["spec"]
335
+ spec = config.get("spec", {})
336
+ template = spec.get("template", {})
337
+ if "spec" in template:
338
+ return template["spec"]
339
+ return {}
340
+
341
+
342
+ class K8sReadOnlyRootFilesystemRule(Rule):
343
+ """Check that containers use read-only root filesystem."""
344
+
345
+ RULE_ID = "CKV_K8S_22"
346
+ TITLE = "Container without read-only root filesystem"
347
+ SEVERITY = Severity.MEDIUM
348
+ DESCRIPTION = (
349
+ "Container does not have a read-only root filesystem. A read-only "
350
+ "filesystem prevents malicious writes to the container filesystem."
351
+ )
352
+ REMEDIATION = (
353
+ "Set securityContext.readOnlyRootFilesystem to true."
354
+ )
355
+ RESOURCE_TYPES = [
356
+ "kubernetes_pod",
357
+ "kubernetes_deployment",
358
+ "Pod",
359
+ "Deployment",
360
+ "StatefulSet",
361
+ "DaemonSet",
362
+ ]
363
+
364
+ def evaluate(self, resource: IaCResource) -> RuleResult:
365
+ """Check if container has read-only root filesystem."""
366
+ has_readonly = True
367
+
368
+ spec = self._get_pod_spec(resource)
369
+ if not spec:
370
+ return self._create_result(True, resource)
371
+
372
+ containers = spec.get("containers", [])
373
+ for container in containers:
374
+ security = container.get("securityContext", {})
375
+ if not security.get("readOnlyRootFilesystem", False):
376
+ has_readonly = False
377
+ break
378
+
379
+ fix_code = None
380
+ if not has_readonly:
381
+ fix_code = '''securityContext:
382
+ readOnlyRootFilesystem: true'''
383
+
384
+ return self._create_result(has_readonly, resource, fix_code)
385
+
386
+ def _get_pod_spec(self, resource: IaCResource) -> dict:
387
+ """Extract pod spec from various resource types."""
388
+ config = resource.config
389
+ if "spec" in config and "containers" in config.get("spec", {}):
390
+ return config["spec"]
391
+ spec = config.get("spec", {})
392
+ template = spec.get("template", {})
393
+ if "spec" in template:
394
+ return template["spec"]
395
+ return {}
396
+
397
+
398
+ class K8sNetworkPolicyRule(Rule):
399
+ """Check that namespaces have network policies defined."""
400
+
401
+ RULE_ID = "CKV_K8S_24"
402
+ TITLE = "Missing network policy"
403
+ SEVERITY = Severity.MEDIUM
404
+ DESCRIPTION = (
405
+ "No network policy is defined for this namespace. Without network "
406
+ "policies, all pods can communicate with each other by default."
407
+ )
408
+ REMEDIATION = (
409
+ "Define NetworkPolicy resources to restrict pod-to-pod communication."
410
+ )
411
+ RESOURCE_TYPES = ["kubernetes_namespace", "Namespace"]
412
+
413
+ def evaluate(self, resource: IaCResource) -> RuleResult:
414
+ """Flag namespaces that should have network policies."""
415
+ # This is a best-effort check - we can't verify NetworkPolicy exists
416
+ # from the namespace resource alone
417
+
418
+ fix_code = '''apiVersion: networking.k8s.io/v1
419
+ kind: NetworkPolicy
420
+ metadata:
421
+ name: default-deny-all
422
+ spec:
423
+ podSelector: {}
424
+ policyTypes:
425
+ - Ingress
426
+ - Egress'''
427
+
428
+ # Default to warning to encourage network policies
429
+ return self._create_result(False, resource, fix_code)