cloudsplaining 0.6.1__tar.gz → 0.6.3__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 (119) hide show
  1. {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/PKG-INFO +14 -4
  2. {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/README.md +5 -1
  3. {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/bin/version.py +1 -1
  4. {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/scan/assume_role_policy_document.py +15 -59
  5. {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/scan/authorization_details.py +1 -1
  6. {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/scan/group_details.py +12 -6
  7. {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/scan/managed_policy_detail.py +3 -2
  8. cloudsplaining-0.6.3/cloudsplaining/scan/resource_policy_document.py +260 -0
  9. {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/scan/role_details.py +11 -6
  10. {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/scan/statement_detail.py +18 -12
  11. {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/scan/user_details.py +11 -6
  12. {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/shared/constants.py +35 -22
  13. cloudsplaining-0.6.3/cloudsplaining/shared/exceptions.py +3 -0
  14. {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/shared/utils.py +11 -3
  15. {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining.egg-info/PKG-INFO +14 -4
  16. {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining.egg-info/SOURCES.txt +2 -0
  17. {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining.egg-info/requires.txt +1 -1
  18. {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/setup.py +8 -2
  19. {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/MANIFEST.in +0 -0
  20. {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/__init__.py +0 -0
  21. {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/bin/__init__.py +0 -0
  22. {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/bin/cli.py +0 -0
  23. {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/command/__init__.py +0 -0
  24. {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/command/create_exclusions_file.py +0 -0
  25. {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/command/create_multi_account_config_file.py +0 -0
  26. {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/command/download.py +0 -0
  27. {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/command/expand_policy.py +0 -0
  28. {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/command/scan.py +0 -0
  29. {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/command/scan_multi_account.py +0 -0
  30. {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/command/scan_policy_file.py +0 -0
  31. {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/__init__.py +0 -0
  32. {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/dist/index.html +0 -0
  33. {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/dist/js/chunk-vendors.js +0 -0
  34. {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/dist/js/index.js +0 -0
  35. {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/policy_finding.py +0 -0
  36. {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/public/index.html +0 -0
  37. {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/report.py +0 -0
  38. {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/src/App.vue +0 -0
  39. {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/src/assets/1-overview.md +0 -0
  40. {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/src/assets/2-triage-guidance.md +0 -0
  41. {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/src/assets/3-remediation-guidance.md +0 -0
  42. {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/src/assets/4-validation.md +0 -0
  43. {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/src/assets/definition-assumable-by-compute-service.md +0 -0
  44. {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/src/assets/definition-credentials-exposure.md +0 -0
  45. {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/src/assets/definition-data-exfiltration.md +0 -0
  46. {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/src/assets/definition-infrastructure-modification.md +0 -0
  47. {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/src/assets/definition-privilege-escalation.md +0 -0
  48. {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/src/assets/definition-resource-exposure.md +0 -0
  49. {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/src/assets/definition-service-wildcard.md +0 -0
  50. {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/src/assets/glossary.md +0 -0
  51. {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/src/assets/how-do-i-validate-results.md +0 -0
  52. {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/src/assets/identifying-false-positives.md +0 -0
  53. {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/src/assets/logo.png +0 -0
  54. {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/src/assets/summary.md +0 -0
  55. {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/src/assets/what-should-i-do.md +0 -0
  56. {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/src/components/Appendix.vue +0 -0
  57. {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/src/components/Button.vue +0 -0
  58. {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/src/components/Glossary.vue +0 -0
  59. {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/src/components/Guidance.vue +0 -0
  60. {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/src/components/InlinePolicies.vue +0 -0
  61. {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/src/components/LinkToFinding.vue +0 -0
  62. {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/src/components/ManagedPolicies.vue +0 -0
  63. {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/src/components/PolicyTable.vue +0 -0
  64. {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/src/components/Principals.vue +0 -0
  65. {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/src/components/ReportMetadata.vue +0 -0
  66. {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/src/components/Summary.vue +0 -0
  67. {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/src/components/TaskTable.vue +0 -0
  68. {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/src/components/charts/SummaryFindings.vue +0 -0
  69. {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/src/components/finding/AssumeRoleDetails.vue +0 -0
  70. {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/src/components/finding/FindingCard.vue +0 -0
  71. {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/src/components/finding/FindingDetails.vue +0 -0
  72. {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/src/components/finding/PolicyDocumentDetails.vue +0 -0
  73. {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/src/components/finding/PrivilegeEscalationDetails.vue +0 -0
  74. {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/src/components/finding/PrivilegeEscalationFormat.vue +0 -0
  75. {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/src/components/finding/RiskAlertIndicators.vue +0 -0
  76. {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/src/components/finding/StandardRiskDetails.vue +0 -0
  77. {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/src/components/principals/PrincipalMetadata.vue +0 -0
  78. {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/src/components/principals/RisksPerPrincipal.vue +0 -0
  79. {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/src/main.js +0 -0
  80. {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/src/routes/routes.js +0 -0
  81. {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/src/sampleData.js +0 -0
  82. {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/src/test/groups-test.js +0 -0
  83. {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/src/test/inline-policies-test.js +0 -0
  84. {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/src/test/managed-policies-test.js +0 -0
  85. {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/src/test/other-test.js +0 -0
  86. {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/src/test/principals-test.js +0 -0
  87. {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/src/test/roles-test.js +0 -0
  88. {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/src/test/task-table-test.js +0 -0
  89. {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/src/util/glossary.js +0 -0
  90. {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/src/util/groups.js +0 -0
  91. {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/src/util/inline-policies.js +0 -0
  92. {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/src/util/managed-policies.js +0 -0
  93. {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/src/util/other.js +0 -0
  94. {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/src/util/principals.js +0 -0
  95. {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/src/util/roles.js +0 -0
  96. {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/src/util/task-table.js +0 -0
  97. {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/src/views/Appendices.vue +0 -0
  98. {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/src/views/AwsPolicies.vue +0 -0
  99. {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/src/views/CustomerPolicies.vue +0 -0
  100. {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/src/views/Guidance.vue +0 -0
  101. {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/src/views/IamPrincipals.vue +0 -0
  102. {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/src/views/InlinePolicies.vue +0 -0
  103. {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/src/views/Summary.vue +0 -0
  104. {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/template.html +0 -0
  105. {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/py.typed +0 -0
  106. {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/scan/__init__.py +0 -0
  107. {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/scan/inline_policy.py +0 -0
  108. {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/scan/policy_document.py +0 -0
  109. {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/shared/__init__.py +0 -0
  110. {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/shared/aws_login.py +0 -0
  111. {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/shared/default-exclusions.yml +0 -0
  112. {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/shared/exclusions.py +0 -0
  113. {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/shared/multi-account-config.yml +0 -0
  114. {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/shared/validation.py +0 -0
  115. {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining.egg-info/dependency_links.txt +0 -0
  116. {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining.egg-info/entry_points.txt +0 -0
  117. {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining.egg-info/top_level.txt +0 -0
  118. {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining.egg-info/zip-safe +0 -0
  119. {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: cloudsplaining
3
- Version: 0.6.1
3
+ Version: 0.6.3
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
@@ -11,7 +11,9 @@ Project-URL: Example Report, https://opensource.salesforce.com/cloudsplaining
11
11
  Project-URL: Code, https://github.com/salesforce/cloudsplaining/
12
12
  Project-URL: Twitter, https://twitter.com/kmcquade3
13
13
  Project-URL: Red Team Report, https://opensource.salesforce.com/policy_sentry
14
- Description: Cloudsplaining
14
+ Description: ## NOTE: This repo/project has been restored by Salesforce.
15
+
16
+ Cloudsplaining
15
17
  --------------
16
18
 
17
19
  Cloudsplaining is an AWS IAM Security Assessment tool that identifies violations of least privilege and generates a risk-prioritized HTML report.
@@ -20,7 +22,9 @@ Description: Cloudsplaining
20
22
  [![Documentation Status](https://readthedocs.org/projects/cloudsplaining/badge/?version=latest)](https://cloudsplaining.readthedocs.io/en/latest/?badge=latest)
21
23
  [![Join the chat at https://gitter.im/cloudsplaining](https://badges.gitter.im/cloudsplaining.svg)](https://gitter.im/cloudsplaining?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
22
24
  [![Twitter](https://img.shields.io/twitter/url/https/twitter.com/kmcquade3.svg?style=social&label=Follow%20the%20author)](https://twitter.com/kmcquade3)
23
- [![Downloads](https://pepy.tech/badge/cloudsplaining)](https://pepy.tech/project/cloudsplaining)
25
+ [![PyPI](https://img.shields.io/pypi/v/cloudsplaining)](https://pypi.org/project/cloudsplaining)
26
+ [![Python Version](https://img.shields.io/pypi/pyversions/cloudsplaining)](#)
27
+ [![Downloads](https://static.pepy.tech/badge/cloudsplaining)](https://pepy.tech/project/cloudsplaining)
24
28
 
25
29
  * [Example report](https://opensource.salesforce.com/cloudsplaining/)
26
30
 
@@ -397,7 +401,13 @@ Description: Cloudsplaining
397
401
 
398
402
  Keywords: aws iam roles policy policies privileges security
399
403
  Platform: UNKNOWN
400
- Classifier: Programming Language :: Python :: 3
404
+ Classifier: Programming Language :: Python :: 3 :: Only
405
+ Classifier: Programming Language :: Python :: 3.7
406
+ Classifier: Programming Language :: Python :: 3.8
407
+ Classifier: Programming Language :: Python :: 3.9
408
+ Classifier: Programming Language :: Python :: 3.10
409
+ Classifier: Programming Language :: Python :: 3.11
410
+ Classifier: Programming Language :: Python :: 3.12
401
411
  Classifier: License :: OSI Approved :: MIT License
402
412
  Classifier: Operating System :: OS Independent
403
413
  Requires-Python: >=3.6
@@ -1,3 +1,5 @@
1
+ ## NOTE: This repo/project has been restored by Salesforce.
2
+
1
3
  Cloudsplaining
2
4
  --------------
3
5
 
@@ -7,7 +9,9 @@ Cloudsplaining is an AWS IAM Security Assessment tool that identifies violations
7
9
  [![Documentation Status](https://readthedocs.org/projects/cloudsplaining/badge/?version=latest)](https://cloudsplaining.readthedocs.io/en/latest/?badge=latest)
8
10
  [![Join the chat at https://gitter.im/cloudsplaining](https://badges.gitter.im/cloudsplaining.svg)](https://gitter.im/cloudsplaining?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
9
11
  [![Twitter](https://img.shields.io/twitter/url/https/twitter.com/kmcquade3.svg?style=social&label=Follow%20the%20author)](https://twitter.com/kmcquade3)
10
- [![Downloads](https://pepy.tech/badge/cloudsplaining)](https://pepy.tech/project/cloudsplaining)
12
+ [![PyPI](https://img.shields.io/pypi/v/cloudsplaining)](https://pypi.org/project/cloudsplaining)
13
+ [![Python Version](https://img.shields.io/pypi/pyversions/cloudsplaining)](#)
14
+ [![Downloads](https://static.pepy.tech/badge/cloudsplaining)](https://pepy.tech/project/cloudsplaining)
11
15
 
12
16
  * [Example report](https://opensource.salesforce.com/cloudsplaining/)
13
17
 
@@ -1,2 +1,2 @@
1
1
  # pylint: disable=missing-module-docstring
2
- __version__ = '0.6.1'
2
+ __version__ = '0.6.3'
@@ -4,23 +4,31 @@
4
4
  # Licensed under the BSD 3-Clause license.
5
5
  # For full license text, see the LICENSE file in the repo root
6
6
  # or https://opensource.org/licenses/BSD-3-Clause
7
+ from __future__ import annotations
8
+
7
9
  import logging
8
10
  from typing import Dict, Any, List
9
11
 
12
+ from cloudsplaining.scan.resource_policy_document import (
13
+ ResourcePolicyDocument,
14
+ ResourceStatement,
15
+ )
10
16
  from cloudsplaining.shared.constants import SERVICE_PREFIXES_WITH_COMPUTE_ROLES
11
17
 
12
18
  logger = logging.getLogger(__name__)
13
19
 
14
20
 
15
- class AssumeRolePolicyDocument:
16
- """
17
- Holds the AssumeRole/Trust Policy document
21
+ class AssumeRolePolicyDocument(ResourcePolicyDocument):
22
+ """Holds the AssumeRole/Trust Policy document
23
+
24
+ It is a specialized version of a Resource-based policy
18
25
  """
19
26
 
20
- def __init__(self, policy: Dict[str, Any]) -> None:
27
+ def __init__(self, policy: dict[str, Any]) -> None:
21
28
  statement_structure = policy.get("Statement", [])
22
29
  self.policy = policy
23
- self.statements = []
30
+ # We would actually need to define a proper base class with a generic type for statements
31
+ self.statements: list[AssumeRoleStatement] = [] # type:ignore[assignment]
24
32
  # leaving here but excluding from tests because IAM Policy grammar dictates that it must be a list
25
33
  if not isinstance(statement_structure, list): # pragma: no cover
26
34
  statement_structure = [statement_structure]
@@ -28,11 +36,6 @@ class AssumeRolePolicyDocument:
28
36
  for statement in statement_structure:
29
37
  self.statements.append(AssumeRoleStatement(statement))
30
38
 
31
- @property
32
- def json(self) -> Dict[str, Any]:
33
- """Return the AssumeRole Policy in JSON"""
34
- return self.policy
35
-
36
39
  @property
37
40
  def role_assumable_by_compute_services(self) -> List[str]:
38
41
  """Determines whether or not the role is assumed from a compute service, and if so which ones."""
@@ -45,17 +48,13 @@ class AssumeRolePolicyDocument:
45
48
  return assumable_by_compute_services
46
49
 
47
50
 
48
- class AssumeRoleStatement:
51
+ class AssumeRoleStatement(ResourceStatement):
49
52
  """
50
53
  Statements in an AssumeRole/Trust Policy document
51
54
  """
52
55
 
53
56
  def __init__(self, statement: Dict[str, Any]) -> None:
54
- self.json = statement
55
- self.statement = statement
56
- self.effect = statement["Effect"]
57
- self.actions = self._assume_role_actions()
58
- self.principals = self._principals()
57
+ super().__init__(statement=statement)
59
58
 
60
59
  # self.not_principal = statement.get("NotPrincipal")
61
60
  if statement.get("NotPrincipal"):
@@ -76,49 +75,6 @@ class AssumeRoleStatement:
76
75
 
77
76
  return [actions]
78
77
 
79
- def _principals(self) -> List[str]:
80
- """Extracts all principals from IAM statement.
81
- Should handle these cases:
82
- "Principal": "value"
83
- "Principal": ["value"]
84
- "Principal": { "AWS": "value" }
85
- "Principal": { "AWS": ["value", "value"] }
86
- "Principal": { "Federated": "value" }
87
- "Principal": { "Federated": ["value", "value"] }
88
- "Principal": { "Service": "value" }
89
- "Principal": { "Service": ["value", "value"] }
90
- Return: Set of principals
91
- """
92
- principals: List[str] = []
93
- principal = self.statement.get("Principal", None)
94
- if not principal:
95
- # It is possible not to define a principal, AWS ignores these statements.
96
- return principals # pragma: no cover
97
-
98
- if isinstance(principal, dict):
99
-
100
- if "AWS" in principal:
101
- if isinstance(principal["AWS"], list):
102
- principals.extend(principal["AWS"])
103
- else:
104
- principals.append(principal["AWS"])
105
-
106
- if "Federated" in principal:
107
- if isinstance(principal["Federated"], list):
108
- principals.extend(principal["Federated"])
109
- else:
110
- principals.append(principal["Federated"])
111
-
112
- if "Service" in principal:
113
- if isinstance(principal["Service"], list):
114
- principals.extend(principal["Service"])
115
- else:
116
- principals.append(principal["Service"])
117
- else:
118
- principals.append(principal)
119
- # principals = list(principals).sort()
120
- return principals
121
-
122
78
  @property
123
79
  def role_assumable_by_compute_services(self) -> List[str]:
124
80
  """Determines whether or not the role is assumed from a compute service, and if so which ones."""
@@ -110,7 +110,7 @@ class AuthorizationDetails:
110
110
  return results
111
111
 
112
112
  @property
113
- def links(self) -> Dict[str, str]:
113
+ def links(self) -> Dict[str, str | None]:
114
114
  """Return a dictionary of the action names as keys and their API documentation links as values"""
115
115
  results = {}
116
116
  unique_action_names = set()
@@ -2,11 +2,14 @@
2
2
  from __future__ import annotations
3
3
 
4
4
  import json
5
+ import logging
5
6
  from typing import Optional, Dict, Any, List
6
7
 
7
8
  from cloudsplaining.scan.inline_policy import InlinePolicy
8
9
  from cloudsplaining.scan.managed_policy_detail import ManagedPolicyDetails
9
10
  from cloudsplaining.scan.statement_detail import StatementDetail
11
+ from cloudsplaining.shared import utils
12
+ from cloudsplaining.shared.exceptions import NotFoundException
10
13
  from cloudsplaining.shared.utils import (
11
14
  is_aws_managed,
12
15
  get_full_policy_path,
@@ -184,12 +187,15 @@ class GroupDetail:
184
187
  or exclusions.is_policy_excluded(get_full_policy_path(arn))
185
188
  or exclusions.is_policy_excluded(get_policy_name(arn))
186
189
  ):
187
- attached_managed_policy_details = policy_details.get_policy_detail(
188
- arn
189
- )
190
- self.attached_managed_policies.append(
191
- attached_managed_policy_details
192
- )
190
+ try:
191
+ attached_managed_policy_details = (
192
+ policy_details.get_policy_detail(arn)
193
+ )
194
+ self.attached_managed_policies.append(
195
+ attached_managed_policy_details
196
+ )
197
+ except NotFoundException as e:
198
+ utils.print_red(f"\tError in group {self.group_name}: {e}")
193
199
 
194
200
  self.iam_data: Dict[str, Dict[Any, Any]] = {
195
201
  "groups": {},
@@ -11,6 +11,7 @@ from typing import Dict, Any, List
11
11
 
12
12
  from policy_sentry.util.arns import get_account_from_arn
13
13
  from cloudsplaining.scan.policy_document import PolicyDocument
14
+ from cloudsplaining.shared.exceptions import NotFoundException
14
15
  from cloudsplaining.shared.utils import get_full_policy_path, is_aws_managed
15
16
  from cloudsplaining.shared.constants import ISSUE_SEVERITY, RISK_DEFINITION
16
17
  from cloudsplaining.shared.exclusions import (
@@ -95,7 +96,7 @@ class ManagedPolicyDetails:
95
96
  for policy_detail in self.policy_details:
96
97
  if policy_detail.arn == arn:
97
98
  return policy_detail
98
- raise Exception("Managed Policy ARN %s not found.", arn)
99
+ raise NotFoundException(f"Managed Policy ARN {arn} not found.")
99
100
 
100
101
  @property
101
102
  def all_infrastructure_modification_actions(self) -> List[str]:
@@ -246,7 +247,7 @@ class ManagedPolicy:
246
247
  if is_aws_managed(self.arn):
247
248
  return "N/A"
248
249
  else:
249
- return get_account_from_arn(self.arn) # type: ignore
250
+ return get_account_from_arn(self.arn)
250
251
 
251
252
  def getFindingLinks(self, findings: List[Dict[str, Any]]) -> Dict[Any, str]:
252
253
  links = {}
@@ -0,0 +1,260 @@
1
+ """Represents the Resource-based policy"""
2
+ from __future__ import annotations
3
+
4
+ import logging
5
+ import re
6
+ from typing import Any
7
+
8
+ from policy_sentry.util.arns import ARN
9
+
10
+
11
+ CONDITION_KEY_CATEGORIES = {
12
+ "aws:sourcearn": "arn",
13
+ "aws:principalarn": "arn",
14
+ "aws:sourceowner": "account",
15
+ "aws:sourceaccount": "account",
16
+ "aws:principalaccount": "account",
17
+ "aws:principalorgid": "organization",
18
+ "aws:principalorgpaths": "organization",
19
+ "kms:calleraccount": "account",
20
+ "aws:userid": "userid",
21
+ "aws:sourceip": "cidr",
22
+ "aws:sourcevpc": "vpc",
23
+ "aws:sourcevpce": "vpce",
24
+ # a key for SAML Federation trust policy.
25
+ # https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_create_for-idp_saml.html
26
+ # https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_providers_create_saml_assertions.html
27
+ "saml:aud": "saml-endpoint",
28
+ }
29
+ RELEVANT_CONDITION_OPERATORS_PATTERN = re.compile(
30
+ "((ForAllValues|ForAnyValue):)?(ARN(Equals|Like)|String(Equals|Like)(IgnoreCase)?|IpAddress)(IfExists)?",
31
+ re.IGNORECASE,
32
+ )
33
+
34
+ logger = logging.getLogger(__name__)
35
+
36
+
37
+ class ResourcePolicyDocument:
38
+ """Holds the Resource Policy document"""
39
+
40
+ def __init__(self, policy: dict[str, Any]) -> None:
41
+ statement_structure = policy.get("Statement", [])
42
+ self.policy = policy
43
+ self.statements = []
44
+ # leaving here but excluding from tests because IAM Policy grammar dictates that it must be a list
45
+ if not isinstance(statement_structure, list): # pragma: no cover
46
+ statement_structure = [statement_structure]
47
+
48
+ for statement in statement_structure:
49
+ self.statements.append(ResourceStatement(statement))
50
+
51
+ @property
52
+ def json(self) -> dict[str, Any]:
53
+ """Return the Resource Policy in JSON"""
54
+ return self.policy
55
+
56
+ @property
57
+ def internet_accessible_actions(self) -> list[str]:
58
+ result = []
59
+ for statement in self.statements:
60
+ actions = statement.internet_accessible_actions
61
+ if actions:
62
+ result.extend(actions)
63
+
64
+ return result
65
+
66
+
67
+ class ResourceStatement:
68
+ """Statements in a Resource Policy document"""
69
+
70
+ def __init__(self, statement: dict[str, Any]) -> None:
71
+ self.json = statement
72
+ self.statement = statement
73
+ self.effect = statement["Effect"]
74
+ self.actions = self._actions()
75
+ self.principals = self._principals()
76
+ self.conditions = self._conditions()
77
+
78
+ def _actions(self) -> list[str]:
79
+ """Extracts all actions"""
80
+ actions = self.statement.get("Action", [])
81
+ if not actions:
82
+ return []
83
+
84
+ if isinstance(actions, list):
85
+ return actions
86
+
87
+ return [actions]
88
+
89
+ def _principals(self) -> list[str]:
90
+ """Extracts all principals from IAM statement.
91
+ Should handle these cases:
92
+ "Principal": "value"
93
+ "Principal": ["value"]
94
+ "Principal": { "AWS": "value" }
95
+ "Principal": { "AWS": ["value", "value"] }
96
+ "Principal": { "Federated": "value" }
97
+ "Principal": { "Federated": ["value", "value"] }
98
+ "Principal": { "Service": "value" }
99
+ "Principal": { "Service": ["value", "value"] }
100
+ Return: Set of principals
101
+ """
102
+ principals: list[str] = []
103
+ principal = self.statement.get("Principal", None)
104
+ if not principal:
105
+ # It is possible not to define a principal, AWS ignores these statements.
106
+ return principals # pragma: no cover
107
+
108
+ if isinstance(principal, dict):
109
+ if "AWS" in principal:
110
+ if isinstance(principal["AWS"], list):
111
+ principals.extend(principal["AWS"])
112
+ else:
113
+ principals.append(principal["AWS"])
114
+
115
+ if "Federated" in principal:
116
+ if isinstance(principal["Federated"], list):
117
+ principals.extend(principal["Federated"])
118
+ else:
119
+ principals.append(principal["Federated"])
120
+
121
+ if "Service" in principal:
122
+ if isinstance(principal["Service"], list):
123
+ principals.extend(principal["Service"])
124
+ else:
125
+ principals.append(principal["Service"])
126
+ else:
127
+ principals.append(principal)
128
+
129
+ return principals
130
+
131
+ # Adapted version of policyuniverse's _condition_entries, here:
132
+ # https://github.com/Netflix-Skunkworks/policyuniverse/blob/master/policyuniverse/statement.py#L146
133
+ def _conditions(self) -> list[tuple[str, Any]]:
134
+ """Extracts any ARNs, Account Numbers, UserIDs, Usernames, CIDRs, VPCs, and VPC Endpoints from a condition block.
135
+
136
+ Ignores any negated condition operators like StringNotLike.
137
+ Ignores weak condition keys like referer, date, etc.
138
+
139
+ Reason: A condition is meant to limit the principal in a statement. Often, resource policies use a wildcard principal
140
+ and rely exclusively on the Condition block to limit access.
141
+
142
+ We would want to alert if the Condition had no limitations (like a non-existent Condition block), or very weak
143
+ limitations. Any negation would be weak, and largely equivelant to having no condition block whatsoever.
144
+
145
+ The alerting code that relies on this data must ensure the condition has at least one of the following:
146
+ - A limiting ARN
147
+ - Account Identifier
148
+ - AWS Organization Principal Org ID
149
+ - User ID
150
+ - Source IP / CIDR
151
+ - VPC
152
+ - VPC Endpoint
153
+
154
+ https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_condition-keys.html
155
+ """
156
+
157
+ conditions: list[tuple[str, Any]] = []
158
+ condition = self.statement.get("Condition")
159
+ if not condition:
160
+ return conditions
161
+
162
+ for condition_operator, condition_context in condition.items():
163
+ if RELEVANT_CONDITION_OPERATORS_PATTERN.match(condition_operator):
164
+ for key, value in condition_context.items():
165
+ key_lower = key.lower()
166
+ if key_lower in CONDITION_KEY_CATEGORIES:
167
+ if isinstance(value, list):
168
+ conditions.extend(
169
+ (CONDITION_KEY_CATEGORIES[key_lower], v) for v in value
170
+ )
171
+ else:
172
+ conditions.append(
173
+ (CONDITION_KEY_CATEGORIES[key_lower], value)
174
+ )
175
+
176
+ return conditions
177
+
178
+ @property
179
+ def internet_accessible_actions(self) -> list[str]:
180
+ """Determines whether the actions can be used by everyone"""
181
+
182
+ # compared to policyuniverse's implementation,
183
+ # there is no need to check for the existence of 'NotPrincipal',
184
+ # because it is not support with self.effect == "Allow"
185
+ if self.effect == "Deny":
186
+ return []
187
+
188
+ for entry in self.conditions:
189
+ if self._is_condition_entry_internet_accessible(entry=entry):
190
+ return self.actions
191
+
192
+ if self.conditions:
193
+ # this means we have conditions, but they protect the policy to be accessible by everyone
194
+ return []
195
+
196
+ for principal in self.principals:
197
+ if self._arn_internet_accessible(arn=principal):
198
+ return self.actions
199
+
200
+ return []
201
+
202
+ # Adapted version of policyuniverse's _is_condition_entry_internet_accessible and the called methods, here:
203
+ # https://github.com/Netflix-Skunkworks/policyuniverse/blob/master/policyuniverse/statement.py#L301
204
+ # and onwards
205
+ def _is_condition_entry_internet_accessible(self, entry: tuple[str, Any]) -> bool:
206
+ category, condition_value = entry
207
+
208
+ if category == "arn":
209
+ return self._arn_internet_accessible(arn=condition_value)
210
+ elif category == "cidr":
211
+ return self._cidr_internet_accessible(cidr=condition_value)
212
+ elif category == "organization":
213
+ return self._organization_internet_accessible(org=condition_value)
214
+ elif category == "userid":
215
+ return self._userid_internet_accessible(userid=condition_value)
216
+
217
+ return "*" in condition_value
218
+
219
+ def _arn_internet_accessible(self, arn: str) -> bool:
220
+ if "*" == arn:
221
+ return True
222
+
223
+ if not arn.startswith("arn:"):
224
+ # probably an account ID or AWS service
225
+ return False
226
+
227
+ try:
228
+ parsed_arn = ARN(provided_arn=arn)
229
+ except Exception:
230
+ logger.info(f"ARN {arn} is not parsable")
231
+ return "*" in arn
232
+
233
+ if parsed_arn.service_prefix == "s3":
234
+ # S3 ARNs don't have account numbers
235
+ return False
236
+
237
+ if not parsed_arn.account and not parsed_arn.service_prefix:
238
+ logger.info(f"ARN {arn} is not valid")
239
+ return True
240
+
241
+ if parsed_arn.account == "*":
242
+ return True
243
+
244
+ return False
245
+
246
+ def _cidr_internet_accessible(self, cidr: str) -> bool:
247
+ return cidr.endswith("/0")
248
+
249
+ def _organization_internet_accessible(self, org: str) -> bool:
250
+ if "o-*" in org:
251
+ return True
252
+ return False
253
+
254
+ def _userid_internet_accessible(self, userid: str) -> bool:
255
+ # Trailing wildcards are okay for user IDs:
256
+ # AROAIIIIIIIIIIIIIIIII:*
257
+ if userid.find("*") == len(userid) - 1:
258
+ # note: this will also return False for a zero-length userid
259
+ return False
260
+ return True
@@ -9,6 +9,8 @@ from cloudsplaining.scan.assume_role_policy_document import AssumeRolePolicyDocu
9
9
  from cloudsplaining.scan.inline_policy import InlinePolicy
10
10
  from cloudsplaining.scan.managed_policy_detail import ManagedPolicyDetails
11
11
  from cloudsplaining.scan.statement_detail import StatementDetail
12
+ from cloudsplaining.shared import utils
13
+ from cloudsplaining.shared.exceptions import NotFoundException
12
14
  from cloudsplaining.shared.utils import (
13
15
  is_aws_managed,
14
16
  get_full_policy_path,
@@ -214,12 +216,15 @@ class RoleDetail:
214
216
  or exclusions.is_policy_excluded(get_full_policy_path(arn))
215
217
  or exclusions.is_policy_excluded(get_policy_name(arn))
216
218
  ):
217
- attached_managed_policy_details = policy_details.get_policy_detail(
218
- arn
219
- )
220
- self.attached_managed_policies.append(
221
- attached_managed_policy_details
222
- )
219
+ try:
220
+ attached_managed_policy_details = (
221
+ policy_details.get_policy_detail(arn)
222
+ )
223
+ self.attached_managed_policies.append(
224
+ attached_managed_policy_details
225
+ )
226
+ except NotFoundException as e:
227
+ utils.print_red(f"\tError in role {self.role_name}: {e}")
223
228
 
224
229
  def set_iam_data(self, iam_data: Dict[str, Dict[Any, Any]]) -> None:
225
230
  self.iam_data = iam_data
@@ -4,7 +4,7 @@ from typing import Dict, Any, List, Optional
4
4
 
5
5
  from cached_property import cached_property
6
6
 
7
- from policy_sentry.analysis.analyze import determine_actions_to_expand
7
+ from policy_sentry.analysis.expand import determine_actions_to_expand
8
8
  from policy_sentry.querying.actions import (
9
9
  remove_actions_not_matching_access_level,
10
10
  get_actions_matching_arn,
@@ -33,7 +33,12 @@ class StatementDetail:
33
33
  Analyzes individual statements within a policy
34
34
  """
35
35
 
36
- def __init__(self, statement: Dict[str, Any], flag_conditional_statements: bool = False, flag_resource_arn_statements: bool = False) -> None:
36
+ def __init__(
37
+ self,
38
+ statement: Dict[str, Any],
39
+ flag_conditional_statements: bool = False,
40
+ flag_resource_arn_statements: bool = False,
41
+ ) -> None:
37
42
  self.json = statement
38
43
  self.statement = statement
39
44
  self.effect = statement["Effect"]
@@ -78,7 +83,8 @@ class StatementDetail:
78
83
 
79
84
  def _not_action(self) -> List[str]:
80
85
  """Holds the NotAction details.
81
- We won't do anything with it - but we will flag it as something for the assessor to triage."""
86
+ We won't do anything with it - but we will flag it as something for the assessor to triage.
87
+ """
82
88
  not_action = self.statement.get("NotAction")
83
89
  if not not_action:
84
90
  return []
@@ -88,7 +94,8 @@ class StatementDetail:
88
94
 
89
95
  def _not_resource(self) -> List[str]:
90
96
  """Holds the NotResource details.
91
- We won't do anything with it - but we will flag it as something for the assessor to triage."""
97
+ We won't do anything with it - but we will flag it as something for the assessor to triage.
98
+ """
92
99
  not_resource = self.statement.get("NotResource")
93
100
  if not not_resource:
94
101
  return []
@@ -98,7 +105,7 @@ class StatementDetail:
98
105
 
99
106
  # @property
100
107
  def _not_action_effective_actions(self) -> Optional[List[str]]:
101
- """If NotAction is used, calculate the allowed actions - i.e., what it would be """
108
+ """If NotAction is used, calculate the allowed actions - i.e., what it would be"""
102
109
  effective_actions = []
103
110
  if not self.not_action:
104
111
  return None
@@ -149,7 +156,8 @@ class StatementDetail:
149
156
  @property
150
157
  def has_not_resource_with_allow(self) -> bool:
151
158
  """Per the AWS documentation, the NotResource should NEVER be used with the Allow Effect.
152
- See documentation here. https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements_notresource.html#notresource-element-combinations"""
159
+ See documentation here. https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements_notresource.html#notresource-element-combinations
160
+ """
153
161
  if self.not_resource and self.effect_allow:
154
162
  logger.warning(
155
163
  "Per the AWS documentation, the NotResource should never be used with the "
@@ -198,9 +206,8 @@ class StatementDetail:
198
206
  do not have resource constraints"""
199
207
  result = []
200
208
  if (
201
- (not self.has_resource_constraints or self.flag_resource_arn_statements) and
202
- not self.has_condition
203
- ):
209
+ not self.has_resource_constraints or self.flag_resource_arn_statements
210
+ ) and not self.has_condition:
204
211
  result = remove_actions_not_matching_access_level(
205
212
  self.restrictable_actions, "Permissions management"
206
213
  )
@@ -213,9 +220,8 @@ class StatementDetail:
213
220
  do not have resource constraints"""
214
221
  result = []
215
222
  if (
216
- (not self.has_resource_constraints or self.flag_resource_arn_statements) and
217
- not self.has_condition
218
- ):
223
+ not self.has_resource_constraints or self.flag_resource_arn_statements
224
+ ) and not self.has_condition:
219
225
  result = remove_actions_not_matching_access_level(
220
226
  self.restrictable_actions, "Write"
221
227
  )
@@ -8,6 +8,8 @@ from cloudsplaining.scan.group_details import GroupDetailList, GroupDetail
8
8
  from cloudsplaining.scan.inline_policy import InlinePolicy
9
9
  from cloudsplaining.scan.managed_policy_detail import ManagedPolicyDetails
10
10
  from cloudsplaining.scan.statement_detail import StatementDetail
11
+ from cloudsplaining.shared import utils
12
+ from cloudsplaining.shared.exceptions import NotFoundException
11
13
  from cloudsplaining.shared.utils import (
12
14
  is_aws_managed,
13
15
  get_full_policy_path,
@@ -197,12 +199,15 @@ class UserDetail:
197
199
  or exclusions.is_policy_excluded(get_full_policy_path(arn))
198
200
  or exclusions.is_policy_excluded(get_policy_name(arn))
199
201
  ):
200
- attached_managed_policy_details = policy_details.get_policy_detail(
201
- arn
202
- )
203
- self.attached_managed_policies.append(
204
- attached_managed_policy_details
205
- )
202
+ try:
203
+ attached_managed_policy_details = (
204
+ policy_details.get_policy_detail(arn)
205
+ )
206
+ self.attached_managed_policies.append(
207
+ attached_managed_policy_details
208
+ )
209
+ except NotFoundException as e:
210
+ utils.print_red(f"\tError in user {self.user_name}: {e}")
206
211
 
207
212
  def set_iam_data(self, iam_data: Dict[str, Dict[Any, Any]]) -> None:
208
213
  self.iam_data = iam_data