Skip to content

Commit

Permalink
[EDR Workflows][Serverless] Gate Protection Updates (#175129)
Browse files Browse the repository at this point in the history
With this pull request, we implement Protection Updates gating on the
Security Essentials tier. The changes include:

1. Addition of an upselling component on the Protection Updates tab.
2. Extension of the package policy create/update API callback to verify
the protection updates app feature before committing changes to
global_manifest_version.
3. Extension of the turn_off_policy_protections plugin callback to
inspect the protection updates app feature on server start. If no app
feature is present, it will roll down global_manifest_version to the
default of 'latest'.

![Screenshot 2024-01-18 at 15 50
32](https://github.com/elastic/kibana/assets/29123534/a018562f-e528-4f29-a070-57b3b20c949f)
  • Loading branch information
szwarckonrad authored Feb 1, 2024
1 parent 9872b70 commit 57374ab
Show file tree
Hide file tree
Showing 17 changed files with 570 additions and 26 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,11 @@ export enum AppFeatureSecurityKey {
*/
osqueryAutomatedResponseActions = 'osquery_automated_response_actions',

/**
* Enables Agent Tamper Protection
*/
endpointProtectionUpdates = 'endpoint_protection_updates',

/**
* Enables Agent Tamper Protection
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ export const securityDefaultAppFeaturesConfig: DefaultSecurityAppFeaturesConfig
},

[AppFeatureSecurityKey.osqueryAutomatedResponseActions]: {},
[AppFeatureSecurityKey.endpointProtectionUpdates]: {},
[AppFeatureSecurityKey.endpointAgentTamperProtection]: {},
[AppFeatureSecurityKey.externalRuleActions]: {},
};
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export type UpsellingSectionId =
| 'entity_analytics_panel'
| 'endpointPolicyProtections'
| 'osquery_automated_response_actions'
| 'endpoint_protection_updates'
| 'endpoint_agent_tamper_protection'
| 'ruleDetailsEndpointExceptions';

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/*
* 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 { IndexedFleetEndpointPolicyResponse } from '../../../../../../../common/endpoint/data_loaders/index_fleet_endpoint_policy';
import { login } from '../../../../tasks/login';
import { loadPage } from '../../../../tasks/common';
import { APP_POLICIES_PATH } from '../../../../../../../common/constants';

describe(
'When displaying the Policy Details in Endpoint Essentials PLI',
{
tags: ['@serverless'],
env: {
ftrConfig: {
productTypes: [
{ product_line: 'security', product_tier: 'essentials' },
{ product_line: 'endpoint', product_tier: 'complete' },
],
},
},
},
() => {
let loadedPolicyData: IndexedFleetEndpointPolicyResponse;
let policyId: string;

before(() => {
cy.task(
'indexFleetEndpointPolicy',
{ policyName: 'tests-serverless' },
{ timeout: 5 * 60 * 1000 }
).then((res) => {
const response = res as IndexedFleetEndpointPolicyResponse;
loadedPolicyData = response;
policyId = response.integrationPolicies[0].id;
});
});

after(() => {
if (loadedPolicyData) {
cy.task('deleteIndexedFleetEndpointPolicies', loadedPolicyData);
}
});

beforeEach(() => {
login();
});

it('should display upselling section for protection updates', () => {
loadPage(`${APP_POLICIES_PATH}/${policyId}/protectionUpdates`);
[
'endpointPolicy-protectionUpdatesLockedCard-title',
'endpointPolicy-protectionUpdatesLockedCard',
'endpointPolicy-protectionUpdatesLockedCard-badge',
].forEach((testSubj) => {
cy.getByTestSubj(testSubj).should('not.exist');
});
[
'protection-updates-warning-callout',
'protection-updates-automatic-updates-enabled',
'protection-updates-manifest-switch',
'protection-updates-manifest-name-title',
].forEach((testSubj) => {
cy.getByTestSubj(testSubj).should('exist').and('be.visible');
});
});
}
);
Original file line number Diff line number Diff line change
@@ -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 type { IndexedFleetEndpointPolicyResponse } from '../../../../../../../common/endpoint/data_loaders/index_fleet_endpoint_policy';
import { login } from '../../../../tasks/login';
import { loadPage } from '../../../../tasks/common';
import { APP_POLICIES_PATH } from '../../../../../../../common/constants';

describe(
'When displaying the Policy Details in Endpoint Essentials PLI',
{
tags: ['@serverless'],
env: {
ftrConfig: {
productTypes: [
{ product_line: 'security', product_tier: 'essentials' },
{ product_line: 'endpoint', product_tier: 'essentials' },
],
},
},
},
() => {
let loadedPolicyData: IndexedFleetEndpointPolicyResponse;
let policyId: string;

before(() => {
cy.task(
'indexFleetEndpointPolicy',
{ policyName: 'tests-serverless' },
{ timeout: 5 * 60 * 1000 }
).then((res) => {
const response = res as IndexedFleetEndpointPolicyResponse;
loadedPolicyData = response;
policyId = response.integrationPolicies[0].id;
});
});

after(() => {
if (loadedPolicyData) {
cy.task('deleteIndexedFleetEndpointPolicies', loadedPolicyData);
}
});

beforeEach(() => {
login();
});

it('should display upselling section for protection updates', () => {
loadPage(`${APP_POLICIES_PATH}/${policyId}/protectionUpdates`);
[
'endpointPolicy-protectionUpdatesLockedCard-title',
'endpointPolicy-protectionUpdatesLockedCard',
'endpointPolicy-protectionUpdatesLockedCard-badge',
].forEach((testSubj) => {
cy.getByTestSubj(testSubj, { timeout: 60000 }).should('exist').and('be.visible');
});
cy.getByTestSubj('protection-updates-layout').should('not.exist');
});
}
);
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@
* 2.0.
*/

import { loadPage } from '../../tasks/common';
import { login } from '../../tasks/login';
import { visitPolicyDetailsPage } from '../../screens/policy_details';
import type { IndexedFleetEndpointPolicyResponse } from '../../../../../common/endpoint/data_loaders/index_fleet_endpoint_policy';
import { APP_POLICIES_PATH } from '../../../../../common/constants';

describe(
'When displaying the Policy Details in Security Essentials PLI',
Expand All @@ -21,14 +23,17 @@ describe(
},
() => {
let loadedPolicyData: IndexedFleetEndpointPolicyResponse;
let policyId: string;

before(() => {
cy.task(
'indexFleetEndpointPolicy',
{ policyName: 'tests-serverless' },
{ timeout: 5 * 60 * 1000 }
).then((response) => {
loadedPolicyData = response as IndexedFleetEndpointPolicyResponse;
).then((res) => {
const response = res as IndexedFleetEndpointPolicyResponse;
loadedPolicyData = response;
policyId = response.integrationPolicies[0].id;
});
});

Expand All @@ -40,13 +45,24 @@ describe(

beforeEach(() => {
login();
visitPolicyDetailsPage(loadedPolicyData.integrationPolicies[0].id);
});

it('should display upselling section for protections', () => {
visitPolicyDetailsPage(policyId);
cy.getByTestSubj('endpointPolicy-protectionsLockedCard', { timeout: 60000 })
.should('exist')
.and('be.visible');
});
it('should display upselling section for protection updates', () => {
loadPage(`${APP_POLICIES_PATH}/${policyId}/protectionUpdates`);
[
'endpointPolicy-protectionUpdatesLockedCard-title',
'endpointPolicy-protectionUpdatesLockedCard',
'endpointPolicy-protectionUpdatesLockedCard-badge',
].forEach((testSubj) => {
cy.getByTestSubj(testSubj, { timeout: 60000 }).should('exist').and('be.visible');
});
cy.getByTestSubj('protection-updates-layout').should('not.exist');
});
}
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/*
* 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 React from 'react';
import { useUpsellingComponent } from '../../../../../../common/hooks/use_upselling';

export const useGetProtectionUpdatesUnavailableComponent = (): React.ComponentType | null => {
return useUpsellingComponent('endpoint_protection_updates');
};
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import type { Moment } from 'moment';
import moment from 'moment';
import { cloneDeep } from 'lodash';
import { FormattedMessage } from '@kbn/i18n-react';
import { useGetProtectionUpdatesUnavailableComponent } from './hooks/use_get_protection_updates_unavailable_component';
import { ProtectionUpdatesBottomBar } from './components/protection_updates_bottom_bar';
import { useCreateProtectionUpdatesNote } from './hooks/use_post_protection_updates_note';
import { useGetProtectionUpdatesNote } from './hooks/use_get_protection_updates_note';
Expand Down Expand Up @@ -401,6 +402,12 @@ export const ProtectionUpdatesLayout = React.memo<ProtectionUpdatesLayoutProps>(
);
};

const ProtectionUpdatesUpsellingComponent = useGetProtectionUpdatesUnavailableComponent();

if (ProtectionUpdatesUpsellingComponent) {
return <ProtectionUpdatesUpsellingComponent />;
}

return (
<>
<EuiPanel
Expand Down
Loading

0 comments on commit 57374ab

Please sign in to comment.