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

Generate db_encrypted_fields_keyfile #205

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

wbclark
Copy link
Collaborator

@wbclark wbclark commented Jun 30, 2021

Pulpcore 3.15 will require a Fernet symmetric encryption
key to encrypt certain sensitive database fields.
This is expected to contain 32 pseudorandom bytes in
url-safe base64-encoded format, with padding.

@wbclark
Copy link
Collaborator Author

wbclark commented Jun 30, 2021

TODO: Update settings.py (depends on implementation details in pulpcore 3.15)

manifests/init.pp Outdated Show resolved Hide resolved
manifests/init.pp Outdated Show resolved Hide resolved
lib/puppet/functions/generate_fernet_key.rb Outdated Show resolved Hide resolved
Puppet::Functions.create_function(:generate_fernet_key) do
# @return 32 byte url-safe base64-encoded (with padding) Fernet symmetric encryption key
dispatch :generate_fernet_key do
return_type 'String'
Copy link
Member

Choose a reason for hiding this comment

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

You can even give a stronger guarantee. It's at least 32 characters, right?

Suggested change
return_type 'String'
return_type 'String[32]'

Copy link
Collaborator Author

@wbclark wbclark Jun 30, 2021

Choose a reason for hiding this comment

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

I'll try Pattern[/\A([a-zA-Z]|\d|-|_){43}=\z/] since this matches exactly what are the allowed values for 32 bytes in url-safe base64 encoded form

In fact, cryptography.fernet claims that it requires the url-safe base64 encoding, for example here: https://github.com/pyca/cryptography/blob/77fb53c75e47f50e09b1b3be3a4d10c7e4e34dc2/src/cryptography/fernet.py#L37-L41

and also in the docs at https://github.com/pyca/cryptography/blob/77fb53c75e47f50e09b1b3be3a4d10c7e4e34dc2/docs/fernet.rst :

param key: A URL-safe base64-encoded 32-byte key. This must be kept secret. Anyone with this key is able to create and read messages.

With that said, it uses base64.urlsafe_b64decode to load the key, and in fact that performs the translation from the non url-safe alphabet: https://github.com/python/cpython/blob/86eeeb425936ba67d79f32bfbd5c5f8002819438/Lib/base64.py#L108-L133

I tested this and it does in fact work to load a key with either base64 alphabet, even though cryptography.fernet claims it doesn't support that:

>>> from cryptography.fernet import Fernet
>>> urlsafe_key = b'xTjQtHgU6OsktqtDGAG5ogEb8xNjT2DQGytK3hWMg-_='
>>> nonurlsafe_key = b'xTjQtHgU6OsktqtDGAG5ogEb8xNjT2DQGytK3hWMg+/='
>>> f1 = Fernet(key = urlsafe_key)
>>> f2 = Fernet(key = nonurlsafe_key)
>>> f1._encryption_key == f2._encryption_key
True
>>> f1._signing_key == f2._signing_key
True

Note that it is also possible to use Fernet encryption using an existing password such as the Django SECRET_KEY and generating the Fernet key from that. There is an example provided in cryptography.fernet docs:

>>> import base64
>>> import os
>>> from cryptography.fernet import Fernet
>>> from cryptography.hazmat.primitives import hashes
>>> from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
>>> password = b"password"
>>> salt = os.urandom(16)
>>> kdf = PBKDF2HMAC(
...     algorithm=hashes.SHA256(),
...     length=32,
...     salt=salt,
...     iterations=100000,
... )
>>> key = base64.urlsafe_b64encode(kdf.derive(password))
>>> f = Fernet(key)
>>> token = f.encrypt(b"Secret message!")
>>> token
b'...'
>>> f.decrypt(token)
b'Secret message!'

django-fernet-fields generates a derived fernet key from the standard Django SECRET_KEY by default[1], although the behavior can be disabled to pass an arbitrary fernet key instead.

[1] https://django-fernet-fields.readthedocs.io/en/latest/#keys

The implementation is similar to the above: https://github.com/orcasgit/django-fernet-fields/blob/master/fernet_fields/hkdf.py#L14-L23

With the approach of generating the Fernet key by hashing the standard Django SECRET_KEY, this PR would become unnecessary, but it does not look like the Pulp team is planning to follow that implementation.

@wbclark wbclark force-pushed the encrypted_fields_database_key branch from 66999c0 to b40d886 Compare June 30, 2021 13:16
@wbclark wbclark force-pushed the encrypted_fields_database_key branch 2 times, most recently from b378506 to 14279c6 Compare June 30, 2021 14:02
Pulpcore 3.15 will require a Fernet symmetric encryption
key to encrypt certain sensitive database fields.
This is expected to contain 32 pseudorandom bytes in
url-safe base64-encoded format, with padding.
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.

3 participants