-
Notifications
You must be signed in to change notification settings - Fork 35
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
add scheduled run models to housewatch
- Loading branch information
1 parent
fd16ae4
commit 7b80942
Showing
9 changed files
with
216 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
41 changes: 41 additions & 0 deletions
41
housewatch/migrations/0007_scheduledbackup_scheduledbackuprun.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
# Generated by Django 4.1.1 on 2023-08-17 01:06 | ||
|
||
from django.db import migrations, models | ||
import django.db.models.deletion | ||
import housewatch.utils.encrypted_fields.fields | ||
|
||
|
||
class Migration(migrations.Migration): | ||
|
||
dependencies = [ | ||
('housewatch', '0006_savedquery'), | ||
] | ||
|
||
operations = [ | ||
migrations.CreateModel( | ||
name='ScheduledBackup', | ||
fields=[ | ||
('id', models.UUIDField(primary_key=True, serialize=False)), | ||
('schedule', models.CharField(max_length=255)), | ||
('table', models.CharField(max_length=255, null=True)), | ||
('database', models.CharField(max_length=255)), | ||
('bucket', models.CharField(max_length=255)), | ||
('path', models.CharField(max_length=255)), | ||
('aws_access_key_id', housewatch.utils.encrypted_fields.fields.EncryptedCharField(max_length=255, null=True)), | ||
('aws_secret_access_key', housewatch.utils.encrypted_fields.fields.EncryptedCharField(max_length=255, null=True)), | ||
('aws_region', housewatch.utils.encrypted_fields.fields.EncryptedCharField(max_length=255, null=True)), | ||
('aws_endpoint_url', housewatch.utils.encrypted_fields.fields.EncryptedCharField(max_length=255, null=True)), | ||
], | ||
), | ||
migrations.CreateModel( | ||
name='ScheduledBackupRun', | ||
fields=[ | ||
('id', models.UUIDField(primary_key=True, serialize=False)), | ||
('started_at', models.DateTimeField(auto_now_add=True)), | ||
('finished_at', models.DateTimeField(null=True)), | ||
('success', models.BooleanField(default=False)), | ||
('error', models.TextField(null=True)), | ||
('scheduled_backup', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='housewatch.scheduledbackup')), | ||
], | ||
), | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,4 @@ | ||
from .instance import Instance | ||
from .backup import ScheduledBackup, ScheduledBackupRun | ||
|
||
__all__ = ["Instance"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
from django.db import models | ||
from housewatch.utils.encrypted_fields.fields import EncryptedCharField | ||
|
||
|
||
class ScheduledBackup(models.Model): | ||
id: models.UUIDField = models.UUIDField(primary_key=True) | ||
# This will be a CRON expression for the job | ||
schedule: models.CharField = models.CharField(max_length=255) | ||
table: models.CharField = models.CharField(max_length=255, null=True) | ||
database: models.CharField = models.CharField(max_length=255) | ||
bucket: models.CharField = models.CharField(max_length=255) | ||
path: models.CharField = models.CharField(max_length=255) | ||
# if set these will override the defaults from settings | ||
# raw keys will not be stored here but will obfuscated | ||
aws_access_key_id: models.CharField = EncryptedCharField(max_length=255, null=True) | ||
aws_secret_access_key: models.CharField = EncryptedCharField(max_length=255, null=True) | ||
aws_region: models.CharField = EncryptedCharField(max_length=255, null=True) | ||
aws_endpoint_url: models.CharField = EncryptedCharField(max_length=255, null=True) | ||
|
||
|
||
class ScheduledBackupRun(models.Model): | ||
id: models.UUIDField = models.UUIDField(primary_key=True) | ||
scheduled_backup: models.ForeignKey = models.ForeignKey(ScheduledBackup, on_delete=models.CASCADE) | ||
started_at: models.DateTimeField = models.DateTimeField(auto_now_add=True) | ||
finished_at: models.DateTimeField = models.DateTimeField(null=True) | ||
success: models.BooleanField = models.BooleanField(default=False) | ||
error: models.TextField = models.TextField(null=True) |
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
from cryptography.fernet import Fernet, MultiFernet | ||
from django.conf import settings | ||
from django.core.exceptions import FieldError, ImproperlyConfigured | ||
from django.db import models | ||
from django.utils.encoding import force_bytes, force_str | ||
from django.utils.functional import cached_property | ||
|
||
from . import hkdf | ||
|
||
|
||
__all__ = [ | ||
"EncryptedField", | ||
"EncryptedTextField", | ||
"EncryptedCharField", | ||
"EncryptedEmailField", | ||
"EncryptedIntegerField", | ||
"EncryptedDateField", | ||
"EncryptedDateTimeField", | ||
] | ||
|
||
|
||
class EncryptedField(models.Field): | ||
"""A field that encrypts values using Fernet symmetric encryption.""" | ||
|
||
_internal_type = "BinaryField" | ||
|
||
def __init__(self, *args, **kwargs): | ||
if kwargs.get("primary_key"): | ||
raise ImproperlyConfigured("%s does not support primary_key=True." % self.__class__.__name__) | ||
if kwargs.get("unique"): | ||
raise ImproperlyConfigured("%s does not support unique=True." % self.__class__.__name__) | ||
if kwargs.get("db_index"): | ||
raise ImproperlyConfigured("%s does not support db_index=True." % self.__class__.__name__) | ||
super(EncryptedField, self).__init__(*args, **kwargs) | ||
|
||
@cached_property | ||
def keys(self): | ||
keys = getattr(settings, "FERNET_KEYS", None) | ||
if keys is None: | ||
keys = [settings.SECRET_KEY] | ||
return keys | ||
|
||
@cached_property | ||
def fernet_keys(self): | ||
if getattr(settings, "FERNET_USE_HKDF", True): | ||
return [hkdf.derive_fernet_key(k) for k in self.keys] | ||
return self.keys | ||
|
||
@cached_property | ||
def fernet(self): | ||
if len(self.fernet_keys) == 1: | ||
return Fernet(self.fernet_keys[0]) | ||
return MultiFernet([Fernet(k) for k in self.fernet_keys]) | ||
|
||
def get_internal_type(self): | ||
return self._internal_type | ||
|
||
def get_db_prep_save(self, value, connection): | ||
value = super(EncryptedField, self).get_db_prep_save(value, connection) | ||
if value is not None: | ||
retval = self.fernet.encrypt(force_bytes(value)) | ||
return connection.Database.Binary(retval) | ||
|
||
def from_db_value(self, value, expression, connection, *args): | ||
if value is not None: | ||
value = bytes(value) | ||
return self.to_python(force_str(self.fernet.decrypt(value))) | ||
|
||
@cached_property | ||
def validators(self): | ||
# Temporarily pretend to be whatever type of field we're masquerading | ||
# as, for purposes of constructing validators (needed for | ||
# IntegerField and subclasses). | ||
self.__dict__["_internal_type"] = super(EncryptedField, self).get_internal_type() | ||
try: | ||
return super(EncryptedField, self).validators | ||
finally: | ||
del self.__dict__["_internal_type"] | ||
|
||
|
||
def get_prep_lookup(self): | ||
"""Raise errors for unsupported lookups""" | ||
raise FieldError("{} '{}' does not support lookups".format(self.lhs.field.__class__.__name__, self.lookup_name)) | ||
|
||
|
||
# Register all field lookups (except 'isnull') to our handler | ||
for name, lookup in models.Field.class_lookups.items(): | ||
# Dynamically create classes that inherit from the right lookups | ||
if name != "isnull": | ||
lookup_class = type("EncryptedField" + name, (lookup,), {"get_prep_lookup": get_prep_lookup}) | ||
EncryptedField.register_lookup(lookup_class) | ||
|
||
|
||
class EncryptedTextField(EncryptedField, models.TextField): | ||
pass | ||
|
||
|
||
class EncryptedCharField(EncryptedField, models.CharField): | ||
pass | ||
|
||
|
||
class EncryptedEmailField(EncryptedField, models.EmailField): | ||
pass | ||
|
||
|
||
class EncryptedIntegerField(EncryptedField, models.IntegerField): | ||
pass | ||
|
||
|
||
class EncryptedDateField(EncryptedField, models.DateField): | ||
pass | ||
|
||
|
||
class EncryptedDateTimeField(EncryptedField, models.DateTimeField): | ||
pass |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
import base64 | ||
|
||
from cryptography.hazmat.primitives import hashes | ||
from cryptography.hazmat.primitives.kdf.hkdf import HKDF | ||
from cryptography.hazmat.backends import default_backend | ||
from django.utils.encoding import force_bytes | ||
|
||
backend = default_backend() | ||
info = b"django-fernet-fields" | ||
# We need reproducible key derivation, so we can't use a random salt | ||
salt = b"django-fernet-fields-hkdf-salt" | ||
|
||
|
||
def derive_fernet_key(input_key): | ||
"""Derive a 32-bit b64-encoded Fernet key from arbitrary input key.""" | ||
hkdf = HKDF( | ||
algorithm=hashes.SHA256(), | ||
length=32, | ||
salt=salt, | ||
info=info, | ||
backend=backend, | ||
) | ||
return base64.urlsafe_b64encode(hkdf.derive(force_bytes(input_key))) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters