[Fleet] Improve handling of frozen variables (#127493)

This commit is contained in:
Nicolas Chaulet 2022-03-10 15:16:17 -05:00 committed by GitHub
parent ce0ccfa693
commit 62c2082d2f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 160 additions and 18 deletions

View file

@ -4247,6 +4247,9 @@
},
"description": {
"type": "string"
},
"force": {
"type": "boolean"
}
},
"required": [

View file

@ -2676,6 +2676,8 @@ components:
type: string
description:
type: string
force:
type: boolean
required:
- name
- namespace

View file

@ -53,6 +53,8 @@ properties:
type: string
description:
type: string
force:
type: boolean
required:
- name
- namespace

View file

@ -139,7 +139,7 @@ export const updatePackagePolicyHandler: RequestHandler<
throw Boom.notFound('Package policy not found');
}
const body = { ...request.body };
const { force, ...body } = request.body;
// removed fields not recognized by schema
const packagePolicyInputs = packagePolicy.inputs.map((input) => {
const newInput = {
@ -180,7 +180,7 @@ export const updatePackagePolicyHandler: RequestHandler<
esClient,
request.params.packagePolicyId,
newData,
{ user },
{ user, force },
packagePolicy.package?.version
);
return response.ok({

View file

@ -632,7 +632,128 @@ describe('Package policy service', () => {
).rejects.toThrow('Saved object [abc/123] conflict');
});
it('should only update input vars that are not frozen', async () => {
it('should throw if the user try to update input vars that are frozen', async () => {
const savedObjectsClient = savedObjectsClientMock.create();
const mockPackagePolicy = createPackagePolicyMock();
const mockInputs = [
{
config: {},
enabled: true,
keep_enabled: true,
type: 'endpoint',
vars: {
dog: {
type: 'text',
value: 'dalmatian',
},
cat: {
type: 'text',
value: 'siamese',
frozen: true,
},
},
streams: [
{
data_stream: {
type: 'birds',
dataset: 'migratory.patterns',
},
enabled: false,
id: `endpoint-migratory.patterns-${mockPackagePolicy.id}`,
vars: {
paths: {
value: ['north', 'south'],
type: 'text',
frozen: true,
},
period: {
value: '6mo',
type: 'text',
},
},
},
],
},
];
const inputsUpdate = [
{
config: {},
enabled: false,
type: 'endpoint',
vars: {
dog: {
type: 'text',
value: 'labrador',
},
cat: {
type: 'text',
value: 'tabby',
},
},
streams: [
{
data_stream: {
type: 'birds',
dataset: 'migratory.patterns',
},
enabled: false,
id: `endpoint-migratory.patterns-${mockPackagePolicy.id}`,
vars: {
paths: {
value: ['east', 'west'],
type: 'text',
},
period: {
value: '12mo',
type: 'text',
},
},
},
],
},
];
const attributes = {
...mockPackagePolicy,
inputs: mockInputs,
};
savedObjectsClient.get.mockResolvedValue({
id: 'test',
type: 'abcd',
references: [],
version: 'test',
attributes,
});
savedObjectsClient.update.mockImplementation(
async (
type: string,
id: string,
attrs: any
): Promise<SavedObjectsUpdateResponse<PackagePolicySOAttributes>> => {
savedObjectsClient.get.mockResolvedValue({
id: 'test',
type: 'abcd',
references: [],
version: 'test',
attributes: attrs,
});
return attrs;
}
);
const elasticsearchClient = elasticsearchServiceMock.createClusterClient().asInternalUser;
const res = packagePolicyService.update(
savedObjectsClient,
elasticsearchClient,
'the-package-policy-id',
{ ...mockPackagePolicy, inputs: inputsUpdate }
);
await expect(res).rejects.toThrow('cat is a frozen variable and cannot be modified');
});
it('should allow to update input vars that are frozen with the force flag', async () => {
const savedObjectsClient = savedObjectsClientMock.create();
const mockPackagePolicy = createPackagePolicyMock();
const mockInputs = [
@ -747,18 +868,18 @@ describe('Package policy service', () => {
savedObjectsClient,
elasticsearchClient,
'the-package-policy-id',
{ ...mockPackagePolicy, inputs: inputsUpdate }
{ ...mockPackagePolicy, inputs: inputsUpdate },
{ force: true }
);
const [modifiedInput] = result.inputs;
expect(modifiedInput.enabled).toEqual(true);
expect(modifiedInput.vars!.dog.value).toEqual('labrador');
expect(modifiedInput.vars!.cat.value).toEqual('siamese');
expect(modifiedInput.vars!.cat.value).toEqual('tabby');
const [modifiedStream] = modifiedInput.streams;
expect(modifiedStream.vars!.paths.value).toEqual(expect.arrayContaining(['north', 'south']));
expect(modifiedStream.vars!.paths.value).toEqual(expect.arrayContaining(['east', 'west']));
expect(modifiedStream.vars!.period.value).toEqual('12mo');
});
it('should add new input vars when updating', async () => {
const savedObjectsClient = savedObjectsClientMock.create();
const mockPackagePolicy = createPackagePolicyMock();
@ -810,7 +931,7 @@ describe('Package policy service', () => {
},
cat: {
type: 'text',
value: 'tabby',
value: 'siamese',
},
},
streams: [
@ -823,7 +944,7 @@ describe('Package policy service', () => {
id: `endpoint-migratory.patterns-${mockPackagePolicy.id}`,
vars: {
paths: {
value: ['east', 'west'],
value: ['north', 'south'],
type: 'text',
},
period: {

View file

@ -5,7 +5,7 @@
* 2.0.
*/
import { omit, partition } from 'lodash';
import { omit, partition, isEqual } from 'lodash';
import { i18n } from '@kbn/i18n';
import semverLt from 'semver/functions/lt';
import { getFlattenedObject } from '@kbn/std';
@ -358,7 +358,7 @@ class PackagePolicyService implements PackagePolicyServiceInterface {
esClient: ElasticsearchClient,
id: string,
packagePolicyUpdate: UpdatePackagePolicy,
options?: { user?: AuthenticatedUser },
options?: { user?: AuthenticatedUser; force?: boolean },
currentVersion?: string
): Promise<PackagePolicy> {
const packagePolicy = { ...packagePolicyUpdate, name: packagePolicyUpdate.name.trim() };
@ -386,7 +386,7 @@ class PackagePolicyService implements PackagePolicyServiceInterface {
assignStreamIdToInput(oldPackagePolicy.id, input)
);
inputs = enforceFrozenInputs(oldPackagePolicy.inputs, inputs);
inputs = enforceFrozenInputs(oldPackagePolicy.inputs, inputs, options?.force);
let elasticsearch: PackagePolicy['elasticsearch'];
if (packagePolicy.package?.name) {
const pkgInfo = await getPackageInfo({
@ -1119,21 +1119,25 @@ async function _compilePackageStream(
return { ...stream };
}
function enforceFrozenInputs(oldInputs: PackagePolicyInput[], newInputs: PackagePolicyInput[]) {
function enforceFrozenInputs(
oldInputs: PackagePolicyInput[],
newInputs: PackagePolicyInput[],
force = false
) {
const resultInputs = [...newInputs];
for (const input of resultInputs) {
const oldInput = oldInputs.find((i) => i.type === input.type);
if (oldInput?.keep_enabled) input.enabled = oldInput.enabled;
if (input.vars && oldInput?.vars) {
input.vars = _enforceFrozenVars(oldInput.vars, input.vars);
input.vars = _enforceFrozenVars(oldInput.vars, input.vars, force);
}
if (input.streams && oldInput?.streams) {
for (const stream of input.streams) {
const oldStream = oldInput.streams.find((s) => s.id === stream.id);
if (oldStream?.keep_enabled) stream.enabled = oldStream.enabled;
if (stream.vars && oldStream?.vars) {
stream.vars = _enforceFrozenVars(oldStream.vars, stream.vars);
stream.vars = _enforceFrozenVars(oldStream.vars, stream.vars, force);
}
}
}
@ -1144,12 +1148,21 @@ function enforceFrozenInputs(oldInputs: PackagePolicyInput[], newInputs: Package
function _enforceFrozenVars(
oldVars: Record<string, PackagePolicyConfigRecordEntry>,
newVars: Record<string, PackagePolicyConfigRecordEntry>
newVars: Record<string, PackagePolicyConfigRecordEntry>,
force = false
) {
const resultVars: Record<string, PackagePolicyConfigRecordEntry> = {};
for (const [key, val] of Object.entries(newVars)) {
if (oldVars[key]?.frozen) {
resultVars[key] = oldVars[key];
if (force) {
resultVars[key] = val;
} else if (!isEqual(oldVars[key].value, val.value) || oldVars[key].type !== val.type) {
throw new PackagePolicyValidationError(
`${key} is a frozen variable and cannot be modified`
);
} else {
resultVars[key] = oldVars[key];
}
} else {
resultVars[key] = val;
}
@ -1206,7 +1219,7 @@ export interface PackagePolicyServiceInterface {
esClient: ElasticsearchClient,
id: string,
packagePolicyUpdate: UpdatePackagePolicy,
options?: { user?: AuthenticatedUser },
options?: { user?: AuthenticatedUser; force?: boolean },
currentVersion?: string
): Promise<PackagePolicy>;

View file

@ -138,6 +138,7 @@ export const UpdatePackagePolicyRequestBodySchema = schema.object({
)
),
version: schema.maybe(schema.string()),
force: schema.maybe(schema.boolean()),
});
export const UpdatePackagePolicySchema = schema.object({