encrypt TSIG keys using Fernet cryptography library
This commit is contained in:
parent
fba94af629
commit
1af8f1e31b
|
@ -90,6 +90,14 @@ python manage.py createsuperuser
|
|||
python manage.py dumpdata -o binder/fixtures/initial_data.json
|
||||
```
|
||||
|
||||
### Encrypted TSIG Keys ###
|
||||
|
||||
Starting with version 1.5, TSIG keys inside the database are encrypted using the [Crytography](https://cryptography.io/en/latest/fernet/) library and Fernet facilities.
|
||||
|
||||
Normally on startup, a new Fernet encryption key is created. This will change upon reboot as the process dies and restarts.
|
||||
|
||||
If you wish to use a statically configured encryption/decryption key, one must pass the DJANGO_FERNET_KEY environment variable, containing this key string. This *should* be used in production. This key *MUST* be kept secret or your TSIG keys will be able to be decrypted.
|
||||
|
||||
## External configuration ##
|
||||
|
||||
Aside from the Binder application itself, other infrastructure is required
|
||||
|
|
|
@ -5,6 +5,7 @@ import binascii
|
|||
import socket
|
||||
|
||||
# 3rd Party
|
||||
from cryptography.fernet import Fernet, InvalidToken
|
||||
from pybindxml import reader as bindreader
|
||||
import dns.exception
|
||||
import dns.query
|
||||
|
@ -14,6 +15,7 @@ import dns.zone
|
|||
# App Imports
|
||||
from binder import exceptions
|
||||
from django.db import models
|
||||
from django.conf import settings
|
||||
|
||||
TSIG_ALGORITHMS = (('HMAC-MD5.SIG-ALG.REG.INT', 'MD5'),
|
||||
('hmac-sha1', 'SHA1'),
|
||||
|
@ -24,10 +26,7 @@ TSIG_ALGORITHMS = (('HMAC-MD5.SIG-ALG.REG.INT', 'MD5'),
|
|||
|
||||
class Key(models.Model):
|
||||
|
||||
"""Store and reference TSIG keys.
|
||||
|
||||
TODO: Should/Can we encrypt these DNS keys in the DB?
|
||||
"""
|
||||
"""Store and reference TSIG keys."""
|
||||
|
||||
name = models.CharField(max_length=255,
|
||||
unique=True,
|
||||
|
@ -46,17 +45,38 @@ class Key(models.Model):
|
|||
class Meta:
|
||||
ordering = ["name"]
|
||||
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
f = Fernet(settings.FERNET_KEY)
|
||||
crypted_key = f.encrypt(bytes(self.data))
|
||||
self.data = crypted_key
|
||||
super(Key, self).save(*args, **kwargs)
|
||||
|
||||
def create_keyring(self):
|
||||
if self.name is None:
|
||||
return None
|
||||
|
||||
try:
|
||||
keyring = dns.tsigkeyring.from_text({self.name: self.data})
|
||||
key_data = self.decrypt_keydata()
|
||||
keyring = dns.tsigkeyring.from_text({self.name: key_data})
|
||||
except binascii.Error, err:
|
||||
raise exceptions.KeyringException("Incorrect key data. Verify key: %s. Reason: %s" % (self.name, err))
|
||||
|
||||
return keyring
|
||||
|
||||
def decrypt_keydata(self, key=None):
|
||||
if key:
|
||||
fernet_key=key
|
||||
else:
|
||||
fernet_key=settings.FERNET_KEY
|
||||
try:
|
||||
f = Fernet(fernet_key)
|
||||
decrypted_key = f.decrypt(bytes(self.data))
|
||||
except InvalidToken:
|
||||
raise exceptions.KeyringException()
|
||||
|
||||
return decrypted_key
|
||||
|
||||
|
||||
class BindServer(models.Model):
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
# Django settings for binder project.
|
||||
import logging
|
||||
import os
|
||||
from cryptography.fernet import Fernet
|
||||
from django.contrib.messages import constants as messages
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
@ -163,6 +164,16 @@ RECORD_TYPE_CHOICES = (("A", "A"),
|
|||
|
||||
LOGIN_REDIRECT_URL = '/'
|
||||
|
||||
# TSIG Encryption Key
|
||||
# If not passed as an environment variable,
|
||||
# create a new Fernet key for encrypting the TSIG key.
|
||||
|
||||
# NOTE: In production, you'll want to pass your own key in.
|
||||
# Otherwise, on successive Binder restarts, you will not be able
|
||||
# to decrypt your TSIG Key and perform DNS updates because the keys
|
||||
# would have changed.
|
||||
FERNET_KEY=os.environ.get("DJANGO_FERNET_KEY", Fernet.generate_key())
|
||||
|
||||
try:
|
||||
from local_settings import *
|
||||
except ImportError:
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
from django.test import TestCase
|
||||
from cryptography.fernet import Fernet, InvalidToken
|
||||
from django.conf import settings
|
||||
from django.test import TestCase, override_settings
|
||||
from django.db import IntegrityError
|
||||
|
||||
from binder import models
|
||||
from binder import exceptions, models
|
||||
import os
|
||||
|
||||
|
||||
class Model_BindServer_Tests(TestCase):
|
||||
|
@ -40,3 +43,27 @@ class Model_Key_Tests(TestCase):
|
|||
def test_NonExistantKey(self):
|
||||
with self.assertRaisesMessage(models.Key.DoesNotExist, "Key matching query does not exist"):
|
||||
models.Key.objects.get(name="does_not_exist")
|
||||
|
||||
@override_settings(FERNET_KEY='yfE1kyYLNlpR-2ybdB-Mvs_k1ZoDMFFVtE_PpWYxVgs=')
|
||||
def test_FernetKeyDecryptionSuccess(self):
|
||||
"""Test encrypt/decryption when Fernet key is generated by Django."""
|
||||
original_tsig_key = 'oGyDayyZ2mDUiJCuTUODnA=='
|
||||
key_1 = models.Key(name='testencryptedkey1',
|
||||
data=original_tsig_key,
|
||||
algorithm='MD5')
|
||||
key_1.save()
|
||||
decrypt_key = Fernet(settings.FERNET_KEY)
|
||||
decrypted_tsig_key = decrypt_key.decrypt(bytes(key_1.data))
|
||||
self.assertEqual(original_tsig_key, decrypted_tsig_key)
|
||||
|
||||
@override_settings(FERNET_KEY='yfE1kyYLNlpR-2ybdB-Mvs_k1ZoDMFFVtE_PpWYxVgs=')
|
||||
def test_FernetKeyDecryptionFailure(self):
|
||||
"""Test encrypt/decryption when Fernet key changes."""
|
||||
original_tsig_key = 'oGyDayyZ2mDUiJCuTUODnA=='
|
||||
key_1 = models.Key(name='testencryptedkey1',
|
||||
data=original_tsig_key,
|
||||
algorithm='MD5')
|
||||
key_1.save()
|
||||
new_fkey = Fernet(Fernet.generate_key())
|
||||
with self.assertRaises(InvalidToken):
|
||||
decrypted_tsig_key = new_fkey.decrypt(bytes(key_1.data))
|
||||
|
|
|
@ -53,6 +53,13 @@ def view_zone_records(request, dns_server, zone_name):
|
|||
return render(request, "bcommon/list_zone.html",
|
||||
{"zone_name": zone_name,
|
||||
"dns_server": this_server})
|
||||
except KeyringException:
|
||||
messages.error(request, "Unable to get zone list. A problem was encountered "
|
||||
"decrypting your TSIG key. Ensure the key is correctly "
|
||||
"specified in the Binder Database.")
|
||||
return render(request, "bcommon/list_zone.html",
|
||||
{ "dns_server": this_server,
|
||||
"zone_name" :zone_name })
|
||||
|
||||
return render(request, "bcommon/list_zone.html",
|
||||
{"zone_array": zone_array,
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
cryptography
|
||||
Django>=1.10
|
||||
dnspython>=1.11
|
||||
git+https://github.com/jforman/pybindxml@0.6.1
|
||||
|
|
Loading…
Reference in New Issue