diff --git a/docs/getting-started/components/authz_manager.md b/docs/getting-started/components/authz_manager.md index fb0babd1e1..09ca4d1366 100644 --- a/docs/getting-started/components/authz_manager.md +++ b/docs/getting-started/components/authz_manager.md @@ -1,17 +1,102 @@ -# Authorization manager -**TODO** Complete and validate once the code is consolidated - -An authorization manager is an implementation of the `AuthManager` interface that is plugged into one of the Feast servers to extract user details from the current request and inject them into the [permissions](../../getting-started/concepts/permissions.md) framework. +# Authorization Manager +An Authorization Manager is an instance of the `AuthManager` class that is plugged into one of the Feast servers to extract user details from the current request and inject them into the [permissions](../../getting-started/concepts/permissions.md) framework. {% hint style="info" %} **Note**: Feast does not provide authentication capabilities; it is the client's responsibility to manage the authentication token and pass it to the Feast server, which then validates the token and extracts user details from the configured authentication server. {% endhint %} -Two implementations are provided out-of-the-box: -* The `OidcAuthManager` implementation, using a configurable OIDC server to extract the user details. -* The `KubernetesAuthManager` implementation, using the Kubernetes RBAC resources to extract the user details. +Two authorization managers are supported out-of-the-box: +* One using a configurable OIDC server to extract the user details. +* One using the Kubernetes RBAC resources to extract the user details. + +These instances are created when the Feast servers are initialized, according to the authorization configuration defined in +their own `feature_store.yaml`. + +Feast servers and clients must have consistent authorization configuration, so that the client proxies can automatically inject +the authorization tokens that the server can properly identify and use to enforce permission validations. + + +## Design notes +The server-side implementation of the authorization functionality is defined [here](./../../../sdk/python/feast/permissions/server). +Few of the key models, classes to understand the authorization implementation on the client side can be found [here](./../../../sdk/python/feast/permissions/client). + +## Configuring Authorization +The authorization is configured using a dedicated `auth` section in the `feature_store.yaml` configuration. + +**Note**: As a consequence, when deploying the Feast servers with the Helm [charts](../../../infra/charts/feast-feature-server/README.md), +the `feature_store_yaml_base64` value must include the `auth` section to specify the authorization configuration. + +### No Authorization +This configuration applies the default `no_auth` authorization: +```yaml +project: my-project +auth: + type: no_auth +... +``` + +### OIDC Authorization +With OIDC authorization, the Feast client proxies retrieve the JWT token from an OIDC server (or [Identity Provider](https://openid.net/developers/how-connect-works/)) +and append it in every request to a Feast server, using an [Authorization Bearer Token](https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication#bearer). + +The server, in turn, uses the same OIDC server to validate the token and extract the user roles from the token itself. + +Some assumptions are made in the OIDC server configuration: +* The OIDC token refers to a client with roles matching the RBAC roles of the configured `Permission`s (*) +* The roles are exposed in the access token passed to the server + +(*) Please note that **the role match is case-sensitive**, e.g. the name of the role in the OIDC server and in the `Permission` configuration +must be exactly the same. + +For example, the access token for a client `app` of a user with `reader` role should have the following `resource_access` section: +```json +{ + "resource_access": { + "app": { + "roles": [ + "reader" + ] + }, +} +``` + +An example of OIDC authorization configuration is the following: +```yaml +project: my-project +auth: + type: oidc + client_id: _CLIENT_ID__ + client_secret: _CLIENT_SECRET__ + realm: _REALM__ + auth_server_url: _OIDC_SERVER_URL_ + auth_discovery_url: _OIDC_SERVER_URL_/realms/master/.well-known/openid-configuration +... +``` + +In case of client configuration, the following settings must be added to specify the current user: +```yaml +auth: + ... + username: _USERNAME_ + password: _PASSWORD_ +``` + +### Kubernetes RBAC Authorization +With Kubernetes RBAC Authorization, the client uses the service account token as the authorizarion bearer token, and the +server fetches the associated roles from the Kubernetes RBAC resources. + +An example of Kubernetes RBAC authorization configuration is the following: +{% hint style="info" %} +**NOTE**: This configuration will only work if you deploy feast on Openshift or a Kubernetes platform. +{% endhint %} +```yaml +project: my-project +auth: + type: kubernetes +... +``` -**TODO** Working assumptions for the auth manager implementations (e.g. bearer tokens) -**TODO** Instruct how to configure it in the servers -**TODO** Instruct how to configure it in the helm releases +In case the client cannot run on the same cluster as the servers, the client token can be injected using the `LOCAL_K8S_TOKEN` +environment variable on the client side. The value must refer to the token of a service account created on the servers cluster +and linked to the desired RBAC roles. \ No newline at end of file diff --git a/docs/getting-started/concepts/permission.md b/docs/getting-started/concepts/permission.md index 57e702ad0c..1b8060a2e4 100644 --- a/docs/getting-started/concepts/permission.md +++ b/docs/getting-started/concepts/permission.md @@ -20,7 +20,7 @@ The permission model is based on the following components: - We assume that the resource has a `name` attribute and optional dictionary of associated key-value `tags`. - An `action` is a logical operation executed on the secured resource, like: - `create`: Create an instance. - - `read`: Access the instance state. + - `describe`: Access the instance state. - `update`: Update the instance state. - `delete`: Delete an instance. - `query`: Query both online and offline stores. @@ -61,15 +61,15 @@ The `feast` CLI includes a new `permissions` command to list the registered perm **Note**: Feast resources that do not match any of the configured permissions are not secured by any authorization policy, meaning any user can execute any action on such resources. {% endhint %} -## Configuration examples -This permission configuration allows access to the resource state and the ability to query all the stores for any feature view or feature service -to all users with role `super-reader`: +## Definition examples +This permission definition grants access to the resource state and the ability to query all the stores for any feature view or +feature service to all users with role `super-reader`: ```py Permission( name="feature-reader", types=[FeatureView, FeatureService], policy=RoleBasedPolicy(roles=["super-reader"]), - actions=[AuthzedAction.READ, QUERY], + actions=[AuthzedAction.DESCRIBE, QUERY], ) ``` @@ -100,52 +100,12 @@ Permission( ) ``` -## Authorizing Feast clients -If you would like to leverage the permissions' functionality then `auth` config block should be added to feature_store.yaml. Currently, feast supports oidc and kubernetes rbac authentication/authorization. - -The default configuration if you don't mention the auth configuration is no_auth configuration. - -* No auth configuration example: -```yaml -project: foo -registry: "registry.db" -provider: local -online_store: - path: foo -entity_key_serialization_version: 2 -auth: - type: no_auth -``` +## Authorization configuration +In order to leverage the permission functionality, the `auth` section is needed in the `feature_store.yaml` configuration. +Currently, Feast supports OIDC and Kubernetes RBAC authorization protocols. -* Kubernetes rbac authentication/authorization example: -{% hint style="info" %} -**NOTE**: This configuration will only work if you deploy feast on Openshift or a Kubernetes platform. -{% endhint %} -```yaml -project: foo -registry: "registry.db" -provider: local -online_store: - path: foo -entity_key_serialization_version: 2 -auth: - type: kubernetes -``` -* OIDC authorization: Below configuration is an example for OIDC authorization example. -```yaml -project: foo -auth: - type: oidc - client_id: test_client_id - client_secret: test_client_secret - username: test_user_name - password: test_password - realm: master - auth_discovery_url: http://localhost:8080/realms/master/.well-known/openid-configuration -registry: "registry.db" -provider: local -online_store: - path: foo -entity_key_serialization_version: 2 -``` -Few of the key models, classes to understand the authorization implementation from the clients can be found [here](./../../../sdk/python/feast/permissions/client). \ No newline at end of file +The default configuration, if you don't specify the `auth` configuration section, is `no_auth`, indicating that no permission +enforcement is applied. + +The `auth` section includes a `type` field specifying the actual authorization protocol, and protocol-specific fields that +are specified in [Authorization Manager](../components/authz_manager.md). diff --git a/docs/reference/feast-cli-commands.md b/docs/reference/feast-cli-commands.md index 37a078f9fd..3a743c0332 100644 --- a/docs/reference/feast-cli-commands.md +++ b/docs/reference/feast-cli-commands.md @@ -174,11 +174,11 @@ Options: +-----------------------+-------------+-----------------------+-----------+----------------+-------------------------+ | NAME | TYPES | NAME_PATTERN | ACTIONS | ROLES | REQUIRED_TAGS | +=======================+=============+=======================+===========+================+================+========+ -| reader_permission1234 | FeatureView | transformed_conv_rate | READ | reader | - | +| reader_permission1234 | FeatureView | transformed_conv_rate | DESCRIBE | reader | - | +-----------------------+-------------+-----------------------+-----------+----------------+-------------------------+ | writer_permission1234 | FeatureView | transformed_conv_rate | CREATE | writer | - | +-----------------------+-------------+-----------------------+-----------+----------------+-------------------------+ -| special | FeatureView | special.* | READ | admin | test-key2 : test-value2 | +| special | FeatureView | special.* | DESCRIBE | admin | test-key2 : test-value2 | | | | | UPDATE | special-reader | test-key : test-value | +-----------------------+-------------+-----------------------+-----------+----------------+-------------------------+ ``` @@ -215,7 +215,7 @@ requiredTags: required1: required-value1 required2: required-value2 actions: -- READ +- DESCRIBE policy: roleBasedPolicy: roles: @@ -256,40 +256,40 @@ admin driver_hourly_stats_source FileSource CREATE DELETE QUERY_OFFLINE QUERY_ONLINE - READ + DESCRIBE UPDATE admin vals_to_add RequestSource CREATE DELETE QUERY_OFFLINE QUERY_ONLINE - READ + DESCRIBE UPDATE admin driver_stats_push_source PushSource CREATE DELETE QUERY_OFFLINE QUERY_ONLINE - READ + DESCRIBE UPDATE admin driver_hourly_stats_source FileSource CREATE DELETE QUERY_OFFLINE QUERY_ONLINE - READ + DESCRIBE UPDATE admin vals_to_add RequestSource CREATE DELETE QUERY_OFFLINE QUERY_ONLINE - READ + DESCRIBE UPDATE admin driver_stats_push_source PushSource CREATE DELETE QUERY_OFFLINE QUERY_ONLINE - READ + DESCRIBE UPDATE -reader driver_hourly_stats FeatureView READ -reader driver_hourly_stats_fresh FeatureView READ +reader driver_hourly_stats FeatureView DESCRIBE +reader driver_hourly_stats_fresh FeatureView DESCRIBE ... ``` diff --git a/protos/feast/core/Permission.proto b/protos/feast/core/Permission.proto index f9f4f3a5d9..127744826c 100644 --- a/protos/feast/core/Permission.proto +++ b/protos/feast/core/Permission.proto @@ -19,7 +19,7 @@ message Permission { message PermissionSpec { enum AuthzedAction { CREATE = 0; - READ = 1; + DESCRIBE = 1; UPDATE = 2; DELETE = 3; QUERY_ONLINE = 4; diff --git a/sdk/python/feast/permissions/action.py b/sdk/python/feast/permissions/action.py index 09bce94511..a181f61f9b 100644 --- a/sdk/python/feast/permissions/action.py +++ b/sdk/python/feast/permissions/action.py @@ -7,7 +7,7 @@ class AuthzedAction(enum.Enum): """ CREATE = "create" # Create an instance - READ = "read" # Access the instance state + DESCRIBE = "describe" # Access the instance state UPDATE = "update" # Update the instance state DELETE = "delete" # Deelete an instance QUERY_ONLINE = "query_online" # Query the online store only @@ -34,7 +34,7 @@ class AuthzedAction(enum.Enum): # Alias for CRUD actions CRUD = [ AuthzedAction.CREATE, - AuthzedAction.READ, + AuthzedAction.DESCRIBE, AuthzedAction.UPDATE, AuthzedAction.DELETE, ] diff --git a/sdk/python/feast/registry_server.py b/sdk/python/feast/registry_server.py index 45f181a0c1..195ecd915d 100644 --- a/sdk/python/feast/registry_server.py +++ b/sdk/python/feast/registry_server.py @@ -57,7 +57,7 @@ def GetEntity(self, request: RegistryServer_pb2.GetEntityRequest, context): project=request.project, allow_cache=request.allow_cache, ), - actions=[AuthzedAction.READ], + actions=[AuthzedAction.DESCRIBE], ).to_proto() def ListEntities(self, request: RegistryServer_pb2.ListEntitiesRequest, context): @@ -73,7 +73,7 @@ def ListEntities(self, request: RegistryServer_pb2.ListEntitiesRequest, context) tags=dict(request.tags), ), ), - actions=AuthzedAction.READ, + actions=AuthzedAction.DESCRIBE, ) ] ) @@ -117,7 +117,7 @@ def GetDataSource(self, request: RegistryServer_pb2.GetDataSourceRequest, contex project=request.project, allow_cache=request.allow_cache, ), - actions=AuthzedAction.READ, + actions=AuthzedAction.DESCRIBE, ).to_proto() def ListDataSources( @@ -135,7 +135,7 @@ def ListDataSources( tags=dict(request.tags), ), ), - actions=AuthzedAction.READ, + actions=AuthzedAction.DESCRIBE, ) ] ) @@ -165,7 +165,7 @@ def GetFeatureView( project=request.project, allow_cache=request.allow_cache, ), - actions=[AuthzedAction.READ], + actions=[AuthzedAction.DESCRIBE], ).to_proto() def ApplyFeatureView( @@ -209,7 +209,7 @@ def ListFeatureViews( tags=dict(request.tags), ), ), - actions=AuthzedAction.READ, + actions=AuthzedAction.DESCRIBE, ) ] ) @@ -246,7 +246,7 @@ def GetStreamFeatureView( project=request.project, allow_cache=request.allow_cache, ), - actions=[AuthzedAction.READ], + actions=[AuthzedAction.DESCRIBE], ).to_proto() def ListStreamFeatureViews( @@ -264,7 +264,7 @@ def ListStreamFeatureViews( tags=dict(request.tags), ), ), - actions=AuthzedAction.READ, + actions=AuthzedAction.DESCRIBE, ) ] ) @@ -278,7 +278,7 @@ def GetOnDemandFeatureView( project=request.project, allow_cache=request.allow_cache, ), - actions=[AuthzedAction.READ], + actions=[AuthzedAction.DESCRIBE], ).to_proto() def ListOnDemandFeatureViews( @@ -296,7 +296,7 @@ def ListOnDemandFeatureViews( tags=dict(request.tags), ), ), - actions=AuthzedAction.READ, + actions=AuthzedAction.DESCRIBE, ) ] ) @@ -327,7 +327,7 @@ def GetFeatureService( project=request.project, allow_cache=request.allow_cache, ), - actions=[AuthzedAction.READ], + actions=[AuthzedAction.DESCRIBE], ).to_proto() def ListFeatureServices( @@ -345,7 +345,7 @@ def ListFeatureServices( tags=dict(request.tags), ), ), - actions=AuthzedAction.READ, + actions=AuthzedAction.DESCRIBE, ) ] ) @@ -395,7 +395,7 @@ def GetSavedDataset( project=request.project, allow_cache=request.allow_cache, ), - actions=[AuthzedAction.READ], + actions=[AuthzedAction.DESCRIBE], ).to_proto() def ListSavedDatasets( @@ -413,7 +413,7 @@ def ListSavedDatasets( tags=dict(request.tags), ), ), - actions=AuthzedAction.READ, + actions=AuthzedAction.DESCRIBE, ) ] ) @@ -459,7 +459,7 @@ def GetValidationReference( project=request.project, allow_cache=request.allow_cache, ), - actions=[AuthzedAction.READ], + actions=[AuthzedAction.DESCRIBE], ).to_proto() def ListValidationReferences( @@ -477,7 +477,7 @@ def ListValidationReferences( tags=dict(request.tags), ), ), - actions=AuthzedAction.READ, + actions=AuthzedAction.DESCRIBE, ) ] ) @@ -563,7 +563,7 @@ def GetPermission(self, request: RegistryServer_pb2.GetPermissionRequest, contex ) assert_permissions( resource=permission, - actions=[AuthzedAction.READ], + actions=[AuthzedAction.DESCRIBE], ) permission.to_proto().spec.project = request.project @@ -582,7 +582,7 @@ def ListPermissions( project=request.project, allow_cache=request.allow_cache ), ), - actions=AuthzedAction.READ, + actions=AuthzedAction.DESCRIBE, ) ] ) diff --git a/sdk/python/tests/integration/registration/test_universal_registry.py b/sdk/python/tests/integration/registration/test_universal_registry.py index 45d9984e2e..63e5cee93a 100644 --- a/sdk/python/tests/integration/registration/test_universal_registry.py +++ b/sdk/python/tests/integration/registration/test_universal_registry.py @@ -1358,7 +1358,7 @@ def validate_project_uuid(project_uuid, test_registry): def test_apply_permission_success(test_registry): permission = Permission( name="read_permission", - actions=AuthzedAction.READ, + actions=AuthzedAction.DESCRIBE, policy=RoleBasedPolicy(roles=["reader"]), types=FeatureView, ) @@ -1383,7 +1383,7 @@ def test_apply_permission_success(test_registry): and len(permission.types) == 1 and permission.types[0] == FeatureView and len(permission.actions) == 1 - and permission.actions[0] == AuthzedAction.READ + and permission.actions[0] == AuthzedAction.DESCRIBE and isinstance(permission.policy, RoleBasedPolicy) and len(permission.policy.roles) == 1 and permission.policy.roles[0] == "reader" @@ -1401,7 +1401,7 @@ def test_apply_permission_success(test_registry): and len(permission.types) == 1 and permission.types[0] == FeatureView and len(permission.actions) == 1 - and permission.actions[0] == AuthzedAction.READ + and permission.actions[0] == AuthzedAction.DESCRIBE and isinstance(permission.policy, RoleBasedPolicy) and len(permission.policy.roles) == 1 and permission.policy.roles[0] == "reader" @@ -1413,7 +1413,7 @@ def test_apply_permission_success(test_registry): # Update permission updated_permission = Permission( name="read_permission", - actions=[AuthzedAction.READ, AuthzedAction.WRITE_ONLINE], + actions=[AuthzedAction.DESCRIBE, AuthzedAction.WRITE_ONLINE], policy=RoleBasedPolicy(roles=["reader", "writer"]), types=FeatureView, ) @@ -1429,7 +1429,7 @@ def test_apply_permission_success(test_registry): and len(updated_permission.types) == 1 and updated_permission.types[0] == FeatureView and len(updated_permission.actions) == 2 - and AuthzedAction.READ in updated_permission.actions + and AuthzedAction.DESCRIBE in updated_permission.actions and AuthzedAction.WRITE_ONLINE in updated_permission.actions and isinstance(updated_permission.policy, RoleBasedPolicy) and len(updated_permission.policy.roles) == 2 @@ -1448,7 +1448,7 @@ def test_apply_permission_success(test_registry): updated_permission = Permission( name="read_permission", - actions=[AuthzedAction.READ, AuthzedAction.WRITE_ONLINE], + actions=[AuthzedAction.DESCRIBE, AuthzedAction.WRITE_ONLINE], policy=RoleBasedPolicy(roles=["reader", "writer"]), types=FeatureView, name_pattern="aaa", @@ -1467,7 +1467,7 @@ def test_apply_permission_success(test_registry): and len(updated_permission.types) == 1 and updated_permission.types[0] == FeatureView and len(updated_permission.actions) == 2 - and AuthzedAction.READ in updated_permission.actions + and AuthzedAction.DESCRIBE in updated_permission.actions and AuthzedAction.WRITE_ONLINE in updated_permission.actions and isinstance(updated_permission.policy, RoleBasedPolicy) and len(updated_permission.policy.roles) == 2 diff --git a/sdk/python/tests/unit/diff/test_registry_diff.py b/sdk/python/tests/unit/diff/test_registry_diff.py index f30df2a2d2..2834c57800 100644 --- a/sdk/python/tests/unit/diff/test_registry_diff.py +++ b/sdk/python/tests/unit/diff/test_registry_diff.py @@ -181,7 +181,7 @@ def test_diff_registry_objects_permissions(): name="reader", types=ALL_RESOURCE_TYPES, policy=RoleBasedPolicy(roles=["reader"]), - actions=[AuthzedAction.READ], + actions=[AuthzedAction.DESCRIBE], ) post_changed = Permission( name="reader", diff --git a/sdk/python/tests/unit/local_feast_tests/test_local_feature_store.py b/sdk/python/tests/unit/local_feast_tests/test_local_feature_store.py index 0cf0292b3f..c86441d56c 100644 --- a/sdk/python/tests/unit/local_feast_tests/test_local_feature_store.py +++ b/sdk/python/tests/unit/local_feast_tests/test_local_feature_store.py @@ -353,7 +353,7 @@ def test_apply_permissions(test_feature_store): name="reader", types=ALL_RESOURCE_TYPES, policy=RoleBasedPolicy(roles=["reader"]), - actions=[AuthzedAction.READ], + actions=[AuthzedAction.DESCRIBE], ) # Register Permission diff --git a/sdk/python/tests/unit/permissions/auth/server/test_utils.py b/sdk/python/tests/unit/permissions/auth/server/test_utils.py index 2053880dd5..5d781919a0 100644 --- a/sdk/python/tests/unit/permissions/auth/server/test_utils.py +++ b/sdk/python/tests/unit/permissions/auth/server/test_utils.py @@ -11,42 +11,42 @@ name="read_permissions_perm", types=Permission, policy=RoleBasedPolicy(roles=["reader"]), - actions=[AuthzedAction.READ], + actions=[AuthzedAction.DESCRIBE], ) read_entities_perm = Permission( name="read_entities_perm", types=Entity, policy=RoleBasedPolicy(roles=["reader"]), - actions=[AuthzedAction.READ], + actions=[AuthzedAction.DESCRIBE], ) read_fv_perm = Permission( name="read_fv_perm", types=FeatureView, policy=RoleBasedPolicy(roles=["reader"]), - actions=[AuthzedAction.READ], + actions=[AuthzedAction.DESCRIBE], ) read_odfv_perm = Permission( name="read_odfv_perm", types=OnDemandFeatureView, policy=RoleBasedPolicy(roles=["reader"]), - actions=[AuthzedAction.READ], + actions=[AuthzedAction.DESCRIBE], ) read_sfv_perm = Permission( name="read_sfv_perm", types=StreamFeatureView, policy=RoleBasedPolicy(roles=["reader"]), - actions=[AuthzedAction.READ], + actions=[AuthzedAction.DESCRIBE], ) invalid_list_entities_perm = Permission( name="invalid_list_entity_perm", types=Entity, policy=RoleBasedPolicy(roles=["dancer"]), - actions=[AuthzedAction.READ], + actions=[AuthzedAction.DESCRIBE], ) diff --git a/sdk/python/tests/unit/permissions/conftest.py b/sdk/python/tests/unit/permissions/conftest.py index fd4113873e..7cd944fb47 100644 --- a/sdk/python/tests/unit/permissions/conftest.py +++ b/sdk/python/tests/unit/permissions/conftest.py @@ -22,7 +22,7 @@ def __init__(self, name, tags): tags=tags, ) - @require_permissions(actions=[AuthzedAction.READ]) + @require_permissions(actions=[AuthzedAction.DESCRIBE]) def read_protected(self) -> bool: return True @@ -60,7 +60,7 @@ def security_manager() -> SecurityManager: name="reader", types=FeatureView, policy=RoleBasedPolicy(roles=["reader"]), - actions=[AuthzedAction.READ], + actions=[AuthzedAction.DESCRIBE], ) ) permissions.append( @@ -77,7 +77,7 @@ def security_manager() -> SecurityManager: types=FeatureView, name_pattern="special.*", policy=RoleBasedPolicy(roles=["admin", "special-reader"]), - actions=[AuthzedAction.READ, AuthzedAction.UPDATE], + actions=[AuthzedAction.DESCRIBE, AuthzedAction.UPDATE], ) ) diff --git a/sdk/python/tests/unit/permissions/test_security_manager.py b/sdk/python/tests/unit/permissions/test_security_manager.py index 0380d4772c..5f73a7a72f 100644 --- a/sdk/python/tests/unit/permissions/test_security_manager.py +++ b/sdk/python/tests/unit/permissions/test_security_manager.py @@ -9,15 +9,15 @@ "username, requested_actions, allowed, allowed_single, raise_error_in_assert, raise_error_in_permit", [ (None, [], False, [False, False], [True, True], False), - ("r", [AuthzedAction.READ], True, [True, True], [False, False], False), + ("r", [AuthzedAction.DESCRIBE], True, [True, True], [False, False], False), ("r", [AuthzedAction.UPDATE], False, [False, False], [True, True], False), - ("w", [AuthzedAction.READ], False, [False, False], [True, True], False), + ("w", [AuthzedAction.DESCRIBE], False, [False, False], [True, True], False), ("w", [AuthzedAction.UPDATE], False, [True, True], [False, False], False), - ("rw", [AuthzedAction.READ], False, [True, True], [False, False], False), + ("rw", [AuthzedAction.DESCRIBE], False, [True, True], [False, False], False), ("rw", [AuthzedAction.UPDATE], False, [True, True], [False, False], False), ( "rw", - [AuthzedAction.READ, AuthzedAction.UPDATE], + [AuthzedAction.DESCRIBE, AuthzedAction.UPDATE], False, [False, False], [True, True], @@ -25,7 +25,7 @@ ), ( "admin", - [AuthzedAction.READ, AuthzedAction.UPDATE], + [AuthzedAction.DESCRIBE, AuthzedAction.UPDATE], False, [False, True], [True, False],