Sonatype Nexus Lifecycle is an SCA product. Currently, Sonatype Nexus Lifecycle cannot push violation events to Jira cloud through a builtin plugin or software. This is a middleware component that can listen to Lifecycle violation events and create Jira cloud tickets
- Implements a POST endpoint /lifecycle/violation that will listen to Nexus Lifecycle violation events
- Works with Jira cloud (Should work in Jira datacenter as well, but not tested)
- Lightweight Express JS middleware
- Configurable Jira payload template
- Post data to Jira webhook that creates a story/bug. If there are multiple violations and multiple components per violation, a jira ticket is created per component. So one event can trigger multiple Jira tickets
- Jira webhook is tied to a specific project. This middleware supports pushing jira events to only 1 webhook/project
- Lifecycle will send all events for every violation. Dedupe using componentFact.hash in the Jira webhook configuration using a custom field
- Deployable as a Docker container or Helm chart in a K8s environment
- Structured logging to help with debugging
- Unit tested. Run
npm test
Following environment variables are available for customizations:
-
LIFECYCLE_SECRET_KEY: Optional. Set this if a HMAC secret key is set in Lifecycle webhook admin page
-
ENABLE_JIRA_WEBHOOK: Set to true to create Jira tickets. Else the event will be received, but no data will be posted to Jira. Defaults to 'false'
-
JIRA_WEBHOOK_AUTH_TOKEN: Required. Jira webhook doesn't have built-in authentication mechanism, it's better to use a made-up encoded string and match it on the webhook side.
-
JIRA_WEBHOOK_HOST: Required. Jira webhook base url
-
JIRA_WEBHOOK_PATH: Required. Jira webhook rest of the url
-
LIFECYCLE_BASE_URL: Required. Nexus lifecycle base url/host
-
LIFECYCLE_APP_REPORT_BASE_URL: Required. Nexus Lifecycle report url path
-
MAPPING_THREAT_LEVEL_TO_JIRA_FIELDS: Required. Threat level is mapped to a policy category like License/Security. Each category has it own definition of priority, severity and bug nature Example:
{ "10": { "License": {"priority": "P1", "severity": "S1", "bugNature": "SCA-License"}, "Security": {"priority": "P1", "severity": "S1", "bugNature": "SCA-Security"} }, "9": { "License": {"priority": "P1", "severity": "S1", "bugNature": "SCA-License"}, "Security": {"priority": "P1", "severity": "S2", "bugNature": "SCA-Security"} }, "8": { "License": {"priority": "P1", "severity": "S1", "bugNature": "SCA-License"}, "Security": {"priority": "P1", "severity": "S3", "bugNature": "SCA-Security"} }, "7": { "License": {"priority": "P1", "severity": "S1", "bugNature": "SCA-License"}, "Security": {"priority": "P2", "severity": "S4", "bugNature": "SCA-Security"} } }
-
MAPPING_STAGE_TO_BRANCH_TYPE: Required. Map Lifecycle stage to an SCM branch type like
{ "build": "develop", "stage-release": "master", "release": "release" }
-
MAPPING_APPID_TO_SCRUM_TEAM: Optional. Map an application to a Jira scrum team
{
"appPublicId": "team1",
"appPublicId2": "team2",
"appPublicId3": "team1"
}
- PORT: Exposed port. Defaults to 3000
- LOG_LEVEL: Minimum log level. Defaults to info
- jiraTemplate.json: Jira webhook POST payload structure
- index.js: Entrypoint
- Dockerfile: Docker file
- chart/: Helm chart directory with templates and chart information to deploy to any K8s cluster
- src/: Source code
- test/: Unit tests
- Work with your Jira admin to create a Jira automation webhook
- Configure Jira webhook actions and mapping to create the desired Jira type and format
- Work with your Nexus Lifecycle admin to configure a webhook with the url of the POST endpoint(/lifecycle/violation)
- Preferable set HMAC secret
- Set notification to Webhook and select the webhook per policy in the Orgs and Policies section
- Create container and run in an environment where Nexus Lifecycle can access the url
# Pull docker image from docker hub
docker pull anoopnair/lifecycle-jira-integration:latest
# Run webhook container
# Add env variables using -e
docker run -p 3000:3000 --name my-lifecycle-jira-integration --rm -d -e PORT=3000 anoopnair/lifecycle-jira-integration
# Ping endpoint and get a "pong" response
curl localhost:3000/ping
- Ensure Nexus lifecycle can access the webhook url
- Re-evaluate an application to manually create a violation
- Verify Jira ticket is created based on the violation
- Monitor container logs
docker logs -f my-lifecycle-jira-integration
Refer Helm chart README
Below is a request to simulate a Lifecycle violation alert event:
curl --request POST \
--url http://localhost:3000/lifecycle/violation \
--header 'Content-Type: application/json' \
--header 'X-Nexus-Webhook-Delivery: 12343' \
--header 'X-Nexus-Webhook-ID: iq:policyAlert' \
--data '{
"initiator": "admin",
"applicationEvaluation": {
"application": {
"id": "1e010417a9fd4624b0eaccebccac21f6",
"publicId": "appPublicId",
"name": "My app",
"organizationId": "2edd9a73b5444ca7b563501445b7b2fc"
},
"policyEvaluationId": "e534d2c0bb64473a8206ead3cdee9d84",
"stage": "build",
"ownerId": "5c2cb33bc52e48b7ad04b4905bf74337",
"evaluationDate": "2019-08-27T20:33:47.854+0000",
"affectedComponentCount": 1,
"criticalComponentCount": 1,
"severeComponentCount": 0,
"moderateComponentCount": 0,
"outcome": "fail",
"reportId": "38e07c8866a242a485e6d7d2c1fd5692"
},
"application": {
"id": "1e010417a9fd4624b0eaccebccac21f6",
"publicId": "appPublicId",
"name": "My app",
"organizationId": "2edd9a73b5444ca7b563501445b7b2fc"
},
"policyAlerts": [
{
"policyId": "6f981ceb94684b3da36ee1a1d863956f",
"policyName": "Security-Critical",
"threatLevel": 10,
"componentFacts": [
{
"hash": "40fb048097caeacdb11d",
"displayName": "apache-collections : commons-collections : 3.1",
"componentIdentifier": {
"format": "maven",
"coordinates": {
"artifactId": "commons-collections",
"classifier": "",
"extension": "jar",
"groupId": "apache-collections",
"version": "3.1"
}
},
"pathNames": [],
"constraintFacts": [
{
"constraintName": "Critical risk CVSS score",
"satisfiedConditions": [
{
"summary": "Security Vulnerability Severity >= 9",
"reason": "Found security vulnerability sonatype-2015-0002 with severity 9.0."
}
]
}
]
},
{
"hash": "10fb048097caeacdb11d",
"displayName": "apache : commons-lang : 1.6",
"componentIdentifier": {
"format": "maven",
"coordinates": {
"artifactId": "commons-lang",
"classifier": "",
"extension": "jar",
"groupId": "apache",
"version": "1.6"
}
},
"pathNames": [],
"constraintFacts": [
{
"constraintName": "Critical risk CVSS score",
"satisfiedConditions": [
{
"summary": "Security Vulnerability Severity >= 9",
"reason": "Found security vulnerability sonatype-2015-0002 with severity 9.0."
}
]
}
]
}
],
"policyViolationId": "62c3f1fc67b149f1a584cd63acb23eed"
}
]
}'
The above event will create below 2 Jira requests:
{
"summary":"Fix Security-Critical vulnerability: My app - apache-collections : commons-collections : 3.1",
"priority":"P1",
"severity":"Sev-1",
"productVersion":"latest",
"environment":"DEV",
"description":"h1. Summary\r\n||*Application*|My app|\r\n||*Evaluation timestamp*|2019-08-27T20:33:47.854+0000|\r\n||*Lifecycle Stage*|build\r\nBranch type: master/feature/develop|\r\n||*Affected Component count*|1|\r\n||*Critical component count*|1|\r\n||*Severe component count*|0|\r\n||*Moderate component count*|0|\r\n\r\nh1. Violation detail\r\n||*Policy*|License-Critical|\r\n||*Threat level*|7|\r\n||*Component*|[apache-collections : commons-collections : 3.1|http://localhost:8070/assets/index.html#/applicationReport/appPublicId/38e07c8866a242a485e6d7d2c1fd5692/componentDetails/40fb048097caeacdb11d/violations]|\r\n",
"replicationSteps":"http://localhost:8070/assets/index.html#/applicationReport/appPublicId/38e07c8866a242a485e6d7d2c1fd5692/componentDetails/40fb048097caeacdb11d/violations",
"bugNature":"SCA-License",
"labels":"License-Critical",
"policyViolationId":"40fb048097caeacdb11d",
"auth":"1234",
"appId":"appPublicId",
"scrumTeam":"team1"
}
{
"summary":"Fix Security-Critical vulnerability: My app - apache : commons-lang : 1.6",
"priority":"P1",
"severity":"Sev-1",
"productVersion":"latest",
"environment":"DEV",
"description":"h1. Summary\r\n||*Application*|My app|\r\n||*Evaluation timestamp*|2019-08-27T20:33:47.854+0000|\r\n||*Lifecycle Stage*|build\r\nBranch type: master/feature/develop|\r\n||*Affected Component count*|1|\r\n||*Critical component count*|1|\r\n||*Severe component count*|0|\r\n||*Moderate component count*|0|\r\n\r\nh1. Violation detail\r\n||*Policy*|License-Critical|\r\n||*Threat level*|7|\r\n||*Component*|[apache : commons-lang : 1.6|http://localhost:8070/assets/index.html#/applicationReport/appPublicId/38e07c8866a242a485e6d7d2c1fd5692/componentDetails/10fb048097caeacdb11d/violations]%7C\r\n",
"replicationSteps":"http://localhost:8070/assets/index.html#/applicationReport/appPublicId/38e07c8866a242a485e6d7d2c1fd5692/componentDetails/10fb048097caeacdb11d/violations",
"bugNature":"SCA-License",
"labels":"License-Critical",
"policyViolationId":"10fb048097caeacdb11d",
"auth":"1234",
"appId":"appPublicId",
"scrumTeam":"team1"
}