Skip to content

Commit

Permalink
Log if a repo contains a vulnerability older than it should (#724)
Browse files Browse the repository at this point in the history
* tell me how many old vulnerabilities have been detected on repocop


* create isProduction function

* Made evaluate repositories not async,
pulled alerts for prod repos up front

Co-authored-by: Akash Askoolum <akash1810@users.noreply.github.com>

* relate tests to rules, add unit tests

* remove alerting from experimental features
more logging

* get all open criticals/highs

---------

Co-authored-by: Akash Askoolum <akash1810@users.noreply.github.com>
Co-authored-by: Chris Jones <chrislomaxjones96@gmail.com>
  • Loading branch information
3 people authored Jan 24, 2024
1 parent 16314f3 commit cb6be47
Show file tree
Hide file tree
Showing 6 changed files with 226 additions and 52 deletions.
33 changes: 30 additions & 3 deletions packages/repocop/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,12 @@ import { sendPotentialInteractives } from './remediations/topics/topic-monitor-i
import { applyProductionTopicAndMessageTeams } from './remediations/topics/topic-monitor-production';
import {
evaluateRepositories,
getAlertsForRepo,
hasOldAlerts,
testExperimentalRepocopFeatures,
} from './rules/repository';
import type { AwsCloudFormationStack } from './types';
import type { AwsCloudFormationStack, RepoAndAlerts } from './types';
import { isProduction } from './utils';

async function writeEvaluationTable(
evaluatedRepos: repocop_github_repository_rules[],
Expand Down Expand Up @@ -63,8 +66,33 @@ export async function main() {
await getStacks(prisma)
).filter((s) => s.tags.Stack !== 'playground');
const snykProjects = await getSnykProjects(prisma);

const prodRepos = unarchivedRepos.filter((repo) => isProduction(repo));
const alerts: RepoAndAlerts[] = (
await Promise.all(
prodRepos.map(async (repo) => {
return {
shortName: repo.full_name,
alerts: await getAlertsForRepo(octokit, repo.name),
};
}),
)
).filter((x) => !!x.alerts);

alerts.forEach((alert) => {
if (alert.alerts && alert.alerts.length > 0) {
console.log(
`Found ${alert.alerts.length} alerts for ${alert.shortName}: `,
);
hasOldAlerts(alert.alerts, alert.shortName);
}
});

console.log(`Found ${alerts.length} repos with alerts`);

const evaluatedRepos: repocop_github_repository_rules[] =
evaluateRepositories(
alerts,
unarchivedRepos,
branches,
repoTeams,
Expand All @@ -77,8 +105,7 @@ export async function main() {
const cloudwatch = new CloudWatchClient(awsConfig);
await sendToCloudwatch(evaluatedRepos, cloudwatch, config);

await testExperimentalRepocopFeatures(
octokit,
testExperimentalRepocopFeatures(
evaluatedRepos,
unarchivedRepos,
archivedRepos,
Expand Down
91 changes: 82 additions & 9 deletions packages/repocop/src/rules/repository.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@ import type {
} from '@prisma/client';
import type {
AwsCloudFormationStack,
PartialAlert,
Repository,
TeamRepository,
} from '../types';
import {
evaluateOneRepo,
findStacks,
hasDependencyTracking,
hasOldAlerts,
parseSnykTags,
} from './repository';

Expand All @@ -23,8 +25,10 @@ function evaluateRepoTestHelper(
languages: github_languages[] = [],
snykProjects: snyk_projects[] = [],
githubWorkflows: github_workflows[] = [],
alerts: PartialAlert[] = [],
) {
return evaluateOneRepo(
alerts,
repo,
branches,
teams,
Expand Down Expand Up @@ -69,7 +73,7 @@ const thePerfectRepo: Repository = {
default_branch: 'main',
};

describe('default_branch_name should be false when the default branch is not main', () => {
describe('REPOSITORY_01 - default_branch_name should be false when the default branch is not main', () => {
test('branch is not main', () => {
const badRepo = { ...thePerfectRepo, default_branch: 'notMain' };
const repos: Repository[] = [thePerfectRepo, badRepo];
Expand All @@ -82,7 +86,7 @@ describe('default_branch_name should be false when the default branch is not mai
});
});

describe('Repositories should have branch protection', () => {
describe('REPOSITORY_02 - Repositories should have branch protection', () => {
const unprotectedMainBranch: github_repository_branches = {
...nullBranch,
repository_id: BigInt(1),
Expand Down Expand Up @@ -134,7 +138,7 @@ describe('Repositories should have branch protection', () => {
});
});

describe('Repository admin access', () => {
describe('REPOSITORY_04 - Repository admin access', () => {
test('Should return false when there is no admin team', () => {
const repo: Repository = {
...nullRepo,
Expand Down Expand Up @@ -228,7 +232,7 @@ describe('Repository admin access', () => {
});
});

describe('Repository topics', () => {
describe('REPOSITORY_06 - Repository topics', () => {
test('Should return true when there is a single recognised topic', () => {
const repo: Repository = {
...nullRepo,
Expand Down Expand Up @@ -294,7 +298,8 @@ describe('Repository topics', () => {
});
});

describe('Repository maintenance', () => {
// No rule for this evaluation yet
describe('NO RULE - Repository maintenance', () => {
test('should have happened at some point in the last two years', () => {
const recentRepo: Repository = {
...nullRepo,
Expand Down Expand Up @@ -334,7 +339,7 @@ describe('Repository maintenance', () => {
});
});

describe('Repositories with related stacks on AWS', () => {
describe('REPOSITORY_08 - Repositories with related stacks on AWS', () => {
test('should be findable if a stack has a matching tag', () => {
const full_name = 'guardian/repo1';
const tags = {
Expand Down Expand Up @@ -372,7 +377,7 @@ describe('Repositories with related stacks on AWS', () => {
});
});

describe('Repositories without any related stacks on AWS', () => {
describe('REPOSITORY_08 - Repositories without any related stacks on AWS', () => {
test('should not be findable', () => {
const repo: Repository = {
...nullRepo,
Expand Down Expand Up @@ -407,7 +412,7 @@ describe('Repositories without any related stacks on AWS', () => {
});
});

describe('Snyk tags', () => {
describe('REPOSITORY_09 - Snyk tags', () => {
const nullSnykProject: snyk_projects = {
cq_source_name: null,
cq_sync_time: null,
Expand Down Expand Up @@ -447,7 +452,7 @@ describe('Snyk tags', () => {
});
});

describe('Dependency tracking', () => {
describe('REPOSITORY_09 - Dependency tracking', () => {
const nullSnykProject: snyk_projects = {
cq_source_name: null,
cq_sync_time: null,
Expand Down Expand Up @@ -643,3 +648,71 @@ describe('Dependency tracking', () => {
expect(actual).toEqual(false);
});
});

function createAlert(
severity: 'critical' | 'high' | 'medium',
createdAt: Date,
state: 'open' | 'auto_dismissed' | 'dismissed' | 'fixed',
): PartialAlert {
const alert = {
state,
created_at: createdAt.toISOString(),
security_vulnerability: {
severity,
vulnerable_version_range: '',
first_patched_version: {
identifier: '',
},
package: {
name: '',
ecosystem: '',
},
},
dependency: {
package: {
name: '',
ecosystem: '',
},
},
};
return alert;
}

describe('NO RULE - Repository alerts', () => {
test('should be flagged if there are critical alerts older than one day', () => {
console.log(new Date('2021-01-01').toISOString());
const alerts: PartialAlert[] = [
createAlert('critical', new Date('2021-01-01'), 'open'),
];

expect(hasOldAlerts(alerts, 'test')).toBe(true);
});
test('should not be flagged if a critical alert was raised today', () => {
const alerts: PartialAlert[] = [
createAlert('critical', new Date(), 'open'),
];

expect(hasOldAlerts(alerts, 'test')).toBe(false);
});
test('should be flagged if there are high alerts older than 14 days', () => {
const alerts: PartialAlert[] = [
createAlert('high', new Date('2021-01-01'), 'open'),
];

expect(hasOldAlerts(alerts, 'test')).toBe(true);
});
test('should not be flagged if a high alert was raised today', () => {
const alerts: PartialAlert[] = [createAlert('high', new Date(), 'open')];

expect(hasOldAlerts(alerts, 'test')).toBe(false);
});
test('should not be flagged if a high alert was raised 13 days ago', () => {
const thirteenDaysAgo = new Date();
thirteenDaysAgo.setDate(thirteenDaysAgo.getDate() - 13);
const alerts: PartialAlert[] = [
createAlert('high', thirteenDaysAgo, 'open'),
];

expect(hasOldAlerts(alerts, 'test')).toBe(false);
});
});
Loading

0 comments on commit cb6be47

Please sign in to comment.