[Fleet] Support agentless traffic filters (#222082)

Introduces new config key to allow passing app tokens for elasticsearch
and fleet server for agentless deployment:

```
xpack.fleet.agentless.deploymentSecrets:
  fleetAppToken: TOKEN1
  elasticsearchAppToken: TOKEN2
```

This new config will be passed to the agentless-api to be used by
agentless agents to support traffic filtering.

---------

Co-authored-by: Michel Losier <michel.losier@elastic.co>
This commit is contained in:
Nicolas Chaulet 2025-06-18 15:07:41 -04:00 committed by GitHub
parent ee41136441
commit fa214dcf1c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 176 additions and 7 deletions

View file

@ -41,6 +41,10 @@ export interface FleetConfigType {
ca?: string;
};
};
deploymentSecrets?: {
fleetAppToken?: string;
elasticsearchAppToken?: string;
};
customIntegrations?: {
enabled?: boolean;
};

View file

@ -171,6 +171,12 @@ export const config: PluginConfigDescriptor = {
),
})
),
deploymentSecrets: schema.maybe(
schema.object({
fleetAppToken: schema.maybe(schema.string()),
elasticsearchAppToken: schema.maybe(schema.string()),
})
),
customIntegrations: schema.maybe(
schema.object({
enabled: schema.maybe(schema.boolean({ defaultValue: false })),

View file

@ -20,6 +20,7 @@ import {
} from '../../../common/types';
import { appContextService } from '../app_context';
import { agentPolicyService } from '../agent_policy';
import { listEnrollmentApiKeys } from '../api_keys';
import { fleetServerHostService } from '../fleet_server_host';
@ -92,6 +93,9 @@ describe('Agentless Agent service', () => {
mockedAppContextService.getLogger.mockReturnValue(mockedLogger);
mockedAppContextService.getExperimentalFeatures.mockReturnValue({ agentless: false } as any);
(axios as jest.MockedFunction<typeof axios>).mockReset();
jest.spyOn(agentPolicyService, 'getFullAgentPolicy').mockResolvedValue({
outputs: { agentless: {} as any },
} as any);
jest.clearAllMocks();
});
@ -117,6 +121,10 @@ describe('Agentless Agent service', () => {
ca: '/path/to/ca',
},
},
deploymentSecrets: {
fleetAppToken: 'fleet-app-token',
elasticsearchAppToken: 'es-app-token',
},
},
} as any);
jest.spyOn(appContextService, 'getCloud').mockReturnValue({ isCloudEnabled: true } as any);
@ -185,6 +193,13 @@ describe('Agentless Agent service', () => {
team: 'fleet',
},
},
secrets: {
fleet_app_token: 'fleet-app-token',
elasticsearch_app_token: 'es-app-token',
},
policy_details: {
output_name: 'agentless',
},
}),
headers: expect.anything(),
httpsAgent: expect.anything(),
@ -212,6 +227,10 @@ describe('Agentless Agent service', () => {
ca: '/path/to/ca',
},
},
deploymentSecrets: {
fleetAppToken: 'fleet-app-token',
elasticsearchAppToken: 'es-app-token',
},
},
} as any);
jest
@ -270,7 +289,7 @@ describe('Agentless Agent service', () => {
expect(createAgentlessAgentReturnValue).toEqual(mockAgentlessDeploymentResponse);
expect(axios).toHaveBeenCalledWith(
expect.objectContaining({
data: {
data: expect.objectContaining({
fleet_token: 'mocked-fleet-enrollment-api-key',
fleet_url: 'http://fleetserver:8220',
policy_id: 'mocked-agentless-agent-policy-id',
@ -281,7 +300,14 @@ describe('Agentless Agent service', () => {
team: 'fleet',
},
},
},
secrets: {
fleet_app_token: 'fleet-app-token',
elasticsearch_app_token: 'es-app-token',
},
policy_details: {
output_name: 'agentless',
},
}),
headers: expect.anything(),
httpsAgent: expect.anything(),
method: 'POST',
@ -308,6 +334,10 @@ describe('Agentless Agent service', () => {
ca: '/path/to/ca',
},
},
deploymentSecrets: {
fleetAppToken: 'fleet-app-token',
elasticsearchAppToken: 'es-app-token',
},
},
} as any);
jest
@ -374,7 +404,7 @@ describe('Agentless Agent service', () => {
expect(createAgentlessAgentReturnValue).toEqual(mockAgentlessDeploymentResponse);
expect(axios).toHaveBeenCalledWith(
expect.objectContaining({
data: {
data: expect.objectContaining({
fleet_token: 'mocked-fleet-enrollment-api-key',
fleet_url: 'http://fleetserver:8220',
policy_id: 'mocked-agentless-agent-policy-id',
@ -391,7 +421,14 @@ describe('Agentless Agent service', () => {
team: 'fleet',
},
},
},
secrets: {
fleet_app_token: 'fleet-app-token',
elasticsearch_app_token: 'es-app-token',
},
policy_details: {
output_name: 'agentless',
},
}),
headers: expect.anything(),
httpsAgent: expect.anything(),
method: 'POST',
@ -418,6 +455,10 @@ describe('Agentless Agent service', () => {
ca: '/path/to/ca',
},
},
deploymentSecrets: {
fleetAppToken: 'fleet-app-token',
elasticsearchAppToken: 'es-app-token',
},
},
} as any);
jest
@ -488,7 +529,7 @@ describe('Agentless Agent service', () => {
expect(createAgentlessAgentReturnValue).toEqual(mockAgentlessDeploymentResponse);
expect(axios).toHaveBeenCalledWith(
expect.objectContaining({
data: {
data: expect.objectContaining({
fleet_token: 'mocked-fleet-enrollment-api-key',
fleet_url: 'http://fleetserver:8220',
policy_id: 'mocked-agentless-agent-policy-id',
@ -509,7 +550,14 @@ describe('Agentless Agent service', () => {
team: 'fleet',
},
},
},
secrets: {
fleet_app_token: 'fleet-app-token',
elasticsearch_app_token: 'es-app-token',
},
policy_details: {
output_name: 'agentless',
},
}),
headers: expect.anything(),
httpsAgent: expect.anything(),
method: 'POST',
@ -536,6 +584,10 @@ describe('Agentless Agent service', () => {
ca: '/path/to/ca',
},
},
deploymentSecrets: {
fleetAppToken: 'fleet-app-token',
elasticsearchAppToken: 'es-app-token',
},
},
} as any);
jest.spyOn(appContextService, 'getCloud').mockReturnValue({ isCloudEnabled: true } as any);
@ -609,6 +661,10 @@ describe('Agentless Agent service', () => {
ca: '/path/to/ca',
},
},
deploymentSecrets: {
fleetAppToken: 'fleet-app-token',
elasticsearchAppToken: 'es-app-token',
},
},
} as any);
jest.spyOn(appContextService, 'getCloud').mockReturnValue({ isCloudEnabled: true } as any);
@ -644,6 +700,10 @@ describe('Agentless Agent service', () => {
ca: '/path/to/ca',
},
},
deploymentSecrets: {
fleetAppToken: 'fleet-app-token',
elasticsearchAppToken: 'es-app-token',
},
},
} as any);
jest.spyOn(appContextService, 'getKibanaVersion').mockReturnValue('8.18.0');
@ -683,6 +743,10 @@ describe('Agentless Agent service', () => {
ca: '/path/to/ca',
},
},
deploymentSecrets: {
fleetAppToken: 'fleet-app-token',
elasticsearchAppToken: 'es-app-token',
},
},
} as any);
jest
@ -723,6 +787,10 @@ describe('Agentless Agent service', () => {
ca: '/path/to/ca',
},
},
deploymentSecrets: {
fleetAppToken: 'fleet-app-token',
elasticsearchAppToken: 'es-app-token',
},
},
} as any);
jest.spyOn(appContextService, 'getCloud').mockReturnValue({ isCloudEnabled: true } as any);
@ -840,6 +908,10 @@ describe('Agentless Agent service', () => {
ca: '/path/to/ca',
},
},
deploymentSecrets: {
fleetAppToken: 'fleet-app-token',
elasticsearchAppToken: 'es-app-token',
},
},
} as any);
jest.spyOn(appContextService, 'getCloud').mockReturnValue({ isCloudEnabled: true } as any);
@ -904,6 +976,10 @@ describe('Agentless Agent service', () => {
ca: '/path/to/ca',
},
},
deploymentSecrets: {
fleetAppToken: 'fleet-app-token',
elasticsearchAppToken: 'es-app-token',
},
},
} as any);
jest.spyOn(appContextService, 'getCloud').mockReturnValue({ isCloudEnabled: true } as any);
@ -964,6 +1040,10 @@ describe('Agentless Agent service', () => {
key: '/path/to/key',
},
},
deploymentSecrets: {
fleetAppToken: 'fleet-app-token',
elasticsearchAppToken: 'es-app-token',
},
},
} as any);
@ -998,6 +1078,10 @@ describe('Agentless Agent service', () => {
key: '/path/to/key',
},
},
deploymentSecrets: {
fleetAppToken: 'fleet-app-token',
elasticsearchAppToken: 'es-app-token',
},
},
} as any);
await expect(
@ -1047,6 +1131,10 @@ describe('Agentless Agent service', () => {
key: '/path/to/key',
},
},
deploymentSecrets: {
fleetAppToken: 'fleet-app-token',
elasticsearchAppToken: 'es-app-token',
},
},
} as any);
jest.spyOn(appContextService, 'getCloud').mockReturnValue({ isCloudEnabled: true } as any);
@ -1085,6 +1173,10 @@ describe('Agentless Agent service', () => {
key: '/path/to/key',
},
},
deploymentSecrets: {
fleetAppToken: 'fleet-app-token',
elasticsearchAppToken: 'es-app-token',
},
},
} as any);
jest.spyOn(appContextService, 'getCloud').mockReturnValue({ isCloudEnabled: true } as any);
@ -1126,6 +1218,10 @@ describe('Agentless Agent service', () => {
ca: '/path/to/ca',
},
},
deploymentSecrets: {
fleetAppToken: 'fleet-app-token',
elasticsearchAppToken: 'es-app-token',
},
},
} as any);
jest.spyOn(appContextService, 'getCloud').mockReturnValue({ isCloudEnabled: true } as any);
@ -1186,6 +1282,10 @@ describe('Agentless Agent service', () => {
ca: '/path/to/ca',
},
},
deploymentSecrets: {
fleetAppToken: 'fleet-app-token',
elasticsearchAppToken: 'es-app-token',
},
},
} as any);
jest.spyOn(appContextService, 'getCloud').mockReturnValue({ isCloudEnabled: true } as any);
@ -1246,6 +1346,10 @@ describe('Agentless Agent service', () => {
ca: '/path/to/ca',
},
},
deploymentSecrets: {
fleetAppToken: 'fleet-app-token',
elasticsearchAppToken: 'es-app-token',
},
},
} as any);
jest.spyOn(appContextService, 'getCloud').mockReturnValue({ isCloudEnabled: true } as any);
@ -1306,6 +1410,10 @@ describe('Agentless Agent service', () => {
ca: '/path/to/ca',
},
},
deploymentSecrets: {
fleetAppToken: 'fleet-app-token',
elasticsearchAppToken: 'es-app-token',
},
},
} as any);
jest.spyOn(appContextService, 'getCloud').mockReturnValue({ isCloudEnabled: true } as any);
@ -1366,6 +1474,10 @@ describe('Agentless Agent service', () => {
ca: '/path/to/ca',
},
},
deploymentSecrets: {
fleetAppToken: 'fleet-app-token',
elasticsearchAppToken: 'es-app-token',
},
},
} as any);
jest.spyOn(appContextService, 'getCloud').mockReturnValue({ isCloudEnabled: true } as any);
@ -1426,6 +1538,10 @@ describe('Agentless Agent service', () => {
ca: '/path/to/ca',
},
},
deploymentSecrets: {
fleetAppToken: 'fleet-app-token',
elasticsearchAppToken: 'es-app-token',
},
},
} as any);
jest.spyOn(appContextService, 'getCloud').mockReturnValue({ isCloudEnabled: true } as any);
@ -1486,6 +1602,10 @@ describe('Agentless Agent service', () => {
ca: '/path/to/ca',
},
},
deploymentSecrets: {
fleetAppToken: 'fleet-app-token',
elasticsearchAppToken: 'es-app-token',
},
},
} as any);
jest.spyOn(appContextService, 'getCloud').mockReturnValue({ isCloudEnabled: true } as any);
@ -1546,6 +1666,10 @@ describe('Agentless Agent service', () => {
ca: '/path/to/ca',
},
},
deploymentSecrets: {
fleetAppToken: 'fleet-app-token',
elasticsearchAppToken: 'es-app-token',
},
},
} as any);
jest.spyOn(appContextService, 'getCloud').mockReturnValue({ isCloudEnabled: true } as any);

View file

@ -44,6 +44,7 @@ import {
RETRYABLE_HTTP_STATUSES,
RETRYABLE_SERVER_CODES,
} from '../../../common/constants/agentless';
import { agentPolicyService } from '../agent_policy';
interface AgentlessAgentErrorHandlingMessages {
[key: string]: {
@ -115,8 +116,9 @@ class AgentlessAgentService {
and TLS ca: ${agentlessConfig?.api?.tls?.ca ? '[REDACTED]' : 'undefined'}`
);
const tlsConfig = this.createTlsConfig(agentlessConfig);
const labels = this.getAgentlessTags(agentlessAgentPolicy);
const secrets = this.getAgentlessSecrets();
const policyDetails = await this.getPolicyDetails(soClient, agentlessAgentPolicy);
const requestConfig: AxiosRequestConfig = {
url: prependAgentlessApiBasePathToEndpoint(agentlessConfig, '/deployments'),
@ -127,6 +129,8 @@ class AgentlessAgentService {
resources: agentlessAgentPolicy.agentless?.resources,
cloud_connectors: agentlessAgentPolicy.agentless?.cloud_connectors,
labels,
secrets,
policy_details: policyDetails,
},
method: 'POST',
...this.getHeaders(tlsConfig, traceId),
@ -287,6 +291,21 @@ class AgentlessAgentService {
return response;
}
private getAgentlessSecrets() {
const deploymentSecrets = appContextService.getConfig()?.agentless?.deploymentSecrets;
if (!deploymentSecrets) return {};
return {
...(deploymentSecrets?.fleetAppToken
? { fleet_app_token: deploymentSecrets?.fleetAppToken }
: {}),
...(deploymentSecrets?.elasticsearchAppToken
? { elasticsearch_app_token: deploymentSecrets?.elasticsearchAppToken }
: {}),
};
}
private getHeaders(tlsConfig: SslConfig, traceId: string | undefined) {
return {
headers: {
@ -303,6 +322,20 @@ class AgentlessAgentService {
};
}
private async getPolicyDetails(
soClient: SavedObjectsClientContract,
agentlessAgentPolicy: AgentPolicy
) {
const fullPolicy = await agentPolicyService.getFullAgentPolicy(
soClient,
agentlessAgentPolicy.id
);
return {
output_name: Object.keys(fullPolicy?.outputs || {})?.[0], // Agentless policies only have one output
};
}
private getAgentlessTags(agentlessAgentPolicy: AgentPolicy) {
if (!agentlessAgentPolicy.global_data_tags) {
return undefined;

View file

@ -25,6 +25,8 @@ export default function ({ getService }: FtrProviderContext) {
before(async () => {
mockApiServer = await mockAgentlessApiService.listen(8089); // Start the agentless api mock server on port 8089
// Ensure fleet default outputs are setup
await supertest.post(`/api/fleet/setup`).set('kbn-xsrf', 'xxxx').expect(200);
await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/fleet/agents');
const getPkRes = await supertest
.get(`/api/fleet/epm/packages/${FLEET_ELASTIC_AGENT_PACKAGE}`)