Skip to content

Commit

Permalink
Merge pull request #1064 from guardian/ash/save-obligatron-results
Browse files Browse the repository at this point in the history
Save obligatron results to DB
  • Loading branch information
AshCorr authored Jun 7, 2024
2 parents eeca681 + 5d9c2ff commit 1819a57
Show file tree
Hide file tree
Showing 5 changed files with 90 additions and 72 deletions.
3 changes: 2 additions & 1 deletion packages/obligatron/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export async function getConfig(): Promise<Config> {
return {
stage,
databaseConnectionString: getDatabaseConnectionString(databaseConfig),
withQueryLogging: stage === 'DEV',
// Our insert queries are VERY big, so don't log them
withQueryLogging: false,
};
}
40 changes: 32 additions & 8 deletions packages/obligatron/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,22 @@ export async function main(obligation: string) {
}

const config = await getConfig();
const startTime = new Date();

console.log({
message: 'Starting Obligatron',
obligation,
stage: config.stage,
withQueryLogging: config.withQueryLogging,
startTime,
});

const db = getPrismaClient(config);

console.log({
message: 'Starting to process obligation resources',
});

let results: ObligationResult[];

switch (obligation) {
Expand All @@ -32,13 +46,23 @@ export async function main(obligation: string) {
}
}

// TODO: Save results to DB
// log compliance for whole department
const compliant = results.filter((r) => r.result).length;
const nonCompliant = results.filter((r) => !r.result).length;
const total = compliant + nonCompliant;
console.log({
message: 'Finished processing obligation resources, saving results to DB.',
total: results.length,
});

await db.obligatron_results.createMany({
data: results.map((r) => ({
date: startTime,
obligation_name: obligation,
resource: r.resource,
reason: r.reason,
contacts: r.contacts ?? {},
url: r.url,
})),
});

console.log(`Total Compliant: ${compliant}`);
console.log(`Total Un-compliant: ${nonCompliant}`);
console.log(`Compliance rate: ${(compliant / total) * 100}%`);
console.log({
message: 'Saved results to DB. Goodbye!',
});
}
18 changes: 4 additions & 14 deletions packages/obligatron/src/obligations/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,28 +6,18 @@ export type ObligationResult = {
*/
resource: string;

/**
* Did this resource meet the obligation check?
*/
result: boolean;

/**
* Explanation for the assessment failing.
*/
reasons: string[];
reason: string;

/**
* Link to where the user can see more details on the resource.
*/
deep_link?: string;

/**
* Associated AWS account ID (if any)
*/
aws_account_id?: string;
url?: string;

/**
* Associated Github teams (if any)
* Key-value pairs to link failing obligations to the responsible teams.
*/
github_teams?: string[];
contacts?: Record<string, string>;
};
52 changes: 21 additions & 31 deletions packages/obligatron/src/obligations/tagging.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,7 @@ describe('The tagging obligation', () => {

const results = await evaluateTaggingObligation(client);

expect(results.filter((r) => !r.result)).toHaveLength(0);
expect(results[0]).toMatchObject({
resource: 'arn:aws:s3:::mybucket',
result: true,
reasons: [],
});
expect(results).toHaveLength(0);
});

it('catches missing Stack tags', async () => {
Expand All @@ -56,11 +51,11 @@ describe('The tagging obligation', () => {

const results = await evaluateTaggingObligation(client);

expect(results.filter((r) => !r.result)).toHaveLength(1);
expect(results[0]).toMatchObject({
expect(results).toHaveLength(1);
expect(results[0]).toEqual({
resource: 'arn:aws:s3:::mybucket',
result: false,
reasons: ["Resource missing 'Stack' tag."],
reason: "Resource missing 'Stack' tag.",
contacts: { aws_account: '123456789012' },
});
});

Expand All @@ -82,11 +77,11 @@ describe('The tagging obligation', () => {

const results = await evaluateTaggingObligation(client);

expect(results.filter((r) => !r.result)).toHaveLength(1);
expect(results[0]).toMatchObject({
expect(results).toHaveLength(1);
expect(results[0]).toEqual({
resource: 'arn:aws:s3:::mybucket',
result: false,
reasons: ["Resource missing 'Stage' tag."],
reason: "Resource missing 'Stage' tag.",
contacts: { aws_account: '123456789012' },
});
});

Expand All @@ -108,11 +103,11 @@ describe('The tagging obligation', () => {

const results = await evaluateTaggingObligation(client);

expect(results.filter((r) => !r.result)).toHaveLength(1);
expect(results[0]).toMatchObject({
expect(results).toHaveLength(1);
expect(results[0]).toEqual({
resource: 'arn:aws:s3:::mybucket',
result: false,
reasons: ["Resource missing 'App' tag."],
reason: "Resource missing 'App' tag.",
contacts: { aws_account: '123456789012' },
});
});

Expand All @@ -134,11 +129,11 @@ describe('The tagging obligation', () => {

const results = await evaluateTaggingObligation(client);

expect(results.filter((r) => !r.result)).toHaveLength(1);
expect(results[0]).toMatchObject({
expect(results).toHaveLength(1);
expect(results[0]).toEqual({
resource: 'arn:aws:s3:::mybucket',
result: false,
reasons: ["Resource missing 'gu:repo' tag."],
reason: "Resource missing 'gu:repo' tag.",
contacts: { aws_account: '123456789012' },
});
});

Expand All @@ -161,16 +156,11 @@ describe('The tagging obligation', () => {

const results = await evaluateTaggingObligation(client);

expect(results.filter((r) => !r.result)).toHaveLength(1);
expect(results[0]).toMatchObject({
expect(results).toHaveLength(4);
expect(results[0]).toEqual({
resource: 'arn:aws:s3:::mybucket',
result: false,
reasons: [
"Resource missing 'Stack' tag.",
"Resource missing 'Stage' tag.",
"Resource missing 'App' tag.",
"Resource missing 'gu:repo' tag.",
],
reason: "Resource missing 'Stack' tag.",
contacts: { aws_account: '123456789012' },
});
});
});
49 changes: 31 additions & 18 deletions packages/obligatron/src/obligations/tagging.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,14 @@ const isExemptResource = (resource: AwsResource): boolean => {
return false;
};

function resourceHasTag(resource: AwsResource, tag: string): boolean {
return (
typeof resource.tags === 'object' &&
resource.tags?.[tag] !== undefined &&
resource.tags[tag] !== ''
);
}

export async function evaluateTaggingObligation(
db: PrismaClient,
): Promise<ObligationResult[]> {
Expand All @@ -38,22 +46,27 @@ export async function evaluateTaggingObligation(
GROUP BY account_id, arn, service, resource_type;
`;

const result: ObligationResult[] = awsResources.map((resource) => {
const reasons = !isExemptResource(resource)
? REQUIRED_TAGS.filter(
(tag) =>
typeof resource.tags === 'object' &&
resource.tags !== null &&
(resource.tags[tag] === undefined || resource.tags[tag] === ''),
).map((tag) => `Resource missing '${tag}' tag.`)
: [];

return {
resource: resource.arn,
result: reasons.length === 0,
reasons,
};
});

return result;
const results: ObligationResult[] = [];

for (const resource of awsResources) {
if (isExemptResource(resource)) {
continue;
}

for (const requiredTag of REQUIRED_TAGS) {
if (resourceHasTag(resource, requiredTag)) {
continue;
}

results.push({
resource: resource.arn,
reason: `Resource missing '${requiredTag}' tag.`,
contacts: {
aws_account: resource.account_id,
},
});
}
}

return results;
}

0 comments on commit 1819a57

Please sign in to comment.