Skip to content

Commit 68f03cf

Browse files
author
Leona Magaya
committed
feat: Add custom guidance and appendices support
- Add TemplateConfig class to detect and process custom HTML files - Support custom-guidance.html and custom-appendices.html in project root - Implement dynamic navigation with conditional tab visibility - Add Vue components for custom content rendering with v-html - Include comprehensive test coverage for all scenarios - Update documentation with usage examples and behavior guide
1 parent 773f83a commit 68f03cf

File tree

10 files changed

+365
-144
lines changed

10 files changed

+365
-144
lines changed

README.md

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -332,6 +332,47 @@ cloudsplaining scan-multi-account \
332332

333333
> Note that if you run the above without the `--profile` flag, it will execute in the standard [AWS Credentials order of precedence](https://docs.aws.amazon.com/sdk-for-java/v1/developer-guide/credentials.html#credentials-default) (i.e., Environment variables, credentials profiles, ECS container credentials, then finally EC2 Instance Profile credentials).
334334

335+
## Custom Guidance and Appendices
336+
337+
Cloudsplaining supports customizing the Guidance and Appendices sections of the HTML report to include organization-specific security recommendations and documentation.
338+
339+
### How It Works
340+
341+
Place HTML files in your project root directory:
342+
343+
- `custom-guidance.html` - Custom security guidance content
344+
- `custom-appendices.html` - Custom appendices content
345+
346+
### Behavior
347+
348+
- **Files don't exist**: Shows default AWS security advice
349+
- **Files exist with content**: Shows your custom HTML content
350+
- **Files exist but are empty**: Hides the tabs entirely
351+
- **Mixed configuration**: Each tab works independently
352+
353+
### Example Usage
354+
355+
```bash
356+
# Create custom guidance
357+
echo '<h1>Company Security Guidelines</h1>
358+
<p>Follow these organization-specific steps:</p>
359+
<ul>
360+
<li>Review with security team</li>
361+
<li>Document in JIRA ticket</li>
362+
<li>Get approval before remediation</li>
363+
</ul>' > custom-guidance.html
364+
365+
# Create custom appendices
366+
echo '<h1>Internal Resources</h1>
367+
<p>Additional company resources:</p>
368+
<ul>
369+
<li><a href="https://internal.company.com/security">Security Portal</a></li>
370+
<li><a href="https://wiki.company.com/iam">IAM Best Practices</a></li>
371+
</ul>' > custom-appendices.html
372+
```
373+
374+
# Generate report with custom content
375+
cloudsplaining scan --input-file account-data.json --output reports/
335376

336377
## Cheatsheet
337378

cloudsplaining/output/dist/js/index.js

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

cloudsplaining/output/report.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from jinja2 import Environment, FileSystemLoader
1212

1313
from cloudsplaining.bin.version import __version__
14+
from cloudsplaining.shared.template_config import TemplateConfig
1415

1516
app_bundle_path = os.path.join(os.path.dirname(__file__), "dist", "js", "index.js")
1617

@@ -30,6 +31,7 @@ def __init__(
3031
self.report_generated_time = datetime.datetime.now().strftime("%Y-%m-%d")
3132
self.minimize = minimize
3233
self.results = f"var iam_data = {json.dumps(results, default=str)}"
34+
self.template_config = TemplateConfig()
3335

3436
@property
3537
def app_bundle(self) -> str:
@@ -73,6 +75,10 @@ def get_html_report(self) -> str:
7375
account_name=self.account_name,
7476
report_generated_time=str(self.report_generated_time),
7577
cloudsplaining_version=__version__,
78+
guidance_content=self.template_config.guidance_content,
79+
appendices_content=self.template_config.appendices_content,
80+
show_guidance_nav=self.template_config.show_guidance_nav,
81+
show_appendices_nav=self.template_config.show_appendices_nav,
7682
)
7783
template_path = os.path.dirname(__file__)
7884
env = Environment(loader=FileSystemLoader(template_path)) # noqa: S701

cloudsplaining/output/src/App.vue

Lines changed: 143 additions & 120 deletions
Original file line numberDiff line numberDiff line change
@@ -1,174 +1,197 @@
11
<template>
22
<div id="main">
33
<b-navbar toggleable="md" variant="faded">
4-
<b-navbar-brand to="/summary">
5-
Cloudsplaining
6-
</b-navbar-brand>
4+
<b-navbar-brand to="/summary"> Cloudsplaining </b-navbar-brand>
75
<b-navbar-toggle target="nav-collapse"></b-navbar-toggle>
86

97
<b-collapse id="nav-collapse" is-nav>
108
<b-navbar-nav>
11-
<b-nav-item to="/customer-policies">Customer Policies</b-nav-item>
12-
<b-nav-item to="/inline-policies">Inline Policies</b-nav-item>
9+
<b-nav-item to="/customer-policies"
10+
>Customer Policies</b-nav-item
11+
>
12+
<b-nav-item to="/inline-policies"
13+
>Inline Policies</b-nav-item
14+
>
1315
<b-nav-item to="/aws-policies">AWS Policies</b-nav-item>
1416
<b-nav-item to="/iam-principals">IAM Principals</b-nav-item>
15-
<b-nav-item to="/guidance">Guidance</b-nav-item>
16-
<b-nav-item to="/appendices">Appendices</b-nav-item>
17+
<b-nav-item v-if="show_guidance_nav" to="/guidance"
18+
>Guidance</b-nav-item
19+
>
20+
<b-nav-item v-if="show_appendices_nav" to="/appendices"
21+
>Appendices</b-nav-item
22+
>
1723
<!-- <b-nav-item to="/task-table">Task Table Demo</b-nav-item> -->
1824
</b-navbar-nav>
1925
<b-navbar-nav class="ml-auto">
20-
<b-nav-text><strong>Account ID:</strong> {{ account_id }} | <strong>Account Name:</strong> {{ account_name }}</b-nav-text>
26+
<b-nav-text
27+
><strong>Account ID:</strong> {{ account_id }} |
28+
<strong>Account Name:</strong>
29+
{{ account_name }}</b-nav-text
30+
>
2131
</b-navbar-nav>
2232
</b-collapse>
2333
</b-navbar>
2434

2535
<b-container class="mt-3 pb-3 report">
2636
<b-tabs nav-class="d-none">
2737
<router-view />
28-
<!-- <b-tab key="task-table">-->
29-
<!-- <br>-->
30-
<!-- <h3>Tasks (demo WIP)</h3>-->
31-
<!-- <br>-->
32-
<!--&lt;!&ndash; <h3>Customer-Managed Policies</h3>&ndash;&gt;-->
33-
<!--&lt;!&ndash; <TaskTable managedBy="Customer" v-bind:items_mapping="getTaskTableMapping('Customer')"/>&ndash;&gt;-->
34-
<!--&lt;!&ndash; <br>&ndash;&gt;-->
35-
<!-- &lt;!&ndash;TODO: Figure out the overlap issue where the two tables results in a double info field in Customer policies&ndash;&gt;-->
36-
<!-- <h3>AWS-Managed Policies</h3>-->
37-
<!-- <TaskTable managedBy="AWS" v-bind:items_mapping="getTaskTableMapping('AWS')"/>-->
38-
<!-- &lt;!&ndash;TODO: Task table for Inline Policies&ndash;&gt;-->
39-
<!--&lt;!&ndash; <h3>Inline Policies</h3>&ndash;&gt;-->
40-
<!--&lt;!&ndash; <TaskTable v-bind:policyNameMapping="getInlinePolicyNameMapping()"/>&ndash;&gt;-->
41-
<!-- </b-tab> -->
38+
<!-- <b-tab key="task-table">-->
39+
<!-- <br>-->
40+
<!-- <h3>Tasks (demo WIP)</h3>-->
41+
<!-- <br>-->
42+
<!--&lt;!&ndash; <h3>Customer-Managed Policies</h3>&ndash;&gt;-->
43+
<!--&lt;!&ndash; <TaskTable managedBy="Customer" v-bind:items_mapping="getTaskTableMapping('Customer')"/>&ndash;&gt;-->
44+
<!--&lt;!&ndash; <br>&ndash;&gt;-->
45+
<!-- &lt;!&ndash;TODO: Figure out the overlap issue where the two tables results in a double info field in Customer policies&ndash;&gt;-->
46+
<!-- <h3>AWS-Managed Policies</h3>-->
47+
<!-- <TaskTable managedBy="AWS" v-bind:items_mapping="getTaskTableMapping('AWS')"/>-->
48+
<!-- &lt;!&ndash;TODO: Task table for Inline Policies&ndash;&gt;-->
49+
<!--&lt;!&ndash; <h3>Inline Policies</h3>&ndash;&gt;-->
50+
<!--&lt;!&ndash; <TaskTable v-bind:policyNameMapping="getInlinePolicyNameMapping()"/>&ndash;&gt;-->
51+
<!-- </b-tab> -->
4252
</b-tabs>
4353
</b-container>
4454
<b-container>
4555
<b-row class="mt-5">
4656
<b-col class="text-center text-muted">
4757
Report Generated: {{ report_generated_time }} &diamond;
4858
Cloudsplaining version:
49-
<b-link href="https://github.com/salesforce/cloudsplaining">{{ cloudsplaining_version }}</b-link>
59+
<b-link
60+
href="https://github.com/salesforce/cloudsplaining"
61+
>{{ cloudsplaining_version }}</b-link
62+
>
5063
</b-col>
5164
</b-row>
5265
</b-container>
5366
</div>
5467
</template>
5568

5669
<script>
57-
const managedPoliciesUtil = require('./util/managed-policies');
58-
const inlinePoliciesUtil = require('./util/inline-policies');
59-
const taskTableUtil = require('./util/task-table');
60-
const sampleData = require('./sampleData');
70+
const managedPoliciesUtil = require("./util/managed-policies");
71+
const inlinePoliciesUtil = require("./util/inline-policies");
72+
const taskTableUtil = require("./util/task-table");
73+
const sampleData = require("./sampleData");
6174
62-
console.log(`process.env.NODE_ENV: ${process.env.NODE_ENV}`)
75+
console.log(`process.env.NODE_ENV: ${process.env.NODE_ENV}`);
6376
64-
// This conditionally loads the local sample data if you are developing, but not if you are viewing the report
77+
// This conditionally loads the local sample data if you are developing, but not if you are viewing the report
6578
79+
// eslint-disable-next-line no-undef
80+
if (process.env.NODE_ENV === "development" || isLocalExample === true) {
6681
// eslint-disable-next-line no-undef
67-
if ((process.env.NODE_ENV === "development") || (isLocalExample === true)) {
68-
// eslint-disable-next-line no-undef
69-
console.log(`isLocalExample is set to: ${isLocalExample}`)
70-
console.log(`process.env.NODE_ENV: ${process.env.NODE_ENV}`)
71-
console.log(`Note: a report generated with the Python template will not have isLocalExample set to True, because that uses a separate template.html file, which has isLocalExample set to False.`)
72-
// eslint-disable-next-line no-unused-vars,no-undef
73-
iam_data = sampleData.sample_iam_data;
74-
// eslint-disable-next-line no-undef
75-
console.log(`IAM Data keys inside the development if statement: ${Object.keys(iam_data)}`);
76-
// eslint-disable-next-line no-unused-vars,no-undef
77-
account_id = "12345678912";
78-
// eslint-disable-next-line no-unused-vars,no-undef
79-
account_name = "example";
80-
// eslint-disable-next-line no-unused-vars,no-undef
81-
report_generated_time = "2020-09-01";
82-
// eslint-disable-next-line no-unused-vars,no-undef
83-
cloudsplaining_version = "0.2.2";
84-
}
85-
else {
86-
// eslint-disable-next-line no-undef
87-
console.log(`isLocalExample is set to: ${isLocalExample}`)
88-
}
82+
console.log(`isLocalExample is set to: ${isLocalExample}`);
83+
console.log(`process.env.NODE_ENV: ${process.env.NODE_ENV}`);
84+
console.log(
85+
`Note: a report generated with the Python template will not have isLocalExample set to True, because that uses a separate template.html file, which has isLocalExample set to False.`
86+
);
87+
// eslint-disable-next-line no-unused-vars,no-undef
88+
iam_data = sampleData.sample_iam_data;
8989
90+
console.log(
91+
`IAM Data keys inside the development if statement: ${Object.keys(
92+
// eslint-disable-next-line no-undef
93+
iam_data
94+
)}`
95+
);
96+
// eslint-disable-next-line no-unused-vars,no-undef
97+
account_id = "12345678912";
98+
// eslint-disable-next-line no-unused-vars,no-undef
99+
account_name = "example";
100+
// eslint-disable-next-line no-unused-vars,no-undef
101+
report_generated_time = "2020-09-01";
102+
// eslint-disable-next-line no-unused-vars,no-undef
103+
cloudsplaining_version = "0.2.2";
104+
} else {
90105
// eslint-disable-next-line no-undef
91-
console.log(`IAM Data keys outside of the NODE_ENV if statements: ${Object.keys(iam_data)}`);
106+
console.log(`isLocalExample is set to: ${isLocalExample}`);
107+
}
92108
93-
94-
function getManagedPolicyNameMapping(managedBy) {
109+
console.log(
110+
`IAM Data keys outside of the NODE_ENV if statements: ${Object.keys(
95111
// eslint-disable-next-line no-undef
96-
return managedPoliciesUtil.getManagedPolicyNameMapping(iam_data, managedBy)
97-
}
112+
iam_data
113+
)}`
114+
);
98115
99-
function getInlinePolicyNameMapping() {
100-
// eslint-disable-next-line no-undef
101-
return inlinePoliciesUtil.getInlinePolicyNameMapping(iam_data)
102-
}
116+
function getManagedPolicyNameMapping(managedBy) {
117+
// eslint-disable-next-line no-undef
118+
return managedPoliciesUtil.getManagedPolicyNameMapping(iam_data, managedBy);
119+
}
103120
104-
function getTaskTableMapping(managedBy) {
105-
// eslint-disable-next-line no-undef
106-
return taskTableUtil.getTaskTableMapping(iam_data, managedBy)
107-
}
121+
function getInlinePolicyNameMapping() {
122+
// eslint-disable-next-line no-undef
123+
return inlinePoliciesUtil.getInlinePolicyNameMapping(iam_data);
124+
}
108125
126+
function getTaskTableMapping(managedBy) {
127+
// eslint-disable-next-line no-undef
128+
return taskTableUtil.getTaskTableMapping(iam_data, managedBy);
129+
}
109130
110-
export default {
111-
name: 'App',
112-
mounted: function()
113-
{
114-
// Workaround for handling anchors when vue-router is in hash mode
115-
// https://stackoverflow.com/a/45206192
116-
setTimeout(() => this.scrollFix(this.$route.hash), 1);
131+
export default {
132+
name: "App",
133+
mounted: function () {
134+
// Workaround for handling anchors when vue-router is in hash mode
135+
// https://stackoverflow.com/a/45206192
136+
setTimeout(() => this.scrollFix(this.$route.hash), 1);
137+
},
138+
data() {
139+
return {
140+
// eslint-disable-next-line no-undef
141+
sharedState: iam_data,
142+
// eslint-disable-next-line no-undef
143+
account_id: account_id,
144+
// eslint-disable-next-line no-undef
145+
account_name: account_name,
146+
// eslint-disable-next-line no-undef
147+
report_generated_time: report_generated_time,
148+
// eslint-disable-next-line no-undef
149+
cloudsplaining_version: cloudsplaining_version,
150+
// eslint-disable-next-line no-undef
151+
show_guidance_nav: show_guidance_nav === "True",
152+
// eslint-disable-next-line no-undef
153+
show_appendices_nav: show_appendices_nav === "True",
154+
};
155+
},
156+
computed: {
157+
iam_data() {
158+
return this.sharedState;
159+
},
160+
},
161+
methods: {
162+
getManagedPolicyNameMapping: function (managedBy) {
163+
return getManagedPolicyNameMapping(managedBy);
117164
},
118-
data() {
119-
return {
120-
// eslint-disable-next-line no-undef
121-
sharedState: iam_data,
122-
// eslint-disable-next-line no-undef
123-
account_id: account_id,
124-
// eslint-disable-next-line no-undef
125-
account_name: account_name,
126-
// eslint-disable-next-line no-undef
127-
report_generated_time: report_generated_time,
128-
// eslint-disable-next-line no-undef
129-
cloudsplaining_version: cloudsplaining_version,
130-
};
165+
getInlinePolicyNameMapping: function () {
166+
return getInlinePolicyNameMapping();
131167
},
132-
computed: {
133-
iam_data() {
134-
return this.sharedState
135-
}
168+
getTaskTableMapping: function (managedBy) {
169+
return getTaskTableMapping(managedBy);
136170
},
137-
methods: {
138-
getManagedPolicyNameMapping: function (managedBy) {
139-
return getManagedPolicyNameMapping(managedBy)
140-
},
141-
getInlinePolicyNameMapping: function () {
142-
return getInlinePolicyNameMapping()
143-
},
144-
getTaskTableMapping: function (managedBy) {
145-
return getTaskTableMapping(managedBy)
146-
},
147-
scrollFix: function(hashbang)
148-
{
149-
location.hash = hashbang;
150-
}
171+
scrollFix: function (hashbang) {
172+
location.hash = hashbang;
151173
},
152-
provide() {
153-
return {
154-
iam_data: this.iam_data,
155-
getManagedPolicyNameMapping: this.getManagedPolicyNameMapping,
156-
getInlinePolicyNameMapping: this.getInlinePolicyNameMapping,
157-
}
158-
}
159-
}
174+
},
175+
provide() {
176+
return {
177+
iam_data: this.iam_data,
178+
getManagedPolicyNameMapping: this.getManagedPolicyNameMapping,
179+
getInlinePolicyNameMapping: this.getInlinePolicyNameMapping,
180+
};
181+
},
182+
};
160183
</script>
161184

162185
<style>
163-
#main {
164-
font-family: Avenir, Helvetica, Arial, sans-serif;
165-
-webkit-font-smoothing: antialiased;
166-
-moz-osx-font-smoothing: grayscale;
167-
text-align: left;
168-
color: #2c3e50;
169-
}
186+
#main {
187+
font-family: Avenir, Helvetica, Arial, sans-serif;
188+
-webkit-font-smoothing: antialiased;
189+
-moz-osx-font-smoothing: grayscale;
190+
text-align: left;
191+
color: #2c3e50;
192+
}
170193
171-
.router-link-exact-active {
172-
font-weight: bold;
173-
}
194+
.router-link-exact-active {
195+
font-weight: bold;
196+
}
174197
</style>

0 commit comments

Comments
 (0)