Skip to content

Commit efbd87c

Browse files
authored
AWS_DEFAULT_PROFILE is respected; made formatting updates; made some path fixes that should help the windows issues (#239)
1 parent f448ed9 commit efbd87c

10 files changed

+90
-218
lines changed

.gitignore

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ _notes
66
results.*
77
default.json
88
default-iam-report.csv
9-
private/default-iam-results.json
9+
private/default-iam-results.json0
1010
default-results-summary.csv
1111
iam-new-principal-policy-mapping-example.json
1212
iam-findings-example.json

cloudsplaining/command/create_exclusions_file.py

+3-12
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
# For full license text, see the LICENSE file in the repo root
99
# or https://opensource.org/licenses/BSD-3-Clause
1010
import os
11-
from pathlib import Path
1211
import logging
1312
import click
1413
from cloudsplaining.shared.constants import EXCLUSIONS_TEMPLATE
@@ -22,14 +21,7 @@
2221
context_settings=dict(max_content_width=160),
2322
short_help="Creates a YML file to be used as a custom exclusions template",
2423
)
25-
@click.option(
26-
"--output-file",
27-
"-o",
28-
type=click.Path(exists=False),
29-
default=os.path.join(os.getcwd(), "exclusions.yml"),
30-
required=True,
31-
help="Relative path to output file where we want to store the exclusions template.",
32-
)
24+
@click.option("-o", "--output-file", type=click.Path(exists=False), default=os.path.join(os.getcwd(), "exclusions.yml"), required=True, help="Relative path to output file where we want to store the exclusions template.")
3325
@click.option("--verbose", "-v", "verbosity", count=True)
3426
def create_exclusions_file(output_file: str, verbosity: int) -> None:
3527
"""
@@ -38,11 +30,10 @@ def create_exclusions_file(output_file: str, verbosity: int) -> None:
3830
"""
3931
set_log_level(verbosity)
4032

41-
filename = Path(output_file).resolve()
42-
with open(filename, "a") as file_obj:
33+
with open(output_file, "a") as file_obj:
4334
for line in EXCLUSIONS_TEMPLATE:
4435
file_obj.write(line)
45-
utils.print_green(f"Success! Exclusions template file written to: {filename}")
36+
utils.print_green(f"Success! Exclusions template file written to: {output_file}")
4637
print(
4738
"Make sure you download your account authorization details before running the scan."
4839
"Set your AWS access keys as environment variables then run: "

cloudsplaining/command/create_multi_account_config_file.py

+9-22
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
# For full license text, see the LICENSE file in the repo root
99
# or https://opensource.org/licenses/BSD-3-Clause
1010
import os
11-
from pathlib import Path
1211
import logging
1312
import click
1413
from cloudsplaining.shared.constants import MULTI_ACCOUNT_CONFIG_TEMPLATE
@@ -24,41 +23,29 @@
2423
context_settings=dict(max_content_width=160),
2524
short_help="Creates a YML file to be used for multi-account scanning",
2625
)
27-
@click.option(
28-
"--output-file",
29-
"-o",
30-
"output_file",
31-
type=click.Path(exists=False),
32-
default=os.path.join(os.getcwd(), "multi-account-config.yml"),
33-
required=True,
34-
help="Relative path to output file where we want to store the multi account config template.",
35-
)
36-
@click.option(
37-
"-v", "--verbose", "verbosity", count=True,
38-
)
26+
@click.option("-o", "--output-file", "output_file", type=click.Path(exists=False), default=os.path.join(os.getcwd(), "multi-account-config.yml"), required=True, help="Relative path to output file where we want to store the multi account config template.")
27+
@click.option("-v", "--verbose", "verbosity", help="Log verbosity level.", count=True)
3928
def create_multi_account_config_file(output_file: str, verbosity: int) -> None:
4029
"""
4130
Creates a YML file to be used as a multi-account config template, so users can scan many different accounts.
4231
"""
4332
set_log_level(verbosity)
4433

45-
filename = Path(output_file).resolve()
46-
47-
if filename.exists():
34+
if os.path.exists(output_file):
4835
logger.debug(
49-
"%s exists. Removing the file and replacing its contents.", filename
36+
"%s exists. Removing the file and replacing its contents.", output_file
5037
)
51-
filename.unlink()
38+
os.remove(output_file)
5239

53-
with open(filename, "a") as file_obj:
40+
with open(output_file, "a") as file_obj:
5441
for line in MULTI_ACCOUNT_CONFIG_TEMPLATE:
5542
file_obj.write(line)
5643
utils.print_green(
57-
f"Success! Multi-account config file written to: {os.path.relpath(filename)}"
44+
f"Success! Multi-account config file written to: {os.path.relpath(output_file)}"
5845
)
5946
print(
60-
f"\nMake sure you edit the {os.path.relpath(filename)} file and then run the scan-multi-account command, as shown below."
47+
f"\nMake sure you edit the {os.path.relpath(output_file)} file and then run the scan-multi-account command, as shown below."
6148
)
6249
print(
63-
f"\n\tcloudsplaining scan-multi-account --exclusions-file exclusions.yml -c {os.path.relpath(filename)} -o ./"
50+
f"\n\tcloudsplaining scan-multi-account --exclusions-file exclusions.yml -c {os.path.relpath(output_file)} -o ./"
6451
)

cloudsplaining/command/download.py

+10-28
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
# or https://opensource.org/licenses/BSD-3-Clause
88
import json
99
import logging
10-
from pathlib import Path
10+
import os
1111
from typing import Dict, List, Any
1212

1313
import boto3
@@ -22,29 +22,10 @@
2222
short_help="Runs aws iam get-authorization-details on all accounts specified in the aws credentials "
2323
"file, and stores them in account-alias.json"
2424
)
25-
@click.option(
26-
"--profile",
27-
"-p",
28-
type=str,
29-
required=False,
30-
help="Specify 'all' to authenticate to AWS and analyze *all* existing IAM policies. Specify a non-default "
31-
"profile here. Defaults to the 'default' profile.",
32-
)
33-
@click.option(
34-
"--output",
35-
"-o",
36-
type=click.Path(exists=True),
37-
default=Path.cwd(),
38-
help="Path to store the output. Defaults to current directory.",
39-
)
40-
@click.option(
41-
"--include-non-default-policy-versions",
42-
is_flag=True,
43-
default=False,
44-
help="When downloading AWS managed policy documents, also include the non-default policy versions."
45-
" Note that this will dramatically increase the size of the downloaded file.",
46-
)
47-
@click.option("--verbose", "-v", "verbosity", count=True)
25+
@click.option("-p", "--profile", type=str, required=False, envvar="AWS_DEFAULT_PROFILE", help="Specify 'all' to authenticate to AWS and scan from *all* AWS credentials profiles. Specify a non-default profile here. Defaults to the 'default' profile.")
26+
@click.option("-o", "--output", type=click.Path(exists=True), default=os.getcwd(), help="Path to store the output. Defaults to current directory.")
27+
@click.option("--include-non-default-policy-versions", is_flag=True, default=False, help="When downloading AWS managed policy documents, also include the non-default policy versions. Note that this will dramatically increase the size of the downloaded file.")
28+
@click.option("-v", "--verbose", "verbosity", help="Log verbosity level.", count=True)
4829
def download(
4930
profile: str, output: str, include_non_default_policy_versions: bool, verbosity: int
5031
) -> int:
@@ -59,15 +40,16 @@ def download(
5940

6041
if profile:
6142
session_data["profile_name"] = profile
62-
output_filename = Path(output) / f"{profile}.json"
43+
output_filename = os.path.join(output, f"{profile}.json")
6344
else:
64-
output_filename = Path("default.json")
45+
output_filename = os.path.join(output, f"default.json")
6546

6647
results = get_account_authorization_details(
6748
session_data, include_non_default_policy_versions
6849
)
69-
70-
output_filename.write_text(json.dumps(results, indent=4, default=str))
50+
with open(output_filename, "w") as f:
51+
json.dump(results, f, indent=4, default=str)
52+
# output_filename.write_text(json.dumps(results, indent=4, default=str))
7153
print(f"Saved results to {output_filename}")
7254
return 1
7355

cloudsplaining/command/expand_policy.py

+2-8
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,8 @@
1818
@click.command(
1919
short_help="Expand the * Actions in IAM policy files to improve readability"
2020
)
21-
@click.option(
22-
"--input-file",
23-
"-i",
24-
type=click.Path(exists=True),
25-
required=True,
26-
help="Path to the JSON policy file.",
27-
)
28-
@click.option("--verbose", "-v", "verbosity", count=True)
21+
@click.option("-i", "--input-file", type=click.Path(exists=True), required=True, help="Path to the JSON policy file.")
22+
@click.option("-v", "--verbose", "verbosity", help="Log verbosity level.", count=True)
2923
def expand_policy(input_file: str, verbosity: int) -> None:
3024
"""
3125
Expand the * Actions in IAM policy files to improve readability

cloudsplaining/command/scan.py

+11-45
Original file line numberDiff line numberDiff line change
@@ -24,54 +24,17 @@
2424
from cloudsplaining.output.report import HTMLReport
2525
from cloudsplaining import set_log_level
2626

27-
logger = logging.getLogger(__name__)
28-
2927

3028
@click.command(
3129
short_help="Scan a single file containing AWS IAM account authorization details and generate report on "
3230
"IAM security posture. "
3331
)
34-
@click.option(
35-
"--input-file",
36-
"-i",
37-
type=click.Path(exists=True),
38-
required=True,
39-
help="Path of IAM account authorization details file",
40-
)
41-
@click.option(
42-
"--exclusions-file",
43-
"-e",
44-
help="A yaml file containing a list of policy names to exclude from the scan.",
45-
type=click.Path(exists=True),
46-
required=False,
47-
default=EXCLUSIONS_FILE,
48-
)
49-
@click.option(
50-
"--output",
51-
"-o",
52-
required=False,
53-
type=click.Path(exists=True),
54-
default=os.getcwd(),
55-
help="Output directory.",
56-
)
57-
@click.option(
58-
"--skip-open-report",
59-
"-s",
60-
required=False,
61-
default=False,
62-
is_flag=True,
63-
help="Don't open the HTML report in the web browser after creating. "
64-
"This helps when running the report in automation.",
65-
)
66-
@click.option(
67-
"--minimize",
68-
"-m",
69-
required=False,
70-
default=False,
71-
is_flag=True,
72-
help="Reduce the size of the HTML Report by pulling the Cloudsplaining Javascript code over the internet.",
73-
)
74-
@click.option("--verbose", "-v", "verbosity", count=True)
32+
@click.option("-i", "--input-file", type=click.Path(exists=True), required=True, help="Path of IAM account authorization details file")
33+
@click.option("-e", "--exclusions-file", help="A yaml file containing a list of policy names to exclude from the scan.", type=click.Path(exists=True), required=False, default=EXCLUSIONS_FILE)
34+
@click.option("-o", "--output", required=False, type=click.Path(exists=True), default=os.getcwd(), help="Output directory.")
35+
@click.option("-s", "--skip-open-report", required=False, default=False, is_flag=True, help="Don't open the HTML report in the web browser after creating. This helps when running the report in automation.")
36+
@click.option("-m", "--minimize", required=False, default=False, is_flag=True, help="Reduce the size of the HTML Report by pulling the Cloudsplaining Javascript code over the internet.")
37+
@click.option("-v", "--verbose", "verbosity", help="Log verbosity level.", count=True)
7538
def scan(
7639
input_file: str,
7740
exclusions_file: str,
@@ -98,7 +61,7 @@ def scan(
9861
exclusions = DEFAULT_EXCLUSIONS
9962

10063
if os.path.isfile(input_file):
101-
account_name = Path(input_file).stem
64+
account_name = os.path.basename(input_file).split(".")[0]
10265
with open(input_file) as f:
10366
contents = f.read()
10467
account_authorization_details_cfg = json.loads(contents)
@@ -137,7 +100,7 @@ def scan(
137100
contents = f.read()
138101
account_authorization_details_cfg = json.loads(contents)
139102

140-
account_name = Path(file).stem
103+
account_name = os.path.basename(input_file).split(".")[0]
141104
# Scan the Account Authorization Details config
142105
rendered_html_report = scan_account_authorization_details(
143106
account_authorization_details_cfg,
@@ -164,6 +127,9 @@ def scan(
164127
webbrowser.open(url, new=2)
165128

166129

130+
logger = logging.getLogger(__name__)
131+
132+
167133
def scan_account_authorization_details(
168134
account_authorization_details_cfg: Dict[str, Any],
169135
exclusions: Exclusions,

cloudsplaining/command/scan_multi_account.py

+12-67
Original file line numberDiff line numberDiff line change
@@ -41,71 +41,16 @@ def _accounts(self) -> Dict[str, str]:
4141

4242

4343
@click.command(short_help="Scan multiple AWS Accounts using a config file")
44-
@click.option(
45-
"--config",
46-
"-c",
47-
"config_file",
48-
type=click.Path(exists=True),
49-
required=True,
50-
help="Path of the multi-account config file",
51-
)
52-
@click.option(
53-
"--profile",
54-
"-p",
55-
"profile",
56-
type=str,
57-
required=False,
58-
help="Specify the AWS IAM profile.",
59-
envvar="AWS_PROFILE",
60-
)
61-
@click.option(
62-
"--role-name",
63-
"-r",
64-
"role_name",
65-
type=str,
66-
required=True,
67-
help="The name of the IAM role to assume in target accounts. Must be the same name in all target accounts.",
68-
)
69-
@click.option(
70-
"--exclusions-file",
71-
"-e",
72-
"exclusions_file",
73-
help="A yaml file containing a list of policy names to exclude from the scan.",
74-
type=click.Path(exists=True),
75-
required=False,
76-
default=EXCLUSIONS_FILE,
77-
)
78-
@optgroup.group(
79-
"Output Target Options", help="",
80-
)
81-
@optgroup.option(
82-
"--output-directory",
83-
"-o",
84-
"output_directory",
85-
type=click.Path(exists=True),
86-
help="Output directory. Supply this and/or --bucket.",
87-
)
88-
@optgroup.option(
89-
"--output-bucket",
90-
"-b",
91-
"output_bucket",
92-
type=str,
93-
help="The S3 bucket to save the results. Supply this and/or --output-directory.",
94-
)
95-
@optgroup.group(
96-
"Other Options", help="",
97-
)
98-
@optgroup.option(
99-
"--write-data-file",
100-
"-w",
101-
is_flag=True,
102-
required=False,
103-
default=False,
104-
help="Save the cloudsplaining JSON-formatted data results.",
105-
)
106-
@click.option(
107-
"-v", "--verbose", "verbosity", count=True,
108-
)
44+
@click.option("--config", "-c", "config_file", type=click.Path(exists=True), required=True, help="Path of the multi-account config file")
45+
@click.option("-p", "--profile", type=str, required=False, envvar="AWS_DEFAULT_PROFILE", help="Specify the AWS IAM profile")
46+
@click.option("-r", "--role-name", "role_name", type=str, required=True, help="The name of the IAM role to assume in target accounts. Must be the same name in all target accounts.")
47+
@click.option("-e", "--exclusions-file", "exclusions_file", help="A yaml file containing a list of policy names to exclude from the scan.", type=click.Path(exists=True), required=False, default=EXCLUSIONS_FILE)
48+
@optgroup.group("Output Target Options", help="")
49+
@optgroup.option("-o", "--output-directory", "output_directory", type=click.Path(exists=True), help="Output directory. Supply this and/or --bucket.")
50+
@optgroup.option("-b", "--output-bucket", "output_bucket", type=str, help="The S3 bucket to save the results. Supply this and/or --output-directory.")
51+
@optgroup.group("Other Options", help="")
52+
@optgroup.option("-w", "--write-data-file", is_flag=True, required=False, default=False, help="Save the cloudsplaining JSON-formatted data results.")
53+
@click.option("-v", "--verbose", "verbosity", help="Log verbosity level.", count=True)
10954
def scan_multi_account(
11055
config_file: str,
11156
profile: str,
@@ -191,8 +136,8 @@ def scan_accounts(
191136
)
192137
if output_directory:
193138
# Write the HTML file
194-
html_output_file = Path(output_directory) / f"{target_account_name}.html"
195-
html_output_file.write_text(rendered_report)
139+
html_output_file = os.path.join(output_directory, f"{target_account_name}.html")
140+
utils.write_file(html_output_file, rendered_report)
196141
utils.print_green(
197142
f"Saved the HTML report to: {os.path.relpath(html_output_file)}"
198143
)

0 commit comments

Comments
 (0)