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.
- {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/PKG-INFO +14 -4
- {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/README.md +5 -1
- {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/bin/version.py +1 -1
- {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/scan/assume_role_policy_document.py +15 -59
- {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/scan/authorization_details.py +1 -1
- {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/scan/group_details.py +12 -6
- {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/scan/managed_policy_detail.py +3 -2
- cloudsplaining-0.6.3/cloudsplaining/scan/resource_policy_document.py +260 -0
- {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/scan/role_details.py +11 -6
- {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/scan/statement_detail.py +18 -12
- {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/scan/user_details.py +11 -6
- {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/shared/constants.py +35 -22
- cloudsplaining-0.6.3/cloudsplaining/shared/exceptions.py +3 -0
- {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/shared/utils.py +11 -3
- {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining.egg-info/PKG-INFO +14 -4
- {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining.egg-info/SOURCES.txt +2 -0
- {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining.egg-info/requires.txt +1 -1
- {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/setup.py +8 -2
- {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/MANIFEST.in +0 -0
- {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/__init__.py +0 -0
- {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/bin/__init__.py +0 -0
- {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/bin/cli.py +0 -0
- {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/command/__init__.py +0 -0
- {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/command/create_exclusions_file.py +0 -0
- {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/command/create_multi_account_config_file.py +0 -0
- {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/command/download.py +0 -0
- {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/command/expand_policy.py +0 -0
- {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/command/scan.py +0 -0
- {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/command/scan_multi_account.py +0 -0
- {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/command/scan_policy_file.py +0 -0
- {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/__init__.py +0 -0
- {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/dist/index.html +0 -0
- {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/dist/js/chunk-vendors.js +0 -0
- {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/dist/js/index.js +0 -0
- {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/policy_finding.py +0 -0
- {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/public/index.html +0 -0
- {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/report.py +0 -0
- {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/src/App.vue +0 -0
- {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/src/assets/1-overview.md +0 -0
- {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/src/assets/2-triage-guidance.md +0 -0
- {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/src/assets/3-remediation-guidance.md +0 -0
- {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/src/assets/4-validation.md +0 -0
- {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/src/assets/definition-assumable-by-compute-service.md +0 -0
- {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/src/assets/definition-credentials-exposure.md +0 -0
- {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/src/assets/definition-data-exfiltration.md +0 -0
- {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/src/assets/definition-infrastructure-modification.md +0 -0
- {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/src/assets/definition-privilege-escalation.md +0 -0
- {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/src/assets/definition-resource-exposure.md +0 -0
- {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/src/assets/definition-service-wildcard.md +0 -0
- {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/src/assets/glossary.md +0 -0
- {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/src/assets/how-do-i-validate-results.md +0 -0
- {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/src/assets/identifying-false-positives.md +0 -0
- {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/src/assets/logo.png +0 -0
- {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/src/assets/summary.md +0 -0
- {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/src/assets/what-should-i-do.md +0 -0
- {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/src/components/Appendix.vue +0 -0
- {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/src/components/Button.vue +0 -0
- {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/src/components/Glossary.vue +0 -0
- {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/src/components/Guidance.vue +0 -0
- {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/src/components/InlinePolicies.vue +0 -0
- {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/src/components/LinkToFinding.vue +0 -0
- {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/src/components/ManagedPolicies.vue +0 -0
- {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/src/components/PolicyTable.vue +0 -0
- {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/src/components/Principals.vue +0 -0
- {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/src/components/ReportMetadata.vue +0 -0
- {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/src/components/Summary.vue +0 -0
- {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/src/components/TaskTable.vue +0 -0
- {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/src/components/charts/SummaryFindings.vue +0 -0
- {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/src/components/finding/AssumeRoleDetails.vue +0 -0
- {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/src/components/finding/FindingCard.vue +0 -0
- {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/src/components/finding/FindingDetails.vue +0 -0
- {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/src/components/finding/PolicyDocumentDetails.vue +0 -0
- {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/src/components/finding/PrivilegeEscalationDetails.vue +0 -0
- {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/src/components/finding/PrivilegeEscalationFormat.vue +0 -0
- {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/src/components/finding/RiskAlertIndicators.vue +0 -0
- {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/src/components/finding/StandardRiskDetails.vue +0 -0
- {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/src/components/principals/PrincipalMetadata.vue +0 -0
- {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/src/components/principals/RisksPerPrincipal.vue +0 -0
- {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/src/main.js +0 -0
- {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/src/routes/routes.js +0 -0
- {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/src/sampleData.js +0 -0
- {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/src/test/groups-test.js +0 -0
- {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/src/test/inline-policies-test.js +0 -0
- {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/src/test/managed-policies-test.js +0 -0
- {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/src/test/other-test.js +0 -0
- {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/src/test/principals-test.js +0 -0
- {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/src/test/roles-test.js +0 -0
- {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/src/test/task-table-test.js +0 -0
- {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/src/util/glossary.js +0 -0
- {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/src/util/groups.js +0 -0
- {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/src/util/inline-policies.js +0 -0
- {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/src/util/managed-policies.js +0 -0
- {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/src/util/other.js +0 -0
- {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/src/util/principals.js +0 -0
- {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/src/util/roles.js +0 -0
- {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/src/util/task-table.js +0 -0
- {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/src/views/Appendices.vue +0 -0
- {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/src/views/AwsPolicies.vue +0 -0
- {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/src/views/CustomerPolicies.vue +0 -0
- {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/src/views/Guidance.vue +0 -0
- {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/src/views/IamPrincipals.vue +0 -0
- {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/src/views/InlinePolicies.vue +0 -0
- {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/src/views/Summary.vue +0 -0
- {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/output/template.html +0 -0
- {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/py.typed +0 -0
- {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/scan/__init__.py +0 -0
- {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/scan/inline_policy.py +0 -0
- {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/scan/policy_document.py +0 -0
- {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/shared/__init__.py +0 -0
- {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/shared/aws_login.py +0 -0
- {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/shared/default-exclusions.yml +0 -0
- {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/shared/exclusions.py +0 -0
- {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/shared/multi-account-config.yml +0 -0
- {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/shared/validation.py +0 -0
- {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining.egg-info/dependency_links.txt +0 -0
- {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining.egg-info/entry_points.txt +0 -0
- {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining.egg-info/top_level.txt +0 -0
- {cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining.egg-info/zip-safe +0 -0
- {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.
|
|
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:
|
|
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
|
[](https://cloudsplaining.readthedocs.io/en/latest/?badge=latest)
|
|
21
23
|
[](https://gitter.im/cloudsplaining?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
|
22
24
|
[](https://twitter.com/kmcquade3)
|
|
23
|
-
[](https://pypi.org/project/cloudsplaining)
|
|
26
|
+
[](#)
|
|
27
|
+
[](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
|
[](https://cloudsplaining.readthedocs.io/en/latest/?badge=latest)
|
|
8
10
|
[](https://gitter.im/cloudsplaining?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
|
9
11
|
[](https://twitter.com/kmcquade3)
|
|
10
|
-
[](https://pypi.org/project/cloudsplaining)
|
|
13
|
+
[](#)
|
|
14
|
+
[](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.
|
|
2
|
+
__version__ = '0.6.3'
|
{cloudsplaining-0.6.1 → cloudsplaining-0.6.3}/cloudsplaining/scan/assume_role_policy_document.py
RENAMED
|
@@ -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
|
-
|
|
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:
|
|
27
|
+
def __init__(self, policy: dict[str, Any]) -> None:
|
|
21
28
|
statement_structure = policy.get("Statement", [])
|
|
22
29
|
self.policy = policy
|
|
23
|
-
|
|
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
|
-
|
|
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
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
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
|
|
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)
|
|
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
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
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.
|
|
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__(
|
|
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
|
-
|
|
202
|
-
|
|
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
|
-
|
|
217
|
-
|
|
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
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
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
|