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

[SecuritySolution] Disable K8S link on serverless #204350

Closed
wants to merge 8 commits into from
Closed
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
1 change: 1 addition & 0 deletions config/serverless.security.yml
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ xpack.securitySolutionServerless.productTypes:

xpack.securitySolution.offeringSettings: {
ILMEnabled: false, # Index Lifecycle Management (ILM) functionalities disabled, not supported by serverless Elasticsearch
defendForContainersEnabled: false # D4C disabled, not supported in serverless
}

newsfeed.enabled: true
Expand Down
5 changes: 5 additions & 0 deletions x-pack/plugins/security_solution/common/config_settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ export interface ConfigSettings {
* Index Lifecycle Management (ILM) feature enabled.
*/
ILMEnabled: boolean;
/**
* Defend for Containers feature enabled.
*/
defendForContainersEnabled: boolean;
}

/**
Expand All @@ -18,6 +22,7 @@ export interface ConfigSettings {
*/
export const defaultSettings: ConfigSettings = Object.freeze({
ILMEnabled: true,
defendForContainersEnabled: true,
});

type ConfigSettingsKey = keyof ConfigSettings;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export const allowedExperimentalValues = Object.freeze({
// FIXME:PT delete?
excludePoliciesInFilterEnabled: false,

kubernetesEnabled: false,
kubernetesEnabled: true,
donutChartEmbeddablesEnabled: false, // Depends on https://github.com/elastic/kibana/issues/136409 item 2 - 6

/**
Expand Down
18 changes: 7 additions & 11 deletions x-pack/plugins/security_solution/public/app_links.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,9 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import type { CoreStart } from '@kbn/core/public';

import { links as attackDiscoveryLinks } from './attack_discovery/links';
import type { StartServices } from './types';
import type { AppLinkItems } from './common/links/types';
import { links as attackDiscoveryLinks } from './attack_discovery/links';
import { indicatorsLinks } from './threat_intelligence/links';
import { links as alertsLinks } from './detections/links';
import { links as rulesLinks } from './rules/links';
Expand All @@ -17,8 +16,7 @@ import { links as managementLinks, getManagementFilteredLinks } from './manageme
import { exploreLinks } from './explore/links';
import { onboardingLinks } from './onboarding/links';
import { findingsLinks } from './cloud_security_posture/links';
import type { StartPlugins } from './types';
import { dashboardsLinks } from './dashboards/links';
import { dashboardsLinks, getDashboardsFilteredLinks } from './dashboards/links';

// TODO: remove after rollout https://github.com/elastic/kibana/issues/179572
export { solutionAppLinksSwitcher } from './app/solution_navigation/links/app_links';
Expand All @@ -37,14 +35,12 @@ export const appLinks: AppLinkItems = Object.freeze([
managementLinks,
]);

export const getFilteredLinks = async (
core: CoreStart,
plugins: StartPlugins
): Promise<AppLinkItems> => {
const managementFilteredLinks = await getManagementFilteredLinks(core, plugins);
export const getFilteredLinks = async (services: StartServices): Promise<AppLinkItems> => {
const managementFilteredLinks = await getManagementFilteredLinks(services);
const dashboardsFilteredLinks = getDashboardsFilteredLinks(services);

return Object.freeze([
dashboardsLinks,
dashboardsFilteredLinks,
alertsLinks,
attackDiscoveryLinks,
findingsLinks,
Expand Down
11 changes: 11 additions & 0 deletions x-pack/plugins/security_solution/public/dashboards/links.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
overviewLinks,
} from '../overview/links';
import { IconDashboards } from '../common/icons/dashboards';
import type { StartServices } from '../types';

const subLinks: LinkItem[] = [
overviewLinks,
Expand All @@ -42,3 +43,13 @@ export const dashboardsLinks: LinkItem = {
links: subLinks,
skipUrlState: false,
};

export const getDashboardsFilteredLinks = (services: StartServices): LinkItem => {
if (services.configSettings.defendForContainersEnabled) {
return dashboardsLinks;
}
return {
...dashboardsLinks,
links: subLinks.filter((link) => link.id !== SecurityPageName.kubernetes),
};
};
6 changes: 3 additions & 3 deletions x-pack/plugins/security_solution/public/kubernetes/routes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,20 @@
*/

import React from 'react';
import { TrackApplicationView } from '@kbn/usage-collection-plugin/public';
import { allowedExperimentalValues } from '../../common';
import { KubernetesContainer } from './pages';

import type { SecuritySubPluginRoutes } from '../app/types';
import { SecurityPageName } from '../app/types';
import { KUBERNETES_PATH } from '../../common/constants';
import { PluginTemplateWrapper } from '../common/components/plugin_template_wrapper';
import { SecurityRoutePageWrapper } from '../common/components/security_route_page_wrapper';

export const KubernetesRoutes = () => (
<PluginTemplateWrapper>
<TrackApplicationView viewId={SecurityPageName.kubernetes}>
<SecurityRoutePageWrapper pageName={SecurityPageName.kubernetes} redirectOnMissing>
<KubernetesContainer />
</TrackApplicationView>
</SecurityRoutePageWrapper>
</PluginTemplateWrapper>
);

Expand Down
41 changes: 21 additions & 20 deletions x-pack/plugins/security_solution/public/management/links.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,11 @@
*/

import type { HttpSetup } from '@kbn/core/public';
import { coreMock } from '@kbn/core/public/mocks';

import { SecurityPageName } from '../app/types';

import { calculateEndpointAuthz } from '../../common/endpoint/service/authz';
import type { StartPlugins } from '../types';
import type { StartServices } from '../types';
import { getManagementFilteredLinks, links } from './links';
import { allowedExperimentalValues } from '../../common/experimental_features';
import { ExperimentalFeaturesService } from '../common/experimental_features_service';
Expand All @@ -21,6 +20,7 @@ import type { LicenseService } from '../../common/license';
import { createLicenseServiceMock } from '../../common/license/mocks';
import { createFleetAuthzMock } from '@kbn/fleet-plugin/common/mocks';
import { ENDPOINT_ARTIFACT_LISTS } from '@kbn/securitysolution-list-constants';
import { createStartServicesMock } from '../common/lib/kibana/kibana_react.mock';

jest.mock('../common/hooks/use_license');

Expand All @@ -37,16 +37,17 @@ jest.mock('../common/lib/kibana');
const licenseServiceMock = _licenseService as jest.Mocked<LicenseService>;

describe('links', () => {
let coreMockStarted: ReturnType<typeof coreMock.createStart>;
let startServicesMock: StartServices;
let fakeHttpServices: jest.Mocked<HttpSetup>;

const getLinksWithout = (...excludedLinks: SecurityPageName[]) => ({
...links,
links: links.links?.filter((link) => !excludedLinks.includes(link.id)),
});

const getPlugins = (noUserAuthz: boolean = false): StartPlugins => {
const getStartServices = (noUserAuthz: boolean = false): StartServices => {
return {
...startServicesMock,
security: {
authc: {
getCurrentUser: noUserAuthz
Expand All @@ -57,7 +58,7 @@ describe('links', () => {
fleet: {
authz: createFleetAuthzMock(),
},
} as unknown as StartPlugins;
} as unknown as StartServices;
};

beforeAll(() => {
Expand All @@ -67,8 +68,8 @@ describe('links', () => {
});

beforeEach(() => {
coreMockStarted = coreMock.createStart();
fakeHttpServices = coreMockStarted.http as jest.Mocked<HttpSetup>;
startServicesMock = createStartServicesMock();
fakeHttpServices = startServicesMock.http as jest.Mocked<HttpSetup>;
});

afterEach(() => {
Expand All @@ -79,12 +80,12 @@ describe('links', () => {
it('should return all links for user with all sub-feature privileges', async () => {
(calculateEndpointAuthz as jest.Mock).mockReturnValue(getEndpointAuthzInitialStateMock());

const filteredLinks = await getManagementFilteredLinks(coreMockStarted, getPlugins());
const filteredLinks = await getManagementFilteredLinks(getStartServices());
expect(filteredLinks).toEqual(links);
});

it('should not return any endpoint management link for user with all sub-feature privileges when no user authz', async () => {
const filteredLinks = await getManagementFilteredLinks(coreMockStarted, getPlugins(true));
const filteredLinks = await getManagementFilteredLinks(getStartServices(true));
expect(filteredLinks).toEqual(
getLinksWithout(
SecurityPageName.blocklist,
Expand All @@ -109,7 +110,7 @@ describe('links', () => {
);
fakeHttpServices.get.mockResolvedValue({ total: 0 });

const filteredLinks = await getManagementFilteredLinks(coreMockStarted, getPlugins());
const filteredLinks = await getManagementFilteredLinks(getStartServices());
expect(filteredLinks).toEqual(getLinksWithout(SecurityPageName.responseActionsHistory));
});
});
Expand All @@ -121,7 +122,7 @@ describe('links', () => {
getEndpointAuthzInitialStateMock({ canAccessHostIsolationExceptions: true })
);

const filteredLinks = await getManagementFilteredLinks(coreMockStarted, getPlugins());
const filteredLinks = await getManagementFilteredLinks(getStartServices());

expect(filteredLinks).toEqual(links);
expect(fakeHttpServices.get).not.toHaveBeenCalled();
Expand All @@ -135,7 +136,7 @@ describe('links', () => {
})
);

const filteredLinks = await getManagementFilteredLinks(coreMockStarted, getPlugins());
const filteredLinks = await getManagementFilteredLinks(getStartServices());

expect(filteredLinks).toEqual(getLinksWithout(SecurityPageName.hostIsolationExceptions));
expect(fakeHttpServices.get).not.toHaveBeenCalled();
Expand All @@ -151,7 +152,7 @@ describe('links', () => {

fakeHttpServices.get.mockResolvedValue({ total: 0 });

const filteredLinks = await getManagementFilteredLinks(coreMockStarted, getPlugins());
const filteredLinks = await getManagementFilteredLinks(getStartServices());

expect(filteredLinks).toEqual(getLinksWithout(SecurityPageName.hostIsolationExceptions));
expect(fakeHttpServices.get).toHaveBeenCalledWith('/api/exception_lists/items/_find', {
Expand All @@ -172,7 +173,7 @@ describe('links', () => {

fakeHttpServices.get.mockResolvedValue({ total: 100 });

const filteredLinks = await getManagementFilteredLinks(coreMockStarted, getPlugins());
const filteredLinks = await getManagementFilteredLinks(getStartServices());

expect(filteredLinks).toEqual(links);
expect(fakeHttpServices.get).toHaveBeenCalledWith('/api/exception_lists/items/_find', {
Expand All @@ -188,7 +189,7 @@ describe('links', () => {
it('should return all links for user with all sub-feature privileges', async () => {
(calculateEndpointAuthz as jest.Mock).mockReturnValue(getEndpointAuthzInitialStateMock());

const filteredLinks = await getManagementFilteredLinks(coreMockStarted, getPlugins());
const filteredLinks = await getManagementFilteredLinks(getStartServices());

expect(filteredLinks).toEqual(links);
});
Expand All @@ -200,7 +201,7 @@ describe('links', () => {
})
);

const filteredLinks = await getManagementFilteredLinks(coreMockStarted, getPlugins());
const filteredLinks = await getManagementFilteredLinks(getStartServices());

expect(filteredLinks).toEqual(getLinksWithout(SecurityPageName.trustedApps));
});
Expand All @@ -212,7 +213,7 @@ describe('links', () => {
})
);

const filteredLinks = await getManagementFilteredLinks(coreMockStarted, getPlugins());
const filteredLinks = await getManagementFilteredLinks(getStartServices());

expect(filteredLinks).toEqual(getLinksWithout(SecurityPageName.eventFilters));
});
Expand All @@ -224,7 +225,7 @@ describe('links', () => {
})
);

const filteredLinks = await getManagementFilteredLinks(coreMockStarted, getPlugins());
const filteredLinks = await getManagementFilteredLinks(getStartServices());

expect(filteredLinks).toEqual(getLinksWithout(SecurityPageName.blocklist));
});
Expand All @@ -236,7 +237,7 @@ describe('links', () => {
})
);

const filteredLinks = await getManagementFilteredLinks(coreMockStarted, getPlugins());
const filteredLinks = await getManagementFilteredLinks(getStartServices());

expect(filteredLinks).toEqual(
getLinksWithout(SecurityPageName.policies, SecurityPageName.cloudDefendPolicies)
Expand All @@ -251,7 +252,7 @@ describe('links', () => {
canReadEndpointList: false,
})
);
const filteredLinks = await getManagementFilteredLinks(coreMockStarted, getPlugins());
const filteredLinks = await getManagementFilteredLinks(getStartServices());
expect(filteredLinks).toEqual(getLinksWithout(SecurityPageName.endpoints));
});
});
Expand Down
14 changes: 5 additions & 9 deletions x-pack/plugins/security_solution/public/management/links.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
* 2.0.
*/

import type { CoreStart } from '@kbn/core/public';
import { i18n } from '@kbn/i18n';
import type { StartServices } from '../types';

import { checkArtifactHasData } from './services/exceptions_list/check_artifact_has_data';
import {
Expand Down Expand Up @@ -41,7 +41,6 @@ import {
} from '../app/translations';
import { licenseService } from '../common/hooks/use_license';
import type { LinkItem } from '../common/links/types';
import type { StartPlugins } from '../types';
import { cloudDefendLink } from '../cloud_defend/links';
import { links as notesLink } from '../notes/links';
import { IconConsole } from '../common/icons/console';
Expand Down Expand Up @@ -226,12 +225,9 @@ const excludeLinks = (linkIds: SecurityPageName[]) => ({
links: links.links?.filter((link) => !linkIds.includes(link.id)),
});

export const getManagementFilteredLinks = async (
core: CoreStart,
plugins: StartPlugins
): Promise<LinkItem> => {
const fleetAuthz = plugins.fleet?.authz;
const currentUser = await plugins.security.authc.getCurrentUser();
export const getManagementFilteredLinks = async (services: StartServices): Promise<LinkItem> => {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed core: CoreStart, plugins: StartPlugins parameters by services: StartServices.

const fleetAuthz = services.fleet?.authz;
const currentUser = await services.security.authc.getCurrentUser();
const {
canReadActionsLogManagement,
canAccessHostIsolationExceptions,
Expand All @@ -251,7 +247,7 @@ export const getManagementFilteredLinks = async (
// read host isolation exceptions is not a paid feature, to allow deleting exceptions after a downgrade scenario.
// however, in this situation we allow to access only when there is data, otherwise the link won't be accessible.
(canReadHostIsolationExceptions &&
(await checkArtifactHasData(HostIsolationExceptionsApiClient.getInstance(core.http))));
(await checkArtifactHasData(HostIsolationExceptionsApiClient.getInstance(services.http))));

const linksToExclude: SecurityPageName[] = [];

Expand Down
17 changes: 13 additions & 4 deletions x-pack/plugins/security_solution/public/plugin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,15 @@ export class Plugin implements IPlugin<PluginSetup, PluginStart, SetupPlugins, S
const subPlugins = await this.startSubPlugins(this.storage, coreStart, startPlugins);
const store = await this.store(coreStart, startPlugins, subPlugins);

const services = await this.services.generateServices(coreStart, startPlugins, params);
const startBaseServices = await this.services.generateServices(coreStart, startPlugins);
const services: StartServices = {
...startBaseServices,
...(params && {
onAppLeave: params.onAppLeave,
setHeaderActionMenu: params.setHeaderActionMenu,
}),
};

return { renderApp, subPlugins, store, services };
};

Expand Down Expand Up @@ -213,7 +221,7 @@ export class Plugin implements IPlugin<PluginSetup, PluginStart, SetupPlugins, S
return this.contract.getSetupContract();
}

public start(core: CoreStart, plugins: StartPlugins): PluginStart {
public start(core: CoreStart, plugins: StartPluginsDependencies): PluginStart {
this.services.start(core, plugins);
this.registerFleetExtensions(core, plugins);
this.registerPluginUpdates(core, plugins); // Not awaiting to prevent blocking start execution
Expand Down Expand Up @@ -430,7 +438,7 @@ export class Plugin implements IPlugin<PluginSetup, PluginStart, SetupPlugins, S
/**
* Registers the plugin updates including status, visibleIn, and deepLinks via the plugin updater$.
*/
private async registerPluginUpdates(core: CoreStart, plugins: StartPlugins) {
private async registerPluginUpdates(core: CoreStart, plugins: StartPluginsDependencies) {
const { license$ } = plugins.licensing;
const { capabilities } = core.application;
const { upsellingService, isSolutionNavigationEnabled$ } = this.contract;
Expand Down Expand Up @@ -471,7 +479,8 @@ export class Plugin implements IPlugin<PluginSetup, PluginStart, SetupPlugins, S
updateAppLinks(links, linksPermissions);
});

const filteredLinks = await getFilteredLinks(core, plugins);
const services = await this.services.generateServices(core, plugins);
const filteredLinks = await getFilteredLinks(services);
appLinksToUpdate$.next(filteredLinks);
}

Expand Down
Loading