diff --git a/README.md b/README.md index 425abd44c..5eda35f5f 100644 --- a/README.md +++ b/README.md @@ -52,9 +52,21 @@ Use this command to get started using Docker: ``` docker-compose -f deploy.docker-compose.yaml up -d ``` +#### 3. Security +The project uses default tokens to encrypt connector keys. Follow the following steps to generate a new token for your deployment: +``` +from cryptography.fernet import Fernet + +# Generate a new Fernet key +key = Fernet.generate_key() +print(key.decode()) # Print it as a string to use in settings +``` +Export this as an ENV variable 'FIELD_ENCRYPTION_KEY' in your deployment. + + Looking for Helm chart or custom branch deployment? Read our installation doc [here](https://docs.drdroid.io/docs/installation). -#### 3. Learn more: Watch tutorials on our [YouTube](https://www.youtube.com/@DrDroidDev) channel +#### 4. Learn more: Watch tutorials on our [YouTube](https://www.youtube.com/@DrDroidDev) channel ## Connect with us: * Want to contribute? Read our [contribution guidelines](https://docs.drdroid.io/docs/contributing). diff --git a/connectors/migrations/0032_alter_connectorkey_key_alter_connectorkey_key_md5.py b/connectors/migrations/0032_alter_connectorkey_key_alter_connectorkey_key_md5.py new file mode 100644 index 000000000..97fdf4cd0 --- /dev/null +++ b/connectors/migrations/0032_alter_connectorkey_key_alter_connectorkey_key_md5.py @@ -0,0 +1,24 @@ +# Generated by Django 4.1.13 on 2024-10-02 09:40 + +from django.db import migrations +import encrypted_model_fields.fields + + +class Migration(migrations.Migration): + + dependencies = [ + ('connectors', '0031_alter_connector_connector_type_and_more'), + ] + + operations = [ + migrations.AlterField( + model_name='connectorkey', + name='key', + field=encrypted_model_fields.fields.EncryptedTextField(), + ), + migrations.AlterField( + model_name='connectorkey', + name='key_md5', + field=encrypted_model_fields.fields.EncryptedCharField(blank=True, null=True), + ), + ] diff --git a/connectors/migrations/0033_encrypt_existing_data.py b/connectors/migrations/0033_encrypt_existing_data.py new file mode 100644 index 000000000..9a4018507 --- /dev/null +++ b/connectors/migrations/0033_encrypt_existing_data.py @@ -0,0 +1,21 @@ +# Generated by Django 4.1.13 on 2024-10-02 09:41 + +from django.db import migrations + + +def encrypt_existing_data(apps, schema_editor): + ConnectorKey = apps.get_model('connectors', 'ConnectorKey') + for obj in ConnectorKey.objects.all(): + obj.key = obj.key # triggers encryption during save + obj.key_md5 = obj.key_md5 # triggers encryption during save + obj.save() + + +class Migration(migrations.Migration): + dependencies = [ + ('connectors', '0032_alter_connectorkey_key_alter_connectorkey_key_md5'), + ] + + operations = [ + migrations.RunPython(encrypt_existing_data), + ] diff --git a/connectors/models.py b/connectors/models.py index 5a01d76ce..720b51bc9 100644 --- a/connectors/models.py +++ b/connectors/models.py @@ -3,6 +3,7 @@ from django.contrib.sites.models import Site as DjangoSite from django.db import models +from encrypted_model_fields.fields import EncryptedTextField, EncryptedCharField from protos.base_pb2 import Source, SourceKeyType, SourceModelType from utils.model_utils import generate_choices @@ -496,8 +497,8 @@ class ConnectorKey(models.Model): connector = models.ForeignKey(Connector, on_delete=models.CASCADE) key_type = models.IntegerField(null=True, blank=True, choices=generate_choices(SourceKeyType), default=SourceKeyType.UNKNOWN_SKT) - key = models.TextField() - key_md5 = models.CharField(max_length=255, null=True, blank=True) + key = EncryptedTextField() + key_md5 = EncryptedCharField(max_length=255, null=True, blank=True) metadata = models.JSONField(null=True, blank=True) is_active = models.BooleanField(default=True) created_at = models.DateTimeField(auto_now_add=True, db_index=True) diff --git a/helm/charts/celery_worker/templates/deployment.yaml b/helm/charts/celery_worker/templates/deployment.yaml index ba91296db..389c48291 100644 --- a/helm/charts/celery_worker/templates/deployment.yaml +++ b/helm/charts/celery_worker/templates/deployment.yaml @@ -36,6 +36,8 @@ spec: value: {{ .Values.djangoDatabaseUsername }} - name: REDIS_URL value: {{ .Values.redisUrl }} + - name: FIELD_ENCRYPTION_KEY + value: {{ .Values.fieldEcryptionKey }} command: ["/bin/sh","-c"] args: ["./start-celery-worker.sh"] diff --git a/helm/charts/server/templates/deployment.yaml b/helm/charts/server/templates/deployment.yaml index 80a12850d..8ee3a8327 100644 --- a/helm/charts/server/templates/deployment.yaml +++ b/helm/charts/server/templates/deployment.yaml @@ -42,6 +42,8 @@ spec: value: {{ .Values.oktaDomain }} - name: OKTA_CLIENT_ID value: {{ .Values.oktaClientId }} + - name: FIELD_ENCRYPTION_KEY + value: {{ .Values.fieldEcryptionKey }} ports: - containerPort: 8080 command: ["/bin/sh","-c"] diff --git a/helm/charts/workflow_action_execution_celery_worker/templates/deployment.yaml b/helm/charts/workflow_action_execution_celery_worker/templates/deployment.yaml index 88845c946..16b29ae99 100644 --- a/helm/charts/workflow_action_execution_celery_worker/templates/deployment.yaml +++ b/helm/charts/workflow_action_execution_celery_worker/templates/deployment.yaml @@ -38,6 +38,8 @@ spec: value: {{ .Values.redisUrl }} - name: CELERY_QUEUE value: {{ .Values.celeryQueue }} + - name: FIELD_ENCRYPTION_KEY + value: {{ .Values.fieldEcryptionKey }} command: ["/bin/sh","-c"] args: ["./start-celery-worker.sh"] diff --git a/helm/charts/workflow_executor_celery_worker/templates/deployment.yaml b/helm/charts/workflow_executor_celery_worker/templates/deployment.yaml index 1be5b74fa..c2bf3d70c 100644 --- a/helm/charts/workflow_executor_celery_worker/templates/deployment.yaml +++ b/helm/charts/workflow_executor_celery_worker/templates/deployment.yaml @@ -38,6 +38,8 @@ spec: value: {{ .Values.redisUrl }} - name: CELERY_QUEUE value: {{ .Values.celeryQueue }} + - name: FIELD_ENCRYPTION_KEY + value: {{ .Values.fieldEcryptionKey }} command: ["/bin/sh","-c"] args: ["./start-celery-worker.sh"] diff --git a/helm/charts/workflow_scheduler_celery_worker/templates/deployment.yaml b/helm/charts/workflow_scheduler_celery_worker/templates/deployment.yaml index 1e3537057..49ef09116 100644 --- a/helm/charts/workflow_scheduler_celery_worker/templates/deployment.yaml +++ b/helm/charts/workflow_scheduler_celery_worker/templates/deployment.yaml @@ -38,6 +38,8 @@ spec: value: {{ .Values.redisUrl }} - name: CELERY_QUEUE value: {{ .Values.celeryQueue }} + - name: FIELD_ENCRYPTION_KEY + value: {{ .Values.fieldEcryptionKey }} command: ["/bin/sh","-c"] args: ["./start-celery-worker.sh"] diff --git a/playbooks/base_settings.py b/playbooks/base_settings.py index 6004d8944..fb9f3f943 100644 --- a/playbooks/base_settings.py +++ b/playbooks/base_settings.py @@ -26,7 +26,9 @@ # See https://docs.djangoproject.com/en/4.1/howto/deployment/checklist/ # SECURITY WARNING: keep the secret key used in production secret! + SECRET_KEY = env.str("DJANGO_SECRET_KEY", default='django-insecure-0=(%5(mscw_h4ah@i^n591qkor_(t+2d&+&j)4l8+4zpk65y%=') +FIELD_ENCRYPTION_KEY = env.list("FIELD_ENCRYPTION_KEY", default=['4TPTg7NnvhEkj0rh5RXl_vdcL2LrgKIyblWPERGDOio=']) # SECURITY WARNING: don't run with debug turned on in production! DEBUG = env.bool("DJANGO_DEBUG", default=True) @@ -134,20 +136,7 @@ 'PASSWORD': env.str('POSTGRES_PASSWORD', default='pass'), 'HOST': _default_postgres_host, 'PORT': env.str('POSTGRES_PORT', default='5432'), - }, - # 'clickhouse': { - # 'ENGINE': 'clickhouse_backend.backend', - # 'HOST': env.str("CLICKHOUSE_HOST", default='localhost'), - # 'PORT': 9440, - # 'USER': env.str("CLICKHOUSE_USERNAME", default='default'), - # 'PASSWORD': env.str("CLICKHOUSE_PASSWORD", default=''), - # 'OPTIONS': { - # 'secure': True, - # 'settings': { - # 'allow_experimental_object_type': 1 - # } - # } - # } + } } DATABASE_ROUTERS = ['playbooks.db.router.DbRouter'] @@ -348,11 +337,11 @@ def get_cache_backend(alias='default'): PAGERDUTY_WEBHOOK_USE_SITE = env.bool("PAGERDUTY_WEBHOOK_USE_SITE", default=True) ROOTLY_WEBHOOK_LOCATION = env.str("ROOTLY_WEBHOOK_LOCATION", default='/connectors/handlers/rootly' - '/handle_incidents') + '/handle_incidents') ROOTLY_WEBHOOK_HTTP_PROTOCOL = env.str("ROOTLY_WEBHOOK_HTTP_PROTOCOL", default='https') ROOTLY_WEBHOOK_USE_SITE = env.bool("ROOTLY_WEBHOOK_USE_SITE", default=True) ZENDUTY_WEBHOOK_LOCATION = env.str("ZENDUTY_WEBHOOK_LOCATION", default='/connectors/handlers/zenduty' - '/handle_incidents') + '/handle_incidents') ZENDUTY_WEBHOOK_HTTP_PROTOCOL = env.str("ZENDUTY_WEBHOOK_HTTP_PROTOCOL", default='https') ZENDUTY_WEBHOOK_USE_SITE = env.bool("ZENDUTY_WEBHOOK_USE_SITE", default=True) diff --git a/requirements.txt b/requirements.txt index 393550702..5a37f23e7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -23,6 +23,7 @@ django-cors-headers==3.13.0 django-filter==22.1 django-prometheus==2.4.0.dev5 django-redis==5.2.0 +django-encrypted-model-fields==0.6.5 djangorestframework-simplejwt==5.2.2 elasticsearch==8.14.0 environs==11.0.0