feat(api, web): enhance agent installation process with invited status and policy support
- Updated the agent enrollment process to include an 'invited' status, allowing for better tracking of agent states. - Implemented support for install links that can now include an `install_link_id`, facilitating the transition from invited to pending status upon enrollment. - Enhanced the MikroTik installation script to include the `EvofwInstallLinkId` for better tracking and management. - Added new API endpoints for fetching agent policies and serving MikroTik-specific installation scripts. - Improved the web UI to reflect the new agent statuses and provide copyable installation commands for agents. Co-authored-by: Cursor <[email protected]>
This commit is contained in:
@@ -24,7 +24,7 @@ describe('install-links', () => {
|
||||
await app.close()
|
||||
})
|
||||
|
||||
it('creates link and serves scripts by id and slug', async () => {
|
||||
it('creates invited agent and serves scripts by id and slug', async () => {
|
||||
const app = await appPromise
|
||||
await app.ready()
|
||||
|
||||
@@ -37,12 +37,27 @@ describe('install-links', () => {
|
||||
const body = created.json() as {
|
||||
id: string
|
||||
slug: string
|
||||
agent_id: string
|
||||
curl: { by_id: string; by_slug: string }
|
||||
}
|
||||
expect(body.id).toBeTruthy()
|
||||
expect(body.slug).toBeTruthy()
|
||||
expect(body.agent_id).toBeTruthy()
|
||||
expect(body.curl.by_id).toContain(`/agent-install/${body.id}`)
|
||||
|
||||
const agents = await app.inject({ method: 'GET', url: '/api/v1/agents' })
|
||||
expect(agents.statusCode).toBe(200)
|
||||
const list = agents.json() as {
|
||||
items: {
|
||||
id: string
|
||||
status: string
|
||||
install_curl?: string | null
|
||||
}[]
|
||||
}
|
||||
const invited = list.items.find((a) => a.id === body.agent_id)
|
||||
expect(invited?.status).toBe('invited')
|
||||
expect(invited?.install_curl).toContain(body.slug)
|
||||
|
||||
const byId = await app.inject({
|
||||
method: 'GET',
|
||||
url: `/agent-install/${body.id}`,
|
||||
@@ -50,7 +65,7 @@ describe('install-links', () => {
|
||||
expect(byId.statusCode).toBe(200)
|
||||
expect(byId.headers['content-type']).toContain('text/x-shellscript')
|
||||
expect(byId.body).toContain("EVOFW_CLIENT_NAME='web-01'")
|
||||
expect(byId.body).toContain("EVOFW_SEED='test-seed'")
|
||||
expect(byId.body).toContain(`EVOFW_INSTALL_LINK_ID='${body.id}'`)
|
||||
|
||||
const bySlug = await app.inject({
|
||||
method: 'GET',
|
||||
@@ -59,4 +74,125 @@ describe('install-links', () => {
|
||||
expect(bySlug.statusCode).toBe(200)
|
||||
expect(bySlug.body).toContain("EVOFW_CP_URL='https://fw.example.com'")
|
||||
})
|
||||
|
||||
it('enroll with install_link_id updates invited agent to pending', async () => {
|
||||
const app = await appPromise
|
||||
await app.ready()
|
||||
|
||||
const created = await app.inject({
|
||||
method: 'POST',
|
||||
url: '/api/v1/install-links',
|
||||
payload: { name: 'web-02', platform: 'linux' },
|
||||
})
|
||||
const link = created.json() as { id: string; agent_id: string }
|
||||
|
||||
const enroll = await app.inject({
|
||||
method: 'POST',
|
||||
url: '/v1/agent/enroll',
|
||||
headers: {
|
||||
'content-type': 'application/json',
|
||||
'x-evofw-seed': 'test-seed',
|
||||
},
|
||||
payload: {
|
||||
name: 'web-02',
|
||||
hostname: 'host-02',
|
||||
platform: 'linux',
|
||||
token: 'evofw_test_token_1234567890abcd',
|
||||
install_link_id: link.id,
|
||||
},
|
||||
})
|
||||
expect(enroll.statusCode).toBe(201)
|
||||
const enrolled = enroll.json() as { id: string; status: string }
|
||||
expect(enrolled.id).toBe(link.agent_id)
|
||||
expect(enrolled.status).toBe('pending')
|
||||
|
||||
const agents = await app.inject({ method: 'GET', url: '/api/v1/agents' })
|
||||
const list = agents.json() as { items: { id: string; status: string }[] }
|
||||
const row = list.items.find((a) => a.id === link.agent_id)
|
||||
expect(row?.status).toBe('pending')
|
||||
})
|
||||
|
||||
it('mikrotik install link serves RSC and fetch/import one-liner', async () => {
|
||||
const app = await appPromise
|
||||
await app.ready()
|
||||
|
||||
const created = await app.inject({
|
||||
method: 'POST',
|
||||
url: '/api/v1/install-links',
|
||||
payload: { name: 'mt-01', platform: 'mikrotik' },
|
||||
})
|
||||
expect(created.statusCode).toBe(201)
|
||||
const body = created.json() as {
|
||||
id: string
|
||||
slug: string
|
||||
agent_id: string
|
||||
curl: { by_id: string; by_slug: string }
|
||||
}
|
||||
expect(body.curl.by_id).toContain('/tool fetch url=')
|
||||
expect(body.curl.by_id).toContain('/import file-name=evofw-install.rsc')
|
||||
expect(body.curl.by_id).not.toContain('| bash')
|
||||
|
||||
const byId = await app.inject({
|
||||
method: 'GET',
|
||||
url: `/agent-install/${body.id}`,
|
||||
})
|
||||
expect(byId.statusCode).toBe(200)
|
||||
expect(byId.headers['content-type']).toContain('text/plain')
|
||||
expect(byId.body).toContain(':global EvofwCpUrl "https://fw.example.com"')
|
||||
expect(byId.body).toContain(`:global EvofwInstallLinkId "${body.id}"`)
|
||||
expect(byId.body).toContain('evofw-bl-drop-input')
|
||||
expect(byId.body).toContain('/v1/agent/policy.rsc')
|
||||
})
|
||||
|
||||
it('approved agent can fetch policy.rsc with address-list commands', async () => {
|
||||
const app = await appPromise
|
||||
await app.ready()
|
||||
|
||||
const created = await app.inject({
|
||||
method: 'POST',
|
||||
url: '/api/v1/install-links',
|
||||
payload: { name: 'mt-policy', platform: 'mikrotik' },
|
||||
})
|
||||
const link = created.json() as { id: string; agent_id: string }
|
||||
const token = 'evofw_mt_policy_token_abcdefghij'
|
||||
|
||||
const enroll = await app.inject({
|
||||
method: 'POST',
|
||||
url: '/v1/agent/enroll',
|
||||
headers: {
|
||||
'content-type': 'application/json',
|
||||
'x-evofw-seed': 'test-seed',
|
||||
},
|
||||
payload: {
|
||||
name: 'mt-policy',
|
||||
platform: 'mikrotik',
|
||||
token,
|
||||
install_link_id: link.id,
|
||||
},
|
||||
})
|
||||
expect(enroll.statusCode).toBe(201)
|
||||
|
||||
await app.inject({
|
||||
method: 'POST',
|
||||
url: `/api/v1/agents/${link.agent_id}/approve`,
|
||||
})
|
||||
|
||||
// add a deny override so policy has a CIDR
|
||||
await app.inject({
|
||||
method: 'POST',
|
||||
url: `/api/v1/agents/${link.agent_id}/overrides`,
|
||||
payload: { action: 'deny', cidr: '203.0.113.0/24' },
|
||||
})
|
||||
|
||||
const rsc = await app.inject({
|
||||
method: 'GET',
|
||||
url: '/v1/agent/policy.rsc',
|
||||
headers: { authorization: `Bearer ${token}` },
|
||||
})
|
||||
expect(rsc.statusCode).toBe(200)
|
||||
expect(rsc.headers['content-type']).toContain('text/plain')
|
||||
expect(rsc.body).toContain('address-list')
|
||||
expect(rsc.body).toContain('EVOFW_DENY')
|
||||
expect(rsc.body).toContain('203.0.113.0/24')
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user