Skip to content

Commit 3a65a40

Browse files
authored
Merge pull request #17 from kmcquade/refactor/show-iam-principals
Added IAM Principals tab to the report
2 parents 71a7b44 + 70b00f4 commit 3a65a40

28 files changed

+4909
-13567
lines changed

CHANGELOG.md

+5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
# CHANGELOG
22

3+
## 0.0.7 (2020-05-03)
4+
* Added separate tab for IAM Principals
5+
* HTML Report improvements - using tabs now
6+
* Changed the naming of some objects to make the object naming more in line with the AWS IAM API Data Types. https://docs.aws.amazon.com/IAM/latest/APIReference/API_Types.html
7+
38
## 0.0.6 (2020-05-01)
49
* Fix `exclude-actions` in the exclusions file - it was not being respected before.
510
* Add a recursive scanning option.

cloudsplaining/bin/cloudsplaining

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
"""
88
Cloudsplaining is an AWS IAM Assessment tool that identifies violations of least privilege and generates a risk-prioritized HTML report with a triage worksheet.
99
"""
10-
__version__ = "0.0.6"
10+
__version__ = "0.0.7"
1111
import click
1212
from cloudsplaining import command
1313

cloudsplaining/command/scan.py

+8
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,8 @@ def scan_account_authorization_file(input_file, exclusions_cfg, output, all_acce
118118
exclusions_cfg, modify_only=True
119119
)
120120

121+
principal_policy_mapping = authorization_details.principal_policy_mapping
122+
121123
account_name = Path(input_file).stem
122124

123125
# Lazy method to get an account ID
@@ -145,10 +147,16 @@ def scan_account_authorization_file(input_file, exclusions_cfg, output, all_acce
145147
raw_data_filepath = write_results_data_file(results, raw_data_file)
146148
print(f"Raw data file saved: {str(raw_data_filepath)}")
147149

150+
# Principal policy mapping
151+
principal_policy_mapping_file = os.path.join(output, f"iam-principals-{account_name}.json")
152+
principal_policy_mapping_filepath = write_results_data_file(principal_policy_mapping, principal_policy_mapping_file)
153+
print(f"Principals data file saved: {str(principal_policy_mapping_filepath)}")
154+
148155
print("Creating the HTML Report")
149156
generate_html_report(
150157
account_metadata,
151158
results,
159+
principal_policy_mapping,
152160
output_directory,
153161
exclusions_cfg,
154162
skip_open_report=skip_open_report,

cloudsplaining/output/findings.py

+15-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
# For full license text, see the LICENSE file in the repo root
66
# or https://opensource.org/licenses/BSD-3-Clause
77
import logging
8+
from operator import itemgetter
89
from policy_sentry.util.arns import get_account_from_arn, get_resource_string
910
from cloudsplaining.shared.constants import READ_ONLY_DATA_LEAK_ACTIONS
1011
from cloudsplaining.shared.exclusions import is_name_excluded
@@ -38,6 +39,8 @@ def json(self):
3839
these_findings = []
3940
for finding in self.findings:
4041
these_findings.append(finding.json)
42+
# sort it
43+
these_findings = sorted(these_findings, key=itemgetter("PolicyName"))
4144
return these_findings
4245

4346
def __len__(self):
@@ -157,7 +160,8 @@ def data_leak_actions(self):
157160

158161
@property
159162
def json(self):
160-
"""Return the JSON representation of the Finding. This is used in the report output and the results data file."""
163+
"""Return the JSON representation of the Finding.
164+
This is used in the report output and the results data file."""
161165
result = {
162166
"AccountID": self.account_id,
163167
"ManagedBy": self.managed_by,
@@ -182,3 +186,13 @@ def json(self):
182186
# "TaggingActions": self.policy_document.tagging_actions_without_constraints,
183187
}
184188
return result
189+
190+
191+
class FindingsPrincipalsMapping:
192+
"""Holds a mapping between AuthorizationDetails.principal_policy_mapping and Findings"""
193+
194+
def __init__(self, findings, principal_policy_mapping):
195+
# TODO: Figure out the Findings vs Principals Mapping thing later.
196+
print(findings)
197+
print(principal_policy_mapping)
198+
print("Figure this out later")

cloudsplaining/output/html_report.py

+11-5
Original file line numberDiff line numberDiff line change
@@ -16,21 +16,21 @@
1616

1717

1818
def generate_html_report(
19-
account_metadata, results, output_directory, exclusions_cfg, skip_open_report=False
19+
account_metadata, results, principal_policy_mapping, output_directory, exclusions_cfg, skip_open_report=False
2020
):
2121
"""Create IAM HTML report"""
2222

2323
account_id = account_metadata.get("account_id")
2424
account_name = account_metadata.get("account_name")
2525
html_output_file = os.path.join(output_directory, f"iam-report-{account_name}.html")
26-
sorted_results = sorted(results, key=lambda i: i["PolicyName"])
26+
# sorted_results = sorted(results, key=lambda i: i["PolicyName"])
2727

2828
# Calculate ratio of policies with PrivEsc, Permissions management, or Data leak potential
2929
policies_with_data_leak_potential = 0
3030
policies_with_permissions_management = 0
3131
policies_with_privilege_escalation = 0
3232

33-
for finding in sorted_results:
33+
for finding in results:
3434
# These are stats we care about regardless of who manages it, as they help with prioritization
3535
if finding["DataExfiltrationActions"]:
3636
policies_with_data_leak_potential += 1
@@ -106,19 +106,25 @@ def generate_html_report(
106106

107107
# Formatted results to feed into the HTML
108108
iam_report_results_formatted = {
109+
# Metadata
109110
"account_name": account_name,
110111
"account_id": account_id,
111112
"report_generated_time": datetime.datetime.now().strftime("%Y-%m-%d"),
112-
"results": sorted_results,
113+
# Actual results
114+
"results": results,
115+
# IAM Principals
116+
"principal_policy_mapping": principal_policy_mapping,
113117
# Write-ups rendered from markdown
114118
"overview_write_up": overview_html,
115119
"triage_guidance_write_up": triage_guidance_html,
116120
"remediation_guidance_write_up": remediation_guidance_html,
117121
"validation_guidance_write_up": validation_guidance_html,
118122
"glossary_write_up": glossary_html,
123+
# Count of policies with these findings for the stats
119124
"policies_with_data_leak_potential": policies_with_data_leak_potential,
120125
"policies_with_privilege_escalation": policies_with_privilege_escalation,
121126
"policies_with_permissions_management": policies_with_permissions_management,
127+
# Exclusions config for appendix and user reference
122128
"exclusions_configuration": yaml.dump(exclusions_cfg),
123129
}
124130

@@ -138,4 +144,4 @@ def generate_html_report(
138144
webbrowser.open(url, new=2)
139145

140146
# Create the CSV triage sheet
141-
create_triage_worksheet(account_name, sorted_results, output_directory)
147+
create_triage_worksheet(account_name, results, output_directory)

cloudsplaining/output/templates/guidance/4-validation.md

+4-2
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,17 @@ After you've rewritten your IAM policy, we suggest two options for validating th
99
</ul>
1010
</div>
1111

12-
<div id="validation-using-cloudsplaining"> <h6>Using Cloudsplaining to Validate your Remediated Policies</h6></div>
12+
13+
<div id="validation-using-cloudsplaining"> <h5>Using Cloudsplaining to Validate your Remediated Policies</h5></div>
1314

1415
You can validate that your remediated policy passes Cloudsplaining by running the following command:
1516

1617
```cloudsplaining scan-policy-file --input policy.json --exclusions-file exclusions.yml```
1718

1819
When there are no more results, it passes!
1920

20-
<div id="validation-using-parliament"> <h6>Using Parliament to Lint your Policies</h6></div>
21+
22+
<div id="validation-using-parliament"> <h5>Using Parliament to Lint your Policies</h5></div>
2123

2224
parliament is an AWS IAM linting library. It reviews policies looking for problems such as:
2325

cloudsplaining/output/templates/guidance/glossary.md

+36
Original file line numberDiff line numberDiff line change
@@ -49,3 +49,39 @@ An IAM identity that you can create in your account that has specific permission
4949
We are particularly interested in roles used for **compute services** - i.e., Compute Service Roles.
5050

5151
This definition was taken from the AWS Documentation [here](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_terms-and-concepts.html#iam-term-role).
52+
53+
<div id="definition-managed-policy"><h6>Managed Policy</h6></div>
54+
55+
There are two types of Managed Policies: AWS-managed policies and Customer-managed policies. They are described below.
56+
57+
Criteria for selecting Managed Policies versus Inline policies can be found in the AWS documentation [here](https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_managed-vs-inline.html#choosing-managed-or-inline).
58+
59+
<div id="definition-customer-managed-policy"><h6>Customer-managed policy</h6></div>
60+
61+
AWS documentation on Customer-managed policies can be found [here](https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_managed-vs-inline.html#customer-managed-policies).
62+
63+
The following diagram illustrates customer managed policies. Each policy is an entity in IAM with its own Amazon Resource Name (ARN) that includes the policy name. Notice that the same policy can be attached to multiple principal entities—for example, the same DynamoDB-books-app policy is attached to two different IAM roles.
64+
65+
![Customer-managed policy diagram](https://docs.aws.amazon.com/IAM/latest/UserGuide/images/policies-customer-managed-policies.diagram.png)
66+
67+
<div id="definition-aws-managed-policy"><h6>AWS-managed policy</h6></div>
68+
69+
An AWS managed policy is a standalone policy that is created and administered by AWS. Standalone policy means that the policy has its own Amazon Resource Name (ARN) that includes the policy name. For example, arn:aws:iam::aws:policy/IAMReadOnlyAccess is an AWS managed policy.
70+
71+
AWS documentation on AWS-managed policies can be found [here](https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_managed-vs-inline.html#aws-managed-policies).
72+
73+
The following diagram (taken from the AWS documentation) illustrates AWS managed policies. The diagram shows three AWS managed policies: AdministratorAccess, PowerUserAccess, and AWSCloudTrailReadOnlyAccess. Notice that a single AWS managed policy can be attached to principal entities in different AWS accounts, and to different principal entities in a single AWS account.
74+
75+
![AWS-managed policy diagram](https://docs.aws.amazon.com/IAM/latest/UserGuide/images/policies-aws-managed-policies.diagram.png)
76+
77+
<div id="definition-inline-policy"><h6>Inline Policy</h6></div>
78+
79+
An inline policy is a policy that's embedded in an IAM identity (a user, group, or role). That is, the policy is an inherent part of the identity. You can create a policy and embed it in a identity, either when you create the identity or later.
80+
81+
AWS documentation on inline policies can be found [here](https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_managed-vs-inline.html#inline-policies).
82+
83+
The following diagram illustrates inline policies. Each policy is an inherent part of the user, group, or role. Notice that two roles include the same policy (the DynamoDB-books-app policy), but they are not sharing a single policy; each role has its own copy of the policy.
84+
85+
![Inline policy diagram](https://docs.aws.amazon.com/IAM/latest/UserGuide/images/policies-inline-policies.diagram.png)
86+
87+
Inline policies are useful if you want to maintain a strict one-to-one relationship between a policy and the identity that it's applied to. For example, you want to be sure that the permissions in a policy are not inadvertently assigned to an identity other than the one they're intended for. When you use an inline policy, the permissions in the policy cannot be inadvertently attached to the wrong identity. In addition, when you use the AWS Management Console to delete that identity, the policies embedded in the identity are deleted as well. That's because they are part of the principal entity.

cloudsplaining/output/templates/summary/executive-summary.html

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<p style="text-align: justify">
2-
This report contains the security assessment results from Cloudsplaining, which maps out the IAM risk landscape in a report, identifies where resource ARN constraints are not used, and identifies other risks in IAM policies like Privilege Escalation<a href="#definition-privilege-escalation"><small>[1]</small></a>, Resource Exposure<a href="#definition-resource-exposure"><small>[2]</small></a>, Infrastructure Modification<a href="#definition-infrastructure-modification"><small>[3]</small></a>, and Data Exfiltration<a href="#definition-data-exfiltration"><small>[4]</small></a>.
2+
This report contains the security assessment results from <a href="https://github.com/salesforce/cloudsplaining">Cloudsplaining</a>, which maps out the IAM risk landscape in a report, identifies where resource ARN constraints are not used, and identifies other risks in IAM policies like Privilege Escalation<a href="#definition-privilege-escalation"><small>[1]</small></a>, Resource Exposure<a href="#definition-resource-exposure"><small>[2]</small></a>, Infrastructure Modification<a href="#definition-infrastructure-modification"><small>[3]</small></a>, and Data Exfiltration<a href="#definition-data-exfiltration"><small>[4]</small></a>.
33
Remediating these issues, where necessary, will help to limit the blast radius in the case of compromised AWS credentials.
44
<br>
55
<br>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<p>
2+
The following table shows the list of IAM Users, Groups, and Roles in the account, whether they have findings or not.
3+
</p>
4+
<span class="badge badge-default"></span>
5+
<table class="table table-striped table-bordered table-sm">
6+
<thead>
7+
<tr>
8+
<th>Type</th>
9+
<th>Name</th>
10+
<th>Policy Type</th>
11+
<th>Managed By</th>
12+
<th>Policy Name</th>
13+
<th>Comment</th>
14+
</tr>
15+
</thead>
16+
<tbody>
17+
{% for principal in t["principal_policy_mapping"] %}
18+
<tr>
19+
<td>{{ principal["Type"] }}</td>
20+
<td>{{ principal["Principal"] }}</td>
21+
<td>{{ principal["PolicyType"] }}{% if principal["PolicyType"] == "Managed" %} <a href="#definition-managed-policy"><small>[0]</small></a>{% endif %}{% if principal["PolicyType"] == "Inline" %} <a href="#definition-inline-policy"><small>[1]</small></a>{% endif %}</td>
22+
<td>{{ principal["ManagedBy"] }}{% if principal["ManagedBy"] == "AWS" %} <a href="#definition-aws-managed-policy"><small>[2]</small></a>{% endif %}{% if principal["ManagedBy"] == "Customer" %} <a href="#definition-customer-managed-policy"><small>[3]</small></a>{% endif %}</td>
23+
<td>{{ principal["PolicyName"] }}</td>
24+
<td>{{ principal["Comment"] }}</td>
25+
</tr>
26+
{% endfor %}
27+
</tbody>
28+
</table>
29+
<br>
30+

0 commit comments

Comments
 (0)