This guide seeks to provide you with a crash course to adding a new check type or make updates to existing checks.
There is the accompanying PowerPoint which is used in the video too.
🚨 PRs welcome, if you see a broken window, fix it! 🎉
Local development and running is generally very easy because Hugo is used to generate static web sites.
To do anything, you need a populated avd-repo
folder. If it doesn't already exist you can do
make md-clone-all
If you have it already, do a
make update-all-repos
The general flow is this;
-
Parse the source repositories as per the specific requirements (eg Kube-Bench is json parsing, defsec is Go code, appshield is rego)
-
Generate Markdown from the source in the required hierarchical folder structure - generating menus to reflect hierarchy
-
Hugo Generates static content from markdown
-
You need Hugo installed
-
If you're doing anything with CSS, you need sass installed
How easy or hard adding a new source is largely dictated by the shape and structure of the data. For the most part, the steps are the same;
-
Add the source repo for download 1.1. Add to the Makefile md-clone-all: and update-all-repos: 1.2. Add a checkout section to the cron.yml GitHub Action workflow
-
Add a code file in
docGen
for your new type of config to parse - this should generate a YAML file using Go Templating. 2.1. Your generator is responsible for creating child menus - if you are going under compliance you will need to add nodes using the compliance menu generatorIn this example, a new entry is added to the
compliance
menucomplianceMenu.AddNode( "cis-1.2.0", // MenuID - this is used as the URL portion, lowercase and URL safe "CIS 1.2.0", // Wherever there is a menu title, this will be used filepath.Join(outputDir), // the menu will create an _index.md file, in the dir you specify here "compliance", // the MenuID of the parent - top level are the only ones where this isn't used []string{}, // this is for remediation codes and isn't relevant here, its for defsec []menu.Breadcrumb{ //this is at the top breadcrumbs and provides links with his menu page as the last one { Name: "Compliance", // Pretty name for the parent menu item Url: "/compliance" // MenuID url } }, "kubernetes", // this is the icon to be used should match something in themes/aquablank/static/images/icon_tile_XXXXXX.png - in this case icon_tile_kubernetes.png true // whether it is a tile based menu - all child items will be in tiles when try )
2.2. The generator code should produce a markdown file with your content, the front matter in your document has required attributes.
Attribute Purpose title The title shown in the menu and at the top of the page id The ID of the check - be this an AVD or TRC ID or otherwise source Where the AVD result will originate - defsec/appshield are Trivy for example icon icon - as found in themes/aquablank/static/images/icons_XXX_XXXXXXXX.png severity If severity isn't relevant, us "n/a" category Which top level it goes under. currently one of [misconfig, compliance, runtime, vulnerabilties] 2.3. An example template for Kube-Bench looks like this
title: {{.ShortName}} id: {{ .ID }} source: Kube Bench icon: kubernetes draft: false shortName: {{.ShortName}} severity: "n/a" version: {{ .Version}} category: compliance keywords: "{{ .Category }}" breadcrumbs: - name: Compliance path: /compliance - name: {{ .NiceVersion }} path: /compliance/{{ .Version}} - name: {{ .ParentTitle }} path: /compliance/{{ .Version}}/{{ .Version}}-{{ .ParentID}} avd_page_type: avd_page --- ### {{ .ID }} {{ .ShortName }} {{ range .Checks }} #### {{ .ID }} {{ .Text}} ##### Recommended Action {{ .Remediation }} <br /> {{ end }}
2.4. The goal is to suck enough information out of you source data to produce a
map
of information to pass to the go template. For example, the required map forKube-Bench
ismap[string]interface{}{ "ShortName": checkGroup.Text, "ID": checkGroup.ID, "Version": config.Version, "NiceVersion": cisVersion(config.Version), "Category": config.Type, "Checks": checkGroup.Checks, "ParentID": group, "ParentTitle": config.Text, }
This has everything that is required to by used in the template execution.
2.5. If your source data has a natural hierarchy, this is achieved by using file paths - nest the pages in the hierarchy in child directories for each section and use menus as above to achieve the breadcrumbs
-
Add a generate section to the main.go to call your new generator
-
If you have acronyms or words like
GitHub
which should be written in a particular casing, check outfunc Nicify(input string) string
in util/util.go which handles this. Feel free to add your capitalisations or special cases here. -
Look at what you've done - once
md-generate
make
target has run, avd-repo/content will have the generate Markdown files that Hugo is going to use to generate the content.
A note on updated CSS - Updating the CSS should be done by changing the the scss files and then running
make compile-theme-sass
to generate the new CSS files
NVD generation takes a lifetime to run, and for the most part it is totally needless to generate locally. My advise is to set firstYear
in main.go to 9999
so it doesn't do them - remember to set it back to 1999
before you update though.
Building locally is done by running
make md-clean md-build sync-all md-generate hugo-generate copy-assets
This cleans, build docGen, makes sure all the files are available in avd-repo
, runs the docGen code, creates the hugo for it, copies the css and html assets over.
the README.md has a section on using nginx
to host locally, I didn't find that much fun to use - good luck if you choose to.
I find the easiest and most reliable (assuming you have python3
installed) way is
cd avd-repo/docs
python3 -m http.server
This will make the whole site available locally at http://localhost:8000
You are unlikely to need to be using search locally, but if you are, the next section should work out for you!!
-
Run MeiliSearch locally
docker run -p 7700:7700 -v $(pwd)/data.ms:/data.ms getmeili/meilisearch
-
Setup search index
curl -X POST 'http://127.0.0.1:7700/indexes' -H 'Content-Type: application/json' --data '{ "uid" : "avd", "primaryKey": "title"}'
-
Add generated index.json to build search indexes
curl -X POST 'http://127.0.0.1:7700/indexes/avd/documents' --data @docs/index.json
-
To monitor index build progress:
curl -X GET 'http://localhost:7700/indexes/avd/updates'
-
Set the host and apiKey in
static/js/fastsearch.js
for using MeiliSearch:
meilisearch = new MeiliSearch({
host: 'http://localhost',
apiKey: "<public meilisearch if you set a master key only, otherwise remove>",
})
The daily build is configured in cron.yml.
The process has the following workflow
-
Download the latest repos for source content
-
Runs docGen code to create Markdown
-
Runs Hugo to generate static site and search index
-
Updates the index in the Meilesearch appliance
-
Syncs the output to the s3 buckets
a. Credentials for S3 write access for both staging and prod are in Secrets and provided by SRE team
b. Bucket names are also secrets, also provide by SRE team
c. Uses an S3 Sync with
--size-only
to limit the amount of traffic
At this point, gh-pages actions kick in and generate the site.