Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions redash/query_runner/pg.py
Original file line number Diff line number Diff line change
Expand Up @@ -254,9 +254,15 @@ def _get_tables(self, schema):

def _gen_role_pass(self, role_name: str) -> str:
"""
Generate a password for a given role using the datasource secret and role name.
Generate a password for a given role using the shared RLS secret and role name.

Must match stacklet.shared.sql.rls.gen_role_pass in the platform.
"""
secret = settings.DATASOURCE_SECRET_KEY
secret = settings.RLS_SECRET
if not secret:
raise ValueError(
f"REDASH_RLS_SECRET is not set, so the password for role {role_name} can't be derived"
)
return hashlib.sha256(f"{secret}:{role_name}".encode("utf-8")).hexdigest()

@inject_iam_auth
Expand Down
5 changes: 5 additions & 0 deletions redash/settings/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,11 @@
# The secret key to use when encrypting data source options
DATASOURCE_SECRET_KEY = os.environ.get("REDASH_SECRET_KEY", SECRET_KEY)

# The platform's shared RLS secret, from which per-role Postgres passwords are derived.
# Deliberately separate from DATASOURCE_SECRET_KEY, which must keep decrypting stored
# data source options.
RLS_SECRET = os.environ.get("REDASH_RLS_SECRET")

# Whether and how to redirect non-HTTP requests to HTTPS. Disabled by default.
ENFORCE_HTTPS = parse_boolean(os.environ.get("REDASH_ENFORCE_HTTPS", "false"))
ENFORCE_HTTPS_PERMANENT = parse_boolean(os.environ.get("REDASH_ENFORCE_HTTPS_PERMANENT", "false"))
Expand Down
30 changes: 29 additions & 1 deletion tests/query_runner/test_pg.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from unittest import TestCase
from unittest.mock import patch

from redash.query_runner.pg import build_schema
from redash.query_runner.pg import PostgreSQL, build_schema


class TestBuildSchema(TestCase):
Expand Down Expand Up @@ -41,3 +42,30 @@ def test_build_schema_with_data_types(self):
self.assertListEqual(
schema["main.users"]["columns"], [{"name": "id", "type": "integer"}, {"name": "name", "type": "varchar"}]
)


class TestGenRolePass(TestCase):
# The same vector is pinned against stacklet.shared.sql.rls.gen_role_pass in the
# platform; the two must derive identical passwords.
ROLE = "sso_alice_at_example_com_a1b2c3"
SECRET = "rls-test-secret"
EXPECTED = "30f1f1a4d09bae826d882a048b1ea7bcc6354e710dd4d7faf4aefd55e07913c9"

def setUp(self):
self.runner = PostgreSQL({})

def test_matches_platform_vector(self):
with patch("redash.settings.RLS_SECRET", self.SECRET):
self.assertEqual(self.runner._gen_role_pass(self.ROLE), self.EXPECTED)

def test_ignores_datasource_secret_key(self):
with (
patch("redash.settings.RLS_SECRET", self.SECRET),
patch("redash.settings.DATASOURCE_SECRET_KEY", "something-else"),
):
self.assertEqual(self.runner._gen_role_pass(self.ROLE), self.EXPECTED)

def test_unset_secret_raises(self):
with patch("redash.settings.RLS_SECRET", None):
with self.assertRaisesRegex(ValueError, "REDASH_RLS_SECRET"):
self.runner._gen_role_pass(self.ROLE)
Loading