Require signed agent releases (Refs #64)

This commit is contained in:
2026-10-09 20:36:18 -04:00
parent df0d395138
commit 6f71822d41
24 changed files with 984 additions and 107 deletions
+27 -8
View File
@@ -24,7 +24,8 @@ describe('public agent release artifacts', () => {
'install.sh',
'ec-agent.py',
'ec-agent-update.py',
'manifest.json'
'manifest.json',
'manifest.json.sig'
] as const) {
expect(await agentReleaseAsset(file)).toBe('fresh-release');
}
@@ -32,7 +33,8 @@ describe('public agent release artifacts', () => {
resolve('static/agent/install.sh'),
resolve('static/agent/ec-agent.py'),
resolve('static/agent/ec-agent-update.py'),
resolve('static/agent/manifest.json')
resolve('static/agent/manifest.json'),
resolve('static/agent/manifest.json.sig')
]);
});
@@ -54,7 +56,8 @@ describe('public agent release artifacts', () => {
'install.sh',
'ec-agent.py',
'ec-agent-update.py',
'manifest.json'
'manifest.json',
'manifest.json.sig'
] as const) {
expect(await agentReleaseAsset(file)).toBe('packaged-release');
}
@@ -62,23 +65,30 @@ describe('public agent release artifacts', () => {
resolve('build/client/agent/install.sh'),
resolve('build/client/agent/ec-agent.py'),
resolve('build/client/agent/ec-agent-update.py'),
resolve('build/client/agent/manifest.json')
resolve('build/client/agent/manifest.json'),
resolve('build/client/agent/manifest.json.sig')
]);
});
it.each(['ec-agent.py', 'ec-agent-update.py', 'manifest.json'])(
it.each(['ec-agent.py', 'ec-agent-update.py', 'manifest.json', 'manifest.json.sig'])(
'serves the packaged %s without a browser session or host credential',
async (file) => {
const content = file.endsWith('.json')
? '{"version":"1.2.0","updaterProtocolVersion":1}'
: '# verified release source\n';
? '{"version":"1.2.0","updaterProtocolVersion":2}'
: file.endsWith('.sig')
? '-----BEGIN PGP SIGNATURE-----\nrelease-signature\n-----END PGP SIGNATURE-----\n'
: '# verified release source\n';
files.readFile.mockResolvedValue(content);
const response = await GET({ params: { file } });
expect(response.status).toBe(200);
expect(await response.text()).toBe(content);
expect(response.headers.get('cache-control')).toBe('no-cache');
expect(response.headers.get('content-type')).toContain(
file.endsWith('.json') ? 'application/json' : 'text/plain'
file.endsWith('.sig')
? 'application/pgp-signature'
: file.endsWith('.json')
? 'application/json'
: 'text/plain'
);
expect(files.readFile.mock.calls).toEqual([
[resolve('build/client/agent', file), 'utf8']
@@ -86,6 +96,13 @@ describe('public agent release artifacts', () => {
}
);
it('returns 404 for an unsigned release without generating a signature', async () => {
files.readFile.mockRejectedValue(Object.assign(new Error('missing'), { code: 'ENOENT' }));
await expect(GET({ params: { file: 'manifest.json.sig' } })).rejects.toMatchObject({
status: 404
});
});
it('serves the updater from development assets when no packaged asset exists', async () => {
files.readFile
.mockRejectedValueOnce(Object.assign(new Error('missing'), { code: 'ENOENT' }))
@@ -111,6 +128,8 @@ describe('public agent release artifacts', () => {
'update-status.json',
'config.json',
'install.sh',
'manifest.json.sig/anything',
'release-signing.key',
'ec-agent-update.py/anything'
])('rejects non-release file %s before reading disk', async (file) => {
await expect(GET({ params: { file } })).rejects.toMatchObject({ status: 404 });