From 4c5e6ca2083245425d7ad0d29935a0e3f466e81c Mon Sep 17 00:00:00 2001 From: Don Bowman Date: Mon, 25 Mar 2019 19:20:13 -0400 Subject: [PATCH] Add HTTP Basic Auth for Druid (Issue #4776) (#6795) * Add HTTP Basic Auth for Druid (Issue #4776) This should resolve [Issue #4776](https://github.com/apache/incubator-superset/issues/4776) Add HTTP Basic Auth to the Druid Broker. Signed-off-by: Don Bowman * Encrypt broker password in database Signed-off-by: Don Bowman * Merge from master, new migration needed Signed-off-by: Don Bowman * Do not expose hashed druid basic auth password * docs: add description_columns for druid basic auth --- superset/connectors/druid/models.py | 13 ++++-- superset/connectors/druid/views.py | 15 +++++- superset/migrations/versions/45e7da7cfeba_.py | 38 +++++++++++++++ .../e553e78e90c5_add_druid_auth_py_py.py | 46 +++++++++++++++++++ 4 files changed, 108 insertions(+), 4 deletions(-) create mode 100644 superset/migrations/versions/45e7da7cfeba_.py create mode 100644 superset/migrations/versions/e553e78e90c5_add_druid_auth_py_py.py diff --git a/superset/connectors/druid/models.py b/superset/connectors/druid/models.py index 144d4001876be..8440a25099a35 100644 --- a/superset/connectors/druid/models.py +++ b/superset/connectors/druid/models.py @@ -45,6 +45,7 @@ Boolean, Column, DateTime, ForeignKey, Integer, String, Table, Text, UniqueConstraint, ) from sqlalchemy.orm import backref, relationship +from sqlalchemy_utils import EncryptedType from superset import conf, db, security_manager from superset.connectors.base.models import BaseColumn, BaseDatasource, BaseMetric @@ -102,9 +103,11 @@ class DruidCluster(Model, AuditMixinNullable, ImportMixin): broker_endpoint = Column(String(255), default='druid/v2') metadata_last_refreshed = Column(DateTime) cache_timeout = Column(Integer) + broker_user = Column(String(255)) + broker_pass = Column(EncryptedType(String(255), conf.get('SECRET_KEY'))) export_fields = ('cluster_name', 'broker_host', 'broker_port', - 'broker_endpoint', 'cache_timeout') + 'broker_endpoint', 'cache_timeout', 'broker_user') update_from_object_fields = export_fields export_children = ['datasources'] @@ -139,16 +142,20 @@ def get_pydruid_client(self): cli = PyDruid( self.get_base_url(self.broker_host, self.broker_port), self.broker_endpoint) + if self.broker_user and self.broker_pass: + cli.set_basic_auth_credentials(self.broker_user, self.broker_pass) return cli def get_datasources(self): endpoint = self.get_base_broker_url() + '/datasources' - return json.loads(requests.get(endpoint).text) + auth = requests.auth.HTTPBasicAuth(self.broker_user, self.broker_pass) + return json.loads(requests.get(endpoint, auth=auth).text) def get_druid_version(self): endpoint = self.get_base_url( self.broker_host, self.broker_port) + '/status' - return json.loads(requests.get(endpoint).text)['version'] + auth = requests.auth.HTTPBasicAuth(self.broker_user, self.broker_pass) + return json.loads(requests.get(endpoint, auth=auth).text)['version'] @property @utils.memoized diff --git a/superset/connectors/druid/views.py b/superset/connectors/druid/views.py index 0981dc30bada7..4b05d70c7b4e3 100644 --- a/superset/connectors/druid/views.py +++ b/superset/connectors/druid/views.py @@ -167,7 +167,8 @@ class DruidClusterModelView(SupersetModelView, DeleteMixin, YamlExportMixin): # add_columns = [ 'verbose_name', 'broker_host', 'broker_port', - 'broker_endpoint', 'cache_timeout', 'cluster_name', + 'broker_user', 'broker_pass', 'broker_endpoint', + 'cache_timeout', 'cluster_name', ] edit_columns = add_columns list_columns = ['cluster_name', 'metadata_last_refreshed'] @@ -176,6 +177,8 @@ class DruidClusterModelView(SupersetModelView, DeleteMixin, YamlExportMixin): # 'cluster_name': _('Cluster'), 'broker_host': _('Broker Host'), 'broker_port': _('Broker Port'), + 'broker_user': _('Broker Username'), + 'broker_pass': _('Broker Password'), 'broker_endpoint': _('Broker Endpoint'), 'verbose_name': _('Verbose Name'), 'cache_timeout': _('Cache Timeout'), @@ -186,6 +189,16 @@ class DruidClusterModelView(SupersetModelView, DeleteMixin, YamlExportMixin): # 'Duration (in seconds) of the caching timeout for this cluster. ' 'A timeout of 0 indicates that the cache never expires. ' 'Note this defaults to the global timeout if undefined.'), + 'broker_user': _( + 'Druid supports basic authentication. See ' + '[auth](http://druid.io/docs/latest/design/auth.html) and ' + 'druid-basic-security extension', + ), + 'broker_pass': _( + 'Druid supports basic authentication. See ' + '[auth](http://druid.io/docs/latest/design/auth.html) and ' + 'druid-basic-security extension', + ), } def pre_add(self, cluster): diff --git a/superset/migrations/versions/45e7da7cfeba_.py b/superset/migrations/versions/45e7da7cfeba_.py new file mode 100644 index 0000000000000..ec475a568b547 --- /dev/null +++ b/superset/migrations/versions/45e7da7cfeba_.py @@ -0,0 +1,38 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +"""empty message + +Revision ID: 45e7da7cfeba +Revises: ('e553e78e90c5', 'c82ee8a39623') +Create Date: 2019-02-16 17:44:44.493427 + +""" + +# revision identifiers, used by Alembic. +revision = '45e7da7cfeba' +down_revision = ('e553e78e90c5', 'c82ee8a39623') + +from alembic import op +import sqlalchemy as sa + + +def upgrade(): + pass + + +def downgrade(): + pass diff --git a/superset/migrations/versions/e553e78e90c5_add_druid_auth_py_py.py b/superset/migrations/versions/e553e78e90c5_add_druid_auth_py_py.py new file mode 100644 index 0000000000000..bb17479cf1287 --- /dev/null +++ b/superset/migrations/versions/e553e78e90c5_add_druid_auth_py_py.py @@ -0,0 +1,46 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +"""add_druid_auth_py.py + +Revision ID: e553e78e90c5 +Revises: 18dc26817ad2 +Create Date: 2019-02-01 16:07:04.268023 + +""" + +# revision identifiers, used by Alembic. +revision = 'e553e78e90c5' +down_revision = '18dc26817ad2' + +from alembic import op +import sqlalchemy as sa +from sqlalchemy_utils import EncryptedType + + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('clusters', sa.Column('broker_pass', EncryptedType(), nullable=True)) + op.add_column('clusters', sa.Column('broker_user', sa.String(length=255), nullable=True)) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column('clusters', 'broker_user') + op.drop_column('clusters', 'broker_pass') + # ### end Alembic commands ###