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

Added ability to pass in encrypted key/values into the CLI and API #4547

Merged
merged 16 commits into from
Mar 19, 2019

Conversation

nmaludy
Copy link
Member

@nmaludy nmaludy commented Feb 13, 2019

Closes #4545

This PR adds the ability to pass in encrypted key/value pairs on the CLI and via the API. The values remain encrypted and then are decrypted prior to being stored in the database.

To signify to the CLI that these values must be decrypted this adds a -d/--debug flag to st2 key set, example:

st2 key set -e -d myencryptedvalue ABC123

A new metadata parameter is now allowed in st2 key load data files called decrypt: true this signifies that the value for a given key is encrypted and needs to be decrypted before being processed.

- name: myencryptedvalue
  value: ABC123
  decrypt: true
  secret: true

Finally the API endpoint PUT /api/v1/keys/{name} accepts a new parameter decrypt that again signifies that the value is encrypted and must be decrypted:

PUT /api/v1/keys/myencryptedvalue
{
  "name": "myencryptedvalue",
  "value": "ABC123",
  "decrypt": true,
  "secret": true
}

The decrypting operation is handled within the API so that st2client doesn't rely on st2common. This also makes it a consistent experience from CLI and API.

TODO

@Kami Kami added this to the 3.0.0 milestone Feb 13, 2019
@arm4b arm4b requested a review from Kami February 14, 2019 20:03
@arm4b
Copy link
Member

arm4b commented Feb 14, 2019

FYI this comes from the StackStorm/puppet-st2#250 PR where @nmaludy had no choice but to copy entire python cryptography code for st2 decrypt.

This PR solves it by adding new option to CLI/API.

@Kami
Copy link
Member

Kami commented Mar 14, 2019

Thanks for contributing this change.

Could we avoid the decrypt -> encrypt round-trip and simply tell API the value is encrypted and to store it as-is?

The limitation of course is that the value needs to be encrypted with the same key which is used by the StackStorm installation. But that's also the case with this approach.

I guess the only downside of that approach would be that there is no "integrity check" on write - user would only find out that encrypted value is corrupted or not encrypted with the same key which is used on this particular installation on access / read time.

@nmaludy
Copy link
Member Author

nmaludy commented Mar 14, 2019

@Kami right, i intentionally decrypt the value prior to saving to the datastore, so that the key validity is checked on write rather than read (at some point in the future).

@Kami
Copy link
Member

Kami commented Mar 14, 2019

Alright, in this case I'm find with this approach 👍

It's almost always better to do integrity checks early on, on write :)

@nmaludy
Copy link
Member Author

nmaludy commented Mar 14, 2019

I'm thinking about renaming the flag from --decrypt to --preencrypted

Does that seem more clear?

@Kami
Copy link
Member

Kami commented Mar 14, 2019

Yeah, --decrypt is a bit config. Perhaps encrypted which signals that a value is already encrypted?

@Kami
Copy link
Member

Kami commented Mar 18, 2019

Thanks for updating the PR.

How do you think this should tie into RBAC? It would probably be safer if only admins can pass pre-encrypted values to the PUT API to avoid potential secrets leakage?

In theory, we could allow every user to use this functionality since we only use decrypt on the API side to ensure encrypted value is encrypted with the correct key and not corrupted, but then we need to be careful that we never return plain-text decrypted version back to the user inside PUT response.

1. Rename attribute and option name from "decrypt" to "pre_encrypted".
   This is a more accurate name and signals the API that the value is
   already encrypted and should be stored as-is.
2. Make sure pre_encrypted always implies secret=True. If we didn't do
   that, any user could read (decrypt) any encrypted value. Big no-no.
3. For additional safety, only allow RBAC admins to use "pre_encrypted"
   functionality.
@Kami
Copy link
Member

Kami commented Mar 18, 2019

Per discussion with @nmaludy on Slack, I made the following changes:

  1. Change option name from decrypt to pre_encrypted

After some more thought, pre_encrypted is much better name and doesn't cause any confusion with decrypt which does something totally different.

It signals the API that the value is already encrypted and should be used and stored as-is.

We do actually perform decryption on the API side, but that's an implementation detail and only done to ensure the integrity of the value (to ensure it has been encrypted with the correct key and that it's not corrupted).

Besides, that, we treat and store value as-is.

  1. Make sure pre_encrypted always implies secret=True

If we didn't do that, any user could read / decrypt any pre-encrypted value which is a big no-no and a security issue.

  1. Only allow RBAC admins to use pre_encrypted=True

This is an additional safe guard.

Primary use case for this feature is migration / restore of datastore values from one StackStorm instance which uses the same crypto key to another using st2 key load command.

I still need to work on the test coverage, especially around the security edge cases to ensure there is no accidental secrets leakage.

which are already encrypted.

Now "encrypted: True" and "secret: True" parameter in a value implies
value is pre-encrypted and is treated as such (aka used as-is).
@Kami
Copy link
Member

Kami commented Mar 18, 2019

Added test cases, and made the changes so st2 key list -n 100 -j > data.json ; st2 key load data.json now also works out of the box for encrypted values without needing to manipulate the format in data.json.

I approved the PR, but since it's touches a security critical code, I also requested review from @m4dcoder.

@Kami Kami requested a review from m4dcoder March 18, 2019 17:44
Copy link
Contributor

@bigmstone bigmstone left a comment

Choose a reason for hiding this comment

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

👍

@@ -168,6 +168,10 @@ def __init__(self, resource, *args, **kwargs):
self.parser.add_argument('-e', '--encrypt', dest='secret',
action='store_true',
help='Encrypt value before saving.')
self.parser.add_argument('--pre-encrypted', dest='pre_encrypted',
Copy link
Contributor

Choose a reason for hiding this comment

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

Why not just simply name the argument --encrypted?

Copy link
Member

@Kami Kami Mar 18, 2019

Choose a reason for hiding this comment

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

Yeah, I was thinking about it, but I thought it might be a bit confusing in combination with the existing --encrypt argument.

I'n fine either way though.

Copy link
Contributor

Choose a reason for hiding this comment

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

Adding mutually exclusive to the args will minimize the confusion.

self.parser.add_argument('--pre-encrypted', dest='pre_encrypted',
action='store_true',
help=('Value provided is already encrypted with the instance '
'crypto key and should be stored as-is.'))
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we make the args --encrypt and --encrypted/--pre-encrypted mutually exclusive? Meaning these args cannot both be present at the same time.

Copy link
Member

Choose a reason for hiding this comment

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

Good point, will add it.

@@ -4885,6 +4885,9 @@ definitions:
secret:
description: Encrypt value before saving the value.
type: boolean
decrypt:
Copy link
Contributor

Choose a reason for hiding this comment

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

This is a surprise. How come we are adding decrypt attribute in this API spec?

Copy link
Member

Choose a reason for hiding this comment

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

You are commenting on an old diff, attribute is now named pre_encrypted.

@Kami
Copy link
Member

Kami commented Mar 18, 2019

Made CLI flags mutually exclusive - 970b3eb and changed the argument and CLI flag name from --pre-encrypted to --encrypted - 88607e3.

Copy link
Contributor

@m4dcoder m4dcoder left a comment

Choose a reason for hiding this comment

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

LGTM. Not going to nit pick on ya but I couldn't find a specific unit test case for failure of checking integrity of setting an encrypted value.

msg = ('Failed to verify the integrity of the provided value for key "%s". Ensure '
'that the value is encrypted with the correct key and not corrupted.' %
(name))
raise ValueError(msg)
Copy link
Contributor

Choose a reason for hiding this comment

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

I may have missed it but I don't see a unit test that shows failure in checking integrity.

Copy link
Member

Choose a reason for hiding this comment

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

Good catch - I did plan to add that one, but forgot to.

Will add it.

Copy link
Member

Choose a reason for hiding this comment

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

Added in 17427bf.

@Kami Kami merged commit 0e9de9d into StackStorm:master Mar 19, 2019
@cognifloyd cognifloyd removed the RFR label Aug 9, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Feature Request - Ability to handle encrypted key/values
6 participants