Skip to content

Commit

Permalink
[8.x] [Cloud Security] [Agentless] Improving error log metadata and s…
Browse files Browse the repository at this point in the history
…ending APM trace id (#192235) (#193901)

# Backport

This will backport the following commits from `main` to `8.x`:
- [[Cloud Security] [Agentless] Improving error log metadata and sending
APM trace id (#192235)](#192235)

<!--- Backport version: 9.4.3 -->

### Questions ?
Please refer to the [Backport tool
documentation](https://github.com/sqren/backport)

<!--BACKPORT [{"author":{"name":"Paulo
Silva","email":"paulo.henrique@elastic.co"},"sourceCommit":{"committedDate":"2024-09-24T16:56:03Z","message":"[Cloud
Security] [Agentless] Improving error log metadata and sending APM trace
id (#192235)\n\n## Summary\r\n\r\nThis PR includes a few improvements in
the communication between Kibana\r\nand the Agentless API.\r\n\r\n-
Adding a `X-Request-ID` Header on all HTTP calls from the
Kibana\r\nserver to the Agentless API. X-Request-ID is
the\r\n[currentTraceparent](https://www.elastic.co/guide/en/apm/agent/nodejs/current/agent-api.html#apm-current-traceparent)\r\nstring
captured from Apm Service and is unique per request.\r\n- Also, this PR
enhances some error logs metadata with the relevant\r\nfields, and all
documents logged to ES also includes
the\r\n[trace.id](https://www.elastic.co/guide/en/ecs/8.11/ecs-tracing.html)\r\nfield\r\n-
Also this PR redacts the fleet token sent to the debug logs to
prevent\r\ncredential
leaking","sha":"10bcc626ecb1d8bf77f5a4d95d4d85f5e094d782","branchLabelMapping":{"^v9.0.0$":"main","^v8.16.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:skip","Team:Fleet","v9.0.0","Team:Cloud
Security","v8.16.0","backport:version"],"title":"[Cloud Security]
[Agentless] Improving error log metadata and sending APM trace
id","number":192235,"url":"https://github.com/elastic/kibana/pull/192235","mergeCommit":{"message":"[Cloud
Security] [Agentless] Improving error log metadata and sending APM trace
id (#192235)\n\n## Summary\r\n\r\nThis PR includes a few improvements in
the communication between Kibana\r\nand the Agentless API.\r\n\r\n-
Adding a `X-Request-ID` Header on all HTTP calls from the
Kibana\r\nserver to the Agentless API. X-Request-ID is
the\r\n[currentTraceparent](https://www.elastic.co/guide/en/apm/agent/nodejs/current/agent-api.html#apm-current-traceparent)\r\nstring
captured from Apm Service and is unique per request.\r\n- Also, this PR
enhances some error logs metadata with the relevant\r\nfields, and all
documents logged to ES also includes
the\r\n[trace.id](https://www.elastic.co/guide/en/ecs/8.11/ecs-tracing.html)\r\nfield\r\n-
Also this PR redacts the fleet token sent to the debug logs to
prevent\r\ncredential
leaking","sha":"10bcc626ecb1d8bf77f5a4d95d4d85f5e094d782"}},"sourceBranch":"main","suggestedTargetBranches":["8.x"],"targetPullRequestStates":[{"branch":"main","label":"v9.0.0","branchLabelMappingKey":"^v9.0.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/192235","number":192235,"mergeCommit":{"message":"[Cloud
Security] [Agentless] Improving error log metadata and sending APM trace
id (#192235)\n\n## Summary\r\n\r\nThis PR includes a few improvements in
the communication between Kibana\r\nand the Agentless API.\r\n\r\n-
Adding a `X-Request-ID` Header on all HTTP calls from the
Kibana\r\nserver to the Agentless API. X-Request-ID is
the\r\n[currentTraceparent](https://www.elastic.co/guide/en/apm/agent/nodejs/current/agent-api.html#apm-current-traceparent)\r\nstring
captured from Apm Service and is unique per request.\r\n- Also, this PR
enhances some error logs metadata with the relevant\r\nfields, and all
documents logged to ES also includes
the\r\n[trace.id](https://www.elastic.co/guide/en/ecs/8.11/ecs-tracing.html)\r\nfield\r\n-
Also this PR redacts the fleet token sent to the debug logs to
prevent\r\ncredential
leaking","sha":"10bcc626ecb1d8bf77f5a4d95d4d85f5e094d782"}},{"branch":"8.x","label":"v8.16.0","branchLabelMappingKey":"^v8.16.0$","isSourceBranch":false,"state":"NOT_CREATED"}]}]
BACKPORT-->

Co-authored-by: Paulo Silva <paulo.henrique@elastic.co>
  • Loading branch information
kibanamachine and opauloh authored Sep 24, 2024
1 parent 108c339 commit be2edae
Show file tree
Hide file tree
Showing 3 changed files with 275 additions and 36 deletions.
12 changes: 6 additions & 6 deletions x-pack/plugins/fleet/common/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,12 @@ export interface FleetConfigType {
};
agentless?: {
enabled: boolean;
api: {
url: string;
tls: {
certificate: string;
key: string;
ca: string;
api?: {
url?: string;
tls?: {
certificate?: string;
key?: string;
ca?: string;
};
};
};
Expand Down
197 changes: 196 additions & 1 deletion x-pack/plugins/fleet/server/services/agents/agentless_agent.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { securityMock } from '@kbn/security-plugin/server/mocks';
import { loggerMock } from '@kbn/logging-mocks';
import type { Logger } from '@kbn/core/server';

import type { AxiosError } from 'axios';
import axios from 'axios';

import { AgentlessAgentCreateError } from '../../errors';
Expand All @@ -20,7 +21,14 @@ import { listFleetServerHosts } from '../fleet_server_host';

import { agentlessAgentService } from './agentless_agent';

jest.mock('axios', () => jest.fn());
jest.mock('axios');
// Add a mock implementation for `isAxiosError` to simulate that the error is an Axios error
(axios.isAxiosError as unknown as jest.Mock).mockImplementation(
(error: any): error is AxiosError => {
return error.isAxiosError === true; // Simulate that the error is an Axios error if it has `isAxiosError` property
}
);

jest.mock('../fleet_server_host');
jest.mock('../api_keys');
jest.mock('../output');
Expand Down Expand Up @@ -361,4 +369,191 @@ describe('Agentless Agent service', () => {
})
);
});

it('should redact sensitive information from debug logs', async () => {
const returnValue = {
id: 'mocked',
regional_id: 'mocked',
};

(axios as jest.MockedFunction<typeof axios>).mockResolvedValueOnce(returnValue);
const soClient = getAgentPolicyCreateMock();
const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser;

jest.spyOn(appContextService, 'getConfig').mockReturnValue({
agentless: {
enabled: true,
api: {
url: 'http://api.agentless.com',
tls: {
certificate: '/path/to/cert',
key: '/path/to/key',
ca: '/path/to/ca',
},
},
},
} as any);
jest.spyOn(appContextService, 'getCloud').mockReturnValue({ isCloudEnabled: true } as any);
jest
.spyOn(appContextService, 'getKibanaVersion')
.mockReturnValue('mocked-kibana-version-infinite');

mockedListFleetServerHosts.mockResolvedValue({
items: [
{
id: 'mocked-fleet-server-id',
host: 'http://fleetserver:8220',
active: true,
is_default: true,
host_urls: ['http://fleetserver:8220'],
},
],
} as any);

mockedListEnrollmentApiKeys.mockResolvedValue({
items: [
{
id: 'mocked-fleet-enrollment-token-id',
policy_id: 'mocked-fleet-enrollment-policy-id',
api_key: 'mocked-fleet-enrollment-api-key',
},
],
} as any);

await agentlessAgentService.createAgentlessAgent(esClient, soClient, {
id: 'mocked-agentless-agent-policy-id',
name: 'agentless agent policy',
namespace: 'default',
supports_agentless: true,
} as AgentPolicy);

// Assert that sensitive information is redacted
expect(mockedLogger.debug).toHaveBeenCalledWith(
expect.stringContaining('fleet_token: [REDACTED]')
);
expect(mockedLogger.debug).toHaveBeenCalledWith(expect.stringContaining('cert: [REDACTED]'));
expect(mockedLogger.debug).toHaveBeenCalledWith(expect.stringContaining('key: [REDACTED]'));
expect(mockedLogger.debug).toHaveBeenCalledWith(expect.stringContaining('ca: [REDACTED]'));
});

it('should log "undefined" on debug logs when tls configuration is missing', async () => {
const returnValue = {
id: 'mocked',
regional_id: 'mocked',
};

(axios as jest.MockedFunction<typeof axios>).mockResolvedValueOnce(returnValue);
const soClient = getAgentPolicyCreateMock();
const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser;

jest.spyOn(appContextService, 'getConfig').mockReturnValue({
agentless: {
enabled: true,
api: {
url: 'http://api.agentless.com',
},
},
} as any);
jest.spyOn(appContextService, 'getCloud').mockReturnValue({ isCloudEnabled: true } as any);
jest
.spyOn(appContextService, 'getKibanaVersion')
.mockReturnValue('mocked-kibana-version-infinite');

mockedListFleetServerHosts.mockResolvedValue({
items: [
{
id: 'mocked-fleet-server-id',
host: 'http://fleetserver:8220',
active: true,
is_default: true,
host_urls: ['http://fleetserver:8220'],
},
],
} as any);

mockedListEnrollmentApiKeys.mockResolvedValue({
items: [
{
id: 'mocked-fleet-enrollment-token-id',
policy_id: 'mocked-fleet-enrollment-policy-id',
api_key: 'mocked-fleet-enrollment-api-key',
},
],
} as any);

await expect(
agentlessAgentService.createAgentlessAgent(esClient, soClient, {
id: 'mocked-agentless-agent-policy-id',
name: 'agentless agent policy',
namespace: 'default',
supports_agentless: true,
} as AgentPolicy)
).rejects.toThrowError();

// Assert that tls configuration is missing
expect(mockedLogger.debug).toHaveBeenCalledWith(expect.stringContaining('cert: undefined'));
expect(mockedLogger.debug).toHaveBeenCalledWith(expect.stringContaining('key: undefined'));
expect(mockedLogger.debug).toHaveBeenCalledWith(expect.stringContaining('ca: undefined'));
});

it('should redact sensitive information from error logs', async () => {
const soClient = getAgentPolicyCreateMock();
const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser;

jest.spyOn(appContextService, 'getConfig').mockReturnValue({
agentless: {
enabled: true,
api: {
url: 'http://api.agentless.com',
tls: {
certificate: '/path/to/cert',
key: '/path/to/key',
ca: '/path/to/ca',
},
},
},
} as any);
jest.spyOn(appContextService, 'getCloud').mockReturnValue({ isCloudEnabled: true } as any);

mockedListFleetServerHosts.mockResolvedValue({
items: [
{
id: 'mocked-fleet-server-id',
host: 'http://fleetserver:8220',
active: true,
is_default: true,
host_urls: ['http://fleetserver:8220'],
},
],
} as any);

mockedListEnrollmentApiKeys.mockResolvedValue({
items: [
{
id: 'mocked-fleet-enrollment-token-id',
policy_id: 'mocked-fleet-enrollment-policy-id',
api_key: 'mocked-fleet-enrollment-api-key',
},
],
} as any);
// Force axios to throw an AxiosError to simulate an error response
const axiosError = new Error('Test Error') as AxiosError;
axiosError.isAxiosError = true; // Mark it as an AxiosError
(axios as jest.MockedFunction<typeof axios>).mockRejectedValueOnce(axiosError);

await expect(
agentlessAgentService.createAgentlessAgent(esClient, soClient, {
id: 'mocked-agentless-agent-policy-id',
name: 'agentless agent policy',
namespace: 'default',
supports_agentless: true,
} as AgentPolicy)
).rejects.toThrowError();

// Assert that sensitive information is redacted
expect(mockedLogger.error).toHaveBeenCalledWith(
expect.stringContaining(`\"fleet_token\":\"[REDACTED]\"`),
expect.any(Object)
);
});
});
Loading

0 comments on commit be2edae

Please sign in to comment.