mirror of
https://github.com/apache/druid.git
synced 2025-02-16 15:05:24 +00:00
Co-authored-by: Victoria Lim <vtlim@users.noreply.github.com> Co-authored-by: Paul Rogers <progers@apache.org>
This commit is contained in:
parent
89bdbdc3ed
commit
5ce1b0903e
@ -24,12 +24,10 @@ description: Overiew of Apache Druid security. Includes best practices, configur
|
||||
-->
|
||||
|
||||
|
||||
|
||||
This document provides an overview of Apache Druid security features, configuration instructions, and some best practices to secure Druid.
|
||||
|
||||
By default, security features in Druid are disabled, which simplifies the initial deployment experience. However, security features must be configured in a production deployment. These features include TLS, authentication, and authorization.
|
||||
|
||||
|
||||
## Best practices
|
||||
|
||||
The following recommendations apply to the Druid cluster setup:
|
||||
@ -55,7 +53,6 @@ The following recommendation applies to Druid's authorization and authentication
|
||||
* Only grant `STATE READ`, `STATE WRITE`, `CONFIG WRITE`, and `DATASOURCE WRITE` permissions to highly-trusted users. These permissions allow users to access resources on behalf of the Druid server process regardless of the datasource.
|
||||
* If your Druid client application allows less-trusted users to control the input source or firehose of an ingestion task, validate the URLs from the users. It is possible to point unchecked URLs to other locations and resources within your network or local file system.
|
||||
|
||||
|
||||
## Enable TLS
|
||||
|
||||
Enabling TLS encrypts the traffic between external clients and the Druid cluster and traffic between services within the cluster.
|
||||
@ -70,30 +67,27 @@ The server uses a KeyStore that contains private keys and certificate chain used
|
||||
The following example demonstrates how to use Java keytool to generate the KeyStore for the server and then create a trustStore to trust the key for the client:
|
||||
|
||||
1. Generate the KeyStore with the Java `keytool` command:
|
||||
```
|
||||
$> keytool -keystore keystore.jks -alias druid -genkey -keyalg RSA
|
||||
```bash
|
||||
keytool -keystore keystore.jks -alias druid -genkey -keyalg RSA
|
||||
```
|
||||
2. Export a public certificate:
|
||||
```
|
||||
$> keytool -export -alias druid -keystore keystore.jks -rfc -file public.cert
|
||||
```bash
|
||||
keytool -export -alias druid -keystore keystore.jks -rfc -file public.cert
|
||||
```
|
||||
3. Create the trustStore:
|
||||
```
|
||||
$> keytool -import -file public.cert -alias druid -keystore truststore.jks
|
||||
```bash
|
||||
keytool -import -file public.cert -alias druid -keystore truststore.jks
|
||||
```
|
||||
|
||||
Druid uses Jetty as its embedded web server. See [Configuring SSL/TLS KeyStores
|
||||
](https://www.eclipse.org/jetty/documentation/jetty-11/operations-guide/index.html#og-keystore) from the Jetty documentation.
|
||||
|
||||
|
||||
> WARNING: Do not use use self-signed certificates for production environments. Instead, rely on your current public key infrastructure to generate and distribute trusted keys.
|
||||
|
||||
|
||||
> WARNING: Do not use self-signed certificates for production environments. Instead, rely on your current public key infrastructure to generate and distribute trusted keys.
|
||||
|
||||
### Update Druid TLS configurations
|
||||
Edit `common.runtime.properties` for all Druid services on all nodes. Add or update the following TLS options. Restart the cluster when you are finished.
|
||||
|
||||
```
|
||||
```properties
|
||||
# Turn on TLS globally
|
||||
druid.enableTlsPort=true
|
||||
|
||||
@ -115,11 +109,9 @@ druid.client.https.trustStorePassword=secret123 # replace with your own passwor
|
||||
druid.server.https.keyStoreType=jks
|
||||
druid.server.https.keyStorePath=my-keystore.jks # replace with correct keyStore file
|
||||
druid.server.https.keyStorePassword=secret123 # replace with your own password
|
||||
druid.server.https.certAlias=druid
|
||||
|
||||
druid.server.https.certAlias=druid
|
||||
```
|
||||
For more information, see [TLS support](tls-support.md) and [Simple SSLContext Provider Module](../development/extensions-core/simple-client-sslcontext.md).
|
||||
|
||||
For more information, see [TLS support](tls-support.md) and [Simple SSLContext Provider Module](../development/extensions-core/simple-client-sslcontext.md).
|
||||
|
||||
## Authentication and authorization
|
||||
|
||||
@ -129,24 +121,23 @@ Within Druid's operating context, authenticators control the way user identities
|
||||
|
||||
The following graphic depicts the course of request through the authentication process:
|
||||
|
||||
|
||||
![Druid security check flow](../assets/security-model-1.png "Druid security check flow")
|
||||
|
||||
![Druid security check flow](../assets/security-model-1.png "Druid security check flow")
|
||||
|
||||
## 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`:
|
||||
```
|
||||
```properties
|
||||
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:
|
||||
|
||||
```properties
|
||||
# Druid basic security
|
||||
druid.auth.authenticatorChain=["MyBasicMetadataAuthenticator"]
|
||||
druid.auth.authenticator.MyBasicMetadataAuthenticator.type=basic
|
||||
@ -157,10 +148,12 @@ An example configuration:
|
||||
# Default password for internal 'druid_system' user, should be changed for production.
|
||||
druid.auth.authenticator.MyBasicMetadataAuthenticator.initialInternalClientPassword=password2
|
||||
|
||||
# Uses the metadata store for storing users, you can use authentication API to create new users and grant permissions
|
||||
# Uses the metadata store for storing users.
|
||||
# You can use the authentication API to create new users and grant permissions
|
||||
druid.auth.authenticator.MyBasicMetadataAuthenticator.credentialsValidator.type=metadata
|
||||
|
||||
# If true and the request credential doesn't exists in this credentials store, the request will proceed to next Authenticator in the chain.
|
||||
# If true and if the request credential doesn't exist in this credentials store,
|
||||
# the request will proceed to next Authenticator in the chain.
|
||||
druid.auth.authenticator.MyBasicMetadataAuthenticator.skipOnFailure=false
|
||||
|
||||
druid.auth.authenticator.MyBasicMetadataAuthenticator.authorizerName=MyBasicMetadataAuthorizer
|
||||
@ -176,70 +169,82 @@ 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, to set up users and roles for learning how security works.
|
||||
|
||||
## 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.
|
||||
|
||||
![Druid Security model](../assets/security-model-2.png "Druid security model")
|
||||
|
||||
![Druid Security model](../assets/security-model-2.png "Druid security model")
|
||||
|
||||
|
||||
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>`.
|
||||
Replace `<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.
|
||||
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:
|
||||
```
|
||||
```
|
||||
> 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 request to `druid-ext/basic-security/authentication/db/MyBasicMetadataAuthenticator/users/<USERNAME>/credentials`. 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"}'
|
||||
```
|
||||
3. 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
|
||||
```
|
||||
4. 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
|
||||
```
|
||||
The payload of `perms.json` should be in the form:
|
||||
```
|
||||
[
|
||||
{
|
||||
"resource": {
|
||||
"name": "<PATTERN>",
|
||||
"type": "DATASOURCE"
|
||||
5. 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
|
||||
```
|
||||
|
||||
6. 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 following form:
|
||||
```json
|
||||
[
|
||||
{
|
||||
"resource": {
|
||||
"type": "DATASOURCE",
|
||||
"name": "<PATTERN>"
|
||||
},
|
||||
"action": "READ"
|
||||
},
|
||||
"action": "READ"
|
||||
},
|
||||
{
|
||||
"resource": {
|
||||
"name": "STATE",
|
||||
"type": "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.
|
||||
{
|
||||
"resource": {
|
||||
"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.
|
||||
|
||||
|
||||
## Configuring an LDAP authenticator
|
||||
@ -254,7 +259,7 @@ Within Druid's trust model there users can have different authorization levels:
|
||||
|
||||
Additionally, Druid operates according to the following principles:
|
||||
|
||||
From the inner most layer:
|
||||
From the innermost layer:
|
||||
1. Druid processes have the same access to the local files granted to the specified system user running the process.
|
||||
2. The Druid ingestion system can create new processes to execute tasks. Those tasks inherit the user of their parent process. This means that any user authorized to submit an ingestion task can use the ingestion task permissions to read or write any local files or external resources that the Druid process has access to.
|
||||
|
||||
@ -263,7 +268,7 @@ From the inner most layer:
|
||||
Within the cluster:
|
||||
1. Druid assumes it operates on an isolated, protected network where no reachable IP within the network is under adversary control. When you implement Druid, take care to setup firewalls and other security measures to secure both inbound and outbound connections.
|
||||
Druid assumes network traffic within the cluster is encrypted, including API calls and data transfers. The default encryption implementation uses TLS.
|
||||
3. Druid assumes auxiliary services such as the metadata store and ZooKeeper nodes are not under adversary control.
|
||||
2. Druid assumes auxiliary services such as the metadata store and ZooKeeper nodes are not under adversary control.
|
||||
|
||||
Cluster to deep storage:
|
||||
1. Druid does not make assumptions about the security for deep storage. It follows the system's native security policies to authenticate and authorize with deep storage.
|
||||
|
@ -15,22 +15,21 @@
|
||||
|
||||
from druidapi.druid import DruidClient
|
||||
|
||||
def jupyter_client(endpoint) -> DruidClient:
|
||||
def jupyter_client(endpoint, auth=None) -> DruidClient:
|
||||
'''
|
||||
Create a Druid client configured to display results as HTML withing a Jupyter notebook.
|
||||
Waits for the cluster to become ready to avoid intermitent problems when using Druid.
|
||||
'''
|
||||
from druidapi.html_display import HtmlDisplayClient
|
||||
druid = DruidClient(endpoint, HtmlDisplayClient())
|
||||
druid = DruidClient(endpoint, HtmlDisplayClient(), auth=auth)
|
||||
druid.status.wait_until_ready()
|
||||
return druid
|
||||
|
||||
def client(endpoint) -> DruidClient:
|
||||
def client(endpoint, auth=None) -> DruidClient:
|
||||
'''
|
||||
Create a Druid client for use in Python scripts that uses a text-based format for
|
||||
displaying results. Does not wait for the cluster to be ready: clients should call
|
||||
`status().wait_until_ready()` before making other Druid calls if there is a chance
|
||||
that the cluster has not yet fully started.
|
||||
'''
|
||||
return DruidClient(endpoint)
|
||||
|
||||
return DruidClient(endpoint, auth=auth)
|
||||
|
@ -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.authentication_users(),
|
||||
"authorizer": self.authorization_users()
|
||||
}
|
||||
|
||||
def status(self):
|
||||
'''
|
||||
Returns both the authenticator and authorizer status.
|
||||
'''
|
||||
return {
|
||||
"authenticator": self.authentication_status(),
|
||||
"authorizer": self.authorization_status()
|
||||
}
|
||||
|
||||
def resource(self, type, name):
|
||||
return {
|
||||
'type': type,
|
||||
'name': name
|
||||
}
|
||||
|
||||
def action(self, resource, action):
|
||||
return {
|
||||
'resource': resource,
|
||||
'action': action
|
||||
}
|
||||
|
||||
def resource_action(self, type, name, action):
|
||||
return self.action(self.resource(type, name), action)
|
||||
|
||||
def grant_permissions(self, role, triples):
|
||||
'''
|
||||
Set the permissions for a role given an array of triples of the form
|
||||
`[[type, name, action], ...]`.
|
||||
|
||||
Overwrites any existing permissions.
|
||||
'''
|
||||
perms = []
|
||||
for triple in triples:
|
||||
perms.append(self.resource_action(triple[0], triple[1], triple[2]))
|
||||
self.set_role_permissions(role, perms)
|
@ -54,3 +54,11 @@ SQL_ARRAY_TYPE = 'ARRAY'
|
||||
RUNNING_STATE = 'RUNNING'
|
||||
SUCCESS_STATE = 'SUCCESS'
|
||||
FAILED_STATE = 'FAILED'
|
||||
|
||||
# Resource constants
|
||||
DATASOURCE_RESOURCE = 'DATASOURCE'
|
||||
STATE_RESOURCE = 'STATE'
|
||||
CONFIG_RESOURCE = 'CONFIG'
|
||||
EXTERNAL_RESOURCE = 'EXTERNAL'
|
||||
READ_ACTION = 'READ'
|
||||
WRITE_ACTION = 'WRITE'
|
||||
|
@ -19,6 +19,7 @@ from druidapi.catalog import CatalogClient
|
||||
from druidapi.sql import QueryClient
|
||||
from druidapi.tasks import TaskClient
|
||||
from druidapi.datasource import DatasourceClient
|
||||
from druidapi.basic_auth import BasicAuthClient
|
||||
|
||||
class DruidClient:
|
||||
'''
|
||||
@ -26,8 +27,8 @@ class DruidClient:
|
||||
specialized "clients" that group many of Druid's REST API calls.
|
||||
'''
|
||||
|
||||
def __init__(self, router_endpoint, display_client=None):
|
||||
self.rest_client = DruidRestClient(router_endpoint)
|
||||
def __init__(self, router_endpoint, display_client=None, auth=None):
|
||||
self.rest_client = DruidRestClient(router_endpoint, auth=auth)
|
||||
self.status_client = None
|
||||
self.catalog_client = None
|
||||
self.sql_client = None
|
||||
@ -116,6 +117,33 @@ class DruidClient:
|
||||
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'))
|
||||
ac = coord.basic_security('yourAuthenticator', 'yourAuthorizer')
|
||||
```
|
||||
|
||||
Parameters
|
||||
----------
|
||||
authenticator: str
|
||||
Authenticator name as set in the `druid.auth.authenticatorChain`
|
||||
runtime property.
|
||||
|
||||
authorizer: str, default = same as authenticator
|
||||
Authorizer name as set in the `druid.auth.authorizers` runtime property.
|
||||
Defaults to the same name as the `authenticator` parameter for simple cases.
|
||||
'''
|
||||
return BasicAuthClient(self.rest_client, authenticator, authorizer)
|
||||
|
||||
@property
|
||||
def display(self):
|
||||
|
@ -95,10 +95,27 @@ class DruidRestClient:
|
||||
concatenating the service endpoint with the request URL.
|
||||
'''
|
||||
|
||||
def __init__(self, endpoint):
|
||||
def __init__(self, endpoint, auth=None):
|
||||
'''
|
||||
Creates a Druid rest client endpoint using the given endpoint URI and
|
||||
optional authentication.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
endpoint: str
|
||||
The Druid router endpoint of the form `'server:port'`. Use
|
||||
`'localhost:8888'` for a Druid instance running locally.
|
||||
|
||||
auth: str, default = None
|
||||
Optional authorization credentials in the format described
|
||||
by the Requests library. For Basic auth use
|
||||
`auth=('user', 'password')`
|
||||
'''
|
||||
self.endpoint = endpoint
|
||||
self.trace = False
|
||||
self.session = requests.Session()
|
||||
if auth:
|
||||
self.session.auth = auth
|
||||
|
||||
def enable_trace(self, flag=True):
|
||||
self.trace = flag
|
||||
@ -210,7 +227,7 @@ class DruidRestClient:
|
||||
check_error(r)
|
||||
return r.json()
|
||||
|
||||
def post_only_json(self, req, body, args=None, headers=None, params=None) -> requests.Request:
|
||||
def post_only_json(self, req, body, args=None, headers=None, params=None, require_ok=True) -> requests.Request:
|
||||
'''
|
||||
Issues a POST request for the given URL on this node, with a JSON request, returning
|
||||
the Requests library `Response` object.
|
||||
@ -244,13 +261,18 @@ class DruidRestClient:
|
||||
if self.trace:
|
||||
print('POST:', url)
|
||||
print('body:', body)
|
||||
return self.session.post(url, json=body, headers=headers, params=params)
|
||||
r = self.session.post(url, json=body, headers=headers, params=params)
|
||||
if require_ok:
|
||||
check_error(r)
|
||||
return r
|
||||
|
||||
def delete(self, req, args=None, params=None, headers=None):
|
||||
def delete(self, req, args=None, params=None, headers=None, require_ok=True):
|
||||
url = self.build_url(req, args)
|
||||
if self.trace:
|
||||
print('DELETE:', url)
|
||||
r = self.session.delete(url, params=params, headers=headers)
|
||||
if require_ok:
|
||||
check_error(r)
|
||||
return r
|
||||
|
||||
def delete_json(self, req, args=None, params=None, headers=None):
|
||||
|
Loading…
x
Reference in New Issue
Block a user