|
| 1 | +- Start Date: 2019-03-22 |
| 2 | +- RFC PR: [#33740](https://github.com/elastic/kibana/pull/33740) |
| 3 | +- Kibana Issue: (leave this empty) |
| 4 | + |
| 5 | +# Summary |
| 6 | + |
| 7 | +In order to support the action service we need a way to encrypt/decrypt |
| 8 | +attributes on saved objects that works with security and spaces filtering as |
| 9 | +well as performing audit logging. Sufficiently hides the private key used and |
| 10 | +removes encrypted attributes from being exposed through regular means. |
| 11 | + |
| 12 | +# Basic example |
| 13 | + |
| 14 | +Register saved object type with the `encrypted_saved_objects` plugin: |
| 15 | + |
| 16 | +```typescript |
| 17 | +server.plugins.encrypted_saved_objects.registerType({ |
| 18 | + type: 'server-action', |
| 19 | + attributesToEncrypt: new Set(['credentials', 'apiKey']), |
| 20 | +}); |
| 21 | +``` |
| 22 | + |
| 23 | +Use the same API to create saved objects with encrypted attributes as for any other saved object type: |
| 24 | + |
| 25 | +```typescript |
| 26 | +const savedObject = await server.savedObjects |
| 27 | + .getScopedSavedObjectsClient(request) |
| 28 | + .create('server-action', { |
| 29 | + name: 'my-server-action', |
| 30 | + data: { location: 'BBOX (100.0, ..., 0.0)', email: '<html>...</html>' }, |
| 31 | + credentials: { username: 'some-user', password: 'some-password' }, |
| 32 | + apiKey: 'dGhpcyBpcyBub3QgYSByZWFsIHRva2VuIGJ1dCBpdCBpcyBvb' |
| 33 | + }); |
| 34 | + |
| 35 | +// savedObject = { |
| 36 | +// id: 'dd9750b9-ef0a-444c-8405-4dfcc2e9d670', |
| 37 | +// type: 'server-action', |
| 38 | +// name: 'my-server-action', |
| 39 | +// data: { location: 'BBOX (100.0, ..., 0.0)', email: '<html>...</html>' }, |
| 40 | +// }; |
| 41 | + |
| 42 | +``` |
| 43 | + |
| 44 | +Use dedicated method to retrieve saved object with decrypted attributes on behalf of Kibana internal user: |
| 45 | + |
| 46 | +```typescript |
| 47 | +const savedObject = await server.plugins.encrypted_saved_objects.getDecryptedAsInternalUser( |
| 48 | + 'server-action', |
| 49 | + 'dd9750b9-ef0a-444c-8405-4dfcc2e9d670' |
| 50 | +); |
| 51 | + |
| 52 | +// savedObject = { |
| 53 | +// id: 'dd9750b9-ef0a-444c-8405-4dfcc2e9d670', |
| 54 | +// type: 'server-action', |
| 55 | +// name: 'my-server-action', |
| 56 | +// data: { location: 'BBOX (100.0, ..., 0.0)', email: '<html>...</html>' }, |
| 57 | +// credentials: { username: 'some-user', password: 'some-password' }, |
| 58 | +// apiKey: 'dGhpcyBpcyBub3QgYSByZWFsIHRva2VuIGJ1dCBpdCBpcyBvb', |
| 59 | +// }; |
| 60 | +``` |
| 61 | + |
| 62 | +# Motivation |
| 63 | + |
| 64 | +Main motivation is the storage and usage of third-party credentials for use with |
| 65 | +the action service to do notifications. Also perform other types integrations, |
| 66 | +call webhooks using tokens. |
| 67 | + |
| 68 | +# Detailed design |
| 69 | + |
| 70 | +In order for this to be in basic it needs to be done as a wrapper around the |
| 71 | +saved object client. This can be added from the `x-pack` plugin. |
| 72 | + |
| 73 | +## General |
| 74 | + |
| 75 | +To be able to manage saved objects with encrypted attributes from any plugin one should |
| 76 | +do the following: |
| 77 | + |
| 78 | +1. Define `encrypted_saved_objects` plugin as a dependency. |
| 79 | +2. Add attributes to be encrypted in `mappings.json` file for the respective saved object type. These attributes should |
| 80 | +always have a `binary` type since they'll contain encrypted content as a `Base64` encoded string and should never be |
| 81 | +searchable or analyzed. This makes defining of attributes that require encryption explicit and auditable, and significantly |
| 82 | +simplifies implementation: |
| 83 | +```json |
| 84 | +{ |
| 85 | + "server-action": { |
| 86 | + "properties": { |
| 87 | + "name": { "type": "keyword" }, |
| 88 | + "data": { |
| 89 | + "properties": { |
| 90 | + "location": { "type": "geo_shape" }, |
| 91 | + "email": { "type": "text" } |
| 92 | + } |
| 93 | + }, |
| 94 | + "credentials": { "type": "binary" }, |
| 95 | + "apiKey": { "type": "binary" } |
| 96 | + } |
| 97 | + } |
| 98 | +} |
| 99 | +``` |
| 100 | +3. Register saved object type and attributes that should be encrypted with `encrypted_saved_objects` plugin: |
| 101 | +```typescript |
| 102 | +server.plugins.encrypted_saved_objects.registerType({ |
| 103 | + type: 'server-action', |
| 104 | + attributesToEncrypt: new Set(['credentials', 'apiKey']), |
| 105 | + attributesToExcludeFromAAD: new Set(['data']), |
| 106 | +}); |
| 107 | +``` |
| 108 | + |
| 109 | +Notice the optional `attributesToExcludeFromAAD` property, it allows one to exclude some of the saved object attributes |
| 110 | +from Additional authenticated data (AAD), read more about that below in `Encryption and decryption` section. |
| 111 | + |
| 112 | +Since `encrypted_saved_objects` adds its own wrapper (`EncryptedSavedObjectsClientWrapper`) into `SavedObjectsClient` |
| 113 | +wrapper chain consumers will be able to create, update, delete and retrieve saved objects using standard Saved Objects API. |
| 114 | +Two main responsibilities of the wrapper are: |
| 115 | + |
| 116 | +* It encrypts attributes that are supposed to be encrypted during `create`, `bulkCreate` and `update` operations |
| 117 | +* It strips encrypted attributes from **any** saved object returned from the Saved Objects API |
| 118 | + |
| 119 | +As noted above the wrapper is stripping encrypted attributes from saved objects returned from the API methods, that means |
| 120 | +that there is no way at all to retrieve encrypted attributes using standard Saved Objects API unless `encrypted_saved_objects` |
| 121 | +plugin is disabled. This potentially can lead to the situation when consumer retrieves saved object, updates its non-encrypted |
| 122 | +properties and passes that same object to the `update` Saved Objects API method without re-defining encrypted attributes. In |
| 123 | +this case only specified attributes will be updated and encrypted attributes will stay untouched. And if these updated |
| 124 | +attributes are included into AAD, that is true by default for all attributes unless they are specifically excluded via |
| 125 | +`attributesToExcludeFromAAD`, then it will be no longer possible to decrypt encrypted attributes. At this stage we consider |
| 126 | +this as a developer mistake and don't prevent it from happening in any way apart from logging this type of event. Partial |
| 127 | +update of only attributes that are not the part of AAD will not cause this issue. |
| 128 | + |
| 129 | +Saved object ID is an essential part of AAD used during encryption process and hence should be as hard to guess as possible. |
| 130 | +To fulfil this requirement wrapper generates highly random IDs (UUIDv4) for the saved objects that contain encrypted |
| 131 | +attributes and hence consumers are not allowed to specify ID when calling `create` or `bulkCreate` method and if they try |
| 132 | +to do so the error will be thrown. |
| 133 | + |
| 134 | +To reduce the risk of unintentional decryption and consequent leaking of the sensitive information there is only one way |
| 135 | +to retrieve saved object and decrypt its encrypted attributes and it's exposed only through `encrypted_saved_objects` plugin: |
| 136 | + |
| 137 | +```typescript |
| 138 | +const savedObject = await server.plugins.encrypted_saved_objects.getDecryptedAsInternalUser( |
| 139 | + 'server-action', |
| 140 | + 'dd9750b9-ef0a-444c-8405-4dfcc2e9d670' |
| 141 | +); |
| 142 | + |
| 143 | +// savedObject = { |
| 144 | +// id: 'dd9750b9-ef0a-444c-8405-4dfcc2e9d670', |
| 145 | +// type: 'server-action', |
| 146 | +// name: 'my-server-action', |
| 147 | +// data: { location: 'BBOX (100.0, ..., 0.0)', email: '<html>...</html>' }, |
| 148 | +// credentials: { username: 'some-user', password: 'some-password' }, |
| 149 | +// apiKey: 'dGhpcyBpcyBub3QgYSByZWFsIHRva2VuIGJ1dCBpdCBpcyBvb', |
| 150 | +// }; |
| 151 | +``` |
| 152 | + |
| 153 | +As can be seen from the method name, the request to retrieve saved object and decrypt its attributes is performed on |
| 154 | +behalf of the internal Kibana user and hence isn't supposed to be called within user request context. |
| 155 | + |
| 156 | +**Note:** the fact that saved object with encrypted attributes is created using standard Saved Objects API within a |
| 157 | +particular user and space context, but retrieved out of any context makes it unclear how consumers are supposed to |
| 158 | +provide that context and retrieve saved object from a particular space. Current plan for `getDecryptedAsInternalUser` |
| 159 | +method is to accept a third `BaseOptions` argument that allows consumers to specify `namespace` that they can retrieve |
| 160 | +from the request using public `spaces` plugin API. |
| 161 | + |
| 162 | +## Encryption and decryption |
| 163 | + |
| 164 | +Saved object attributes are encrypted using [@elastic/node-crypto](https://github.com/elastic/node-crypto) library. Please |
| 165 | +take a look at the source code of this library to know how encryption is performed exactly, what algorithm and encryption |
| 166 | +parameters are used, but in short it's AES Encryption with AES-256-GCM that uses random initialization vector and salt. |
| 167 | + |
| 168 | +As with encryption key for Kibana's session cookie, master encryption key used by `encrypted_saved_objects` plugin can be |
| 169 | +defined as a configuration value (`xpack.encryptedSavedObjects.encryptionKey`) via `kibana.yml`, but it's **highly |
| 170 | +recommended** to define this key in the [Kibana Keystore](https://www.elastic.co/guide/en/kibana/current/secure-settings.html) |
| 171 | +instead. The master key should be cryptographically safe and be equal or greater than 32 bytes. |
| 172 | + |
| 173 | +To prevent certain vectors of attacks where raw content of encrypted attributes of one saved object is copied to another |
| 174 | +saved object which would unintentionally allow it to decrypt content that was not supposed to be decrypted we rely on Additional |
| 175 | +authenticated data (AAD) during encryption and decryption. AAD consists of the following components: |
| 176 | + |
| 177 | +* Saved object ID |
| 178 | +* Saved object type |
| 179 | +* Saved object attributes |
| 180 | + |
| 181 | +AAD does not include encrypted attributes themselves and attributes defined in optional `attributesToExcludeFromAAD` |
| 182 | +parameter provided during saved object type registration with `encrypted_saved_objects` plugin. There are a number of |
| 183 | +reasons why one would want to exclude certain attributes from AAD: |
| 184 | + |
| 185 | +* if attribute contains large amount of data that can significantly slow down encryption and decryption, especially during |
| 186 | +bulk operations (e.g. large geo shape or arbitrary HTML document) |
| 187 | +* if attribute contains data that is supposed to be updated separately from encrypted attributes or attributes included |
| 188 | +into AAD (e.g some user defined content associated with the email action or alert) |
| 189 | + |
| 190 | +## Audit |
| 191 | + |
| 192 | +Encrypted attributes will most likely contain sensitive information and any attempt to access these should be properly |
| 193 | +logged to allow any further audit procedures. The following events will be logged with Kibana audit log functionality: |
| 194 | + |
| 195 | +* Successful attempt to encrypt attributes (incl. saved object ID, type and attributes names) |
| 196 | +* Failed attempt to encrypt attribute (incl. saved object ID, type and attribute name) |
| 197 | +* Successful attempt to decrypt attributes (incl. saved object ID, type and attributes names) |
| 198 | +* Failed attempt to decrypt attribute (incl. saved object ID, type and attribute name) |
| 199 | + |
| 200 | +In addition to audit log events we'll issue ordinary log events for any attempts to save, update or decrypt saved objects |
| 201 | +with missing attributes that were supposed to be encrypted/decrypted based on the registration parameters. |
| 202 | + |
| 203 | +# Benefits |
| 204 | + |
| 205 | +* None of the registered types will expose their encrypted details. The saved |
| 206 | +objects with their unencrypted attributes could still be obtained and searched |
| 207 | +on. The wrapper will follow all the security and spaces filtering of saved |
| 208 | +objects so that only users with appropriate permissions will be able to obtain |
| 209 | +the scrubbed objects or _save_ objects with encrypted attributes. |
| 210 | + |
| 211 | +* No explicit access to a method that takes in an encrypted string exists. If the |
| 212 | +type was not registered no decryption is possible. No need to handle the saved object |
| 213 | +with the encrypted attributes reducing the risk of accidentally returning it in a |
| 214 | +handler. |
| 215 | + |
| 216 | +# Drawbacks |
| 217 | + |
| 218 | +* It isn't possible to decrypt existing encrypted attributes once encryption key changes |
| 219 | +* Possibly have a performance impact on Saved Objects API operations that require encryption/decryption |
| 220 | +* Will require non trivial tests to test functionality along with spaces and security |
| 221 | +* The attributes that are encrypted have to be defined and if they change they need to be migrated |
| 222 | + |
| 223 | +# Out of scope |
| 224 | + |
| 225 | +* Encryption key rotation mechanism, either regular or emergency |
| 226 | +* Mechanism that would detect and warn when Kibana does not use keystore to store encryption key |
| 227 | + |
| 228 | +# Alternatives |
| 229 | + |
| 230 | +Only allow this to be used within the Actions service itself where the details |
| 231 | +of the saved object are handled there directly. And the saved objects are |
| 232 | +`hidden` but still use the security and spaces wrappers. |
| 233 | + |
| 234 | +# Adoption strategy |
| 235 | + |
| 236 | +Integration should be pretty easy which would include depending on the plugin, registering the desired saved object type |
| 237 | +with it and defining encrypted attributes in the `mappings.json`. |
| 238 | + |
| 239 | +# How we teach this |
| 240 | + |
| 241 | +The `encrypted_saved_objects` as the name of the `thing` where it's seen as a separate |
| 242 | +extension on top of the saved object service. |
| 243 | + |
| 244 | +Provide a README.md in the plugin directory with the usage examples. |
| 245 | + |
| 246 | +# Unresolved questions |
| 247 | + |
| 248 | +* Is it acceptable to have this plugin in Basic? |
| 249 | +* Are there any other use-cases that are not served with that interface? |
| 250 | +* How would this work with Saved Objects Export\Import API? |
| 251 | +* How would this work with migrations, if the attribute names wanted to be |
| 252 | + changed, a decrypt context would need to be created for migration? |
0 commit comments