sraverify 0.1.1__py3-none-any.whl → 0.1.3__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,123 @@
1
+ """
2
+ Check if organization has foundational OU - Workloads.
3
+ """
4
+ from typing import Dict, List, Any
5
+ from sraverify.services.organizations.base import OrganizationsCheck
6
+
7
+
8
+ class SRA_ORGANIZATIONS_04(OrganizationsCheck):
9
+ """Check if organization has foundational OU - Workloads."""
10
+
11
+ def __init__(self):
12
+ """Initialize Workloads OU check."""
13
+ super().__init__(resource_type="AWS::Organizations::OrganizationalUnit")
14
+ self.check_id = "SRA-ORGANIZATIONS-04"
15
+ self.check_name = "Organization has foundational OU - Workloads"
16
+ self.description = (
17
+ "This check verifies that the organization has a Workloads organizational unit (OU) "
18
+ "directly under the root. The Workloads OU is a foundational OU recommended by AWS SRA "
19
+ "for organizing application workload accounts including production and non-production environments."
20
+ )
21
+ self.severity = "MEDIUM"
22
+ self.check_logic = (
23
+ "Retrieve the organization root using ListRoots API, then list all OUs under the root "
24
+ "using ListOrganizationalUnitsForParent API. Check passes if an OU named 'Workloads' "
25
+ "exists directly under the root."
26
+ )
27
+
28
+ def execute(self) -> List[Dict[str, Any]]:
29
+ """
30
+ Execute the check.
31
+
32
+ Returns:
33
+ List of findings
34
+ """
35
+ # Organizations is a global service, use "global" as region
36
+ region = "global"
37
+
38
+ # Get organization roots
39
+ roots_response = self.get_roots()
40
+
41
+ # Check for errors getting roots
42
+ if "Error" in roots_response:
43
+ error_message = roots_response["Error"].get("Message", "Unknown error")
44
+ self.findings.append(self.create_finding(
45
+ status="ERROR",
46
+ region=region,
47
+ resource_id=None,
48
+ actual_value=f"Error: {error_message}",
49
+ remediation="Check IAM permissions for Organizations API access",
50
+ checked_value="Workloads OU exists under root"
51
+ ))
52
+ return self.findings
53
+
54
+ roots = roots_response.get("Roots", [])
55
+ if not roots:
56
+ self.findings.append(self.create_finding(
57
+ status="ERROR",
58
+ region=region,
59
+ resource_id=None,
60
+ actual_value="No organization root found",
61
+ remediation="Ensure AWS Organizations is enabled and properly configured",
62
+ checked_value="Workloads OU exists under root"
63
+ ))
64
+ return self.findings
65
+
66
+ # Get the first root (organizations have only one root)
67
+ root = roots[0]
68
+ root_id = root.get("Id", "")
69
+
70
+ # Get OUs under the root
71
+ ous_response = self.get_ous_for_parent(root_id)
72
+
73
+ # Check for errors getting OUs
74
+ if "Error" in ous_response:
75
+ error_message = ous_response["Error"].get("Message", "Unknown error")
76
+ self.findings.append(self.create_finding(
77
+ status="ERROR",
78
+ region=region,
79
+ resource_id=root_id,
80
+ actual_value=f"Error: {error_message}",
81
+ remediation="Check IAM permissions for Organizations API access",
82
+ checked_value="Workloads OU exists under root"
83
+ ))
84
+ return self.findings
85
+
86
+ ous = ous_response.get("OrganizationalUnits", [])
87
+
88
+ # Look for Workloads OU
89
+ workloads_ou = None
90
+ for ou in ous:
91
+ if ou.get("Name") == "Workloads":
92
+ workloads_ou = ou
93
+ break
94
+
95
+ if workloads_ou:
96
+ ou_id = workloads_ou.get("Id", "Unknown")
97
+ self.findings.append(self.create_finding(
98
+ status="PASS",
99
+ region=region,
100
+ resource_id=ou_id,
101
+ actual_value=f"Workloads OU exists: {ou_id}",
102
+ remediation="No remediation needed",
103
+ checked_value="Workloads OU exists under root"
104
+ ))
105
+ else:
106
+ # List existing OUs for context
107
+ existing_ous = [ou.get("Name", "Unknown") for ou in ous]
108
+ existing_ous_str = ", ".join(existing_ous) if existing_ous else "None"
109
+ self.findings.append(self.create_finding(
110
+ status="FAIL",
111
+ region=region,
112
+ resource_id=root_id,
113
+ actual_value=f"Workloads OU not found. Existing OUs under root: {existing_ous_str}",
114
+ remediation=(
115
+ "Create a Workloads organizational unit under the organization root. "
116
+ "Navigate to AWS Organizations in the console, select the root, and create "
117
+ "a new OU named 'Workloads'. Alternatively, use the AWS CLI: "
118
+ f"aws organizations create-organizational-unit --parent-id {root_id} --name Workloads"
119
+ ),
120
+ checked_value="Workloads OU exists under root"
121
+ ))
122
+
123
+ return self.findings
@@ -0,0 +1,92 @@
1
+ """
2
+ Check if organization has all features enabled.
3
+ """
4
+ from typing import Dict, List, Any
5
+ from sraverify.services.organizations.base import OrganizationsCheck
6
+
7
+
8
+ class SRA_ORGANIZATIONS_05(OrganizationsCheck):
9
+ """Check if organization has all features enabled."""
10
+
11
+ def __init__(self):
12
+ """Initialize all features enabled check."""
13
+ super().__init__(resource_type="AWS::Organizations::Organization")
14
+ self.check_id = "SRA-ORGANIZATIONS-05"
15
+ self.check_name = "Organization has all features enabled"
16
+ self.description = (
17
+ "This check verifies that the organization has all features enabled. "
18
+ "All features mode enables full governance capabilities including Service Control Policies (SCPs), "
19
+ "tag policies, backup policies, and AI services opt-out policies. Organizations with only "
20
+ "consolidated billing have limited governance capabilities."
21
+ )
22
+ self.severity = "HIGH"
23
+ self.check_logic = (
24
+ "Call DescribeOrganization API to retrieve organization details. "
25
+ "Check passes if FeatureSet equals 'ALL', fails if FeatureSet equals 'CONSOLIDATED_BILLING'."
26
+ )
27
+
28
+ def execute(self) -> List[Dict[str, Any]]:
29
+ """
30
+ Execute the check.
31
+
32
+ Returns:
33
+ List of findings
34
+ """
35
+ # Organizations is a global service, use "global" as region
36
+ region = "global"
37
+
38
+ # Get organization details
39
+ response = self.get_organization()
40
+
41
+ # Check for errors
42
+ if "Error" in response:
43
+ error_message = response["Error"].get("Message", "Unknown error")
44
+ self.findings.append(self.create_finding(
45
+ status="ERROR",
46
+ region=region,
47
+ resource_id=None,
48
+ actual_value=f"Error: {error_message}",
49
+ remediation="Check IAM permissions for Organizations API access",
50
+ checked_value="Organization FeatureSet"
51
+ ))
52
+ return self.findings
53
+
54
+ # Extract organization details
55
+ organization = response.get("Organization", {})
56
+ org_id = organization.get("Id", "Unknown")
57
+ feature_set = organization.get("FeatureSet", "Unknown")
58
+
59
+ if feature_set == "ALL":
60
+ self.findings.append(self.create_finding(
61
+ status="PASS",
62
+ region=region,
63
+ resource_id=org_id,
64
+ actual_value=f"FeatureSet: {feature_set}",
65
+ remediation="No remediation needed",
66
+ checked_value="Organization FeatureSet"
67
+ ))
68
+ elif feature_set == "CONSOLIDATED_BILLING":
69
+ self.findings.append(self.create_finding(
70
+ status="FAIL",
71
+ region=region,
72
+ resource_id=org_id,
73
+ actual_value=f"FeatureSet: {feature_set}",
74
+ remediation=(
75
+ "Enable all features in AWS Organizations to gain full governance capabilities. "
76
+ "Navigate to AWS Organizations in the console and enable all features. "
77
+ "Note: This requires consent from all member accounts. "
78
+ "See: https://docs.aws.amazon.com/organizations/latest/userguide/orgs_manage_org_support-all-features.html"
79
+ ),
80
+ checked_value="Organization FeatureSet"
81
+ ))
82
+ else:
83
+ self.findings.append(self.create_finding(
84
+ status="ERROR",
85
+ region=region,
86
+ resource_id=org_id,
87
+ actual_value=f"Unknown FeatureSet: {feature_set}",
88
+ remediation="Verify organization configuration",
89
+ checked_value="Organization FeatureSet"
90
+ ))
91
+
92
+ return self.findings
@@ -0,0 +1,125 @@
1
+ """
2
+ Check if organization has Service Control Policies configured.
3
+ """
4
+ from typing import Dict, List, Any
5
+ from sraverify.services.organizations.base import OrganizationsCheck
6
+
7
+
8
+ class SRA_ORGANIZATIONS_06(OrganizationsCheck):
9
+ """Check if organization has Service Control Policies configured."""
10
+
11
+ def __init__(self):
12
+ """Initialize SCP check."""
13
+ super().__init__(resource_type="AWS::Organizations::Policy")
14
+ self.check_id = "SRA-ORGANIZATIONS-06"
15
+ self.check_name = "Organization has Service Control Policies configured"
16
+ self.description = (
17
+ "This check verifies that the organization has at least one custom Service Control Policy (SCP) "
18
+ "configured beyond the default FullAWSAccess policy. SCPs are essential for implementing "
19
+ "permission guardrails across the organization to enforce security and compliance requirements."
20
+ )
21
+ self.severity = "HIGH"
22
+ self.check_logic = (
23
+ "Call ListPolicies API with filter for SERVICE_CONTROL_POLICY type. "
24
+ "Check passes if at least one custom SCP exists (AwsManaged=False), "
25
+ "fails if only the default FullAWSAccess policy exists or no SCPs are found."
26
+ )
27
+
28
+ def execute(self) -> List[Dict[str, Any]]:
29
+ """
30
+ Execute the check.
31
+
32
+ Returns:
33
+ List of findings
34
+ """
35
+ # Organizations is a global service, use "global" as region
36
+ region = "global"
37
+
38
+ # Get organization details for org_id
39
+ org_response = self.get_organization()
40
+ org_id = None
41
+ if "Organization" in org_response:
42
+ org_id = org_response["Organization"].get("Id", "Unknown")
43
+
44
+ # Get SCPs
45
+ response = self.list_policies("SERVICE_CONTROL_POLICY")
46
+
47
+ # Check for errors
48
+ if "Error" in response:
49
+ error_code = response["Error"].get("Code", "")
50
+ error_message = response["Error"].get("Message", "Unknown error")
51
+
52
+ # PolicyTypeNotEnabledException means SCPs are not enabled
53
+ if error_code == "PolicyTypeNotEnabledException":
54
+ self.findings.append(self.create_finding(
55
+ status="FAIL",
56
+ region=region,
57
+ resource_id=org_id,
58
+ actual_value="Service Control Policies are not enabled",
59
+ remediation=(
60
+ "Enable Service Control Policies in AWS Organizations. "
61
+ "Navigate to AWS Organizations > Policies > Service control policies and enable SCPs. "
62
+ "Then create custom SCPs to implement permission guardrails."
63
+ ),
64
+ checked_value="Custom SCPs configured"
65
+ ))
66
+ else:
67
+ self.findings.append(self.create_finding(
68
+ status="ERROR",
69
+ region=region,
70
+ resource_id=org_id,
71
+ actual_value=f"Error: {error_message}",
72
+ remediation="Check IAM permissions for Organizations API access",
73
+ checked_value="Custom SCPs configured"
74
+ ))
75
+ return self.findings
76
+
77
+ policies = response.get("Policies", [])
78
+
79
+ # Count custom SCPs (not AWS managed)
80
+ custom_scps = [p for p in policies if not p.get("AwsManaged", False)]
81
+ custom_scp_count = len(custom_scps)
82
+
83
+ if not policies:
84
+ # No SCPs at all - SCPs might not be enabled
85
+ self.findings.append(self.create_finding(
86
+ status="FAIL",
87
+ region=region,
88
+ resource_id=org_id,
89
+ actual_value="No Service Control Policies found",
90
+ remediation=(
91
+ "Enable Service Control Policies in AWS Organizations and create custom SCPs. "
92
+ "Navigate to AWS Organizations > Policies > Service control policies and enable SCPs. "
93
+ "Then create custom SCPs to implement permission guardrails."
94
+ ),
95
+ checked_value="Custom SCPs configured"
96
+ ))
97
+ elif custom_scp_count == 0:
98
+ # Only AWS managed policies (FullAWSAccess)
99
+ policy_names = [p.get("Name", "Unknown") for p in policies]
100
+ self.findings.append(self.create_finding(
101
+ status="FAIL",
102
+ region=region,
103
+ resource_id=org_id,
104
+ actual_value=f"Only default policies found: {', '.join(policy_names)}",
105
+ remediation=(
106
+ "Create custom Service Control Policies to implement permission guardrails. "
107
+ "Navigate to AWS Organizations > Policies > Service control policies and create new SCPs. "
108
+ "Consider implementing SCPs for: denying root user actions, restricting regions, "
109
+ "preventing disabling of security services, and enforcing encryption."
110
+ ),
111
+ checked_value="Custom SCPs configured"
112
+ ))
113
+ else:
114
+ # Custom SCPs exist
115
+ custom_scp_names = [p.get("Name", "Unknown") for p in custom_scps]
116
+ self.findings.append(self.create_finding(
117
+ status="PASS",
118
+ region=region,
119
+ resource_id=org_id,
120
+ actual_value=f"{custom_scp_count} custom SCP(s) configured: {', '.join(custom_scp_names)}",
121
+ remediation="No remediation needed",
122
+ checked_value="Custom SCPs configured"
123
+ ))
124
+
125
+ return self.findings
@@ -0,0 +1,128 @@
1
+ """
2
+ Check if organization has Resource Control Policies configured.
3
+ """
4
+ from typing import Dict, List, Any
5
+ from sraverify.services.organizations.base import OrganizationsCheck
6
+
7
+
8
+ class SRA_ORGANIZATIONS_07(OrganizationsCheck):
9
+ """Check if organization has Resource Control Policies configured."""
10
+
11
+ def __init__(self):
12
+ """Initialize RCP check."""
13
+ super().__init__(resource_type="AWS::Organizations::Policy")
14
+ self.check_id = "SRA-ORGANIZATIONS-07"
15
+ self.check_name = "Organization has Resource Control Policies configured"
16
+ self.description = (
17
+ "This check verifies that the organization has at least one custom Resource Control Policy (RCP) "
18
+ "configured beyond the default RCPFullAWSAccess policy. RCPs are a type of organization policy "
19
+ "that help you centrally establish data perimeter controls across AWS resources in your organization. "
20
+ "RCPs complement SCPs by controlling what resources can be accessed rather than what actions "
21
+ "principals can perform."
22
+ )
23
+ self.severity = "MEDIUM"
24
+ self.check_logic = (
25
+ "Call ListPolicies API with filter for RESOURCE_CONTROL_POLICY type. "
26
+ "Check passes if at least one custom RCP exists (AwsManaged=False), "
27
+ "fails if only the default RCPFullAWSAccess policy exists or no RCPs are found."
28
+ )
29
+
30
+ def execute(self) -> List[Dict[str, Any]]:
31
+ """
32
+ Execute the check.
33
+
34
+ Returns:
35
+ List of findings
36
+ """
37
+ # Organizations is a global service, use "global" as region
38
+ region = "global"
39
+
40
+ # Get organization details for org_id
41
+ org_response = self.get_organization()
42
+ org_id = None
43
+ if "Organization" in org_response:
44
+ org_id = org_response["Organization"].get("Id", "Unknown")
45
+
46
+ # Get RCPs
47
+ response = self.list_policies("RESOURCE_CONTROL_POLICY")
48
+
49
+ # Check for errors
50
+ if "Error" in response:
51
+ error_code = response["Error"].get("Code", "")
52
+ error_message = response["Error"].get("Message", "Unknown error")
53
+
54
+ # PolicyTypeNotEnabledException means RCPs are not enabled
55
+ if error_code == "PolicyTypeNotEnabledException":
56
+ self.findings.append(self.create_finding(
57
+ status="FAIL",
58
+ region=region,
59
+ resource_id=org_id,
60
+ actual_value="Resource Control Policies are not enabled",
61
+ remediation=(
62
+ "Enable Resource Control Policies in AWS Organizations. "
63
+ "Navigate to AWS Organizations > Policies > Resource control policies and enable RCPs. "
64
+ "Then create RCPs to implement data perimeter controls across your organization."
65
+ ),
66
+ checked_value="RCPs configured"
67
+ ))
68
+ else:
69
+ self.findings.append(self.create_finding(
70
+ status="ERROR",
71
+ region=region,
72
+ resource_id=org_id,
73
+ actual_value=f"Error: {error_message}",
74
+ remediation="Check IAM permissions for Organizations API access",
75
+ checked_value="RCPs configured"
76
+ ))
77
+ return self.findings
78
+
79
+ policies = response.get("Policies", [])
80
+
81
+ # Count custom RCPs (not AWS managed - exclude RCPFullAWSAccess)
82
+ custom_rcps = [p for p in policies if not p.get("AwsManaged", False)]
83
+ custom_rcp_count = len(custom_rcps)
84
+
85
+ if not policies:
86
+ # No RCPs at all - RCPs might not be enabled
87
+ self.findings.append(self.create_finding(
88
+ status="FAIL",
89
+ region=region,
90
+ resource_id=org_id,
91
+ actual_value="No Resource Control Policies found",
92
+ remediation=(
93
+ "Enable Resource Control Policies in AWS Organizations and create RCPs. "
94
+ "Navigate to AWS Organizations > Policies > Resource control policies and enable RCPs. "
95
+ "Then create RCPs to implement data perimeter controls such as restricting access "
96
+ "to resources based on organization membership or network location."
97
+ ),
98
+ checked_value="Custom RCPs configured"
99
+ ))
100
+ elif custom_rcp_count == 0:
101
+ # Only AWS managed policies (RCPFullAWSAccess)
102
+ policy_names = [p.get("Name", "Unknown") for p in policies]
103
+ self.findings.append(self.create_finding(
104
+ status="FAIL",
105
+ region=region,
106
+ resource_id=org_id,
107
+ actual_value=f"Only default policies found: {', '.join(policy_names)}",
108
+ remediation=(
109
+ "Create custom Resource Control Policies to implement data perimeter controls. "
110
+ "Navigate to AWS Organizations > Policies > Resource control policies and create new RCPs. "
111
+ "Consider implementing RCPs for: restricting access to resources based on organization "
112
+ "membership, enforcing HTTPS connections, and cross-service confused deputy protection."
113
+ ),
114
+ checked_value="Custom RCPs configured"
115
+ ))
116
+ else:
117
+ # Custom RCPs exist
118
+ custom_rcp_names = [p.get("Name", "Unknown") for p in custom_rcps]
119
+ self.findings.append(self.create_finding(
120
+ status="PASS",
121
+ region=region,
122
+ resource_id=org_id,
123
+ actual_value=f"{custom_rcp_count} custom RCP(s) configured: {', '.join(custom_rcp_names)}",
124
+ remediation="No remediation needed",
125
+ checked_value="Custom RCPs configured"
126
+ ))
127
+
128
+ return self.findings
@@ -0,0 +1,167 @@
1
+ """
2
+ Check if audit account is in Security OU.
3
+ """
4
+ from typing import Dict, List, Any
5
+ from sraverify.services.organizations.base import OrganizationsCheck
6
+
7
+
8
+ class SRA_ORGANIZATIONS_08(OrganizationsCheck):
9
+ """Check if audit account is in Security OU."""
10
+
11
+ def __init__(self):
12
+ """Initialize audit account in Security OU check."""
13
+ super().__init__(resource_type="AWS::Organizations::Account")
14
+ self.check_id = "SRA-ORGANIZATIONS-08"
15
+ self.check_name = "Audit account is in Security OU"
16
+ self.description = (
17
+ "This check verifies that the audit account (Security Tooling account) is located in the "
18
+ "Security organizational unit. According to AWS SRA best practices, the audit account should "
19
+ "be placed in the Security OU to ensure proper isolation and governance of security tooling."
20
+ )
21
+ self.severity = "HIGH"
22
+ self.check_logic = (
23
+ "Get the Security OU under the organization root, then list all accounts in the Security OU. "
24
+ "Check passes if the audit account (provided via --audit-account CLI parameter) is found in "
25
+ "the Security OU."
26
+ )
27
+ self._audit_accounts = [] # Will be populated from CLI --audit-account parameter
28
+
29
+ def execute(self) -> List[Dict[str, Any]]:
30
+ """
31
+ Execute the check.
32
+
33
+ Returns:
34
+ List of findings
35
+ """
36
+ # Organizations is a global service, use "global" as region
37
+ region = "global"
38
+
39
+ # Check if audit account is provided
40
+ if not self._audit_accounts:
41
+ self.findings.append(self.create_finding(
42
+ status="FAIL",
43
+ region=region,
44
+ resource_id=None,
45
+ actual_value="Audit account ID not provided",
46
+ remediation="Run check with --audit-account parameter to specify the audit account ID",
47
+ checked_value="Audit account in Security OU"
48
+ ))
49
+ return self.findings
50
+
51
+ # Get organization roots
52
+ roots_response = self.get_roots()
53
+ if "Error" in roots_response:
54
+ error_message = roots_response["Error"].get("Message", "Unknown error")
55
+ self.findings.append(self.create_finding(
56
+ status="ERROR",
57
+ region=region,
58
+ resource_id=None,
59
+ actual_value=f"Error: {error_message}",
60
+ remediation="Check IAM permissions for Organizations API access",
61
+ checked_value="Audit account in Security OU"
62
+ ))
63
+ return self.findings
64
+
65
+ roots = roots_response.get("Roots", [])
66
+ if not roots:
67
+ self.findings.append(self.create_finding(
68
+ status="ERROR",
69
+ region=region,
70
+ resource_id=None,
71
+ actual_value="No organization root found",
72
+ remediation="Ensure AWS Organizations is enabled and properly configured",
73
+ checked_value="Audit account in Security OU"
74
+ ))
75
+ return self.findings
76
+
77
+ root = roots[0]
78
+ root_id = root.get("Id", "")
79
+
80
+ # Get OUs under the root to find Security OU
81
+ ous_response = self.get_ous_for_parent(root_id)
82
+ if "Error" in ous_response:
83
+ error_message = ous_response["Error"].get("Message", "Unknown error")
84
+ self.findings.append(self.create_finding(
85
+ status="ERROR",
86
+ region=region,
87
+ resource_id=root_id,
88
+ actual_value=f"Error: {error_message}",
89
+ remediation="Check IAM permissions for Organizations API access",
90
+ checked_value="Audit account in Security OU"
91
+ ))
92
+ return self.findings
93
+
94
+ ous = ous_response.get("OrganizationalUnits", [])
95
+
96
+ # Find Security OU
97
+ security_ou = None
98
+ for ou in ous:
99
+ if ou.get("Name") == "Security":
100
+ security_ou = ou
101
+ break
102
+
103
+ if not security_ou:
104
+ self.findings.append(self.create_finding(
105
+ status="FAIL",
106
+ region=region,
107
+ resource_id=root_id,
108
+ actual_value="Security OU not found under organization root",
109
+ remediation=(
110
+ "Create a Security organizational unit under the organization root and move the "
111
+ "audit account into it. Navigate to AWS Organizations in the console, create the "
112
+ "Security OU, then move the audit account to the Security OU."
113
+ ),
114
+ checked_value="Audit account in Security OU"
115
+ ))
116
+ return self.findings
117
+
118
+ security_ou_id = security_ou.get("Id", "")
119
+
120
+ # Get accounts in Security OU
121
+ accounts_response = self.get_accounts_for_parent(security_ou_id)
122
+ if "Error" in accounts_response:
123
+ error_message = accounts_response["Error"].get("Message", "Unknown error")
124
+ self.findings.append(self.create_finding(
125
+ status="ERROR",
126
+ region=region,
127
+ resource_id=security_ou_id,
128
+ actual_value=f"Error: {error_message}",
129
+ remediation="Check IAM permissions for Organizations API access",
130
+ checked_value="Audit account in Security OU"
131
+ ))
132
+ return self.findings
133
+
134
+ accounts = accounts_response.get("Accounts", [])
135
+ account_ids_in_security_ou = [acc.get("Id") for acc in accounts]
136
+
137
+ # Check if audit account(s) are in Security OU
138
+ for audit_account_id in self._audit_accounts:
139
+ if audit_account_id in account_ids_in_security_ou:
140
+ # Find account name for better reporting
141
+ account_name = next(
142
+ (acc.get("Name", "Unknown") for acc in accounts if acc.get("Id") == audit_account_id),
143
+ "Unknown"
144
+ )
145
+ self.findings.append(self.create_finding(
146
+ status="PASS",
147
+ region=region,
148
+ resource_id=audit_account_id,
149
+ actual_value=f"Audit account {audit_account_id} ({account_name}) is in Security OU",
150
+ remediation="No remediation needed",
151
+ checked_value="Audit account in Security OU"
152
+ ))
153
+ else:
154
+ self.findings.append(self.create_finding(
155
+ status="FAIL",
156
+ region=region,
157
+ resource_id=audit_account_id,
158
+ actual_value=f"Audit account {audit_account_id} is not in Security OU",
159
+ remediation=(
160
+ f"Move the audit account {audit_account_id} to the Security OU. "
161
+ "Navigate to AWS Organizations in the console, select the audit account, "
162
+ "and move it to the Security OU."
163
+ ),
164
+ checked_value="Audit account in Security OU"
165
+ ))
166
+
167
+ return self.findings