12 KiB
- Start Date: 2019-03-22
- RFC PR: #33740
- Kibana Issue: (leave this empty)
Summary
In order to support the action service we need a way to encrypt/decrypt attributes on saved objects that works with security and spaces filtering as well as performing audit logging. Sufficiently hides the private key used and removes encrypted attributes from being exposed through regular means.
Basic example
Register saved object type with the encrypted_saved_objects
plugin:
server.plugins.encrypted_saved_objects.registerType({
type: 'server-action',
attributesToEncrypt: new Set(['credentials', 'apiKey']),
});
Use the same API to create saved objects with encrypted attributes as for any other saved object type:
const savedObject = await server.savedObjects
.getScopedSavedObjectsClient(request)
.create('server-action', {
name: 'my-server-action',
data: { location: 'BBOX (100.0, ..., 0.0)', email: '<html>...</html>' },
credentials: { username: 'some-user', password: 'some-password' },
apiKey: 'dGhpcyBpcyBub3QgYSByZWFsIHRva2VuIGJ1dCBpdCBpcyBvb'
});
// savedObject = {
// id: 'dd9750b9-ef0a-444c-8405-4dfcc2e9d670',
// type: 'server-action',
// name: 'my-server-action',
// data: { location: 'BBOX (100.0, ..., 0.0)', email: '<html>...</html>' },
// };
Use dedicated method to retrieve saved object with decrypted attributes on behalf of Kibana internal user:
const savedObject = await server.plugins.encrypted_saved_objects.getDecryptedAsInternalUser(
'server-action',
'dd9750b9-ef0a-444c-8405-4dfcc2e9d670'
);
// savedObject = {
// id: 'dd9750b9-ef0a-444c-8405-4dfcc2e9d670',
// type: 'server-action',
// name: 'my-server-action',
// data: { location: 'BBOX (100.0, ..., 0.0)', email: '<html>...</html>' },
// credentials: { username: 'some-user', password: 'some-password' },
// apiKey: 'dGhpcyBpcyBub3QgYSByZWFsIHRva2VuIGJ1dCBpdCBpcyBvb',
// };
Motivation
Main motivation is the storage and usage of third-party credentials for use with the action service to do notifications. Also perform other types integrations, call webhooks using tokens.
Detailed design
In order for this to be in basic it needs to be done as a wrapper around the
saved object client. This can be added from the x-pack
plugin.
General
To be able to manage saved objects with encrypted attributes from any plugin one should do the following:
- Define
encrypted_saved_objects
plugin as a dependency. - Add attributes to be encrypted in
mappings.json
file for the respective saved object type. These attributes should always have abinary
type since they'll contain encrypted content as aBase64
encoded string and should never be searchable or analyzed. This makes defining of attributes that require encryption explicit and auditable, and significantly simplifies implementation:
{
"server-action": {
"properties": {
"name": { "type": "keyword" },
"data": {
"properties": {
"location": { "type": "geo_shape" },
"email": { "type": "text" }
}
},
"credentials": { "type": "binary" },
"apiKey": { "type": "binary" }
}
}
}
- Register saved object type and attributes that should be encrypted with
encrypted_saved_objects
plugin:
server.plugins.encrypted_saved_objects.registerType({
type: 'server-action',
attributesToEncrypt: new Set(['credentials', 'apiKey']),
attributesToExcludeFromAAD: new Set(['data']),
});
Notice the optional attributesToExcludeFromAAD
property, it allows one to exclude some of the saved object attributes
from Additional authenticated data (AAD), read more about that below in Encryption and decryption
section.
Since encrypted_saved_objects
adds its own wrapper (EncryptedSavedObjectsClientWrapper
) into SavedObjectsClient
wrapper chain consumers will be able to create, update, delete and retrieve saved objects using standard Saved Objects API.
Two main responsibilities of the wrapper are:
- It encrypts attributes that are supposed to be encrypted during
create
,bulkCreate
andupdate
operations - It strips encrypted attributes from any saved object returned from the Saved Objects API
As noted above the wrapper is stripping encrypted attributes from saved objects returned from the API methods, that means
that there is no way at all to retrieve encrypted attributes using standard Saved Objects API unless encrypted_saved_objects
plugin is disabled. This potentially can lead to the situation when consumer retrieves saved object, updates its non-encrypted
properties and passes that same object to the update
Saved Objects API method without re-defining encrypted attributes. In
this case only specified attributes will be updated and encrypted attributes will stay untouched. And if these updated
attributes are included into AAD, that is true by default for all attributes unless they are specifically excluded via
attributesToExcludeFromAAD
, then it will be no longer possible to decrypt encrypted attributes. At this stage we consider
this as a developer mistake and don't prevent it from happening in any way apart from logging this type of event. Partial
update of only attributes that are not the part of AAD will not cause this issue.
Saved object ID is an essential part of AAD used during encryption process and hence should be as hard to guess as possible.
To fulfil this requirement wrapper generates highly random IDs (UUIDv4) for the saved objects that contain encrypted
attributes and hence consumers are not allowed to specify ID when calling create
or bulkCreate
method and if they try
to do so the error will be thrown.
To reduce the risk of unintentional decryption and consequent leaking of the sensitive information there is only one way
to retrieve saved object and decrypt its encrypted attributes and it's exposed only through encrypted_saved_objects
plugin:
const savedObject = await server.plugins.encrypted_saved_objects.getDecryptedAsInternalUser(
'server-action',
'dd9750b9-ef0a-444c-8405-4dfcc2e9d670'
);
// savedObject = {
// id: 'dd9750b9-ef0a-444c-8405-4dfcc2e9d670',
// type: 'server-action',
// name: 'my-server-action',
// data: { location: 'BBOX (100.0, ..., 0.0)', email: '<html>...</html>' },
// credentials: { username: 'some-user', password: 'some-password' },
// apiKey: 'dGhpcyBpcyBub3QgYSByZWFsIHRva2VuIGJ1dCBpdCBpcyBvb',
// };
As can be seen from the method name, the request to retrieve saved object and decrypt its attributes is performed on behalf of the internal Kibana user and hence isn't supposed to be called within user request context.
Note: the fact that saved object with encrypted attributes is created using standard Saved Objects API within a
particular user and space context, but retrieved out of any context makes it unclear how consumers are supposed to
provide that context and retrieve saved object from a particular space. Current plan for getDecryptedAsInternalUser
method is to accept a third BaseOptions
argument that allows consumers to specify namespace
that they can retrieve
from the request using public spaces
plugin API.
Encryption and decryption
Saved object attributes are encrypted using @elastic/node-crypto library. Please take a look at the source code of this library to know how encryption is performed exactly, what algorithm and encryption parameters are used, but in short it's AES Encryption with AES-256-GCM that uses random initialization vector and salt.
As with encryption key for Kibana's session cookie, master encryption key used by encrypted_saved_objects
plugin can be
defined as a configuration value (xpack.encrypted_saved_objects.encryptionKey
) via kibana.yml
, but it's highly
recommended to define this key in the Kibana Keystore
instead. The master key should be cryptographically safe and be equal or greater than 32 bytes.
To prevent certain vectors of attacks where raw content of encrypted attributes of one saved object is copied to another saved object which would unintentionally allow it to decrypt content that was not supposed to be decrypted we rely on Additional authenticated data (AAD) during encryption and decryption. AAD consists of the following components:
- Saved object ID
- Saved object type
- Saved object attributes
AAD does not include encrypted attributes themselves and attributes defined in optional attributesToExcludeFromAAD
parameter provided during saved object type registration with encrypted_saved_objects
plugin. There are a number of
reasons why one would want to exclude certain attributes from AAD:
- if attribute contains large amount of data that can significantly slow down encryption and decryption, especially during bulk operations (e.g. large geo shape or arbitrary HTML document)
- if attribute contains data that is supposed to be updated separately from encrypted attributes or attributes included into AAD (e.g some user defined content associated with the email action or alert)
Audit
Encrypted attributes will most likely contain sensitive information and any attempt to access these should be properly logged to allow any further audit procedures. The following events will be logged with Kibana audit log functionality:
- Successful attempt to encrypt attributes (incl. saved object ID, type and attributes names)
- Failed attempt to encrypt attribute (incl. saved object ID, type and attribute name)
- Successful attempt to decrypt attributes (incl. saved object ID, type and attributes names)
- Failed attempt to decrypt attribute (incl. saved object ID, type and attribute name)
In addition to audit log events we'll issue ordinary log events for any attempts to save, update or decrypt saved objects with missing attributes that were supposed to be encrypted/decrypted based on the registration parameters.
Benefits
-
None of the registered types will expose their encrypted details. The saved objects with their unencrypted attributes could still be obtained and searched on. The wrapper will follow all the security and spaces filtering of saved objects so that only users with appropriate permissions will be able to obtain the scrubbed objects or save objects with encrypted attributes.
-
No explicit access to a method that takes in an encrypted string exists. If the type was not registered no decryption is possible. No need to handle the saved object with the encrypted attributes reducing the risk of accidentally returning it in a handler.
Drawbacks
- It isn't possible to decrypt existing encrypted attributes once encryption key changes
- Possibly have a performance impact on Saved Objects API operations that require encryption/decryption
- Will require non trivial tests to test functionality along with spaces and security
- The attributes that are encrypted have to be defined and if they change they need to be migrated
Out of scope
- Encryption key rotation mechanism, either regular or emergency
- Mechanism that would detect and warn when Kibana does not use keystore to store encryption key
Alternatives
Only allow this to be used within the Actions service itself where the details
of the saved object are handled there directly. And the saved objects are
hidden
but still use the security and spaces wrappers.
Adoption strategy
Integration should be pretty easy which would include depending on the plugin, registering the desired saved object type
with it and defining encrypted attributes in the mappings.json
.
How we teach this
The encrypted_saved_objects
as the name of the thing
where it's seen as a separate
extension on top of the saved object service.
Provide a README.md in the plugin directory with the usage examples.
Unresolved questions
- Is it acceptable to have this plugin in Basic?
- Are there any other use-cases that are not served with that interface?
- How would this work with Saved Objects Export\Import API?
- How would this work with migrations, if the attribute names wanted to be changed, a decrypt context would need to be created for migration?