Skip to content

Commit

Permalink
[Security solution][Endpoint] API error when edit/create TA by policy…
Browse files Browse the repository at this point in the history
… and license under platinum (#113363)

* New function to check if the effect scope has been changed without right license

* Checks if license is under platinum when creating/updating trusted apps by policy

* Change error type. Use translations in frontend for api error. Also use helper mapping function to avoid use string replace method

* Remove name from constructor and changed mock function name

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
  • Loading branch information
dasansol92 and kibanamachine committed Sep 30, 2021
1 parent 7f5cd24 commit 4f95060
Show file tree
Hide file tree
Showing 8 changed files with 268 additions and 37 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,18 @@ export const CreateTrustedAppFlyout = memo<CreateTrustedAppFlyoutProps>(
policies.options.forEach((policy) => {
errorMessage = errorMessage?.replace(policy.id, policy.name);
});
} else if (
creationErrors &&
creationErrors.attributes &&
creationErrors.attributes.type === 'EndpointLicenseError'
) {
errorMessage = i18n.translate(
'xpack.securitySolution.trustedapps.createTrustedAppFlyout.byPolicyLicenseError',
{
defaultMessage:
'Your Kibana license has been downgraded. As such, individual policy configuration is no longer supported.',
}
);
}
return errorMessage;
}, [creationErrors, policies]);
Expand Down
5 changes: 5 additions & 0 deletions x-pack/plugins/security_solution/server/endpoint/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,8 @@ export class EndpointAppContentServicesNotStartedError extends EndpointError {
super('EndpointAppContextService has not been started (EndpointAppContextService.start())');
}
}
export class EndpointLicenseError extends EndpointError {
constructor() {
super('Your license level does not allow for this action.');
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ export class TrustedAppPolicyNotExistsError extends Error {
);
}
}

export class TrustedAppVersionConflictError extends Error {
constructor(id: string, public sourceError: Error) {
super(`Trusted Application (${id}) has been updated since last retrieved`);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
*/

import { KibanaResponseFactory } from 'kibana/server';
import { Subject } from 'rxjs';

import { xpackMocks } from '../../../fixtures';
import { loggingSystemMock, httpServerMock } from '../../../../../../../src/core/server/mocks';
Expand All @@ -20,6 +21,9 @@ import {
OperatingSystem,
TrustedApp,
} from '../../../../common/endpoint/types';
import { LicenseService } from '../../../../common/license';
import { ILicense } from '../../../../../licensing/common/types';
import { licenseMock } from '../../../../../licensing/common/licensing.mock';
import { parseExperimentalConfigValue } from '../../../../common/experimental_features';
import { EndpointAppContextService } from '../../endpoint_app_context_services';
import { createConditionEntry, createEntryMatch } from './mapping';
Expand All @@ -41,7 +45,12 @@ import { updateExceptionListItemImplementationMock } from './test_utils';
import { Logger } from '@kbn/logging';
import { PackagePolicyServiceInterface } from '../../../../../fleet/server';
import { createPackagePolicyServiceMock } from '../../../../../fleet/server/mocks';
import { getPackagePoliciesResponse, getTrustedAppByPolicy } from './mocks';
import {
getPackagePoliciesResponse,
getPutTrustedAppByPolicyMock,
getTrustedAppByPolicy,
} from './mocks';
import { EndpointLicenseError } from '../../errors';

const EXCEPTION_LIST_ITEM: ExceptionListItemSchema = {
_version: 'abc123',
Expand Down Expand Up @@ -95,13 +104,19 @@ const TRUSTED_APP: TrustedApp = {
],
};

const Platinum = licenseMock.createLicense({ license: { type: 'platinum', mode: 'platinum' } });
const Gold = licenseMock.createLicense({ license: { type: 'gold', mode: 'gold' } });

const packagePolicyClient =
createPackagePolicyServiceMock() as jest.Mocked<PackagePolicyServiceInterface>;

describe('handlers', () => {
beforeEach(() => {
packagePolicyClient.getByIDs.mockReset();
});
const licenseEmitter: Subject<ILicense> = new Subject();
const licenseService = new LicenseService();
licenseService.start(licenseEmitter);

const createAppContextMock = () => {
const context = {
Expand All @@ -112,6 +127,7 @@ describe('handlers', () => {
};

context.service.getPackagePolicyService = () => packagePolicyClient;
context.service.getLicenseService = () => licenseService;

// Ensure that `logFactory.get()` always returns the same instance for the same given prefix
const instances = new Map<string, ReturnType<typeof context.logFactory.get>>();
Expand Down Expand Up @@ -151,6 +167,7 @@ describe('handlers', () => {
beforeEach(() => {
appContextMock = createAppContextMock();
exceptionsListClient = listMock.getExceptionListClient() as jest.Mocked<ExceptionListClient>;
licenseEmitter.next(Platinum);
});

describe('getTrustedAppsDeleteRouteHandler', () => {
Expand Down Expand Up @@ -261,6 +278,27 @@ describe('handlers', () => {
body: { message: error.message, attributes: { type: error.type } },
});
});

it('should return error when license under platinum and by policy', async () => {
licenseEmitter.next(Gold);
const mockResponse = httpServerMock.createResponseFactory();
packagePolicyClient.getByIDs.mockReset();
packagePolicyClient.getByIDs.mockResolvedValueOnce(getPackagePoliciesResponse());

const trustedAppByPolicy = getTrustedAppByPolicy();
await createTrustedAppHandler(
createHandlerContextMock(),
httpServerMock.createKibanaRequest({ body: trustedAppByPolicy }),
mockResponse
);

const error = new EndpointLicenseError();

expect(appContextMock.logFactory.get('trusted_apps').error).toHaveBeenCalledWith(error);
expect(mockResponse.badRequest).toHaveBeenCalledWith({
body: { message: error.message, attributes: { type: error.name } },
});
});
});

describe('getTrustedAppsListRouteHandler', () => {
Expand Down Expand Up @@ -578,18 +616,57 @@ describe('handlers', () => {
packagePolicyClient.getByIDs.mockReset();
packagePolicyClient.getByIDs.mockResolvedValueOnce(getPackagePoliciesResponse());

const trustedAppByPolicy = getTrustedAppByPolicy();
const exceptionByPolicy = getPutTrustedAppByPolicyMock();
const customExceptionListClient = {
...exceptionsListClient,
getExceptionListItem: () => exceptionByPolicy,
};
const handlerContextMock = {
...xpackMocks.createRequestHandlerContext(),
lists: {
getListClient: jest.fn(),
getExceptionListClient: jest.fn().mockReturnValue(customExceptionListClient),
},
} as unknown as jest.Mocked<SecuritySolutionRequestHandlerContext>;
await updateHandler(
createHandlerContextMock(),
httpServerMock.createKibanaRequest({ body: trustedAppByPolicy }),
handlerContextMock,
httpServerMock.createKibanaRequest({ body: getTrustedAppByPolicy() }),
mockResponse
);

expect(appContextMock.logFactory.get('trusted_apps').error).toHaveBeenCalledWith(
new TrustedAppPolicyNotExistsError(trustedAppByPolicy.name, [
new TrustedAppPolicyNotExistsError(exceptionByPolicy.name, [
'9da95be9-9bee-4761-a8c4-28d6d9bd8c71',
])
);
});

it('should return error when license under platinum and by policy', async () => {
licenseEmitter.next(Gold);
packagePolicyClient.getByIDs.mockReset();
packagePolicyClient.getByIDs.mockResolvedValueOnce(getPackagePoliciesResponse());

const exceptionByPolicy = getPutTrustedAppByPolicyMock();
const customExceptionListClient = {
...exceptionsListClient,
getExceptionListItem: () => exceptionByPolicy,
};
const handlerContextMock = {
...xpackMocks.createRequestHandlerContext(),
lists: {
getListClient: jest.fn(),
getExceptionListClient: jest.fn().mockReturnValue(customExceptionListClient),
},
} as unknown as jest.Mocked<SecuritySolutionRequestHandlerContext>;
await updateHandler(
handlerContextMock,
httpServerMock.createKibanaRequest({ body: getTrustedAppByPolicy() }),
mockResponse
);

expect(appContextMock.logFactory.get('trusted_apps').error).toHaveBeenCalledWith(
new EndpointLicenseError()
);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import {
TrustedAppPolicyNotExistsError,
} from './errors';
import { PackagePolicyServiceInterface } from '../../../../../fleet/server';
import { EndpointLicenseError } from '../../errors';

const getBodyAfterFeatureFlagCheck = (
body: PutTrustedAppUpdateRequest | PostTrustedAppCreateRequest,
Expand Down Expand Up @@ -87,6 +88,11 @@ const errorHandler = <E extends Error>(
return res.badRequest({ body: { message: error.message, attributes: { type: error.type } } });
}

if (error instanceof EndpointLicenseError) {
logger.error(error);
return res.badRequest({ body: { message: error.message, attributes: { type: error.name } } });
}

if (error instanceof TrustedAppVersionConflictError) {
logger.error(error);
return res.conflict({ body: error });
Expand Down Expand Up @@ -177,7 +183,8 @@ export const getTrustedAppsCreateRouteHandler = (
exceptionListClientFromContext(context),
context.core.savedObjects.client,
packagePolicyClientFromEndpointContext(endpointAppContext),
body
body,
endpointAppContext.service.getLicenseService().isAtLeast('platinum')
),
});
} catch (error) {
Expand Down Expand Up @@ -206,7 +213,8 @@ export const getTrustedAppsUpdateRouteHandler = (
context.core.savedObjects.client,
packagePolicyClientFromEndpointContext(endpointAppContext),
req.params.id,
body
body,
endpointAppContext.service.getLicenseService().isAtLeast('platinum')
),
});
} catch (error) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
* 2.0.
*/

import { ExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types';
import { PackagePolicy } from '../../../../../fleet/common';

import {
Expand Down Expand Up @@ -36,6 +37,32 @@ export const getTrustedAppByPolicy = function (): TrustedApp {
};
};

export const getPutTrustedAppByPolicyMock = function (): ExceptionListItemSchema {
return {
id: '123',
_version: '1',
comments: [],
namespace_type: 'agnostic',
created_at: '11/11/2011T11:11:11.111',
created_by: 'admin',
updated_at: '11/11/2011T11:11:11.111',
updated_by: 'admin',
name: 'linux trusted app 1',
description: 'Linux trusted app 1',
os_types: [OperatingSystem.LINUX],
tags: ['policy:9da95be9-9bee-4761-a8c4-28d6d9bd8c71'],
entries: [
createConditionEntry(ConditionEntryField.HASH, 'match', '1234234659af249ddf3e40864e9fb241'),
createConditionEntry(ConditionEntryField.PATH, 'match', '/bin/malware'),
],
item_id: '1',
list_id: '1',
meta: undefined,
tie_breaker_id: '1',
type: 'simple',
};
};

export const getPackagePoliciesResponse = function (): PackagePolicy[] {
return [
// Next line is ts-ignored as this is the response when the policy doesn't exists but the type is complaining about it.
Expand Down
Loading

0 comments on commit 4f95060

Please sign in to comment.