Skip to content

Commit d06f300

Browse files
authored
Update setup.py to include cached-property; Fix linting (#160)
* Update setup.py to include cached-property * Bump to 0.3.0 * black
1 parent 7d9a157 commit d06f300

21 files changed

+293
-127
lines changed

cloudsplaining/__init__.py

+5-3
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,26 @@
11
# pylint: disable=missing-module-docstring
22
import logging
33
import sys
4+
45
# Set default logging handler to avoid "No handler found" warnings.
56
from logging import NullHandler
67

7-
#logging.getLogger(__name__).addHandler(NullHandler())
8+
# logging.getLogger(__name__).addHandler(NullHandler())
89
# Uncomment to get the full debug logs.
910
# 2020-10-06 10:04:17,200 - root - DEBUG - Leveraging the bundled IAM Definition.
1011
# Need to figure out how to get click_log to do this for me.
1112
logger = logging.getLogger(__name__)
1213
logger.setLevel(logging.WARNING)
1314
handler = logging.StreamHandler(sys.stdout)
14-
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
15+
formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
1516
handler.setFormatter(formatter)
1617
logger.addHandler(handler)
1718

18-
#logging.getLogger().addHandler(NullHandler())
19+
# logging.getLogger().addHandler(NullHandler())
1920

2021
name = "cloudsplaining" # pylint: disable=invalid-name
2122

23+
2224
def change_log_level(log_level):
2325
""""Change log level of module logger"""
2426
logger.setLevel(log_level)

cloudsplaining/bin/version.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
# pylint: disable=missing-module-docstring
2-
__version__ = "0.2.5"
2+
__version__ = "0.3.0"

cloudsplaining/command/create_exclusions_file.py

+9-4
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,12 @@
2929
help="Relative path to output file where we want to store the exclusions template.",
3030
)
3131
@click.option(
32-
'--verbose','-v',
33-
type=click.Choice(['critical', 'error', 'warning', 'info', 'debug'],
34-
case_sensitive=False))
32+
"--verbose",
33+
"-v",
34+
type=click.Choice(
35+
["critical", "error", "warning", "info", "debug"], case_sensitive=False
36+
),
37+
)
3538
def create_exclusions_file(output_file, verbose):
3639
"""
3740
Creates a YML file to be used as a custom exclusions template,
@@ -51,4 +54,6 @@ def create_exclusions_file(output_file, verbose):
5154
)
5255
print("\tcloudsplaining download")
5356
print("You can use this with the scan command as shown below: ")
54-
print("\tcloudsplaining scan --exclusions-file exclusions.yml --input-file default.json")
57+
print(
58+
"\tcloudsplaining scan --exclusions-file exclusions.yml --input-file default.json"
59+
)

cloudsplaining/command/download.py

+12-5
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,12 @@
4242
" Note that this will dramatically increase the size of the downloaded file.",
4343
)
4444
@click.option(
45-
'--verbose','-v',
46-
type=click.Choice(['critical', 'error', 'warning', 'info', 'debug'],
47-
case_sensitive=False))
45+
"--verbose",
46+
"-v",
47+
type=click.Choice(
48+
["critical", "error", "warning", "info", "debug"], case_sensitive=False
49+
),
50+
)
4851
def download(profile, output, include_non_default_policy_versions, verbose):
4952
"""
5053
Runs aws iam get-authorization-details on all accounts specified in the aws credentials file, and stores them in
@@ -62,7 +65,9 @@ def download(profile, output, include_non_default_policy_versions, verbose):
6265
else:
6366
output_filename = "default.json"
6467

65-
results = get_account_authorization_details(session_data, include_non_default_policy_versions)
68+
results = get_account_authorization_details(
69+
session_data, include_non_default_policy_versions
70+
)
6671

6772
if os.path.exists(output_filename):
6873
os.remove(output_filename)
@@ -72,7 +77,9 @@ def download(profile, output, include_non_default_policy_versions, verbose):
7277
return 1
7378

7479

75-
def get_account_authorization_details(session_data, include_non_default_policy_versions):
80+
def get_account_authorization_details(
81+
session_data, include_non_default_policy_versions
82+
):
7683
"""Runs aws-iam-get-account-authorization-details"""
7784
session = boto3.Session(**session_data)
7885
config = Config(connect_timeout=5, retries={"max_attempts": 10})

cloudsplaining/command/expand_policy.py

+6-3
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,12 @@
2525
help="Path to the JSON policy file.",
2626
)
2727
@click.option(
28-
'--verbose','-v',
29-
type=click.Choice(['critical', 'error', 'warning', 'info', 'debug'],
30-
case_sensitive=False))
28+
"--verbose",
29+
"-v",
30+
type=click.Choice(
31+
["critical", "error", "warning", "info", "debug"], case_sensitive=False
32+
),
33+
)
3134
def expand_policy(input_file, verbose): # pylint: disable=redefined-builtin
3235
"""
3336
Expand the * Actions in IAM policy files to improve readability

cloudsplaining/command/scan.py

+40-16
Original file line numberDiff line numberDiff line change
@@ -62,15 +62,19 @@
6262
required=False,
6363
default=False,
6464
is_flag=True,
65-
help="Reduce the size of the HTML Report by pulling the Cloudsplaining Javascript code over the internet."
65+
help="Reduce the size of the HTML Report by pulling the Cloudsplaining Javascript code over the internet.",
6666
)
6767
@click.option(
68-
'--verbose','-v',
69-
type=click.Choice(['critical', 'error', 'warning', 'info', 'debug'],
70-
case_sensitive=False))
68+
"--verbose",
69+
"-v",
70+
type=click.Choice(
71+
["critical", "error", "warning", "info", "debug"], case_sensitive=False
72+
),
73+
)
7174
# pylint: disable=redefined-builtin
7275
def scan(
73-
input_file, exclusions_file, output, skip_open_report, minimize, verbose): # pragma: no cover
76+
input_file, exclusions_file, output, skip_open_report, minimize, verbose
77+
): # pragma: no cover
7478
"""
7579
Given the path to account authorization details files and the exclusions config file, scan all inline and
7680
managed policies in the account to identify actions that do not leverage resource constraints.
@@ -95,8 +99,12 @@ def scan(
9599
contents = f.read()
96100
account_authorization_details_cfg = json.loads(contents)
97101
rendered_html_report = scan_account_authorization_details(
98-
account_authorization_details_cfg, exclusions, account_name, output, write_data_files=True,
99-
minimize=minimize
102+
account_authorization_details_cfg,
103+
exclusions,
104+
account_name,
105+
output,
106+
write_data_files=True,
107+
minimize=minimize,
100108
)
101109
html_output_file = os.path.join(output, f"iam-report-{account_name}.html")
102110
logger.info("Saving the report to %s", html_output_file)
@@ -128,8 +136,12 @@ def scan(
128136
account_name = Path(file).stem
129137
# Scan the Account Authorization Details config
130138
rendered_html_report = scan_account_authorization_details(
131-
account_authorization_details_cfg, exclusions, account_name, output, write_data_files=True,
132-
minimize=minimize
139+
account_authorization_details_cfg,
140+
exclusions,
141+
account_name,
142+
output,
143+
write_data_files=True,
144+
minimize=minimize,
133145
)
134146
html_output_file = os.path.join(output, f"iam-report-{account_name}.html")
135147
logger.info("Saving the report to %s", html_output_file)
@@ -149,8 +161,12 @@ def scan(
149161

150162

151163
def scan_account_authorization_details(
152-
account_authorization_details_cfg, exclusions, account_name="default", output_directory=os.getcwd(),
153-
write_data_files=False, minimize=False
164+
account_authorization_details_cfg,
165+
exclusions,
166+
account_name="default",
167+
output_directory=os.getcwd(),
168+
write_data_files=False,
169+
minimize=False,
154170
): # pragma: no cover
155171
"""
156172
Given the path to account authorization details files and the exclusions config file, scan all inline and
@@ -162,7 +178,9 @@ def scan_account_authorization_details(
162178
"resource constraints..."
163179
)
164180
check_authorization_details_schema(account_authorization_details_cfg)
165-
authorization_details = AuthorizationDetails(account_authorization_details_cfg, exclusions)
181+
authorization_details = AuthorizationDetails(
182+
account_authorization_details_cfg, exclusions
183+
)
166184
results = authorization_details.results
167185

168186
# Lazy method to get an account ID
@@ -176,7 +194,7 @@ def scan_account_authorization_details(
176194
account_id=account_id,
177195
account_name=account_name,
178196
results=results,
179-
minimize=minimize
197+
minimize=minimize,
180198
)
181199
rendered_report = html_report.get_html_report()
182200

@@ -185,11 +203,17 @@ def scan_account_authorization_details(
185203
if output_directory is None:
186204
output_directory = os.getcwd()
187205

188-
results_data_file = os.path.join(output_directory, f"iam-results-{account_name}.json")
189-
results_data_filepath = write_results_data_file(authorization_details.results, results_data_file)
206+
results_data_file = os.path.join(
207+
output_directory, f"iam-results-{account_name}.json"
208+
)
209+
results_data_filepath = write_results_data_file(
210+
authorization_details.results, results_data_file
211+
)
190212
print(f"Results data saved: {str(results_data_filepath)}")
191213

192-
findings_data_file = os.path.join(output_directory, f"iam-findings-{account_name}.json")
214+
findings_data_file = os.path.join(
215+
output_directory, f"iam-findings-{account_name}.json"
216+
)
193217
findings_data_filepath = write_results_data_file(results, findings_data_file)
194218
print(f"Findings data file saved: {str(findings_data_filepath)}")
195219

cloudsplaining/command/scan_policy_file.py

+28-14
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
from cloudsplaining.shared.exclusions import Exclusions
1717
from cloudsplaining.output.policy_finding import PolicyFinding
1818
from cloudsplaining import change_log_level
19+
1920
logger = logging.getLogger(__name__)
2021
BOLD = "\033[1m"
2122
RED = "\033[91m"
@@ -47,11 +48,16 @@
4748
" (Resource Exposure, Privilege Escalation, Data Exfiltration). This can help with prioritization.",
4849
)
4950
@click.option(
50-
'--verbose','-v',
51-
type=click.Choice(['critical', 'error', 'warning', 'info', 'debug'],
52-
case_sensitive=False))
51+
"--verbose",
52+
"-v",
53+
type=click.Choice(
54+
["critical", "error", "warning", "info", "debug"], case_sensitive=False
55+
),
56+
)
5357
# pylint: disable=redefined-builtin
54-
def scan_policy_file(input_file, exclusions_file, high_priority_only, verbose): # pragma: no cover
58+
def scan_policy_file(
59+
input_file, exclusions_file, high_priority_only, verbose
60+
): # pragma: no cover
5561
"""Scan a single policy file to identify missing resource constraints."""
5662
if verbose:
5763
log_level = getattr(logging, verbose.upper())
@@ -85,7 +91,9 @@ def scan_policy_file(input_file, exclusions_file, high_priority_only, verbose):
8591
if results:
8692
# Privilege Escalation
8793
if results.get("PrivilegeEscalation"):
88-
print(f"{RED}Potential Issue found: Policy is capable of Privilege Escalation{END}")
94+
print(
95+
f"{RED}Potential Issue found: Policy is capable of Privilege Escalation{END}"
96+
)
8997
results_exist += 1
9098
for item in results.get("PrivilegeEscalation"):
9199
print(f"- Method: {item.get('type')}")
@@ -94,31 +102,33 @@ def scan_policy_file(input_file, exclusions_file, high_priority_only, verbose):
94102
# Data Exfiltration
95103
if results.get("DataExfiltration"):
96104
results_exist += 1
97-
print(f"{RED}Potential Issue found: Policy is capable of Data Exfiltration{END}")
98105
print(
99-
f"{BOLD}Actions{END}: {', '.join(results.get('DataExfiltration'))}\n"
106+
f"{RED}Potential Issue found: Policy is capable of Data Exfiltration{END}"
100107
)
108+
print(f"{BOLD}Actions{END}: {', '.join(results.get('DataExfiltration'))}\n")
101109

102110
# Resource Exposure
103111
if results.get("ResourceExposure"):
104112
results_exist += 1
105-
print(f"{RED}Potential Issue found: Policy is capable of Resource Exposure{END}")
106113
print(
107-
f"{BOLD}Actions{END}: {', '.join(results.get('ResourceExposure'))}\n"
114+
f"{RED}Potential Issue found: Policy is capable of Resource Exposure{END}"
108115
)
116+
print(f"{BOLD}Actions{END}: {', '.join(results.get('ResourceExposure'))}\n")
109117

110118
# Service Wildcard
111119
if results.get("ServiceWildcard"):
112120
results_exist += 1
113-
print(f"{RED}Potential Issue found: Policy allows ALL Actions from a service (like service:*){END}")
114121
print(
115-
f"{BOLD}Actions{END}: {', '.join(results.get('ServiceWildcard'))}\n"
122+
f"{RED}Potential Issue found: Policy allows ALL Actions from a service (like service:*){END}"
116123
)
124+
print(f"{BOLD}Actions{END}: {', '.join(results.get('ServiceWildcard'))}\n")
117125

118126
# Credentials Exposure
119127
if results.get("CredentialsExposure"):
120128
results_exist += 1
121-
print(f"{RED}Potential Issue found: Policy allows actions that return credentials{END}")
129+
print(
130+
f"{RED}Potential Issue found: Policy allows actions that return credentials{END}"
131+
)
122132
print(
123133
f"{BOLD}Actions{END}: {', '.join(results.get('CredentialsExposure'))}\n"
124134
)
@@ -127,8 +137,12 @@ def scan_policy_file(input_file, exclusions_file, high_priority_only, verbose):
127137

128138
# Infrastructure Modification
129139
results_exist += 1
130-
print(f"{RED}Potential Issue found: Policy is capable of Unrestricted Infrastructure Modification{END}")
131-
print(f"{BOLD}Actions{END}: {', '.join(results.get('InfrastructureModification'))}")
140+
print(
141+
f"{RED}Potential Issue found: Policy is capable of Unrestricted Infrastructure Modification{END}"
142+
)
143+
print(
144+
f"{BOLD}Actions{END}: {', '.join(results.get('InfrastructureModification'))}"
145+
)
132146

133147
if results_exist == 0:
134148
print("There were no results found.")

cloudsplaining/output/policy_finding.py

+15-7
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,12 @@
77
import logging
88
from cloudsplaining.shared.constants import (
99
READ_ONLY_DATA_EXFILTRATION_ACTIONS,
10-
ACTIONS_THAT_RETURN_CREDENTIALS
10+
ACTIONS_THAT_RETURN_CREDENTIALS,
1111
)
1212
from cloudsplaining.shared.exclusions import (
1313
Exclusions,
1414
DEFAULT_EXCLUSIONS,
15-
is_name_excluded
15+
is_name_excluded,
1616
)
1717

1818
logger = logging.getLogger(__name__)
@@ -31,7 +31,9 @@ def __init__(self, policy_document, exclusions=DEFAULT_EXCLUSIONS):
3131
self.exclusions = exclusions
3232
self.always_exclude_actions = exclusions.exclude_actions
3333

34-
self.missing_resource_constraints_for_modify_actions = self._missing_resource_constraints_for_modify_actions()
34+
self.missing_resource_constraints_for_modify_actions = (
35+
self._missing_resource_constraints_for_modify_actions()
36+
)
3537

3638
def _missing_resource_constraints_for_modify_actions(self):
3739
"""Find modify actions that lack resource ARN constraints"""
@@ -40,7 +42,9 @@ def _missing_resource_constraints_for_modify_actions(self):
4042
logger.debug("Evaluating statement: %s", statement.json)
4143
if statement.effect == "Allow":
4244
actions_missing_resource_constraints.extend(
43-
statement.missing_resource_constraints_for_modify_actions(self.exclusions)
45+
statement.missing_resource_constraints_for_modify_actions(
46+
self.exclusions
47+
)
4448
)
4549
if actions_missing_resource_constraints:
4650
these_results = list(
@@ -101,7 +105,9 @@ def privilege_escalation(self):
101105
def data_exfiltration(self):
102106
"""Returns data exfiltration actions in the policy, if present"""
103107
result = []
104-
for action in self.policy_document.allows_specific_actions_without_constraints(READ_ONLY_DATA_EXFILTRATION_ACTIONS):
108+
for action in self.policy_document.allows_specific_actions_without_constraints(
109+
READ_ONLY_DATA_EXFILTRATION_ACTIONS
110+
):
105111
if action.lower() not in self.exclusions.exclude_actions:
106112
result.append(action)
107113
return result
@@ -116,7 +122,9 @@ def credentials_exposure(self):
116122
"""Determine if the action returns credentials"""
117123
# https://gist.github.com/kmcquade/33860a617e651104d243c324ddf7992a
118124
results = []
119-
for action in self.policy_document.allows_specific_actions_without_constraints(ACTIONS_THAT_RETURN_CREDENTIALS):
125+
for action in self.policy_document.allows_specific_actions_without_constraints(
126+
ACTIONS_THAT_RETURN_CREDENTIALS
127+
):
120128
if action.lower() not in self.exclusions.exclude_actions:
121129
results.append(action)
122130
return results
@@ -131,6 +139,6 @@ def results(self):
131139
ResourceExposure=self.resource_exposure,
132140
DataExfiltration=self.data_exfiltration,
133141
CredentialsExposure=self.credentials_exposure,
134-
InfrastructureModification=self.missing_resource_constraints_for_modify_actions
142+
InfrastructureModification=self.missing_resource_constraints_for_modify_actions,
135143
)
136144
return findings

0 commit comments

Comments
 (0)