cloudsplaining 0.8.1__tar.gz → 0.9.0__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.1 → cloudsplaining-0.9.0}/PKG-INFO +67 -20
- cloudsplaining-0.8.1/cloudsplaining.egg-info/PKG-INFO → cloudsplaining-0.9.0/README.md +41 -29
- {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/__init__.py +1 -1
- cloudsplaining-0.9.0/cloudsplaining/bin/version.py +7 -0
- {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/command/create_exclusions_file.py +4 -4
- {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/command/create_multi_account_config_file.py +11 -12
- {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/command/download.py +8 -6
- {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/command/expand_policy.py +2 -1
- {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/command/scan.py +33 -32
- {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/command/scan_multi_account.py +14 -15
- {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/command/scan_policy_file.py +4 -3
- cloudsplaining-0.9.0/cloudsplaining/output/dist/fonts/bootstrap-icons.woff +0 -0
- cloudsplaining-0.9.0/cloudsplaining/output/dist/fonts/bootstrap-icons.woff2 +0 -0
- cloudsplaining-0.9.0/cloudsplaining/output/dist/index.html +56 -0
- cloudsplaining-0.9.0/cloudsplaining/output/dist/js/index.js +47 -0
- {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/output/policy_finding.py +14 -18
- cloudsplaining-0.9.0/cloudsplaining/output/public/index.html +26 -0
- cloudsplaining-0.9.0/cloudsplaining/output/report.py +90 -0
- cloudsplaining-0.9.0/cloudsplaining/output/src/App.vue +193 -0
- {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/output/src/components/InlinePolicies.vue +24 -1
- {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/output/src/components/LinkToFinding.vue +3 -8
- {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/output/src/components/ManagedPolicies.vue +25 -2
- {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/output/src/components/PolicyTable.vue +61 -12
- {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/output/src/components/Summary.vue +1 -1
- {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/output/src/components/TaskTable.vue +17 -13
- {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/output/src/components/charts/SummaryFindings.vue +3 -6
- {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/output/src/components/finding/AssumeRoleDetails.vue +25 -12
- {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/output/src/components/finding/PolicyDocumentDetails.vue +25 -14
- {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/output/src/components/finding/PrivilegeEscalationDetails.vue +28 -15
- {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/output/src/components/finding/PrivilegeEscalationFormat.vue +11 -1
- {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/output/src/components/finding/StandardRiskDetails.vue +32 -15
- cloudsplaining-0.9.0/cloudsplaining/output/src/main.js +24 -0
- cloudsplaining-0.9.0/cloudsplaining/output/src/routes/routes.js +59 -0
- cloudsplaining-0.9.0/cloudsplaining/output/src/sampleData.js +75096 -0
- {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/output/src/test/groups-test.js +11 -10
- {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/output/src/test/inline-policies-test.js +39 -34
- {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/output/src/test/managed-policies-test.js +49 -44
- {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/output/src/test/other-test.js +17 -16
- cloudsplaining-0.9.0/cloudsplaining/output/src/test/pathfinding-test.js +37 -0
- cloudsplaining-0.9.0/cloudsplaining/output/src/test/principals-test.js +80 -0
- {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/output/src/test/roles-test.js +8 -7
- {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/output/src/test/task-table-test.js +6 -5
- {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/output/src/util/inline-policies.js +1 -0
- {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/output/src/util/managed-policies.js +1 -0
- cloudsplaining-0.9.0/cloudsplaining/output/src/util/pathfinding-paths.json +61 -0
- cloudsplaining-0.9.0/cloudsplaining/output/src/util/pathfinding.js +18 -0
- cloudsplaining-0.9.0/cloudsplaining/output/src/views/Appendices.vue +19 -0
- cloudsplaining-0.9.0/cloudsplaining/output/src/views/AwsPolicies.vue +95 -0
- cloudsplaining-0.9.0/cloudsplaining/output/src/views/CustomerPolicies.vue +94 -0
- cloudsplaining-0.9.0/cloudsplaining/output/src/views/Guidance.vue +19 -0
- {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/output/src/views/IamPrincipals.vue +2 -4
- cloudsplaining-0.9.0/cloudsplaining/output/src/views/InlinePolicies.vue +93 -0
- {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/output/src/views/Summary.vue +0 -2
- {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/output/template.html +8 -19
- {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/scan/__init__.py +1 -1
- {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/scan/assume_role_policy_document.py +3 -9
- {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/scan/group_details.py +19 -29
- {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/scan/inline_policy.py +34 -30
- {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/scan/managed_policy_detail.py +56 -56
- {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/scan/policy_document.py +5 -2
- {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/scan/resource_policy_document.py +3 -3
- {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/scan/role_details.py +21 -28
- {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/scan/statement_detail.py +9 -9
- {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/scan/user_details.py +20 -29
- {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/shared/__init__.py +1 -1
- {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/shared/aws_login.py +4 -14
- cloudsplaining-0.9.0/cloudsplaining/shared/constants.py +360 -0
- {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/shared/exclusions.py +15 -22
- cloudsplaining-0.9.0/cloudsplaining/shared/template_config.py +40 -0
- {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/shared/utils.py +13 -15
- {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/shared/validation.py +7 -8
- cloudsplaining-0.9.0/pyproject.toml +166 -0
- cloudsplaining-0.9.0/test/command/test_create_multi_account_config_file.py +30 -0
- cloudsplaining-0.9.0/test/command/test_expand.py +42 -0
- cloudsplaining-0.9.0/test/command/test_expand_policy.py +34 -0
- cloudsplaining-0.9.0/test/command/test_scan.py +23 -0
- cloudsplaining-0.9.0/test/command/test_scan_multi_account.py +37 -0
- cloudsplaining-0.9.0/test/command/test_scan_policy_file.py +329 -0
- cloudsplaining-0.9.0/test/files/example-authz-details.json +4345 -0
- cloudsplaining-0.9.0/test/files/example_authz_details_for_overrides.json +52 -0
- cloudsplaining-0.9.0/test/files/example_authz_details_for_overrides_complete.json +241 -0
- cloudsplaining-0.9.0/test/files/example_authz_v2.json +202 -0
- cloudsplaining-0.9.0/test/files/managed_policy_mismatch.json +138 -0
- cloudsplaining-0.9.0/test/files/policy-overrides.json +13 -0
- cloudsplaining-0.9.0/test/files/scanning/test_authorization_file_details_missing_constraints_v2.json +220 -0
- cloudsplaining-0.9.0/test/files/scanning/test_group_detail_results.json +13 -0
- cloudsplaining-0.9.0/test/files/scanning/test_inline_policy_results.json +52 -0
- cloudsplaining-0.9.0/test/files/scanning/test_role_detail_results.json +79 -0
- cloudsplaining-0.9.0/test/files/scanning/test_user_detail_results.json +26 -0
- cloudsplaining-0.9.0/test/files/test-exclusions.yml +37 -0
- cloudsplaining-0.9.0/test/files/test_policy_file.json +34 -0
- cloudsplaining-0.9.0/test/output/test_policy_finding.py +244 -0
- cloudsplaining-0.9.0/test/scanning/test_action_links.py +85 -0
- cloudsplaining-0.9.0/test/scanning/test_authorization_details.py +146 -0
- cloudsplaining-0.9.0/test/scanning/test_exclusions_on_attached_policies.py +27 -0
- cloudsplaining-0.9.0/test/scanning/test_group_detail_list.py +72 -0
- cloudsplaining-0.9.0/test/scanning/test_inline_policy.py +52 -0
- cloudsplaining-0.9.0/test/scanning/test_managed_policy_detail.py +59 -0
- cloudsplaining-0.9.0/test/scanning/test_policy_document.py +588 -0
- cloudsplaining-0.9.0/test/scanning/test_privilege_escalation_methods.py +27 -0
- cloudsplaining-0.9.0/test/scanning/test_resource_policy_document.py +146 -0
- cloudsplaining-0.9.0/test/scanning/test_role_detail_list.py +43 -0
- cloudsplaining-0.9.0/test/scanning/test_statement_detail.py +292 -0
- cloudsplaining-0.9.0/test/scanning/test_trust_policies.py +458 -0
- cloudsplaining-0.9.0/test/scanning/test_user_detail_list.py +61 -0
- cloudsplaining-0.9.0/test/shared/test_aws_login.py +79 -0
- cloudsplaining-0.9.0/test/shared/test_exclusions.py +123 -0
- cloudsplaining-0.9.0/test/shared/test_pathfinding_mapping.py +50 -0
- cloudsplaining-0.9.0/test/shared/test_template_config.py +74 -0
- cloudsplaining-0.9.0/test/shared/test_utils.py +34 -0
- cloudsplaining-0.9.0/test/shared/test_validation.py +30 -0
- cloudsplaining-0.9.0/test/skills/test_iterate_pr_scripts.py +78 -0
- cloudsplaining-0.9.0/test/test_sample_data_in_sync.py +37 -0
- cloudsplaining-0.9.0/test/utils/test_build_example_dataset.py +90 -0
- cloudsplaining-0.9.0/test/utils/test_compare_example_reports.py +65 -0
- cloudsplaining-0.9.0/test/utils/test_safety_scan.py +66 -0
- cloudsplaining-0.8.1/MANIFEST.in +0 -7
- cloudsplaining-0.8.1/README.md +0 -387
- cloudsplaining-0.8.1/cloudsplaining/bin/version.py +0 -2
- cloudsplaining-0.8.1/cloudsplaining/output/dist/index.html +0 -6
- cloudsplaining-0.8.1/cloudsplaining/output/dist/js/index.js +0 -63
- cloudsplaining-0.8.1/cloudsplaining/output/public/index.html +0 -40
- cloudsplaining-0.8.1/cloudsplaining/output/report.py +0 -95
- cloudsplaining-0.8.1/cloudsplaining/output/src/App.vue +0 -174
- cloudsplaining-0.8.1/cloudsplaining/output/src/main.js +0 -16
- cloudsplaining-0.8.1/cloudsplaining/output/src/routes/routes.js +0 -52
- cloudsplaining-0.8.1/cloudsplaining/output/src/sampleData.js +0 -14594
- cloudsplaining-0.8.1/cloudsplaining/output/src/test/principals-test.js +0 -69
- cloudsplaining-0.8.1/cloudsplaining/output/src/views/Appendices.vue +0 -12
- cloudsplaining-0.8.1/cloudsplaining/output/src/views/AwsPolicies.vue +0 -50
- cloudsplaining-0.8.1/cloudsplaining/output/src/views/CustomerPolicies.vue +0 -49
- cloudsplaining-0.8.1/cloudsplaining/output/src/views/Guidance.vue +0 -12
- cloudsplaining-0.8.1/cloudsplaining/output/src/views/InlinePolicies.vue +0 -49
- cloudsplaining-0.8.1/cloudsplaining/shared/constants.py +0 -214
- cloudsplaining-0.8.1/cloudsplaining.egg-info/SOURCES.txt +0 -119
- cloudsplaining-0.8.1/cloudsplaining.egg-info/dependency_links.txt +0 -1
- cloudsplaining-0.8.1/cloudsplaining.egg-info/entry_points.txt +0 -3
- cloudsplaining-0.8.1/cloudsplaining.egg-info/requires.txt +0 -10
- cloudsplaining-0.8.1/cloudsplaining.egg-info/top_level.txt +0 -1
- cloudsplaining-0.8.1/cloudsplaining.egg-info/zip-safe +0 -1
- cloudsplaining-0.8.1/pyproject.toml +0 -82
- cloudsplaining-0.8.1/setup.cfg +0 -4
- cloudsplaining-0.8.1/setup.py +0 -72
- {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/LICENSE +0 -0
- {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/bin/__init__.py +0 -0
- {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/bin/cli.py +0 -0
- {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/command/__init__.py +0 -0
- {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/output/__init__.py +0 -0
- {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/output/dist/js/chunk-vendors.js +0 -0
- {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/output/src/assets/1-overview.md +0 -0
- {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/output/src/assets/2-triage-guidance.md +0 -0
- {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/output/src/assets/3-remediation-guidance.md +0 -0
- {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/output/src/assets/4-validation.md +0 -0
- {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/output/src/assets/definition-assumable-by-compute-service.md +0 -0
- {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/output/src/assets/definition-credentials-exposure.md +0 -0
- {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/output/src/assets/definition-data-exfiltration.md +0 -0
- {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/output/src/assets/definition-infrastructure-modification.md +0 -0
- {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/output/src/assets/definition-privilege-escalation.md +0 -0
- {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/output/src/assets/definition-resource-exposure.md +0 -0
- {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/output/src/assets/definition-service-wildcard.md +0 -0
- {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/output/src/assets/glossary.md +0 -0
- {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/output/src/assets/how-do-i-validate-results.md +0 -0
- {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/output/src/assets/identifying-false-positives.md +0 -0
- {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/output/src/assets/logo.png +0 -0
- {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/output/src/assets/summary.md +0 -0
- {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/output/src/assets/what-should-i-do.md +0 -0
- {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/output/src/components/Appendix.vue +0 -0
- {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/output/src/components/Button.vue +0 -0
- {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/output/src/components/Glossary.vue +0 -0
- {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/output/src/components/Guidance.vue +0 -0
- {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/output/src/components/Principals.vue +0 -0
- {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/output/src/components/ReportMetadata.vue +0 -0
- {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/output/src/components/finding/FindingCard.vue +0 -0
- {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/output/src/components/finding/FindingDetails.vue +0 -0
- {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/output/src/components/finding/RiskAlertIndicators.vue +0 -0
- {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/output/src/components/principals/PrincipalMetadata.vue +0 -0
- {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/output/src/components/principals/RisksPerPrincipal.vue +0 -0
- {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/output/src/util/glossary.js +0 -0
- {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/output/src/util/groups.js +0 -0
- {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/output/src/util/other.js +0 -0
- {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/output/src/util/principals.js +0 -0
- {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/output/src/util/roles.js +0 -0
- {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/output/src/util/task-table.js +0 -0
- {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/py.typed +0 -0
- {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/scan/authorization_details.py +0 -0
- {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/shared/default-exclusions.yml +0 -0
- {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/shared/exceptions.py +0 -0
- {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/shared/multi-account-config.yml +0 -0
|
@@ -1,29 +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.0
|
|
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
|
-
Classifier:
|
|
23
|
-
Classifier:
|
|
24
|
-
Requires-
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
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
|
|
25
34
|
Description-Content-Type: text/markdown
|
|
26
|
-
License-File: LICENSE
|
|
27
35
|
|
|
28
36
|
## NOTE: This repo/project has been restored by Salesforce.
|
|
29
37
|
|
|
@@ -359,6 +367,47 @@ cloudsplaining scan-multi-account \
|
|
|
359
367
|
|
|
360
368
|
> Note that if you run the above without the `--profile` flag, it will execute in the standard [AWS Credentials order of precedence](https://docs.aws.amazon.com/sdk-for-java/v1/developer-guide/credentials.html#credentials-default) (i.e., Environment variables, credentials profiles, ECS container credentials, then finally EC2 Instance Profile credentials).
|
|
361
369
|
|
|
370
|
+
## Custom Guidance and Appendices
|
|
371
|
+
|
|
372
|
+
Cloudsplaining supports customizing the Guidance and Appendices sections of the HTML report to include organization-specific security recommendations and documentation.
|
|
373
|
+
|
|
374
|
+
### How It Works
|
|
375
|
+
|
|
376
|
+
Place HTML files in your project root directory:
|
|
377
|
+
|
|
378
|
+
- `custom-guidance.html` - Custom security guidance content
|
|
379
|
+
- `custom-appendices.html` - Custom appendices content
|
|
380
|
+
|
|
381
|
+
### Behavior
|
|
382
|
+
|
|
383
|
+
- **Files don't exist**: Shows default AWS security advice
|
|
384
|
+
- **Files exist with content**: Shows your custom HTML content
|
|
385
|
+
- **Files exist but are empty**: Hides the tabs entirely
|
|
386
|
+
- **Mixed configuration**: Each tab works independently
|
|
387
|
+
|
|
388
|
+
### Example Usage
|
|
389
|
+
|
|
390
|
+
```bash
|
|
391
|
+
# Create custom guidance
|
|
392
|
+
echo '<h1>Company Security Guidelines</h1>
|
|
393
|
+
<p>Follow these organization-specific steps:</p>
|
|
394
|
+
<ul>
|
|
395
|
+
<li>Review with security team</li>
|
|
396
|
+
<li>Document in JIRA ticket</li>
|
|
397
|
+
<li>Get approval before remediation</li>
|
|
398
|
+
</ul>' > custom-guidance.html
|
|
399
|
+
|
|
400
|
+
# Create custom appendices
|
|
401
|
+
echo '<h1>Internal Resources</h1>
|
|
402
|
+
<p>Additional company resources:</p>
|
|
403
|
+
<ul>
|
|
404
|
+
<li><a href="https://internal.company.com/security">Security Portal</a></li>
|
|
405
|
+
<li><a href="https://wiki.company.com/iam">IAM Best Practices</a></li>
|
|
406
|
+
</ul>' > custom-appendices.html
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
# Generate report with custom content
|
|
410
|
+
cloudsplaining scan --input-file account-data.json --output reports/
|
|
362
411
|
|
|
363
412
|
## Cheatsheet
|
|
364
413
|
|
|
@@ -412,5 +461,3 @@ Try upgrading to the latest version of Cloudsplaining. This error was fixed in v
|
|
|
412
461
|
* [AWS Privilege Escalation Methods](https://github.com/RhinoSecurityLabs/AWS-IAM-Privilege-Escalation) by [Spencer Gietzen](https://twitter.com/SpenGietz) at Rhino Security Labs
|
|
413
462
|
* [Understanding Access Level Summaries within Policy Summaries](https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_understand-policy-summary-access-level-summaries.html)
|
|
414
463
|
* [Leveraging next-generation blockchain-based AI across multiple service meshes to transparently automate multi-cloud IAM wizardry :mage_man:](http://kmcquade.com/rick.html)
|
|
415
|
-
|
|
416
|
-
|
|
@@ -1,30 +1,3 @@
|
|
|
1
|
-
Metadata-Version: 2.1
|
|
2
|
-
Name: cloudsplaining
|
|
3
|
-
Version: 0.8.1
|
|
4
|
-
Summary: AWS IAM Security Assessment tool that identifies violations of least privilege and generates a risk-prioritized HTML report.
|
|
5
|
-
Home-page: https://github.com/salesforce/cloudsplaining
|
|
6
|
-
Author: Kinnaird McQuade
|
|
7
|
-
Author-email: kinnairdm@gmail.com
|
|
8
|
-
License: UNKNOWN
|
|
9
|
-
Project-URL: Documentation, https://policy-sentry.readthedocs.io/
|
|
10
|
-
Project-URL: Example Report, https://opensource.salesforce.com/cloudsplaining
|
|
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
|
|
16
|
-
Classifier: Programming Language :: Python :: 3 :: Only
|
|
17
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
18
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
-
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
-
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
-
Classifier: Programming Language :: Python :: 3.13
|
|
22
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
23
|
-
Classifier: Operating System :: OS Independent
|
|
24
|
-
Requires-Python: >=3.9
|
|
25
|
-
Description-Content-Type: text/markdown
|
|
26
|
-
License-File: LICENSE
|
|
27
|
-
|
|
28
1
|
## NOTE: This repo/project has been restored by Salesforce.
|
|
29
2
|
|
|
30
3
|
Cloudsplaining
|
|
@@ -359,6 +332,47 @@ cloudsplaining scan-multi-account \
|
|
|
359
332
|
|
|
360
333
|
> Note that if you run the above without the `--profile` flag, it will execute in the standard [AWS Credentials order of precedence](https://docs.aws.amazon.com/sdk-for-java/v1/developer-guide/credentials.html#credentials-default) (i.e., Environment variables, credentials profiles, ECS container credentials, then finally EC2 Instance Profile credentials).
|
|
361
334
|
|
|
335
|
+
## Custom Guidance and Appendices
|
|
336
|
+
|
|
337
|
+
Cloudsplaining supports customizing the Guidance and Appendices sections of the HTML report to include organization-specific security recommendations and documentation.
|
|
338
|
+
|
|
339
|
+
### How It Works
|
|
340
|
+
|
|
341
|
+
Place HTML files in your project root directory:
|
|
342
|
+
|
|
343
|
+
- `custom-guidance.html` - Custom security guidance content
|
|
344
|
+
- `custom-appendices.html` - Custom appendices content
|
|
345
|
+
|
|
346
|
+
### Behavior
|
|
347
|
+
|
|
348
|
+
- **Files don't exist**: Shows default AWS security advice
|
|
349
|
+
- **Files exist with content**: Shows your custom HTML content
|
|
350
|
+
- **Files exist but are empty**: Hides the tabs entirely
|
|
351
|
+
- **Mixed configuration**: Each tab works independently
|
|
352
|
+
|
|
353
|
+
### Example Usage
|
|
354
|
+
|
|
355
|
+
```bash
|
|
356
|
+
# Create custom guidance
|
|
357
|
+
echo '<h1>Company Security Guidelines</h1>
|
|
358
|
+
<p>Follow these organization-specific steps:</p>
|
|
359
|
+
<ul>
|
|
360
|
+
<li>Review with security team</li>
|
|
361
|
+
<li>Document in JIRA ticket</li>
|
|
362
|
+
<li>Get approval before remediation</li>
|
|
363
|
+
</ul>' > custom-guidance.html
|
|
364
|
+
|
|
365
|
+
# Create custom appendices
|
|
366
|
+
echo '<h1>Internal Resources</h1>
|
|
367
|
+
<p>Additional company resources:</p>
|
|
368
|
+
<ul>
|
|
369
|
+
<li><a href="https://internal.company.com/security">Security Portal</a></li>
|
|
370
|
+
<li><a href="https://wiki.company.com/iam">IAM Best Practices</a></li>
|
|
371
|
+
</ul>' > custom-appendices.html
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
# Generate report with custom content
|
|
375
|
+
cloudsplaining scan --input-file account-data.json --output reports/
|
|
362
376
|
|
|
363
377
|
## Cheatsheet
|
|
364
378
|
|
|
@@ -412,5 +426,3 @@ Try upgrading to the latest version of Cloudsplaining. This error was fixed in v
|
|
|
412
426
|
* [AWS Privilege Escalation Methods](https://github.com/RhinoSecurityLabs/AWS-IAM-Privilege-Escalation) by [Spencer Gietzen](https://twitter.com/SpenGietz) at Rhino Security Labs
|
|
413
427
|
* [Understanding Access Level Summaries within Policy Summaries](https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_understand-policy-summary-access-level-summaries.html)
|
|
414
428
|
* [Leveraging next-generation blockchain-based AI across multiple service meshes to transparently automate multi-cloud IAM wizardry :mage_man:](http://kmcquade.com/rick.html)
|
|
415
|
-
|
|
416
|
-
|
{cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/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:
|