abhishekrb19 commented on code in PR #14009:
URL: https://github.com/apache/druid/pull/14009#discussion_r1160797075
##########
docs/operations/security-overview.md:
##########
@@ -129,24 +122,23 @@ Within Druid's operating context, authenticators control
the way user identities
The following graphic depicts the course of request through the authentication
process:
-
-
-
+
## Enable an authenticator
-To authenticate requests in Druid, you configure an Authenticator.
Authenticator extensions exist for HTTP basic authentication, LDAP, and
Kerberos.
+To authenticate requests in Druid, you configure an Authenticator.
Authenticator extensions exist for HTTP basic authentication, LDAP, and
Kerberos.
-The following takes you through sample configuration steps for enabling basic
auth:
+The following takes you through sample configuration steps for enabling basic
auth:
1. Add the `druid-basic-security` extension to `druid.extensions.loadList` in
`common.runtime.properties`. For the quickstart installation, for example, the
properties file is at `conf/druid/cluster/_common`:
```
druid.extensions.loadList=["druid-basic-security", "druid-histogram",
"druid-datasketches", "druid-kafka-indexing-service"]
```
-2. Configure the basic Authenticator, Authorizer, and Escalator settings in
the same common.runtime.properties file. The Escalator defines how Druid
processes authenticate with one another.
+2. Configure the basic Authenticator, Authorizer, and Escalator settings in
the same common.runtime.properties file. The Escalator defines how Druid
processes authenticate with one another.
-An example configuration:
- ```
+ An example configuration:
+
+ ```text
Review Comment:
nit: if you switch this from `text` -> `properties`, you get some nice
syntax highlighting
```suggestion
```properties
##########
docs/operations/security-overview.md:
##########
@@ -176,70 +170,83 @@ An example configuration:
druid.auth.authorizer.MyBasicMetadataAuthorizer.type=basic
```
-3. Restart the cluster.
+3. Restart the cluster.
-See [Authentication and Authorization](../design/auth.md) for more information
about the Authenticator, Escalator, and Authorizer concepts. See [Basic
Security](../development/extensions-core/druid-basic-security.md) for more
information about the extension used in the examples above, and
[Kerberos](../development/extensions-core/druid-kerberos.md) for Kerberos
authentication.
+See the following topics for more information:
+* [Authentication and Authorization](../design/auth.md) for more information
about the Authenticator,
+Escalator, and Authorizer.
+* [Basic Security](../development/extensions-core/druid-basic-security.md) for
more information about
+the extension used in the examples above.
+* [Kerberos](../development/extensions-core/druid-kerberos.md) for Kerberos
authentication.
+* [User authentication and authorization](security-user-auth.md) for details
about permissions.
+* [SQL permissions](security-user-auth.md#sql-permissions) for permissions on
SQL system tables.
+* [The `druidapi` Python library](../tutorials/tutorial-jupyter-index.md),
+ provided as part of the Druid tutorials, for functions you can
+ use in a Jupyter notebook to learn how security works, and to set up users
and roles for testing.
## Enable authorizers
-After enabling the basic auth extension, you can add users, roles, and
permissions via the Druid Coordinator `user` endpoint. Note that you cannot
assign permissions directly to individual users. They must be assigned through
roles.
+After enabling the basic auth extension, you can add users, roles, and
permissions via the Druid Coordinator `user` endpoint. Note that you cannot
assign permissions directly to individual users. They must be assigned through
roles.
The following diagram depicts the authorization model, and the relationship
between users, roles, permissions, and resources.
-
-
+
-The following steps walk through a sample setup procedure:
+
+The following steps walk through a sample setup procedure:
> The default Coordinator API port is 8081 for non-TLS connections and 8281
> for secured connections.
-1. Create a user by issuing a POST request to
`druid-ext/basic-security/authentication/db/MyBasicMetadataAuthenticator/users/<USERNAME>`,
replacing USERNAME with the *new* username you are trying to create. For
example:
- ```
+1. Create a user by issuing a POST request to
`druid-ext/basic-security/authentication/db/MyBasicMetadataAuthenticator/users/<USERNAME>`,
+ replacing USERNAME with the *new* username you are trying to create. For
example:
+ ```bash
curl -u admin:password1 -XPOST
https://my-coordinator-ip:8281/druid-ext/basic-security/authentication/db/MyBasicMetadataAuthenticator/users/myname
- ```
- > If you have TLS enabled, be sure to adjust the curl command accordingly.
For example, if your Druid servers use self-signed certificates, you may choose
to include the `insecure` curl option to forgo certificate checking for the
curl command.
+ ```
+ > If you have TLS enabled, be sure to adjust the curl command accordingly.
For example, if your Druid servers use self-signed certificates,
+ you may choose to include the `insecure` curl option to forgo certificate
checking for the curl command.
+
2. Add a credential for the user by issuing a POST to
`druid-ext/basic-security/authentication/db/MyBasicMetadataAuthenticator/users/<USERNAME>/credentials`.
For example:
- ```
- curl -u admin:password1 -H'Content-Type: application/json' -XPOST
https://my-coordinator-ip:8281/druid-ext/basic-security/authentication/db/MyBasicMetadataAuthenticator/users/myname/credentials
--data-raw '{"password": "my_password"}'
- ```
-2. For each authenticator user you create, create a corresponding authorizer
user by issuing a POST request to
`druid-ext/basic-security/authorization/db/MyBasicMetadataAuthorizer/users/<USERNAME>`.
For example:
- ```
- curl -u admin:password1 -XPOST
https://my-coordinator-ip:8281/druid-ext/basic-security/authorization/db/MyBasicMetadataAuthorizer/users/myname
- ```
-3. Create authorizer roles to control permissions by issuing a POST request to
`druid-ext/basic-security/authorization/db/MyBasicMetadataAuthorizer/roles/<ROLENAME>`.
For example:
- ```
+ ```bash
+ curl -u admin:password1 -H'Content-Type: application/json' -XPOST
https://my-coordinator-ip:8281/druid-ext/basic-security/authentication/db/MyBasicMetadataAuthenticator/users/myname/credentials
--data-raw '{"password": "my_password"}'
+ ```
+2. For each authenticator user you create, create a corresponding authorizer
user by issuing a POST request to
`druid-ext/basic-security/authorization/db/MyBasicMetadataAuthorizer/users/<USERNAME>`.
For example:
+ ```bash
+ curl -u admin:password1 -XPOST
https://my-coordinator-ip:8281/druid-ext/basic-security/authorization/db/MyBasicMetadataAuthorizer/users/myname
+ ```
+3. Create authorizer roles to control permissions by issuing a POST request to
`druid-ext/basic-security/authorization/db/MyBasicMetadataAuthorizer/roles/<ROLENAME>`.
For example:
+ ```bash
curl -u admin:password1 -XPOST
https://my-coordinator-ip:8281/druid-ext/basic-security/authorization/db/MyBasicMetadataAuthorizer/roles/myrole
```
-4. Assign roles to users by issuing a POST request to
`druid-ext/basic-security/authorization/db/MyBasicMetadataAuthorizer/users/<USERNAME>/roles/<ROLENAME>`.
For example:
- ```
- curl -u admin:password1 -XPOST
https://my-coordinator-ip:8281/druid-ext/basic-security/authorization/db/MyBasicMetadataAuthorizer/users/myname/roles/myrole
| jq
- ```
-5. Finally, attach permissions to the roles to control how they can interact
with Druid at
`druid-ext/basic-security/authorization/db/MyBasicMetadataAuthorizer/roles/<ROLENAME>/permissions`.
- For example:
- ```
- curl -u admin:password1 -H'Content-Type: application/json' -XPOST
--data-binary @perms.json
https://my-coordinator-ip:8281/druid-ext/basic-security/authorization/db/MyBasicMetadataAuthorizer/roles/myrole/permissions
- ```
+4. Assign roles to users by issuing a POST request to
`druid-ext/basic-security/authorization/db/MyBasicMetadataAuthorizer/users/<USERNAME>/roles/<ROLENAME>`.
For example:
+ ```bash
+ curl -u admin:password1 -XPOST
https://my-coordinator-ip:8281/druid-ext/basic-security/authorization/db/MyBasicMetadataAuthorizer/users/myname/roles/myrole
| jq
+ ```
+5. Finally, attach permissions to the roles to control how they can interact
with Druid at
`druid-ext/basic-security/authorization/db/MyBasicMetadataAuthorizer/roles/<ROLENAME>/permissions`.
+ For example:
+ ```bash
+ curl -u admin:password1 -H'Content-Type: application/json' -XPOST
--data-binary @perms.json
https://my-coordinator-ip:8281/druid-ext/basic-security/authorization/db/MyBasicMetadataAuthorizer/roles/myrole/permissions
+ ```
The payload of `perms.json` should be in the form:
- ```
- [
+ ```json
+ [
{
"resource": {
- "name": "<PATTERN>",
- "type": "DATASOURCE"
+ "type": "DATASOURCE",
+ "name": "<PATTERN>"
},
"action": "READ"
},
{
"resource": {
- "name": "STATE",
- "type": "STATE"
- },
- "action": "READ"
+ "type": "STATE",
+ "name": "STATE"
+ },
+ "action": "READ"
}
- ]
- ```
- > Note: Druid treats the resource name as a regular expression (regex).
You can use a specific datasource name or regex to grant permissions for
multiple datasources at a time.
+ ]
+ ```
Review Comment:
I suspect there's a missing backtick in this diff that's causing markdown to
render the following section inside a codeblock - please see screenshot:
<img width="1190" alt="CleanShot 2023-04-07 at 09 19 12@2x"
src="https://user-images.githubusercontent.com/8687261/230642632-2cb849a2-e56d-4e8f-8cc5-26198376c04f.png">
##########
examples/quickstart/jupyter-notebooks/druidapi/druidapi/basic_auth.py:
##########
@@ -0,0 +1,238 @@
+# 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.
+
+BASIC_AUTH_BASE = '/druid-ext/basic-security'
+
+AUTHENTICATION_BASE = BASIC_AUTH_BASE + '/authentication'
+REQ_AUTHENTICATION_LOAD_STATUS = AUTHENTICATION_BASE + '/loadStatus'
+REQ_AUTHENTICATION_REFRESH_ALL = AUTHENTICATION_BASE + '/refreshAll'
+AUTHENTICATOR_BASE = AUTHENTICATION_BASE + '/db/{}'
+REQ_AUTHENTICATION_USERS = AUTHENTICATOR_BASE + '/users'
+REQ_AUTHENTICATION_USER = REQ_AUTHENTICATION_USERS + '/{}'
+REQ_AUTHENTICATION_CREDENTIALS = REQ_AUTHENTICATION_USER + '/credentials'
+
+AUTHORIZATION_BASE = BASIC_AUTH_BASE + '/authorization'
+REQ_AUTHORIZATION_LOAD_STATUS = AUTHORIZATION_BASE + '/loadStatus'
+REQ_AUTHORIZATION_REFRESH_ALL = AUTHORIZATION_BASE + '/refreshAll'
+AUTHORIZATION_BASE = AUTHORIZATION_BASE + '/db/{}'
+REQ_AUTHORIZATION_USERS = AUTHORIZATION_BASE + '/users'
+REQ_AUTHORIZATION_USER = REQ_AUTHORIZATION_USERS + '/{}'
+REQ_AUTHORIZATION_USER_ROLES = REQ_AUTHORIZATION_USER + '/roles'
+REQ_AUTHORIZATION_USER_ROLE = REQ_AUTHORIZATION_USER_ROLES + '/{}'
+REQ_AUTHORIZATION_GROUP_MAPPINGS = AUTHORIZATION_BASE + '/groupMappings'
+REQ_AUTHORIZATION_GROUP_MAPPING = AUTHORIZATION_BASE + '/groupMappings/{}'
+REQ_AUTHORIZATION_GROUP_ROLES = REQ_AUTHORIZATION_GROUP_MAPPING + '/roles'
+REQ_AUTHORIZATION_GROUP_ROLE = REQ_AUTHORIZATION_GROUP_ROLES + '/{}'
+REQ_AUTHORIZATION_ROLES = AUTHORIZATION_BASE + '/roles'
+REQ_AUTHORIZATION_ROLE = REQ_AUTHORIZATION_ROLES + '/{}'
+REQ_AUTHORIZATION_ROLE_PERMISSIONS = REQ_AUTHORIZATION_ROLE + '/permissions'
+REQ_USER_MAP = AUTHORIZATION_BASE + '/cachedSerializedUserMap'
+
+class BasicAuthClient:
+ '''
+ Manage Basic security. The Druid session must be logged in with the super
+ user, or some other user who has permission to modify user credentials.
+
+ Each client works with one authorizer/authenticator pair. Create multiple
clients if you have to
+ work with multiple authenticators on a single server.
+
+ The basic pattern to add users and permissions is:
+
+ ```
+ # Create a client for your coordinator (Basic auth is not proxied through
the router)
+ coord = druidapi.jupyter_client('http://localhost:8081', auth=('admin',
'password'))
+
+ # Get a client for your authenticator and authorizer:
+ ac = coord.basic_security('yourAuthorizer', 'yourAuthenticator')
+
+ # Create a user in both the authenticator and authorizer
+ ac.add_user('bob', 'secret')
+
+ # Define a role
+ ac.add_role('myRole')
+
+ # Assign the role to the user
+ ac.assign_role_to_user('myRole', 'bob')
+
+ # Give the role some permissions
+ ac.grant_permissions('myRole', [[consts.DATASOURCE_RESOURCE, 'foo',
consts.READ_ACTION]])
+ ```
+
+ Then use the various other methods to list users, roles and permissions to
verify the
+ setup. You can then create a second Druid client that acts as the new user:
+
+ ```
+ bob_client = druidapi.jupyter_client('http://localhost:8888', auth=('bob',
'secret'))
+ ```
+
+ See
https://druid.apache.org/docs/latest/operations/security-overview.html#enable-authorizers
+ '''
+
+ def __init__(self, rest_client, authenticator, authorizer=None):
+ self.rest_client = rest_client
+ self.authenticator = authenticator
+ self.authorizer = authorizer if authorizer else authenticator
+
+ # Authentication
+
+ def authentication_status(self) -> dict:
+ return self.rest_client.get_json(REQ_AUTHENTICATION_LOAD_STATUS)
+
+ def authentication_refresh(self) -> None:
+ self.rest_client.get(REQ_AUTHENTICATION_REFRESH_ALL)
+
+ def create_authentication_user(self, user) -> None:
+ self.rest_client.post(REQ_AUTHENTICATION_USER, None,
args=[self.authenticator, user])
+
+ def set_password(self, user, password) -> None:
+ self.rest_client.post_only_json(REQ_AUTHENTICATION_CREDENTIALS,
{'password': password}, args=[self.authenticator, user])
+
+ def drop_authentication_user(self, user) -> None:
+ self.rest_client.delete(REQ_AUTHENTICATION_USER,
args=[self.authenticator, user])
+
+ def authentication_user(self, user) -> dict:
+ return self.rest_client.get_json(REQ_AUTHENTICATION_USER,
args=[self.authenticator, user])
+
+ def authentication_users(self) -> list:
+ return self.rest_client.get_json(REQ_AUTHENTICATION_USERS,
args=[self.authenticator])
+
+ # Authorization
+ # Groups are not documented. Use at your own risk.
+
+ def authorization_status(self) -> dict:
+ return self.rest_client.get_json(REQ_AUTHORIZATION_LOAD_STATUS)
+
+ def authorization_refresh(self) -> None:
+ self.rest_client.get(REQ_AUTHORIZATION_REFRESH_ALL)
+
+ def create_authorization_user(self, user) -> None:
+ self.rest_client.post(REQ_AUTHORIZATION_USER, None,
args=[self.authorizer, user])
+
+ def drop_authorization_user(self, user) -> None:
+ self.rest_client.delete(REQ_AUTHORIZATION_USER,
args=[self.authenticator, user])
+
+ def authorization_user(self, user) -> dict:
+ return self.rest_client.get_json(REQ_AUTHORIZATION_USER,
args=[self.authorizer, user])
+
+ def authorization_users(self) -> list:
+ return self.rest_client.get_json(REQ_AUTHORIZATION_USERS,
args=[self.authorizer])
+
+ def create_group(self, group, payload):
+ self.rest_client.post_json(REQ_AUTHORIZATION_GROUP_MAPPING, payload,
args=[self.authorizer, group])
+
+ def drop_group(self, group):
+ self.rest_client.delete(REQ_AUTHORIZATION_GROUP_MAPPING,
args=[self.authorizer, group])
+
+ def groups(self) -> dict:
+ return self.rest_client.get_json(REQ_AUTHORIZATION_GROUP_MAPPINGS,
args=[self.authorizer])
+
+ def group(self, group) -> dict:
+ return self.rest_client.get_json(REQ_AUTHORIZATION_GROUP_MAPPING,
args=[self.authorizer, group])
+
+ def roles(self):
+ return self.rest_client.get_json(REQ_AUTHORIZATION_ROLES,
args=[self.authenticator])
+
+ def add_role(self, role):
+ self.rest_client.post(REQ_AUTHORIZATION_ROLE, None,
args=[self.authenticator, role])
+
+ def drop_role(self, role):
+ self.rest_client.delete(REQ_AUTHORIZATION_ROLE, args=[self.authorizer,
role])
+
+ def set_role_permissions(self, role, permissions):
+ self.rest_client.post_only_json(REQ_AUTHORIZATION_ROLE_PERMISSIONS,
permissions, args=[self.authenticator, role])
+
+ def role_permissions(self, role):
+ return self.rest_client.get_json(REQ_AUTHORIZATION_ROLE_PERMISSIONS,
args=[self.authenticator, role])
+
+ def assign_role_to_user(self, role, user):
+ self.rest_client.post(REQ_AUTHORIZATION_USER_ROLE, None,
args=[self.authenticator, user, role])
+
+ def revoke_role_from_user(self, role, user):
+ self.rest_client.delete(REQ_AUTHORIZATION_USER_ROLE,
args=[self.authenticator, user, role])
+
+ def assign_role_to_group(self, group, role):
+ self.rest_client.post(REQ_AUTHORIZATION_GROUP_ROLE, None,
args=[self.authenticator, group, role])
+
+ def revoke_role_from_group(self, group, role):
+ self.rest_client.delete(REQ_AUTHORIZATION_GROUP_ROLE,
args=[self.authenticator, group, role])
+
+ def user_map(self):
+ # Result uses Smile encoding, not JSON. This is really just for sanity
+ # checks: a Python client can't make use of the info.
+ # To decode, see newsmile: https://pypi.org/project/newsmile/
+ # However, the format Druid returns is not quite compatible with
newsmile
+ return self.rest_client.get(REQ_USER_MAP, args=[self.authenticator])
+
+ # Convenience methods
+
+ def add_user(self, user, password):
+ '''
+ Adds a user to both the authenticator and authorizer.
+ '''
+ self.create_authentication_user(user)
+ self.set_password(user, password)
+ self.create_authorization_user(user)
+
+ def drop_user(self, user):
+ '''
+ Drops a user from both the authenticator and authorizer.
+ '''
+ self.drop_authorization_user(user)
+ self.drop_authentication_user(user)
+
+ def users(self):
+ '''
+ Returns the list of authenticator and authorizer users.
+ '''
+ return {
+ "authenticator": self.authorization_users(),
+ "authorizer": self.authentication_users()
+ }
+
+ def status(self):
+ '''
+ Returns both the authenticator and authorizer status.
+ '''
+ return {
+ "authenticator": self.authorization_status(),
Review Comment:
Same as above - I think the status assignment for authenticator and
authorizer is flipped.
##########
examples/quickstart/jupyter-notebooks/druidapi/druidapi/basic_auth.py:
##########
@@ -0,0 +1,238 @@
+# 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.
+
+BASIC_AUTH_BASE = '/druid-ext/basic-security'
+
+AUTHENTICATION_BASE = BASIC_AUTH_BASE + '/authentication'
+REQ_AUTHENTICATION_LOAD_STATUS = AUTHENTICATION_BASE + '/loadStatus'
+REQ_AUTHENTICATION_REFRESH_ALL = AUTHENTICATION_BASE + '/refreshAll'
+AUTHENTICATOR_BASE = AUTHENTICATION_BASE + '/db/{}'
+REQ_AUTHENTICATION_USERS = AUTHENTICATOR_BASE + '/users'
+REQ_AUTHENTICATION_USER = REQ_AUTHENTICATION_USERS + '/{}'
+REQ_AUTHENTICATION_CREDENTIALS = REQ_AUTHENTICATION_USER + '/credentials'
+
+AUTHORIZATION_BASE = BASIC_AUTH_BASE + '/authorization'
+REQ_AUTHORIZATION_LOAD_STATUS = AUTHORIZATION_BASE + '/loadStatus'
+REQ_AUTHORIZATION_REFRESH_ALL = AUTHORIZATION_BASE + '/refreshAll'
+AUTHORIZATION_BASE = AUTHORIZATION_BASE + '/db/{}'
+REQ_AUTHORIZATION_USERS = AUTHORIZATION_BASE + '/users'
+REQ_AUTHORIZATION_USER = REQ_AUTHORIZATION_USERS + '/{}'
+REQ_AUTHORIZATION_USER_ROLES = REQ_AUTHORIZATION_USER + '/roles'
+REQ_AUTHORIZATION_USER_ROLE = REQ_AUTHORIZATION_USER_ROLES + '/{}'
+REQ_AUTHORIZATION_GROUP_MAPPINGS = AUTHORIZATION_BASE + '/groupMappings'
+REQ_AUTHORIZATION_GROUP_MAPPING = AUTHORIZATION_BASE + '/groupMappings/{}'
+REQ_AUTHORIZATION_GROUP_ROLES = REQ_AUTHORIZATION_GROUP_MAPPING + '/roles'
+REQ_AUTHORIZATION_GROUP_ROLE = REQ_AUTHORIZATION_GROUP_ROLES + '/{}'
+REQ_AUTHORIZATION_ROLES = AUTHORIZATION_BASE + '/roles'
+REQ_AUTHORIZATION_ROLE = REQ_AUTHORIZATION_ROLES + '/{}'
+REQ_AUTHORIZATION_ROLE_PERMISSIONS = REQ_AUTHORIZATION_ROLE + '/permissions'
+REQ_USER_MAP = AUTHORIZATION_BASE + '/cachedSerializedUserMap'
+
+class BasicAuthClient:
+ '''
+ Manage Basic security. The Druid session must be logged in with the super
Review Comment:
nit: "Basic" -> "basic"
##########
examples/quickstart/jupyter-notebooks/druidapi/druidapi/druid.py:
##########
@@ -116,6 +117,33 @@ def datasources(self) -> DatasourceClient:
if not self.datasource_client:
self.datasource_client = DatasourceClient(self.rest_client)
return self.datasource_client
+
+ def basic_security(self, authenticator, authorizer=None):
+ '''
+ Returns a client to work with a basic authorization
authenticator/authorizer pair.
+ This client assumes the typical case of one authenticator and one
authorizer. If
+ you have more than one, create multiple clients.
+
+ The basic security API is not proxied through the Router: it must work
directly with
+ the Coordinator. Create an ad hoc Druid client for your Coordinator.
Because you have
+ basic security enabled, you must specify the admin user and password:
+
+ ```
+ coord = druidapi.jupyter_client('http://localhost:8081',
auth=('admin', 'admin-pwd'))
Review Comment:
Neat!
##########
examples/quickstart/jupyter-notebooks/druidapi/druidapi/basic_auth.py:
##########
@@ -0,0 +1,238 @@
+# 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.
+
+BASIC_AUTH_BASE = '/druid-ext/basic-security'
+
+AUTHENTICATION_BASE = BASIC_AUTH_BASE + '/authentication'
+REQ_AUTHENTICATION_LOAD_STATUS = AUTHENTICATION_BASE + '/loadStatus'
+REQ_AUTHENTICATION_REFRESH_ALL = AUTHENTICATION_BASE + '/refreshAll'
+AUTHENTICATOR_BASE = AUTHENTICATION_BASE + '/db/{}'
+REQ_AUTHENTICATION_USERS = AUTHENTICATOR_BASE + '/users'
+REQ_AUTHENTICATION_USER = REQ_AUTHENTICATION_USERS + '/{}'
+REQ_AUTHENTICATION_CREDENTIALS = REQ_AUTHENTICATION_USER + '/credentials'
+
+AUTHORIZATION_BASE = BASIC_AUTH_BASE + '/authorization'
+REQ_AUTHORIZATION_LOAD_STATUS = AUTHORIZATION_BASE + '/loadStatus'
+REQ_AUTHORIZATION_REFRESH_ALL = AUTHORIZATION_BASE + '/refreshAll'
+AUTHORIZATION_BASE = AUTHORIZATION_BASE + '/db/{}'
+REQ_AUTHORIZATION_USERS = AUTHORIZATION_BASE + '/users'
+REQ_AUTHORIZATION_USER = REQ_AUTHORIZATION_USERS + '/{}'
+REQ_AUTHORIZATION_USER_ROLES = REQ_AUTHORIZATION_USER + '/roles'
+REQ_AUTHORIZATION_USER_ROLE = REQ_AUTHORIZATION_USER_ROLES + '/{}'
+REQ_AUTHORIZATION_GROUP_MAPPINGS = AUTHORIZATION_BASE + '/groupMappings'
+REQ_AUTHORIZATION_GROUP_MAPPING = AUTHORIZATION_BASE + '/groupMappings/{}'
+REQ_AUTHORIZATION_GROUP_ROLES = REQ_AUTHORIZATION_GROUP_MAPPING + '/roles'
+REQ_AUTHORIZATION_GROUP_ROLE = REQ_AUTHORIZATION_GROUP_ROLES + '/{}'
+REQ_AUTHORIZATION_ROLES = AUTHORIZATION_BASE + '/roles'
+REQ_AUTHORIZATION_ROLE = REQ_AUTHORIZATION_ROLES + '/{}'
+REQ_AUTHORIZATION_ROLE_PERMISSIONS = REQ_AUTHORIZATION_ROLE + '/permissions'
+REQ_USER_MAP = AUTHORIZATION_BASE + '/cachedSerializedUserMap'
+
+class BasicAuthClient:
+ '''
+ Manage Basic security. The Druid session must be logged in with the super
+ user, or some other user who has permission to modify user credentials.
+
+ Each client works with one authorizer/authenticator pair. Create multiple
clients if you have to
+ work with multiple authenticators on a single server.
+
+ The basic pattern to add users and permissions is:
+
+ ```
+ # Create a client for your coordinator (Basic auth is not proxied through
the router)
+ coord = druidapi.jupyter_client('http://localhost:8081', auth=('admin',
'password'))
+
+ # Get a client for your authenticator and authorizer:
+ ac = coord.basic_security('yourAuthorizer', 'yourAuthenticator')
+
+ # Create a user in both the authenticator and authorizer
+ ac.add_user('bob', 'secret')
+
+ # Define a role
+ ac.add_role('myRole')
+
+ # Assign the role to the user
+ ac.assign_role_to_user('myRole', 'bob')
+
+ # Give the role some permissions
+ ac.grant_permissions('myRole', [[consts.DATASOURCE_RESOURCE, 'foo',
consts.READ_ACTION]])
+ ```
+
+ Then use the various other methods to list users, roles and permissions to
verify the
+ setup. You can then create a second Druid client that acts as the new user:
+
+ ```
+ bob_client = druidapi.jupyter_client('http://localhost:8888', auth=('bob',
'secret'))
+ ```
+
+ See
https://druid.apache.org/docs/latest/operations/security-overview.html#enable-authorizers
+ '''
+
+ def __init__(self, rest_client, authenticator, authorizer=None):
+ self.rest_client = rest_client
+ self.authenticator = authenticator
+ self.authorizer = authorizer if authorizer else authenticator
+
+ # Authentication
+
+ def authentication_status(self) -> dict:
+ return self.rest_client.get_json(REQ_AUTHENTICATION_LOAD_STATUS)
+
+ def authentication_refresh(self) -> None:
+ self.rest_client.get(REQ_AUTHENTICATION_REFRESH_ALL)
+
+ def create_authentication_user(self, user) -> None:
+ self.rest_client.post(REQ_AUTHENTICATION_USER, None,
args=[self.authenticator, user])
+
+ def set_password(self, user, password) -> None:
+ self.rest_client.post_only_json(REQ_AUTHENTICATION_CREDENTIALS,
{'password': password}, args=[self.authenticator, user])
+
+ def drop_authentication_user(self, user) -> None:
+ self.rest_client.delete(REQ_AUTHENTICATION_USER,
args=[self.authenticator, user])
+
+ def authentication_user(self, user) -> dict:
+ return self.rest_client.get_json(REQ_AUTHENTICATION_USER,
args=[self.authenticator, user])
+
+ def authentication_users(self) -> list:
+ return self.rest_client.get_json(REQ_AUTHENTICATION_USERS,
args=[self.authenticator])
+
+ # Authorization
+ # Groups are not documented. Use at your own risk.
+
+ def authorization_status(self) -> dict:
+ return self.rest_client.get_json(REQ_AUTHORIZATION_LOAD_STATUS)
+
+ def authorization_refresh(self) -> None:
+ self.rest_client.get(REQ_AUTHORIZATION_REFRESH_ALL)
+
+ def create_authorization_user(self, user) -> None:
+ self.rest_client.post(REQ_AUTHORIZATION_USER, None,
args=[self.authorizer, user])
+
+ def drop_authorization_user(self, user) -> None:
+ self.rest_client.delete(REQ_AUTHORIZATION_USER,
args=[self.authenticator, user])
+
+ def authorization_user(self, user) -> dict:
+ return self.rest_client.get_json(REQ_AUTHORIZATION_USER,
args=[self.authorizer, user])
+
+ def authorization_users(self) -> list:
+ return self.rest_client.get_json(REQ_AUTHORIZATION_USERS,
args=[self.authorizer])
+
+ def create_group(self, group, payload):
+ self.rest_client.post_json(REQ_AUTHORIZATION_GROUP_MAPPING, payload,
args=[self.authorizer, group])
+
+ def drop_group(self, group):
+ self.rest_client.delete(REQ_AUTHORIZATION_GROUP_MAPPING,
args=[self.authorizer, group])
+
+ def groups(self) -> dict:
+ return self.rest_client.get_json(REQ_AUTHORIZATION_GROUP_MAPPINGS,
args=[self.authorizer])
+
+ def group(self, group) -> dict:
+ return self.rest_client.get_json(REQ_AUTHORIZATION_GROUP_MAPPING,
args=[self.authorizer, group])
+
+ def roles(self):
+ return self.rest_client.get_json(REQ_AUTHORIZATION_ROLES,
args=[self.authenticator])
+
+ def add_role(self, role):
+ self.rest_client.post(REQ_AUTHORIZATION_ROLE, None,
args=[self.authenticator, role])
+
+ def drop_role(self, role):
+ self.rest_client.delete(REQ_AUTHORIZATION_ROLE, args=[self.authorizer,
role])
+
+ def set_role_permissions(self, role, permissions):
+ self.rest_client.post_only_json(REQ_AUTHORIZATION_ROLE_PERMISSIONS,
permissions, args=[self.authenticator, role])
+
+ def role_permissions(self, role):
+ return self.rest_client.get_json(REQ_AUTHORIZATION_ROLE_PERMISSIONS,
args=[self.authenticator, role])
+
+ def assign_role_to_user(self, role, user):
+ self.rest_client.post(REQ_AUTHORIZATION_USER_ROLE, None,
args=[self.authenticator, user, role])
+
+ def revoke_role_from_user(self, role, user):
+ self.rest_client.delete(REQ_AUTHORIZATION_USER_ROLE,
args=[self.authenticator, user, role])
+
+ def assign_role_to_group(self, group, role):
+ self.rest_client.post(REQ_AUTHORIZATION_GROUP_ROLE, None,
args=[self.authenticator, group, role])
+
+ def revoke_role_from_group(self, group, role):
+ self.rest_client.delete(REQ_AUTHORIZATION_GROUP_ROLE,
args=[self.authenticator, group, role])
+
+ def user_map(self):
+ # Result uses Smile encoding, not JSON. This is really just for sanity
+ # checks: a Python client can't make use of the info.
+ # To decode, see newsmile: https://pypi.org/project/newsmile/
+ # However, the format Druid returns is not quite compatible with
newsmile
+ return self.rest_client.get(REQ_USER_MAP, args=[self.authenticator])
+
+ # Convenience methods
+
+ def add_user(self, user, password):
+ '''
+ Adds a user to both the authenticator and authorizer.
+ '''
+ self.create_authentication_user(user)
+ self.set_password(user, password)
+ self.create_authorization_user(user)
+
+ def drop_user(self, user):
+ '''
+ Drops a user from both the authenticator and authorizer.
+ '''
+ self.drop_authorization_user(user)
+ self.drop_authentication_user(user)
+
+ def users(self):
+ '''
+ Returns the list of authenticator and authorizer users.
+ '''
+ return {
+ "authenticator": self.authorization_users(),
Review Comment:
Should this be:
```python
return {
"authenticator": self.authentication_users(),
"authorizer": self. authorization_users()
}
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]