Skip to content

Commit

Permalink
feat: create git-node security release command (#715)
Browse files Browse the repository at this point in the history
  • Loading branch information
RafaelGSS authored Sep 20, 2023
1 parent 47c2390 commit 6d68c99
Show file tree
Hide file tree
Showing 7 changed files with 321 additions and 0 deletions.
35 changes: 35 additions & 0 deletions components/git/security.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import CLI from '../../lib/cli.js';
import SecurityReleaseSteward from '../../lib/prepare_security.js';

export const command = 'security [options]';
export const describe = 'Manage an in-progress security release or start a new one.';

const securityOptions = {
start: {
describe: 'Start security release process',
type: 'boolean'
}
};

let yargsInstance;

export function builder(yargs) {
yargsInstance = yargs;
return yargs.options(securityOptions).example(
'git node security --start',
'Prepare a security release of Node.js');
}

export function handler(argv) {
if (argv.start) {
return startSecurityRelease(argv);
}
yargsInstance.showHelp();
}

async function startSecurityRelease(argv) {
const logStream = process.stdout.isTTY ? process.stdout : process.stderr;
const cli = new CLI(logStream);
const release = new SecurityReleaseSteward(cli);
return release.start();
}
27 changes: 27 additions & 0 deletions docs/git-node.md
Original file line number Diff line number Diff line change
Expand Up @@ -427,6 +427,32 @@ $ git node vote \
==============================================================================
```

## `git node security`

Manage or starts a security release process.

<a id="git-node-security-prerequisites"></a>

### Prerequisites

It's necessary to set up `.ncurc` with HackerOne keys:

```console
$ ncu-config --global set h1_token $H1_TOKEN
$ ncu-config --global set h1_username $H1_TOKEN
```

- `h1_token`: HackerOne Organization API Token, preferable with read-only
access.
- `h1_username`: HackerOne API Token username.

### `git node security --start`

This command creates the Next Security Issue in Node.js private repository
following the [Security Release Process][] document.
It will retrieve all the triaged HackerOne reports and add them to the list
with the affected release line.

## `git node status`

Return status and information about the current git-node land session. Shows the following information:
Expand Down Expand Up @@ -488,3 +514,4 @@ $ git node wpt url --commit=43feb7f612fe9160639e09a47933a29834904d69
```

[node.js abi version registry]: https://github.com/nodejs/node/blob/main/doc/abi_version_registry.json
[Security Release Process]: https://github.com/nodejs/node/blob/main/doc/contributing/security-release-process.md
14 changes: 14 additions & 0 deletions lib/auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,20 @@ async function auth(
check(username, jenkins_token);
result.jenkins = encode(username, jenkins_token);
}

if (options.h1) {
const { h1_username, h1_token } = getMergedConfig();
if (!h1_username || !h1_token) {
errorExit(
'Get your HackerOne API token in ' +
'https://docs.hackerone.com/organizations/api-tokens.html ' +
'and run the following command to add it to your ncu config: ' +
'ncu-config --global set h1_token TOKEN or ' +
'ncu-config --global set h1_username USERNAME'
);
};
result.h1 = encode(h1_username, h1_token);
}
return result;
}

Expand Down
97 changes: 97 additions & 0 deletions lib/github/templates/next-security-release.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
## Planning

* [X] Open an [issue](https://github.com/nodejs-private/node-private) titled
`Next Security Release`, and put this checklist in the description.

* [ ] Get agreement on the list of vulnerabilities to be addressed:
%REPORTS%

* [ ] PR release announcements in [private](https://github.com/nodejs-private/nodejs.org-private):
* [ ] pre-release: %PRE_RELEASE_PRIV%
* [ ] post-release: %POS_RELEASE_PRIV%
* List vulnerabilities in order of descending severity
* Ask the HackerOne reporter if they would like to be credited on the
security release blog page

* [ ] Get agreement on the planned date for the release: %RELEASE_DATE%

* [ ] Get release team volunteers for all affected lines:
%AFFECTED_LINES%

## Announcement (one week in advance of the planned release)

* [ ] Verify that GitHub Actions are working as normal: <https://www.githubstatus.com/>.

* [ ] Check that all vulnerabilities are ready for release integration:
* PRs against all affected release lines or cherry-pick clean
* Approved
* (optional) Approved by the reporter
* Build and send the binary to the reporter according to its architecture
and ask for a review. This step is important to avoid insufficient fixes
between Security Releases.
* Have CVEs
* Make sure that dependent libraries have CVEs for their issues. We should
only create CVEs for vulnerabilities in Node.js itself. This is to avoid
having duplicate CVEs for the same vulnerability.
* Described in the pre/post announcements

* [ ] Pre-release announcement to nodejs.org blog: TBD
(Re-PR the pre-approved branch from nodejs-private/nodejs.org-private to
nodejs/nodejs.org)

* [ ] Pre-release announcement [email](https://groups.google.com/forum/#!forum/nodejs-sec): TBD
* Subject: `Node.js security updates for all active release lines, Month Year`

* [ ] CC `[email protected]` on pre-release
* [ ] Forward the email you receive to `[email protected]`.

* [ ] Create a new issue in [nodejs/tweet](https://github.com/nodejs/tweet/issues)

* [ ] Request releaser(s) to start integrating the PRs to be released.

* [ ] Notify [docker-node](https://github.com/nodejs/docker-node/issues) of upcoming security release date: TBD

* [ ] Notify build-wg of upcoming security release date by opening an issue
in [nodejs/build](https://github.com/nodejs/build/issues) to request WG members are available to fix any CI issues: TBD

## Release day

* [ ] [Lock CI](https://github.com/nodejs/build/blob/HEAD/doc/jenkins-guide.md#before-the-release)

* [ ] The releaser(s) run the release process to completion.

* [ ] [Unlock CI](https://github.com/nodejs/build/blob/HEAD/doc/jenkins-guide.md#after-the-release)

* [ ] Post-release announcement to Nodejs.org blog: https://github.com/nodejs/nodejs.org/pull/5447
* (Re-PR the pre-approved branch from nodejs-private/nodejs.org-private to
nodejs/nodejs.org)

* [ ] Post-release announcement in reply email: TBD

* [ ] Create a new issue in nodejs/tweet

* [ ] Comment in [docker-node][] issue that release is ready for integration.
The docker-node team will build and release docker image updates.

* [ ] For every H1 report resolved:
* Close as Resolved
* Request Disclosure
* Request publication of H1 CVE requests
* (Check that the "Version Fixed" field in the CVE is correct, and provide
links to the release blogs in the "Public Reference" section)

* [ ] PR machine-readable JSON descriptions of the vulnerabilities to the
[core](https://github.com/nodejs/security-wg/tree/HEAD/vuln/core)
vulnerability DB. https://github.com/nodejs/security-wg/pull/1029
* For each vulnerability add a `#.json` file, one can copy an existing
[json](https://github.com/nodejs/security-wg/blob/0d82062d917cb9ddab88f910559469b2b13812bf/vuln/core/78.json)
file, and increment the latest created file number and use that as the name
of the new file to be added. For example, `79.json`.

* [ ] Close this issue

* [ ] Make sure the PRs for the vulnerabilities are closed.

* [ ] PR in that you stewarded the release in
[Security release stewards](https://github.com/nodejs/node/blob/HEAD/doc/contributing/security-release-process.md#security-release-stewards).
If necessary add the next rotation of the steward rotation.
117 changes: 117 additions & 0 deletions lib/prepare_security.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import nv from '@pkgjs/nv';
import auth from './auth.js';
import Request from './request.js';
import fs from 'node:fs';

export default class SecurityReleaseSteward {
constructor(cli) {
this.cli = cli;
}

async start() {
const { cli } = this;
const credentials = await auth({
github: true,
h1: true
});

const req = new Request(credentials);
const create = await cli.prompt(
'Create the Next Security Release issue?',
{ defaultAnswer: true });
if (create) {
const issue = new SecurityReleaseIssue(req);
const content = await issue.buildIssue(cli);
const data = await req.createIssue('Next Security Release', content, {
owner: 'nodejs-private',
repo: 'node-private'
});
if (data.html_url) {
cli.ok('Created: ' + data.html_url);
} else {
cli.error(data);
}
}
}
}

class SecurityReleaseIssue {
constructor(req) {
this.req = req;
this.content = '';
this.title = 'Next Security Release';
this.affectedLines = {};
}

getSecurityIssueTemplate() {
return fs.readFileSync(
new URL(
'./github/templates/next-security-release.md',
import.meta.url
),
'utf-8'
);
}

async buildIssue(cli) {
this.content = this.getSecurityIssueTemplate();
cli.info('Getting triaged H1 reports...');
const reports = await this.req.getTriagedReports();
await this.fillReports(cli, reports);

this.fillAffectedLines(Object.keys(this.affectedLines));

const target = await cli.prompt('Enter target date in YYYY-MM-DD format:', {
questionType: 'input',
defaultAnswer: 'TBD'
});
this.fillTargetDate(target);

return this.content;
}

async fillReports(cli, reports) {
const supportedVersions = (await nv('supported'))
.map((v) => v.versionName + '.x')
.join(',');

let reportsContent = '';
for (const report of reports.data) {
const { id, attributes: { title }, relationships: { severity } } = report;
const reportLevel = severity.data.attributes.rating;
cli.separator();
cli.info(`Report: ${id} - ${title} (${reportLevel})`);
const include = await cli.prompt(
'Would you like to include this report to the next security release?',
{ defaultAnswer: true });
if (!include) {
continue;
}

reportsContent +=
` * **[${id}](https://hackerone.com/bugs?subject=nodejs&report_id=${id}) - ${title} (TBD) - (${reportLevel})**\n`;
const versions = await cli.prompt('Which active release lines this report affects?', {
questionType: 'input',
defaultAnswer: supportedVersions
});
for (const v of versions.split(',')) {
if (!this.affectedLines[v]) this.affectedLines[v] = true;
reportsContent += ` * ${v} - TBD\n`;
}
}
this.content = this.content.replace('%REPORTS%', reportsContent);
}

fillAffectedLines(affectedLines) {
let affected = '';
for (const line of affectedLines) {
affected += ` * ${line} - TBD\n`;
}
this.content =
this.content.replace('%AFFECTED_LINES%', affected);
}

fillTargetDate(date) {
this.content = this.content.replace('%RELEASE_DATE%', date);
}
}
30 changes: 30 additions & 0 deletions lib/request.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,23 @@ export default class Request {
}
}

async createIssue(title, body, { owner, repo }) {
const url = `https://api.github.com/repos/${owner}/${repo}/issues`;
const options = {
method: 'POST',
headers: {
Authorization: `Basic ${this.credentials.github}`,
'User-Agent': 'node-core-utils',
Accept: 'application/vnd.github+json'
},
body: JSON.stringify({
title,
body
})
};
return this.json(url, options);
}

async gql(name, variables, path) {
const query = this.loadQuery(name);
if (path) {
Expand All @@ -83,6 +100,19 @@ export default class Request {
};
}

async getTriagedReports() {
const url = 'https://api.hackerone.com/v1/reports?filter[program][]=nodejs&filter[state][]=triaged';
const options = {
method: 'GET',
headers: {
Authorization: `Basic ${this.credentials.h1}`,
'User-Agent': 'node-core-utils',
Accept: 'application/json'
}
};
return this.json(url, options);
}

// This is for github v4 API queries, for other types of queries
// use .text or .json
async query(query, variables) {
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
"branch-diff": "^2.1.4",
"chalk": "^5.3.0",
"changelog-maker": "^3.2.4",
"@pkgjs/nv": "^0.2.1",
"cheerio": "^1.0.0-rc.12",
"clipboardy": "^3.0.0",
"core-validate-commit": "^4.0.0",
Expand Down

0 comments on commit 6d68c99

Please sign in to comment.