security-use 0.1.1__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.
@@ -0,0 +1,29 @@
1
+ """IaC security rules."""
2
+
3
+ from security_use.iac.rules.base import Rule, RuleResult
4
+ from security_use.iac.rules.registry import RuleRegistry, get_registry
5
+ from security_use.iac.rules.aws import (
6
+ S3BucketEncryptionRule,
7
+ S3BucketPublicAccessRule,
8
+ SecurityGroupOpenIngressRule,
9
+ IAMUserMFARule,
10
+ RDSEncryptionRule,
11
+ EBSEncryptionRule,
12
+ CloudTrailEnabledRule,
13
+ VPCFlowLogsRule,
14
+ )
15
+
16
+ __all__ = [
17
+ "Rule",
18
+ "RuleResult",
19
+ "RuleRegistry",
20
+ "get_registry",
21
+ "S3BucketEncryptionRule",
22
+ "S3BucketPublicAccessRule",
23
+ "SecurityGroupOpenIngressRule",
24
+ "IAMUserMFARule",
25
+ "RDSEncryptionRule",
26
+ "EBSEncryptionRule",
27
+ "CloudTrailEnabledRule",
28
+ "VPCFlowLogsRule",
29
+ ]
@@ -0,0 +1,338 @@
1
+ """AWS 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 S3BucketEncryptionRule(Rule):
9
+ """Check that S3 buckets have server-side encryption enabled."""
10
+
11
+ RULE_ID = "CKV_AWS_19"
12
+ TITLE = "S3 bucket without encryption"
13
+ SEVERITY = Severity.HIGH
14
+ DESCRIPTION = (
15
+ "S3 bucket does not have server-side encryption enabled. "
16
+ "Data at rest should be encrypted to protect sensitive information."
17
+ )
18
+ REMEDIATION = (
19
+ "Enable server-side encryption using SSE-S3, SSE-KMS, or SSE-C. "
20
+ "Add a server_side_encryption_configuration block to the bucket."
21
+ )
22
+ RESOURCE_TYPES = ["aws_s3_bucket", "AWS::S3::Bucket"]
23
+
24
+ def evaluate(self, resource: IaCResource) -> RuleResult:
25
+ """Check if S3 bucket has encryption enabled."""
26
+ has_encryption = False
27
+
28
+ # Terraform: Check for server_side_encryption_configuration
29
+ if "server_side_encryption_configuration" in resource.config:
30
+ has_encryption = True
31
+
32
+ # CloudFormation: Check for BucketEncryption
33
+ if "BucketEncryption" in resource.config:
34
+ has_encryption = True
35
+
36
+ fix_code = None
37
+ if not has_encryption:
38
+ if resource.provider == "aws":
39
+ fix_code = '''server_side_encryption_configuration {
40
+ rule {
41
+ apply_server_side_encryption_by_default {
42
+ sse_algorithm = "AES256"
43
+ }
44
+ }
45
+ }'''
46
+
47
+ return self._create_result(has_encryption, resource, fix_code)
48
+
49
+
50
+ class S3BucketPublicAccessRule(Rule):
51
+ """Check that S3 buckets do not allow public access."""
52
+
53
+ RULE_ID = "CKV_AWS_20"
54
+ TITLE = "S3 bucket with public access"
55
+ SEVERITY = Severity.CRITICAL
56
+ DESCRIPTION = (
57
+ "S3 bucket allows public access. This can lead to data exposure "
58
+ "and security breaches."
59
+ )
60
+ REMEDIATION = (
61
+ "Set the bucket ACL to 'private' and enable block public access settings. "
62
+ "Remove any bucket policies that grant public access."
63
+ )
64
+ RESOURCE_TYPES = ["aws_s3_bucket", "AWS::S3::Bucket"]
65
+
66
+ PUBLIC_ACLS = ["public-read", "public-read-write", "authenticated-read"]
67
+
68
+ def evaluate(self, resource: IaCResource) -> RuleResult:
69
+ """Check if S3 bucket has public access."""
70
+ has_public_access = False
71
+
72
+ # Check ACL
73
+ acl = resource.get_config("acl", default="private")
74
+ if acl in self.PUBLIC_ACLS:
75
+ has_public_access = True
76
+
77
+ # CloudFormation: Check AccessControl
78
+ access_control = resource.get_config("AccessControl", default="Private")
79
+ if access_control in ["PublicRead", "PublicReadWrite", "AuthenticatedRead"]:
80
+ has_public_access = True
81
+
82
+ fix_code = None
83
+ if has_public_access:
84
+ fix_code = 'acl = "private"'
85
+
86
+ return self._create_result(not has_public_access, resource, fix_code)
87
+
88
+
89
+ class SecurityGroupOpenIngressRule(Rule):
90
+ """Check that security groups don't allow unrestricted ingress on sensitive ports."""
91
+
92
+ RULE_ID = "CKV_AWS_23"
93
+ TITLE = "Security group allows unrestricted ingress"
94
+ SEVERITY = Severity.HIGH
95
+ DESCRIPTION = (
96
+ "Security group allows ingress from 0.0.0.0/0 on sensitive ports. "
97
+ "This exposes services to the entire internet."
98
+ )
99
+ REMEDIATION = (
100
+ "Restrict ingress rules to specific IP ranges or security groups. "
101
+ "Avoid using 0.0.0.0/0 as the source CIDR."
102
+ )
103
+ RESOURCE_TYPES = ["aws_security_group", "AWS::EC2::SecurityGroup"]
104
+
105
+ SENSITIVE_PORTS = [22, 3389, 3306, 5432, 1433, 27017, 6379, 11211]
106
+
107
+ def evaluate(self, resource: IaCResource) -> RuleResult:
108
+ """Check if security group has open ingress on sensitive ports."""
109
+ has_open_ingress = False
110
+
111
+ # Terraform format
112
+ ingress_rules = resource.get_config("ingress", default=[])
113
+ if isinstance(ingress_rules, list):
114
+ for rule in ingress_rules:
115
+ if self._is_open_rule(rule):
116
+ has_open_ingress = True
117
+ break
118
+
119
+ # CloudFormation format
120
+ sg_ingress = resource.get_config("SecurityGroupIngress", default=[])
121
+ if isinstance(sg_ingress, list):
122
+ for rule in sg_ingress:
123
+ if self._is_open_cfn_rule(rule):
124
+ has_open_ingress = True
125
+ break
126
+
127
+ fix_code = None
128
+ if has_open_ingress:
129
+ fix_code = "# Restrict cidr_blocks 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
+ def _is_open_rule(self, rule: dict) -> bool:
134
+ """Check if a Terraform ingress rule is open to the world."""
135
+ cidr_blocks = rule.get("cidr_blocks", [])
136
+ if "0.0.0.0/0" not in cidr_blocks and "::/0" not in cidr_blocks:
137
+ return False
138
+
139
+ from_port = rule.get("from_port", 0)
140
+ to_port = rule.get("to_port", 65535)
141
+
142
+ # Check if any sensitive port is in the range
143
+ for port in self.SENSITIVE_PORTS:
144
+ if from_port <= port <= to_port:
145
+ return True
146
+
147
+ return False
148
+
149
+ def _is_open_cfn_rule(self, rule: dict) -> bool:
150
+ """Check if a CloudFormation ingress rule is open to the world."""
151
+ cidr = rule.get("CidrIp", "")
152
+ cidr_v6 = rule.get("CidrIpv6", "")
153
+ if cidr != "0.0.0.0/0" and cidr_v6 != "::/0":
154
+ return False
155
+
156
+ from_port = rule.get("FromPort", 0)
157
+ to_port = rule.get("ToPort", 65535)
158
+
159
+ for port in self.SENSITIVE_PORTS:
160
+ if from_port <= port <= to_port:
161
+ return True
162
+
163
+ return False
164
+
165
+
166
+ class IAMUserMFARule(Rule):
167
+ """Check that IAM users have MFA enabled."""
168
+
169
+ RULE_ID = "CKV_AWS_14"
170
+ TITLE = "IAM user without MFA"
171
+ SEVERITY = Severity.MEDIUM
172
+ DESCRIPTION = (
173
+ "IAM user does not have MFA enabled. MFA provides an additional "
174
+ "layer of security for user authentication."
175
+ )
176
+ REMEDIATION = (
177
+ "Enable MFA for all IAM users with console access. "
178
+ "Use virtual MFA devices or hardware tokens."
179
+ )
180
+ RESOURCE_TYPES = ["aws_iam_user", "AWS::IAM::User"]
181
+
182
+ def evaluate(self, resource: IaCResource) -> RuleResult:
183
+ """Check if IAM user has MFA configured."""
184
+ # For Terraform, we'd check for associated aws_iam_user_mfa_device
185
+ # For CloudFormation, MFA is typically configured separately
186
+ # This is a best-effort check
187
+
188
+ # If the user has login profile (console access), MFA should be required
189
+ has_login_profile = "login_profile" in resource.config or "LoginProfile" in resource.config
190
+
191
+ # We can't definitively check MFA from the resource alone
192
+ # Flag users with console access as needing review
193
+ if has_login_profile:
194
+ return self._create_result(
195
+ False,
196
+ resource,
197
+ fix_code="# Ensure MFA is configured for this user",
198
+ )
199
+
200
+ return self._create_result(True, resource)
201
+
202
+
203
+ class RDSEncryptionRule(Rule):
204
+ """Check that RDS instances have encryption enabled."""
205
+
206
+ RULE_ID = "CKV_AWS_16"
207
+ TITLE = "RDS instance without encryption"
208
+ SEVERITY = Severity.HIGH
209
+ DESCRIPTION = (
210
+ "RDS instance does not have encryption at rest enabled. "
211
+ "Database data should be encrypted to protect sensitive information."
212
+ )
213
+ REMEDIATION = (
214
+ "Enable encryption at rest for the RDS instance. "
215
+ "Set storage_encrypted = true (Terraform) or StorageEncrypted: true (CloudFormation)."
216
+ )
217
+ RESOURCE_TYPES = ["aws_db_instance", "AWS::RDS::DBInstance"]
218
+
219
+ def evaluate(self, resource: IaCResource) -> RuleResult:
220
+ """Check if RDS instance has encryption enabled."""
221
+ # Terraform
222
+ storage_encrypted = resource.get_config("storage_encrypted", default=False)
223
+
224
+ # CloudFormation
225
+ if not storage_encrypted:
226
+ storage_encrypted = resource.get_config("StorageEncrypted", default=False)
227
+
228
+ fix_code = None
229
+ if not storage_encrypted:
230
+ fix_code = "storage_encrypted = true"
231
+
232
+ return self._create_result(bool(storage_encrypted), resource, fix_code)
233
+
234
+
235
+ class EBSEncryptionRule(Rule):
236
+ """Check that EBS volumes have encryption enabled."""
237
+
238
+ RULE_ID = "CKV_AWS_3"
239
+ TITLE = "EBS volume without encryption"
240
+ SEVERITY = Severity.HIGH
241
+ DESCRIPTION = (
242
+ "EBS volume does not have encryption enabled. "
243
+ "Data on EBS volumes should be encrypted at rest."
244
+ )
245
+ REMEDIATION = (
246
+ "Enable encryption for the EBS volume. "
247
+ "Set encrypted = true (Terraform) or Encrypted: true (CloudFormation)."
248
+ )
249
+ RESOURCE_TYPES = ["aws_ebs_volume", "AWS::EC2::Volume"]
250
+
251
+ def evaluate(self, resource: IaCResource) -> RuleResult:
252
+ """Check if EBS volume has encryption enabled."""
253
+ # Terraform
254
+ encrypted = resource.get_config("encrypted", default=False)
255
+
256
+ # CloudFormation
257
+ if not encrypted:
258
+ encrypted = resource.get_config("Encrypted", default=False)
259
+
260
+ fix_code = None
261
+ if not encrypted:
262
+ fix_code = "encrypted = true"
263
+
264
+ return self._create_result(bool(encrypted), resource, fix_code)
265
+
266
+
267
+ class CloudTrailEnabledRule(Rule):
268
+ """Check that CloudTrail is properly configured."""
269
+
270
+ RULE_ID = "CKV_AWS_35"
271
+ TITLE = "CloudTrail not logging all events"
272
+ SEVERITY = Severity.MEDIUM
273
+ DESCRIPTION = (
274
+ "CloudTrail is not configured to log all management events. "
275
+ "Complete audit logging is essential for security monitoring."
276
+ )
277
+ REMEDIATION = (
278
+ "Enable CloudTrail with is_multi_region_trail = true and "
279
+ "include_global_service_events = true."
280
+ )
281
+ RESOURCE_TYPES = ["aws_cloudtrail", "AWS::CloudTrail::Trail"]
282
+
283
+ def evaluate(self, resource: IaCResource) -> RuleResult:
284
+ """Check if CloudTrail is properly configured."""
285
+ is_multi_region = resource.get_config("is_multi_region_trail", default=False)
286
+ include_global = resource.get_config("include_global_service_events", default=False)
287
+
288
+ # CloudFormation
289
+ if not is_multi_region:
290
+ is_multi_region = resource.get_config("IsMultiRegionTrail", default=False)
291
+ if not include_global:
292
+ include_global = resource.get_config("IncludeGlobalServiceEvents", default=False)
293
+
294
+ passed = is_multi_region and include_global
295
+
296
+ fix_code = None
297
+ if not passed:
298
+ fix_code = "is_multi_region_trail = true\ninclude_global_service_events = true"
299
+
300
+ return self._create_result(passed, resource, fix_code)
301
+
302
+
303
+ class VPCFlowLogsRule(Rule):
304
+ """Check that VPC flow logs are enabled."""
305
+
306
+ RULE_ID = "CKV_AWS_12"
307
+ TITLE = "VPC without flow logs"
308
+ SEVERITY = Severity.MEDIUM
309
+ DESCRIPTION = (
310
+ "VPC does not have flow logs enabled. Flow logs provide visibility "
311
+ "into network traffic for security analysis and troubleshooting."
312
+ )
313
+ REMEDIATION = (
314
+ "Enable VPC flow logs by creating an aws_flow_log resource "
315
+ "(Terraform) or AWS::EC2::FlowLog (CloudFormation)."
316
+ )
317
+ RESOURCE_TYPES = ["aws_vpc", "AWS::EC2::VPC"]
318
+
319
+ def evaluate(self, resource: IaCResource) -> RuleResult:
320
+ """Check if VPC has flow logs.
321
+
322
+ Note: This is a best-effort check. Flow logs are typically
323
+ defined as separate resources, so we flag VPCs for review.
324
+ """
325
+ # VPCs should have associated flow logs
326
+ # We can't verify this from the VPC resource alone
327
+ # Return a warning to encourage flow log configuration
328
+
329
+ fix_code = '''resource "aws_flow_log" "example" {
330
+ vpc_id = aws_vpc.example.id
331
+ traffic_type = "ALL"
332
+ log_destination_type = "cloud-watch-logs"
333
+ log_destination = aws_cloudwatch_log_group.example.arn
334
+ iam_role_arn = aws_iam_role.example.arn
335
+ }'''
336
+
337
+ # Default to warning (not passed) to encourage flow logs
338
+ return self._create_result(False, resource, fix_code)
@@ -0,0 +1,96 @@
1
+ """Base classes for IaC security rules."""
2
+
3
+ from abc import ABC, abstractmethod
4
+ from dataclasses import dataclass
5
+ from typing import Any, Optional
6
+
7
+ from security_use.models import Severity
8
+ from security_use.iac.base import IaCResource
9
+
10
+
11
+ @dataclass
12
+ class RuleResult:
13
+ """Result of applying a rule to a resource."""
14
+
15
+ passed: bool
16
+ rule_id: str
17
+ title: str
18
+ severity: Severity
19
+ resource_type: str
20
+ resource_name: str
21
+ description: str
22
+ remediation: str
23
+ fix_code: Optional[str] = None
24
+
25
+
26
+ class Rule(ABC):
27
+ """Abstract base class for security rules."""
28
+
29
+ # Rule metadata - override in subclasses
30
+ RULE_ID: str = "UNKNOWN"
31
+ TITLE: str = "Unknown Rule"
32
+ SEVERITY: Severity = Severity.MEDIUM
33
+ DESCRIPTION: str = ""
34
+ REMEDIATION: str = ""
35
+
36
+ # Resource types this rule applies to
37
+ RESOURCE_TYPES: list[str] = []
38
+
39
+ @abstractmethod
40
+ def evaluate(self, resource: IaCResource) -> RuleResult:
41
+ """Evaluate the rule against a resource.
42
+
43
+ Args:
44
+ resource: The IaC resource to evaluate.
45
+
46
+ Returns:
47
+ RuleResult indicating pass/fail and details.
48
+ """
49
+ pass
50
+
51
+ def applies_to(self, resource: IaCResource) -> bool:
52
+ """Check if this rule applies to the given resource.
53
+
54
+ Args:
55
+ resource: The IaC resource to check.
56
+
57
+ Returns:
58
+ True if the rule should be evaluated for this resource.
59
+ """
60
+ if not self.RESOURCE_TYPES:
61
+ return False
62
+
63
+ # Handle both Terraform and CloudFormation resource types
64
+ resource_type = resource.resource_type.lower()
65
+ for rule_type in self.RESOURCE_TYPES:
66
+ if rule_type.lower() in resource_type:
67
+ return True
68
+ return False
69
+
70
+ def _create_result(
71
+ self,
72
+ passed: bool,
73
+ resource: IaCResource,
74
+ fix_code: Optional[str] = None,
75
+ ) -> RuleResult:
76
+ """Create a RuleResult for this rule.
77
+
78
+ Args:
79
+ passed: Whether the rule passed.
80
+ resource: The evaluated resource.
81
+ fix_code: Optional suggested fix code.
82
+
83
+ Returns:
84
+ RuleResult with rule metadata.
85
+ """
86
+ return RuleResult(
87
+ passed=passed,
88
+ rule_id=self.RULE_ID,
89
+ title=self.TITLE,
90
+ severity=self.SEVERITY,
91
+ resource_type=resource.resource_type,
92
+ resource_name=resource.name,
93
+ description=self.DESCRIPTION,
94
+ remediation=self.REMEDIATION,
95
+ fix_code=fix_code,
96
+ )
@@ -0,0 +1,115 @@
1
+ """Rule registry for managing security rules."""
2
+
3
+ from typing import Optional, Type
4
+
5
+ from security_use.iac.rules.base import Rule
6
+
7
+
8
+ class RuleRegistry:
9
+ """Registry for IaC security rules."""
10
+
11
+ def __init__(self) -> None:
12
+ """Initialize the rule registry."""
13
+ self._rules: dict[str, Rule] = {}
14
+
15
+ def register(self, rule: Rule) -> None:
16
+ """Register a rule.
17
+
18
+ Args:
19
+ rule: Rule instance to register.
20
+ """
21
+ self._rules[rule.RULE_ID] = rule
22
+
23
+ def register_class(self, rule_class: Type[Rule]) -> None:
24
+ """Register a rule class (instantiates it).
25
+
26
+ Args:
27
+ rule_class: Rule class to register.
28
+ """
29
+ rule = rule_class()
30
+ self.register(rule)
31
+
32
+ def get(self, rule_id: str) -> Optional[Rule]:
33
+ """Get a rule by ID.
34
+
35
+ Args:
36
+ rule_id: The rule ID.
37
+
38
+ Returns:
39
+ Rule instance or None if not found.
40
+ """
41
+ return self._rules.get(rule_id)
42
+
43
+ def get_all(self) -> list[Rule]:
44
+ """Get all registered rules.
45
+
46
+ Returns:
47
+ List of all registered rules.
48
+ """
49
+ return list(self._rules.values())
50
+
51
+ def get_for_resource(self, resource_type: str) -> list[Rule]:
52
+ """Get rules that apply to a resource type.
53
+
54
+ Args:
55
+ resource_type: The resource type to match.
56
+
57
+ Returns:
58
+ List of applicable rules.
59
+ """
60
+ from security_use.iac.base import IaCResource
61
+
62
+ # Create a dummy resource to check applicability
63
+ dummy = IaCResource(
64
+ resource_type=resource_type,
65
+ name="",
66
+ config={},
67
+ file_path="",
68
+ line_number=0,
69
+ )
70
+
71
+ return [rule for rule in self._rules.values() if rule.applies_to(dummy)]
72
+
73
+ def clear(self) -> None:
74
+ """Clear all registered rules."""
75
+ self._rules.clear()
76
+
77
+
78
+ # Global registry instance
79
+ _registry: Optional[RuleRegistry] = None
80
+
81
+
82
+ def get_registry() -> RuleRegistry:
83
+ """Get the global rule registry.
84
+
85
+ Returns:
86
+ The global RuleRegistry instance.
87
+ """
88
+ global _registry
89
+ if _registry is None:
90
+ _registry = RuleRegistry()
91
+ _register_default_rules(_registry)
92
+ return _registry
93
+
94
+
95
+ def _register_default_rules(registry: RuleRegistry) -> None:
96
+ """Register all default rules."""
97
+ from security_use.iac.rules.aws import (
98
+ S3BucketEncryptionRule,
99
+ S3BucketPublicAccessRule,
100
+ SecurityGroupOpenIngressRule,
101
+ IAMUserMFARule,
102
+ RDSEncryptionRule,
103
+ EBSEncryptionRule,
104
+ CloudTrailEnabledRule,
105
+ VPCFlowLogsRule,
106
+ )
107
+
108
+ registry.register_class(S3BucketEncryptionRule)
109
+ registry.register_class(S3BucketPublicAccessRule)
110
+ registry.register_class(SecurityGroupOpenIngressRule)
111
+ registry.register_class(IAMUserMFARule)
112
+ registry.register_class(RDSEncryptionRule)
113
+ registry.register_class(EBSEncryptionRule)
114
+ registry.register_class(CloudTrailEnabledRule)
115
+ registry.register_class(VPCFlowLogsRule)