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.
Files changed (188) hide show
  1. {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/PKG-INFO +67 -20
  2. cloudsplaining-0.8.1/cloudsplaining.egg-info/PKG-INFO → cloudsplaining-0.9.0/README.md +41 -29
  3. {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/__init__.py +1 -1
  4. cloudsplaining-0.9.0/cloudsplaining/bin/version.py +7 -0
  5. {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/command/create_exclusions_file.py +4 -4
  6. {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/command/create_multi_account_config_file.py +11 -12
  7. {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/command/download.py +8 -6
  8. {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/command/expand_policy.py +2 -1
  9. {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/command/scan.py +33 -32
  10. {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/command/scan_multi_account.py +14 -15
  11. {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/command/scan_policy_file.py +4 -3
  12. cloudsplaining-0.9.0/cloudsplaining/output/dist/fonts/bootstrap-icons.woff +0 -0
  13. cloudsplaining-0.9.0/cloudsplaining/output/dist/fonts/bootstrap-icons.woff2 +0 -0
  14. cloudsplaining-0.9.0/cloudsplaining/output/dist/index.html +56 -0
  15. cloudsplaining-0.9.0/cloudsplaining/output/dist/js/index.js +47 -0
  16. {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/output/policy_finding.py +14 -18
  17. cloudsplaining-0.9.0/cloudsplaining/output/public/index.html +26 -0
  18. cloudsplaining-0.9.0/cloudsplaining/output/report.py +90 -0
  19. cloudsplaining-0.9.0/cloudsplaining/output/src/App.vue +193 -0
  20. {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/output/src/components/InlinePolicies.vue +24 -1
  21. {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/output/src/components/LinkToFinding.vue +3 -8
  22. {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/output/src/components/ManagedPolicies.vue +25 -2
  23. {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/output/src/components/PolicyTable.vue +61 -12
  24. {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/output/src/components/Summary.vue +1 -1
  25. {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/output/src/components/TaskTable.vue +17 -13
  26. {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/output/src/components/charts/SummaryFindings.vue +3 -6
  27. {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/output/src/components/finding/AssumeRoleDetails.vue +25 -12
  28. {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/output/src/components/finding/PolicyDocumentDetails.vue +25 -14
  29. {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/output/src/components/finding/PrivilegeEscalationDetails.vue +28 -15
  30. {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/output/src/components/finding/PrivilegeEscalationFormat.vue +11 -1
  31. {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/output/src/components/finding/StandardRiskDetails.vue +32 -15
  32. cloudsplaining-0.9.0/cloudsplaining/output/src/main.js +24 -0
  33. cloudsplaining-0.9.0/cloudsplaining/output/src/routes/routes.js +59 -0
  34. cloudsplaining-0.9.0/cloudsplaining/output/src/sampleData.js +75096 -0
  35. {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/output/src/test/groups-test.js +11 -10
  36. {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/output/src/test/inline-policies-test.js +39 -34
  37. {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/output/src/test/managed-policies-test.js +49 -44
  38. {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/output/src/test/other-test.js +17 -16
  39. cloudsplaining-0.9.0/cloudsplaining/output/src/test/pathfinding-test.js +37 -0
  40. cloudsplaining-0.9.0/cloudsplaining/output/src/test/principals-test.js +80 -0
  41. {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/output/src/test/roles-test.js +8 -7
  42. {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/output/src/test/task-table-test.js +6 -5
  43. {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/output/src/util/inline-policies.js +1 -0
  44. {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/output/src/util/managed-policies.js +1 -0
  45. cloudsplaining-0.9.0/cloudsplaining/output/src/util/pathfinding-paths.json +61 -0
  46. cloudsplaining-0.9.0/cloudsplaining/output/src/util/pathfinding.js +18 -0
  47. cloudsplaining-0.9.0/cloudsplaining/output/src/views/Appendices.vue +19 -0
  48. cloudsplaining-0.9.0/cloudsplaining/output/src/views/AwsPolicies.vue +95 -0
  49. cloudsplaining-0.9.0/cloudsplaining/output/src/views/CustomerPolicies.vue +94 -0
  50. cloudsplaining-0.9.0/cloudsplaining/output/src/views/Guidance.vue +19 -0
  51. {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/output/src/views/IamPrincipals.vue +2 -4
  52. cloudsplaining-0.9.0/cloudsplaining/output/src/views/InlinePolicies.vue +93 -0
  53. {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/output/src/views/Summary.vue +0 -2
  54. {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/output/template.html +8 -19
  55. {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/scan/__init__.py +1 -1
  56. {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/scan/assume_role_policy_document.py +3 -9
  57. {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/scan/group_details.py +19 -29
  58. {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/scan/inline_policy.py +34 -30
  59. {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/scan/managed_policy_detail.py +56 -56
  60. {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/scan/policy_document.py +5 -2
  61. {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/scan/resource_policy_document.py +3 -3
  62. {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/scan/role_details.py +21 -28
  63. {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/scan/statement_detail.py +9 -9
  64. {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/scan/user_details.py +20 -29
  65. {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/shared/__init__.py +1 -1
  66. {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/shared/aws_login.py +4 -14
  67. cloudsplaining-0.9.0/cloudsplaining/shared/constants.py +360 -0
  68. {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/shared/exclusions.py +15 -22
  69. cloudsplaining-0.9.0/cloudsplaining/shared/template_config.py +40 -0
  70. {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/shared/utils.py +13 -15
  71. {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/shared/validation.py +7 -8
  72. cloudsplaining-0.9.0/pyproject.toml +166 -0
  73. cloudsplaining-0.9.0/test/command/test_create_multi_account_config_file.py +30 -0
  74. cloudsplaining-0.9.0/test/command/test_expand.py +42 -0
  75. cloudsplaining-0.9.0/test/command/test_expand_policy.py +34 -0
  76. cloudsplaining-0.9.0/test/command/test_scan.py +23 -0
  77. cloudsplaining-0.9.0/test/command/test_scan_multi_account.py +37 -0
  78. cloudsplaining-0.9.0/test/command/test_scan_policy_file.py +329 -0
  79. cloudsplaining-0.9.0/test/files/example-authz-details.json +4345 -0
  80. cloudsplaining-0.9.0/test/files/example_authz_details_for_overrides.json +52 -0
  81. cloudsplaining-0.9.0/test/files/example_authz_details_for_overrides_complete.json +241 -0
  82. cloudsplaining-0.9.0/test/files/example_authz_v2.json +202 -0
  83. cloudsplaining-0.9.0/test/files/managed_policy_mismatch.json +138 -0
  84. cloudsplaining-0.9.0/test/files/policy-overrides.json +13 -0
  85. cloudsplaining-0.9.0/test/files/scanning/test_authorization_file_details_missing_constraints_v2.json +220 -0
  86. cloudsplaining-0.9.0/test/files/scanning/test_group_detail_results.json +13 -0
  87. cloudsplaining-0.9.0/test/files/scanning/test_inline_policy_results.json +52 -0
  88. cloudsplaining-0.9.0/test/files/scanning/test_role_detail_results.json +79 -0
  89. cloudsplaining-0.9.0/test/files/scanning/test_user_detail_results.json +26 -0
  90. cloudsplaining-0.9.0/test/files/test-exclusions.yml +37 -0
  91. cloudsplaining-0.9.0/test/files/test_policy_file.json +34 -0
  92. cloudsplaining-0.9.0/test/output/test_policy_finding.py +244 -0
  93. cloudsplaining-0.9.0/test/scanning/test_action_links.py +85 -0
  94. cloudsplaining-0.9.0/test/scanning/test_authorization_details.py +146 -0
  95. cloudsplaining-0.9.0/test/scanning/test_exclusions_on_attached_policies.py +27 -0
  96. cloudsplaining-0.9.0/test/scanning/test_group_detail_list.py +72 -0
  97. cloudsplaining-0.9.0/test/scanning/test_inline_policy.py +52 -0
  98. cloudsplaining-0.9.0/test/scanning/test_managed_policy_detail.py +59 -0
  99. cloudsplaining-0.9.0/test/scanning/test_policy_document.py +588 -0
  100. cloudsplaining-0.9.0/test/scanning/test_privilege_escalation_methods.py +27 -0
  101. cloudsplaining-0.9.0/test/scanning/test_resource_policy_document.py +146 -0
  102. cloudsplaining-0.9.0/test/scanning/test_role_detail_list.py +43 -0
  103. cloudsplaining-0.9.0/test/scanning/test_statement_detail.py +292 -0
  104. cloudsplaining-0.9.0/test/scanning/test_trust_policies.py +458 -0
  105. cloudsplaining-0.9.0/test/scanning/test_user_detail_list.py +61 -0
  106. cloudsplaining-0.9.0/test/shared/test_aws_login.py +79 -0
  107. cloudsplaining-0.9.0/test/shared/test_exclusions.py +123 -0
  108. cloudsplaining-0.9.0/test/shared/test_pathfinding_mapping.py +50 -0
  109. cloudsplaining-0.9.0/test/shared/test_template_config.py +74 -0
  110. cloudsplaining-0.9.0/test/shared/test_utils.py +34 -0
  111. cloudsplaining-0.9.0/test/shared/test_validation.py +30 -0
  112. cloudsplaining-0.9.0/test/skills/test_iterate_pr_scripts.py +78 -0
  113. cloudsplaining-0.9.0/test/test_sample_data_in_sync.py +37 -0
  114. cloudsplaining-0.9.0/test/utils/test_build_example_dataset.py +90 -0
  115. cloudsplaining-0.9.0/test/utils/test_compare_example_reports.py +65 -0
  116. cloudsplaining-0.9.0/test/utils/test_safety_scan.py +66 -0
  117. cloudsplaining-0.8.1/MANIFEST.in +0 -7
  118. cloudsplaining-0.8.1/README.md +0 -387
  119. cloudsplaining-0.8.1/cloudsplaining/bin/version.py +0 -2
  120. cloudsplaining-0.8.1/cloudsplaining/output/dist/index.html +0 -6
  121. cloudsplaining-0.8.1/cloudsplaining/output/dist/js/index.js +0 -63
  122. cloudsplaining-0.8.1/cloudsplaining/output/public/index.html +0 -40
  123. cloudsplaining-0.8.1/cloudsplaining/output/report.py +0 -95
  124. cloudsplaining-0.8.1/cloudsplaining/output/src/App.vue +0 -174
  125. cloudsplaining-0.8.1/cloudsplaining/output/src/main.js +0 -16
  126. cloudsplaining-0.8.1/cloudsplaining/output/src/routes/routes.js +0 -52
  127. cloudsplaining-0.8.1/cloudsplaining/output/src/sampleData.js +0 -14594
  128. cloudsplaining-0.8.1/cloudsplaining/output/src/test/principals-test.js +0 -69
  129. cloudsplaining-0.8.1/cloudsplaining/output/src/views/Appendices.vue +0 -12
  130. cloudsplaining-0.8.1/cloudsplaining/output/src/views/AwsPolicies.vue +0 -50
  131. cloudsplaining-0.8.1/cloudsplaining/output/src/views/CustomerPolicies.vue +0 -49
  132. cloudsplaining-0.8.1/cloudsplaining/output/src/views/Guidance.vue +0 -12
  133. cloudsplaining-0.8.1/cloudsplaining/output/src/views/InlinePolicies.vue +0 -49
  134. cloudsplaining-0.8.1/cloudsplaining/shared/constants.py +0 -214
  135. cloudsplaining-0.8.1/cloudsplaining.egg-info/SOURCES.txt +0 -119
  136. cloudsplaining-0.8.1/cloudsplaining.egg-info/dependency_links.txt +0 -1
  137. cloudsplaining-0.8.1/cloudsplaining.egg-info/entry_points.txt +0 -3
  138. cloudsplaining-0.8.1/cloudsplaining.egg-info/requires.txt +0 -10
  139. cloudsplaining-0.8.1/cloudsplaining.egg-info/top_level.txt +0 -1
  140. cloudsplaining-0.8.1/cloudsplaining.egg-info/zip-safe +0 -1
  141. cloudsplaining-0.8.1/pyproject.toml +0 -82
  142. cloudsplaining-0.8.1/setup.cfg +0 -4
  143. cloudsplaining-0.8.1/setup.py +0 -72
  144. {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/LICENSE +0 -0
  145. {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/bin/__init__.py +0 -0
  146. {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/bin/cli.py +0 -0
  147. {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/command/__init__.py +0 -0
  148. {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/output/__init__.py +0 -0
  149. {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/output/dist/js/chunk-vendors.js +0 -0
  150. {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/output/src/assets/1-overview.md +0 -0
  151. {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/output/src/assets/2-triage-guidance.md +0 -0
  152. {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/output/src/assets/3-remediation-guidance.md +0 -0
  153. {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/output/src/assets/4-validation.md +0 -0
  154. {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/output/src/assets/definition-assumable-by-compute-service.md +0 -0
  155. {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/output/src/assets/definition-credentials-exposure.md +0 -0
  156. {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/output/src/assets/definition-data-exfiltration.md +0 -0
  157. {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/output/src/assets/definition-infrastructure-modification.md +0 -0
  158. {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/output/src/assets/definition-privilege-escalation.md +0 -0
  159. {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/output/src/assets/definition-resource-exposure.md +0 -0
  160. {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/output/src/assets/definition-service-wildcard.md +0 -0
  161. {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/output/src/assets/glossary.md +0 -0
  162. {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/output/src/assets/how-do-i-validate-results.md +0 -0
  163. {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/output/src/assets/identifying-false-positives.md +0 -0
  164. {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/output/src/assets/logo.png +0 -0
  165. {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/output/src/assets/summary.md +0 -0
  166. {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/output/src/assets/what-should-i-do.md +0 -0
  167. {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/output/src/components/Appendix.vue +0 -0
  168. {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/output/src/components/Button.vue +0 -0
  169. {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/output/src/components/Glossary.vue +0 -0
  170. {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/output/src/components/Guidance.vue +0 -0
  171. {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/output/src/components/Principals.vue +0 -0
  172. {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/output/src/components/ReportMetadata.vue +0 -0
  173. {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/output/src/components/finding/FindingCard.vue +0 -0
  174. {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/output/src/components/finding/FindingDetails.vue +0 -0
  175. {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/output/src/components/finding/RiskAlertIndicators.vue +0 -0
  176. {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/output/src/components/principals/PrincipalMetadata.vue +0 -0
  177. {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/output/src/components/principals/RisksPerPrincipal.vue +0 -0
  178. {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/output/src/util/glossary.js +0 -0
  179. {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/output/src/util/groups.js +0 -0
  180. {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/output/src/util/other.js +0 -0
  181. {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/output/src/util/principals.js +0 -0
  182. {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/output/src/util/roles.js +0 -0
  183. {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/output/src/util/task-table.js +0 -0
  184. {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/py.typed +0 -0
  185. {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/scan/authorization_details.py +0 -0
  186. {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/shared/default-exclusions.yml +0 -0
  187. {cloudsplaining-0.8.1 → cloudsplaining-0.9.0}/cloudsplaining/shared/exceptions.py +0 -0
  188. {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
1
+ Metadata-Version: 2.4
2
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
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: 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
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: License :: OSI Approved :: MIT License
23
- Classifier: Operating System :: OS Independent
24
- Requires-Python: >=3.9
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
-
@@ -1,4 +1,4 @@
1
- # pylint: disable=missing-module-docstring
1
+ # ruff: noqa: RUF067
2
2
  from __future__ import annotations
3
3
 
4
4
  import logging
@@ -0,0 +1,7 @@
1
+ import importlib.metadata
2
+
3
+ try:
4
+ __version__ = importlib.metadata.version("cloudsplaining")
5
+ except Exception:
6
+ # needed for local dev
7
+ __version__ = "0.0.0"
@@ -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 os
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=dict(max_content_width=160),
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=os.path.join(os.getcwd(), "exclusions.yml"),
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(output_file, "a", encoding="utf-8") as file_obj:
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 os
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=dict(max_content_width=160),
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=os.path.join(os.getcwd(), "multi-account-config.yml"),
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
- if os.path.exists(output_file):
46
- logger.debug("%s exists. Removing the file and replacing its contents.", output_file)
47
- os.remove(output_file)
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(output_file, "a", encoding="utf-8") as file_obj:
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: {os.path.relpath(output_file)}")
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 {os.path.relpath(output_file)} file and then run the scan-multi-account command, as shown below."
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 mypy_boto3_iam import IAMClient
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 = os.path.join(output, f"{profile}.json")
68
+ output_filename = output_path / f"{profile}.json"
67
69
  else:
68
- output_filename = os.path.join(output, "default.json")
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(output_filename, "w", encoding="utf-8") as f:
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) # type:ignore[arg-type]
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(input_file, encoding="utf-8") as json_file:
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(exclusions_file, encoding="utf-8") as yaml_file:
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
- if os.path.isfile(input_file):
143
- account_name = os.path.basename(input_file).split(".")[0]
144
- account_authorization_details_cfg = json.loads(Path(input_file).read_text(encoding="utf-8"))
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
- output,
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 = os.path.join(output, f"iam-report-{account_name}.html")
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 os.path.exists(html_output_file):
160
- os.remove(html_output_file)
161
+ if html_output_file.exists():
162
+ html_output_file.unlink()
161
163
 
162
- Path(html_output_file).write_text(rendered_html_report, encoding="utf-8")
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://{os.path.abspath(html_output_file)}"
171
+ url = f"file://{html_output_file.absolute()}"
170
172
  webbrowser.open(url, new=2)
171
173
 
172
- if os.path.isdir(input_file):
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(input_file)
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 = os.path.basename(input_file).split(".")[0]
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
- output,
187
+ output_path,
186
188
  write_data_files=True,
187
189
  minimize=minimize,
188
190
  severity=severity,
189
191
  )
190
- html_output_file = os.path.join(output, f"iam-report-{account_name}.html")
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 os.path.exists(html_output_file):
193
- os.remove(html_output_file)
194
+ if html_output_file.exists():
195
+ html_output_file.unlink()
194
196
 
195
- Path(html_output_file).write_text(rendered_html_report, encoding="utf-8")
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://{os.path.abspath(html_output_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 = os.getcwd(),
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 is None:
289
- output_directory = os.getcwd()
290
+ output_directory = Path(output_directory) if output_directory else Path.cwd()
290
291
 
291
- results_data_file = os.path.join(output_directory, f"iam-results-{account_name}.json")
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 = os.path.join(output_directory, f"iam-findings-{account_name}.json")
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
- else:
306
- return rendered_report
306
+
307
+ return rendered_report
307
308
 
308
309
 
309
310
  def get_authorization_files_in_directory(
310
- directory: str,
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 Path(directory).glob("*.json")]
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 os
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 mypy_boto3_s3 import S3ServiceResource
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(config_file, encoding="utf-8") as yaml_file:
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
- html_output_file = os.path.join(output_directory, f"{target_account_name}.html")
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: {os.path.relpath(html_output_file)}")
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 = os.path.join(output_directory, f"{target_account_name}.json")
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: {os.path.relpath(results_data_filepath)}")
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
- results = authorization_details.results
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
- authorization_details = get_account_authorization_details(session_data, include_non_default_policy_versions)
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(exclusions_file, encoding="utf-8") as yaml_file:
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: