mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[Fleet] Logstash Output - prevent updating data_output_id for preconfigured policies (#154445)
Closes https://github.com/elastic/kibana/issues/154326 ## Summary After the merge of https://github.com/elastic/kibana/pull/153226, when creating/updating a Logstash output as default, the `Elastic Cloud agent policy` preconfigured on Cloud gets reassigned to the "default" ES policy instead than keeping the `Elastic Cloud internal output`. <img width="1418" alt="Screenshot 2023-04-04 at 12 51 21" src="https://user-images.githubusercontent.com/16084106/230112067-a2767d1a-1191-4877-8dec-546d1590e41f.png"> Tee bug is fixed by checking if any given fleet server policy is `preconfigured` or if it has already an assigned `data_output_id`, in which cases it doesn't get updated. ### Testing - Create an ES output additional to the default one - Create a preconfigured fleet server policy and make sure it has `fleet-server` integration (this is to simulate the preconfigured cloud policy) - Assign the previous output to the preconfigured policy - Now create a new `logstash` output and make it default - Check that the preconfigured policy maintains the custom output previously assigned ### Checklist - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios --------- Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Nicolas Chaulet <nicolas.chaulet@elastic.co>
This commit is contained in:
parent
2636262e09
commit
a5de314687
3 changed files with 196 additions and 53 deletions
|
@ -189,6 +189,7 @@ describe('Output Service', () => {
|
|||
mockedAppContextService.getInternalUserSOClient.mockReset();
|
||||
mockedAppContextService.getEncryptedSavedObjectsSetup.mockReset();
|
||||
mockedAuditLoggingService.writeCustomSoAuditLog.mockReset();
|
||||
mockedAgentPolicyService.update.mockReset();
|
||||
});
|
||||
describe('create', () => {
|
||||
it('work with a predefined id', async () => {
|
||||
|
@ -443,7 +444,7 @@ describe('Output Service', () => {
|
|||
expect.anything(),
|
||||
'fleet_server_policy',
|
||||
{ data_output_id: 'output-test' },
|
||||
{ force: true }
|
||||
{ force: false }
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -497,14 +498,6 @@ describe('Output Service', () => {
|
|||
},
|
||||
{ id: 'output-1' }
|
||||
);
|
||||
|
||||
expect(mockedAgentPolicyService.update).toBeCalledWith(
|
||||
expect.anything(),
|
||||
expect.anything(),
|
||||
'fleet_server_policy',
|
||||
{ data_output_id: 'output-test' },
|
||||
{ force: true }
|
||||
);
|
||||
});
|
||||
|
||||
it('should call audit logger', async () => {
|
||||
|
@ -798,6 +791,72 @@ describe('Output Service', () => {
|
|||
is_default: true,
|
||||
});
|
||||
|
||||
expect(soClient.update).toBeCalledWith(expect.anything(), expect.anything(), {
|
||||
type: 'logstash',
|
||||
hosts: ['test:4343'],
|
||||
is_default: true,
|
||||
ca_sha256: null,
|
||||
ca_trusted_fingerprint: null,
|
||||
});
|
||||
expect(mockedAgentPolicyService.update).toBeCalledWith(
|
||||
expect.anything(),
|
||||
expect.anything(),
|
||||
'fleet_server_policy',
|
||||
{ data_output_id: 'output-test' },
|
||||
{ force: false }
|
||||
);
|
||||
});
|
||||
|
||||
it('Should update fleet server policies with data_output_id=default_output_id and force=true if a default ES output is changed to logstash, from preconfiguration', async () => {
|
||||
const soClient = getMockedSoClient({
|
||||
defaultOutputId: 'output-test',
|
||||
});
|
||||
mockedAgentPolicyService.list.mockResolvedValue({
|
||||
items: [
|
||||
{
|
||||
name: 'fleet server policy',
|
||||
id: 'fleet_server_policy',
|
||||
is_default_fleet_server: true,
|
||||
package_policies: [
|
||||
{
|
||||
name: 'fleet-server-123',
|
||||
package: {
|
||||
name: 'fleet_server',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'agent policy 1',
|
||||
id: 'agent_policy_1',
|
||||
is_managed: false,
|
||||
package_policies: [
|
||||
{
|
||||
name: 'nginx',
|
||||
package: {
|
||||
name: 'nginx',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
} as unknown as ReturnType<typeof mockedAgentPolicyService.list>);
|
||||
mockedAgentPolicyService.hasFleetServerIntegration.mockReturnValue(true);
|
||||
|
||||
await outputService.update(
|
||||
soClient,
|
||||
esClientMock,
|
||||
'output-test',
|
||||
{
|
||||
type: 'logstash',
|
||||
hosts: ['test:4343'],
|
||||
is_default: true,
|
||||
},
|
||||
{
|
||||
fromPreconfiguration: true,
|
||||
}
|
||||
);
|
||||
|
||||
expect(soClient.update).toBeCalledWith(expect.anything(), expect.anything(), {
|
||||
type: 'logstash',
|
||||
hosts: ['test:4343'],
|
||||
|
|
|
@ -165,7 +165,8 @@ async function validateTypeChanges(
|
|||
id: string,
|
||||
data: Partial<Output>,
|
||||
originalOutput: Output,
|
||||
defaultDataOutputId: string | null
|
||||
defaultDataOutputId: string | null,
|
||||
fromPreconfiguration: boolean
|
||||
) {
|
||||
const mergedIsDefault = data.is_default ?? originalOutput.is_default;
|
||||
const fleetServerPolicies = await findPoliciesWithFleetServer(soClient, id, mergedIsDefault);
|
||||
|
@ -178,18 +179,42 @@ async function validateTypeChanges(
|
|||
// Validate no policy with fleet server use that policy
|
||||
validateLogstashOutputNotUsedInFleetServerPolicy(fleetServerPolicies);
|
||||
}
|
||||
// if a logstash output is updated to become default, update the fleet server policies to use the previous ES output or default output
|
||||
if (data?.type === outputType.Logstash && mergedIsDefault) {
|
||||
await updateFleetServerPoliciesDataOutputId(
|
||||
soClient,
|
||||
esClient,
|
||||
data,
|
||||
mergedIsDefault,
|
||||
defaultDataOutputId,
|
||||
fleetServerPolicies,
|
||||
fromPreconfiguration
|
||||
);
|
||||
}
|
||||
|
||||
async function updateFleetServerPoliciesDataOutputId(
|
||||
soClient: SavedObjectsClientContract,
|
||||
esClient: ElasticsearchClient,
|
||||
data: Partial<Output>,
|
||||
isDefault: boolean,
|
||||
defaultDataOutputId: string | null,
|
||||
fleetServerPolicies: AgentPolicy[],
|
||||
fromPreconfiguration: boolean
|
||||
) {
|
||||
// if a logstash output is updated to become default
|
||||
// if fleet server policies are don't have data_output_id or if they are using the new output
|
||||
// update them to use the default output
|
||||
if (data?.type === outputType.Logstash && isDefault) {
|
||||
for (const policy of fleetServerPolicies) {
|
||||
await agentPolicyService.update(
|
||||
soClient,
|
||||
esClient,
|
||||
policy.id,
|
||||
{ data_output_id: defaultDataOutputId },
|
||||
{
|
||||
force: true,
|
||||
}
|
||||
);
|
||||
if (!policy.data_output_id || policy.data_output_id === data?.id) {
|
||||
await agentPolicyService.update(
|
||||
soClient,
|
||||
esClient,
|
||||
policy.id,
|
||||
{
|
||||
data_output_id: defaultDataOutputId,
|
||||
},
|
||||
{ force: fromPreconfiguration }
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -302,6 +327,7 @@ class OutputService {
|
|||
options?: { id?: string; fromPreconfiguration?: boolean; overwrite?: boolean }
|
||||
): Promise<Output> {
|
||||
const data: OutputSOAttributes = { ...omit(output, 'ssl') };
|
||||
const defaultDataOutputId = await this.getDefaultDataOutputId(soClient);
|
||||
|
||||
if (output.type === outputType.Logstash) {
|
||||
await validateLogstashOutputNotUsedInAPMPolicy(soClient, undefined, data.is_default);
|
||||
|
@ -311,34 +337,24 @@ class OutputService {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (data.type === outputType.Logstash) {
|
||||
const defaultDataOutputId = await this.getDefaultDataOutputId(soClient);
|
||||
const fleetServerPolicies = await findPoliciesWithFleetServer(soClient);
|
||||
// if a logstash output is updated to become default, update the fleet server policies to use the previous ES output or default output
|
||||
if (data.is_default) {
|
||||
for (const policy of fleetServerPolicies) {
|
||||
await agentPolicyService.update(
|
||||
soClient,
|
||||
esClient,
|
||||
policy.id,
|
||||
{ data_output_id: defaultDataOutputId },
|
||||
{
|
||||
force: true,
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
const fleetServerPolicies = await findPoliciesWithFleetServer(soClient);
|
||||
await updateFleetServerPoliciesDataOutputId(
|
||||
soClient,
|
||||
esClient,
|
||||
data,
|
||||
data.is_default,
|
||||
defaultDataOutputId,
|
||||
fleetServerPolicies,
|
||||
options?.fromPreconfiguration ?? false
|
||||
);
|
||||
|
||||
// ensure only default output exists
|
||||
if (data.is_default) {
|
||||
const defaultDataOuputId = await this.getDefaultDataOutputId(soClient);
|
||||
if (defaultDataOuputId) {
|
||||
if (defaultDataOutputId) {
|
||||
await this.update(
|
||||
soClient,
|
||||
esClient,
|
||||
defaultDataOuputId,
|
||||
defaultDataOutputId,
|
||||
{ is_default: false },
|
||||
{ fromPreconfiguration: options?.fromPreconfiguration ?? false }
|
||||
);
|
||||
|
@ -549,7 +565,15 @@ class OutputService {
|
|||
const mergedType = data.type ?? originalOutput.type;
|
||||
const defaultDataOutputId = await this.getDefaultDataOutputId(soClient);
|
||||
|
||||
await validateTypeChanges(soClient, esClient, id, data, originalOutput, defaultDataOutputId);
|
||||
await validateTypeChanges(
|
||||
soClient,
|
||||
esClient,
|
||||
id,
|
||||
data,
|
||||
originalOutput,
|
||||
defaultDataOutputId,
|
||||
fromPreconfiguration
|
||||
);
|
||||
|
||||
// If the output type changed
|
||||
if (data.type && data.type !== originalOutput.type) {
|
||||
|
|
|
@ -27,6 +27,9 @@ export default function (providerContext: FtrProviderContext) {
|
|||
setupFleetAndAgents(providerContext);
|
||||
|
||||
let defaultOutputId: string;
|
||||
let ESOutputId: string;
|
||||
let fleetServerPolicyId: string;
|
||||
let fleetServerPolicyWithCustomOutputId: string;
|
||||
|
||||
before(async function () {
|
||||
// we must first force install the fleet_server package to override package verification error on policy create
|
||||
|
@ -53,6 +56,7 @@ export default function (providerContext: FtrProviderContext) {
|
|||
})
|
||||
.expect(200);
|
||||
const fleetServerPolicy = apiResponse.item;
|
||||
fleetServerPolicyId = fleetServerPolicy.id;
|
||||
|
||||
({ body: apiResponse } = await supertest
|
||||
.post(`/api/fleet/agent_policies`)
|
||||
|
@ -92,6 +96,30 @@ export default function (providerContext: FtrProviderContext) {
|
|||
}
|
||||
|
||||
defaultOutputId = defaultOutput.id;
|
||||
|
||||
const { body: postResponse1 } = await supertest
|
||||
.post(`/api/fleet/outputs`)
|
||||
.set('kbn-xsrf', 'xxxx')
|
||||
.send({
|
||||
name: 'ESoutput',
|
||||
type: 'elasticsearch',
|
||||
hosts: ['https://test.fr'],
|
||||
})
|
||||
.expect(200);
|
||||
ESOutputId = postResponse1.item.id;
|
||||
|
||||
({ body: apiResponse } = await supertest
|
||||
.post(`/api/fleet/agent_policies`)
|
||||
.set('kbn-xsrf', 'kibana')
|
||||
.send({
|
||||
name: 'Preconfigured Fleet Server policy',
|
||||
namespace: 'default',
|
||||
has_fleet_server: true,
|
||||
data_output_id: `${ESOutputId}`,
|
||||
})
|
||||
.expect(200));
|
||||
const fleetServerPolicyWithCustomOutput = apiResponse.item;
|
||||
fleetServerPolicyWithCustomOutputId = fleetServerPolicyWithCustomOutput.id;
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
|
@ -100,10 +128,12 @@ export default function (providerContext: FtrProviderContext) {
|
|||
});
|
||||
|
||||
describe('GET /outputs', () => {
|
||||
it('should list the default output', async () => {
|
||||
it('should list all the outputs', async () => {
|
||||
const { body: getOutputsRes } = await supertest.get(`/api/fleet/outputs`).expect(200);
|
||||
|
||||
expect(getOutputsRes.items.length).to.eql(1);
|
||||
expect(getOutputsRes.items.length).to.eql(2);
|
||||
const findDefault = getOutputsRes.items.find((item: any) => item.is_default === true);
|
||||
expect(findDefault.id).to.eql(defaultOutputId);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -160,7 +190,7 @@ export default function (providerContext: FtrProviderContext) {
|
|||
);
|
||||
});
|
||||
|
||||
it('should allow to update a default ES output keeping it ES', async function () {
|
||||
it('should allow to update a default ES output if keeping it ES', async function () {
|
||||
await supertest
|
||||
.put(`/api/fleet/outputs/${defaultOutputId}`)
|
||||
.set('kbn-xsrf', 'xxxx')
|
||||
|
@ -173,7 +203,7 @@ export default function (providerContext: FtrProviderContext) {
|
|||
});
|
||||
|
||||
it('should allow to update a non-default ES output to logstash', async function () {
|
||||
const { body: postResponse } = await supertest
|
||||
const { body: postResponse2 } = await supertest
|
||||
.post(`/api/fleet/outputs`)
|
||||
.set('kbn-xsrf', 'xxxx')
|
||||
.send({
|
||||
|
@ -188,7 +218,7 @@ export default function (providerContext: FtrProviderContext) {
|
|||
})
|
||||
.expect(200);
|
||||
|
||||
const { id: logstashOutput1Id } = postResponse.item;
|
||||
const { id: logstashOutput1Id } = postResponse2.item;
|
||||
await supertest
|
||||
.put(`/api/fleet/outputs/${logstashOutput1Id}`)
|
||||
.set('kbn-xsrf', 'xxxx')
|
||||
|
@ -203,9 +233,19 @@ export default function (providerContext: FtrProviderContext) {
|
|||
},
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
const { body } = await supertest.get(`/api/fleet/agent_policies/${fleetServerPolicyId}`);
|
||||
const updatedFleetServerPolicy = body.item;
|
||||
expect(updatedFleetServerPolicy.data_output_id === defaultOutputId);
|
||||
|
||||
const { body: bodyWithOutput } = await supertest.get(
|
||||
`/api/fleet/agent_policies/${fleetServerPolicyWithCustomOutputId}`
|
||||
);
|
||||
const updatedFleetServerPolicyWithCustomOutput = bodyWithOutput.item;
|
||||
expect(updatedFleetServerPolicyWithCustomOutput.data_output_id === ESOutputId);
|
||||
});
|
||||
|
||||
it('should allow to update a default logstash output to logstash', async function () {
|
||||
it('should allow to update a default logstash output to logstash and fleet server policies should be updated', async function () {
|
||||
const { body: postResponse } = await supertest
|
||||
.post(`/api/fleet/outputs`)
|
||||
.set('kbn-xsrf', 'xxxx')
|
||||
|
@ -238,6 +278,16 @@ export default function (providerContext: FtrProviderContext) {
|
|||
.expect(200);
|
||||
|
||||
await supertest.get(`/api/fleet/outputs`).expect(200);
|
||||
|
||||
const { body } = await supertest.get(`/api/fleet/agent_policies/${fleetServerPolicyId}`);
|
||||
const updatedFleetServerPolicy = body.item;
|
||||
expect(updatedFleetServerPolicy.data_output_id === defaultOutputId);
|
||||
|
||||
const { body: bodyWithOutput } = await supertest.get(
|
||||
`/api/fleet/agent_policies/${fleetServerPolicyWithCustomOutputId}`
|
||||
);
|
||||
const updatedFleetServerPolicyWithCustomOutput = bodyWithOutput.item;
|
||||
expect(updatedFleetServerPolicyWithCustomOutput.data_output_id === ESOutputId);
|
||||
});
|
||||
|
||||
it('should allow to update a logstash output with the shipper values', async function () {
|
||||
|
@ -361,7 +411,7 @@ export default function (providerContext: FtrProviderContext) {
|
|||
});
|
||||
});
|
||||
|
||||
it('should allow to create a logstash output', async function () {
|
||||
it('should allow to create a new logstash output', async function () {
|
||||
const { body: postResponse } = await supertest
|
||||
.post(`/api/fleet/outputs`)
|
||||
.set('kbn-xsrf', 'xxxx')
|
||||
|
@ -392,7 +442,7 @@ export default function (providerContext: FtrProviderContext) {
|
|||
});
|
||||
});
|
||||
|
||||
it('should allow to create a new logstash default output', async function () {
|
||||
it('should allow to create a new logstash default output and fleet server policies should not change', async function () {
|
||||
const { body: postResponse } = await supertest
|
||||
.post(`/api/fleet/outputs`)
|
||||
.set('kbn-xsrf', 'xxxx')
|
||||
|
@ -422,6 +472,16 @@ export default function (providerContext: FtrProviderContext) {
|
|||
certificate_authorities: ['CA1', 'CA2'],
|
||||
},
|
||||
});
|
||||
|
||||
const { body } = await supertest.get(`/api/fleet/agent_policies/${fleetServerPolicyId}`);
|
||||
const updatedFleetServerPolicy = body.item;
|
||||
expect(updatedFleetServerPolicy.data_output_id === defaultOutputId);
|
||||
|
||||
const { body: bodyWithOutput } = await supertest.get(
|
||||
`/api/fleet/agent_policies/${fleetServerPolicyWithCustomOutputId}`
|
||||
);
|
||||
const updatedFleetServerPolicyWithCustomOutput = bodyWithOutput.item;
|
||||
expect(updatedFleetServerPolicyWithCustomOutput.data_output_id === ESOutputId);
|
||||
});
|
||||
|
||||
it('should not allow to create a logstash output with http hosts ', async function () {
|
||||
|
@ -445,7 +505,7 @@ export default function (providerContext: FtrProviderContext) {
|
|||
);
|
||||
});
|
||||
|
||||
it('should toggle default output when creating a new default output ', async function () {
|
||||
it('should toggle the default output when creating a new one', async function () {
|
||||
await supertest
|
||||
.post(`/api/fleet/outputs`)
|
||||
.set('kbn-xsrf', 'xxxx')
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue