@enterprise-cmcs/macpro-security-hub-sync
Advanced tools
Comparing version 1.0.2 to 1.1.0
@@ -5,8 +5,7 @@ import { IssueObject } from "jira-client"; | ||
jiraClosedStatuses: string[]; | ||
project: string; | ||
constructor(); | ||
private static checkEnvVars; | ||
getAllSecurityHubIssuesInJiraProject(): Promise<IssueObject[]>; | ||
getAllSecurityHubIssuesInJiraProject(identifyingLabels: string[]): Promise<IssueObject[]>; | ||
createNewIssue(issue: IssueObject): Promise<IssueObject>; | ||
closeIssue(issueKey: string): Promise<void>; | ||
} |
@@ -36,6 +36,4 @@ "use strict"; | ||
jiraClosedStatuses; | ||
project; | ||
constructor() { | ||
Jira.checkEnvVars(); | ||
this.project = process.env.PROJECT ?? ""; | ||
this.jiraClosedStatuses = process.env.JIRA_CLOSED_STATUSES | ||
@@ -60,3 +58,2 @@ ? process.env.JIRA_CLOSED_STATUSES.split(",") | ||
"JIRA_PROJECT", | ||
"PROJECT", | ||
]; | ||
@@ -68,5 +65,6 @@ const missingEnvVars = requiredEnvVars.filter((envVar) => !process.env[envVar]); | ||
} | ||
async getAllSecurityHubIssuesInJiraProject() { | ||
async getAllSecurityHubIssuesInJiraProject(identifyingLabels) { | ||
const labelQuery = identifyingLabels.reduce((accumulator, currentValue) => accumulator + `AND labels = ${currentValue} `, ""); | ||
const searchOptions = {}; | ||
const query = `project = ${process.env.JIRA_PROJECT} AND labels = ${this.project} AND labels = security-hub AND status not in ("${this.jiraClosedStatuses.join('","')}")`; | ||
const query = `project = ${process.env.JIRA_PROJECT} AND labels = security-hub ${labelQuery} AND status not in ("${this.jiraClosedStatuses.join('","')}")`; | ||
let totalIssuesReceived = 0; | ||
@@ -76,2 +74,9 @@ let allIssues = []; | ||
do { | ||
// We want to do everything possible to prevent matching tickets that we shouldn't | ||
if (!query.includes("AND labels = security-hub ")) { | ||
throw "ERROR: Your query does not include the 'security-hub' label, and is too broad. Refusing to continue"; | ||
} | ||
if (!query.match(" AND labels = [0-9]{12}")) { | ||
throw "ERROR: Your query does not include an AWS Account ID as a label, and is too broad. Refusing to continue"; | ||
} | ||
results = await this.jira.searchJira(query, searchOptions); | ||
@@ -88,4 +93,2 @@ allIssues = allIssues.concat(results.issues); | ||
issue.fields.project = { key: process.env.JIRA_PROJECT }; | ||
// add aditional labels | ||
issue.fields.labels.push(this.project); | ||
const response = await this.jira.addNewIssue(issue); | ||
@@ -107,3 +110,2 @@ response["webUrl"] = `https://${process.env.JIRA_HOST}/browse/${response.key}`; | ||
const doneTransition = transitions.transitions.find((t) => t.name === "Done"); | ||
const doneTransitionId = doneTransition ? doneTransition.id : undefined; | ||
if (!doneTransition) { | ||
@@ -110,0 +112,0 @@ console.error(`Cannot find "Done" transition for issue ${issueKey}`); |
@@ -6,2 +6,5 @@ import { SecurityHubFinding } from "./libs"; | ||
severities?: string[]; | ||
customJiraFields?: { | ||
[id: string]: any; | ||
}; | ||
} | ||
@@ -11,10 +14,13 @@ export declare class SecurityHubJiraSync { | ||
private readonly securityHub; | ||
private readonly customJiraFields; | ||
private readonly region; | ||
constructor(options?: SecurityHubJiraSyncOptions); | ||
sync(): Promise<void>; | ||
closeIssuesForResolvedFindings(jiraIssues: IssueObject[], shFindings: SecurityHubFinding[]): void; | ||
getAWSAccountID(): Promise<string>; | ||
closeIssuesForResolvedFindings(jiraIssues: IssueObject[], shFindings: SecurityHubFinding[]): Promise<void>; | ||
createIssueBody(finding: SecurityHubFinding): string; | ||
createSecurityHubFindingUrl(standardsControlArn?: string): string; | ||
createJiraIssueFromFinding(finding: SecurityHubFinding): Promise<void>; | ||
createJiraIssuesForNewFindings(jiraIssues: IssueObject[], shFindings: SecurityHubFinding[]): void; | ||
createJiraIssueFromFinding(finding: SecurityHubFinding, identifyingLabels: string[]): Promise<void>; | ||
createJiraIssuesForNewFindings(jiraIssues: IssueObject[], shFindings: SecurityHubFinding[], identifyingLabels: string[]): void; | ||
} | ||
export {}; |
@@ -5,13 +5,21 @@ "use strict"; | ||
const libs_1 = require("./libs"); | ||
const client_sts_1 = require("@aws-sdk/client-sts"); | ||
class SecurityHubJiraSync { | ||
jira; | ||
securityHub; | ||
customJiraFields; | ||
region; | ||
constructor(options = {}) { | ||
const { region = "us-east-1", severities = ["HIGH", "CRITICAL"] } = options; | ||
const { region = "us-east-1", severities = ["MEDIUM", "HIGH", "CRITICAL"], customJiraFields = {}, } = options; | ||
this.securityHub = new libs_1.SecurityHub({ region, severities }); | ||
this.region = region; | ||
this.jira = new libs_1.Jira(); | ||
this.customJiraFields = customJiraFields; | ||
} | ||
async sync() { | ||
// Step 0. Gather and set some information that will be used throughout this function | ||
const accountId = await this.getAWSAccountID(); | ||
const identifyingLabels = [accountId, this.region]; | ||
// Step 1. Get all open Security Hub issues from Jira | ||
const jiraIssues = await this.jira.getAllSecurityHubIssuesInJiraProject(); | ||
const jiraIssues = await this.jira.getAllSecurityHubIssuesInJiraProject(identifyingLabels); | ||
// console.log( | ||
@@ -24,14 +32,26 @@ // "all current statuses on security hub issues:", | ||
// Step 3. Close existing Jira issues if their finding is no longer active/current | ||
this.closeIssuesForResolvedFindings(jiraIssues, shFindings); | ||
await this.closeIssuesForResolvedFindings(jiraIssues, shFindings); | ||
// Step 4. Create Jira issue for current findings that do not already have a Jira issue | ||
this.createJiraIssuesForNewFindings(jiraIssues, shFindings); | ||
await this.createJiraIssuesForNewFindings(jiraIssues, shFindings, identifyingLabels); | ||
} | ||
closeIssuesForResolvedFindings(jiraIssues, shFindings) { | ||
async getAWSAccountID() { | ||
const client = new client_sts_1.STSClient({ | ||
region: this.region, | ||
}); | ||
const command = new client_sts_1.GetCallerIdentityCommand({}); | ||
const response = await client.send(command); | ||
let accountID = response.Account || ""; | ||
if (!accountID.match("[0-9]{12}")) { | ||
throw "ERROR: An issue was encountered when looking up your AWS Account ID. Refusing to continue."; | ||
} | ||
return accountID; | ||
} | ||
async closeIssuesForResolvedFindings(jiraIssues, shFindings) { | ||
const expectedJiraIssueTitles = Array.from(new Set(shFindings.map((finding) => `SecurityHub Finding - ${finding.title}`))); | ||
// close all security-hub labeled Jira issues that do not have an active finding | ||
jiraIssues.forEach((issue) => { | ||
if (!expectedJiraIssueTitles.includes(issue.fields.summary)) { | ||
this.jira.closeIssue(issue.key); | ||
for (var i = 0; i < jiraIssues.length; i++) { | ||
if (!expectedJiraIssueTitles.includes(jiraIssues[i].fields.summary)) { | ||
await this.jira.closeIssue(jiraIssues[i].key); | ||
} | ||
}); | ||
} | ||
} | ||
@@ -85,3 +105,3 @@ createIssueBody(finding) { | ||
} | ||
async createJiraIssueFromFinding(finding) { | ||
async createJiraIssueFromFinding(finding, identifyingLabels) { | ||
const newIssueData = { | ||
@@ -94,7 +114,7 @@ fields: { | ||
"security-hub", | ||
finding.region, | ||
finding.severity, | ||
finding.accountAlias, | ||
finding.awsAccountId, | ||
...identifyingLabels, | ||
], | ||
...this.customJiraFields, | ||
}, | ||
@@ -105,3 +125,3 @@ }; | ||
} | ||
createJiraIssuesForNewFindings(jiraIssues, shFindings) { | ||
createJiraIssuesForNewFindings(jiraIssues, shFindings, identifyingLabels) { | ||
const existingJiraIssueTitles = jiraIssues.map((i) => i.fields.summary); | ||
@@ -113,5 +133,5 @@ const uniqueSecurityHubFindings = [ | ||
.filter((finding) => !existingJiraIssueTitles.includes(`SecurityHub Finding - ${finding.title}`)) | ||
.forEach((finding) => this.createJiraIssueFromFinding(finding)); | ||
.forEach((finding) => this.createJiraIssueFromFinding(finding, identifyingLabels)); | ||
} | ||
} | ||
exports.SecurityHubJiraSync = SecurityHubJiraSync; |
@@ -7,3 +7,3 @@ { | ||
}, | ||
"version": "1.0.2", | ||
"version": "1.1.0", | ||
"description": "NPM module to create Jira issues for all findings in Security Hub for the current AWS account..", | ||
@@ -31,7 +31,4 @@ "main": "./dist/index.js", | ||
"devDependencies": { | ||
"@aws-sdk/client-iam": "^3.259.0", | ||
"@aws-sdk/client-securityhub": "^3.257.0", | ||
"@semantic-release/changelog": "^6.0.2", | ||
"@semantic-release/git": "^10.0.1", | ||
"@types/jira-client": "^7.1.6", | ||
"@types/node": "^18.11.18", | ||
@@ -45,3 +42,3 @@ "@vitest/coverage-c8": "^0.28.2", | ||
"typescript": "^4.9.4", | ||
"vitest": "^0.28.2" | ||
"vitest": "^0.28.4" | ||
}, | ||
@@ -60,2 +57,6 @@ "release": { | ||
"dependencies": { | ||
"@aws-sdk/client-iam": "^3.266.0", | ||
"@aws-sdk/client-securityhub": "^3.266.0", | ||
"@aws-sdk/client-sts": "^3.266.1", | ||
"@types/jira-client": "^7.1.6", | ||
"dotenv": "^16.0.3", | ||
@@ -62,0 +63,0 @@ "jira-client": "^8.2.2" |
<h1 align="center" style="border-bottom: none;">macpro-security-hub-sync</h1> | ||
<h3 align="center">NPM module to create Jira issues for all findings in Security Hub for the current AWS account.</h3> | ||
<p align="center"> | ||
<a href="https://cmsgov.slack.com/archives/C04MBTV136X"> | ||
<img alt="Slack" src="https://img.shields.io/badge/Slack-channel-purple.svg"> | ||
</a> | ||
<a href="https://github.com/Enterprise-CMCS/macpro-security-hub-sync/releases/latest"> | ||
@@ -27,50 +30,60 @@ <img alt="latest release" src="https://img.shields.io/github/release/Enterprise-CMCS/macpro-security-hub-sync.svg"> | ||
## Information | ||
## Usage | ||
This package syncs AWS Security Hub Findings to Jira. | ||
Set a few enviroment variables that are expected by the package: | ||
- When the sync utility is run, each Security Hub Finding type (Title) is represented as a single issue. So if you have violated the 'S3.8' rule three individual times, you will have one S3.8 Jira Issue created. | ||
- By default, CRITICAL and HIGH severity findings get issues created in Jira. However, this is configurable in either direction (more or less sensitivity). | ||
- When the utility runs, previously created Jira issues that no longer have an active finding are closed. In this way, Jira issues can be automatically closed as the Findings are resolved, if you run the utility on a schedule (recommended). | ||
## Synchronization Process | ||
The SecurityHubJiraSyncOptions class's main function is sync. The sync process follows this process: | ||
Step 1. Get all open Security Hub issues from Jira | ||
Step 2. Get all current findings from Security Hub | ||
Step 3. Close existing Jira issues if their finding is no longer active/current | ||
Step 4. Create Jira issue for current findings that do not already have a Jira issue | ||
## Usage and Getting Started | ||
To install the package run the following command: | ||
``` | ||
npm install --save-dev @enterprise-cmcs/macpro-security-hub-sync | ||
export JIRA_HOST=yourorg.atlassian.net | ||
export JIRA_PROJECT=OY2 // This is the ID for the Jira Project you want to interact with | ||
export JIRA_USERNAME="myuser@example.com" | ||
export JIRA_TOKEN="a very long string" // This should be a [Personal Access Token](https://confluence.atlassian.com/enterprise/using-personal-access-tokens-1026032365.html) that you generate | ||
``` | ||
or | ||
Install the package with a dependency manager of your choice, probably as a dev dependency: | ||
``` | ||
yarn add --dev @enterprise-cmcs/macpro-security-hub-sync | ||
npm install @enterprise-cmcs/macpro-security-hub-sync --save-dev | ||
``` | ||
After installing the package in your project include this import statement | ||
Import the package and execute a sync: | ||
``` | ||
import { SecurityHubJiraSync } from "@enterprise-cmcs/macpro-security-hub-sync"; | ||
await new SecurityHubJiraSync().sync(); | ||
``` | ||
With SecurityHubJiraSync imported you can now execute it like: | ||
Or, override defaults by passing more options: | ||
``` | ||
await new SecurityHubJiraSync({ region = "us-east-1", severities: ["MEDIUM"] }).sync(); | ||
await new SecurityHubJiraSync({ | ||
region: "us-west-2", // Which regional Security Hub to scrape; default is "us-east-1" | ||
severities: ["HIGH","CRITICAL"], // List of all severities to find; default is ["MEDIUM","HIGH","CRITICAL"] | ||
customJiraFields: { // A map of custom fields to add to each Jira Issue; no default; making this nicer is WIP | ||
customfield_14117: [{value: "Platform Team"}], | ||
customfield_14151: [{value: "Not Applicable "}], | ||
} | ||
}).sync(); | ||
``` | ||
## Contributing | ||
## Info | ||
Found a bug, want to help with updating the docs or maybe you want to help add a feature. Refer to our contribution documentation for more information: [Documentation](./docs/CONTRIBUTING.MD) | ||
#### Overview | ||
## Instructions to test locally with a yarn project | ||
This package syncs AWS Security Hub Findings to Jira. | ||
- When the sync utility is run, each Security Hub Finding type (Title) is represented as a single issue. So if you have violated the 'S3.8' rule three individual times, you will have one S3.8 Jira Issue created. | ||
- By default, CRITICAL and HIGH severity findings get issues created in Jira. However, this is configurable in either direction (more or less sensitivity). | ||
- When the utility runs, previously created Jira issues that no longer have an active finding are closed. In this way, Jira issues can be automatically closed as the Findings are resolved, if you run the utility on a schedule (recommended). | ||
#### Sync Process | ||
The SecurityHubJiraSyncOptions class's main function is sync. The sync process follows this process: | ||
1. Get all open Security Hub issues (identified by a label convention) from Jira | ||
2. Get all current findings from Security Hub | ||
3. Close existing Jira issues if their finding is no longer active/current | ||
4. Create Jira issue (including labels from our label convention) for current findings that do not already have a Jira issue | ||
#### Instructions to test locally with a yarn project | ||
- in your terminal from your local clone of macpro-security-hub-sync with your development branch | ||
@@ -107,6 +120,12 @@ - `yarn link` (note, when testing is complete, run `yarn unlink`) | ||
## Contributing | ||
You can check out our current open issues [here](https://github.com/Enterprise-CMCS/macpro-security-hub-sync/issues). Please feel free to open new issues for bugs or enhancements. | ||
Also, join us on [Slack](https://cmsgov.slack.com/archives/C04MBTV136X) | ||
## License | ||
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT) | ||
[![License](https://img.shields.io/badge/License-CC0--1.0--Universal-blue.svg)](https://creativecommons.org/publicdomain/zero/1.0/legalcode) | ||
See [LICENSE](LICENSE) for full details. |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Unidentified License
License(Experimental) Something that seems like a license was found, but its contents could not be matched with a known license.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
28396
11
442
130
9
6
1
80
+ Added@aws-sdk/client-iam@^3.266.0
+ Added@aws-sdk/client-sts@^3.266.1
+ Added@types/jira-client@^7.1.6
+ Added@aws-crypto/sha256-browser@5.2.0(transitive)
+ Added@aws-crypto/sha256-js@5.2.0(transitive)
+ Added@aws-crypto/supports-web-crypto@5.2.0(transitive)
+ Added@aws-crypto/util@5.2.0(transitive)
+ Added@aws-sdk/client-iam@3.712.0(transitive)
+ Added@aws-sdk/client-securityhub@3.712.0(transitive)
+ Added@aws-sdk/client-sso@3.712.0(transitive)
+ Added@aws-sdk/client-sso-oidc@3.712.0(transitive)
+ Added@aws-sdk/client-sts@3.712.0(transitive)
+ Added@aws-sdk/core@3.709.0(transitive)
+ Added@aws-sdk/credential-provider-env@3.709.0(transitive)
+ Added@aws-sdk/credential-provider-http@3.709.0(transitive)
+ Added@aws-sdk/credential-provider-ini@3.712.0(transitive)
+ Added@aws-sdk/credential-provider-node@3.712.0(transitive)
+ Added@aws-sdk/credential-provider-process@3.709.0(transitive)
+ Added@aws-sdk/credential-provider-sso@3.712.0(transitive)
+ Added@aws-sdk/credential-provider-web-identity@3.709.0(transitive)
+ Added@aws-sdk/middleware-host-header@3.709.0(transitive)
+ Added@aws-sdk/middleware-logger@3.709.0(transitive)
+ Added@aws-sdk/middleware-recursion-detection@3.709.0(transitive)
+ Added@aws-sdk/middleware-user-agent@3.709.0(transitive)
+ Added@aws-sdk/region-config-resolver@3.709.0(transitive)
+ Added@aws-sdk/token-providers@3.709.0(transitive)
+ Added@aws-sdk/types@3.709.0(transitive)
+ Added@aws-sdk/util-endpoints@3.709.0(transitive)
+ Added@aws-sdk/util-locate-window@3.693.0(transitive)
+ Added@aws-sdk/util-user-agent-browser@3.709.0(transitive)
+ Added@aws-sdk/util-user-agent-node@3.712.0(transitive)
+ Added@smithy/abort-controller@3.1.9(transitive)
+ Added@smithy/config-resolver@3.0.13(transitive)
+ Added@smithy/core@2.5.5(transitive)
+ Added@smithy/credential-provider-imds@3.2.8(transitive)
+ Added@smithy/fetch-http-handler@4.1.2(transitive)
+ Added@smithy/hash-node@3.0.11(transitive)
+ Added@smithy/invalid-dependency@3.0.11(transitive)
+ Added@smithy/is-array-buffer@2.2.03.0.0(transitive)
+ Added@smithy/middleware-content-length@3.0.13(transitive)
+ Added@smithy/middleware-endpoint@3.2.5(transitive)
+ Added@smithy/middleware-retry@3.0.30(transitive)
+ Added@smithy/middleware-serde@3.0.11(transitive)
+ Added@smithy/middleware-stack@3.0.11(transitive)
+ Added@smithy/node-config-provider@3.1.12(transitive)
+ Added@smithy/node-http-handler@3.3.2(transitive)
+ Added@smithy/property-provider@3.1.11(transitive)
+ Added@smithy/protocol-http@4.1.8(transitive)
+ Added@smithy/querystring-builder@3.0.11(transitive)
+ Added@smithy/querystring-parser@3.0.11(transitive)
+ Added@smithy/service-error-classification@3.0.11(transitive)
+ Added@smithy/shared-ini-file-loader@3.1.12(transitive)
+ Added@smithy/signature-v4@4.2.4(transitive)
+ Added@smithy/smithy-client@3.5.0(transitive)
+ Added@smithy/types@3.7.2(transitive)
+ Added@smithy/url-parser@3.0.11(transitive)
+ Added@smithy/util-base64@3.0.0(transitive)
+ Added@smithy/util-body-length-browser@3.0.0(transitive)
+ Added@smithy/util-body-length-node@3.0.0(transitive)
+ Added@smithy/util-buffer-from@2.2.03.0.0(transitive)
+ Added@smithy/util-config-provider@3.0.0(transitive)
+ Added@smithy/util-defaults-mode-browser@3.0.30(transitive)
+ Added@smithy/util-defaults-mode-node@3.0.30(transitive)
+ Added@smithy/util-endpoints@2.1.7(transitive)
+ Added@smithy/util-hex-encoding@3.0.0(transitive)
+ Added@smithy/util-middleware@3.0.11(transitive)
+ Added@smithy/util-retry@3.0.11(transitive)
+ Added@smithy/util-stream@3.3.2(transitive)
+ Added@smithy/util-uri-escape@3.0.0(transitive)
+ Added@smithy/util-utf8@2.3.03.0.0(transitive)
+ Added@smithy/util-waiter@3.2.0(transitive)
+ Added@types/caseless@0.12.5(transitive)
+ Added@types/jira-client@7.1.9(transitive)
+ Added@types/node@22.10.2(transitive)
+ Added@types/request@2.48.12(transitive)
+ Added@types/tough-cookie@4.0.5(transitive)
+ Addedbowser@2.11.0(transitive)
+ Addedfast-xml-parser@4.4.1(transitive)
+ Addedform-data@2.5.2(transitive)
+ Addedstrnum@1.0.5(transitive)
+ Addedtslib@2.8.1(transitive)
+ Addedundici-types@6.20.0(transitive)
+ Addeduuid@9.0.1(transitive)