Skip to content

Commit

Permalink
feat: add support for in-memory client certificates (#952)
Browse files Browse the repository at this point in the history
* feat: add support for in-memory client certificates

* Apply suggestions from code review

Co-authored-by: Vignesh Shanmugam <vignesh.shanmugam22@gmail.com>

* Update snapshots

---------

Co-authored-by: Vignesh Shanmugam <vignesh.shanmugam22@gmail.com>
  • Loading branch information
emilioalvap and vigneshshanmugam authored Sep 13, 2024
1 parent 9600695 commit cce2844
Show file tree
Hide file tree
Showing 9 changed files with 169 additions and 40 deletions.
34 changes: 32 additions & 2 deletions __tests__/options.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
*/

import { CliArgs } from '../src/common_types';
import { normalizeOptions } from '../src/options';
import { normalizeOptions, parsePlaywrightOptions } from '../src/options';
import { join } from 'path';

describe('options', () => {
Expand Down Expand Up @@ -66,7 +66,7 @@ describe('options', () => {
ignoreHTTPSErrors: undefined,
isMobile: true,
userAgent:
'Mozilla/5.0 (Linux; Android 8.0.0; SM-G965U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.17 Mobile Safari/537.36',
'Mozilla/5.0 (Linux; Android 8.0.0; SM-G965U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.6668.29 Mobile Safari/537.36',
viewport: {
height: 658,
width: 320,
Expand Down Expand Up @@ -148,4 +148,34 @@ describe('options', () => {
},
});
});

it('parses cli playwrightOptions.clientCertificates', async () => {
const test = {
clientCertificates: [
{
key: Buffer.from('This should be revived'),
cert: Buffer.from('This should be revived'),
pfx: Buffer.from('This should be revived'),
origin: Buffer.from('This should not be revived'),
passphrase: Buffer.from('This should not be revived'),
},
{
key: 'This should be revived',
cert: 'This should be revived',
pfx: 'This should be revived',
origin: 'This should not be revived',
passphrase: 'This should not be revived',
},
],
};
const result = parsePlaywrightOptions(JSON.stringify(test));

result.clientCertificates.forEach(t => {
expect(Buffer.isBuffer(t.cert)).toBeTruthy();
expect(Buffer.isBuffer(t.key)).toBeTruthy();
expect(Buffer.isBuffer(t.pfx)).toBeTruthy();
expect(Buffer.isBuffer(t.origin)).toBeFalsy();
expect(Buffer.isBuffer(t.passphrase)).toBeFalsy();
});
});
});
27 changes: 27 additions & 0 deletions __tests__/push/__snapshots__/index.test.ts.snap
Original file line number Diff line number Diff line change
@@ -1,5 +1,32 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`Push abort on push with clientCertificate.certPath used in cloud 1`] = `
"Aborted. Invalid synthetics project settings.
Certificate path options (certPath, keyPath, pfxPath) are not supported on globally managed locations, use in-memory alternatives (cert, key, pfx) when running on cloud.
Run 'npx @elastic/synthetics init' to create project with default settings.
"
`;

exports[`Push abort on push with clientCertificate.keyPath used in cloud 1`] = `
"Aborted. Invalid synthetics project settings.
Certificate path options (certPath, keyPath, pfxPath) are not supported on globally managed locations, use in-memory alternatives (cert, key, pfx) when running on cloud.
Run 'npx @elastic/synthetics init' to create project with default settings.
"
`;

exports[`Push abort on push with clientCertificate.pfxPath used in cloud 1`] = `
"Aborted. Invalid synthetics project settings.
Certificate path options (certPath, keyPath, pfxPath) are not supported on globally managed locations, use in-memory alternatives (cert, key, pfx) when running on cloud.
Run 'npx @elastic/synthetics init' to create project with default settings.
"
`;

exports[`Push error on empty project id 1`] = `
"Aborted. Invalid synthetics project settings.
Expand Down
53 changes: 38 additions & 15 deletions __tests__/push/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,7 @@ describe('Push', () => {
) {
await writeFile(
join(PROJECT_DIR, filename),
`export default { monitor: ${JSON.stringify(
monitor
)}, project: ${JSON.stringify(settings)} }`
`export default ${JSON.stringify({ ...settings, monitor })}`
);
}

Expand Down Expand Up @@ -89,20 +87,23 @@ describe('Push', () => {
});

it('error on invalid location', async () => {
await fakeProjectSetup({ id: 'test-project' }, {});
await fakeProjectSetup({ project: { id: 'test-project' } }, {});
const output = await runPush();
expect(output).toMatchSnapshot();
});

it('error when schedule is not present', async () => {
await fakeProjectSetup({ id: 'test-project' }, { locations: ['test-loc'] });
await fakeProjectSetup(
{ project: { id: 'test-project' } },
{ locations: ['test-loc'] }
);
const output = await runPush();
expect(output).toMatchSnapshot();
});

it('error on invalid schedule', async () => {
await fakeProjectSetup(
{ id: 'test-project' },
{ project: { id: 'test-project' } },
{ locations: ['test-loc'], schedule: 12 }
);
const output = await runPush();
Expand All @@ -111,7 +112,7 @@ describe('Push', () => {

it('abort on push with different project id', async () => {
await fakeProjectSetup(
{ id: 'test-project' },
{ project: { id: 'test-project' } },
{ locations: ['test-loc'], schedule: 3 }
);
const output = await runPush(
Expand All @@ -125,7 +126,13 @@ describe('Push', () => {

it('error on invalid schedule in monitor DSL', async () => {
await fakeProjectSetup(
{ id: 'test-project', space: 'dummy', url: 'http://localhost:8080' },
{
project: {
id: 'test-project',
space: 'dummy',
url: 'http://localhost:8080',
},
},
{ locations: ['test-loc'], schedule: 3 }
);
const testJourney = join(PROJECT_DIR, 'test.journey.ts');
Expand All @@ -141,7 +148,7 @@ journey('journey 1', () => monitor.use({ id: 'j1', schedule: 8 }));`

it('errors on duplicate browser monitors', async () => {
await fakeProjectSetup(
{ id: 'test-project', space: 'dummy', url: server.PREFIX },
{ project: { id: 'test-project', space: 'dummy', url: server.PREFIX } },
{ locations: ['test-loc'], schedule: 3 }
);

Expand All @@ -164,7 +171,7 @@ journey('duplicate name', () => monitor.use({ schedule: 15 }));`

it('warn if throttling config is set', async () => {
await fakeProjectSetup(
{ id: 'test-project' },
{ project: { id: 'test-project' } },
{ locations: ['test-loc'], schedule: 3 }
);
const testJourney = join(PROJECT_DIR, 'test.journey.ts');
Expand All @@ -180,7 +187,7 @@ journey('duplicate name', () => monitor.use({ schedule: 15 }));`

it('errors on duplicate lightweight monitors', async () => {
await fakeProjectSetup(
{ id: 'test-project', space: 'dummy', url: server.PREFIX },
{ project: { id: 'test-project', space: 'dummy', url: server.PREFIX } },
{ locations: ['test-loc'], schedule: 3 }
);

Expand Down Expand Up @@ -220,7 +227,7 @@ heartbeat.monitors:

it('error on invalid CHUNK SIZE', async () => {
await fakeProjectSetup(
{ id: 'test-project', space: 'dummy', url: server.PREFIX },
{ project: { id: 'test-project', space: 'dummy', url: server.PREFIX } },
{ locations: ['test-loc'], schedule: 3 }
);
const output = await runPush(undefined, { CHUNK_SIZE: '251' });
Expand All @@ -231,7 +238,7 @@ heartbeat.monitors:

it('respects valid CHUNK SIZE', async () => {
await fakeProjectSetup(
{ id: 'test-project', space: 'dummy', url: server.PREFIX },
{ project: { id: 'test-project', space: 'dummy', url: server.PREFIX } },
{ locations: ['test-loc'], schedule: 3 }
);
const testJourney = join(PROJECT_DIR, 'chunk.journey.ts');
Expand Down Expand Up @@ -260,7 +267,9 @@ heartbeat.monitors:
beforeAll(async () => {
server = await createKibanaTestServer(version);
await fakeProjectSetup(
{ id: 'test-project', space: 'dummy', url: server.PREFIX },
{
project: { id: 'test-project', space: 'dummy', url: server.PREFIX },
},
{ locations: ['test-loc'], schedule: 3 }
);
});
Expand Down Expand Up @@ -314,7 +323,7 @@ heartbeat.monitors:
journey('journey 1', () => monitor.use({ id: 'j1' }));`
);
await fakeProjectSetup(
{ id: 'bar', space: 'dummy', url: server.PREFIX },
{ project: { id: 'bar', space: 'dummy', url: server.PREFIX } },
{ locations: ['test-loc'], schedule: 3 },
'synthetics.config.test.ts'
);
Expand All @@ -330,4 +339,18 @@ heartbeat.monitors:
});
});
});

['certPath', 'keyPath', 'pfxPath'].forEach(key => {
it(`abort on push with clientCertificate.${key} used in cloud`, async () => {
await fakeProjectSetup(
{
project: { id: 'test-project', space: 'dummy', url: server.PREFIX },
playwrightOptions: { clientCertificates: [{ [key]: 'test.file' }] },
},
{ locations: ['test-loc'], schedule: 3 }
);
const output = await runPush();
expect(output).toMatchSnapshot();
});
});
});
28 changes: 14 additions & 14 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,9 @@
"kleur": "^4.1.5",
"micromatch": "^4.0.7",
"pirates": "^4.0.5",
"playwright": "=1.45.1",
"playwright-chromium": "=1.45.1",
"playwright-core": "=1.45.1",
"playwright": "=1.47.0",
"playwright-chromium": "=1.47.0",
"playwright-core": "=1.47.0",
"semver": "^7.5.4",
"sharp": "^0.33.5",
"snakecase-keys": "^4.0.1",
Expand Down
1 change: 1 addition & 0 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ export async function readConfig(
options = await optionsPromise;
}
}

return options;
}

Expand Down
4 changes: 2 additions & 2 deletions src/formatter/javascript.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
import {
JavaScriptLanguageGenerator,
JavaScriptFormatter,
} from 'playwright-core/lib/server/recorder/javascript';
} from 'playwright-core/lib/server/codegen/javascript';

export type Step = {
actions: ActionInContext[];
Expand Down Expand Up @@ -272,7 +272,7 @@ export class SyntheticsGenerator extends JavaScriptLanguageGenerator {
if (isAssert && action.command) {
formatter.add(toAssertCall(pageAlias, action));
} else {
formatter.add(super._generateActionCall(subject, action));
formatter.add(super._generateActionCall(subject, actionInContext));
}

if (signals.popup)
Expand Down
44 changes: 42 additions & 2 deletions src/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,15 @@ export async function normalizeOptions(
*/
const playwrightOpts = merge(
config.playwrightOptions,
cliArgs.playwrightOptions || {}
cliArgs.playwrightOptions || {},
{
arrayMerge(target, source) {
if (source && source.length > 0) {
return [...new Set(source)];
}
return target;
},
}
);
options.playwrightOptions = {
...playwrightOpts,
Expand Down Expand Up @@ -195,7 +203,7 @@ export function getCommonCommandOpts() {
const playwrightOpts = createOption(
'--playwright-options <jsonstring>',
'JSON object to pass in custom Playwright options for the agent. Options passed will be merged with Playwright options defined in your synthetics.config.js file.'
).argParser(JSON.parse);
).argParser(parsePlaywrightOptions);

const pattern = createOption(
'--pattern <pattern>',
Expand Down Expand Up @@ -237,3 +245,35 @@ export function getCommonCommandOpts() {
match,
};
}

export function parsePlaywrightOptions(playwrightOpts: string) {
return JSON.parse(playwrightOpts, (key, value) => {
if (key !== 'clientCertificates') {
return value;
}

// Revive serialized clientCertificates buffer objects
return (value ?? []).map(item => {
const revived = { ...item };
if (item.cert && !Buffer.isBuffer(item.cert)) {
revived.cert = parseAsBuffer(item.cert);
}
if (item.key && !Buffer.isBuffer(item.key)) {
revived.key = parseAsBuffer(item.key);
}
if (item.pfx && !Buffer.isBuffer(item.pfx)) {
revived.pfx = parseAsBuffer(item.pfx);
}

return revived;
});
});
}

function parseAsBuffer(value: any): Buffer {
try {
return Buffer.from(value);
} catch (e) {
return value;
}
}
Loading

0 comments on commit cce2844

Please sign in to comment.