-
-
Notifications
You must be signed in to change notification settings - Fork 751
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
Conversation
…ecrypt them prior to being stored in the database
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. |
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. |
@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). |
Alright, in this case I'm find with this approach 👍 It's almost always better to do integrity checks early on, on write :) |
I'm thinking about renaming the flag from --decrypt to --preencrypted Does that seem more clear? |
Yeah, |
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. |
preencrypted value.
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.
Per discussion with @nmaludy on Slack, I made the following changes:
After some more thought, 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.
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.
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 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).
accidental data / secrets leakage.
Added test cases, and made the changes so I approved the PR, but since it's touches a security critical code, I also requested review from @m4dcoder. |
There was a problem hiding this 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', |
There was a problem hiding this comment.
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
?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.')) |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
st2common/st2common/openapi.yaml.j2
Outdated
@@ -4885,6 +4885,9 @@ definitions: | |||
secret: | |||
description: Encrypt value before saving the value. | |||
type: boolean | |||
decrypt: |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
.
There was a problem hiding this 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) |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added in 17427bf.
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 tost2 key set
, example:st2 key set -e -d myencryptedvalue ABC123
A new metadata parameter is now allowed in
st2 key load
data files calleddecrypt: true
this signifies that the value for a given key is encrypted and needs to be decrypted before being processed.Finally the API endpoint
PUT /api/v1/keys/{name}
accepts a new parameterdecrypt
that again signifies that the value is encrypted and must be decrypted:The decrypting operation is handled within the API so that
st2client
doesn't rely onst2common
. This also makes it a consistent experience from CLI and API.TODO