Require signed agent releases (Refs #64)
This commit is contained in:
@@ -499,7 +499,8 @@ describe('machine and route identity boundaries', () => {
|
||||
'/agent/ec-agent.py',
|
||||
'/agent/ec-agent-update.py',
|
||||
'/agent/install.sh',
|
||||
'/agent/manifest.json'
|
||||
'/agent/manifest.json',
|
||||
'/agent/manifest.json.sig'
|
||||
]) {
|
||||
const call = requestFor(null, { path });
|
||||
expect((await call.invoke()).status).toBe(200);
|
||||
|
||||
@@ -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 });
|
||||
|
||||
@@ -38,6 +38,13 @@ async function enrolled() {
|
||||
}
|
||||
|
||||
describe('native host enrollment and scoped authentication', () => {
|
||||
it('enrolls with an independently obtained local installer and quotes the token', async () => {
|
||||
const { installerCommand } = await import('../../src/lib/server/hosts/http.js');
|
||||
expect(installerCommand('https://controller.example/hosts/new', "token'quote")).toBe(
|
||||
"sudo bash ./electric-cherry-install.sh --controller 'https://controller.example' --token 'token'\\''quote'"
|
||||
);
|
||||
});
|
||||
|
||||
it.each(['instance.inspect', 'rcon.status-batch'])(
|
||||
'expires undelivered %s on timeout and delivers the next fresh check',
|
||||
async (kind) => {
|
||||
|
||||
Reference in New Issue
Block a user