Skip to content
This repository was archived by the owner on Aug 6, 2025. It is now read-only.

Commit 6800a4f

Browse files
caesarbellCaesar Bell
andauthored
DOP-4240 implements filtering sensitive values method to prevent sensitive values being logged (#1012)
* ✨ DOP-4240 implements filtering sensitive values method to prevent sensitive values being logged * ✅ DOP-4240 adds test for the redacted logic from the logger * ✏️ DOP-4240 fixed type "having" to "have" --------- Co-authored-by: Caesar Bell <[email protected]>
1 parent 84ea9ca commit 6800a4f

File tree

5 files changed

+136
-92
lines changed

5 files changed

+136
-92
lines changed

cdk-infra/utils/ssm.ts

Lines changed: 5 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -1,82 +1,16 @@
1-
import { GetParameterCommand, SSMClient } from '@aws-sdk/client-ssm';
21
import { getEnv } from './env';
2+
import {
3+
workerSecureStrings,
4+
webhookSecureStrings,
5+
getSecureStrings,
6+
} from '../../src/enhanced/utils/get-sensitive-values';
37

48
export function getSsmPathPrefix(): string {
59
const env = getEnv();
610

711
return `/env/${env}/docs/worker_pool`;
812
}
913

10-
/**
11-
* Returns the secure strings from SSM using the SSM client.
12-
* @param ssmPrefix the path prefix that should contain the environment for the SSM strings (e.g. /env/dotcomstg/worker_pool/)
13-
* @returns The map of environment variables.
14-
*/
15-
async function getSecureStrings(
16-
ssmPrefix: string,
17-
secureStrings: readonly string[],
18-
paramToEnvMap: Map<string, string>,
19-
resourceName: string
20-
) {
21-
const ssmClient = new SSMClient({ region: process.env.CDK_DEFAULT_REGION });
22-
23-
const secureStringsMap: Record<string, string> = {};
24-
25-
await Promise.all(
26-
secureStrings.map(async (paramName: string) => {
27-
const getParamCommand = new GetParameterCommand({
28-
Name: `${ssmPrefix}${paramName}`,
29-
WithDecryption: true,
30-
});
31-
32-
const ssmResponse = await ssmClient.send(getParamCommand);
33-
const secureString = ssmResponse.Parameter?.Value;
34-
35-
if (!secureString) {
36-
console.error(`ERROR! Could not retrieve string for the following param: ${paramName}`);
37-
return;
38-
}
39-
40-
const envName = paramToEnvMap.get(paramName);
41-
42-
if (!envName) {
43-
console.error(
44-
`ERROR! The param '${paramName}' does not having a mapping to an environment variable name. Please define this in the ${resourceName} map.`
45-
);
46-
return;
47-
}
48-
49-
secureStringsMap[envName] = secureString;
50-
})
51-
);
52-
53-
return secureStringsMap;
54-
}
55-
56-
// This array contains the Parameter Store paths of the secure strings
57-
// we want to add to the worker environment. These are mapped
58-
// to their environment variable name in the workerParamPathToEnvName map.
59-
const workerSecureStrings = [
60-
'/npm/auth',
61-
'/github/webhook/secret',
62-
'/github/bot/password',
63-
'/atlas/password',
64-
'/fastly/docs/dochub/token',
65-
'/npm/auth',
66-
'/fastly/docs/dochub/service_id',
67-
'/fastly/dochub_map',
68-
'/fastly/docs/main/token',
69-
'/fastly/docs/main/service_id',
70-
'/fastly/docs/cloudmanager/token',
71-
'/fastly/docs/cloudmanager/service_id',
72-
'/fastly/docs/atlas/token',
73-
'/fastly/docs/atlas/service_id',
74-
'/fastly/docs/opsmanager/token',
75-
'/fastly/docs/opsmanager/service_id',
76-
'/cdn/client/id',
77-
'/cdn/client/secret',
78-
] as const;
79-
8014
type WorkerSecureString = (typeof workerSecureStrings)[number];
8115

8216
const workerParamPathToEnvName = new Map<WorkerSecureString, string>();
@@ -103,24 +37,6 @@ export async function getWorkerSecureStrings(ssmPrefix: string): Promise<Record<
10337
return getSecureStrings(ssmPrefix, workerSecureStrings, workerParamPathToEnvName, 'workerParamPathToEnvName');
10438
}
10539

106-
// This array contains the Parameter Store paths of the secure strings
107-
// we want to add to the webhooks environment. These are mapped
108-
// to their environment variable name in the webhookParamPathToEnvName map.
109-
const webhookSecureStrings = [
110-
'/github/bot/password',
111-
'/github/webhook/secret',
112-
'/github/webhook/deletionSecret',
113-
'/atlas/password',
114-
'/fastly/docs/dochub/token',
115-
'/fastly/docs/dochub/service_id',
116-
'/fastly/dochub_map',
117-
'/cdn/client/id',
118-
'/cdn/client/secret',
119-
'/slack/webhook/secret',
120-
'/slack/auth/token',
121-
'/snooty/webhook/secret',
122-
] as const;
123-
12440
type WebhookSecureString = (typeof webhookSecureStrings)[number];
12541

12642
const webhookParamPathToEnvName = new Map<WebhookSecureString, string>();
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { sensitiveKeys } from './get-sensitive-values';
2+
3+
export const filterSensitiveValues = (message: string) => {
4+
for (const key of sensitiveKeys) {
5+
if (message.includes(key)) {
6+
message = message.replace(key, '*******');
7+
}
8+
}
9+
10+
return message;
11+
};
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import { GetParameterCommand, SSMClient } from '@aws-sdk/client-ssm';
2+
3+
// This array contains the Parameter Store paths of the secure strings
4+
// we want to add to the worker environment. These are mapped
5+
// to their environment variable name in the workerParamPathToEnvName map.
6+
export const workerSecureStrings = [
7+
'/npm/auth',
8+
'/github/webhook/secret',
9+
'/github/bot/password',
10+
'/atlas/password',
11+
'/fastly/docs/dochub/token',
12+
'/npm/auth',
13+
'/fastly/docs/dochub/service_id',
14+
'/fastly/dochub_map',
15+
'/fastly/docs/main/token',
16+
'/fastly/docs/main/service_id',
17+
'/fastly/docs/cloudmanager/token',
18+
'/fastly/docs/cloudmanager/service_id',
19+
'/fastly/docs/atlas/token',
20+
'/fastly/docs/atlas/service_id',
21+
'/fastly/docs/opsmanager/token',
22+
'/fastly/docs/opsmanager/service_id',
23+
'/cdn/client/id',
24+
'/cdn/client/secret',
25+
] as const;
26+
27+
// This array contains the Parameter Store paths of the secure strings
28+
// we want to add to the webhooks environment. These are mapped
29+
// to their environment variable name in the webhookParamPathToEnvName map.
30+
export const webhookSecureStrings = [
31+
'/github/bot/password',
32+
'/github/webhook/secret',
33+
'/github/webhook/deletionSecret',
34+
'/atlas/password',
35+
'/fastly/docs/dochub/token',
36+
'/fastly/docs/dochub/service_id',
37+
'/fastly/dochub_map',
38+
'/cdn/client/id',
39+
'/cdn/client/secret',
40+
'/slack/webhook/secret',
41+
'/slack/auth/token',
42+
'/snooty/webhook/secret',
43+
] as const;
44+
45+
/**
46+
* Used to capture the sensitive values and store them
47+
* as sensitive keys, to later be filtered in the Logger
48+
*/
49+
export const sensitiveKeys: string[] = [];
50+
51+
/**
52+
* Returns the secure strings from SSM using the SSM client.
53+
* @param ssmPrefix the path prefix that should contain the environment for the SSM strings (e.g. /env/dotcomstg/worker_pool/)
54+
* @returns The map of environment variables.
55+
*/
56+
export async function getSecureStrings(
57+
ssmPrefix: string,
58+
secureStrings: readonly string[],
59+
paramToEnvMap: Map<string, string>,
60+
resourceName: string
61+
) {
62+
const ssmClient = new SSMClient({ region: process.env.CDK_DEFAULT_REGION });
63+
64+
const secureStringsMap: Record<string, string> = {};
65+
66+
await Promise.all(
67+
secureStrings.map(async (paramName: string) => {
68+
const getParamCommand = new GetParameterCommand({
69+
Name: `${ssmPrefix}${paramName}`,
70+
WithDecryption: true,
71+
});
72+
73+
const ssmResponse = await ssmClient.send(getParamCommand);
74+
const secureString = ssmResponse.Parameter?.Value;
75+
76+
if (!secureString) {
77+
console.error(`ERROR! Could not retrieve string for the following param: ${paramName}`);
78+
return;
79+
}
80+
81+
/**
82+
* Hijacks the getSecureString to populate the
83+
* sensitive key array, used in the filterSensitiveValue
84+
*/
85+
sensitiveKeys.push(secureString);
86+
87+
const envName = paramToEnvMap.get(paramName);
88+
89+
if (!envName) {
90+
console.error(
91+
`ERROR! The param '${paramName}' does not have a mapping to an environment variable name. Please define this in the ${resourceName} map.`
92+
);
93+
return;
94+
}
95+
96+
secureStringsMap[envName] = secureString;
97+
})
98+
);
99+
100+
return secureStringsMap;
101+
}

src/services/logger.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { JobRepository } from '../repositories/jobRepository';
2+
import { filterSensitiveValues } from '../enhanced/utils/filter-sensitive-values';
23

34
export interface ILogger {
45
info(contextId: string, message: string): void;
@@ -11,14 +12,17 @@ export interface IJobRepoLogger extends ILogger {
1112
}
1213

1314
export class ConsoleLogger implements ILogger {
15+
private filterMessage(message: string) {
16+
return filterSensitiveValues(message);
17+
}
1418
info(contextId: string, message: string): void {
15-
console.info(`Context: ${contextId} message: ${message}`);
19+
console.info(`Context: ${contextId} message: ${this.filterMessage(message)}`);
1620
}
1721
warn(contextId: string, message: string): void {
18-
console.warn(`Context: ${contextId} message: ${message}`);
22+
console.warn(`Context: ${contextId} message: ${this.filterMessage(message)}`);
1923
}
2024
error(contextId: string, message: string): void {
21-
console.error(`Context: ${contextId} message: ${message}`);
25+
console.error(`Context: ${contextId} message: ${this.filterMessage(message)}`);
2226
}
2327
}
2428

@@ -29,6 +33,7 @@ export class HybridJobLogger extends ConsoleLogger implements IJobRepoLogger {
2933
this._jobRepo = jobRepo;
3034
}
3135
async save(contextId: string, message: string): Promise<void> {
36+
message = filterSensitiveValues(message);
3237
try {
3338
this.info(contextId, message);
3439
await this._jobRepo.insertLogStatement(contextId, [message]);

tests/unit/services/hybridJobLogger.test.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { HybridJobLogger } from '../../../src/services/logger';
22
import { JobRepository } from '../../../src/repositories/jobRepository';
3+
import { sensitiveKeys } from '../../../src/enhanced/utils/get-sensitive-values';
34
import { mockDeep } from 'jest-mock-extended';
45

56
describe('HybridJobLogger Tests', () => {
@@ -62,4 +63,14 @@ describe('HybridJobLogger Tests', () => {
6263
expect(jobRepo.insertLogStatement.mock.calls).toHaveLength(1);
6364
});
6465
});
66+
67+
describe('HybridJobLogger Redact Test', () => {
68+
test('HybridJobLogger redacts secrets from the message', async () => {
69+
sensitiveKeys.push('my_secret');
70+
await hybridJobLogger.save('SensitiveKeyContext', 'Oops I just logged my_secret');
71+
expect(console.info).toBeCalledWith('Context: SensitiveKeyContext message: Oops I just logged *******');
72+
expect(console.info.mock.calls).toHaveLength(1);
73+
expect(jobRepo.insertLogStatement.mock.calls).toHaveLength(1);
74+
});
75+
});
6576
});

0 commit comments

Comments
 (0)