Skip to content

Commit

Permalink
[Cloud Posture] Create concrete csp rules post create package (#130120)
Browse files Browse the repository at this point in the history
  • Loading branch information
CohenIdo authored Apr 20, 2022
1 parent a8b0149 commit 285e336
Show file tree
Hide file tree
Showing 8 changed files with 240 additions and 87 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,12 @@ export const cspRuleSchema = rt.object({
default_value: rt.string(),
remediation: rt.string(),
benchmark: rt.object({ name: rt.string(), version: rt.string() }),
rego_rule_id: rt.string(),
tags: rt.arrayOf(rt.string()),
enabled: rt.boolean(),
muted: rt.boolean(),
package_policy_id: rt.string(),
policy_id: rt.string(),
});

export type CspRuleSchema = TypeOf<typeof cspRuleSchema>;
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,18 @@
import { schema as rt, TypeOf } from '@kbn/config-schema';

const cspRuleTemplateSchema = rt.object({
id: rt.string(),
name: rt.string(),
tags: rt.arrayOf(rt.string()),
description: rt.string(),
rationale: rt.string(),
impact: rt.string(),
default_value: rt.string(),
impact: rt.string(),
remediation: rt.string(),
benchmark: rt.object({ name: rt.string(), version: rt.string() }),
severity: rt.string(),
benchmark_rule_id: rt.string(),
rego_rule_id: rt.string(),
tags: rt.arrayOf(rt.string()),
enabled: rt.boolean(),
muted: rt.boolean(),
});
export const cloudSecurityPostureRuleTemplateSavedObjectType = 'csp-rule-template';
export type CloudSecurityPostureRuleTemplateSchema = TypeOf<typeof cspRuleTemplateSchema>;
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/*
* 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 { loggingSystemMock, savedObjectsClientMock } from '@kbn/core/server/mocks';
import { SavedObjectsClientContract, SavedObjectsFindResponse } from '@kbn/core/server';
import { createPackagePolicyMock } from '@kbn/fleet-plugin/common/mocks';
import { CIS_KUBERNETES_PACKAGE_NAME } from '../../common/constants';
import { onPackagePolicyPostCreateCallback } from './fleet_integration';

describe('create CSP rules with post package create callback', () => {
let logger: ReturnType<typeof loggingSystemMock.createLogger>;
let mockSoClient: jest.Mocked<SavedObjectsClientContract>;
const ruleAttributes = {
id: '41308bcdaaf665761478bb6f0d745a5c',
name: 'Ensure that the API server pod specification file permissions are set to 644 or more restrictive (Automated)',
tags: ['CIS', 'Kubernetes', 'CIS 1.1.1', 'Master Node Configuration Files'],
description:
'Ensure that the API server pod specification file has permissions of `644` or more restrictive.\n',
rationale:
'The API server pod specification file controls various parameters that set the behavior of the API server. You should restrict its file permissions to maintain the integrity of the file. The file should be writable by only the administrators on the system.\n',
default_value: 'By default, the `kube-apiserver.yaml` file has permissions of `640`.\n',
impact: 'None\n',
remediation:
'Run the below command (based on the file location on your system) on the\nmaster node.\nFor example,\n```\nchmod 644 /etc/kubernetes/manifests/kube-apiserver.yaml\n```\n',
benchmark: {
name: 'CIS Kubernetes V1.20',
version: 'v1.0.0',
},
enabled: true,
rego_rule_id: 'cis_1_2_2',
};

beforeEach(() => {
logger = loggingSystemMock.createLogger();
mockSoClient = savedObjectsClientMock.create();
});
it('should create stateful rules based on rule template', async () => {
const mockPackagePolicy = createPackagePolicyMock();
mockPackagePolicy.package!.name = CIS_KUBERNETES_PACKAGE_NAME;
mockSoClient.find.mockResolvedValueOnce({
saved_objects: [
{
type: 'csp-rule-template',
id: 'csp_rule_template-41308bcdaaf665761478bb6f0d745a5c',
attributes: { ...ruleAttributes },
},
],
pit_id: undefined,
} as unknown as SavedObjectsFindResponse);

await onPackagePolicyPostCreateCallback(logger, mockPackagePolicy, mockSoClient);

expect(mockSoClient.bulkCreate.mock.calls[0][0]).toMatchObject([
{
type: 'csp_rule',
attributes: {
...ruleAttributes,
package_policy_id: mockPackagePolicy.id,
policy_id: mockPackagePolicy.policy_id,
},
},
]);
});

it('should not create rules when the package policy is not csp package', async () => {
const mockPackagePolicy = createPackagePolicyMock();
mockPackagePolicy.package!.name = 'not_csp_package';

await onPackagePolicyPostCreateCallback(logger, mockPackagePolicy, mockSoClient);
expect(mockSoClient.find).toHaveBeenCalledTimes(0);
expect(mockSoClient.bulkCreate).toHaveBeenCalledTimes(0);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
/*
* 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 {
SavedObjectsBulkCreateObject,
SavedObjectsFindResponse,
SavedObjectsFindResult,
ISavedObjectsRepository,
SavedObjectsClientContract,
Logger,
} from '@kbn/core/server';
import { PackagePolicy, DeletePackagePoliciesResponse } from '@kbn/fleet-plugin/common';
import {
cloudSecurityPostureRuleTemplateSavedObjectType,
CloudSecurityPostureRuleTemplateSchema,
} from '../../common/schemas/csp_rule_template';
import { CIS_KUBERNETES_PACKAGE_NAME } from '../../common/constants';
import { CspRuleSchema, cspRuleAssetSavedObjectType } from '../../common/schemas/csp_rule';

type ArrayElement<ArrayType extends readonly unknown[]> = ArrayType extends ReadonlyArray<
infer ElementType
>
? ElementType
: never;

const isCspPackagePolicy = <T extends { package?: { name: string } }>(
packagePolicy: T
): boolean => {
return packagePolicy.package?.name === CIS_KUBERNETES_PACKAGE_NAME;
};

/**
* Callback to handle creation of PackagePolicies in Fleet
*/
export const onPackagePolicyPostCreateCallback = async (
logger: Logger,
packagePolicy: PackagePolicy,
savedObjectsClient: SavedObjectsClientContract
): Promise<void> => {
// We only care about Cloud Security Posture package policies
if (!isCspPackagePolicy(packagePolicy)) {
return;
}
// Create csp-rules from the generic asset
const existingRuleTemplates: SavedObjectsFindResponse<CloudSecurityPostureRuleTemplateSchema> =
await savedObjectsClient.find({ type: cloudSecurityPostureRuleTemplateSavedObjectType });

if (existingRuleTemplates.total === 0) {
return;
}

const cspRules = generateRulesFromTemplates(
packagePolicy.id,
packagePolicy.policy_id,
existingRuleTemplates.saved_objects
);

try {
await savedObjectsClient.bulkCreate(cspRules);
logger.info(`Generated CSP rules for package ${packagePolicy.policy_id}`);
} catch (e) {
logger.error('failed to generate rules out of template');
logger.error(e);
}
};

/**
* Callback to handle deletion of PackagePolicies in Fleet
*/
export const onPackagePolicyDeleteCallback = async (
logger: Logger,
deletedPackagePolicy: ArrayElement<DeletePackagePoliciesResponse>,
soClient: ISavedObjectsRepository
): Promise<void> => {
try {
const { saved_objects: cspRules }: SavedObjectsFindResponse<CspRuleSchema> =
await soClient.find({
type: cspRuleAssetSavedObjectType,
filter: `csp_rule.attributes.package_policy_id: ${deletedPackagePolicy.id} AND csp_rule.attributes.policy_id: ${deletedPackagePolicy.policy_id}`,
});
await Promise.all(
cspRules.map((rule) => soClient.delete(cspRuleAssetSavedObjectType, rule.id))
);
} catch (e) {
logger.error(`Failed to delete CSP rules after delete package ${deletedPackagePolicy.id}`);
logger.error(e);
}
};

const generateRulesFromTemplates = (
packagePolicyId: string,
policyId: string,
cspRuleTemplates: Array<SavedObjectsFindResult<CloudSecurityPostureRuleTemplateSchema>>
): Array<SavedObjectsBulkCreateObject<CspRuleSchema>> =>
cspRuleTemplates.map((template) => ({
type: cspRuleAssetSavedObjectType,
attributes: {
...template.attributes,
package_policy_id: packagePolicyId,
policy_id: policyId,
},
}));
50 changes: 44 additions & 6 deletions x-pack/plugins/cloud_security_posture/server/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ import type {
Plugin,
Logger,
} from '@kbn/core/server';
import { KibanaRequest, RequestHandlerContext } from '@kbn/core/server';
import { DeepReadonly } from 'utility-types';
import { DeletePackagePoliciesResponse, PackagePolicy } from '@kbn/fleet-plugin/common';
import { CspAppService } from './lib/csp_app_services';
import type {
CspServerPluginSetup,
Expand All @@ -23,9 +26,12 @@ import type {
import { defineRoutes } from './routes';
import { cspRuleTemplateAssetType } from './saved_objects/csp_rule_template';
import { cspRuleAssetType } from './saved_objects/csp_rule_type';
import { initializeCspRules } from './saved_objects/initialize_rules';
import { initializeCspTransformsIndices } from './create_indices/create_transforms_indices';
import { initializeCspTransforms } from './create_transforms/create_transforms';
import {
onPackagePolicyPostCreateCallback,
onPackagePolicyDeleteCallback,
} from './fleet_integration/fleet_integration';
import { CIS_KUBERNETES_PACKAGE_NAME } from '../common/constants';

export interface CspAppContext {
logger: Logger;
Expand Down Expand Up @@ -72,10 +78,42 @@ export class CspPlugin
...plugins.fleet,
});

initializeCspRules(core.savedObjects.createInternalRepository());
initializeCspTransformsIndices(core.elasticsearch.client.asInternalUser, this.logger).then(
(_) => initializeCspTransforms(core.elasticsearch.client.asInternalUser, this.logger)
);
initializeCspTransformsIndices(core.elasticsearch.client.asInternalUser, this.logger);
plugins.fleet.fleetSetupCompleted().then(() => {
plugins.fleet.registerExternalCallback(
'packagePolicyPostCreate',
async (
packagePolicy: PackagePolicy,
context: RequestHandlerContext,
request: KibanaRequest
): Promise<PackagePolicy> => {
if (packagePolicy.package?.name === CIS_KUBERNETES_PACKAGE_NAME) {
await onPackagePolicyPostCreateCallback(
this.logger,
packagePolicy,
context.core.savedObjects.client
);
}

return packagePolicy;
}
);

plugins.fleet.registerExternalCallback(
'postPackagePolicyDelete',
async (deletedPackagePolicies: DeepReadonly<DeletePackagePoliciesResponse>) => {
for (const deletedPackagePolicy of deletedPackagePolicies) {
if (deletedPackagePolicy.package?.name === CIS_KUBERNETES_PACKAGE_NAME) {
await onPackagePolicyDeleteCallback(
this.logger,
deletedPackagePolicy,
core.savedObjects.createInternalRepository()
);
}
}
}
);
});

return {};
}
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,12 @@ export const ruleAssetSavedObjectMappings: SavedObjectsType<CspRuleSchema>['mapp
},
},
},
package_policy_id: {
type: 'keyword',
},
policy_id: {
type: 'keyword',
},
description: {
type: 'text',
},
Expand Down

This file was deleted.

0 comments on commit 285e336

Please sign in to comment.