[8.x] [ESO] Add flag to allow ESO consumers to opt-out of highly random UIDs (#198287) (#198956)

# Backport

This will backport the following commits from `main` to `8.x`:
- [[ESO] Add flag to allow ESO consumers to opt-out of highly random
UIDs (#198287)](https://github.com/elastic/kibana/pull/198287)

<!--- Backport version: 9.4.3 -->

### Questions ?
Please refer to the [Backport tool
documentation](https://github.com/sqren/backport)

<!--BACKPORT
[{"author":{"name":"Sid","email":"siddharthmantri1@gmail.com"},"sourceCommit":{"committedDate":"2024-11-05T14:40:53Z","message":"[ESO]
Add flag to allow ESO consumers to opt-out of highly random UIDs
(#198287)\n\nCloses
https://github.com/elastic/kibana/issues/194692\r\n\r\n##
Summary\r\nAllow consumers of ESOs to explicitly opt out of the strict
highly\r\nrandom UID requirements while registering the ESO
type\r\n\r\n### Description\r\n\r\nThe `getValidId` method was updated
to allow consumers of Encrypted\r\nSaved Objects to explicitly opt-out
of the enforced random ID\r\nrequirement.\r\n\r\nThis change is added
during ESO registration - consumers can now pass a\r\nnew field to
opt-out of random UIDs.\r\n\r\nAdditional changes\r\n\r\n- Updated
canSpecifyID logic:\r\n- The canSpecifyID condition now also checks if
enforceRandomId is\r\nexplicitly set to false.\r\nThis opt-out approach
allows specific ESOs to bypass the random ID\r\nenforcement without
affecting the default behavior, keeping it secure
by\r\ndefault.\r\n\r\n\r\nDuring the registration phase of the saved
object, consumers can now\r\nspecify if they'd like to opt-out of the
random ID\r\n\r\n```\r\nsavedObjects.registerType({\r\n name:
TYPE_WITH_PREDICTABLE_ID,\r\n
//...\r\n});\r\n\r\nencryptedSavedObjects.registerType({\r\n type:
TYPE_WITH_PREDICTABLE_ID,\r\n //...\r\n enforceRandomId:
false,\r\n});\r\n\r\n```\r\n\r\n\r\n### Release notes\r\n\r\nImproves
Encrypted Saved Objects (ESO) ID validation by adding
an\r\nenforceRandomId parameter, allowing consumers to opt out of the
default\r\nrandom ID requirement for specific use cases.\r\n\r\n###
Checklist\r\n\r\n-
[x]\r\n[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)\r\nwas
added for features that require explanation or tutorials\r\n- [x] [Unit
or
functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere
updated or added to match the most common scenarios\r\n- [ ] [Flaky
Test\r\nRunner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1)
was\r\nused on any tests changed\r\n\r\n\r\n### For maintainers\r\n\r\n-
[ ] This was checked for breaking API changes and was
[labeled\r\nappropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#_add_your_labels)\r\n-
[ ] This will appear in the **Release Notes** and follow
the\r\n[guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)\r\n\r\n---------\r\n\r\nCo-authored-by:
Elastic Machine
<elasticmachine@users.noreply.github.com>\r\nCo-authored-by: Jeramy
Soucy
<jeramy.soucy@elastic.co>","sha":"56c0806af5a7f20903e92bfe88dc227e93ca2858","branchLabelMapping":{"^v9.0.0$":"main","^v8.17.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:enhancement","backport","Team:Security","v9.0.0","Feature:Security/Encrypted
Saved Objects","v8.17.0"],"title":"[ESO] Add flag to allow ESO consumers
to opt-out of highly random
UIDs","number":198287,"url":"https://github.com/elastic/kibana/pull/198287","mergeCommit":{"message":"[ESO]
Add flag to allow ESO consumers to opt-out of highly random UIDs
(#198287)\n\nCloses
https://github.com/elastic/kibana/issues/194692\r\n\r\n##
Summary\r\nAllow consumers of ESOs to explicitly opt out of the strict
highly\r\nrandom UID requirements while registering the ESO
type\r\n\r\n### Description\r\n\r\nThe `getValidId` method was updated
to allow consumers of Encrypted\r\nSaved Objects to explicitly opt-out
of the enforced random ID\r\nrequirement.\r\n\r\nThis change is added
during ESO registration - consumers can now pass a\r\nnew field to
opt-out of random UIDs.\r\n\r\nAdditional changes\r\n\r\n- Updated
canSpecifyID logic:\r\n- The canSpecifyID condition now also checks if
enforceRandomId is\r\nexplicitly set to false.\r\nThis opt-out approach
allows specific ESOs to bypass the random ID\r\nenforcement without
affecting the default behavior, keeping it secure
by\r\ndefault.\r\n\r\n\r\nDuring the registration phase of the saved
object, consumers can now\r\nspecify if they'd like to opt-out of the
random ID\r\n\r\n```\r\nsavedObjects.registerType({\r\n name:
TYPE_WITH_PREDICTABLE_ID,\r\n
//...\r\n});\r\n\r\nencryptedSavedObjects.registerType({\r\n type:
TYPE_WITH_PREDICTABLE_ID,\r\n //...\r\n enforceRandomId:
false,\r\n});\r\n\r\n```\r\n\r\n\r\n### Release notes\r\n\r\nImproves
Encrypted Saved Objects (ESO) ID validation by adding
an\r\nenforceRandomId parameter, allowing consumers to opt out of the
default\r\nrandom ID requirement for specific use cases.\r\n\r\n###
Checklist\r\n\r\n-
[x]\r\n[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)\r\nwas
added for features that require explanation or tutorials\r\n- [x] [Unit
or
functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere
updated or added to match the most common scenarios\r\n- [ ] [Flaky
Test\r\nRunner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1)
was\r\nused on any tests changed\r\n\r\n\r\n### For maintainers\r\n\r\n-
[ ] This was checked for breaking API changes and was
[labeled\r\nappropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#_add_your_labels)\r\n-
[ ] This will appear in the **Release Notes** and follow
the\r\n[guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)\r\n\r\n---------\r\n\r\nCo-authored-by:
Elastic Machine
<elasticmachine@users.noreply.github.com>\r\nCo-authored-by: Jeramy
Soucy
<jeramy.soucy@elastic.co>","sha":"56c0806af5a7f20903e92bfe88dc227e93ca2858"}},"sourceBranch":"main","suggestedTargetBranches":["8.x"],"targetPullRequestStates":[{"branch":"main","label":"v9.0.0","branchLabelMappingKey":"^v9.0.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/198287","number":198287,"mergeCommit":{"message":"[ESO]
Add flag to allow ESO consumers to opt-out of highly random UIDs
(#198287)\n\nCloses
https://github.com/elastic/kibana/issues/194692\r\n\r\n##
Summary\r\nAllow consumers of ESOs to explicitly opt out of the strict
highly\r\nrandom UID requirements while registering the ESO
type\r\n\r\n### Description\r\n\r\nThe `getValidId` method was updated
to allow consumers of Encrypted\r\nSaved Objects to explicitly opt-out
of the enforced random ID\r\nrequirement.\r\n\r\nThis change is added
during ESO registration - consumers can now pass a\r\nnew field to
opt-out of random UIDs.\r\n\r\nAdditional changes\r\n\r\n- Updated
canSpecifyID logic:\r\n- The canSpecifyID condition now also checks if
enforceRandomId is\r\nexplicitly set to false.\r\nThis opt-out approach
allows specific ESOs to bypass the random ID\r\nenforcement without
affecting the default behavior, keeping it secure
by\r\ndefault.\r\n\r\n\r\nDuring the registration phase of the saved
object, consumers can now\r\nspecify if they'd like to opt-out of the
random ID\r\n\r\n```\r\nsavedObjects.registerType({\r\n name:
TYPE_WITH_PREDICTABLE_ID,\r\n
//...\r\n});\r\n\r\nencryptedSavedObjects.registerType({\r\n type:
TYPE_WITH_PREDICTABLE_ID,\r\n //...\r\n enforceRandomId:
false,\r\n});\r\n\r\n```\r\n\r\n\r\n### Release notes\r\n\r\nImproves
Encrypted Saved Objects (ESO) ID validation by adding
an\r\nenforceRandomId parameter, allowing consumers to opt out of the
default\r\nrandom ID requirement for specific use cases.\r\n\r\n###
Checklist\r\n\r\n-
[x]\r\n[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)\r\nwas
added for features that require explanation or tutorials\r\n- [x] [Unit
or
functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere
updated or added to match the most common scenarios\r\n- [ ] [Flaky
Test\r\nRunner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1)
was\r\nused on any tests changed\r\n\r\n\r\n### For maintainers\r\n\r\n-
[ ] This was checked for breaking API changes and was
[labeled\r\nappropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#_add_your_labels)\r\n-
[ ] This will appear in the **Release Notes** and follow
the\r\n[guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)\r\n\r\n---------\r\n\r\nCo-authored-by:
Elastic Machine
<elasticmachine@users.noreply.github.com>\r\nCo-authored-by: Jeramy
Soucy
<jeramy.soucy@elastic.co>","sha":"56c0806af5a7f20903e92bfe88dc227e93ca2858"}},{"branch":"8.x","label":"v8.17.0","branchLabelMappingKey":"^v8.17.0$","isSourceBranch":false,"state":"NOT_CREATED"}]}]
BACKPORT-->

Co-authored-by: Sid <siddharthmantri1@gmail.com>
This commit is contained in:
Kibana Machine 2024-11-06 03:29:30 +11:00 committed by GitHub
parent d650c380c5
commit 937aeee908
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 249 additions and 4 deletions

View file

@ -105,10 +105,14 @@ export class CommonHelper {
if (!id) {
return SavedObjectsUtils.generateId();
}
// only allow a specified ID if we're overwriting an existing ESO with a Version
// this helps us ensure that the document really was previously created using ESO
// and not being used to get around the specified ID limitation
const canSpecifyID = (overwrite && version) || SavedObjectsUtils.isRandomId(id);
const shouldEnforceRandomId = this.encryptionExtension?.shouldEnforceRandomId(type);
// Allow specified ID if:
// 1. we're overwriting an existing ESO with a Version (this helps us ensure that the document really was previously created using ESO)
// 2. enforceRandomId is explicitly set to false
const canSpecifyID =
!shouldEnforceRandomId || (overwrite && version) || SavedObjectsUtils.isRandomId(id);
if (!canSpecifyID) {
throw SavedObjectsErrorHelpers.createBadRequestError(
'Predefined IDs are not allowed for saved objects with encrypted attributes unless the ID is a UUID.'

View file

@ -261,6 +261,7 @@ describe('SavedObjectsRepository Encryption Extension', () => {
it(`fails if non-UUID ID is specified for encrypted type`, async () => {
mockEncryptionExt.isEncryptableType.mockReturnValue(true);
mockEncryptionExt.shouldEnforceRandomId.mockReturnValue(true);
mockEncryptionExt.decryptOrStripResponseAttributes.mockResolvedValue({
...encryptedSO,
...decryptedStrippedAttributes,
@ -291,6 +292,25 @@ describe('SavedObjectsRepository Encryption Extension', () => {
).resolves.not.toThrowError();
});
it('allows to opt-out of random ID enforcement', async () => {
mockEncryptionExt.isEncryptableType.mockReturnValue(true);
mockEncryptionExt.shouldEnforceRandomId.mockReturnValue(false);
mockEncryptionExt.decryptOrStripResponseAttributes.mockResolvedValue({
...encryptedSO,
...decryptedStrippedAttributes,
});
const result = await repository.create(encryptedSO.type, encryptedSO.attributes, {
id: encryptedSO.id,
version: mockVersion,
});
expect(client.create).toHaveBeenCalled();
expect(mockEncryptionExt.isEncryptableType).toHaveBeenCalledWith(encryptedSO.type);
expect(mockEncryptionExt.shouldEnforceRandomId).toHaveBeenCalledWith(encryptedSO.type);
expect(result.id).toBe(encryptedSO.id);
});
describe('namespace', () => {
const doTest = async (optNamespace: string, expectNamespaceInDescriptor: boolean) => {
const options = { overwrite: true, namespace: optNamespace };
@ -483,6 +503,7 @@ describe('SavedObjectsRepository Encryption Extension', () => {
it(`fails if non-UUID ID is specified for encrypted type`, async () => {
mockEncryptionExt.isEncryptableType.mockReturnValue(true);
mockEncryptionExt.shouldEnforceRandomId.mockReturnValue(true);
const result = await bulkCreateSuccess(client, repository, [
encryptedSO, // Predefined IDs are not allowed for saved objects with encrypted attributes unless the ID is a UUID
]);
@ -529,6 +550,25 @@ describe('SavedObjectsRepository Encryption Extension', () => {
expect(result.saved_objects.length).toBe(1);
expect(result.saved_objects[0].error).toBeUndefined();
});
it('allows to opt-out of random ID enforcement', async () => {
mockEncryptionExt.isEncryptableType.mockReturnValue(true);
mockEncryptionExt.shouldEnforceRandomId.mockReturnValue(false);
mockEncryptionExt.decryptOrStripResponseAttributes.mockResolvedValue({
...encryptedSO,
...decryptedStrippedAttributes,
});
const result = await bulkCreateSuccess(client, repository, [
{ ...encryptedSO, version: mockVersion },
]);
expect(client.bulk).toHaveBeenCalled();
expect(result.saved_objects).not.toBeUndefined();
expect(result.saved_objects.length).toBe(1);
expect(result.saved_objects[0].error).toBeUndefined();
expect(result.saved_objects[0].id).toBe(encryptedSO.id);
});
});
describe('#bulkUpdate', () => {

View file

@ -17,6 +17,7 @@ const createEncryptionExtension = (): jest.Mocked<ISavedObjectsEncryptionExtensi
isEncryptableType: jest.fn(),
decryptOrStripResponseAttributes: jest.fn(),
encryptAttributes: jest.fn(),
shouldEnforceRandomId: jest.fn(),
});
const createSecurityExtension = (): jest.Mocked<ISavedObjectsSecurityExtension> => ({

View file

@ -18,6 +18,7 @@ const createEncryptionExtension = (): jest.Mocked<ISavedObjectsEncryptionExtensi
isEncryptableType: jest.fn(),
decryptOrStripResponseAttributes: jest.fn(),
encryptAttributes: jest.fn(),
shouldEnforceRandomId: jest.fn(),
});
const createSecurityExtension = (): jest.Mocked<ISavedObjectsSecurityExtension> => ({

View file

@ -39,6 +39,14 @@ export interface ISavedObjectsEncryptionExtension {
*/
isEncryptableType: (type: string) => boolean;
/**
* Returns false if ESO type explicitly opts out of highly random UID
*
* @param type the string name of the object type
* @returns boolean, true by default unless explicitly set to false
*/
shouldEnforceRandomId: (type: string) => boolean;
/**
* Given a saved object, will return a decrypted saved object or will strip
* attributes from the returned object if decryption fails.

View file

@ -16,6 +16,7 @@ export class EncryptedSavedObjectAttributesDefinition {
public readonly attributesToEncrypt: ReadonlySet<string>;
private readonly attributesToIncludeInAAD: ReadonlySet<string> | undefined;
private readonly attributesToStrip: ReadonlySet<string>;
public readonly enforceRandomId: boolean;
constructor(typeRegistration: EncryptedSavedObjectTypeRegistration) {
if (typeRegistration.attributesToIncludeInAAD) {
@ -49,6 +50,8 @@ export class EncryptedSavedObjectAttributesDefinition {
}
}
this.enforceRandomId = typeRegistration.enforceRandomId !== false;
this.attributesToEncrypt = attributesToEncrypt;
this.attributesToStrip = attributesToStrip;
this.attributesToIncludeInAAD = typeRegistration.attributesToIncludeInAAD;

View file

@ -2405,3 +2405,24 @@ describe('#decryptAttributesSync', () => {
});
});
});
describe('#shouldEnforceRandomId', () => {
it('defaults to true if enforceRandomId is undefined', () => {
service.registerType({ type: 'known-type-1', attributesToEncrypt: new Set(['attr']) });
expect(service.shouldEnforceRandomId('known-type-1')).toBe(true);
});
it('should return the value of enforceRandomId if it is defined', () => {
service.registerType({
type: 'known-type-1',
attributesToEncrypt: new Set(['attr']),
enforceRandomId: false,
});
service.registerType({
type: 'known-type-2',
attributesToEncrypt: new Set(['attr']),
enforceRandomId: true,
});
expect(service.shouldEnforceRandomId('known-type-1')).toBe(false);
expect(service.shouldEnforceRandomId('known-type-2')).toBe(true);
});
});

View file

@ -33,6 +33,7 @@ export interface EncryptedSavedObjectTypeRegistration {
readonly type: string;
readonly attributesToEncrypt: ReadonlySet<string | AttributeToEncrypt>;
readonly attributesToIncludeInAAD?: ReadonlySet<string>;
readonly enforceRandomId?: boolean;
}
/**
@ -152,6 +153,16 @@ export class EncryptedSavedObjectsService {
return this.typeDefinitions.has(type);
}
/**
* Checks whether the ESO type has explicitly opted out of enforcing random IDs.
* @param type Saved object type.
* @returns boolean - true unless explicitly opted out by setting enforceRandomId to false
*/
public shouldEnforceRandomId(type: string) {
const typeDefinition = this.typeDefinitions.get(type);
return typeDefinition?.enforceRandomId !== false;
}
/**
* Takes saved object attributes for the specified type and, depending on the type definition,
* either decrypts or strips encrypted attributes (e.g. in case AAD or encryption key has changed

View file

@ -40,6 +40,10 @@ export class SavedObjectsEncryptionExtension implements ISavedObjectsEncryptionE
return this._service.isRegistered(type);
}
shouldEnforceRandomId(type: string) {
return this._service.shouldEnforceRandomId(type);
}
async decryptOrStripResponseAttributes<T, R extends SavedObject<T>>(
response: R,
originalAttributes?: T

View file

@ -32,6 +32,8 @@ const SAVED_OBJECT_WITH_MIGRATION_TYPE = 'saved-object-with-migration';
const SAVED_OBJECT_MV_TYPE = 'saved-object-mv';
const TYPE_WITH_PREDICTABLE_ID = 'type-with-predictable-ids';
interface MigratedTypePre790 {
nonEncryptedAttribute: string;
encryptedAttribute: string;
@ -83,6 +85,30 @@ export const plugin: PluginInitializer<void, void, PluginsSetup, PluginsStart> =
});
}
core.savedObjects.registerType({
name: TYPE_WITH_PREDICTABLE_ID,
hidden: false,
namespaceType: 'single',
mappings: deepFreeze({
properties: {
publicProperty: { type: 'keyword' },
publicPropertyExcludedFromAAD: { type: 'keyword' },
publicPropertyStoredEncrypted: { type: 'binary' },
privateProperty: { type: 'binary' },
},
}),
});
deps.encryptedSavedObjects.registerType({
type: TYPE_WITH_PREDICTABLE_ID,
attributesToEncrypt: new Set([
'privateProperty',
{ key: 'publicPropertyStoredEncrypted', dangerouslyExposeValue: true },
]),
attributesToIncludeInAAD: new Set(['publicProperty']),
enforceRandomId: false,
});
core.savedObjects.registerType({
name: SAVED_OBJECT_WITHOUT_SECRET_TYPE,
hidden: false,

View file

@ -26,6 +26,8 @@ export default function ({ getService }: FtrProviderContext) {
'saved-object-with-secret-and-multiple-spaces';
const SAVED_OBJECT_WITHOUT_SECRET_TYPE = 'saved-object-without-secret';
const TYPE_WITH_PREDICTABLE_ID = 'type-with-predictable-ids';
function runTests(
encryptedSavedObjectType: string,
getURLAPIBaseURL: () => string,
@ -900,5 +902,129 @@ export default function ({ getService }: FtrProviderContext) {
}
});
});
describe('enforceRandomId', () => {
describe('false', () => {
it('#create allows setting non-random ID', async () => {
const id = 'my_predictable_id';
const savedObjectOriginalAttributes = {
publicProperty: randomness.string(),
publicPropertyStoredEncrypted: randomness.string(),
privateProperty: randomness.string(),
publicPropertyExcludedFromAAD: randomness.string(),
};
const { body: response } = await supertest
.post(`/api/saved_objects/${TYPE_WITH_PREDICTABLE_ID}/${id}`)
.set('kbn-xsrf', 'xxx')
.send({ attributes: savedObjectOriginalAttributes })
.expect(200);
expect(response.id).to.be(id);
});
it('#bulkCreate not enforcing random ID allows to specify ID', async () => {
const bulkCreateParams = [
{
type: TYPE_WITH_PREDICTABLE_ID,
id: 'my_predictable_id',
attributes: {
publicProperty: randomness.string(),
publicPropertyExcludedFromAAD: randomness.string(),
publicPropertyStoredEncrypted: randomness.string(),
privateProperty: randomness.string(),
},
},
{
type: TYPE_WITH_PREDICTABLE_ID,
id: 'my_predictable_id_2',
attributes: {
publicProperty: randomness.string(),
publicPropertyExcludedFromAAD: randomness.string(),
publicPropertyStoredEncrypted: randomness.string(),
privateProperty: randomness.string(),
},
},
];
const {
body: { saved_objects: savedObjects },
} = await supertest
.post('/api/saved_objects/_bulk_create')
.set('kbn-xsrf', 'xxx')
.send(bulkCreateParams)
.expect(200);
expect(savedObjects).to.have.length(bulkCreateParams.length);
expect(savedObjects[0].id).to.be('my_predictable_id');
expect(savedObjects[1].id).to.be('my_predictable_id_2');
});
});
describe('true or undefined', () => {
it('#create setting a predictable id on ESO types that have not opted out throws an error', async () => {
const id = 'my_predictable_id';
const savedObjectOriginalAttributes = {
publicProperty: randomness.string(),
publicPropertyStoredEncrypted: randomness.string(),
privateProperty: randomness.string(),
publicPropertyExcludedFromAAD: randomness.string(),
};
const { body: response } = await supertest
.post(`/api/saved_objects/saved-object-with-secret/${id}`)
.set('kbn-xsrf', 'xxx')
.send({ attributes: savedObjectOriginalAttributes })
.expect(400);
expect(response.message).to.contain(
'Predefined IDs are not allowed for saved objects with encrypted attributes unless the ID is a UUID.'
);
});
it('#bulkCreate setting random ID on ESO types that have not opted out throws an error', async () => {
const bulkCreateParams = [
{
type: SAVED_OBJECT_WITH_SECRET_TYPE,
id: 'my_predictable_id',
attributes: {
publicProperty: randomness.string(),
publicPropertyExcludedFromAAD: randomness.string(),
publicPropertyStoredEncrypted: randomness.string(),
privateProperty: randomness.string(),
},
},
{
type: SAVED_OBJECT_WITH_SECRET_TYPE,
id: 'my_predictable_id_2',
attributes: {
publicProperty: randomness.string(),
publicPropertyExcludedFromAAD: randomness.string(),
publicPropertyStoredEncrypted: randomness.string(),
privateProperty: randomness.string(),
},
},
];
const {
body: { saved_objects: savedObjects },
} = await supertest
.post('/api/saved_objects/_bulk_create')
.set('kbn-xsrf', 'xxx')
.send(bulkCreateParams)
.expect(200);
expect(savedObjects).to.have.length(bulkCreateParams.length);
savedObjects.forEach((savedObject: any) => {
expect(savedObject.error.message).to.contain(
'Predefined IDs are not allowed for saved objects with encrypted attributes unless the ID is a UUID.'
);
});
});
});
});
});
}