cloudsplaining 0.8.0__tar.gz → 0.8.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (122) hide show
  1. {cloudsplaining-0.8.0/cloudsplaining.egg-info → cloudsplaining-0.8.1}/PKG-INFO +2 -2
  2. {cloudsplaining-0.8.0 → cloudsplaining-0.8.1}/cloudsplaining/bin/version.py +1 -1
  3. {cloudsplaining-0.8.0 → cloudsplaining-0.8.1}/cloudsplaining/command/create_exclusions_file.py +2 -2
  4. {cloudsplaining-0.8.0 → cloudsplaining-0.8.1}/cloudsplaining/command/create_multi_account_config_file.py +2 -2
  5. {cloudsplaining-0.8.0 → cloudsplaining-0.8.1}/cloudsplaining/command/scan.py +14 -0
  6. {cloudsplaining-0.8.0 → cloudsplaining-0.8.1}/cloudsplaining/command/scan_multi_account.py +14 -0
  7. cloudsplaining-0.8.1/cloudsplaining/scan/assume_role_policy_document.py +209 -0
  8. {cloudsplaining-0.8.0 → cloudsplaining-0.8.1}/cloudsplaining/scan/authorization_details.py +3 -0
  9. {cloudsplaining-0.8.0 → cloudsplaining-0.8.1}/cloudsplaining/scan/inline_policy.py +1 -1
  10. {cloudsplaining-0.8.0 → cloudsplaining-0.8.1}/cloudsplaining/scan/managed_policy_detail.py +1 -1
  11. {cloudsplaining-0.8.0 → cloudsplaining-0.8.1}/cloudsplaining/scan/role_details.py +70 -1
  12. {cloudsplaining-0.8.0 → cloudsplaining-0.8.1}/cloudsplaining/scan/statement_detail.py +1 -2
  13. {cloudsplaining-0.8.0 → cloudsplaining-0.8.1}/cloudsplaining/shared/constants.py +12 -0
  14. {cloudsplaining-0.8.0 → cloudsplaining-0.8.1}/cloudsplaining/shared/default-exclusions.yml +6 -1
  15. {cloudsplaining-0.8.0 → cloudsplaining-0.8.1}/cloudsplaining/shared/exclusions.py +7 -0
  16. {cloudsplaining-0.8.0 → cloudsplaining-0.8.1}/cloudsplaining/shared/utils.py +14 -0
  17. {cloudsplaining-0.8.0 → cloudsplaining-0.8.1}/cloudsplaining/shared/validation.py +1 -0
  18. {cloudsplaining-0.8.0 → cloudsplaining-0.8.1/cloudsplaining.egg-info}/PKG-INFO +2 -2
  19. {cloudsplaining-0.8.0 → cloudsplaining-0.8.1}/setup.py +1 -1
  20. cloudsplaining-0.8.0/cloudsplaining/scan/assume_role_policy_document.py +0 -91
  21. {cloudsplaining-0.8.0 → cloudsplaining-0.8.1}/LICENSE +0 -0
  22. {cloudsplaining-0.8.0 → cloudsplaining-0.8.1}/MANIFEST.in +0 -0
  23. {cloudsplaining-0.8.0 → cloudsplaining-0.8.1}/README.md +0 -0
  24. {cloudsplaining-0.8.0 → cloudsplaining-0.8.1}/cloudsplaining/__init__.py +0 -0
  25. {cloudsplaining-0.8.0 → cloudsplaining-0.8.1}/cloudsplaining/bin/__init__.py +0 -0
  26. {cloudsplaining-0.8.0 → cloudsplaining-0.8.1}/cloudsplaining/bin/cli.py +0 -0
  27. {cloudsplaining-0.8.0 → cloudsplaining-0.8.1}/cloudsplaining/command/__init__.py +0 -0
  28. {cloudsplaining-0.8.0 → cloudsplaining-0.8.1}/cloudsplaining/command/download.py +0 -0
  29. {cloudsplaining-0.8.0 → cloudsplaining-0.8.1}/cloudsplaining/command/expand_policy.py +0 -0
  30. {cloudsplaining-0.8.0 → cloudsplaining-0.8.1}/cloudsplaining/command/scan_policy_file.py +0 -0
  31. {cloudsplaining-0.8.0 → cloudsplaining-0.8.1}/cloudsplaining/output/__init__.py +0 -0
  32. {cloudsplaining-0.8.0 → cloudsplaining-0.8.1}/cloudsplaining/output/dist/index.html +0 -0
  33. {cloudsplaining-0.8.0 → cloudsplaining-0.8.1}/cloudsplaining/output/dist/js/chunk-vendors.js +0 -0
  34. {cloudsplaining-0.8.0 → cloudsplaining-0.8.1}/cloudsplaining/output/dist/js/index.js +0 -0
  35. {cloudsplaining-0.8.0 → cloudsplaining-0.8.1}/cloudsplaining/output/policy_finding.py +0 -0
  36. {cloudsplaining-0.8.0 → cloudsplaining-0.8.1}/cloudsplaining/output/public/index.html +0 -0
  37. {cloudsplaining-0.8.0 → cloudsplaining-0.8.1}/cloudsplaining/output/report.py +0 -0
  38. {cloudsplaining-0.8.0 → cloudsplaining-0.8.1}/cloudsplaining/output/src/App.vue +0 -0
  39. {cloudsplaining-0.8.0 → cloudsplaining-0.8.1}/cloudsplaining/output/src/assets/1-overview.md +0 -0
  40. {cloudsplaining-0.8.0 → cloudsplaining-0.8.1}/cloudsplaining/output/src/assets/2-triage-guidance.md +0 -0
  41. {cloudsplaining-0.8.0 → cloudsplaining-0.8.1}/cloudsplaining/output/src/assets/3-remediation-guidance.md +0 -0
  42. {cloudsplaining-0.8.0 → cloudsplaining-0.8.1}/cloudsplaining/output/src/assets/4-validation.md +0 -0
  43. {cloudsplaining-0.8.0 → cloudsplaining-0.8.1}/cloudsplaining/output/src/assets/definition-assumable-by-compute-service.md +0 -0
  44. {cloudsplaining-0.8.0 → cloudsplaining-0.8.1}/cloudsplaining/output/src/assets/definition-credentials-exposure.md +0 -0
  45. {cloudsplaining-0.8.0 → cloudsplaining-0.8.1}/cloudsplaining/output/src/assets/definition-data-exfiltration.md +0 -0
  46. {cloudsplaining-0.8.0 → cloudsplaining-0.8.1}/cloudsplaining/output/src/assets/definition-infrastructure-modification.md +0 -0
  47. {cloudsplaining-0.8.0 → cloudsplaining-0.8.1}/cloudsplaining/output/src/assets/definition-privilege-escalation.md +0 -0
  48. {cloudsplaining-0.8.0 → cloudsplaining-0.8.1}/cloudsplaining/output/src/assets/definition-resource-exposure.md +0 -0
  49. {cloudsplaining-0.8.0 → cloudsplaining-0.8.1}/cloudsplaining/output/src/assets/definition-service-wildcard.md +0 -0
  50. {cloudsplaining-0.8.0 → cloudsplaining-0.8.1}/cloudsplaining/output/src/assets/glossary.md +0 -0
  51. {cloudsplaining-0.8.0 → cloudsplaining-0.8.1}/cloudsplaining/output/src/assets/how-do-i-validate-results.md +0 -0
  52. {cloudsplaining-0.8.0 → cloudsplaining-0.8.1}/cloudsplaining/output/src/assets/identifying-false-positives.md +0 -0
  53. {cloudsplaining-0.8.0 → cloudsplaining-0.8.1}/cloudsplaining/output/src/assets/logo.png +0 -0
  54. {cloudsplaining-0.8.0 → cloudsplaining-0.8.1}/cloudsplaining/output/src/assets/summary.md +0 -0
  55. {cloudsplaining-0.8.0 → cloudsplaining-0.8.1}/cloudsplaining/output/src/assets/what-should-i-do.md +0 -0
  56. {cloudsplaining-0.8.0 → cloudsplaining-0.8.1}/cloudsplaining/output/src/components/Appendix.vue +0 -0
  57. {cloudsplaining-0.8.0 → cloudsplaining-0.8.1}/cloudsplaining/output/src/components/Button.vue +0 -0
  58. {cloudsplaining-0.8.0 → cloudsplaining-0.8.1}/cloudsplaining/output/src/components/Glossary.vue +0 -0
  59. {cloudsplaining-0.8.0 → cloudsplaining-0.8.1}/cloudsplaining/output/src/components/Guidance.vue +0 -0
  60. {cloudsplaining-0.8.0 → cloudsplaining-0.8.1}/cloudsplaining/output/src/components/InlinePolicies.vue +0 -0
  61. {cloudsplaining-0.8.0 → cloudsplaining-0.8.1}/cloudsplaining/output/src/components/LinkToFinding.vue +0 -0
  62. {cloudsplaining-0.8.0 → cloudsplaining-0.8.1}/cloudsplaining/output/src/components/ManagedPolicies.vue +0 -0
  63. {cloudsplaining-0.8.0 → cloudsplaining-0.8.1}/cloudsplaining/output/src/components/PolicyTable.vue +0 -0
  64. {cloudsplaining-0.8.0 → cloudsplaining-0.8.1}/cloudsplaining/output/src/components/Principals.vue +0 -0
  65. {cloudsplaining-0.8.0 → cloudsplaining-0.8.1}/cloudsplaining/output/src/components/ReportMetadata.vue +0 -0
  66. {cloudsplaining-0.8.0 → cloudsplaining-0.8.1}/cloudsplaining/output/src/components/Summary.vue +0 -0
  67. {cloudsplaining-0.8.0 → cloudsplaining-0.8.1}/cloudsplaining/output/src/components/TaskTable.vue +0 -0
  68. {cloudsplaining-0.8.0 → cloudsplaining-0.8.1}/cloudsplaining/output/src/components/charts/SummaryFindings.vue +0 -0
  69. {cloudsplaining-0.8.0 → cloudsplaining-0.8.1}/cloudsplaining/output/src/components/finding/AssumeRoleDetails.vue +0 -0
  70. {cloudsplaining-0.8.0 → cloudsplaining-0.8.1}/cloudsplaining/output/src/components/finding/FindingCard.vue +0 -0
  71. {cloudsplaining-0.8.0 → cloudsplaining-0.8.1}/cloudsplaining/output/src/components/finding/FindingDetails.vue +0 -0
  72. {cloudsplaining-0.8.0 → cloudsplaining-0.8.1}/cloudsplaining/output/src/components/finding/PolicyDocumentDetails.vue +0 -0
  73. {cloudsplaining-0.8.0 → cloudsplaining-0.8.1}/cloudsplaining/output/src/components/finding/PrivilegeEscalationDetails.vue +0 -0
  74. {cloudsplaining-0.8.0 → cloudsplaining-0.8.1}/cloudsplaining/output/src/components/finding/PrivilegeEscalationFormat.vue +0 -0
  75. {cloudsplaining-0.8.0 → cloudsplaining-0.8.1}/cloudsplaining/output/src/components/finding/RiskAlertIndicators.vue +0 -0
  76. {cloudsplaining-0.8.0 → cloudsplaining-0.8.1}/cloudsplaining/output/src/components/finding/StandardRiskDetails.vue +0 -0
  77. {cloudsplaining-0.8.0 → cloudsplaining-0.8.1}/cloudsplaining/output/src/components/principals/PrincipalMetadata.vue +0 -0
  78. {cloudsplaining-0.8.0 → cloudsplaining-0.8.1}/cloudsplaining/output/src/components/principals/RisksPerPrincipal.vue +0 -0
  79. {cloudsplaining-0.8.0 → cloudsplaining-0.8.1}/cloudsplaining/output/src/main.js +0 -0
  80. {cloudsplaining-0.8.0 → cloudsplaining-0.8.1}/cloudsplaining/output/src/routes/routes.js +0 -0
  81. {cloudsplaining-0.8.0 → cloudsplaining-0.8.1}/cloudsplaining/output/src/sampleData.js +0 -0
  82. {cloudsplaining-0.8.0 → cloudsplaining-0.8.1}/cloudsplaining/output/src/test/groups-test.js +0 -0
  83. {cloudsplaining-0.8.0 → cloudsplaining-0.8.1}/cloudsplaining/output/src/test/inline-policies-test.js +0 -0
  84. {cloudsplaining-0.8.0 → cloudsplaining-0.8.1}/cloudsplaining/output/src/test/managed-policies-test.js +0 -0
  85. {cloudsplaining-0.8.0 → cloudsplaining-0.8.1}/cloudsplaining/output/src/test/other-test.js +0 -0
  86. {cloudsplaining-0.8.0 → cloudsplaining-0.8.1}/cloudsplaining/output/src/test/principals-test.js +0 -0
  87. {cloudsplaining-0.8.0 → cloudsplaining-0.8.1}/cloudsplaining/output/src/test/roles-test.js +0 -0
  88. {cloudsplaining-0.8.0 → cloudsplaining-0.8.1}/cloudsplaining/output/src/test/task-table-test.js +0 -0
  89. {cloudsplaining-0.8.0 → cloudsplaining-0.8.1}/cloudsplaining/output/src/util/glossary.js +0 -0
  90. {cloudsplaining-0.8.0 → cloudsplaining-0.8.1}/cloudsplaining/output/src/util/groups.js +0 -0
  91. {cloudsplaining-0.8.0 → cloudsplaining-0.8.1}/cloudsplaining/output/src/util/inline-policies.js +0 -0
  92. {cloudsplaining-0.8.0 → cloudsplaining-0.8.1}/cloudsplaining/output/src/util/managed-policies.js +0 -0
  93. {cloudsplaining-0.8.0 → cloudsplaining-0.8.1}/cloudsplaining/output/src/util/other.js +0 -0
  94. {cloudsplaining-0.8.0 → cloudsplaining-0.8.1}/cloudsplaining/output/src/util/principals.js +0 -0
  95. {cloudsplaining-0.8.0 → cloudsplaining-0.8.1}/cloudsplaining/output/src/util/roles.js +0 -0
  96. {cloudsplaining-0.8.0 → cloudsplaining-0.8.1}/cloudsplaining/output/src/util/task-table.js +0 -0
  97. {cloudsplaining-0.8.0 → cloudsplaining-0.8.1}/cloudsplaining/output/src/views/Appendices.vue +0 -0
  98. {cloudsplaining-0.8.0 → cloudsplaining-0.8.1}/cloudsplaining/output/src/views/AwsPolicies.vue +0 -0
  99. {cloudsplaining-0.8.0 → cloudsplaining-0.8.1}/cloudsplaining/output/src/views/CustomerPolicies.vue +0 -0
  100. {cloudsplaining-0.8.0 → cloudsplaining-0.8.1}/cloudsplaining/output/src/views/Guidance.vue +0 -0
  101. {cloudsplaining-0.8.0 → cloudsplaining-0.8.1}/cloudsplaining/output/src/views/IamPrincipals.vue +0 -0
  102. {cloudsplaining-0.8.0 → cloudsplaining-0.8.1}/cloudsplaining/output/src/views/InlinePolicies.vue +0 -0
  103. {cloudsplaining-0.8.0 → cloudsplaining-0.8.1}/cloudsplaining/output/src/views/Summary.vue +0 -0
  104. {cloudsplaining-0.8.0 → cloudsplaining-0.8.1}/cloudsplaining/output/template.html +0 -0
  105. {cloudsplaining-0.8.0 → cloudsplaining-0.8.1}/cloudsplaining/py.typed +0 -0
  106. {cloudsplaining-0.8.0 → cloudsplaining-0.8.1}/cloudsplaining/scan/__init__.py +0 -0
  107. {cloudsplaining-0.8.0 → cloudsplaining-0.8.1}/cloudsplaining/scan/group_details.py +0 -0
  108. {cloudsplaining-0.8.0 → cloudsplaining-0.8.1}/cloudsplaining/scan/policy_document.py +0 -0
  109. {cloudsplaining-0.8.0 → cloudsplaining-0.8.1}/cloudsplaining/scan/resource_policy_document.py +0 -0
  110. {cloudsplaining-0.8.0 → cloudsplaining-0.8.1}/cloudsplaining/scan/user_details.py +0 -0
  111. {cloudsplaining-0.8.0 → cloudsplaining-0.8.1}/cloudsplaining/shared/__init__.py +0 -0
  112. {cloudsplaining-0.8.0 → cloudsplaining-0.8.1}/cloudsplaining/shared/aws_login.py +0 -0
  113. {cloudsplaining-0.8.0 → cloudsplaining-0.8.1}/cloudsplaining/shared/exceptions.py +0 -0
  114. {cloudsplaining-0.8.0 → cloudsplaining-0.8.1}/cloudsplaining/shared/multi-account-config.yml +0 -0
  115. {cloudsplaining-0.8.0 → cloudsplaining-0.8.1}/cloudsplaining.egg-info/SOURCES.txt +0 -0
  116. {cloudsplaining-0.8.0 → cloudsplaining-0.8.1}/cloudsplaining.egg-info/dependency_links.txt +0 -0
  117. {cloudsplaining-0.8.0 → cloudsplaining-0.8.1}/cloudsplaining.egg-info/entry_points.txt +0 -0
  118. {cloudsplaining-0.8.0 → cloudsplaining-0.8.1}/cloudsplaining.egg-info/requires.txt +0 -0
  119. {cloudsplaining-0.8.0 → cloudsplaining-0.8.1}/cloudsplaining.egg-info/top_level.txt +0 -0
  120. {cloudsplaining-0.8.0 → cloudsplaining-0.8.1}/cloudsplaining.egg-info/zip-safe +0 -0
  121. {cloudsplaining-0.8.0 → cloudsplaining-0.8.1}/pyproject.toml +0 -0
  122. {cloudsplaining-0.8.0 → cloudsplaining-0.8.1}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: cloudsplaining
3
- Version: 0.8.0
3
+ Version: 0.8.1
4
4
  Summary: AWS IAM Security Assessment tool that identifies violations of least privilege and generates a risk-prioritized HTML report.
5
5
  Home-page: https://github.com/salesforce/cloudsplaining
6
6
  Author: Kinnaird McQuade
@@ -21,7 +21,7 @@ Classifier: Programming Language :: Python :: 3.12
21
21
  Classifier: Programming Language :: Python :: 3.13
22
22
  Classifier: License :: OSI Approved :: MIT License
23
23
  Classifier: Operating System :: OS Independent
24
- Requires-Python: >=3.8
24
+ Requires-Python: >=3.9
25
25
  Description-Content-Type: text/markdown
26
26
  License-File: LICENSE
27
27
 
@@ -1,2 +1,2 @@
1
1
  # pylint: disable=missing-module-docstring
2
- __version__ = "0.8.0"
2
+ __version__ = "0.8.1"
@@ -41,8 +41,8 @@ def create_exclusions_file(output_file: str, verbosity: int) -> None:
41
41
  set_log_level(verbosity)
42
42
 
43
43
  with open(output_file, "a", encoding="utf-8") as file_obj:
44
- for line in EXCLUSIONS_TEMPLATE:
45
- file_obj.write(line)
44
+ file_obj.write(EXCLUSIONS_TEMPLATE)
45
+
46
46
  utils.print_green(f"Success! Exclusions template file written to: {output_file}")
47
47
  print(
48
48
  "Make sure you download your account authorization details before running the scan."
@@ -47,8 +47,8 @@ def create_multi_account_config_file(output_file: str, verbosity: int) -> None:
47
47
  os.remove(output_file)
48
48
 
49
49
  with open(output_file, "a", encoding="utf-8") as file_obj:
50
- for line in MULTI_ACCOUNT_CONFIG_TEMPLATE:
51
- file_obj.write(line)
50
+ 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
53
  print(
54
54
  f"\nMake sure you edit the {os.path.relpath(output_file)} file and then run the scan-multi-account command, as shown below."
@@ -96,6 +96,14 @@ from cloudsplaining.shared.validation import check_authorization_details_schema
96
96
  multiple=True,
97
97
  type=click.Choice(["CRITICAL", "HIGH", "MEDIUM", "LOW", "NONE"], case_sensitive=False),
98
98
  )
99
+ @click.option(
100
+ "-t",
101
+ "--flag-trust-policies",
102
+ required=False,
103
+ default=False,
104
+ is_flag=True,
105
+ help="Flag risky trust policies in roles.",
106
+ )
99
107
  def scan(
100
108
  input_file: str,
101
109
  exclusions_file: str,
@@ -105,6 +113,7 @@ def scan(
105
113
  flag_all_risky_actions: bool,
106
114
  verbosity: int,
107
115
  severity: list[str],
116
+ flag_trust_policies: bool,
108
117
  ) -> None: # pragma: no cover
109
118
  """
110
119
  Given the path to account authorization details files and the exclusions config file, scan all inline and
@@ -142,6 +151,7 @@ def scan(
142
151
  minimize=minimize,
143
152
  flag_conditional_statements=flag_conditional_statements,
144
153
  flag_resource_arn_statements=flag_resource_arn_statements,
154
+ flag_trust_policies=flag_trust_policies,
145
155
  severity=severity,
146
156
  )
147
157
  html_output_file = os.path.join(output, f"iam-report-{account_name}.html")
@@ -207,6 +217,7 @@ def scan_account_authorization_details(
207
217
  return_json_results: Literal[True],
208
218
  flag_conditional_statements: bool = ...,
209
219
  flag_resource_arn_statements: bool = ...,
220
+ flag_trust_policies: bool = ...,
210
221
  severity: list[str] | None = ...,
211
222
  ) -> dict[str, Any]: ...
212
223
 
@@ -222,6 +233,7 @@ def scan_account_authorization_details(
222
233
  return_json_results: Literal[False] = ...,
223
234
  flag_conditional_statements: bool = ...,
224
235
  flag_resource_arn_statements: bool = ...,
236
+ flag_trust_policies: bool = ...,
225
237
  severity: list[str] | None = ...,
226
238
  ) -> str: ...
227
239
 
@@ -236,6 +248,7 @@ def scan_account_authorization_details(
236
248
  return_json_results: bool = False,
237
249
  flag_conditional_statements: bool = False,
238
250
  flag_resource_arn_statements: bool = False,
251
+ flag_trust_policies: bool = False,
239
252
  severity: list[str] | None = None,
240
253
  ) -> str | dict[str, Any]: # pragma: no cover
241
254
  """
@@ -250,6 +263,7 @@ def scan_account_authorization_details(
250
263
  exclusions=exclusions,
251
264
  flag_conditional_statements=flag_conditional_statements,
252
265
  flag_resource_arn_statements=flag_resource_arn_statements,
266
+ flag_trust_policies=flag_trust_policies,
253
267
  severity=severity,
254
268
  )
255
269
  results = authorization_details.results
@@ -120,6 +120,14 @@ class MultiAccountConfig:
120
120
  multiple=True,
121
121
  type=click.Choice(["CRITICAL", "HIGH", "MEDIUM", "LOW", "NONE"], case_sensitive=False),
122
122
  )
123
+ @click.option(
124
+ "-t",
125
+ "--flag-trust-policies",
126
+ required=False,
127
+ default=False,
128
+ is_flag=True,
129
+ help="Flag risky trust policies in roles.",
130
+ )
123
131
  def scan_multi_account(
124
132
  config_file: str,
125
133
  profile: str,
@@ -131,6 +139,7 @@ def scan_multi_account(
131
139
  flag_all_risky_actions: bool,
132
140
  verbosity: int,
133
141
  severity: list[str],
142
+ flag_trust_policies: bool,
134
143
  ) -> None:
135
144
  """Scan multiple accounts via AssumeRole"""
136
145
  set_log_level(verbosity)
@@ -160,6 +169,7 @@ def scan_multi_account(
160
169
  severity=severity,
161
170
  flag_conditional_statements=flag_conditional_statements,
162
171
  flag_resource_arn_statements=flag_resource_arn_statements,
172
+ flag_trust_policies=flag_trust_policies,
163
173
  )
164
174
 
165
175
 
@@ -174,6 +184,7 @@ def scan_accounts(
174
184
  severity: list[str] | None = None,
175
185
  flag_conditional_statements: bool = False,
176
186
  flag_resource_arn_statements: bool = False,
187
+ flag_trust_policies: bool = False,
177
188
  ) -> None:
178
189
  """Use this method as a library to scan multiple accounts"""
179
190
  # TODO: Speed improvements? Multithreading? This currently runs sequentially.
@@ -187,6 +198,7 @@ def scan_accounts(
187
198
  severity=severity,
188
199
  flag_conditional_statements=flag_conditional_statements,
189
200
  flag_resource_arn_statements=flag_resource_arn_statements,
201
+ flag_trust_policies=flag_trust_policies,
190
202
  )
191
203
  html_report = HTMLReport(
192
204
  account_id=target_account_id,
@@ -233,6 +245,7 @@ def scan_account(
233
245
  severity: list[str] | None = None,
234
246
  flag_conditional_statements: bool = False,
235
247
  flag_resource_arn_statements: bool = False,
248
+ flag_trust_policies: bool = False,
236
249
  ) -> dict[str, dict[str, Any]]:
237
250
  """Scan a target account in one shot"""
238
251
  account_authorization_details = download_account_authorization_details(
@@ -247,6 +260,7 @@ def scan_account(
247
260
  severity=severity,
248
261
  flag_conditional_statements=flag_conditional_statements,
249
262
  flag_resource_arn_statements=flag_resource_arn_statements,
263
+ flag_trust_policies=flag_trust_policies,
250
264
  )
251
265
  results = authorization_details.results
252
266
  return results
@@ -0,0 +1,209 @@
1
+ """Represents the AssumeRole Trust Policy. This is mainly used for identifying whether roles are assumable via AWS compute services."""
2
+
3
+ # Copyright (c) 2020, salesforce.com, inc.
4
+ # All rights reserved.
5
+ # Licensed under the BSD 3-Clause license.
6
+ # For full license text, see the LICENSE file in the repo root
7
+ # or https://opensource.org/licenses/BSD-3-Clause
8
+ from __future__ import annotations
9
+
10
+ import logging
11
+ from typing import Any
12
+
13
+ from cloudsplaining.scan.resource_policy_document import (
14
+ ResourcePolicyDocument,
15
+ ResourceStatement,
16
+ )
17
+ from cloudsplaining.shared.constants import SERVICE_PREFIXES_WITH_COMPUTE_ROLES
18
+ from cloudsplaining.shared.exclusions import (
19
+ DEFAULT_EXCLUSIONS,
20
+ Exclusions,
21
+ )
22
+ from cloudsplaining.shared.utils import get_account_id_from_principal
23
+
24
+ logger = logging.getLogger(__name__)
25
+
26
+
27
+ class AssumeRolePolicyDocument(ResourcePolicyDocument):
28
+ """Holds the AssumeRole/Trust Policy document
29
+
30
+ It is a specialized version of a Resource-based policy
31
+ """
32
+
33
+ def __init__(
34
+ self,
35
+ policy: dict[str, Any],
36
+ current_account_id: str | None = None,
37
+ exclusions: Exclusions = DEFAULT_EXCLUSIONS,
38
+ ) -> None:
39
+ statement_structure = policy.get("Statement", [])
40
+ self.policy = policy
41
+ self.current_account_id = current_account_id
42
+ # We would actually need to define a proper base class with a generic type for statements
43
+ self.statements: list[AssumeRoleStatement] = [] # type:ignore[assignment]
44
+ self.exclusions = exclusions
45
+ # leaving here but excluding from tests because IAM Policy grammar dictates that it must be a list
46
+ if not isinstance(statement_structure, list): # pragma: no cover
47
+ statement_structure = [statement_structure]
48
+
49
+ for statement in statement_structure:
50
+ self.statements.append(AssumeRoleStatement(statement, current_account_id, exclusions))
51
+
52
+ @property
53
+ def role_assumable_by_compute_services(self) -> list[str]:
54
+ """Determines whether or not the role is assumed from a compute service, and if so which ones."""
55
+ return [
56
+ principal
57
+ for statement in self.statements
58
+ if statement.role_assumable_by_compute_services
59
+ for principal in statement.role_assumable_by_compute_services
60
+ ]
61
+
62
+ @property
63
+ def role_assumable_by_cross_account_principals(self) -> list[str]:
64
+ """Determines whether or not the role can be assumed from principals in other accounts, and if so which ones."""
65
+ return [
66
+ principal
67
+ for statement in self.statements
68
+ if statement.role_assumable_by_cross_account_principals
69
+ for principal in statement.role_assumable_by_cross_account_principals
70
+ ]
71
+
72
+ @property
73
+ def role_assumable_by_any_principal(self) -> list[str]:
74
+ """Determines whether or not the role can be assumed by any principal (*) or any AWS account root."""
75
+ return [
76
+ principal
77
+ for statement in self.statements
78
+ if statement.role_assumable_by_any_principal
79
+ for principal in statement.role_assumable_by_any_principal
80
+ ]
81
+
82
+ @property
83
+ def role_assumable_by_any_principal_with_conditions(self) -> list[str]:
84
+ """Determines whether or not the role can be assumed by any principal (*) or any AWS account root with conditions."""
85
+ return [
86
+ principal
87
+ for statement in self.statements
88
+ if statement.role_assumable_by_any_principal_with_conditions
89
+ for principal in statement.role_assumable_by_any_principal_with_conditions
90
+ ]
91
+
92
+
93
+ class AssumeRoleStatement(ResourceStatement):
94
+ """
95
+ Statements in an AssumeRole/Trust Policy document
96
+ """
97
+
98
+ def __init__(
99
+ self,
100
+ statement: dict[str, Any],
101
+ current_account_id: str | None = None,
102
+ exclusions: Exclusions = DEFAULT_EXCLUSIONS,
103
+ ) -> None:
104
+ super().__init__(statement=statement)
105
+ self.current_account_id = current_account_id
106
+ self.exclusions = exclusions
107
+
108
+ # self.not_principal = statement.get("NotPrincipal")
109
+ if statement.get("NotPrincipal"):
110
+ logger.critical( # pragma: no cover
111
+ "NotPrincipal is used in the IAM AssumeRole Trust Policy. "
112
+ "This should NOT be used. We suggest reviewing it ASAP."
113
+ )
114
+
115
+ def _assume_role_actions(self) -> list[str]:
116
+ """Verifies that this is limited to just sts:AssumeRole"""
117
+ actions = self.statement.get("Action", [])
118
+ if not actions:
119
+ logger.debug("The AssumeRole Policy has no actions in it.")
120
+ return []
121
+
122
+ if isinstance(actions, list):
123
+ return actions
124
+
125
+ return [actions]
126
+
127
+ @property
128
+ def role_assumable_by_compute_services(self) -> list[str]:
129
+ """Determines whether or not the role is assumed from a compute service, and if so which ones."""
130
+ # sts:AssumeRole must be there
131
+ lowercase_actions = [x.lower() for x in self.actions]
132
+ if "sts:AssumeRole".lower() not in lowercase_actions:
133
+ return []
134
+
135
+ # Effect must be Allow
136
+ if self.effect.lower() != "allow":
137
+ return []
138
+
139
+ assumable_by_compute_services = []
140
+ for principal in self.principals:
141
+ if principal.endswith(".amazonaws.com"):
142
+ service_prefix_to_evaluate = principal.split(".")[0]
143
+ if service_prefix_to_evaluate in SERVICE_PREFIXES_WITH_COMPUTE_ROLES:
144
+ assumable_by_compute_services.append(service_prefix_to_evaluate)
145
+ return assumable_by_compute_services
146
+
147
+ @property
148
+ def role_assumable_by_cross_account_principals(self) -> list[str]:
149
+ """Determines whether or not the role can be assumed from principals in other accounts, and if so which ones."""
150
+ # sts:AssumeRole must be there
151
+ lowercase_actions = [x.lower() for x in self.actions]
152
+ if "sts:AssumeRole".lower() not in lowercase_actions:
153
+ return []
154
+
155
+ # Effect must be Allow
156
+ if self.effect.lower() != "allow":
157
+ return []
158
+
159
+ return [
160
+ principal
161
+ for principal in self.principals
162
+ if (principal_account_id := get_account_id_from_principal(principal))
163
+ and (self.current_account_id is None or principal_account_id != self.current_account_id)
164
+ and principal_account_id not in self.exclusions.known_accounts
165
+ ]
166
+
167
+ @property
168
+ def role_assumable_by_any_principal(self) -> list[str]:
169
+ """Determines whether or not the role can be assumed by any principal (*) or any AWS account root."""
170
+ # sts:AssumeRole must be there
171
+ lowercase_actions = [x.lower() for x in self.actions]
172
+ if "sts:AssumeRole".lower() not in lowercase_actions:
173
+ return []
174
+
175
+ # Effect must be Allow
176
+ if self.effect.lower() != "allow":
177
+ return []
178
+
179
+ # Must have no conditions
180
+ if self.statement.get("Condition"):
181
+ return []
182
+
183
+ # Check if any principal is "*" or "arn:aws:iam::*:root"
184
+ any_principals = [
185
+ principal for principal in self.principals if principal == "*" or principal == "arn:aws:iam::*:root"
186
+ ]
187
+ return any_principals
188
+
189
+ @property
190
+ def role_assumable_by_any_principal_with_conditions(self) -> list[str]:
191
+ """Determines whether or not the role can be assumed by any principal (*) or any AWS account root with conditions."""
192
+ # sts:AssumeRole must be there
193
+ lowercase_actions = [x.lower() for x in self.actions]
194
+ if "sts:AssumeRole".lower() not in lowercase_actions:
195
+ return []
196
+
197
+ # Effect must be Allow
198
+ if self.effect.lower() != "allow":
199
+ return []
200
+
201
+ # Must have conditions (opposite of role_assumable_by_any_principal)
202
+ if not self.statement.get("Condition"):
203
+ return []
204
+
205
+ # Check if any principal is "*" or "arn:aws:iam::*:root"
206
+ any_principals = [
207
+ principal for principal in self.principals if principal == "*" or principal == "arn:aws:iam::*:root"
208
+ ]
209
+ return any_principals
@@ -35,6 +35,7 @@ class AuthorizationDetails:
35
35
  exclusions: Exclusions = DEFAULT_EXCLUSIONS,
36
36
  flag_conditional_statements: bool = False,
37
37
  flag_resource_arn_statements: bool = False,
38
+ flag_trust_policies: bool = False,
38
39
  severity: list[str] | None = None,
39
40
  ) -> None:
40
41
  """
@@ -53,6 +54,7 @@ class AuthorizationDetails:
53
54
  self.exclusions = exclusions
54
55
  self.flag_conditional_statements = flag_conditional_statements
55
56
  self.flag_resource_arn_statements = flag_resource_arn_statements
57
+ self.flag_trust_policies = flag_trust_policies
56
58
 
57
59
  self.policies = ManagedPolicyDetails(
58
60
  auth_json.get("Policies", []),
@@ -86,6 +88,7 @@ class AuthorizationDetails:
86
88
  exclusions,
87
89
  flag_conditional_statements=flag_conditional_statements,
88
90
  flag_resource_arn_statements=flag_resource_arn_statements,
91
+ flag_trust_policies=flag_trust_policies,
89
92
  severity=severity,
90
93
  )
91
94
 
@@ -70,7 +70,7 @@ class InlinePolicy:
70
70
  links = {}
71
71
  for finding in findings:
72
72
  links[finding["type"]] = (
73
- f'https://cloudsplaining.readthedocs.io/en/latest/glossary/privilege-escalation/#{finding["type"]}'
73
+ f"https://cloudsplaining.readthedocs.io/en/latest/glossary/privilege-escalation/#{finding['type']}"
74
74
  )
75
75
  return links
76
76
 
@@ -244,7 +244,7 @@ class ManagedPolicy:
244
244
  links = {}
245
245
  for finding in findings:
246
246
  links[finding["type"]] = (
247
- f'https://cloudsplaining.readthedocs.io/en/latest/glossary/privilege-escalation/#{finding["type"]}'
247
+ f"https://cloudsplaining.readthedocs.io/en/latest/glossary/privilege-escalation/#{finding['type']}"
248
248
  )
249
249
  return links
250
250
 
@@ -2,13 +2,17 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ import contextlib
5
6
  import json
6
7
  import logging
7
8
  from typing import TYPE_CHECKING, Any
8
9
 
10
+ from policy_sentry.util.arns import get_account_from_arn
11
+
9
12
  from cloudsplaining.scan.assume_role_policy_document import AssumeRolePolicyDocument
10
13
  from cloudsplaining.scan.inline_policy import InlinePolicy
11
14
  from cloudsplaining.shared import utils
15
+ from cloudsplaining.shared.constants import ISSUE_SEVERITY, RISK_DEFINITION
12
16
  from cloudsplaining.shared.exceptions import NotFoundException
13
17
  from cloudsplaining.shared.exclusions import (
14
18
  DEFAULT_EXCLUSIONS,
@@ -39,6 +43,7 @@ class RoleDetailList:
39
43
  exclusions: Exclusions = DEFAULT_EXCLUSIONS,
40
44
  flag_conditional_statements: bool = False,
41
45
  flag_resource_arn_statements: bool = False,
46
+ flag_trust_policies: bool = False,
42
47
  severity: list[str] | None = None,
43
48
  ) -> None:
44
49
  self.severity = [] if severity is None else severity
@@ -50,6 +55,7 @@ class RoleDetailList:
50
55
  # Fix Issue #254 - Allow flagging risky actions even when there are resource constraints
51
56
  self.flag_conditional_statements = flag_conditional_statements
52
57
  self.flag_resource_arn_statements = flag_resource_arn_statements
58
+ self.flag_trust_policies = flag_trust_policies
53
59
  self.iam_data: dict[str, dict[Any, Any]] = {
54
60
  "groups": {},
55
61
  "users": {},
@@ -73,6 +79,7 @@ class RoleDetailList:
73
79
  exclusions=exclusions,
74
80
  flag_conditional_statements=self.flag_conditional_statements,
75
81
  flag_resource_arn_statements=self.flag_resource_arn_statements,
82
+ flag_trust_policies=flag_trust_policies,
76
83
  severity=self.severity,
77
84
  )
78
85
  )
@@ -138,6 +145,7 @@ class RoleDetail:
138
145
  exclusions: Exclusions = DEFAULT_EXCLUSIONS,
139
146
  flag_conditional_statements: bool = False,
140
147
  flag_resource_arn_statements: bool = False,
148
+ flag_trust_policies: bool = False,
141
149
  severity: list[str] | None = None,
142
150
  ) -> None:
143
151
  """
@@ -147,6 +155,7 @@ class RoleDetail:
147
155
  :param policy_details: The ManagedPolicyDetails object - i.e., details about all managed policies in the account
148
156
  so the role can inherit those attributes
149
157
  """
158
+ self.severity = [] if severity is None else severity
150
159
  # Metadata
151
160
  self.path = role_detail["Path"]
152
161
  self.role_name = role_detail["RoleName"]
@@ -165,6 +174,7 @@ class RoleDetail:
165
174
  # Fix Issue #254 - Allow flagging risky actions even when there are resource constraints
166
175
  self.flag_conditional_statements = flag_conditional_statements
167
176
  self.flag_resource_arn_statements = flag_resource_arn_statements
177
+ self.flag_trust_policies = flag_trust_policies
168
178
 
169
179
  self.iam_data: dict[str, dict[Any, Any]] = {
170
180
  "groups": {},
@@ -176,7 +186,16 @@ class RoleDetail:
176
186
  self.assume_role_policy_document = None
177
187
  assume_role_policy = role_detail.get("AssumeRolePolicyDocument")
178
188
  if assume_role_policy:
179
- self.assume_role_policy_document = AssumeRolePolicyDocument(assume_role_policy)
189
+ # Extract current account ID from role ARN
190
+ current_account_id = None
191
+ if self.arn:
192
+ with contextlib.suppress(Exception):
193
+ # If we can't parse the account ID, continue without it
194
+ current_account_id = get_account_from_arn(self.arn)
195
+
196
+ self.assume_role_policy_document = AssumeRolePolicyDocument(
197
+ assume_role_policy, current_account_id, exclusions
198
+ )
180
199
 
181
200
  # TODO: Create a class for InstanceProfileList
182
201
  self.instance_profile_list = role_detail.get("InstanceProfileList", [])
@@ -331,4 +350,54 @@ class RoleDetail:
331
350
  aws_managed_policies=self.attached_aws_managed_policies_pointer_json,
332
351
  is_excluded=self.is_excluded,
333
352
  )
353
+
354
+ if self.flag_trust_policies:
355
+ severities = {x.lower() for x in self.severity}
356
+ this_role_detail.update(
357
+ {
358
+ "AssumableByComputeServices": {
359
+ "severity": ISSUE_SEVERITY["AssumableByComputeService"],
360
+ "description": RISK_DEFINITION["AssumableByComputeService"],
361
+ "findings": (
362
+ self.assume_role_policy_document.role_assumable_by_compute_services
363
+ if self.assume_role_policy_document
364
+ and (ISSUE_SEVERITY["AssumableByComputeService"] in severities or not self.severity)
365
+ else []
366
+ ),
367
+ },
368
+ "AssumableByCrossAccountPrincipal": {
369
+ "severity": ISSUE_SEVERITY["AssumableByCrossAccountPrincipal"],
370
+ "description": RISK_DEFINITION["AssumableByCrossAccountPrincipal"],
371
+ "findings": (
372
+ self.assume_role_policy_document.role_assumable_by_cross_account_principals
373
+ if self.assume_role_policy_document
374
+ and (ISSUE_SEVERITY["AssumableByCrossAccountPrincipal"] in severities or not self.severity)
375
+ else []
376
+ ),
377
+ },
378
+ "AssumableByAnyPrincipal": {
379
+ "severity": ISSUE_SEVERITY["AssumableByAnyPrincipal"],
380
+ "description": RISK_DEFINITION["AssumableByAnyPrincipal"],
381
+ "findings": (
382
+ self.assume_role_policy_document.role_assumable_by_any_principal
383
+ if self.assume_role_policy_document
384
+ and (ISSUE_SEVERITY["AssumableByAnyPrincipal"] in severities or not self.severity)
385
+ else []
386
+ ),
387
+ },
388
+ "AssumableByAnyPrincipalWithConditions": {
389
+ "severity": ISSUE_SEVERITY["AssumableByAnyPrincipalWithConditions"],
390
+ "description": RISK_DEFINITION["AssumableByAnyPrincipalWithConditions"],
391
+ "findings": (
392
+ self.assume_role_policy_document.role_assumable_by_any_principal_with_conditions
393
+ if self.assume_role_policy_document
394
+ and (
395
+ ISSUE_SEVERITY["AssumableByAnyPrincipalWithConditions"] in severities
396
+ or not self.severity
397
+ )
398
+ else []
399
+ ),
400
+ },
401
+ }
402
+ )
334
403
  return this_role_detail
@@ -241,8 +241,7 @@ class StatementDetail:
241
241
  # Fix #390 - if flag_resource_arn_statements is True, then let's treat this as missing resource constraints so we can flag the action anyway.
242
242
  elif self.flag_resource_arn_statements:
243
243
  actions_missing_resource_constraints = self.restrictable_actions
244
- else:
245
- pass
244
+
246
245
  result = exclusions.get_allowed_actions(actions_missing_resource_constraints)
247
246
  result.sort()
248
247
  return result
@@ -54,6 +54,12 @@ include-actions:
54
54
  # Write actions to include from the results, such as kms:Decrypt
55
55
  exclude-actions:
56
56
  - ""
57
+ # Known AWS Account IDs to exclude from assume role analysis.
58
+ # Add your organization's account IDs and any thrid party vendor's
59
+ # account IDs here to avoid false positives.
60
+ # https://github.com/fwdcloudsec/known_aws_accounts/blob/main/accounts.yaml
61
+ known-accounts:
62
+ - ""
57
63
  """
58
64
 
59
65
  MULTI_ACCOUNT_CONFIG_TEMPLATE = """accounts:
@@ -83,6 +89,9 @@ ISSUE_SEVERITY = {
83
89
  "CredentialsExposure": "high",
84
90
  "InfrastructureModification": "low",
85
91
  "AssumableByComputeService": "low",
92
+ "AssumableByCrossAccountPrincipal": "medium",
93
+ "AssumableByAnyPrincipal": "critical",
94
+ "AssumableByAnyPrincipalWithConditions": "medium",
86
95
  }
87
96
 
88
97
  RISK_DEFINITION = {
@@ -93,6 +102,9 @@ RISK_DEFINITION = {
93
102
  "CredentialsExposure": "<p>Credentials Exposure actions return credentials as part of the API response , such as ecr:GetAuthorizationToken, iam:UpdateAccessKey, and others. The full list is maintained here: https://gist.github.com/kmcquade/33860a617e651104d243c324ddf7992a</p>",
94
103
  "InfrastructureModification": "",
95
104
  "AssumableByComputeService": "<p>IAM Roles can be assumed by AWS Compute Services (such as EC2, ECS, EKS, or Lambda) can present greater risk than user-defined roles, especially if the AWS Compute service is on an instance that is directly or indirectly exposed to the internet. Flagging these roles is particularly useful to penetration testers (or attackers) under certain scenarios.<br>For example, if an attacker obtains privileges to execute <code>ssm:SendCommand</code> and there are privileged EC2 instances with the SSM agent installed, they can effectively have the privileges of those EC2 instances.</p>",
105
+ "AssumableByCrossAccountPrincipal": "<p>IAM Roles that can be assumed from other AWS accounts can present a greater risk than roles that can only be assumed within the same AWS account. This is especially true if the trusting account is not owned by your organization.</p>",
106
+ "AssumableByAnyPrincipal": "<p>IAM Roles that can be assumed by any principal (i.e. Principal: '*') present a very high risk and should be remediated immediately.</p>",
107
+ "AssumableByAnyPrincipalWithConditions": "<p>IAM Roles that can be assumed by any principal (i.e. Principal: '*') but have conditions present can lead to unexpected outcomes. The conditions should be carefully reviewed to ensure they are not overly permissive.</p>",
96
108
  }
97
109
 
98
110
  PRIVILEGE_ESCALATION_METHODS = {
@@ -31,4 +31,9 @@ include-actions:
31
31
  # Write actions to include from the results, such as kms:Decrypt
32
32
  exclude-actions:
33
33
  - ""
34
-
34
+ # Known AWS Account IDs to exclude from assume role analysis.
35
+ # Add your organization's account IDs and any thrid party vendor's
36
+ # account IDs here to avoid false positives.
37
+ # https://github.com/fwdcloudsec/known_aws_accounts/blob/main/accounts.yaml
38
+ known-accounts:
39
+ - ""
@@ -28,6 +28,7 @@ class Exclusions:
28
28
  self.users = self._users()
29
29
  self.groups = self._groups()
30
30
  self.policies = self._policies()
31
+ self.known_accounts = self._known_accounts()
31
32
 
32
33
  def _roles(self) -> list[str]:
33
34
  provided_roles = self.config.get("roles", [])
@@ -65,6 +66,12 @@ class Exclusions:
65
66
  always_exclude_actions = [x.lower() for x in exclude_actions]
66
67
  return always_exclude_actions
67
68
 
69
+ def _known_accounts(self) -> set[str]:
70
+ provided_known_accounts = self.config.get("known-accounts", [])
71
+ # Normalize for comparisons - remove empty strings
72
+ known_accounts = {account for account in provided_known_accounts if account.strip()}
73
+ return known_accounts
74
+
68
75
  def is_action_always_included(self, action_in_question: str) -> bool | str:
69
76
  """
70
77
  Supply an IAM action, and get a decision about whether or not it is excluded.
@@ -7,6 +7,7 @@
7
7
  # or https://opensource.org/licenses/BSD-3-Clause
8
8
  from __future__ import annotations
9
9
 
10
+ import contextlib
10
11
  import json
11
12
  import logging
12
13
  import os
@@ -20,6 +21,7 @@ from policy_sentry.querying.actions import (
20
21
  remove_actions_not_matching_access_level,
21
22
  )
22
23
  from policy_sentry.querying.all import get_all_service_prefixes
24
+ from policy_sentry.util.arns import get_account_from_arn
23
25
 
24
26
  all_service_prefixes = get_all_service_prefixes()
25
27
  logger = logging.getLogger(__name__)
@@ -169,3 +171,15 @@ def write_json_to_file(file: str, content: str) -> None:
169
171
  os.remove(file)
170
172
 
171
173
  Path(file).write_text(json.dumps(content, indent=4, default=str), encoding="utf-8")
174
+
175
+
176
+ def get_account_id_from_principal(principal: str) -> str | None:
177
+ """Return the AWS account ID for a principal ARN or bare 12-digit account identifier."""
178
+ principal_stripped = principal.strip()
179
+ if principal_stripped.startswith("arn:aws:iam::"):
180
+ with contextlib.suppress(Exception):
181
+ return get_account_from_arn(principal_stripped)
182
+ return None
183
+ if principal_stripped.isdigit() and len(principal_stripped) == 12:
184
+ return principal_stripped
185
+ return None
@@ -22,6 +22,7 @@ EXCLUSIONS_TEMPLATE_SCHEMA = Schema(
22
22
  Optional("groups"): [str],
23
23
  Optional("exclude-actions"): [str],
24
24
  Optional("include-actions"): [str],
25
+ Optional("known-accounts"): [str],
25
26
  }
26
27
  )
27
28
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: cloudsplaining
3
- Version: 0.8.0
3
+ Version: 0.8.1
4
4
  Summary: AWS IAM Security Assessment tool that identifies violations of least privilege and generates a risk-prioritized HTML report.
5
5
  Home-page: https://github.com/salesforce/cloudsplaining
6
6
  Author: Kinnaird McQuade
@@ -21,7 +21,7 @@ Classifier: Programming Language :: Python :: 3.12
21
21
  Classifier: Programming Language :: Python :: 3.13
22
22
  Classifier: License :: OSI Approved :: MIT License
23
23
  Classifier: Operating System :: OS Independent
24
- Requires-Python: >=3.8
24
+ Requires-Python: >=3.9
25
25
  Description-Content-Type: text/markdown
26
26
  License-File: LICENSE
27
27
 
@@ -68,5 +68,5 @@ setuptools.setup(
68
68
  entry_points={"console_scripts": "cloudsplaining=cloudsplaining.bin.cli:main"},
69
69
  zip_safe=True,
70
70
  keywords="aws iam roles policy policies privileges security",
71
- python_requires=">=3.8",
71
+ python_requires=">=3.9",
72
72
  )
@@ -1,91 +0,0 @@
1
- """Represents the AssumeRole Trust Policy. This is mainly used for identifying whether roles are assumable via AWS compute services."""
2
-
3
- # Copyright (c) 2020, salesforce.com, inc.
4
- # All rights reserved.
5
- # Licensed under the BSD 3-Clause license.
6
- # For full license text, see the LICENSE file in the repo root
7
- # or https://opensource.org/licenses/BSD-3-Clause
8
- from __future__ import annotations
9
-
10
- import logging
11
- from typing import Any
12
-
13
- from cloudsplaining.scan.resource_policy_document import (
14
- ResourcePolicyDocument,
15
- ResourceStatement,
16
- )
17
- from cloudsplaining.shared.constants import SERVICE_PREFIXES_WITH_COMPUTE_ROLES
18
-
19
- logger = logging.getLogger(__name__)
20
-
21
-
22
- class AssumeRolePolicyDocument(ResourcePolicyDocument):
23
- """Holds the AssumeRole/Trust Policy document
24
-
25
- It is a specialized version of a Resource-based policy
26
- """
27
-
28
- def __init__(self, policy: dict[str, Any]) -> None:
29
- statement_structure = policy.get("Statement", [])
30
- self.policy = policy
31
- # We would actually need to define a proper base class with a generic type for statements
32
- self.statements: list[AssumeRoleStatement] = [] # type:ignore[assignment]
33
- # leaving here but excluding from tests because IAM Policy grammar dictates that it must be a list
34
- if not isinstance(statement_structure, list): # pragma: no cover
35
- statement_structure = [statement_structure]
36
-
37
- for statement in statement_structure:
38
- self.statements.append(AssumeRoleStatement(statement))
39
-
40
- @property
41
- def role_assumable_by_compute_services(self) -> list[str]:
42
- """Determines whether or not the role is assumed from a compute service, and if so which ones."""
43
- assumable_by_compute_services = []
44
- for statement in self.statements:
45
- if statement.role_assumable_by_compute_services:
46
- assumable_by_compute_services.extend(statement.role_assumable_by_compute_services)
47
- return assumable_by_compute_services
48
-
49
-
50
- class AssumeRoleStatement(ResourceStatement):
51
- """
52
- Statements in an AssumeRole/Trust Policy document
53
- """
54
-
55
- def __init__(self, statement: dict[str, Any]) -> None:
56
- super().__init__(statement=statement)
57
-
58
- # self.not_principal = statement.get("NotPrincipal")
59
- if statement.get("NotPrincipal"):
60
- logger.critical( # pragma: no cover
61
- "NotPrincipal is used in the IAM AssumeRole Trust Policy. "
62
- "This should NOT be used. We suggest reviewing it ASAP."
63
- )
64
-
65
- def _assume_role_actions(self) -> list[str]:
66
- """Verifies that this is limited to just sts:AssumeRole"""
67
- actions = self.statement.get("Action", [])
68
- if not actions:
69
- logger.debug("The AssumeRole Policy has no actions in it.")
70
- return []
71
-
72
- if isinstance(actions, list):
73
- return actions
74
-
75
- return [actions]
76
-
77
- @property
78
- def role_assumable_by_compute_services(self) -> list[str]:
79
- """Determines whether or not the role is assumed from a compute service, and if so which ones."""
80
- # sts:AssumeRole must be there
81
- lowercase_actions = [x.lower() for x in self.actions]
82
- if "sts:AssumeRole".lower() not in lowercase_actions:
83
- return []
84
-
85
- assumable_by_compute_services = []
86
- for principal in self.principals:
87
- if principal.endswith(".amazonaws.com"):
88
- service_prefix_to_evaluate = principal.split(".")[0]
89
- if service_prefix_to_evaluate in SERVICE_PREFIXES_WITH_COMPUTE_ROLES:
90
- assumable_by_compute_services.append(service_prefix_to_evaluate)
91
- return assumable_by_compute_services
File without changes
File without changes
File without changes