-
-
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
Changes from 11 commits
800fe97
559f7ce
80fe135
247e51c
d6fd8c8
820b809
9a8d1fd
677c4b4
5228fa7
4f6fa27
ddd1514
8e34d21
970b3eb
88607e3
b77e98a
17427bf
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -84,7 +84,7 @@ def __init__(self, resource, *args, **kwargs): | |
# Filter options | ||
self.parser.add_argument('--prefix', help=('Only return values with names starting with ' | ||
'the provided prefix.')) | ||
self.parser.add_argument('--decrypt', action='store_true', | ||
self.parser.add_argument('-d', '--decrypt', action='store_true', | ||
help='Decrypt secrets and displays plain text.') | ||
self.parser.add_argument('-s', '--scope', default=DEFAULT_LIST_SCOPE, dest='scope', | ||
help='Scope item is under. Example: "user".') | ||
|
@@ -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', | ||
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 commentThe reason will be displayed to describe this comment to others. Learn more. Can we make the args There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good point, will add it. |
||
self.parser.add_argument('-s', '--scope', dest='scope', default=DEFAULT_CUD_SCOPE, | ||
help='Specify the scope under which you want ' + | ||
'to place the item.') | ||
|
@@ -186,6 +190,9 @@ def run(self, args, **kwargs): | |
if args.secret: | ||
instance.secret = args.secret | ||
|
||
if args.pre_encrypted: | ||
instance.pre_encrypted = args.pre_encrypted | ||
|
||
if args.ttl: | ||
instance.ttl = args.ttl | ||
|
||
|
@@ -312,7 +319,9 @@ def run(self, args, **kwargs): | |
# parse optional KeyValuePair properties | ||
scope = item.get('scope', DEFAULT_CUD_SCOPE) | ||
user = item.get('user', None) | ||
encrypted = item.get('encrypted', False) | ||
secret = item.get('secret', False) | ||
pre_encrypted = item.get('pre_encrypted', False) | ||
ttl = item.get('ttl', None) | ||
|
||
# if the value is not a string, convert it to JSON | ||
|
@@ -332,13 +341,23 @@ def run(self, args, **kwargs): | |
instance.name = name | ||
instance.value = value | ||
instance.scope = scope | ||
|
||
if user: | ||
instance.user = user | ||
if encrypted: | ||
instance.encrypted = encrypted | ||
if secret: | ||
instance.secret = secret | ||
if pre_encrypted: | ||
instance.pre_encrypted = pre_encrypted | ||
if ttl: | ||
instance.ttl = ttl | ||
|
||
# encrypted=True and secret=True implies that the value is already encrypted and should | ||
# be used as such | ||
if encrypted and secret: | ||
instance.pre_encrypted = True | ||
|
||
# call the API to create/update the KeyValuePair | ||
self.manager.update(instance, **kwargs) | ||
instances.append(instance) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -67,6 +67,11 @@ class KeyValuePairAPI(BaseAPI): | |
'required': False, | ||
'default': False | ||
}, | ||
'pre_encrypted': { | ||
'type': 'boolean', | ||
'required': False, | ||
'default': False | ||
}, | ||
'encrypted': { | ||
'type': 'boolean', | ||
'required': False, | ||
|
@@ -157,6 +162,7 @@ def to_model(cls, kvp): | |
name = getattr(kvp, 'name', None) | ||
description = getattr(kvp, 'description', None) | ||
value = kvp.value | ||
original_value = value | ||
secret = False | ||
|
||
if getattr(kvp, 'ttl', None): | ||
|
@@ -165,13 +171,32 @@ def to_model(cls, kvp): | |
else: | ||
expire_timestamp = None | ||
|
||
pre_encrypted = getattr(kvp, 'pre_encrypted', False) | ||
secret = getattr(kvp, 'secret', False) | ||
|
||
if secret: | ||
if not KeyValuePairAPI.crypto_key: | ||
msg = ('Crypto key not found in %s. Unable to encrypt value for key %s.' % | ||
(KeyValuePairAPI.crypto_key_path, name)) | ||
raise CryptoKeyNotSetupException(msg) | ||
# If user transmitted the value in an pre-encrypted format, we perform the decryption here | ||
# to ensure data integrity. Besides that, we store data as-is. | ||
# Keep in mind that pre_encrypted=True also always implies secret=True. If we didn't do | ||
# that and supported pre_encrypted=True, secret=False, this would allow users to decrypt | ||
# any encrypted value. | ||
if pre_encrypted: | ||
secret = True | ||
|
||
cls._verif_key_is_set_up(name=name) | ||
|
||
try: | ||
symmetric_decrypt(KeyValuePairAPI.crypto_key, value) | ||
except Exception: | ||
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 commentThe 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 commentThe 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 commentThe reason will be displayed to describe this comment to others. Learn more. Added in 17427bf. |
||
|
||
# Additional safety check to ensure that the value hasn't been decrypted | ||
assert value == original_value | ||
elif secret: | ||
cls._verif_key_is_set_up(name=name) | ||
|
||
value = symmetric_encrypt(KeyValuePairAPI.crypto_key, value) | ||
|
||
scope = getattr(kvp, 'scope', FULL_SYSTEM_SCOPE) | ||
|
@@ -181,12 +206,25 @@ def to_model(cls, kvp): | |
scope, ALLOWED_SCOPES) | ||
) | ||
|
||
# NOTE: For security reasons, pre_encrypted always implies secret=True. See comment | ||
# above for explanation. | ||
if pre_encrypted and not secret: | ||
raise ValueError('pre_encrypted option can only be used in combination with secret ' | ||
'option') | ||
|
||
model = cls.model(id=kvp_id, name=name, description=description, value=value, | ||
secret=secret, scope=scope, | ||
expire_timestamp=expire_timestamp) | ||
|
||
return model | ||
|
||
@classmethod | ||
def _verif_key_is_set_up(cls, name): | ||
if not KeyValuePairAPI.crypto_key: | ||
msg = ('Crypto key not found in %s. Unable to encrypt / decrypt value for key %s.' % | ||
(KeyValuePairAPI.crypto_key_path, name)) | ||
raise CryptoKeyNotSetupException(msg) | ||
|
||
|
||
class KeyValuePairSetAPI(KeyValuePairAPI): | ||
""" | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 commentThe 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 commentThe reason will be displayed to describe this comment to others. Learn more. You are commenting on an old diff, attribute is now named |
||
description: Value provided is encrypted with this instances crypto key and must be decrypted when received. This allows values to be transmitted in encrypted format. | ||
type: boolean | ||
scope: | ||
description: "Scope the item is under. Example: 'user'." | ||
type: string | ||
|
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.