-
Notifications
You must be signed in to change notification settings - Fork 14.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(dashboard_rbac): dashboard extra jwt #13773
Changes from 18 commits
38c678d
2223b10
c11fc8b
a2261f7
b0b576c
52a30b1
c5d0599
29c218a
deaedc6
ecfaaac
de11299
8ac8df0
ff4984f
8c05a48
429fa38
9a1bdf4
f56bf36
51b9e71
308c659
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
# 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. | ||
from typing import List, Optional | ||
|
||
import jwt | ||
from flask import Flask | ||
|
||
|
||
class DashboardJwtDataObject: # pylint: disable=too-few-public-methods | ||
dashboard_id: int | ||
dataset_ids: List[int] | ||
|
||
def __init__(self, dashboard_id: int, dataset_ids: List[int]) -> None: | ||
super().__init__() | ||
self.dashboard_id = dashboard_id | ||
self.dataset_ids = dataset_ids | ||
|
||
|
||
class DashboardJwtManager: | ||
def __init__(self) -> None: | ||
super().__init__() | ||
self._jwt_secret: str | ||
|
||
def init_app(self, app: Flask) -> None: | ||
config = app.config | ||
|
||
self._jwt_secret = config["DASHBOARD_JWT_SECRET"] | ||
|
||
def generate_jwt(self, data: DashboardJwtDataObject) -> str: | ||
encoded_jwt = jwt.encode(data.__dict__, self._jwt_secret, algorithm="HS256") | ||
return encoded_jwt.decode("utf-8") | ||
|
||
def parse_jwt(self, token: Optional[str]) -> Optional[DashboardJwtDataObject]: | ||
if token: | ||
data = jwt.decode(token, self._jwt_secret, algorithms=["HS256"]) | ||
return DashboardJwtDataObject( | ||
dashboard_id=data["id"], dataset_ids=data["dataset_ids"] | ||
) | ||
return None |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -84,7 +84,12 @@ | |
SupersetTemplateParamsErrorException, | ||
SupersetTimeoutException, | ||
) | ||
from superset.extensions import async_query_manager, cache_manager | ||
from superset.extensions import ( | ||
async_query_manager, | ||
cache_manager, | ||
dashboard_jwt_manager, | ||
feature_flag_manager, | ||
) | ||
from superset.jinja_context import get_template_processor | ||
from superset.models.core import Database, FavStar, Log | ||
from superset.models.dashboard import Dashboard | ||
|
@@ -102,6 +107,7 @@ | |
from superset.utils.async_query_manager import AsyncQueryTokenException | ||
from superset.utils.cache import etag_cache | ||
from superset.utils.core import ReservedUrlParameters | ||
from superset.utils.dashboard_jwt_manager import DashboardJwtDataObject | ||
from superset.utils.dates import now_as_float | ||
from superset.utils.decorators import check_dashboard_access | ||
from superset.views.base import ( | ||
|
@@ -1869,6 +1875,19 @@ def dashboard( # pylint: disable=too-many-locals | |
if key not in [param.value for param in utils.ReservedUrlParameters] | ||
} | ||
|
||
extra_jwt = {} | ||
if feature_flag_manager.is_feature_enabled("DASHBOARD_RBAC"): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There is a project underway to make Dashboards part of a single page app. This code will only work if dashboards are accessed from this route, but in a SPA that will not necessarily be the case. If you click a link to a dashboard from the dashboards list or the homepage, the routing will be done on the frontend. Maybe this code should be moved to a separate endpoint that the frontend calls to get the jwt. (see also #13306) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm also not really clear on why the jwt is needed. Some more background info would be helpful. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ok so the jwt is being used to manage read data access of charts that exists on a dashboard implicitly just by having a access to the dashboard This really upgrade the dashboard access RBAC mechanism by also giving Temporary access to datasets that are being used within the dash lard without the need to explicitly giving permission to those datasets There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm totally aware of SPA project and I plan to also support and implement the dashboard jwt feature under that as well But for now until SPA maturity I want to to be supported in the old style There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm expecting to have a PR up that removes dashboard as a standalone app this week, so I think compatibility is an important consideration now. What about adding logic to the security of the chart data and dataset endpoints, to check whether access should be granted at the time of request? Is that an option? Sorry if I'm missing something, I'd just like to fully understand the need for this JWT pattern. Is the time limit for access a feature requirement or an implementation detail? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm a little bit wary of the security implication of allowing dashboard readers to have access to charts and datasets they explicitly don't have access to. What if someone creates a dashboard with charts that use datasets they don't have access to, but add themselves to the allowed list of roles? What is the access control flow look like? Would you restrict who can change dashboard roles only to those who have admin access to the underlying datasets? Because if someone has view access to a dataset or edit access to a dashboard, it doesn't mean they can just publish this dataset or the content of this dashboard to any audience. |
||
extra_jwt = { | ||
"extra_jwt": dashboard_jwt_manager.generate_jwt( | ||
DashboardJwtDataObject( | ||
dashboard.id, | ||
list( | ||
map(lambda datasource: datasource.id, dashboard.datasources) | ||
), | ||
) | ||
) | ||
} | ||
|
||
bootstrap_data = { | ||
"user_id": g.user.get_id(), | ||
"common": common_bootstrap_payload(), | ||
|
@@ -1883,6 +1902,7 @@ def dashboard( # pylint: disable=too-many-locals | |
"superset_can_csv": superset_can_csv, | ||
"slice_can_edit": slice_can_edit, | ||
}, | ||
**extra_jwt, | ||
"datasources": data["datasources"], | ||
} | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ktmud @suddjian upon each chart data query request (legacy or v1 API) this part is being called of validating from extracting the signed jwt if that dataset is being called from a queryContext /Viz
Only dashboard owner/admin an give dashboard access (see the video)
Let's assume that daahboard access is being granted to a read only persona
And to that persona explore chart permission is not granted as well
So that type of a user can only see data under the dashboard
He cannot edit the dataset or play around with it in the explore
In theory he could use that jwt to query the dataset freely in SQLlab(if he permission to sqllab to begin with)
From a dashboard point of view is up to the dashboard creator to wrap data with the relevant datasets(either physical or virtual) that will restrict the readonly personas to what is relevant for them
If there is a huge physical dataset that might expose sensitive data then you can always have a thiner portion of it by creating a virtual one
How would you suggest make that flow secure?
@suddjian I would be happy to do a follow up PR just to support SPA , no worries there
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you elaborate more on why you need users without direct access to certain datasets to have access to that dataset through dashboards? Can you just create virtual datasets and grant them read access to the dataset instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
in our org every dashboard might serve different set of users which basically means a separated role per dashboard.
so managing access for every dataset on every dashboard is a cumbersome work.
it will require managing all datasets permissions for every role.
this problem scale when having hundreds of dashboards.
additionally, dataset permission mechanism has a defect , when changing a virtual dataset name then permission name changes as well but if it is already associated to a role you'll need to remove it and then re-associate bc permission is based on the name of the dataset and not by the id
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the SPA project we have already done the work to remove dashboard-specific bootstrap data so that the apps can be cleanly merged. I think rather than adding new bootstrap properties at this stage, it would be better to use an endpoint. That said, if you're okay with this feature breaking in the very near future, I won't stand in your way. The compatibility discussion is a relatively minor issue.
I think the security concerns have already been discussed somewhat in SIP-51. Anyone using the feature flag should understand that dashboard owners have the ability to grant data access to others arbitrarily. These effects should be well documented. It's an entirely different security profile for the application.
I am of the opinion that the security manager should make a database query to find the information it needs, rather than using this new JWT pattern. I haven't seen anything that indicates that a JWT is actually required here, though I could be mistaken.
Introducing new authorization patterns specific to a given feature flag will make it more difficult to onboard into the Superset codebase, as well as making it more complicated to change system behavior. The JWT pattern also makes it complicated to revoke or change a user's access, and introduces a new responsibility for the client to correctly manage the JWT.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The jwt and it's data indicates that the request indeed coming from the Dashboard and with the nessecery dataset permission.
Otherwise upon chart data request it would be impossible to know if the request does originates from a dashboard or not
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But you could write a query selecting dashboards the user can access which use the dataset that's being requested, no?
Something akin to:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What if you want to publish a dashboard to some users, but a subset of these users cannot have access to a couple of more sensitive charts in the dashboard? If seems by turning on the DASHBOARD_RBAC flag, there is no way to accommodate this case, unless you add some kind of dashboard level metadata to indicate this dashboard should not issue a JWT.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In this use case we would probably just have 2 separated Dashboards
Just bc there no sense of providing dashboard to a client and show him only have of charts, that is a very bad UX don't you think ? 💆
I know Air BnB have this portion of a dashboard use case
But for us it just doesnt makes sense 🙅
Our Dashboards are for external clients , try to explain to them why they see only half
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd say even your external clients may have people with different access levels. Maybe not now, but it's possible.
Sometimes you want a single dashboard for all audience because it's easier to navigate and easier to communicate---you can share the same short link to all users and executives don't have to jump between two dashboards just to see the more sensitive data. You may also have two duplicate dashboards with 80% of the content the same, but it'd gradually become difficult to maintain.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe we need a "nested dashboard" viz plugin 😁