Skip to content

Commit

Permalink
feat: Support Org and Enterprise level alerts (#190)
Browse files Browse the repository at this point in the history
Fixes #187
  • Loading branch information
kunalnagar authored Aug 5, 2024
1 parent 308c6db commit 19e0872
Show file tree
Hide file tree
Showing 10 changed files with 156 additions and 33 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ jobs:
uses: ./
with:
token: ${{ secrets.PERSONAL_ACCESS_TOKEN }}
# org: ${{ secrets.ORG_NAME }}
# enterprise: ${{ secrets.ENTERPRISE_NAME }}
# microsoft_teams_webhook: ${{ secrets.MICROSOFT_TEAMS_WEBHOOK }}
# slack_webhook: ${{ secrets.SLACK_WEBHOOK }}
# severity: low,medium
Expand Down
8 changes: 6 additions & 2 deletions action.yml
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
name: 'check-cve'
description: 'Send GitHub vulnerability alerts to multiple platforms like Slack, PagerDuty.'
description: 'Send GitHub vulnerability alerts to multiple platforms.'
author: '@kunalnagar'
inputs:
token:
description: 'GitHub Personal Access Token'
org:
description: 'Org name to support Org level alerts: https://docs.github.com/en/rest/dependabot/alerts?apiVersion=2022-11-28#list-dependabot-alerts-for-an-organization'
enterprise:
description: 'Enterprise name to support Enterprise level alerts: https://docs.github.com/en/rest/dependabot/alerts?apiVersion=2022-11-28#list-dependabot-alerts-for-an-enterprise'
microsoft_teams_webhook:
description: 'Microsoft Teams Channel Webhook URL. More info: https://docs.microsoft.com/en-us/microsoftteams/platform/webhooks-and-connectors/how-to/add-incoming-webhook'
slack_webhook:
Expand Down Expand Up @@ -38,7 +42,7 @@ inputs:
severity:
description: 'Comma separated list of severities. E.g. low,medium,high,critical (NO SPACES BETWEEN COMMA AND SEVERITY)'
ecosystem:
description: "A comma-separated list of ecosystems. If specified, only alerts for these ecosystems will be returned."
description: 'A comma-separated list of ecosystems. If specified, only alerts for these ecosystems will be returned.'
branding:
icon: 'alert-octagon'
color: 'red'
Expand Down
14 changes: 5 additions & 9 deletions src/destinations/email.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { Alert, getFullRepositoryNameFromAlert } from '../entities'
const createTableRow = (alert: Alert): string => `
<tr>
<td>${alert.packageName}</td>
<td>${getFullRepositoryNameFromAlert(alert)}</td>
<td>${alert.vulnerability?.vulnerableVersionRange}</td>
<td>${alert.vulnerability?.firstPatchedVersion}</td>
<td>${alert.advisory?.severity}</td>
Expand All @@ -23,7 +24,8 @@ const createTable = (alerts: Alert[]): string => {
return `
<table border="1" cellpadding="10" width="100%">
<thead>
<th>Package name</th>
<th>Package</th>
<th>Repository</th>
<th>Vulnerability Version Range</th>
<th>Patched Version</th>
<th>Severity</th>
Expand All @@ -39,9 +41,7 @@ const createTable = (alerts: Alert[]): string => {

const createEmailBody = (alerts: Alert[]): string => `
<p>Hello,</p>
<p>You are receiving this message as you have set up email notifications for vulnerabilities in <b>${getFullRepositoryNameFromAlert(
alerts[0],
)}</b> via <a href="${ACTION_URL}">${ACTION_SHORT_SUMMARY}</a>.</p>
<p>You are receiving this message as you have set up email notifications for vulnerabilities via <a href="${ACTION_URL}">${ACTION_SHORT_SUMMARY}</a>.</p>
${createTable(alerts)}
`

Expand All @@ -56,11 +56,7 @@ export const sendAlertsToEmailSmtp = async (
await transporter.sendMail({
from: emailFrom,
bcc: emailList,
subject:
subject ||
`${ACTION_SHORT_SUMMARY} - ${
alerts.length
} vulnerabilities in ${getFullRepositoryNameFromAlert(alerts[0])}`,
subject: subject || ACTION_SHORT_SUMMARY,
html: createEmailBody(alerts),
})
}
7 changes: 5 additions & 2 deletions src/destinations/microsoft-teams.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
request,
} from '../utils'
import { ACTION_SHORT_SUMMARY } from '../constants'
import { Alert } from '../entities'
import { Alert, getFullRepositoryNameFromAlert } from '../entities'

const createTableRow = (key: string, value: string): Row => {
const row = createRow()
Expand Down Expand Up @@ -53,7 +53,10 @@ export const sendAlertsToMicrosoftTeams = async (

alerts.forEach((alert) => {
const container = createContainer(true, true)
container.addItem(createTableRow('Package Name', alert.packageName))
container.addItem(createTableRow('Package', alert.packageName))
container.addItem(
createTableRow('Repository', getFullRepositoryNameFromAlert(alert)),
)
container.addItem(
createTableRow(
'Vulnerability Version Range',
Expand Down
2 changes: 1 addition & 1 deletion src/destinations/pager-duty.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export const sendAlertsToPagerDuty = async (
routing_key: integrationKey,
event_action: 'trigger',
payload: {
summary: `You have ${alerts.length} vulnerabilities in ${alerts[0].repository.owner}/${alerts[0].repository.name}`,
summary: `You have ${alerts.length} vulnerabilities`,
source: 'GitHub Dependabot Alerts',
severity: 'info',
custom_details: { ...alerts },
Expand Down
5 changes: 3 additions & 2 deletions src/destinations/slack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { IncomingWebhook } from '@slack/webhook'
import { KnownBlock } from '@slack/types'

import { ACTION_ICON, ACTION_SHORT_SUMMARY } from '../constants'
import { Alert } from '../entities'
import { Alert, getFullRepositoryNameFromAlert } from '../entities'

export const MAX_COUNT_SLACK = 30

Expand Down Expand Up @@ -33,7 +33,8 @@ const createAlertBlock = (alert: Alert): KnownBlock => ({
text: {
type: 'mrkdwn',
text: `
*Package name:* ${alert.packageName}
*Package:* ${alert.packageName}
*Repository:* ${getFullRepositoryNameFromAlert(alert)}
*Vulnerability Version Range:* ${alert.vulnerability?.vulnerableVersionRange}
*Patched Version:* ${alert.vulnerability?.firstPatchedVersion}
*Severity:* ${alert.advisory?.severity}
Expand Down
5 changes: 3 additions & 2 deletions src/destinations/zenduty.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ACTION_SHORT_SUMMARY } from '../constants'
import { Alert } from '../entities'
import { Alert, getFullRepositoryNameFromAlert } from '../entities'
import { request } from '../utils'

export const sendAlertsToZenduty = async (
Expand All @@ -16,7 +16,8 @@ export const sendAlertsToZenduty = async (
`
alerts.forEach((alert) => {
summary += `
Package name: ${alert.packageName}
Package: ${alert.packageName}
Repository: ${getFullRepositoryNameFromAlert(alert)}
Vulnerability Version Range: ${alert.vulnerability?.vulnerableVersionRange}
Patched Version: ${alert.vulnerability?.firstPatchedVersion}
Severity: ${alert.advisory?.severity}
Expand Down
41 changes: 40 additions & 1 deletion src/entities/alert.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export interface Alert {
createdAt: string
}

export const toAlert = (
export const toRepositoryAlert = (
dependabotAlert: DependabotAlert,
repositoryName: string,
repositoryOwner: string,
Expand All @@ -33,3 +33,42 @@ export const toAlert = (
: undefined,
createdAt: dependabotAlert.created_at,
})

export type DependabotOrgAlert =
Endpoints['GET /orgs/{org}/dependabot/alerts']['response']['data'][0]

export const toOrgAlert = (dependabotOrgAlert: DependabotOrgAlert): Alert => ({
repository: {
name: dependabotOrgAlert.repository.name,
owner: dependabotOrgAlert.repository.owner.login,
},
packageName: dependabotOrgAlert.security_vulnerability.package.name || '',
advisory: dependabotOrgAlert.security_advisory
? toAdvisory(dependabotOrgAlert.security_advisory)
: undefined,
vulnerability: dependabotOrgAlert.security_vulnerability
? toVulnerability(dependabotOrgAlert.security_vulnerability)
: undefined,
createdAt: dependabotOrgAlert.created_at,
})

export type DependabotEnterpriseAlert =
Endpoints['GET /enterprises/{enterprise}/dependabot/alerts']['response']['data'][0]

export const toEnterpriseAlert = (
dependabotEnterpriseAlert: DependabotEnterpriseAlert,
): Alert => ({
repository: {
name: dependabotEnterpriseAlert.repository.name,
owner: dependabotEnterpriseAlert.repository.owner.login,
},
packageName:
dependabotEnterpriseAlert.security_vulnerability.package.name || '',
advisory: dependabotEnterpriseAlert.security_advisory
? toAdvisory(dependabotEnterpriseAlert.security_advisory)
: undefined,
vulnerability: dependabotEnterpriseAlert.security_vulnerability
? toVulnerability(dependabotEnterpriseAlert.security_vulnerability)
: undefined,
createdAt: dependabotEnterpriseAlert.created_at,
})
63 changes: 60 additions & 3 deletions src/fetch-alerts.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
import { Octokit } from '@octokit/rest'

import { Alert, toAlert } from './entities'
import {
Alert,
toRepositoryAlert,
toOrgAlert,
toEnterpriseAlert,
} from './entities'

export const fetchAlerts = async (
export const fetchRepositoryAlerts = async (
gitHubPersonalAccessToken: string,
repositoryName: string,
repositoryOwner: string,
Expand All @@ -25,7 +30,59 @@ export const fetchAlerts = async (
per_page: count,
})
const alerts: Alert[] = response.data.map((dependabotAlert) =>
toAlert(dependabotAlert, repositoryName, repositoryOwner),
toRepositoryAlert(dependabotAlert, repositoryName, repositoryOwner),
)
return alerts
}

export const fetchOrgAlerts = async (
gitHubPersonalAccessToken: string,
org: string,
severity: string,
ecosystem: string,
count: number,
): Promise<Alert[] | []> => {
const octokit = new Octokit({
auth: gitHubPersonalAccessToken,
request: {
fetch,
},
})
const response = await octokit.dependabot.listAlertsForOrg({
org,
state: 'open',
severity,
ecosystem: ecosystem.length > 0 ? ecosystem : undefined,
per_page: count,
})
const alerts: Alert[] = response.data.map((dependabotOrgAlert) =>
toOrgAlert(dependabotOrgAlert),
)
return alerts
}

export const fetchEnterpriseAlerts = async (
gitHubPersonalAccessToken: string,
enterprise: string,
severity: string,
ecosystem: string,
count: number,
): Promise<Alert[] | []> => {
const octokit = new Octokit({
auth: gitHubPersonalAccessToken,
request: {
fetch,
},
})
const response = await octokit.dependabot.listAlertsForEnterprise({
enterprise,
state: 'open',
severity,
ecosystem: ecosystem.length > 0 ? ecosystem : undefined,
per_page: count,
})
const alerts: Alert[] = response.data.map((dependabotEnterpriseAlert) =>
toEnterpriseAlert(dependabotEnterpriseAlert),
)
return alerts
}
42 changes: 31 additions & 11 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,18 @@ import {
sendAlertsToEmailSmtp,
validateSlackWebhookUrl,
} from './destinations'
import { fetchAlerts } from './fetch-alerts'
import {
fetchRepositoryAlerts,
fetchOrgAlerts,
fetchEnterpriseAlerts,
} from './fetch-alerts'
import { Alert } from './entities'

async function run(): Promise<void> {
try {
const token = getInput('token')
const org = getInput('org')
const enterprise = getInput('enterprise')
const microsoftTeamsWebhookUrl = getInput('microsoft_teams_webhook')
const slackWebhookUrl = getInput('slack_webhook')
const pagerDutyIntegrationKey = getInput('pager_duty_integration_key')
Expand All @@ -32,16 +39,29 @@ async function run(): Promise<void> {
const count = parseInt(getInput('count'))
const severity = getInput('severity')
const ecosystem = getInput('ecosystem')
const { owner } = context.repo
const { repo } = context.repo
const alerts = await fetchAlerts(
token,
repo,
owner,
severity,
ecosystem,
count,
)

let alerts: Alert[] = []
if (org) {
alerts = await fetchOrgAlerts(token, org, severity, ecosystem, count)
} else if (enterprise) {
alerts = await fetchEnterpriseAlerts(
token,
org,
severity,
ecosystem,
count,
)
} else {
const { owner, repo } = context.repo
alerts = await fetchRepositoryAlerts(
token,
repo,
owner,
severity,
ecosystem,
count,
)
}
if (alerts.length > 0) {
if (microsoftTeamsWebhookUrl) {
await sendAlertsToMicrosoftTeams(microsoftTeamsWebhookUrl, alerts)
Expand Down

0 comments on commit 19e0872

Please sign in to comment.