Skip to content

Commit

Permalink
Change: encrypt data source options. 🔓 (#2970)
Browse files Browse the repository at this point in the history
* Change: encrypt data source options
* Implement migration
  • Loading branch information
arikfr authored Feb 17, 2019
1 parent a8f74a1 commit a34269c
Show file tree
Hide file tree
Showing 5 changed files with 67 additions and 3 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
"""add_encrypted_options_to_data_sources
Revision ID: 98af61feea92
Revises: 73beceabb948
Create Date: 2019-01-31 09:21:31.517265
"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import postgresql
from sqlalchemy.sql import table
from sqlalchemy_utils.types.encrypted.encrypted_type import FernetEngine

from redash import settings
from redash.utils.configuration import ConfigurationContainer
from redash.models.types import EncryptedConfiguration, Configuration, MutableDict, MutableList, PseudoJSON

# revision identifiers, used by Alembic.
revision = '98af61feea92'
down_revision = '73beceabb948'
branch_labels = None
depends_on = None


def upgrade():
op.add_column('data_sources', sa.Column('encrypted_options', postgresql.BYTEA(), nullable=True))

# copy values
data_sources = table(
'data_sources',
sa.Column('id', sa.Integer, primary_key=True),
sa.Column('encrypted_options', ConfigurationContainer.as_mutable(EncryptedConfiguration(sa.Text, settings.SECRET_KEY, FernetEngine))),
sa.Column('options', ConfigurationContainer.as_mutable(Configuration)))

conn = op.get_bind()
for ds in conn.execute(data_sources.select()):
conn.execute(
data_sources
.update()
.where(data_sources.c.id == ds.id)
.values(encrypted_options=ds.options))

op.drop_column('data_sources', 'options')
op.alter_column('data_sources', 'encrypted_options',
nullable=False)


def downgrade():
pass
7 changes: 4 additions & 3 deletions redash/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,9 @@
from sqlalchemy_utils import generic_relationship
from sqlalchemy_utils.types import TSVectorType
from sqlalchemy_utils.models import generic_repr
from sqlalchemy_utils.types.encrypted.encrypted_type import FernetEngine

from redash import redis_connection, utils
from redash import redis_connection, utils, settings
from redash.destinations import (get_configuration_schema_for_destination_type,
get_destination)
from redash.metrics import database # noqa: F401
Expand All @@ -32,7 +33,7 @@
from .changes import ChangeTrackingMixin, Change # noqa
from .mixins import BelongsToOrgMixin, TimestampMixin
from .organizations import Organization
from .types import Configuration, MutableDict, MutableList, PseudoJSON
from .types import EncryptedConfiguration, Configuration, MutableDict, MutableList, PseudoJSON
from .users import (AccessPermission, AnonymousUser, ApiUser, Group, User) # noqa

logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -72,7 +73,7 @@ class DataSource(BelongsToOrgMixin, db.Model):

name = Column(db.String(255))
type = Column(db.String(255))
options = Column(ConfigurationContainer.as_mutable(Configuration))
options = Column('encrypted_options', ConfigurationContainer.as_mutable(EncryptedConfiguration(db.Text, settings.SECRET_KEY, FernetEngine)))
queue_name = Column(db.String(255), default="queries")
scheduled_queue_name = Column(db.String(255), default="scheduled_queries")
created_at = Column(db.DateTime(True), default=db.func.now())
Expand Down
10 changes: 10 additions & 0 deletions redash/models/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from sqlalchemy.types import TypeDecorator
from sqlalchemy.ext.indexable import index_property
from sqlalchemy.ext.mutable import Mutable
from sqlalchemy_utils import EncryptedType

from redash.utils import json_dumps, json_loads
from redash.utils.configuration import ConfigurationContainer
Expand All @@ -19,6 +20,14 @@ def process_result_value(self, value, dialect):
return ConfigurationContainer.from_json(value)


class EncryptedConfiguration(EncryptedType):
def process_bind_param(self, value, dialect):
return super(EncryptedConfiguration, self).process_bind_param(value.to_json(), dialect)

def process_result_value(self, value, dialect):
return ConfigurationContainer.from_json(super(EncryptedConfiguration, self).process_result_value(value, dialect))


# XXX replace PseudoJSON and MutableDict with real JSON field
class PseudoJSON(TypeDecorator):
impl = db.Text
Expand Down Expand Up @@ -87,6 +96,7 @@ class json_cast_property(index_property):
entity attribute as the specified cast type. Useful
for JSON and JSONB colums for easier querying/filtering.
"""

def __init__(self, cast_type, *args, **kwargs):
super(json_cast_property, self).__init__(*args, **kwargs)
self.cast_type = cast_type
Expand Down
2 changes: 2 additions & 0 deletions redash/settings/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ def all_settings():

return settings


REDIS_URL = os.environ.get('REDASH_REDIS_URL', os.environ.get('REDIS_URL', "redis://localhost:6379/0"))
PROXIES_COUNT = int(os.environ.get('REDASH_PROXIES_COUNT', "1"))

Expand Down Expand Up @@ -107,6 +108,7 @@ def all_settings():
JOB_EXPIRY_TIME = int(os.environ.get("REDASH_JOB_EXPIRY_TIME", 3600 * 12))
COOKIE_SECRET = os.environ.get("REDASH_COOKIE_SECRET", "c292a0a3aa32397cdb050e233733900f")
SESSION_COOKIE_SECURE = parse_boolean(os.environ.get("REDASH_SESSION_COOKIE_SECURE") or str(ENFORCE_HTTPS))
SECRET_KEY = os.environ.get('REDASH_SECRET_KEY', COOKIE_SECRET)

LOG_LEVEL = os.environ.get("REDASH_LOG_LEVEL", "INFO")
LOG_STDOUT = parse_boolean(os.environ.get('REDASH_LOG_STDOUT', 'false'))
Expand Down
2 changes: 2 additions & 0 deletions redash/utils/configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,4 +92,6 @@ def __contains__(self, item):

@classmethod
def from_json(cls, config_in_json):
if config_in_json is None:
return cls({})
return cls(json_loads(config_in_json))

0 comments on commit a34269c

Please sign in to comment.