Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[8.x] [Cloud Security] [Agentless] Improving error log metadata and sending APM trace id (#192235) #193901

Merged
merged 1 commit into from
Sep 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading