cloudsplaining 0.8.2__tar.gz → 0.9.1__tar.gz
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.
- {cloudsplaining-0.8.2/cloudsplaining.egg-info → cloudsplaining-0.9.1}/PKG-INFO +32 -28
- {cloudsplaining-0.8.2 → cloudsplaining-0.9.1}/README.md +7 -8
- {cloudsplaining-0.8.2 → cloudsplaining-0.9.1}/cloudsplaining/__init__.py +1 -1
- {cloudsplaining-0.8.2 → cloudsplaining-0.9.1}/cloudsplaining/bin/cli.py +11 -4
- cloudsplaining-0.9.1/cloudsplaining/bin/version.py +7 -0
- {cloudsplaining-0.8.2 → cloudsplaining-0.9.1}/cloudsplaining/command/create_exclusions_file.py +4 -4
- {cloudsplaining-0.8.2 → cloudsplaining-0.9.1}/cloudsplaining/command/create_multi_account_config_file.py +11 -12
- {cloudsplaining-0.8.2 → cloudsplaining-0.9.1}/cloudsplaining/command/download.py +8 -6
- {cloudsplaining-0.8.2 → cloudsplaining-0.9.1}/cloudsplaining/command/expand_policy.py +2 -1
- {cloudsplaining-0.8.2 → cloudsplaining-0.9.1}/cloudsplaining/command/scan.py +33 -32
- {cloudsplaining-0.8.2 → cloudsplaining-0.9.1}/cloudsplaining/command/scan_multi_account.py +14 -15
- {cloudsplaining-0.8.2 → cloudsplaining-0.9.1}/cloudsplaining/command/scan_policy_file.py +4 -3
- cloudsplaining-0.9.1/cloudsplaining/output/dist/fonts/bootstrap-icons.woff +0 -0
- cloudsplaining-0.9.1/cloudsplaining/output/dist/fonts/bootstrap-icons.woff2 +0 -0
- cloudsplaining-0.9.1/cloudsplaining/output/dist/index.html +56 -0
- cloudsplaining-0.9.1/cloudsplaining/output/dist/js/index.js +47 -0
- {cloudsplaining-0.8.2 → cloudsplaining-0.9.1}/cloudsplaining/output/policy_finding.py +14 -18
- cloudsplaining-0.9.1/cloudsplaining/output/public/index.html +26 -0
- cloudsplaining-0.9.1/cloudsplaining/output/report.py +90 -0
- {cloudsplaining-0.8.2 → cloudsplaining-0.9.1}/cloudsplaining/output/src/App.vue +14 -18
- {cloudsplaining-0.8.2 → cloudsplaining-0.9.1}/cloudsplaining/output/src/assets/2-triage-guidance.md +4 -4
- {cloudsplaining-0.8.2 → cloudsplaining-0.9.1}/cloudsplaining/output/src/components/InlinePolicies.vue +24 -1
- {cloudsplaining-0.8.2 → cloudsplaining-0.9.1}/cloudsplaining/output/src/components/LinkToFinding.vue +3 -8
- {cloudsplaining-0.8.2 → cloudsplaining-0.9.1}/cloudsplaining/output/src/components/ManagedPolicies.vue +25 -2
- {cloudsplaining-0.8.2 → cloudsplaining-0.9.1}/cloudsplaining/output/src/components/PolicyTable.vue +61 -12
- {cloudsplaining-0.8.2 → cloudsplaining-0.9.1}/cloudsplaining/output/src/components/Summary.vue +1 -1
- {cloudsplaining-0.8.2 → cloudsplaining-0.9.1}/cloudsplaining/output/src/components/TaskTable.vue +17 -13
- {cloudsplaining-0.8.2 → cloudsplaining-0.9.1}/cloudsplaining/output/src/components/charts/SummaryFindings.vue +3 -6
- {cloudsplaining-0.8.2 → cloudsplaining-0.9.1}/cloudsplaining/output/src/components/finding/AssumeRoleDetails.vue +25 -12
- {cloudsplaining-0.8.2 → cloudsplaining-0.9.1}/cloudsplaining/output/src/components/finding/PolicyDocumentDetails.vue +25 -14
- {cloudsplaining-0.8.2 → cloudsplaining-0.9.1}/cloudsplaining/output/src/components/finding/PrivilegeEscalationDetails.vue +28 -15
- {cloudsplaining-0.8.2 → cloudsplaining-0.9.1}/cloudsplaining/output/src/components/finding/PrivilegeEscalationFormat.vue +11 -1
- {cloudsplaining-0.8.2 → cloudsplaining-0.9.1}/cloudsplaining/output/src/components/finding/StandardRiskDetails.vue +32 -15
- cloudsplaining-0.9.1/cloudsplaining/output/src/main.js +24 -0
- {cloudsplaining-0.8.2 → cloudsplaining-0.9.1}/cloudsplaining/output/src/routes/routes.js +15 -18
- cloudsplaining-0.9.1/cloudsplaining/output/src/sampleData.js +75096 -0
- {cloudsplaining-0.8.2 → cloudsplaining-0.9.1}/cloudsplaining/output/src/test/groups-test.js +11 -10
- {cloudsplaining-0.8.2 → cloudsplaining-0.9.1}/cloudsplaining/output/src/test/inline-policies-test.js +39 -34
- {cloudsplaining-0.8.2 → cloudsplaining-0.9.1}/cloudsplaining/output/src/test/managed-policies-test.js +49 -44
- {cloudsplaining-0.8.2 → cloudsplaining-0.9.1}/cloudsplaining/output/src/test/other-test.js +17 -16
- cloudsplaining-0.9.1/cloudsplaining/output/src/test/pathfinding-test.js +37 -0
- cloudsplaining-0.9.1/cloudsplaining/output/src/test/principals-test.js +90 -0
- {cloudsplaining-0.8.2 → cloudsplaining-0.9.1}/cloudsplaining/output/src/test/roles-test.js +8 -7
- {cloudsplaining-0.8.2 → cloudsplaining-0.9.1}/cloudsplaining/output/src/test/task-table-test.js +6 -5
- {cloudsplaining-0.8.2 → cloudsplaining-0.9.1}/cloudsplaining/output/src/util/inline-policies.js +1 -0
- {cloudsplaining-0.8.2 → cloudsplaining-0.9.1}/cloudsplaining/output/src/util/managed-policies.js +1 -0
- cloudsplaining-0.9.1/cloudsplaining/output/src/util/pathfinding-paths.json +61 -0
- cloudsplaining-0.9.1/cloudsplaining/output/src/util/pathfinding.js +18 -0
- {cloudsplaining-0.8.2 → cloudsplaining-0.9.1}/cloudsplaining/output/src/util/principals.js +16 -6
- {cloudsplaining-0.8.2 → cloudsplaining-0.9.1}/cloudsplaining/output/src/views/Appendices.vue +0 -2
- cloudsplaining-0.9.1/cloudsplaining/output/src/views/AwsPolicies.vue +95 -0
- cloudsplaining-0.9.1/cloudsplaining/output/src/views/CustomerPolicies.vue +94 -0
- {cloudsplaining-0.8.2 → cloudsplaining-0.9.1}/cloudsplaining/output/src/views/Guidance.vue +0 -2
- {cloudsplaining-0.8.2 → cloudsplaining-0.9.1}/cloudsplaining/output/src/views/IamPrincipals.vue +2 -4
- cloudsplaining-0.9.1/cloudsplaining/output/src/views/InlinePolicies.vue +93 -0
- {cloudsplaining-0.8.2 → cloudsplaining-0.9.1}/cloudsplaining/output/src/views/Summary.vue +0 -2
- {cloudsplaining-0.8.2 → cloudsplaining-0.9.1}/cloudsplaining/output/template.html +0 -19
- {cloudsplaining-0.8.2 → cloudsplaining-0.9.1}/cloudsplaining/scan/__init__.py +1 -1
- {cloudsplaining-0.8.2 → cloudsplaining-0.9.1}/cloudsplaining/scan/assume_role_policy_document.py +3 -9
- {cloudsplaining-0.8.2 → cloudsplaining-0.9.1}/cloudsplaining/scan/group_details.py +19 -29
- {cloudsplaining-0.8.2 → cloudsplaining-0.9.1}/cloudsplaining/scan/inline_policy.py +34 -30
- {cloudsplaining-0.8.2 → cloudsplaining-0.9.1}/cloudsplaining/scan/managed_policy_detail.py +56 -56
- {cloudsplaining-0.8.2 → cloudsplaining-0.9.1}/cloudsplaining/scan/policy_document.py +5 -2
- {cloudsplaining-0.8.2 → cloudsplaining-0.9.1}/cloudsplaining/scan/resource_policy_document.py +3 -3
- {cloudsplaining-0.8.2 → cloudsplaining-0.9.1}/cloudsplaining/scan/role_details.py +20 -25
- {cloudsplaining-0.8.2 → cloudsplaining-0.9.1}/cloudsplaining/scan/statement_detail.py +9 -9
- {cloudsplaining-0.8.2 → cloudsplaining-0.9.1}/cloudsplaining/scan/user_details.py +20 -29
- {cloudsplaining-0.8.2 → cloudsplaining-0.9.1}/cloudsplaining/shared/__init__.py +1 -1
- {cloudsplaining-0.8.2 → cloudsplaining-0.9.1}/cloudsplaining/shared/aws_login.py +4 -14
- cloudsplaining-0.9.1/cloudsplaining/shared/constants.py +360 -0
- {cloudsplaining-0.8.2 → cloudsplaining-0.9.1}/cloudsplaining/shared/exclusions.py +42 -25
- {cloudsplaining-0.8.2 → cloudsplaining-0.9.1}/cloudsplaining/shared/template_config.py +4 -5
- {cloudsplaining-0.8.2 → cloudsplaining-0.9.1}/cloudsplaining/shared/utils.py +13 -15
- {cloudsplaining-0.8.2 → cloudsplaining-0.9.1}/cloudsplaining/shared/validation.py +7 -8
- cloudsplaining-0.9.1/pyproject.toml +166 -0
- cloudsplaining-0.9.1/test/command/test_create_multi_account_config_file.py +30 -0
- cloudsplaining-0.9.1/test/command/test_expand.py +42 -0
- cloudsplaining-0.9.1/test/command/test_expand_policy.py +34 -0
- cloudsplaining-0.9.1/test/command/test_scan.py +23 -0
- cloudsplaining-0.9.1/test/command/test_scan_multi_account.py +37 -0
- cloudsplaining-0.9.1/test/command/test_scan_policy_file.py +329 -0
- cloudsplaining-0.9.1/test/conftest.py +18 -0
- cloudsplaining-0.9.1/test/files/example-authz-details.json +4345 -0
- cloudsplaining-0.9.1/test/files/example_authz_details_for_overrides.json +52 -0
- cloudsplaining-0.9.1/test/files/example_authz_details_for_overrides_complete.json +241 -0
- cloudsplaining-0.9.1/test/files/example_authz_v2.json +202 -0
- cloudsplaining-0.9.1/test/files/managed_policy_mismatch.json +138 -0
- cloudsplaining-0.9.1/test/files/policy-overrides.json +13 -0
- cloudsplaining-0.9.1/test/files/scanning/test_authorization_file_details_missing_constraints_v2.json +220 -0
- cloudsplaining-0.9.1/test/files/scanning/test_group_detail_results.json +13 -0
- cloudsplaining-0.9.1/test/files/scanning/test_inline_policy_results.json +52 -0
- cloudsplaining-0.9.1/test/files/scanning/test_role_detail_results.json +79 -0
- cloudsplaining-0.9.1/test/files/scanning/test_user_detail_results.json +26 -0
- cloudsplaining-0.9.1/test/files/test-exclusions.yml +37 -0
- cloudsplaining-0.9.1/test/files/test_policy_file.json +34 -0
- cloudsplaining-0.9.1/test/output/test_policy_finding.py +244 -0
- cloudsplaining-0.9.1/test/scanning/test_action_links.py +85 -0
- cloudsplaining-0.9.1/test/scanning/test_authorization_details.py +146 -0
- cloudsplaining-0.9.1/test/scanning/test_exclusions_on_attached_policies.py +27 -0
- cloudsplaining-0.9.1/test/scanning/test_group_detail_list.py +72 -0
- cloudsplaining-0.9.1/test/scanning/test_inline_policy.py +52 -0
- cloudsplaining-0.9.1/test/scanning/test_managed_policy_detail.py +59 -0
- cloudsplaining-0.9.1/test/scanning/test_policy_document.py +588 -0
- cloudsplaining-0.9.1/test/scanning/test_privilege_escalation_methods.py +27 -0
- cloudsplaining-0.9.1/test/scanning/test_resource_policy_document.py +146 -0
- cloudsplaining-0.9.1/test/scanning/test_role_detail_list.py +43 -0
- cloudsplaining-0.9.1/test/scanning/test_statement_detail.py +292 -0
- cloudsplaining-0.9.1/test/scanning/test_trust_policies.py +458 -0
- cloudsplaining-0.9.1/test/scanning/test_user_detail_list.py +61 -0
- cloudsplaining-0.9.1/test/shared/test_aws_login.py +79 -0
- cloudsplaining-0.9.1/test/shared/test_exclusion_output.py +78 -0
- cloudsplaining-0.9.1/test/shared/test_exclusions.py +123 -0
- cloudsplaining-0.9.1/test/shared/test_pathfinding_mapping.py +50 -0
- cloudsplaining-0.9.1/test/shared/test_template_config.py +74 -0
- cloudsplaining-0.9.1/test/shared/test_utils.py +34 -0
- cloudsplaining-0.9.1/test/shared/test_validation.py +30 -0
- cloudsplaining-0.9.1/test/skills/test_iterate_pr_scripts.py +78 -0
- cloudsplaining-0.9.1/test/test_sample_data_in_sync.py +37 -0
- cloudsplaining-0.9.1/test/utils/test_build_example_dataset.py +90 -0
- cloudsplaining-0.9.1/test/utils/test_compare_example_reports.py +65 -0
- cloudsplaining-0.9.1/test/utils/test_safety_scan.py +66 -0
- cloudsplaining-0.8.2/MANIFEST.in +0 -7
- cloudsplaining-0.8.2/PKG-INFO +0 -458
- cloudsplaining-0.8.2/cloudsplaining/bin/version.py +0 -2
- cloudsplaining-0.8.2/cloudsplaining/output/dist/index.html +0 -6
- cloudsplaining-0.8.2/cloudsplaining/output/dist/js/index.js +0 -63
- cloudsplaining-0.8.2/cloudsplaining/output/public/index.html +0 -40
- cloudsplaining-0.8.2/cloudsplaining/output/report.py +0 -101
- cloudsplaining-0.8.2/cloudsplaining/output/src/main.js +0 -16
- cloudsplaining-0.8.2/cloudsplaining/output/src/sampleData.js +0 -14594
- cloudsplaining-0.8.2/cloudsplaining/output/src/test/principals-test.js +0 -69
- cloudsplaining-0.8.2/cloudsplaining/output/src/views/AwsPolicies.vue +0 -50
- cloudsplaining-0.8.2/cloudsplaining/output/src/views/CustomerPolicies.vue +0 -49
- cloudsplaining-0.8.2/cloudsplaining/output/src/views/InlinePolicies.vue +0 -49
- cloudsplaining-0.8.2/cloudsplaining/shared/constants.py +0 -214
- cloudsplaining-0.8.2/cloudsplaining.egg-info/SOURCES.txt +0 -120
- cloudsplaining-0.8.2/cloudsplaining.egg-info/dependency_links.txt +0 -1
- cloudsplaining-0.8.2/cloudsplaining.egg-info/entry_points.txt +0 -3
- cloudsplaining-0.8.2/cloudsplaining.egg-info/requires.txt +0 -10
- cloudsplaining-0.8.2/cloudsplaining.egg-info/top_level.txt +0 -1
- cloudsplaining-0.8.2/cloudsplaining.egg-info/zip-safe +0 -1
- cloudsplaining-0.8.2/pyproject.toml +0 -87
- cloudsplaining-0.8.2/setup.cfg +0 -4
- cloudsplaining-0.8.2/setup.py +0 -73
- {cloudsplaining-0.8.2 → cloudsplaining-0.9.1}/LICENSE +0 -0
- {cloudsplaining-0.8.2 → cloudsplaining-0.9.1}/cloudsplaining/bin/__init__.py +0 -0
- {cloudsplaining-0.8.2 → cloudsplaining-0.9.1}/cloudsplaining/command/__init__.py +0 -0
- {cloudsplaining-0.8.2 → cloudsplaining-0.9.1}/cloudsplaining/output/__init__.py +0 -0
- {cloudsplaining-0.8.2 → cloudsplaining-0.9.1}/cloudsplaining/output/dist/js/chunk-vendors.js +0 -0
- {cloudsplaining-0.8.2 → cloudsplaining-0.9.1}/cloudsplaining/output/src/assets/1-overview.md +0 -0
- {cloudsplaining-0.8.2 → cloudsplaining-0.9.1}/cloudsplaining/output/src/assets/3-remediation-guidance.md +0 -0
- {cloudsplaining-0.8.2 → cloudsplaining-0.9.1}/cloudsplaining/output/src/assets/4-validation.md +0 -0
- {cloudsplaining-0.8.2 → cloudsplaining-0.9.1}/cloudsplaining/output/src/assets/definition-assumable-by-compute-service.md +0 -0
- {cloudsplaining-0.8.2 → cloudsplaining-0.9.1}/cloudsplaining/output/src/assets/definition-credentials-exposure.md +0 -0
- {cloudsplaining-0.8.2 → cloudsplaining-0.9.1}/cloudsplaining/output/src/assets/definition-data-exfiltration.md +0 -0
- {cloudsplaining-0.8.2 → cloudsplaining-0.9.1}/cloudsplaining/output/src/assets/definition-infrastructure-modification.md +0 -0
- {cloudsplaining-0.8.2 → cloudsplaining-0.9.1}/cloudsplaining/output/src/assets/definition-privilege-escalation.md +0 -0
- {cloudsplaining-0.8.2 → cloudsplaining-0.9.1}/cloudsplaining/output/src/assets/definition-resource-exposure.md +0 -0
- {cloudsplaining-0.8.2 → cloudsplaining-0.9.1}/cloudsplaining/output/src/assets/definition-service-wildcard.md +0 -0
- {cloudsplaining-0.8.2 → cloudsplaining-0.9.1}/cloudsplaining/output/src/assets/glossary.md +0 -0
- {cloudsplaining-0.8.2 → cloudsplaining-0.9.1}/cloudsplaining/output/src/assets/how-do-i-validate-results.md +0 -0
- {cloudsplaining-0.8.2 → cloudsplaining-0.9.1}/cloudsplaining/output/src/assets/identifying-false-positives.md +0 -0
- {cloudsplaining-0.8.2 → cloudsplaining-0.9.1}/cloudsplaining/output/src/assets/logo.png +0 -0
- {cloudsplaining-0.8.2 → cloudsplaining-0.9.1}/cloudsplaining/output/src/assets/summary.md +0 -0
- {cloudsplaining-0.8.2 → cloudsplaining-0.9.1}/cloudsplaining/output/src/assets/what-should-i-do.md +0 -0
- {cloudsplaining-0.8.2 → cloudsplaining-0.9.1}/cloudsplaining/output/src/components/Appendix.vue +0 -0
- {cloudsplaining-0.8.2 → cloudsplaining-0.9.1}/cloudsplaining/output/src/components/Button.vue +0 -0
- {cloudsplaining-0.8.2 → cloudsplaining-0.9.1}/cloudsplaining/output/src/components/Glossary.vue +0 -0
- {cloudsplaining-0.8.2 → cloudsplaining-0.9.1}/cloudsplaining/output/src/components/Guidance.vue +0 -0
- {cloudsplaining-0.8.2 → cloudsplaining-0.9.1}/cloudsplaining/output/src/components/Principals.vue +0 -0
- {cloudsplaining-0.8.2 → cloudsplaining-0.9.1}/cloudsplaining/output/src/components/ReportMetadata.vue +0 -0
- {cloudsplaining-0.8.2 → cloudsplaining-0.9.1}/cloudsplaining/output/src/components/finding/FindingCard.vue +0 -0
- {cloudsplaining-0.8.2 → cloudsplaining-0.9.1}/cloudsplaining/output/src/components/finding/FindingDetails.vue +0 -0
- {cloudsplaining-0.8.2 → cloudsplaining-0.9.1}/cloudsplaining/output/src/components/finding/RiskAlertIndicators.vue +0 -0
- {cloudsplaining-0.8.2 → cloudsplaining-0.9.1}/cloudsplaining/output/src/components/principals/PrincipalMetadata.vue +0 -0
- {cloudsplaining-0.8.2 → cloudsplaining-0.9.1}/cloudsplaining/output/src/components/principals/RisksPerPrincipal.vue +0 -0
- {cloudsplaining-0.8.2 → cloudsplaining-0.9.1}/cloudsplaining/output/src/util/glossary.js +0 -0
- {cloudsplaining-0.8.2 → cloudsplaining-0.9.1}/cloudsplaining/output/src/util/groups.js +0 -0
- {cloudsplaining-0.8.2 → cloudsplaining-0.9.1}/cloudsplaining/output/src/util/other.js +0 -0
- {cloudsplaining-0.8.2 → cloudsplaining-0.9.1}/cloudsplaining/output/src/util/roles.js +0 -0
- {cloudsplaining-0.8.2 → cloudsplaining-0.9.1}/cloudsplaining/output/src/util/task-table.js +0 -0
- {cloudsplaining-0.8.2 → cloudsplaining-0.9.1}/cloudsplaining/py.typed +0 -0
- {cloudsplaining-0.8.2 → cloudsplaining-0.9.1}/cloudsplaining/scan/authorization_details.py +0 -0
- {cloudsplaining-0.8.2 → cloudsplaining-0.9.1}/cloudsplaining/shared/default-exclusions.yml +0 -0
- {cloudsplaining-0.8.2 → cloudsplaining-0.9.1}/cloudsplaining/shared/exceptions.py +0 -0
- {cloudsplaining-0.8.2 → cloudsplaining-0.9.1}/cloudsplaining/shared/multi-account-config.yml +0 -0
|
@@ -1,32 +1,37 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: cloudsplaining
|
|
3
|
-
Version: 0.
|
|
4
|
-
Summary: AWS IAM Security Assessment tool that identifies violations of least privilege and generates a risk-prioritized HTML report
|
|
5
|
-
|
|
3
|
+
Version: 0.9.1
|
|
4
|
+
Summary: AWS IAM Security Assessment tool that identifies violations of least privilege and generates a risk-prioritized HTML report
|
|
5
|
+
Keywords: aws,iam,roles,policy,policies,privileges,security
|
|
6
6
|
Author: Kinnaird McQuade
|
|
7
|
-
Author-email: kinnairdm@gmail.com
|
|
8
|
-
License:
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
Project-URL: Code, https://github.com/salesforce/cloudsplaining/
|
|
12
|
-
Project-URL: Twitter, https://twitter.com/kmcquade3
|
|
13
|
-
Project-URL: Red Team Report, https://opensource.salesforce.com/policy_sentry
|
|
14
|
-
Keywords: aws iam roles policy policies privileges security
|
|
15
|
-
Platform: UNKNOWN
|
|
7
|
+
Author-email: Kinnaird McQuade <kinnairdm@gmail.com>
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
16
11
|
Classifier: Programming Language :: Python :: 3 :: Only
|
|
17
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
18
12
|
Classifier: Programming Language :: Python :: 3.10
|
|
19
13
|
Classifier: Programming Language :: Python :: 3.11
|
|
20
14
|
Classifier: Programming Language :: Python :: 3.12
|
|
21
15
|
Classifier: Programming Language :: Python :: 3.13
|
|
22
16
|
Classifier: Programming Language :: Python :: 3.14
|
|
23
|
-
Classifier:
|
|
24
|
-
|
|
25
|
-
Requires-
|
|
17
|
+
Classifier: Typing :: Typed
|
|
18
|
+
Requires-Dist: boto3>=1.41.0,<2.0.0
|
|
19
|
+
Requires-Dist: botocore>=1.41.0,<2.0.0
|
|
20
|
+
Requires-Dist: click>=8.1.0,<9.0.0
|
|
21
|
+
Requires-Dist: click-option-group>=0.5.9,<0.6.0
|
|
22
|
+
Requires-Dist: jinja2>=3.1.0,<4.0.0
|
|
23
|
+
Requires-Dist: policy-sentry>=0.15.0,<0.16.0
|
|
24
|
+
Requires-Dist: pyyaml>=6.0.0,<7.0.0
|
|
25
|
+
Requires-Dist: schema>=0.7.0,<0.8.0
|
|
26
|
+
Requires-Python: >=3.10
|
|
27
|
+
Project-URL: Homepage, https://github.com/salesforce/cloudsplaining
|
|
28
|
+
Project-URL: Repository, https://github.com/salesforce/cloudsplaining
|
|
29
|
+
Project-URL: Code, https://github.com/salesforce/cloudsplaining
|
|
30
|
+
Project-URL: Documentation, https://cloudsplaining.readthedocs.io/
|
|
31
|
+
Project-URL: Example Report, https://opensource.salesforce.com/cloudsplaining
|
|
32
|
+
Project-URL: Red Team Report, https://opensource.salesforce.com/policy_sentry
|
|
33
|
+
Project-URL: Twitter, https://twitter.com/kmcquade3
|
|
26
34
|
Description-Content-Type: text/markdown
|
|
27
|
-
License-File: LICENSE
|
|
28
|
-
|
|
29
|
-
## NOTE: This repo/project has been restored by Salesforce.
|
|
30
35
|
|
|
31
36
|
Cloudsplaining
|
|
32
37
|
--------------
|
|
@@ -55,13 +60,14 @@ For full documentation, please visit the [project on ReadTheDocs](https://clouds
|
|
|
55
60
|
|
|
56
61
|
## Overview
|
|
57
62
|
|
|
58
|
-
Cloudsplaining identifies violations of least privilege in AWS IAM policies and generates a pretty HTML report
|
|
63
|
+
Cloudsplaining identifies violations of least privilege in AWS IAM policies and generates a pretty HTML report. It can scan all the policies in your AWS account, across multiple AWS accounts, or it can scan a single policy file.
|
|
59
64
|
|
|
60
65
|
It helps to identify IAM actions that do not leverage resource constraints. It also helps prioritize the remediation process by flagging IAM policies that present the following risks to the AWS account in question without restriction:
|
|
61
|
-
* Data Exfiltration (`s3:GetObject`, `ssm:GetParameter`, `secretsmanager:GetSecretValue`)
|
|
62
|
-
* Infrastructure Modification
|
|
63
|
-
* Resource Exposure (the ability to modify resource-based policies)
|
|
64
|
-
* Privilege Escalation (based on
|
|
66
|
+
* [Data Exfiltration](https://cloudsplaining.readthedocs.io/en/latest/glossary/data-exfiltration/) (`s3:GetObject`, `ssm:GetParameter`, `secretsmanager:GetSecretValue`)
|
|
67
|
+
* [Infrastructure Modification](https://cloudsplaining.readthedocs.io/en/latest/glossary/infrastructure-modification/)
|
|
68
|
+
* [Resource Exposure](https://cloudsplaining.readthedocs.io/en/latest/glossary/resource-exposure/) (the ability to modify resource-based policies)
|
|
69
|
+
* [Privilege Escalation](https://cloudsplaining.readthedocs.io/en/latest/glossary/privilege-escalation/) (based on Pathfinding.cloud)
|
|
70
|
+
* [Credentials Exposure](https://cloudsplaining.readthedocs.io/en/latest/glossary/credentials-exposure/)
|
|
65
71
|
|
|
66
72
|
Cloudsplaining also identifies IAM Roles that can be assumed by AWS Compute Services (such as EC2, ECS, EKS, or Lambda), as they can present greater risk than user-defined roles - especially if the AWS Compute service is on an instance that is directly or indirectly exposed to the internet. Flagging these roles is particularly useful to penetration testers (or attackers) under certain scenarios. For example, if an attacker obtains privileges to execute [ssm:SendCommand](https://docs.aws.amazon.com/systems-manager/latest/APIReference/API_SendCommand.html) and there are privileged EC2 instances with the SSM agent installed, they can effectively have the privileges of those EC2 instances. Remote Code Execution via AWS Systems Manager Agent was already a known escalation/exploitation path, but Cloudsplaining can make the process of identifying theses cases easier. See the [sample report](https://opensource.salesforce.com/cloudsplaining/#executive-summary) for some examples.
|
|
67
73
|
|
|
@@ -108,7 +114,7 @@ Policy Sentry [makes it really easy to do this](https://github.com/salesforce/po
|
|
|
108
114
|
|
|
109
115
|
That's why we wrote Cloudsplaining.
|
|
110
116
|
|
|
111
|
-
Cloudsplaining identifies violations of least privilege in AWS IAM policies and generates a pretty HTML report
|
|
117
|
+
Cloudsplaining identifies violations of least privilege in AWS IAM policies and generates a pretty HTML report. It can scan all the policies in your AWS account, across multiple AWS accounts, or it can scan a single policy file.
|
|
112
118
|
|
|
113
119
|
## Installation
|
|
114
120
|
|
|
@@ -454,5 +460,3 @@ Try upgrading to the latest version of Cloudsplaining. This error was fixed in v
|
|
|
454
460
|
* [AWS Privilege Escalation Methods](https://github.com/RhinoSecurityLabs/AWS-IAM-Privilege-Escalation) by [Spencer Gietzen](https://twitter.com/SpenGietz) at Rhino Security Labs
|
|
455
461
|
* [Understanding Access Level Summaries within Policy Summaries](https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_understand-policy-summary-access-level-summaries.html)
|
|
456
462
|
* [Leveraging next-generation blockchain-based AI across multiple service meshes to transparently automate multi-cloud IAM wizardry :mage_man:](http://kmcquade.com/rick.html)
|
|
457
|
-
|
|
458
|
-
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
## NOTE: This repo/project has been restored by Salesforce.
|
|
2
|
-
|
|
3
1
|
Cloudsplaining
|
|
4
2
|
--------------
|
|
5
3
|
|
|
@@ -27,13 +25,14 @@ For full documentation, please visit the [project on ReadTheDocs](https://clouds
|
|
|
27
25
|
|
|
28
26
|
## Overview
|
|
29
27
|
|
|
30
|
-
Cloudsplaining identifies violations of least privilege in AWS IAM policies and generates a pretty HTML report
|
|
28
|
+
Cloudsplaining identifies violations of least privilege in AWS IAM policies and generates a pretty HTML report. It can scan all the policies in your AWS account, across multiple AWS accounts, or it can scan a single policy file.
|
|
31
29
|
|
|
32
30
|
It helps to identify IAM actions that do not leverage resource constraints. It also helps prioritize the remediation process by flagging IAM policies that present the following risks to the AWS account in question without restriction:
|
|
33
|
-
* Data Exfiltration (`s3:GetObject`, `ssm:GetParameter`, `secretsmanager:GetSecretValue`)
|
|
34
|
-
* Infrastructure Modification
|
|
35
|
-
* Resource Exposure (the ability to modify resource-based policies)
|
|
36
|
-
* Privilege Escalation (based on
|
|
31
|
+
* [Data Exfiltration](https://cloudsplaining.readthedocs.io/en/latest/glossary/data-exfiltration/) (`s3:GetObject`, `ssm:GetParameter`, `secretsmanager:GetSecretValue`)
|
|
32
|
+
* [Infrastructure Modification](https://cloudsplaining.readthedocs.io/en/latest/glossary/infrastructure-modification/)
|
|
33
|
+
* [Resource Exposure](https://cloudsplaining.readthedocs.io/en/latest/glossary/resource-exposure/) (the ability to modify resource-based policies)
|
|
34
|
+
* [Privilege Escalation](https://cloudsplaining.readthedocs.io/en/latest/glossary/privilege-escalation/) (based on Pathfinding.cloud)
|
|
35
|
+
* [Credentials Exposure](https://cloudsplaining.readthedocs.io/en/latest/glossary/credentials-exposure/)
|
|
37
36
|
|
|
38
37
|
Cloudsplaining also identifies IAM Roles that can be assumed by AWS Compute Services (such as EC2, ECS, EKS, or Lambda), as they can present greater risk than user-defined roles - especially if the AWS Compute service is on an instance that is directly or indirectly exposed to the internet. Flagging these roles is particularly useful to penetration testers (or attackers) under certain scenarios. For example, if an attacker obtains privileges to execute [ssm:SendCommand](https://docs.aws.amazon.com/systems-manager/latest/APIReference/API_SendCommand.html) and there are privileged EC2 instances with the SSM agent installed, they can effectively have the privileges of those EC2 instances. Remote Code Execution via AWS Systems Manager Agent was already a known escalation/exploitation path, but Cloudsplaining can make the process of identifying theses cases easier. See the [sample report](https://opensource.salesforce.com/cloudsplaining/#executive-summary) for some examples.
|
|
39
38
|
|
|
@@ -80,7 +79,7 @@ Policy Sentry [makes it really easy to do this](https://github.com/salesforce/po
|
|
|
80
79
|
|
|
81
80
|
That's why we wrote Cloudsplaining.
|
|
82
81
|
|
|
83
|
-
Cloudsplaining identifies violations of least privilege in AWS IAM policies and generates a pretty HTML report
|
|
82
|
+
Cloudsplaining identifies violations of least privilege in AWS IAM policies and generates a pretty HTML report. It can scan all the policies in your AWS account, across multiple AWS accounts, or it can scan a single policy file.
|
|
84
83
|
|
|
85
84
|
## Installation
|
|
86
85
|
|
|
@@ -5,21 +5,28 @@
|
|
|
5
5
|
# For full license text, see the LICENSE file in the repo root
|
|
6
6
|
# or https://opensource.org/licenses/BSD-3-Clause
|
|
7
7
|
"""
|
|
8
|
-
Cloudsplaining is an AWS IAM Assessment tool that identifies violations of least privilege and generates a risk-prioritized HTML report
|
|
8
|
+
Cloudsplaining is an AWS IAM Assessment tool that identifies violations of least privilege and generates a risk-prioritized HTML report.
|
|
9
9
|
"""
|
|
10
10
|
|
|
11
11
|
import click
|
|
12
12
|
|
|
13
13
|
from cloudsplaining import command
|
|
14
14
|
from cloudsplaining.bin.version import __version__
|
|
15
|
+
from cloudsplaining.shared.exclusions import set_exclusion_output
|
|
15
16
|
|
|
16
17
|
|
|
17
18
|
@click.group()
|
|
18
19
|
@click.version_option(version=__version__)
|
|
19
|
-
|
|
20
|
+
@click.pass_context
|
|
21
|
+
def cloudsplaining(ctx: click.Context) -> None:
|
|
20
22
|
"""
|
|
21
|
-
Cloudsplaining is an AWS IAM Assessment tool that identifies violations of least privilege and generates a risk-prioritized HTML report
|
|
23
|
+
Cloudsplaining is an AWS IAM Assessment tool that identifies violations of least privilege and generates a risk-prioritized HTML report.
|
|
22
24
|
"""
|
|
25
|
+
# Surface exclusion-match messages on stdout for the CLI (historical behavior), then
|
|
26
|
+
# restore the prior value when the Click context tears down so an in-process CLI run
|
|
27
|
+
# does not leak printing state into later library use.
|
|
28
|
+
previous = set_exclusion_output(True)
|
|
29
|
+
ctx.call_on_close(lambda: set_exclusion_output(previous))
|
|
23
30
|
|
|
24
31
|
|
|
25
32
|
cloudsplaining.add_command(command.create_exclusions_file.create_exclusions_file)
|
|
@@ -32,7 +39,7 @@ cloudsplaining.add_command(command.download.download)
|
|
|
32
39
|
|
|
33
40
|
|
|
34
41
|
def main() -> None:
|
|
35
|
-
"""Cloudsplaining is an AWS IAM Assessment tool that identifies violations of least privilege and generates a risk-prioritized HTML report
|
|
42
|
+
"""Cloudsplaining is an AWS IAM Assessment tool that identifies violations of least privilege and generates a risk-prioritized HTML report."""
|
|
36
43
|
cloudsplaining()
|
|
37
44
|
|
|
38
45
|
|
{cloudsplaining-0.8.2 → cloudsplaining-0.9.1}/cloudsplaining/command/create_exclusions_file.py
RENAMED
|
@@ -9,7 +9,7 @@ This way, users don't have to remember exactly how to phrase the yaml files, sin
|
|
|
9
9
|
# For full license text, see the LICENSE file in the repo root
|
|
10
10
|
# or https://opensource.org/licenses/BSD-3-Clause
|
|
11
11
|
import logging
|
|
12
|
-
import
|
|
12
|
+
from pathlib import Path
|
|
13
13
|
|
|
14
14
|
import click
|
|
15
15
|
|
|
@@ -21,14 +21,14 @@ logger = logging.getLogger(__name__)
|
|
|
21
21
|
|
|
22
22
|
|
|
23
23
|
@click.command(
|
|
24
|
-
context_settings=
|
|
24
|
+
context_settings={"max_content_width": 160},
|
|
25
25
|
short_help="Creates a YML file to be used as a custom exclusions template",
|
|
26
26
|
)
|
|
27
27
|
@click.option(
|
|
28
28
|
"-o",
|
|
29
29
|
"--output-file",
|
|
30
30
|
type=click.Path(exists=False),
|
|
31
|
-
default=
|
|
31
|
+
default=str(Path.cwd() / "exclusions.yml"),
|
|
32
32
|
required=True,
|
|
33
33
|
help="Relative path to output file where we want to store the exclusions template.",
|
|
34
34
|
)
|
|
@@ -40,7 +40,7 @@ def create_exclusions_file(output_file: str, verbosity: int) -> None:
|
|
|
40
40
|
"""
|
|
41
41
|
set_log_level(verbosity)
|
|
42
42
|
|
|
43
|
-
with open(
|
|
43
|
+
with Path(output_file).open("a", encoding="utf-8") as file_obj:
|
|
44
44
|
file_obj.write(EXCLUSIONS_TEMPLATE)
|
|
45
45
|
|
|
46
46
|
utils.print_green(f"Success! Exclusions template file written to: {output_file}")
|
|
@@ -9,7 +9,7 @@ This way, users don't have to remember exactly how to phrase the yaml files, sin
|
|
|
9
9
|
# For full license text, see the LICENSE file in the repo root
|
|
10
10
|
# or https://opensource.org/licenses/BSD-3-Clause
|
|
11
11
|
import logging
|
|
12
|
-
import
|
|
12
|
+
from pathlib import Path
|
|
13
13
|
|
|
14
14
|
import click
|
|
15
15
|
|
|
@@ -23,7 +23,7 @@ END = "\033[0m"
|
|
|
23
23
|
|
|
24
24
|
|
|
25
25
|
@click.command(
|
|
26
|
-
context_settings=
|
|
26
|
+
context_settings={"max_content_width": 160},
|
|
27
27
|
short_help="Creates a YML file to be used for multi-account scanning",
|
|
28
28
|
)
|
|
29
29
|
@click.option(
|
|
@@ -31,7 +31,7 @@ END = "\033[0m"
|
|
|
31
31
|
"--output-file",
|
|
32
32
|
"output_file",
|
|
33
33
|
type=click.Path(exists=False),
|
|
34
|
-
default=
|
|
34
|
+
default=str(Path.cwd() / "multi-account-config.yml"),
|
|
35
35
|
required=True,
|
|
36
36
|
help="Relative path to output file where we want to store the multi account config template.",
|
|
37
37
|
)
|
|
@@ -42,17 +42,16 @@ def create_multi_account_config_file(output_file: str, verbosity: int) -> None:
|
|
|
42
42
|
"""
|
|
43
43
|
set_log_level(verbosity)
|
|
44
44
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
45
|
+
output_file_path = Path(output_file)
|
|
46
|
+
if output_file_path.exists():
|
|
47
|
+
logger.debug("%s exists. Removing the file and replacing its contents.", output_file_path)
|
|
48
|
+
output_file_path.unlink()
|
|
48
49
|
|
|
49
|
-
with open(
|
|
50
|
+
with output_file_path.open("a", encoding="utf-8") as file_obj:
|
|
50
51
|
file_obj.write(MULTI_ACCOUNT_CONFIG_TEMPLATE)
|
|
51
52
|
|
|
52
|
-
utils.print_green(f"Success! Multi-account config file written to: {
|
|
53
|
+
utils.print_green(f"Success! Multi-account config file written to: {output_file_path}")
|
|
53
54
|
print(
|
|
54
|
-
f"\nMake sure you edit the {
|
|
55
|
-
)
|
|
56
|
-
print(
|
|
57
|
-
f"\n\tcloudsplaining scan-multi-account --exclusions-file exclusions.yml -c {os.path.relpath(output_file)} -o ./"
|
|
55
|
+
f"\nMake sure you edit the {output_file_path} file and then run the scan-multi-account command, as shown below."
|
|
58
56
|
)
|
|
57
|
+
print(f"\n\tcloudsplaining scan-multi-account --exclusions-file exclusions.yml -c {output_file_path} -o ./")
|
|
@@ -11,6 +11,7 @@ from __future__ import annotations
|
|
|
11
11
|
import json
|
|
12
12
|
import logging
|
|
13
13
|
import os
|
|
14
|
+
from pathlib import Path
|
|
14
15
|
from typing import TYPE_CHECKING, Any
|
|
15
16
|
|
|
16
17
|
import boto3
|
|
@@ -20,7 +21,7 @@ from botocore.config import Config
|
|
|
20
21
|
from cloudsplaining import set_log_level
|
|
21
22
|
|
|
22
23
|
if TYPE_CHECKING:
|
|
23
|
-
from
|
|
24
|
+
from types_boto3_iam import IAMClient
|
|
24
25
|
|
|
25
26
|
logger = logging.getLogger(__name__)
|
|
26
27
|
|
|
@@ -41,7 +42,7 @@ logger = logging.getLogger(__name__)
|
|
|
41
42
|
"-o",
|
|
42
43
|
"--output",
|
|
43
44
|
type=click.Path(exists=True),
|
|
44
|
-
default=os.getcwd(),
|
|
45
|
+
default=os.getcwd(), # noqa: PTH109
|
|
45
46
|
help="Path to store the output. Defaults to current directory.",
|
|
46
47
|
)
|
|
47
48
|
@click.option(
|
|
@@ -61,14 +62,15 @@ def download(profile: str, output: str, include_non_default_policy_versions: boo
|
|
|
61
62
|
default_region = "us-east-1"
|
|
62
63
|
session_data = {"region_name": default_region}
|
|
63
64
|
|
|
65
|
+
output_path = Path(output)
|
|
64
66
|
if profile:
|
|
65
67
|
session_data["profile_name"] = profile
|
|
66
|
-
output_filename =
|
|
68
|
+
output_filename = output_path / f"{profile}.json"
|
|
67
69
|
else:
|
|
68
|
-
output_filename =
|
|
70
|
+
output_filename = output_path / "default.json"
|
|
69
71
|
|
|
70
72
|
results = get_account_authorization_details(session_data, include_non_default_policy_versions)
|
|
71
|
-
with open(
|
|
73
|
+
with output_filename.open("w", encoding="utf-8") as f:
|
|
72
74
|
json.dump(results, f, indent=4, default=str)
|
|
73
75
|
# output_filename.write_text(json.dumps(results, indent=4, default=str))
|
|
74
76
|
print(f"Saved results to {output_filename}")
|
|
@@ -79,7 +81,7 @@ def get_account_authorization_details(
|
|
|
79
81
|
session_data: dict[str, str], include_non_default_policy_versions: bool
|
|
80
82
|
) -> dict[str, list[Any]]:
|
|
81
83
|
"""Runs aws-iam-get-account-authorization-details"""
|
|
82
|
-
session = boto3.Session(**session_data) #
|
|
84
|
+
session = boto3.Session(**session_data) # ty: ignore[invalid-argument-type]
|
|
83
85
|
config = Config(connect_timeout=5, retries={"max_attempts": 10})
|
|
84
86
|
iam_client: IAMClient = session.client("iam", config=config)
|
|
85
87
|
|
|
@@ -9,6 +9,7 @@ Expands the wildcards (*) on an IAM policy file so it is easier for a human to u
|
|
|
9
9
|
# or https://opensource.org/licenses/BSD-3-Clause
|
|
10
10
|
import json
|
|
11
11
|
import logging
|
|
12
|
+
from pathlib import Path
|
|
12
13
|
|
|
13
14
|
import click
|
|
14
15
|
from policy_sentry.analysis.expand import get_expanded_policy
|
|
@@ -33,7 +34,7 @@ def expand_policy(input_file: str, verbosity: int) -> None:
|
|
|
33
34
|
"""
|
|
34
35
|
set_log_level(verbosity)
|
|
35
36
|
|
|
36
|
-
with open(
|
|
37
|
+
with Path(input_file).open(encoding="utf-8") as json_file:
|
|
37
38
|
logger.debug(f"Opening {input_file}")
|
|
38
39
|
data = json.load(json_file)
|
|
39
40
|
policy = get_expanded_policy(data)
|
|
@@ -47,14 +47,14 @@ from cloudsplaining.shared.validation import check_authorization_details_schema
|
|
|
47
47
|
help="A yaml file containing a list of policy names to exclude from the scan.",
|
|
48
48
|
type=click.Path(exists=True),
|
|
49
49
|
required=False,
|
|
50
|
-
default=EXCLUSIONS_FILE,
|
|
50
|
+
default=str(EXCLUSIONS_FILE),
|
|
51
51
|
)
|
|
52
52
|
@click.option(
|
|
53
53
|
"-o",
|
|
54
54
|
"--output",
|
|
55
55
|
required=False,
|
|
56
56
|
type=click.Path(exists=True),
|
|
57
|
-
default=os.getcwd(),
|
|
57
|
+
default=os.getcwd(), # noqa: PTH109
|
|
58
58
|
help="Output directory.",
|
|
59
59
|
)
|
|
60
60
|
@click.option(
|
|
@@ -123,7 +123,7 @@ def scan(
|
|
|
123
123
|
|
|
124
124
|
if exclusions_file:
|
|
125
125
|
# Get the exclusions configuration
|
|
126
|
-
with open(
|
|
126
|
+
with Path(exclusions_file).open(encoding="utf-8") as yaml_file:
|
|
127
127
|
try:
|
|
128
128
|
exclusions_cfg = yaml.safe_load(yaml_file)
|
|
129
129
|
except yaml.YAMLError as exc:
|
|
@@ -139,14 +139,16 @@ def scan(
|
|
|
139
139
|
flag_conditional_statements = False
|
|
140
140
|
flag_resource_arn_statements = False
|
|
141
141
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
142
|
+
output_path = Path(output)
|
|
143
|
+
input_file_path = Path(input_file)
|
|
144
|
+
if input_file_path.is_file():
|
|
145
|
+
account_name = input_file_path.stem
|
|
146
|
+
account_authorization_details_cfg = json.loads(input_file_path.read_text(encoding="utf-8"))
|
|
145
147
|
rendered_html_report = scan_account_authorization_details(
|
|
146
148
|
account_authorization_details_cfg,
|
|
147
149
|
exclusions,
|
|
148
150
|
account_name,
|
|
149
|
-
|
|
151
|
+
output_path,
|
|
150
152
|
write_data_files=True,
|
|
151
153
|
minimize=minimize,
|
|
152
154
|
flag_conditional_statements=flag_conditional_statements,
|
|
@@ -154,52 +156,52 @@ def scan(
|
|
|
154
156
|
flag_trust_policies=flag_trust_policies,
|
|
155
157
|
severity=severity,
|
|
156
158
|
)
|
|
157
|
-
html_output_file =
|
|
159
|
+
html_output_file = output_path / f"iam-report-{account_name}.html"
|
|
158
160
|
logger.info("Saving the report to %s", html_output_file)
|
|
159
|
-
if
|
|
160
|
-
|
|
161
|
+
if html_output_file.exists():
|
|
162
|
+
html_output_file.unlink()
|
|
161
163
|
|
|
162
|
-
|
|
164
|
+
html_output_file.write_text(rendered_html_report, encoding="utf-8")
|
|
163
165
|
|
|
164
166
|
print(f"Wrote HTML results to: {html_output_file}")
|
|
165
167
|
|
|
166
168
|
# Open the report by default
|
|
167
169
|
if not skip_open_report:
|
|
168
170
|
print("Opening the HTML report")
|
|
169
|
-
url = f"file://{
|
|
171
|
+
url = f"file://{html_output_file.absolute()}"
|
|
170
172
|
webbrowser.open(url, new=2)
|
|
171
173
|
|
|
172
|
-
if
|
|
174
|
+
if input_file_path.is_dir():
|
|
173
175
|
logger.info("The path given is a directory. Scanning for account authorization files and generating report.")
|
|
174
|
-
input_files = get_authorization_files_in_directory(
|
|
176
|
+
input_files = get_authorization_files_in_directory(input_file_path)
|
|
175
177
|
for file in input_files:
|
|
176
178
|
logger.info(f"Scanning file: {file}")
|
|
177
179
|
account_authorization_details_cfg = json.loads(Path(file).read_text(encoding="utf-8"))
|
|
178
180
|
|
|
179
|
-
account_name =
|
|
181
|
+
account_name = input_file_path.parent.stem
|
|
180
182
|
# Scan the Account Authorization Details config
|
|
181
183
|
rendered_html_report = scan_account_authorization_details(
|
|
182
184
|
account_authorization_details_cfg,
|
|
183
185
|
exclusions,
|
|
184
186
|
account_name,
|
|
185
|
-
|
|
187
|
+
output_path,
|
|
186
188
|
write_data_files=True,
|
|
187
189
|
minimize=minimize,
|
|
188
190
|
severity=severity,
|
|
189
191
|
)
|
|
190
|
-
html_output_file =
|
|
192
|
+
html_output_file = output_path / f"iam-report-{account_name}.html"
|
|
191
193
|
logger.info("Saving the report to %s", html_output_file)
|
|
192
|
-
if
|
|
193
|
-
|
|
194
|
+
if html_output_file.exists():
|
|
195
|
+
html_output_file.unlink()
|
|
194
196
|
|
|
195
|
-
|
|
197
|
+
html_output_file.write_text(rendered_html_report, encoding="utf-8")
|
|
196
198
|
|
|
197
199
|
print(f"Wrote HTML results to: {html_output_file}")
|
|
198
200
|
|
|
199
201
|
# Open the report by default
|
|
200
202
|
if not skip_open_report:
|
|
201
203
|
print("Opening the HTML report")
|
|
202
|
-
url = f"file://{
|
|
204
|
+
url = f"file://{html_output_file.absolute()}"
|
|
203
205
|
webbrowser.open(url, new=2)
|
|
204
206
|
|
|
205
207
|
|
|
@@ -211,7 +213,7 @@ def scan_account_authorization_details(
|
|
|
211
213
|
account_authorization_details_cfg: dict[str, Any],
|
|
212
214
|
exclusions: Exclusions,
|
|
213
215
|
account_name: str,
|
|
214
|
-
output_directory: str,
|
|
216
|
+
output_directory: str | Path | None,
|
|
215
217
|
write_data_files: bool,
|
|
216
218
|
minimize: bool,
|
|
217
219
|
return_json_results: Literal[True],
|
|
@@ -227,7 +229,7 @@ def scan_account_authorization_details(
|
|
|
227
229
|
account_authorization_details_cfg: dict[str, Any],
|
|
228
230
|
exclusions: Exclusions,
|
|
229
231
|
account_name: str = ...,
|
|
230
|
-
output_directory: str = ...,
|
|
232
|
+
output_directory: str | Path | None = ...,
|
|
231
233
|
write_data_files: bool = ...,
|
|
232
234
|
minimize: bool = ...,
|
|
233
235
|
return_json_results: Literal[False] = ...,
|
|
@@ -242,7 +244,7 @@ def scan_account_authorization_details(
|
|
|
242
244
|
account_authorization_details_cfg: dict[str, Any],
|
|
243
245
|
exclusions: Exclusions,
|
|
244
246
|
account_name: str = "default",
|
|
245
|
-
output_directory: str =
|
|
247
|
+
output_directory: str | Path | None = None,
|
|
246
248
|
write_data_files: bool = False,
|
|
247
249
|
minimize: bool = False,
|
|
248
250
|
return_json_results: bool = False,
|
|
@@ -285,14 +287,13 @@ def scan_account_authorization_details(
|
|
|
285
287
|
|
|
286
288
|
# Raw data file
|
|
287
289
|
if write_data_files:
|
|
288
|
-
if output_directory
|
|
289
|
-
output_directory = os.getcwd()
|
|
290
|
+
output_directory = Path(output_directory) if output_directory else Path.cwd()
|
|
290
291
|
|
|
291
|
-
results_data_file =
|
|
292
|
+
results_data_file = output_directory / f"iam-results-{account_name}.json"
|
|
292
293
|
results_data_filepath = write_results_data_file(authorization_details.results, results_data_file)
|
|
293
294
|
print(f"Results data saved: {results_data_filepath}")
|
|
294
295
|
|
|
295
|
-
findings_data_file =
|
|
296
|
+
findings_data_file = output_directory / f"iam-findings-{account_name}.json"
|
|
296
297
|
findings_data_filepath = write_results_data_file(results, findings_data_file)
|
|
297
298
|
print(f"Findings data file saved: {findings_data_filepath}")
|
|
298
299
|
|
|
@@ -302,15 +303,15 @@ def scan_account_authorization_details(
|
|
|
302
303
|
"iam_findings": results,
|
|
303
304
|
"rendered_report": rendered_report,
|
|
304
305
|
}
|
|
305
|
-
|
|
306
|
-
|
|
306
|
+
|
|
307
|
+
return rendered_report
|
|
307
308
|
|
|
308
309
|
|
|
309
310
|
def get_authorization_files_in_directory(
|
|
310
|
-
directory:
|
|
311
|
+
directory: Path,
|
|
311
312
|
) -> list[str]: # pragma: no cover
|
|
312
313
|
"""Get a list of download-account-authorization-files in a directory"""
|
|
313
|
-
file_list_with_full_path = [file.absolute() for file in
|
|
314
|
+
file_list_with_full_path = [file.absolute() for file in directory.glob("*.json")]
|
|
314
315
|
|
|
315
316
|
new_file_list = []
|
|
316
317
|
for file in file_list_with_full_path:
|
|
@@ -4,7 +4,7 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import json
|
|
6
6
|
import logging
|
|
7
|
-
import
|
|
7
|
+
from pathlib import Path
|
|
8
8
|
from typing import TYPE_CHECKING, Any, cast
|
|
9
9
|
|
|
10
10
|
import click
|
|
@@ -22,7 +22,7 @@ from cloudsplaining.shared.exclusions import DEFAULT_EXCLUSIONS, Exclusions
|
|
|
22
22
|
from cloudsplaining.shared.validation import check_authorization_details_schema
|
|
23
23
|
|
|
24
24
|
if TYPE_CHECKING:
|
|
25
|
-
from
|
|
25
|
+
from types_boto3_s3 import S3ServiceResource
|
|
26
26
|
|
|
27
27
|
logger = logging.getLogger(__name__)
|
|
28
28
|
OK_GREEN = "\033[92m"
|
|
@@ -77,7 +77,7 @@ class MultiAccountConfig:
|
|
|
77
77
|
help="A yaml file containing a list of policy names to exclude from the scan.",
|
|
78
78
|
type=click.Path(exists=True),
|
|
79
79
|
required=False,
|
|
80
|
-
default=EXCLUSIONS_FILE,
|
|
80
|
+
default=str(EXCLUSIONS_FILE),
|
|
81
81
|
)
|
|
82
82
|
@optgroup.group("Output Target Options", help="")
|
|
83
83
|
@optgroup.option(
|
|
@@ -145,7 +145,7 @@ def scan_multi_account(
|
|
|
145
145
|
set_log_level(verbosity)
|
|
146
146
|
|
|
147
147
|
# Read the config file from the user
|
|
148
|
-
with open(
|
|
148
|
+
with Path(config_file).open(encoding="utf-8") as yaml_file:
|
|
149
149
|
config = yaml.safe_load(yaml_file)
|
|
150
150
|
|
|
151
151
|
if flag_all_risky_actions:
|
|
@@ -217,24 +217,25 @@ def scan_accounts(
|
|
|
217
217
|
)
|
|
218
218
|
# Write the HTML file
|
|
219
219
|
output_file = f"{target_account_name}.html"
|
|
220
|
-
s3.Object(output_bucket, output_file).put(ACL="bucket-owner-full-control", Body=rendered_report)
|
|
220
|
+
s3.Object(output_bucket, output_file).put(ACL="bucket-owner-full-control", Body=rendered_report) # ty: ignore[unresolved-attribute]
|
|
221
221
|
utils.print_green(f"Saved the HTML report to: s3://{output_bucket}/{output_file}")
|
|
222
222
|
# Write the JSON data file
|
|
223
223
|
if write_data_file:
|
|
224
224
|
output_file = f"{target_account_name}.json"
|
|
225
225
|
body = json.dumps(results, sort_keys=True, default=str, indent=4)
|
|
226
|
-
s3.Object(output_bucket, output_file).put(ACL="bucket-owner-full-control", Body=body)
|
|
226
|
+
s3.Object(output_bucket, output_file).put(ACL="bucket-owner-full-control", Body=body) # ty: ignore[unresolved-attribute]
|
|
227
227
|
utils.print_green(f"Saved the JSON data to: s3://{output_bucket}/{output_file}")
|
|
228
228
|
if output_directory:
|
|
229
229
|
# Write the HTML file
|
|
230
|
-
|
|
230
|
+
output_dir_path = Path(output_directory)
|
|
231
|
+
html_output_file = output_dir_path / f"{target_account_name}.html"
|
|
231
232
|
utils.write_file(html_output_file, rendered_report)
|
|
232
|
-
utils.print_green(f"Saved the HTML report to: {
|
|
233
|
+
utils.print_green(f"Saved the HTML report to: {html_output_file}")
|
|
233
234
|
# Write the JSON data file
|
|
234
235
|
if write_data_file:
|
|
235
|
-
results_data_file =
|
|
236
|
+
results_data_file = output_dir_path / f"{target_account_name}.json"
|
|
236
237
|
results_data_filepath = utils.write_results_data_file(results, results_data_file)
|
|
237
|
-
utils.print_green(f"Saved the JSON data to: {
|
|
238
|
+
utils.print_green(f"Saved the JSON data to: {results_data_filepath}")
|
|
238
239
|
|
|
239
240
|
|
|
240
241
|
def scan_account(
|
|
@@ -262,8 +263,7 @@ def scan_account(
|
|
|
262
263
|
flag_resource_arn_statements=flag_resource_arn_statements,
|
|
263
264
|
flag_trust_policies=flag_trust_policies,
|
|
264
265
|
)
|
|
265
|
-
|
|
266
|
-
return results
|
|
266
|
+
return authorization_details.results
|
|
267
267
|
|
|
268
268
|
|
|
269
269
|
def download_account_authorization_details(
|
|
@@ -286,15 +286,14 @@ def download_account_authorization_details(
|
|
|
286
286
|
"aws_session_token": aws_session_token,
|
|
287
287
|
}
|
|
288
288
|
include_non_default_policy_versions = False
|
|
289
|
-
|
|
290
|
-
return authorization_details
|
|
289
|
+
return get_account_authorization_details(session_data, include_non_default_policy_versions)
|
|
291
290
|
|
|
292
291
|
|
|
293
292
|
def get_exclusions(exclusions_file: str | None = None) -> Exclusions:
|
|
294
293
|
"""Get the exclusions configuration from a file"""
|
|
295
294
|
# Get the exclusions configuration
|
|
296
295
|
if exclusions_file:
|
|
297
|
-
with open(
|
|
296
|
+
with Path(exclusions_file).open(encoding="utf-8") as yaml_file:
|
|
298
297
|
try:
|
|
299
298
|
exclusions_cfg = yaml.safe_load(yaml_file)
|
|
300
299
|
except yaml.YAMLError as exc:
|