Skip to content

Commit

Permalink
Allow encrypted saved-object properties to be accessed by end-users (#…
Browse files Browse the repository at this point in the history
…64941)

Co-authored-by: kobelb <[email protected]>
  • Loading branch information
azasypkin and kobelb authored May 14, 2020
1 parent 0fd5311 commit 65186b3
Show file tree
Hide file tree
Showing 18 changed files with 1,710 additions and 336 deletions.
1 change: 1 addition & 0 deletions x-pack/plugins/encrypted_saved_objects/kibana.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"version": "8.0.0",
"kibanaVersion": "kibana",
"configPath": ["xpack", "encryptedSavedObjects"],
"optionalPlugins": ["security"],
"server": true,
"ui": false
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@
*/

import { EncryptedSavedObjectsAuditLogger } from './audit_logger';
import { mockAuthenticatedUser } from '../../../security/common/model/authenticated_user.mock';

test('properly logs audit events', () => {
it('properly logs audit events', () => {
const mockInternalAuditLogger = { log: jest.fn() };
const audit = new EncryptedSavedObjectsAuditLogger(() => mockInternalAuditLogger);

Expand All @@ -19,6 +20,11 @@ test('properly logs audit events', () => {
id: 'object-id-ns',
namespace: 'object-ns',
});
audit.encryptAttributesSuccess(
['one', 'two'],
{ type: 'known-type-ns', id: 'object-id-ns', namespace: 'object-ns' },
mockAuthenticatedUser()
);

audit.decryptAttributesSuccess(['three', 'four'], {
type: 'known-type-1',
Expand All @@ -29,6 +35,11 @@ test('properly logs audit events', () => {
id: 'object-id-1-ns',
namespace: 'object-ns',
});
audit.decryptAttributesSuccess(
['three', 'four'],
{ type: 'known-type-1-ns', id: 'object-id-1-ns', namespace: 'object-ns' },
mockAuthenticatedUser()
);

audit.encryptAttributeFailure('five', {
type: 'known-type-2',
Expand All @@ -39,6 +50,11 @@ test('properly logs audit events', () => {
id: 'object-id-2-ns',
namespace: 'object-ns',
});
audit.encryptAttributeFailure(
'five',
{ type: 'known-type-2-ns', id: 'object-id-2-ns', namespace: 'object-ns' },
mockAuthenticatedUser()
);

audit.decryptAttributeFailure('six', {
type: 'known-type-3',
Expand All @@ -49,8 +65,13 @@ test('properly logs audit events', () => {
id: 'object-id-3-ns',
namespace: 'object-ns',
});
audit.decryptAttributeFailure(
'six',
{ type: 'known-type-3-ns', id: 'object-id-3-ns', namespace: 'object-ns' },
mockAuthenticatedUser()
);

expect(mockInternalAuditLogger.log).toHaveBeenCalledTimes(8);
expect(mockInternalAuditLogger.log).toHaveBeenCalledTimes(12);
expect(mockInternalAuditLogger.log).toHaveBeenCalledWith(
'encrypt_success',
'Successfully encrypted attributes "[one,two]" for saved object "[known-type,object-id]".',
Expand All @@ -66,6 +87,17 @@ test('properly logs audit events', () => {
attributesNames: ['one', 'two'],
}
);
expect(mockInternalAuditLogger.log).toHaveBeenCalledWith(
'encrypt_success',
'Successfully encrypted attributes "[one,two]" for saved object "[object-ns,known-type-ns,object-id-ns]".',
{
id: 'object-id-ns',
type: 'known-type-ns',
namespace: 'object-ns',
attributesNames: ['one', 'two'],
username: 'user',
}
);

expect(mockInternalAuditLogger.log).toHaveBeenCalledWith(
'decrypt_success',
Expand All @@ -82,6 +114,17 @@ test('properly logs audit events', () => {
attributesNames: ['three', 'four'],
}
);
expect(mockInternalAuditLogger.log).toHaveBeenCalledWith(
'decrypt_success',
'Successfully decrypted attributes "[three,four]" for saved object "[object-ns,known-type-1-ns,object-id-1-ns]".',
{
id: 'object-id-1-ns',
type: 'known-type-1-ns',
namespace: 'object-ns',
attributesNames: ['three', 'four'],
username: 'user',
}
);

expect(mockInternalAuditLogger.log).toHaveBeenCalledWith(
'encrypt_failure',
Expand All @@ -93,6 +136,17 @@ test('properly logs audit events', () => {
'Failed to encrypt attribute "five" for saved object "[object-ns,known-type-2-ns,object-id-2-ns]".',
{ id: 'object-id-2-ns', type: 'known-type-2-ns', namespace: 'object-ns', attributeName: 'five' }
);
expect(mockInternalAuditLogger.log).toHaveBeenCalledWith(
'encrypt_failure',
'Failed to encrypt attribute "five" for saved object "[object-ns,known-type-2-ns,object-id-2-ns]".',
{
id: 'object-id-2-ns',
type: 'known-type-2-ns',
namespace: 'object-ns',
attributeName: 'five',
username: 'user',
}
);

expect(mockInternalAuditLogger.log).toHaveBeenCalledWith(
'decrypt_failure',
Expand All @@ -104,4 +158,15 @@ test('properly logs audit events', () => {
'Failed to decrypt attribute "six" for saved object "[object-ns,known-type-3-ns,object-id-3-ns]".',
{ id: 'object-id-3-ns', type: 'known-type-3-ns', namespace: 'object-ns', attributeName: 'six' }
);
expect(mockInternalAuditLogger.log).toHaveBeenCalledWith(
'decrypt_failure',
'Failed to decrypt attribute "six" for saved object "[object-ns,known-type-3-ns,object-id-3-ns]".',
{
id: 'object-id-3-ns',
type: 'known-type-3-ns',
namespace: 'object-ns',
attributeName: 'six',
username: 'user',
}
);
});
Original file line number Diff line number Diff line change
Expand Up @@ -6,56 +6,67 @@

import { SavedObjectDescriptor, descriptorToArray } from '../crypto';
import { LegacyAPI } from '../plugin';
import { AuthenticatedUser } from '../../../security/common/model';

/**
* Represents all audit events the plugin can log.
*/
export class EncryptedSavedObjectsAuditLogger {
constructor(private readonly getAuditLogger: () => LegacyAPI['auditLogger']) {}

public encryptAttributeFailure(attributeName: string, descriptor: SavedObjectDescriptor) {
public encryptAttributeFailure(
attributeName: string,
descriptor: SavedObjectDescriptor,
user?: AuthenticatedUser
) {
this.getAuditLogger().log(
'encrypt_failure',
`Failed to encrypt attribute "${attributeName}" for saved object "[${descriptorToArray(
descriptor
)}]".`,
{ ...descriptor, attributeName }
{ ...descriptor, attributeName, username: user?.username }
);
}

public decryptAttributeFailure(attributeName: string, descriptor: SavedObjectDescriptor) {
public decryptAttributeFailure(
attributeName: string,
descriptor: SavedObjectDescriptor,
user?: AuthenticatedUser
) {
this.getAuditLogger().log(
'decrypt_failure',
`Failed to decrypt attribute "${attributeName}" for saved object "[${descriptorToArray(
descriptor
)}]".`,
{ ...descriptor, attributeName }
{ ...descriptor, attributeName, username: user?.username }
);
}

public encryptAttributesSuccess(
attributesNames: readonly string[],
descriptor: SavedObjectDescriptor
descriptor: SavedObjectDescriptor,
user?: AuthenticatedUser
) {
this.getAuditLogger().log(
'encrypt_success',
`Successfully encrypted attributes "[${attributesNames}]" for saved object "[${descriptorToArray(
descriptor
)}]".`,
{ ...descriptor, attributesNames }
{ ...descriptor, attributesNames, username: user?.username }
);
}

public decryptAttributesSuccess(
attributesNames: readonly string[],
descriptor: SavedObjectDescriptor
descriptor: SavedObjectDescriptor,
user?: AuthenticatedUser
) {
this.getAuditLogger().log(
'decrypt_success',
`Successfully decrypted attributes "[${attributesNames}]" for saved object "[${descriptorToArray(
descriptor
)}]".`,
{ ...descriptor, attributesNames }
{ ...descriptor, attributesNames, username: user?.username }
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import { EncryptedSavedObjectTypeRegistration } from './encrypted_saved_objects_service';
import { EncryptedSavedObjectAttributesDefinition } from './encrypted_saved_object_type_definition';

it('correctly determines attribute properties', () => {
const attributes = ['attr#1', 'attr#2', 'attr#3', 'attr#4'];
const cases: Array<[
EncryptedSavedObjectTypeRegistration,
{
shouldBeEncrypted: boolean[];
shouldBeExcludedFromAAD: boolean[];
shouldBeStripped: boolean[];
}
]> = [
[
{
type: 'so-type',
attributesToEncrypt: new Set(['attr#1', 'attr#2', 'attr#3', 'attr#4']),
},
{
shouldBeEncrypted: [true, true, true, true],
shouldBeExcludedFromAAD: [true, true, true, true],
shouldBeStripped: [true, true, true, true],
},
],
[
{
type: 'so-type',
attributesToEncrypt: new Set(['attr#1', 'attr#2']),
},
{
shouldBeEncrypted: [true, true, false, false],
shouldBeExcludedFromAAD: [true, true, false, false],
shouldBeStripped: [true, true, false, false],
},
],
[
{
type: 'so-type',
attributesToEncrypt: new Set([{ key: 'attr#1' }, { key: 'attr#2' }]),
},
{
shouldBeEncrypted: [true, true, false, false],
shouldBeExcludedFromAAD: [true, true, false, false],
shouldBeStripped: [true, true, false, false],
},
],
[
{
type: 'so-type',
attributesToEncrypt: new Set(['attr#1', 'attr#2']),
attributesToExcludeFromAAD: new Set(['attr#3']),
},
{
shouldBeEncrypted: [true, true, false, false],
shouldBeExcludedFromAAD: [true, true, true, false],
shouldBeStripped: [true, true, false, false],
},
],
[
{
type: 'so-type',
attributesToEncrypt: new Set([
'attr#1',
'attr#2',
{ key: 'attr#4', dangerouslyExposeValue: true },
]),
attributesToExcludeFromAAD: new Set(['attr#3']),
},
{
shouldBeEncrypted: [true, true, false, true],
shouldBeExcludedFromAAD: [true, true, true, true],
shouldBeStripped: [true, true, false, false],
},
],
[
{
type: 'so-type',
attributesToEncrypt: new Set([
{ key: 'attr#1', dangerouslyExposeValue: true },
'attr#2',
{ key: 'attr#4', dangerouslyExposeValue: true },
]),
attributesToExcludeFromAAD: new Set(['some-other-attribute']),
},
{
shouldBeEncrypted: [true, true, false, true],
shouldBeExcludedFromAAD: [true, true, false, true],
shouldBeStripped: [false, true, false, false],
},
],
];

for (const [typeRegistration, asserts] of cases) {
const typeDefinition = new EncryptedSavedObjectAttributesDefinition(typeRegistration);
for (const [attributeIndex, attributeName] of attributes.entries()) {
expect(typeDefinition.shouldBeEncrypted(attributeName)).toBe(
asserts.shouldBeEncrypted[attributeIndex]
);
expect(typeDefinition.shouldBeStripped(attributeName)).toBe(
asserts.shouldBeStripped[attributeIndex]
);
expect(typeDefinition.shouldBeExcludedFromAAD(attributeName)).toBe(
asserts.shouldBeExcludedFromAAD[attributeIndex]
);
}
}
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import { EncryptedSavedObjectTypeRegistration } from './encrypted_saved_objects_service';

/**
* Represents the definition of the attributes of the specific saved object that are supposed to be
* encrypted. The definition also dictates which attributes should be excluded from AAD and/or
* stripped from response.
*/
export class EncryptedSavedObjectAttributesDefinition {
public readonly attributesToEncrypt: ReadonlySet<string>;
private readonly attributesToExcludeFromAAD: ReadonlySet<string> | undefined;
private readonly attributesToStrip: ReadonlySet<string>;

constructor(typeRegistration: EncryptedSavedObjectTypeRegistration) {
const attributesToEncrypt = new Set<string>();
const attributesToStrip = new Set<string>();
for (const attribute of typeRegistration.attributesToEncrypt) {
if (typeof attribute === 'string') {
attributesToEncrypt.add(attribute);
attributesToStrip.add(attribute);
} else {
attributesToEncrypt.add(attribute.key);
if (!attribute.dangerouslyExposeValue) {
attributesToStrip.add(attribute.key);
}
}
}

this.attributesToEncrypt = attributesToEncrypt;
this.attributesToStrip = attributesToStrip;
this.attributesToExcludeFromAAD = typeRegistration.attributesToExcludeFromAAD;
}

/**
* Determines whether particular attribute should be encrypted. Full list of attributes that
* should be encrypted can be retrieved via `attributesToEncrypt` property.
* @param attributeName Name of the attribute.
*/
public shouldBeEncrypted(attributeName: string) {
return this.attributesToEncrypt.has(attributeName);
}

/**
* Determines whether particular attribute should be excluded from AAD.
* @param attributeName Name of the attribute.
*/
public shouldBeExcludedFromAAD(attributeName: string) {
return (
this.shouldBeEncrypted(attributeName) ||
(this.attributesToExcludeFromAAD != null &&
this.attributesToExcludeFromAAD.has(attributeName))
);
}

/**
* Determines whether particular attribute should be stripped from the attribute list.
* @param attributeName Name of the attribute.
*/
public shouldBeStripped(attributeName: string) {
return this.attributesToStrip.has(attributeName);
}
}
Loading

0 comments on commit 65186b3

Please sign in to comment.