[Cloud Security] Serverless PLI auth block using UI extension infra (#184665)

This commit is contained in:
Jordan 2024-06-17 16:46:16 +03:00 committed by GitHub
parent 96024b8d40
commit 6fc0663d2a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
34 changed files with 505 additions and 220 deletions

View file

@ -442,7 +442,8 @@ enabled:
- x-pack/test_serverless/functional/test_suites/search/common_configs/config.group6.ts
- x-pack/test_serverless/functional/test_suites/security/config.ts
- x-pack/test_serverless/functional/test_suites/security/config.examples.ts
- x-pack/test_serverless/functional/test_suites/security/config.cloud_security_posture.ts
- x-pack/test_serverless/functional/test_suites/security/config.cloud_security_posture.basic.ts
- x-pack/test_serverless/functional/test_suites/security/config.cloud_security_posture.essentials.ts
- x-pack/test_serverless/functional/test_suites/security/config.saved_objects_management.ts
- x-pack/test_serverless/functional/test_suites/security/common_configs/config.group1.ts
- x-pack/test_serverless/functional/test_suites/security/common_configs/config.group2.ts

3
.github/CODEOWNERS vendored
View file

@ -1651,7 +1651,8 @@ x-pack/test/security_solution_api_integration/test_suites/genai @elastic/securit
/x-pack/test/cloud_security_posture_functional/ @elastic/kibana-cloud-security-posture
/x-pack/test/cloud_security_posture_api/ @elastic/kibana-cloud-security-posture
/x-pack/test_serverless/functional/test_suites/security/ftr/cloud_security_posture/ @elastic/kibana-cloud-security-posture
/x-pack/test_serverless/functional/test_suites/security/config.cloud_security_posture.ts @elastic/kibana-cloud-security-posture
/x-pack/test_serverless/functional/test_suites/security/config.cloud_security_posture.basic.ts @elastic/kibana-cloud-security-posture
/x-pack/test_serverless/functional/test_suites/security/config.cloud_security_posture.essentials.ts @elastic/kibana-cloud-security-posture
/x-pack/test_serverless/api_integration/test_suites/security/cloud_security_posture/ @elastic/kibana-cloud-security-posture
/x-pack/plugins/fleet/public/components/cloud_security_posture @elastic/fleet @elastic/kibana-cloud-security-posture
/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/components/cloud_security_posture @elastic/fleet @elastic/kibana-cloud-security-posture

View file

@ -67,6 +67,11 @@ export enum ProductFeatureSecurityKey {
* enables all rule actions
*/
externalRuleActions = 'external_rule_actions',
/**
* enables Cloud Security Posture - CSPM, KSPM, CNVM
*/
cloudSecurityPosture = 'cloud_security_posture',
}
export enum ProductFeatureCasesKey {

View file

@ -121,4 +121,5 @@ export const securityDefaultProductFeaturesConfig: DefaultSecurityProductFeature
[ProductFeatureSecurityKey.endpointProtectionUpdates]: {},
[ProductFeatureSecurityKey.endpointAgentTamperProtection]: {},
[ProductFeatureSecurityKey.externalRuleActions]: {},
[ProductFeatureSecurityKey.cloudSecurityPosture]: {},
};

View file

@ -17,6 +17,7 @@ export type UpsellingSectionId =
| 'osquery_automated_response_actions'
| 'endpoint_protection_updates'
| 'endpoint_agent_tamper_protection'
| 'cloud_security_posture_integration_installation'
| 'ruleDetailsEndpointExceptions';
export type UpsellingMessageId =

View file

@ -5,7 +5,7 @@
* 2.0.
*/
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import React, { useCallback, useEffect, useMemo, useState, Suspense } from 'react';
import { useRouteMatch } from 'react-router-dom';
import styled from 'styled-components';
import { i18n } from '@kbn/i18n';
@ -321,6 +321,20 @@ export const CreatePackagePolicySinglePage: CreatePackagePolicyParams = ({
'package-policy-replace-define-step'
);
// PLI auth blocks are registered to UI Extension context and are used to display upselling components.
// Upselling components are rendered conditionally based on their availability from the PLI upselling service.
const pliAuthBlockView = useUIExtension(packageInfo?.name ?? '', 'pli-auth-block');
// If an auth block view is registered to the UI Extension context, we expect the registered component to return a React component when the PLI is not sufficient,
// or simply a wrapper returning the children if the PLI is sufficient.
const PliAuthBlockWrapper: React.FC = useMemo(
() =>
pliAuthBlockView?.Component && !isPackageInfoLoading
? pliAuthBlockView.Component
: ({ children }) => <>{children}</>, // when no UI Extension is registered, render children
[isPackageInfoLoading, pliAuthBlockView?.Component]
);
if (replaceDefineStepView && extensionView) {
throw new Error(
"'package-policy-create' and 'package-policy-replace-define-step' cannot both be registered as UI extensions"
@ -455,172 +469,182 @@ export const CreatePackagePolicySinglePage: CreatePackagePolicyParams = ({
return (
<CreatePackagePolicySinglePageLayout {...layoutProps} data-test-subj="createPackagePolicy">
<EuiErrorBoundary>
{formState === 'CONFIRM' && agentPolicies.length > 0 && (
<ConfirmDeployAgentPolicyModal
agentCount={agentCount}
agentPolicies={agentPolicies}
onConfirm={onSubmit}
onCancel={() => setFormState('VALID')}
showUnprivilegedAgentsCallout={Boolean(
packageInfo && isRootPrivilegesRequired(packageInfo) && unprivilegedAgentsCount > 0
)}
unprivilegedAgentsCount={unprivilegedAgentsCount}
dataStreams={rootPrivilegedDataStreams}
/>
)}
{formState === 'SUBMITTED_NO_AGENTS' &&
agentPolicies.length > 0 &&
packageInfo &&
savedPackagePolicy && (
<PostInstallAddAgentModal
packageInfo={packageInfo}
onConfirm={() => navigateAddAgent(savedPackagePolicy)}
onCancel={() => navigateAddAgentHelp(savedPackagePolicy)}
/>
)}
{formState === 'SUBMITTED_AZURE_ARM_TEMPLATE' &&
agentPolicies.length > 0 &&
savedPackagePolicy && (
<PostInstallAzureArmTemplateModal
agentPolicy={agentPolicies[0]}
packagePolicy={savedPackagePolicy}
onConfirm={() => navigateAddAgent(savedPackagePolicy)}
onCancel={() => navigateAddAgentHelp(savedPackagePolicy)}
/>
)}
{formState === 'SUBMITTED_CLOUD_FORMATION' &&
agentPolicies.length > 0 &&
savedPackagePolicy && (
<PostInstallCloudFormationModal
agentPolicy={agentPolicies[0]}
packagePolicy={savedPackagePolicy}
onConfirm={() => navigateAddAgent(savedPackagePolicy)}
onCancel={() => navigateAddAgentHelp(savedPackagePolicy)}
/>
)}
{formState === 'SUBMITTED_GOOGLE_CLOUD_SHELL' &&
agentPolicies.length > 0 &&
savedPackagePolicy && (
<PostInstallGoogleCloudShellModal
agentPolicy={agentPolicies[0]}
packagePolicy={savedPackagePolicy}
onConfirm={() => navigateAddAgent(savedPackagePolicy)}
onCancel={() => navigateAddAgentHelp(savedPackagePolicy)}
/>
)}
{packageInfo && (
<IntegrationBreadcrumb
pkgTitle={integrationInfo?.title || packageInfo.title}
pkgkey={pkgKeyFromPackageInfo(packageInfo)}
integration={integrationInfo?.name}
/>
)}
{packageInfo && isRootPrivilegesRequired(packageInfo) ? (
<>
<RootPrivilegesCallout dataStreams={rootPrivilegedDataStreams} />
<EuiSpacer size="m" />
</>
) : null}
{numTransformAssets > 0 ? (
<>
<TransformInstallWithCurrentUserPermissionCallout count={numTransformAssets} />
<EuiSpacer size="xl" />
</>
) : null}
{showSecretsDisabledCallout && (
<>
<EuiCallOut
size="m"
color="warning"
title={
<FormattedMessage
id="xpack.fleet.createPackagePolicy.secretsDisabledCalloutTitle"
defaultMessage="Policy secrets are disabled"
/>
}
>
<FormattedMessage
id="xpack.fleet.createPackagePolicy.secretsDisabledCalloutDescription"
defaultMessage="This integration contains {policySecretsLink}, but you have a Fleet Server running on a version earlier than {minimumSecretsVersion}. Please upgrade your Fleet Server to enable policy secrets for all integrations."
values={{
policySecretsLink: (
<EuiLink href={docLinks.links.fleet.policySecrets} target="_blank">
<FormattedMessage
id="xpack.fleet.createPackagePolicy.secretsDisabledCalloutDocsLink"
defaultMessage="policy secrets"
/>
</EuiLink>
),
minimumSecretsVersion: <EuiCode>{SECRETS_MINIMUM_FLEET_SERVER_VERSION}</EuiCode>,
}}
<Suspense fallback={<Loading />}>
<PliAuthBlockWrapper>
<EuiErrorBoundary>
{formState === 'CONFIRM' && agentPolicies.length > 0 && (
<ConfirmDeployAgentPolicyModal
agentCount={agentCount}
agentPolicies={agentPolicies}
onConfirm={onSubmit}
onCancel={() => setFormState('VALID')}
showUnprivilegedAgentsCallout={Boolean(
packageInfo &&
isRootPrivilegesRequired(packageInfo) &&
unprivilegedAgentsCount > 0
)}
unprivilegedAgentsCount={unprivilegedAgentsCount}
dataStreams={rootPrivilegedDataStreams}
/>
</EuiCallOut>
<EuiSpacer size="m" />
</>
)}
<StepsWithLessPadding steps={steps} />
<EuiSpacer size="xl" />
<EuiSpacer size="xl" />
<CustomEuiBottomBar data-test-subj="integrationsBottomBar">
<EuiFlexGroup justifyContent="spaceBetween" alignItems="center">
<EuiFlexItem grow={false}>
{packageInfo && (formState === 'INVALID' || hasAgentPolicyError) ? (
<FormattedMessage
id="xpack.fleet.createPackagePolicy.errorOnSaveText"
defaultMessage="Your integration policy has errors. Please fix them before saving."
)}
{formState === 'SUBMITTED_NO_AGENTS' &&
agentPolicies.length > 0 &&
packageInfo &&
savedPackagePolicy && (
<PostInstallAddAgentModal
packageInfo={packageInfo}
onConfirm={() => navigateAddAgent(savedPackagePolicy)}
onCancel={() => navigateAddAgentHelp(savedPackagePolicy)}
/>
) : null}
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiFlexGroup gutterSize="s" justifyContent="flexEnd">
<EuiFlexItem grow={false}>
{/* eslint-disable-next-line @elastic/eui/href-or-on-click */}
<EuiButtonEmpty
color="text"
href={cancelUrl}
onClick={cancelClickHandler}
data-test-subj="createPackagePolicyCancelButton"
>
)}
{formState === 'SUBMITTED_AZURE_ARM_TEMPLATE' &&
agentPolicies.length > 0 &&
savedPackagePolicy && (
<PostInstallAzureArmTemplateModal
agentPolicy={agentPolicies[0]}
packagePolicy={savedPackagePolicy}
onConfirm={() => navigateAddAgent(savedPackagePolicy)}
onCancel={() => navigateAddAgentHelp(savedPackagePolicy)}
/>
)}
{formState === 'SUBMITTED_CLOUD_FORMATION' &&
agentPolicies.length > 0 &&
savedPackagePolicy && (
<PostInstallCloudFormationModal
agentPolicy={agentPolicies[0]}
packagePolicy={savedPackagePolicy}
onConfirm={() => navigateAddAgent(savedPackagePolicy)}
onCancel={() => navigateAddAgentHelp(savedPackagePolicy)}
/>
)}
{formState === 'SUBMITTED_GOOGLE_CLOUD_SHELL' &&
agentPolicies.length > 0 &&
savedPackagePolicy && (
<PostInstallGoogleCloudShellModal
agentPolicy={agentPolicies[0]}
packagePolicy={savedPackagePolicy}
onConfirm={() => navigateAddAgent(savedPackagePolicy)}
onCancel={() => navigateAddAgentHelp(savedPackagePolicy)}
/>
)}
{packageInfo && (
<IntegrationBreadcrumb
pkgTitle={integrationInfo?.title || packageInfo.title}
pkgkey={pkgKeyFromPackageInfo(packageInfo)}
integration={integrationInfo?.name}
/>
)}
{packageInfo && isRootPrivilegesRequired(packageInfo) ? (
<>
<RootPrivilegesCallout dataStreams={rootPrivilegedDataStreams} />
<EuiSpacer size="m" />
</>
) : null}
{numTransformAssets > 0 ? (
<>
<TransformInstallWithCurrentUserPermissionCallout count={numTransformAssets} />
<EuiSpacer size="xl" />
</>
) : null}
{showSecretsDisabledCallout && (
<>
<EuiCallOut
size="m"
color="warning"
title={
<FormattedMessage
id="xpack.fleet.createPackagePolicy.cancelButton"
defaultMessage="Cancel"
id="xpack.fleet.createPackagePolicy.secretsDisabledCalloutTitle"
defaultMessage="Policy secrets are disabled"
/>
</EuiButtonEmpty>
}
>
<FormattedMessage
id="xpack.fleet.createPackagePolicy.secretsDisabledCalloutDescription"
defaultMessage="This integration contains {policySecretsLink}, but you have a Fleet Server running on a version earlier than {minimumSecretsVersion}. Please upgrade your Fleet Server to enable policy secrets for all integrations."
values={{
policySecretsLink: (
<EuiLink href={docLinks.links.fleet.policySecrets} target="_blank">
<FormattedMessage
id="xpack.fleet.createPackagePolicy.secretsDisabledCalloutDocsLink"
defaultMessage="policy secrets"
/>
</EuiLink>
),
minimumSecretsVersion: (
<EuiCode>{SECRETS_MINIMUM_FLEET_SERVER_VERSION}</EuiCode>
),
}}
/>
</EuiCallOut>
<EuiSpacer size="m" />
</>
)}
<StepsWithLessPadding steps={steps} />
<EuiSpacer size="xl" />
<EuiSpacer size="xl" />
<CustomEuiBottomBar data-test-subj="integrationsBottomBar">
<EuiFlexGroup justifyContent="spaceBetween" alignItems="center">
<EuiFlexItem grow={false}>
{packageInfo && (formState === 'INVALID' || hasAgentPolicyError) ? (
<FormattedMessage
id="xpack.fleet.createPackagePolicy.errorOnSaveText"
defaultMessage="Your integration policy has errors. Please fix them before saving."
/>
) : null}
</EuiFlexItem>
{showDevtoolsRequest ? (
<EuiFlexItem grow={false}>
<DevtoolsRequestFlyoutButton
request={devtoolRequest}
description={devtoolRequestDescription}
btnProps={{
color: 'text',
}}
/>
</EuiFlexItem>
) : null}
<EuiFlexItem grow={false}>
<EuiButton
onClick={() => onSubmit()}
isLoading={formState === 'LOADING'}
disabled={formState !== 'VALID' || hasAgentPolicyError || !validationResults}
iconType="save"
color="primary"
fill
data-test-subj="createPackagePolicySaveButton"
>
<FormattedMessage
id="xpack.fleet.createPackagePolicy.saveButton"
defaultMessage="Save and continue"
/>
</EuiButton>
<EuiFlexGroup gutterSize="s" justifyContent="flexEnd">
<EuiFlexItem grow={false}>
{/* eslint-disable-next-line @elastic/eui/href-or-on-click */}
<EuiButtonEmpty
color="text"
href={cancelUrl}
onClick={cancelClickHandler}
data-test-subj="createPackagePolicyCancelButton"
>
<FormattedMessage
id="xpack.fleet.createPackagePolicy.cancelButton"
defaultMessage="Cancel"
/>
</EuiButtonEmpty>
</EuiFlexItem>
{showDevtoolsRequest ? (
<EuiFlexItem grow={false}>
<DevtoolsRequestFlyoutButton
request={devtoolRequest}
description={devtoolRequestDescription}
btnProps={{
color: 'text',
}}
/>
</EuiFlexItem>
) : null}
<EuiFlexItem grow={false}>
<EuiButton
onClick={() => onSubmit()}
isLoading={formState === 'LOADING'}
disabled={
formState !== 'VALID' || hasAgentPolicyError || !validationResults
}
iconType="save"
color="primary"
fill
data-test-subj="createPackagePolicySaveButton"
>
<FormattedMessage
id="xpack.fleet.createPackagePolicy.saveButton"
defaultMessage="Save and continue"
/>
</EuiButton>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
</EuiFlexGroup>
</CustomEuiBottomBar>
</EuiErrorBoundary>
</CustomEuiBottomBar>
</EuiErrorBoundary>
</PliAuthBlockWrapper>
</Suspense>
</CreatePackagePolicySinglePageLayout>
);
};

View file

@ -119,6 +119,12 @@ export interface EndpointAgentTamperProtectionExtension {
Component: LazyExoticComponent<ComponentType>;
}
export interface PliAuthBlockExtension {
package: string;
view: 'pli-auth-block';
Component: LazyExoticComponent<ComponentType>;
}
export interface PackageGenericErrorsListExtension {
package: string;
view: 'package-generic-errors-list';
@ -226,4 +232,5 @@ export type UIExtensionPoint =
| PackageGenericErrorsListExtension
| AgentEnrollmentFlyoutFinalStepExtension
| PackagePolicyCreateMultiStepExtension
| EndpointAgentTamperProtectionExtension;
| EndpointAgentTamperProtectionExtension
| PliAuthBlockExtension;

View file

@ -0,0 +1,22 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import type { PropsWithChildren } from 'react';
import React, { memo } from 'react';
import { useUpsellingComponent } from '../common/hooks/use_upselling';
export const CloudSecurityPosturePliAuthBlockExtension = memo<PropsWithChildren<unknown>>(
({ children }) => {
const Component = useUpsellingComponent('cloud_security_posture_integration_installation');
if (!Component) {
return <>{children}</>;
}
return <Component />;
}
);
CloudSecurityPosturePliAuthBlockExtension.displayName = 'CloudSecurityPosturePliAuthBlockExtension';

View file

@ -0,0 +1,30 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { lazy } from 'react';
import type { FleetUiExtensionGetterOptions } from '../common/types';
export const getLazyCloudSecurityPosturePliAuthBlockExtension = ({
coreStart,
depsStart,
services,
}: FleetUiExtensionGetterOptions) =>
lazy(async () => {
const [{ withSecurityContext }, { CloudSecurityPosturePliAuthBlockExtension }] =
await Promise.all([
import('../common/components/with_security_context/with_security_context'),
import('./cloud_security_posture_pli_auth_block_extension'),
]);
return {
default: withSecurityContext({
coreStart,
depsStart,
services,
WrappedComponent: CloudSecurityPosturePliAuthBlockExtension,
}),
};
});

View file

@ -11,14 +11,14 @@ import { Provider as ReduxStoreProvider } from 'react-redux';
import type { Store } from 'redux';
import { NavigationProvider } from '@kbn/security-solution-navigation';
import type { UpsellingService } from '@kbn/security-solution-upselling/service';
import { UpsellingProvider } from '../../../../../../../common/components/upselling_provider';
import { UserPrivilegesProvider } from '../../../../../../../common/components/user_privileges/user_privileges_context';
import type { SecuritySolutionQueryClient } from '../../../../../../../common/containers/query_client/query_client_provider';
import { ReactQueryClientProvider } from '../../../../../../../common/containers/query_client/query_client_provider';
import { SecuritySolutionStartDependenciesContext } from '../../../../../../../common/components/user_privileges/endpoint/security_solution_start_dependencies';
import { CurrentLicense } from '../../../../../../../common/components/current_license';
import type { StartPlugins } from '../../../../../../../types';
import { useKibana } from '../../../../../../../common/lib/kibana';
import { UpsellingProvider } from '../upselling_provider';
import { UserPrivilegesProvider } from '../user_privileges/user_privileges_context';
import type { SecuritySolutionQueryClient } from '../../containers/query_client/query_client_provider';
import { ReactQueryClientProvider } from '../../containers/query_client/query_client_provider';
import { SecuritySolutionStartDependenciesContext } from '../user_privileges/endpoint/security_solution_start_dependencies';
import { CurrentLicense } from '../current_license';
import type { StartPlugins } from '../../../types';
import { useKibana } from '../../lib/kibana';
export type RenderContextProvidersProps = PropsWithChildren<{
store: Store;

View file

@ -8,14 +8,14 @@
import type { Dispatch, Middleware, PreloadedState, ReducersMapObject } from 'redux';
import { applyMiddleware, combineReducers, compose, createStore } from 'redux';
import type { CoreStart } from '@kbn/core/public';
import { managementReducer } from '../../../../../../store/reducer';
import { appReducer } from '../../../../../../../common/store/app';
import { ExperimentalFeaturesService } from '../../../../../../../common/experimental_features_service';
import { managementMiddlewareFactory } from '../../../../../../store/middleware';
import type { StartPlugins } from '../../../../../../../types';
import type { State } from '../../../../../../../common/store';
import type { AppAction } from '../../../../../../../common/store/actions';
import type { Immutable } from '../../../../../../../../common/endpoint/types';
import { managementReducer } from '../../../management/store/reducer';
import { appReducer } from '../../store/app';
import { ExperimentalFeaturesService } from '../../experimental_features_service';
import { managementMiddlewareFactory } from '../../../management/store/middleware';
import type { StartPlugins } from '../../../types';
import type { State } from '../../store';
import type { AppAction } from '../../store/actions';
import type { Immutable } from '../../../../common/endpoint/types';
type ComposeType = typeof compose;
declare global {

View file

@ -8,6 +8,9 @@
import type { ResponseErrorAttributes } from '@kbn/core/server';
import type { DataViewBase } from '@kbn/es-query';
import type { FieldSpec } from '@kbn/data-views-plugin/common';
import type { CoreStart } from '@kbn/core-lifecycle-browser';
import type { UpsellingService } from '@kbn/security-solution-upselling/service';
import type { StartPlugins } from '../types';
export interface ServerApiError {
statusCode: number;
@ -32,3 +35,11 @@ export interface SecuritySolutionDataViewBase extends DataViewBase {
export type AlertWorkflowStatus = 'open' | 'closed' | 'acknowledged';
export type Refetch = () => void;
export interface FleetUiExtensionGetterOptions {
coreStart: CoreStart;
depsStart: Pick<StartPlugins, 'data' | 'fleet'>;
services: {
upsellingService: UpsellingService;
};
}

View file

@ -6,7 +6,7 @@
*/
import { lazy } from 'react';
import type { FleetUiExtensionGetterOptions } from './types';
import type { FleetUiExtensionGetterOptions } from '../../../../../common/types';
export const getLazyEndpointAgentTamperProtectionExtension = ({
coreStart,
@ -16,7 +16,7 @@ export const getLazyEndpointAgentTamperProtectionExtension = ({
lazy(async () => {
const [{ withSecurityContext }, { EndpointAgentTamperProtectionExtension }] = await Promise.all(
[
import('./components/with_security_context/with_security_context'),
import('../../../../../common/components/with_security_context/with_security_context'),
import('./endpoint_agent_tamper_protection_extension'),
]
);

View file

@ -10,7 +10,7 @@ import type {
PackageGenericErrorsListComponent,
PackageGenericErrorsListProps,
} from '@kbn/fleet-plugin/public';
import type { FleetUiExtensionGetterOptions } from './types';
import type { FleetUiExtensionGetterOptions } from '../../../../../common/types';
export const getLazyEndpointGenericErrorsListExtension = ({
coreStart,
@ -19,7 +19,7 @@ export const getLazyEndpointGenericErrorsListExtension = ({
}: FleetUiExtensionGetterOptions) => {
return lazy<PackageGenericErrorsListComponent>(async () => {
const [{ withSecurityContext }, { EndpointGenericErrorsList }] = await Promise.all([
import('./components/with_security_context/with_security_context'),
import('../../../../../common/components/with_security_context/with_security_context'),
import('./endpoint_generic_errors_list'),
]);

View file

@ -7,7 +7,7 @@
import { lazy } from 'react';
import type { PackageCustomExtensionComponent } from '@kbn/fleet-plugin/public';
import type { FleetUiExtensionGetterOptions } from './types';
import type { FleetUiExtensionGetterOptions } from '../../../../../common/types';
export const getLazyEndpointPackageCustomExtension = ({
coreStart,
@ -16,7 +16,7 @@ export const getLazyEndpointPackageCustomExtension = ({
}: FleetUiExtensionGetterOptions) => {
return lazy<PackageCustomExtensionComponent>(async () => {
const [{ withSecurityContext }, { EndpointPackageCustomExtension }] = await Promise.all([
import('./components/with_security_context/with_security_context'),
import('../../../../../common/components/with_security_context/with_security_context'),
import('./endpoint_package_custom_extension'),
]);
return {

View file

@ -7,7 +7,7 @@
import { lazy } from 'react';
import type { PackagePolicyCreateExtensionComponent } from '@kbn/fleet-plugin/public';
import type { FleetUiExtensionGetterOptions } from './types';
import type { FleetUiExtensionGetterOptions } from '../../../../../common/types';
export const getLazyEndpointPolicyCreateExtension = ({
coreStart,
@ -16,7 +16,7 @@ export const getLazyEndpointPolicyCreateExtension = ({
}: FleetUiExtensionGetterOptions) => {
return lazy<PackagePolicyCreateExtensionComponent>(async () => {
const [{ withSecurityContext }, { EndpointPolicyCreateExtension }] = await Promise.all([
import('./components/with_security_context/with_security_context'),
import('../../../../../common/components/with_security_context/with_security_context'),
import('./endpoint_policy_create_extension'),
]);

View file

@ -10,7 +10,7 @@ import type {
PackagePolicyEditExtensionComponent,
PackagePolicyEditExtensionComponentProps,
} from '@kbn/fleet-plugin/public';
import type { FleetUiExtensionGetterOptions } from './types';
import type { FleetUiExtensionGetterOptions } from '../../../../../common/types';
export const getLazyEndpointPolicyEditExtension = ({
coreStart,
@ -19,7 +19,7 @@ export const getLazyEndpointPolicyEditExtension = ({
}: FleetUiExtensionGetterOptions) => {
return lazy<PackagePolicyEditExtensionComponent>(async () => {
const [{ withSecurityContext }, { EndpointPolicyEditExtension }] = await Promise.all([
import('./components/with_security_context/with_security_context'),
import('../../../../../common/components/with_security_context/with_security_context'),
import('./endpoint_policy_edit_extension/endpoint_policy_edit_extension'),
]);

View file

@ -10,7 +10,7 @@ import type {
PackagePolicyResponseExtensionComponent,
PackagePolicyResponseExtensionComponentProps,
} from '@kbn/fleet-plugin/public';
import type { FleetUiExtensionGetterOptions } from './types';
import type { FleetUiExtensionGetterOptions } from '../../../../../common/types';
export const getLazyEndpointPolicyResponseExtension = ({
coreStart,
@ -19,7 +19,7 @@ export const getLazyEndpointPolicyResponseExtension = ({
}: FleetUiExtensionGetterOptions) => {
return lazy<PackagePolicyResponseExtensionComponent>(async () => {
const [{ withSecurityContext }, { EndpointPolicyResponseExtension }] = await Promise.all([
import('./components/with_security_context/with_security_context'),
import('../../../../../common/components/with_security_context/with_security_context'),
import('./endpoint_policy_response_extension'),
]);

View file

@ -16,9 +16,9 @@ import { EuiThemeProvider } from '@kbn/kibana-react-plugin/common';
import { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render';
import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public';
import { deepFreeze } from '@kbn/std';
import { createFleetContextReduxStore } from '../../../../../common/components/with_security_context/store';
import type { AppContextTestRender, UiRender } from '../../../../../common/mock/endpoint';
import { createAppRootMockRenderer } from '../../../../../common/mock/endpoint';
import { createFleetContextReduxStore } from './components/with_security_context/store';
import type { ExperimentalFeatures } from '../../../../../../common/experimental_features';
import { allowedExperimentalValues } from '../../../../../../common/experimental_features';
import type { State } from '../../../../../common/store';
@ -26,7 +26,7 @@ import { mockGlobalState } from '../../../../../common/mock';
import { managementReducer } from '../../../../store/reducer';
import { appReducer } from '../../../../../common/store/app';
import { ExperimentalFeaturesService } from '../../../../../common/experimental_features_service';
import { RenderContextProviders } from './components/with_security_context/render_context_providers';
import { RenderContextProviders } from '../../../../../common/components/with_security_context/render_context_providers';
import type { AppAction } from '../../../../../common/store/actions';
// Defined a private custom reducer that reacts to an action that enables us to update the

View file

@ -1,18 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import type { CoreStart } from '@kbn/core-lifecycle-browser';
import type { UpsellingService } from '@kbn/security-solution-upselling/service';
import type { StartPlugins } from '../../../../../types';
export interface FleetUiExtensionGetterOptions {
coreStart: CoreStart;
depsStart: Pick<StartPlugins, 'data' | 'fleet'>;
services: {
upsellingService: UpsellingService;
};
}

View file

@ -20,8 +20,8 @@ import type {
import { DEFAULT_APP_CATEGORIES } from '@kbn/core/public';
import { Storage } from '@kbn/kibana-utils-plugin/public';
import type { TriggersAndActionsUIPublicPluginSetup } from '@kbn/triggers-actions-ui-plugin/public';
import { getLazyCloudSecurityPosturePliAuthBlockExtension } from './cloud_security_posture/lazy_cloud_security_posture_pli_auth_block_extension';
import { getLazyEndpointAgentTamperProtectionExtension } from './management/pages/policy/view/ingest_manager_integration/lazy_endpoint_agent_tamper_protection_extension';
import type { FleetUiExtensionGetterOptions } from './management/pages/policy/view/ingest_manager_integration/types';
import type {
PluginSetup,
PluginStart,
@ -39,7 +39,7 @@ import { APP_ID, APP_UI_ID, APP_PATH, APP_ICON_SOLUTION } from '../common/consta
import type { AppLinkItems } from './common/links';
import { updateAppLinks, type LinksPermissions } from './common/links';
import { registerDeepLinksUpdater } from './common/links/deep_links';
import type { SecuritySolutionUiConfigType } from './common/types';
import type { FleetUiExtensionGetterOptions, SecuritySolutionUiConfigType } from './common/types';
import { getLazyEndpointPolicyEditExtension } from './management/pages/policy/view/ingest_manager_integration/lazy_endpoint_policy_edit_extension';
import { getLazyEndpointPolicyCreateExtension } from './management/pages/policy/view/ingest_manager_integration/lazy_endpoint_policy_create_extension';
@ -263,6 +263,12 @@ export class Plugin implements IPlugin<PluginSetup, PluginStart, SetupPlugins, S
Component: getLazyEndpointAgentTamperProtectionExtension(registerOptions),
});
registerExtension({
package: 'cloud_security_posture',
view: 'pli-auth-block',
Component: getLazyCloudSecurityPosturePliAuthBlockExtension(registerOptions),
});
registerExtension({
package: 'cribl',
view: 'package-policy-replace-define-step',

View file

@ -44,7 +44,7 @@ export const PLI_PRODUCT_FEATURES: PliProductFeatures = {
],
},
cloud: {
essentials: [],
essentials: [ProductFeatureKey.cloudSecurityPosture],
complete: [],
},
} as const;

View file

@ -20,6 +20,13 @@ import type {
UpsellingSectionId,
} from '@kbn/security-solution-upselling/service/types';
import React from 'react';
import { CloudSecurityPostureIntegrationPliBlockLazy } from './sections/cloud_security_posture';
import {
EndpointAgentTamperProtectionLazy,
EndpointPolicyProtectionsLazy,
EndpointProtectionUpdatesLazy,
RuleDetailsEndpointExceptionsLazy,
} from './sections/endpoint_management';
import type { SecurityProductTypes } from '../../common/config';
import { getProductProductFeatures } from '../../common/pli/pli_features';
import type { Services } from '../common/services';
@ -32,12 +39,6 @@ import {
OsqueryResponseActionsUpsellingSectionLazy,
ThreatIntelligencePaywallLazy,
} from './lazy_upselling';
import {
EndpointAgentTamperProtectionLazy,
EndpointPolicyProtectionsLazy,
EndpointProtectionUpdatesLazy,
RuleDetailsEndpointExceptionsLazy,
} from './sections/endpoint_management';
import * as i18n from './translations';
interface UpsellingsConfig {
@ -160,6 +161,11 @@ export const upsellingSections: UpsellingSections = [
pli: ProductFeatureKey.endpointProtectionUpdates,
component: EndpointProtectionUpdatesLazy,
},
{
id: 'cloud_security_posture_integration_installation',
pli: ProductFeatureKey.cloudSecurityPosture,
component: CloudSecurityPostureIntegrationPliBlockLazy,
},
{
id: 'entity_analytics_panel',
pli: ProductFeatureKey.advancedInsights,

View file

@ -0,0 +1,64 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import React, { memo } from 'react';
import { EuiCard, EuiIcon, EuiSpacer } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
/**
* Component displayed when a given product tier is not allowed to use Cloud Security Posture Integrations installation forms.
*/
export const CloudSecurityPostureIntegrationPliBlock = memo(() => {
// TODO: prefer to use getProductTypeByPLI(ProductFeatureKey.cloudSecurityPosture) after we change returned text to include "Protection"
const requiredPLI = 'Cloud Protection Essentials';
return (
<>
<EuiSpacer size="s" />
<EuiCard
data-test-subj="cloud-security-posture-integration-pli-auth-block"
isDisabled={true}
description={false}
icon={<EuiIcon size="xl" type="lock" />}
betaBadgeProps={{
label: i18n.translate(
'xpack.securitySolutionServerless.cloudSecurityPostureIntegrationPliBlock.badgeText',
{
defaultMessage: 'Cloud Protection Essentials',
}
),
}}
title={
<h3>
<strong>
{i18n.translate(
'xpack.securitySolutionServerless.cloudSecurityPostureIntegrationPliBlock.cardTitle',
{
defaultMessage: 'Protection updates',
}
)}
</strong>
</h3>
}
>
<div>
{i18n.translate(
'xpack.securitySolutionServerless.cloudSecurityPostureIntegrationPliBlock.cardMessage',
{
defaultMessage:
'To turn on CSPM, KSPM or CNVM, view your Cloud Posture Dashboards and generate findings of misconfiguration or vulnerabilities in your cloud environment, you must add {requiredPLI} under Manage --> Project features.',
values: {
requiredPLI,
},
}
)}
</div>
</EuiCard>
</>
);
});
CloudSecurityPostureIntegrationPliBlock.displayName = 'CloudSecurityPostureIntegrationPliBlock';

View file

@ -0,0 +1,16 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { lazy } from 'react';
export const CloudSecurityPostureIntegrationPliBlockLazy = lazy(() =>
import('./cloud_security_posture_integration_pli_block').then(
({ CloudSecurityPostureIntegrationPliBlock }) => ({
default: CloudSecurityPostureIntegrationPliBlock,
})
)
);

View file

@ -201,6 +201,10 @@ export function AddCisIntegrationFormPageProvider({
return await testSubjects.find('confirmModalTitleText');
};
const checkIntegrationPliAuthBlockExists = async () => {
return await testSubjects.exists('cloud-security-posture-integration-pli-auth-block');
};
const fillInTextField = async (selector: string, text: string) => {
const textField = await testSubjects.find(selector);
await textField.type(text);
@ -282,5 +286,6 @@ export function AddCisIntegrationFormPageProvider({
selectValue,
getValueInEditPage,
isOptionChecked,
checkIntegrationPliAuthBlockExists,
};
}

View file

@ -16,7 +16,6 @@ import type { CreateTestConfigOptions } from '../shared/types';
export function createTestConfig(options: CreateTestConfigOptions) {
return async ({ readConfigFile }: FtrConfigProviderContext) => {
const svlSharedConfig = await readConfigFile(require.resolve('../shared/config.base.ts'));
return {
...svlSharedConfig.getAll(),
@ -110,6 +109,9 @@ export function createTestConfig(options: CreateTestConfigOptions) {
maintenanceWindows: {
pathname: '/app/management/insightsAndAlerting/maintenanceWindows',
},
fleet: {
pathname: '/app/fleet',
},
},
// choose where screenshots should be saved
screenshots: {

View file

@ -16,6 +16,8 @@ export default createTestConfig({
kbnServerArgs: [
`--xpack.fleet.packages.0.name=cloud_security_posture`,
`--xpack.fleet.packages.0.version=${CLOUD_SECURITY_PLUGIN_VERSION}`,
// configs the environment to run on the basic product tier, which may include PLI block components or messages
`--xpack.securitySolutionServerless.productTypes=${JSON.stringify([])}`,
],
// load tests in the index file
testFiles: [require.resolve('./ftr/cloud_security_posture')],

View file

@ -0,0 +1,26 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { createTestConfig } from '../../config.base';
export default createTestConfig({
serverlessProject: 'security',
junit: {
reportName: 'Serverless Security Cloud Security Functional Tests',
},
kbnServerArgs: [
`--xpack.fleet.packages.0.name=cloud_security_posture`,
`--xpack.fleet.packages.0.version=1.5.2`,
`--xpack.securitySolutionServerless.productTypes=${JSON.stringify([
{ product_line: 'security', product_tier: 'essentials' },
{ product_line: 'endpoint', product_tier: 'essentials' },
{ product_line: 'cloud', product_tier: 'essentials' },
])}`,
],
// we should only resolve files which are ending with `.essentials.ts`
testFiles: [require.resolve('./ftr/cloud_security_posture/csp_integrations_form.essentials.ts')],
});

View file

@ -0,0 +1,35 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import expect from '@kbn/expect';
import type { FtrProviderContext } from '../../../../ftr_provider_context';
export default function (providerContext: FtrProviderContext) {
const { getPageObjects } = providerContext;
const pageObjects = getPageObjects(['cisAddIntegration', 'header', 'svlCommonPage']);
describe('[Essentials PLI] Test Cloud Security Posture Integrations on Serverless', function () {
this.tags(['skipMKI']);
let cisIntegration: typeof pageObjects.cisAddIntegration;
before(async () => {
await pageObjects.svlCommonPage.login();
});
beforeEach(async () => {
cisIntegration = pageObjects.cisAddIntegration;
await cisIntegration.navigateToAddIntegrationCspmPage();
});
it('[Essentials PLI] Integration installation form should be available with Essentials or Complete PLI', async () => {
await pageObjects.header.waitUntilLoadingHasFinished();
const pliBlockExists = await cisIntegration.checkIntegrationPliAuthBlockExists();
expect(pliBlockExists).to.be(false);
});
});
}

View file

@ -0,0 +1,35 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import expect from '@kbn/expect';
import type { FtrProviderContext } from '../../../../ftr_provider_context';
export default function (providerContext: FtrProviderContext) {
const { getPageObjects } = providerContext;
const pageObjects = getPageObjects(['cisAddIntegration', 'header', 'svlCommonPage']);
describe('Test Cloud Security Posture Integrations on Serverless', function () {
this.tags(['skipMKI']);
let cisIntegration: typeof pageObjects.cisAddIntegration;
before(async () => {
await pageObjects.svlCommonPage.login();
});
beforeEach(async () => {
cisIntegration = pageObjects.cisAddIntegration;
await cisIntegration.navigateToAddIntegrationCspmPage();
});
it('Integration installation form should not be available without required PLI', async () => {
await pageObjects.header.waitUntilLoadingHasFinished();
const pliBlockExists = await cisIntegration.checkIntegrationPliAuthBlockExists();
expect(pliBlockExists).to.be(true);
});
});
}

View file

@ -10,6 +10,9 @@ import { FtrProviderContext } from '../../../../ftr_provider_context';
export default function ({ loadTestFile }: FtrProviderContext) {
describe('cloud_security_posture', function () {
this.tags(['cloud_security_posture']);
// do not resolve files which are ending with `.essentials.ts`
loadTestFile(require.resolve('./compliance_dashboard'));
loadTestFile(require.resolve('./csp_integrations_form'));
});
}