Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Secrets in kv store phase #0 #2631

Merged
merged 15 commits into from
Apr 26, 2016
Merged

Secrets in kv store phase #0 #2631

merged 15 commits into from
Apr 26, 2016

Conversation

lakshmi-kannan
Copy link
Contributor

@lakshmi-kannan lakshmi-kannan commented Apr 19, 2016

What?

This PR allows encrypted items to be stored in key value store. The encryption is symmetric AES256 encryption and we use keyczar to do this. The encryption key is global for all keys. So StackStorm admin can decrypt all the keys. However, with RBAC, we'll make sure only intended user (and admin) can decrypt the user's keys.

TODO

CLI examples

PUT

(virtualenv)vagrant@st2dev /mnt/src/storm/st2 (secrets_kv_store●●)$ st2 key set foo bar                                                                                                                                                       [ruby-1.9.3p484]
+------------------+-------+
| Property         | Value |
+------------------+-------+
| name             | foo   |
| value            | bar   |
| expire_timestamp |       |
+------------------+-------+
(virtualenv)vagrant@st2dev /mnt/src/storm/st2 (secrets_kv_store●●)$ st2 key set secret_foo secret_bar --encrypt                                                                                                                               [ruby-1.9.3p484]
+------------------+--------------------------------------------------------------+
| Property         | Value                                                        |
+------------------+--------------------------------------------------------------+
| name             | secret_foo                                                   |
| value            | 0083E1033A7C286818517F1DDD2190042C6F6161A4A6318CA7A7767B97E8 |
|                  | DCD2984F36B070A60BA3BFE0ACF0C583F6DE8233B7C842E2EAD2D6       |
| expire_timestamp |                                                              |
+------------------+--------------------------------------------------------------+
(virtualenv)vagrant@st2dev /mnt/src/storm/st2 (secrets_kv_store●●)$

GET

(virtualenv)vagrant@st2dev /mnt/src/storm/st2 (secrets_kv_store●●)$ st2 key get secret_foo --decrypt                                                                                                                                          [ruby-1.9.3p484]
+------------------+------------+
| Property         | Value      |
+------------------+------------+
| name             | secret_foo |
| value            | secret_bar |
| secret           | True       |
| encrypted        | False      |
| expire_timestamp |            |
+------------------+------------+
(virtualenv)vagrant@st2dev /mnt/src/storm/st2 (secrets_kv_store●●)$ st2 key get secret_foo                                                                                                                                                    [ruby-1.9.3p484]
+------------------+--------------------------------------------------------------+
| Property         | Value                                                        |
+------------------+--------------------------------------------------------------+
| name             | secret_foo                                                   |
| value            | 0083E1033A90EB483B7368A414E9456928FDAB3808830B553CA63691E045 |
|                  | 3990D8B4BEB0263B3F9363F1D19CF06BF70EC9B2D81F7F7F0F6199       |
| secret           | True                                                         |
| encrypted        | True                                                         |
| expire_timestamp |                                                              |
+------------------+--------------------------------------------------------------+
(virtualenv)vagrant@st2dev /mnt/src/storm/st2 (secrets_kv_store●●)$ st2 key get foo                                                                                                                                                           [ruby-1.9.3p484]
+------------------+-------+
| Property         | Value |
+------------------+-------+
| name             | foo   |
| value            | bar   |
| secret           | False |
| encrypted        | False |
| expire_timestamp |       |
+------------------+-------+
(virtualenv)vagrant@st2dev /mnt/src/storm/st2 (secrets_kv_store●●)$

List

(virtualenv)vagrant@st2dev /mnt/src/storm/st2 (secrets_kv_store●●)$ st2 key list --decrypt                                                                                                                                                   [ruby-1.9.3p484]

+------------+------------+--------+-----------+------------------+
| name       | value      | secret | encrypted | expire_timestamp |
+------------+------------+--------+-----------+------------------+
| secret_foo | secret_bar | True   | False     |                  |
| foo        | bar        | False  | False     |                  |
+------------+------------+--------+-----------+------------------+
(virtualenv)vagrant@st2dev /mnt/src/storm/st2 (secrets_kv_store●●)$                                                                                                                                                                          [ruby-1.9.3p484]
(virtualenv)vagrant@st2dev /mnt/src/storm/st2 (secrets_kv_store●●)$ st2 key list                                                                                                                                                             [ruby-1.9.3p484]
+------------+----------------------------------------------------+--------+-----------+------------------+
| name       | value                                              | secret | encrypted | expire_timestamp |
+------------+----------------------------------------------------+--------+-----------+------------------+
| secret_foo | 0083E1033A040EA17E5D5A61E1087675EEFED8C7A1BFF0927F | True   | True      |                  |
|            | EDEF0BE9C948044A87BE35DE9238FC67E707D377999C3FEA43 |        |           |                  |
|            | 17B4EF32371572                                     |        |           |                  |
| foo        | bar                                                | False  | False     |                  |
+------------+----------------------------------------------------+--------+-----------+------------------+
(virtualenv)vagrant@st2dev /mnt/src/storm/st2 (secrets_kv_store●●)$

API examples

No secret case

(virtualenv)vagrant@st2dev /mnt/src/storm/st2 (secrets_kv_store●●)$ http PUT http://127.0.0.1:9101/v1/keys/foo < kv_nosecret.json                                                                                                             [ruby-1.9.3p484]
HTTP/1.1 200 OK
Access-Control-Allow-Headers: Content-Type,Authorization,X-Auth-Token,St2-Api-Key,X-Request-ID
Access-Control-Allow-Methods: GET,POST,PUT,DELETE,OPTIONS
Access-Control-Allow-Origin: http://172.168.50.50:3000
Access-Control-Expose-Headers: Content-Type,X-Limit,X-Total-Count,X-Request-ID
Connection: keep-alive
Content-Length: 97
Content-Type: application/json; charset=UTF-8
Date: Thu, 21 Apr 2016 20:12:54 GMT
Server: gunicorn/19.4.5
X-Request-ID: 6244e7c9-bdd3-4040-9da5-2941e84391eb

{
    "encrypted": false,
    "name": "foo",
    "secret": false,
    "uid": "key_value_pair:foo",
    "value": "bar"
}

(virtualenv)vagrant@st2dev /mnt/src/storm/st2 (secrets_kv_store●●)$

Secret case. No encryption key setup by admin

XXX: Note I return 400 BAD REQUEST. Opinions welcome.

(virtualenv)vagrant@st2dev /mnt/src/storm/st2 (secrets_kv_store●)$ http PUT http://127.0.0.1:9101/v1/keys/secret_foo < kv.json                                                                                                               [ruby-1.9.3p484]
HTTP/1.1 400 Bad Request
Access-Control-Allow-Headers: Content-Type,Authorization,X-Auth-Token,St2-Api-Key,X-Request-ID
Access-Control-Allow-Methods: GET,POST,PUT,DELETE,OPTIONS
Access-Control-Allow-Origin: http://172.168.50.50:3000
Access-Control-Expose-Headers: Content-Type,X-Limit,X-Total-Count,X-Request-ID
Connection: keep-alive
Content-Length: 131
Content-Type: application/json
Date: Tue, 19 Apr 2016 23:43:26 GMT
Server: gunicorn/19.4.5
X-Request-ID: 14ae1cf2-f6be-472b-b1e4-8fa46b4d6148

{
    "faultstring": "Crypto key not found in conf/st2_kvstore_demo.crypto.key.json. Unable to encrypt value for key secret_foo."
}

(virtualenv)vagrant@st2dev /mnt/src/storm/st2 (secrets_kv_store●)$

Secret case. Encryption key setup.

(virtualenv)vagrant@st2dev /mnt/src/storm/st2 (secrets_kv_store●●)$ http PUT http://127.0.0.1:9101/v1/keys/secret_foo < kv.json                                                                                                               [ruby-1.9.3p484]
HTTP/1.1 200 OK
Access-Control-Allow-Headers: Content-Type,Authorization,X-Auth-Token,St2-Api-Key,X-Request-ID
Access-Control-Allow-Methods: GET,POST,PUT,DELETE,OPTIONS
Access-Control-Allow-Origin: http://172.168.50.50:3000
Access-Control-Expose-Headers: Content-Type,X-Limit,X-Total-Count,X-Request-ID
Connection: keep-alive
Content-Length: 220
Content-Type: application/json; charset=UTF-8
Date: Thu, 21 Apr 2016 20:13:26 GMT
Server: gunicorn/19.4.5
X-Request-ID: 41cc580c-7557-4e1b-9e32-df4672da7a63

{
    "encrypted": true,
    "name": "secret_foo",
    "secret": true,
    "uid": "key_value_pair:secret_foo",
    "value": "0083E1033A66FF9293F6715BCF5B3918E3A18E2281B6A45B08603032571E3812C6D9CEE25BD1E060DADD8E951C85E4479C1C1BE91835BACEF1"
}

(virtualenv)vagrant@st2dev /mnt/src/storm/st2 (secrets_kv_store●●)$

(virtualenv)vagrant@st2dev /mnt/src/storm/st2 (secrets_kv_store●●)$ http GET "http://127.0.0.1:9101/v1/keys/secret_foo?decrypt=False"                                                                                                         [ruby-1.9.3p484]
HTTP/1.1 200 OK
Access-Control-Allow-Headers: Content-Type,Authorization,X-Auth-Token,St2-Api-Key,X-Request-ID
Access-Control-Allow-Methods: GET,POST,PUT,DELETE,OPTIONS
Access-Control-Allow-Origin: http://172.168.50.50:3000
Access-Control-Expose-Headers: Content-Type,X-Limit,X-Total-Count,X-Request-ID
Connection: keep-alive
Content-Length: 220
Content-Type: application/json; charset=UTF-8
Date: Thu, 21 Apr 2016 20:13:58 GMT
Server: gunicorn/19.4.5
X-Request-ID: 8ee5d905-5878-47ca-98d1-118ccddc5a3d

{
    "encrypted": true,
    "name": "secret_foo",
    "secret": true,
    "uid": "key_value_pair:secret_foo",
    "value": "0083E1033A66FF9293F6715BCF5B3918E3A18E2281B6A45B08603032571E3812C6D9CEE25BD1E060DADD8E951C85E4479C1C1BE91835BACEF1"
}

(virtualenv)vagrant@st2dev /mnt/src/storm/st2 (secrets_kv_store●●)$ http GET "http://127.0.0.1:9101/v1/keys/secret_foo?decrypt=True"                                                                                                          [ruby-1.9.3p484]
HTTP/1.1 200 OK
Access-Control-Allow-Headers: Content-Type,Authorization,X-Auth-Token,St2-Api-Key,X-Request-ID
Access-Control-Allow-Methods: GET,POST,PUT,DELETE,OPTIONS
Access-Control-Allow-Origin: http://172.168.50.50:3000
Access-Control-Expose-Headers: Content-Type,X-Limit,X-Total-Count,X-Request-ID
Connection: keep-alive
Content-Length: 117
Content-Type: application/json; charset=UTF-8
Date: Thu, 21 Apr 2016 20:14:03 GMT
Server: gunicorn/19.4.5
X-Request-ID: 3a5ad5a8-bb68-472b-854f-3d3764b22b28

{
    "encrypted": false,
    "name": "secret_foo",
    "secret": true,
    "uid": "key_value_pair:secret_foo",
    "value": "secret_bar"
}

(virtualenv)vagrant@st2dev /mnt/src/storm/st2 (secrets_kv_store●●)$
> db.key_value_pair_d_b.find({'name': 'secret_foo'})
{ "_id" : ObjectId("5717e445d9d7ed0ea5b07ec7"), "uid" : "key_value_pair:secret_foo", "name" : "secret_foo", "value" : "0083E1033AE5F3D1B045398FC51F1046927971A46502A5A5BEB72ED37FB32073709569E37D081D9D38BF3E96F8A9F4D1EA5FCE349D6D18B3EB", "encrypted" : true }
>

@manasdk
Copy link
Contributor

manasdk commented Apr 22, 2016

Secret case. No encryption key setup by admin

I am ok with 400. I guess we are saying for the current configuration state this request is bad.

@@ -57,8 +76,39 @@ class KeyValuePairAPI(BaseAPI):
'additionalProperties': False
}

@staticmethod
def _setup_crypto():
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would imagine encryption and decryption for kvp being handled in the persistence or the db modules. This way its not an API only feature and it would be possible to get decrypted keys even if not using the API i.e. from services do not use the API layer.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I can move the code around when we address that.

@manasdk
Copy link
Contributor

manasdk commented Apr 22, 2016

st2 key list --decrypt

Would it be safer to not support a list level decrypt? I wonder if there is value in providing this in the first place.

@manasdk
Copy link
Contributor

manasdk commented Apr 22, 2016

Overall +1 to design and impl.

Once you add some tests and docs around this good to go :)

@lakshmi-kannan
Copy link
Contributor Author

Would it be safer to not support a list level decrypt?

Well, with RBAC this might get even harder but it is something we need to solve I think. I was debating it myself too. Let's wait for another input to break the tie.

'Initialization Vector' per run and the IV is part of the output.

:param encrypt_key: Symmetric AES key to use for encryption.
:type encrypt_key: :class:`keyczar.keys.AesKey`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for this method does it matter if its an AesKey? Is there is higher interface/superclass?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep. Unfortunately it does. The Encrypt method is on the key object which is keyczar and AES specific implementation.

@Kami
Copy link
Member

Kami commented Apr 25, 2016

Tool for generating AES key in tools/

Keyczar already provides a tool for managing and rotating keys. Do you plan to build a wrapper around that or something else?

@@ -0,0 +1 @@
{"hmacKey": {"hmacKeyString": "685EWJ8U2U_c3Ulv9ibB6aa0Y9aYm-0bCwe2mqjLJuw", "size": 256}, "aesKeyString": "eKGFrfNEUhAgX0uofaU1wQ", "mode": "CBC", "size": 128}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For fixtures that's a fine path, but for actual key on production, we should advise users to put in a path with limited access (e.g. /etc/private or similar).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, will say that in docs.

@Kami
Copy link
Member

Kami commented Apr 25, 2016

I'm not totally against this approach, but as mentioned on Slack, I would prefer to use public key cryptography.

This will make it easier for us to eventually move to a safer model, where we have a single service (or similar) which is responsible for decrypting data (this would be the only service which has access to the private key, reducing the surface area), but other services (e.g. API) will still be able to encrypt data using the public key...

@lakshmi-kannan
Copy link
Contributor Author

I'm not totally against this approach, but as mentioned on Slack, I would prefer to use public key cryptography.

We can swap it out if needed. I don't see this as a big problem now that we have API & CLI changes.

@mock.patch.object(
models.ResourceManager, 'get_by_id',
mock.MagicMock(return_value=None))
def test_command_get_by_name(self):
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I removed this bogus test. The underlying code was anyways calling get_by_id and we are already testing that in previous test case.

@lakshmi-kannan
Copy link
Contributor Author

Keyczar already provides a tool for managing and rotating keys. Do you plan to build a wrapper around that or something else?

Correct - a wrapper. Some argparse + keyczar + fwrite()

@dzimine
Copy link

dzimine commented Apr 25, 2016

👍 for design, thanks! didn't look into impl. yet.

st2 key list --decrypt

Would it be safer to not support a list level decrypt? I wonder if there is value in providing this in the first place.

The use case is, for instance, migration by admin to another node. As admin, I dump all KV with st2 key list --decrypt -j and recreate it on the other one. Here having "encrypted" value helps too, to hint to decrypt it when json is loaded.

@lakshmi-kannan lakshmi-kannan changed the title WIP: Secrets in kv store Secrets in kv store phase #0 Apr 26, 2016
@lakshmi-kannan lakshmi-kannan merged commit d0345b7 into master Apr 26, 2016
@lakshmi-kannan lakshmi-kannan deleted the secrets_kv_store branch April 26, 2016 00:07
@manasdk
Copy link
Contributor

manasdk commented Apr 26, 2016

As admin, I dump all KV with st2 key list --decrypt -j and recreate it on the other one.

Would migrating the encrypted data as well as the key work just as well?

@Kami
Copy link
Member

Kami commented Apr 26, 2016

Re saving and serializing encrypted data - I can't recall if Keyczar already does that, but if doesn't we should update serialized data so it includes header with the encryption information (which algorithm was used, etc.).

This will makes this such as migrating to a different algorithm or similar easier.

@@ -240,7 +240,8 @@ def query(self, **kwargs):
if 'limit' in kwargs and kwargs.get('limit') <= 0:
kwargs.pop('limit')
token = kwargs.get('token', None)
params = {}
params = kwargs.get('params', {})
print(params)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably need to remove print here?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed it in 406a701.

@lakshmi-kannan
Copy link
Contributor Author

Re saving and serializing encrypted data - I can't recall if Keyczar already does that, but if doesn't we should update serialized data so it includes header with the encryption information (which algorithm was used, etc.).

It does not. It assumes symmetric crypto key to be always AES. The library doesn't support other symmetric crypto algos. https://github.com/google/keyczar/blob/master/python/src/keyczar/keys.py#L425

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants