Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 1 addition & 4 deletions .github/workflows/build_executables.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,7 @@ jobs:
build:
strategy:
matrix:
include:
- type: linux-x64
- type: mac-x64
- type: windows-x64
type: [linux-x64, mac-x64, windows-x64]

runs-on: ubuntu-latest

Expand Down
72 changes: 72 additions & 0 deletions .github/workflows/codeql-analysis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
#
# ******** NOTE ********
# We have attempted to detect the languages in your repository. Please check
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
#
name: "CodeQL"

on:
push:
branches: [ "main" ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ "main" ]
schedule:
- cron: '31 13 * * 1'

jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write

strategy:
fail-fast: false
matrix:
language: [ 'javascript' ]
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
# Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support

steps:
- name: Checkout repository
uses: actions/checkout@v3

# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.

# Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
# queries: security-extended,security-and-quality


# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v2

# ℹ️ Command-line programs to run using the OS shell.
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun

# If the Autobuild fails above, remove it and uncomment the following three lines.
# modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.

# - run: |
# echo "Run, Build Application using script"
# ./location_of_script_within_repo/buildscript.sh

- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
1 change: 0 additions & 1 deletion .github/workflows/manual.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ jobs:
with:
token: ${{ secrets.SECURITY_TOKEN }}
sarifReportDir: ./samples/sarif/peter-murray/advanced-security-java
outputDir: .

- name: Upload Artifacts
uses: actions/upload-artifact@v2
Expand Down
19 changes: 19 additions & 0 deletions .github/workflows/manual_custom_template.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
name: Manual Test Custom Template

on:
workflow_dispatch:

jobs:
test:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v2
- uses: ./
with:
token: ${{ secrets.SECURITY_TOKEN }}
templateFile: template.html
- uses: actions/upload-artifact@v2
with:
name: reports
path: ./*.pdf
1 change: 0 additions & 1 deletion .github/workflows/manual_test_repo.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ jobs:
with:
token: ${{ secrets.TEST_TOKEN }}
sarifReportDir: ./samples/sarif/java/detailed
outputDir: .
repository: octodemo/ghas-reporting

- name: Upload Artifacts
Expand Down
4 changes: 1 addition & 3 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,5 @@
},
"[html]": {
"editor.tabSize": 2
},
// GitHub Codespace Theme
"workbench.colorTheme": "GitHub Dark Dimmed"
}
}
48 changes: 21 additions & 27 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,50 +7,49 @@ along with the ability to in the future provide your own templates to the render

Due to the nature of CodeQL Analysis this action ideally should be executed after the `github/codeql-action/analyze`
action step, as this will generate the SARIF files on the runner which can be used to identify ALL the rules that were
applied during the analysis. The results stored on your repository will only contain the results that generated an alert.
applied during the analysis. The results stored on your repository will only contain the results that generated an alert.

## Processing

The action will use the provided token to load all the dependencies, dependency vulnerabilities and the Code Scanning
results for the specified repository. It will then look in the directory specified for any SARIF reports.

With this data it will construct a JSON payload that it then passes into the template system (using Nunjucks a Jinja
like templating system for JavaScript) and will generate a Summary Report (with more of these to come in the future)
With this data it will construct a JSON payload that it then passes into the template system (using Nunjucks a Jinja
like templating system for JavaScript) and will generate a Summary Report (with more of these to come in the future)
providing a roll up summary security report in HTML.

Using this HTML, it then passes it over to Puppeteer to render this in a headless Chromium before generating a PDF and
Using this HTML, it then passes it over to Puppeteer to render this in a headless Chromium before generating a PDF and
saving it in the specified directory.

## Parameters
Various inputs are defined in [`action.yml`](action.yml):

* `token`: A GitHub Personal Access Token with access to `repo` scope
* `sarifReportDir`: The directory to look for SARIF reports (from the CodeQL analyze action this defaults to `../results`)
* `outputDir`: The output directory for the PDF reports, defaults to `github.workspace`
* `repository`: The repository in `<owner>/<repo_name>` form, defaults to `github.repository`
| Name | Description | Default |
| --- | - | - |
| **token** | Token to use to authorize. | ${{&nbsp;github.token&nbsp;}} |
| sarifReportDir | The CodeQL output directory for SARIF report(s). | ../results |
| outputDir | The output directory for the generated report(s). | ${{ github.workspace }} |
| repository | Repository name with owner. For example, peter-murray/github-security-report | ${{ github.repository }} |
| templateFile | File to use as PDF template. For example, summary.pdf | N/A |


## Templates

Currently the templates are hard coded into the action. There are extension points built into the action that will allow
a future release to provide customization of these templates, via an ability to specify your own.


## Examples
## Usage

### Example Basic
```
name: Generate Security Report
uses: peter-murray/github-security-report-action@v2
uses: peter-murray/github-security-report-action@main
with:
token: ${{ secrets.SECURITY_TOKEN }}
```

Example summary report output:
![Example summary report](summary_report_example.png)
![Example summary report](https://user-images.githubusercontent.com/22425467/187445447-2290bfa4-b00a-4687-bb52-94a89cce1e97.png)

### Example Template
You can specify your own template file to use for the PDF report via the `templateFile` parameter. See [templates](./templates/).

## Standalone execution

For the v2 version, there are bundles that can be used to provide a command line client executable for Linux, MacOS and Windows platforms. The bundles can be downloaded from the [v2 Release](https://github.com/peter-murray/github-security-report-action/releases/tag/v2) assets.
For the v2 version, there are bundles that can be used to provide a command line client executable for Linux, MacOS and Windows platforms. The bundles can be downloaded from the [Releases](https://github.com/peter-murray/github-security-report-action/releases).

* [Linux bundle](https://github.com/peter-murray/github-security-report-action/releases/download/v2/github-security-report-bundle-linux-x64.zip)
* [MacOS bundle](https://github.com/peter-murray/github-security-report-action/releases/download/v2/github-security-report-bundle-mac-x64.zip)
Expand All @@ -67,15 +66,10 @@ Options:
* `-r`, `--repository`: The repository that contains the source code, in `<owner>/<repository_name>` form, e.g. `peter-murray/node-hue-api`
* `-s`, `--sarif-directory`: The directory containing the SARIF report files
* `-o`, `--output-directory`: The directory to output the PDF report to. This will be created if it does not exist.
* `-t`, `--template-file`: The file to use as a custom template for the PDF report.

An example of running the MacOS command line executable from the un:
```
```bash
$ ./github-security-report-mac-x64 -t <GitHub PAT Token> -r peter-murray/node-hue-api -s <directory containing CodeQL SARIF file(s)>
```
The above command would output a `summary.pdf` file in the current working directory.

## Future improvements

* Add support for selecting reporting templates to the parameters
* Example of extending html templates and using them

15 changes: 10 additions & 5 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,29 @@ author: Peter Murray

inputs:
token:
description: GitHub Access Token with permissions for Dependencies and Security API access on the repository.
required: true
description: The GitHub token used to create an authenticated client
default: ${{ github.token }}
required: false

sarifReportDir:
description: The CodeQL output directory for SARIF report(s).
required: true
required: false
default: "../results"

outputDir:
description: The output directory for the generated report(s).
required: true
required: false
default: ${{ github.workspace }}

repository:
description: Repository name with owner. For example, peter-murray/github-security-report
required: true
required: false
default: ${{ github.repository }}

templateFile:
description: File to use as PDF template. For example, summary.pdf
required: false

runs:
using: node12
main: dist/index.js
Expand Down
46 changes: 46 additions & 0 deletions dist/DataCollector.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const GitHubCodeScanning_1 = __importDefault(require("./codeScanning/GitHubCodeScanning"));
const GitHubDependencies_1 = __importDefault(require("./dependencies/GitHubDependencies"));
const SarifReportFinder_1 = __importDefault(require("./sarif/SarifReportFinder"));
const ReportData_1 = __importDefault(require("./templating/ReportData"));
class DataCollector {
constructor(octokit, repo) {
if (!octokit) {
throw new Error('A GitHub Octokit client needs to be provided');
}
this.octokit = octokit;
if (!repo) {
throw new Error('A GitHub repository must be provided');
}
const parts = repo.split('/');
this.repo = {
owner: parts[0],
repo: parts[1]
};
}
getPayload(sarifReportDir) {
const ghDeps = new GitHubDependencies_1.default(this.octokit), codeScanning = new GitHubCodeScanning_1.default(this.octokit), sarifFinder = new SarifReportFinder_1.default(sarifReportDir);
return Promise.all([
sarifFinder.getSarifFiles(),
ghDeps.getAllDependencies(this.repo),
ghDeps.getAllVulnerabilities(this.repo),
codeScanning.getOpenCodeScanningAlerts(this.repo),
codeScanning.getClosedCodeScanningAlerts(this.repo),
]).then(results => {
const data = {
github: this.repo,
sarifReports: results[0],
dependencies: results[1],
vulnerabilities: results[2],
codeScanningOpen: results[3],
codeScanningClosed: results[4],
};
return new ReportData_1.default(data);
});
}
}
exports.default = DataCollector;
50 changes: 50 additions & 0 deletions dist/ReportGenerator.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const DataCollector_1 = __importDefault(require("./DataCollector"));
const Template_1 = __importDefault(require("./templating/Template"));
const pdfWriter_1 = require("./pdf/pdfWriter");
const path = __importStar(require("path"));
const io_1 = require("@actions/io");
class ReportGenerator {
constructor(config) {
this.config = config;
}
run() {
const config = this.config;
const collector = new DataCollector_1.default(config.octokit, config.repository);
return collector.getPayload(config.sarifReportDirectory)
.then(reportData => {
const reportTemplate = new Template_1.default(config.templating.directory);
return reportTemplate.render(reportData.getJSONPayload(), config.templating.name);
})
.then(html => {
return io_1.mkdirP(config.outputDirectory)
.then(() => {
return pdfWriter_1.createPDF(html, path.join(config.outputDirectory, 'summary.pdf'));
});
});
}
}
exports.default = ReportGenerator;
Loading