mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
[Cloud Security] Add is_internal flag to fleet server hosts configuration (#175983)
## Summary - Follow up after https://github.com/elastic/kibana/pull/175546 - Part of https://github.com/elastic/kibana/issues/165251 introducing a new `is_internal` config option for `xpack.fleet.fleetServerHosts`. The usage is currently to protect the internal fleet server hosts in the UI: - filter them out in the Settings UI - disable internal hosts in the agent policy form ### Checklist Delete any items that are not applicable to this PR. - [x] [Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html) was added for features that require explanation or tutorials - [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 - [x] [Flaky Test Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was used on any tests changed --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: David Kilfoyle <41695641+kilfoyle@users.noreply.github.com>
This commit is contained in:
parent
5c00ddf1ef
commit
2b929cafeb
23 changed files with 217 additions and 11 deletions
|
@ -269,6 +269,8 @@ List of {fleet-server} hosts that are configured when the {fleet} app starts.
|
|||
=====
|
||||
`is_default`:::
|
||||
Whether or not this host should be the default to use for {fleet-server}.
|
||||
`is_internal`:::
|
||||
If `true` the host will not appear in the UI, and can only be managed through `kibana.yml` or the {fleet} API.
|
||||
`proxy_id`:::
|
||||
Unique ID of the proxy to access the {fleet-server} host.
|
||||
=====
|
||||
|
|
|
@ -632,6 +632,7 @@
|
|||
"fleet-fleet-server-host": [
|
||||
"host_urls",
|
||||
"is_default",
|
||||
"is_internal",
|
||||
"is_preconfigured",
|
||||
"name",
|
||||
"proxy_id"
|
||||
|
|
|
@ -2091,6 +2091,10 @@
|
|||
"is_default": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"is_internal": {
|
||||
"type": "boolean",
|
||||
"index": false
|
||||
},
|
||||
"host_urls": {
|
||||
"type": "keyword",
|
||||
"index": false
|
||||
|
|
|
@ -94,7 +94,7 @@ describe('checking migration metadata changes on all registered SO types', () =>
|
|||
"file": "6b65ae5899b60ebe08656fd163ea532e557d3c98",
|
||||
"file-upload-usage-collection-telemetry": "06e0a8c04f991e744e09d03ab2bd7f86b2088200",
|
||||
"fileShare": "5be52de1747d249a221b5241af2838264e19aaa1",
|
||||
"fleet-fleet-server-host": "b04898fcde07f4ce86e844c8fe2f4b23b77ef60a",
|
||||
"fleet-fleet-server-host": "69be15f6b6f2a2875ad3c7050ddea7a87f505417",
|
||||
"fleet-message-signing-keys": "93421f43fed2526b59092a4e3c65d64bc2266c0f",
|
||||
"fleet-preconfiguration-deletion-record": "c52ea1e13c919afe8a5e8e3adbb7080980ecc08e",
|
||||
"fleet-proxy": "6cb688f0d2dd856400c1dbc998b28704ff70363d",
|
||||
|
|
|
@ -5083,6 +5083,9 @@
|
|||
"is_default": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"is_internal": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"host_urls": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
|
@ -5195,6 +5198,9 @@
|
|||
"is_default": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"is_internal": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"host_urls": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
|
@ -8904,6 +8910,9 @@
|
|||
"is_default": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"is_internal": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"is_preconfigured": {
|
||||
"type": "boolean"
|
||||
},
|
||||
|
|
|
@ -3171,6 +3171,8 @@ paths:
|
|||
type: string
|
||||
is_default:
|
||||
type: boolean
|
||||
is_internal:
|
||||
type: boolean
|
||||
host_urls:
|
||||
type: array
|
||||
items:
|
||||
|
@ -3241,6 +3243,8 @@ paths:
|
|||
type: string
|
||||
is_default:
|
||||
type: boolean
|
||||
is_internal:
|
||||
type: boolean
|
||||
host_urls:
|
||||
type: array
|
||||
items:
|
||||
|
@ -5762,6 +5766,8 @@ components:
|
|||
type: string
|
||||
is_default:
|
||||
type: boolean
|
||||
is_internal:
|
||||
type: boolean
|
||||
is_preconfigured:
|
||||
type: boolean
|
||||
host_urls:
|
||||
|
|
|
@ -7,6 +7,8 @@ properties:
|
|||
type: string
|
||||
is_default:
|
||||
type: boolean
|
||||
is_internal:
|
||||
type: boolean
|
||||
is_preconfigured:
|
||||
type: boolean
|
||||
host_urls:
|
||||
|
|
|
@ -51,6 +51,8 @@ post:
|
|||
type: string
|
||||
is_default:
|
||||
type: boolean
|
||||
is_internal:
|
||||
type: boolean
|
||||
host_urls:
|
||||
type: array
|
||||
items:
|
||||
|
|
|
@ -59,6 +59,8 @@ put:
|
|||
type: string
|
||||
is_default:
|
||||
type: boolean
|
||||
is_internal:
|
||||
type: boolean
|
||||
host_urls:
|
||||
type: array
|
||||
items:
|
||||
|
|
|
@ -11,6 +11,7 @@ export interface NewFleetServerHost {
|
|||
host_urls: string[];
|
||||
is_default: boolean;
|
||||
is_preconfigured: boolean;
|
||||
is_internal?: boolean;
|
||||
proxy_id?: string | null;
|
||||
}
|
||||
|
||||
|
|
|
@ -19,6 +19,7 @@ export interface PutFleetServerHostsRequest {
|
|||
name?: string;
|
||||
host_urls?: string[];
|
||||
is_default?: boolean;
|
||||
is_internal?: boolean;
|
||||
proxy_id?: string | null;
|
||||
};
|
||||
}
|
||||
|
@ -29,6 +30,7 @@ export interface PostFleetServerHostsRequest {
|
|||
name?: string;
|
||||
host_urls?: string[];
|
||||
is_default?: boolean;
|
||||
is_internal?: boolean;
|
||||
proxy_id?: string | null;
|
||||
};
|
||||
}
|
||||
|
|
|
@ -28,6 +28,13 @@ describe('Edit settings', () => {
|
|||
host_urls: ['https://localhost:8220'],
|
||||
is_default: true,
|
||||
},
|
||||
{
|
||||
id: 'fleet-internal-host',
|
||||
name: 'Internal Host',
|
||||
host_urls: ['https://internal:8220'],
|
||||
is_default: false,
|
||||
is_internal: true,
|
||||
},
|
||||
],
|
||||
page: 1,
|
||||
perPage: 10000,
|
||||
|
@ -160,4 +167,8 @@ describe('Edit settings', () => {
|
|||
expect(interception.request.body.name).to.equal('output-logstash-1');
|
||||
});
|
||||
});
|
||||
|
||||
it('should not display internal fleet server hosts', () => {
|
||||
cy.getBySel(SETTINGS_FLEET_SERVER_HOSTS.TABLE).should('not.contain', 'Internal Host');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -190,6 +190,7 @@ export const SETTINGS_OUTPUTS_KAFKA = {
|
|||
export const SETTINGS_FLEET_SERVER_HOSTS = {
|
||||
ADD_BUTTON: 'settings.fleetServerHosts.addFleetServerHostBtn',
|
||||
EDIT_BUTTON: 'fleetServerHostsTable.edit.btn',
|
||||
TABLE: 'settingsFleetServerHostsTable',
|
||||
};
|
||||
|
||||
export const AGENT_POLICY_FORM = {
|
||||
|
|
|
@ -11,7 +11,7 @@ import { useLicense } from '../../../../../../hooks/use_license';
|
|||
import type { LicenseService } from '../../../../services';
|
||||
import type { AgentPolicy } from '../../../../types';
|
||||
|
||||
import { useOutputOptions } from './hooks';
|
||||
import { useOutputOptions, useFleetServerHostsOptions } from './hooks';
|
||||
|
||||
jest.mock('../../../../../../hooks/use_license');
|
||||
|
||||
|
@ -153,6 +153,35 @@ const mockApiCallsWithInternalOutputs = (http: MockedFleetStartServices['http'])
|
|||
});
|
||||
};
|
||||
|
||||
const mockApiCallsWithInternalFleetServerHost = (http: MockedFleetStartServices['http']) => {
|
||||
http.get.mockImplementation(async (path) => {
|
||||
if (typeof path !== 'string') {
|
||||
throw new Error('Invalid request');
|
||||
}
|
||||
if (path === '/api/fleet/fleet_server_hosts') {
|
||||
return {
|
||||
data: {
|
||||
items: [
|
||||
{
|
||||
id: 'default-host',
|
||||
name: 'Default',
|
||||
is_default: true,
|
||||
},
|
||||
{
|
||||
id: 'internal-output',
|
||||
name: 'Internal',
|
||||
is_default: false,
|
||||
is_internal: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return defaultHttpClientGetImplementation(path);
|
||||
});
|
||||
};
|
||||
|
||||
describe('useOutputOptions', () => {
|
||||
it('should generate enabled options if the licence is platinium', async () => {
|
||||
const testRenderer = createFleetTestRendererMock();
|
||||
|
@ -636,3 +665,30 @@ describe('useOutputOptions', () => {
|
|||
`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('useFleetServerHostsOptions', () => {
|
||||
it('should not enable internal fleet server hosts', async () => {
|
||||
const testRenderer = createFleetTestRendererMock();
|
||||
mockApiCallsWithInternalFleetServerHost(testRenderer.startServices.http);
|
||||
const { result, waitForNextUpdate } = testRenderer.renderHook(() =>
|
||||
useFleetServerHostsOptions({} as AgentPolicy)
|
||||
);
|
||||
expect(result.current.isLoading).toBeTruthy();
|
||||
|
||||
await waitForNextUpdate();
|
||||
expect(result.current.fleetServerHostsOptions).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Object {
|
||||
"disabled": undefined,
|
||||
"inputDisplay": "Default (currently Default)",
|
||||
"value": "@@##DEFAULT_SELECT##@@",
|
||||
},
|
||||
Object {
|
||||
"disabled": true,
|
||||
"inputDisplay": "Internal",
|
||||
"value": "internal-output",
|
||||
},
|
||||
]
|
||||
`);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -224,9 +224,12 @@ export function useFleetServerHostsOptions(agentPolicy: Partial<NewAgentPolicy |
|
|||
...fleetServerHostsRequest.data.items
|
||||
.filter((item) => !item.is_default)
|
||||
.map((item) => {
|
||||
const isInternalFleetServerHost = !!item.is_internal;
|
||||
|
||||
return {
|
||||
value: item.id,
|
||||
inputDisplay: item.name,
|
||||
disabled: isInternalFleetServerHost,
|
||||
};
|
||||
}),
|
||||
];
|
||||
|
|
|
@ -152,5 +152,11 @@ export const FleetServerHostsTable: React.FunctionComponent<FleetServerHostsTabl
|
|||
];
|
||||
}, [getHref, deleteFleetServerHost]);
|
||||
|
||||
return <EuiBasicTable columns={columns} items={fleetServerHosts} />;
|
||||
return (
|
||||
<EuiBasicTable
|
||||
columns={columns}
|
||||
items={fleetServerHosts}
|
||||
data-test-subj="settingsFleetServerHostsTable"
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -50,6 +50,7 @@ export const SettingsApp = withConfirmModalProvider(() => {
|
|||
|
||||
const { outputs, fleetServerHosts, downloadSources, proxies } = useSettingsAppData();
|
||||
const outputItems = outputs.data?.items.filter((item) => !item.is_internal);
|
||||
const fleetServerHostsItems = fleetServerHosts.data?.items.filter((item) => !item.is_internal);
|
||||
|
||||
const { deleteOutput } = useDeleteOutput(outputs.resendRequest);
|
||||
const { deleteDownloadSource } = useDeleteDownloadSource(downloadSources.resendRequest);
|
||||
|
@ -81,7 +82,7 @@ export const SettingsApp = withConfirmModalProvider(() => {
|
|||
(outputs.isLoading && outputs.isInitialRequest) ||
|
||||
!outputItems ||
|
||||
(fleetServerHosts.isLoading && fleetServerHosts.isInitialRequest) ||
|
||||
!fleetServerHosts.data?.items ||
|
||||
!fleetServerHostsItems ||
|
||||
(downloadSources.isLoading && downloadSources.isInitialRequest) ||
|
||||
!downloadSources.data?.items ||
|
||||
(proxies.isLoading && proxies.isInitialRequest) ||
|
||||
|
@ -99,7 +100,7 @@ export const SettingsApp = withConfirmModalProvider(() => {
|
|||
<Routes>
|
||||
<Route path={FLEET_ROUTING_PATHS.settings_edit_fleet_server_hosts}>
|
||||
{(route: { match: { params: { itemId: string } } }) => {
|
||||
const fleetServerHost = fleetServerHosts.data?.items.find(
|
||||
const fleetServerHost = fleetServerHostsItems.find(
|
||||
(o) => route.match.params.itemId === o.id
|
||||
);
|
||||
if (!fleetServerHost) {
|
||||
|
@ -198,7 +199,7 @@ export const SettingsApp = withConfirmModalProvider(() => {
|
|||
deleteFleetProxy={deleteFleetProxy}
|
||||
proxies={proxies.data.items}
|
||||
outputs={outputItems}
|
||||
fleetServerHosts={fleetServerHosts.data.items}
|
||||
fleetServerHosts={fleetServerHostsItems}
|
||||
deleteOutput={deleteOutput}
|
||||
deleteFleetServerHost={deleteFleetServerHost}
|
||||
downloadSources={downloadSources.data.items}
|
||||
|
|
|
@ -626,11 +626,24 @@ const getSavedObjectTypes = (): { [key: string]: SavedObjectsType } => ({
|
|||
properties: {
|
||||
name: { type: 'keyword' },
|
||||
is_default: { type: 'boolean' },
|
||||
is_internal: { type: 'boolean', index: false },
|
||||
host_urls: { type: 'keyword', index: false },
|
||||
is_preconfigured: { type: 'boolean' },
|
||||
proxy_id: { type: 'keyword' },
|
||||
},
|
||||
},
|
||||
modelVersions: {
|
||||
'1': {
|
||||
changes: [
|
||||
{
|
||||
type: 'mappings_addition',
|
||||
addedMappings: {
|
||||
is_internal: { type: 'boolean', index: false },
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
[FLEET_PROXY_SAVED_OBJECT_TYPE]: {
|
||||
name: FLEET_PROXY_SAVED_OBJECT_TYPE,
|
||||
|
|
|
@ -4,20 +4,29 @@
|
|||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import { savedObjectsClientMock } from '@kbn/core/server/mocks';
|
||||
import { savedObjectsClientMock, elasticsearchServiceMock } from '@kbn/core/server/mocks';
|
||||
import { securityMock } from '@kbn/security-plugin/server/mocks';
|
||||
|
||||
import { appContextService } from '../app_context';
|
||||
import { getDefaultFleetServerHost, createFleetServerHost } from '../fleet_server_host';
|
||||
import {
|
||||
getDefaultFleetServerHost,
|
||||
createFleetServerHost,
|
||||
bulkGetFleetServerHosts,
|
||||
updateFleetServerHost,
|
||||
} from '../fleet_server_host';
|
||||
|
||||
import {
|
||||
createCloudFleetServerHostIfNeeded,
|
||||
getCloudFleetServersHosts,
|
||||
getPreconfiguredFleetServerHostFromConfig,
|
||||
createOrUpdatePreconfiguredFleetServerHosts,
|
||||
} from './fleet_server_host';
|
||||
|
||||
import type { FleetServerHost } from '../../../common/types';
|
||||
|
||||
jest.mock('../fleet_server_host');
|
||||
jest.mock('../app_context');
|
||||
jest.mock('../agent_policy');
|
||||
|
||||
const mockedAppContextService = appContextService as jest.Mocked<typeof appContextService>;
|
||||
mockedAppContextService.getSecuritySetup.mockImplementation(() => ({
|
||||
|
@ -30,6 +39,12 @@ const mockedGetDefaultFleetServerHost = getDefaultFleetServerHost as jest.Mocked
|
|||
const mockedCreateFleetServerHost = createFleetServerHost as jest.MockedFunction<
|
||||
typeof createFleetServerHost
|
||||
>;
|
||||
const mockedUpdateFleetServerHost = updateFleetServerHost as jest.MockedFunction<
|
||||
typeof updateFleetServerHost
|
||||
>;
|
||||
const mockedBulkGetFleetServerHosts = bulkGetFleetServerHosts as jest.MockedFunction<
|
||||
typeof bulkGetFleetServerHosts
|
||||
>;
|
||||
|
||||
describe('getPreconfiguredFleetServerHostFromConfig', () => {
|
||||
it('should work with preconfigured fleetServerHosts', () => {
|
||||
|
@ -85,6 +100,30 @@ describe('getPreconfiguredFleetServerHostFromConfig', () => {
|
|||
expect(res.map(({ id }) => id)).toEqual(['fleet-123', 'fleet-default-fleet-server-host']);
|
||||
});
|
||||
|
||||
it('should work with preconfigured internal fleetServerHosts', () => {
|
||||
const config = {
|
||||
fleetServerHosts: [
|
||||
{
|
||||
id: 'fleet-123',
|
||||
name: 'TEST',
|
||||
is_default: true,
|
||||
host_urls: ['http://test.fr'],
|
||||
},
|
||||
{
|
||||
id: 'fleet-internal',
|
||||
name: 'TEST_INTERNAL',
|
||||
is_default: false,
|
||||
is_internal: true,
|
||||
host_urls: ['http://test-internal.fr'],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const res = getPreconfiguredFleetServerHostFromConfig(config);
|
||||
|
||||
expect(res).toEqual(config.fleetServerHosts);
|
||||
});
|
||||
|
||||
it('should throw if there is multiple default outputs', () => {
|
||||
const config = {
|
||||
agents: { fleet_server: { hosts: ['http://test.fr'] } },
|
||||
|
@ -174,10 +213,8 @@ describe('getCloudFleetServersHosts', () => {
|
|||
});
|
||||
|
||||
describe('createCloudFleetServerHostIfNeeded', () => {
|
||||
beforeEach(() => {
|
||||
mockedCreateFleetServerHost.mockReset();
|
||||
});
|
||||
afterEach(() => {
|
||||
mockedCreateFleetServerHost.mockReset();
|
||||
mockedAppContextService.getCloud.mockReset();
|
||||
});
|
||||
it('should do nothing if there is no cloud fleet server hosts', async () => {
|
||||
|
@ -243,3 +280,45 @@ describe('createCloudFleetServerHostIfNeeded', () => {
|
|||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('createOrUpdatePreconfiguredFleetServerHosts', () => {
|
||||
beforeEach(() => {
|
||||
mockedBulkGetFleetServerHosts.mockResolvedValue([
|
||||
{
|
||||
id: 'fleet-123',
|
||||
name: 'TEST',
|
||||
is_default: true,
|
||||
host_urls: ['http://test.fr'],
|
||||
},
|
||||
{
|
||||
id: 'fleet-internal',
|
||||
name: 'TEST_INTERNAL',
|
||||
is_default: false,
|
||||
is_internal: false,
|
||||
host_urls: ['http://test-internal.fr'],
|
||||
},
|
||||
] as FleetServerHost[]);
|
||||
});
|
||||
afterEach(() => {
|
||||
mockedBulkGetFleetServerHosts.mockReset();
|
||||
});
|
||||
|
||||
it('should update preconfigured fleet server hosts if is_internal flag changes', async () => {
|
||||
const soClient = savedObjectsClientMock.create();
|
||||
const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser;
|
||||
|
||||
await createOrUpdatePreconfiguredFleetServerHosts(soClient, esClient, [
|
||||
{
|
||||
id: 'fleet-internal',
|
||||
name: 'TEST_INTERNAL',
|
||||
is_default: false,
|
||||
is_internal: true,
|
||||
host_urls: ['http://test-internal.fr'],
|
||||
is_preconfigured: false,
|
||||
},
|
||||
]);
|
||||
|
||||
expect(mockedCreateFleetServerHost).not.toBeCalled();
|
||||
expect(mockedUpdateFleetServerHost).toBeCalled();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -110,6 +110,7 @@ export async function createOrUpdatePreconfiguredFleetServerHosts(
|
|||
(!existingHost.is_preconfigured ||
|
||||
existingHost.is_default !== preconfiguredFleetServerHost.is_default ||
|
||||
existingHost.name !== preconfiguredFleetServerHost.name ||
|
||||
isDifferent(existingHost.is_internal, preconfiguredFleetServerHost.is_internal) ||
|
||||
isDifferent(
|
||||
existingHost.host_urls.map(normalizeHostsForAgents),
|
||||
preconfiguredFleetServerHost.host_urls.map(normalizeHostsForAgents)
|
||||
|
|
|
@ -103,6 +103,7 @@ export const PreconfiguredFleetServerHostsSchema = schema.arrayOf(
|
|||
id: schema.string(),
|
||||
name: schema.string(),
|
||||
is_default: schema.boolean({ defaultValue: false }),
|
||||
is_internal: schema.maybe(schema.boolean()),
|
||||
host_urls: schema.arrayOf(schema.string(), { minSize: 1 }),
|
||||
proxy_id: schema.nullable(schema.string()),
|
||||
}),
|
||||
|
|
|
@ -13,6 +13,7 @@ export const PostFleetServerHostRequestSchema = {
|
|||
name: schema.string(),
|
||||
host_urls: schema.arrayOf(schema.string(), { minSize: 1 }),
|
||||
is_default: schema.boolean({ defaultValue: false }),
|
||||
is_internal: schema.maybe(schema.boolean()),
|
||||
proxy_id: schema.nullable(schema.string()),
|
||||
}),
|
||||
};
|
||||
|
@ -27,6 +28,7 @@ export const PutFleetServerHostRequestSchema = {
|
|||
name: schema.maybe(schema.string()),
|
||||
host_urls: schema.maybe(schema.arrayOf(schema.string(), { minSize: 1 })),
|
||||
is_default: schema.maybe(schema.boolean({ defaultValue: false })),
|
||||
is_internal: schema.maybe(schema.boolean()),
|
||||
proxy_id: schema.nullable(schema.string()),
|
||||
}),
|
||||
};
|
||||
|
|
|
@ -104,6 +104,7 @@ export interface FleetServerHostSOAttributes {
|
|||
host_urls: string[];
|
||||
is_default: boolean;
|
||||
is_preconfigured: boolean;
|
||||
is_internal?: boolean;
|
||||
proxy_id?: string | null;
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue