Skip to content

Token authentication -> edge #616

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

Open
wants to merge 5 commits into
base: antalya
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 2 additions & 5 deletions contrib/jwt-cpp-cmake/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
set(ENABLE_JWT_CPP_DEFAULT OFF)
if(ENABLE_LIBRARIES AND CLICKHOUSE_CLOUD)
set(ENABLE_JWT_CPP_DEFAULT ON)
endif()
set(ENABLE_JWT_CPP_DEFAULT ON)

option(ENABLE_JWT_CPP "Enable jwt-cpp library" ${ENABLE_JWT_CPP_DEFAULT})

Expand All @@ -20,4 +17,4 @@ set (JWT_CPP_INCLUDE_DIR "${ClickHouse_SOURCE_DIR}/contrib/jwt-cpp/include")

add_library (_jwt-cpp INTERFACE)
target_include_directories(_jwt-cpp SYSTEM BEFORE INTERFACE ${JWT_CPP_INCLUDE_DIR})
add_library(ch_contrib::jwt-cpp ALIAS _jwt-cpp)
add_library(ch_contrib::jwt-cpp ALIAS _jwt-cpp)
3 changes: 2 additions & 1 deletion docs/en/operations/external-authenticators/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,5 @@ The following external authenticators and directories are supported:
- [LDAP](./ldap.md#external-authenticators-ldap) [Authenticator](./ldap.md#ldap-external-authenticator) and [Directory](./ldap.md#ldap-external-user-directory)
- Kerberos [Authenticator](./kerberos.md#external-authenticators-kerberos)
- [SSL X.509 authentication](./ssl-x509.md#ssl-external-authentication)
- HTTP [Authenticator](./http.md)
- HTTP [Authenticator](./http.md)
- JWT [Authenticator](./jwt.md)
219 changes: 219 additions & 0 deletions docs/en/operations/external-authenticators/jwt.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
---
slug: /en/operations/external-authenticators/jwt
---
# JWT
import SelfManaged from '@site/docs/en/_snippets/_self_managed_only_no_roadmap.md';

<SelfManaged />

Existing and properly configured ClickHouse users can be authenticated via JWT.

Currently, JWT can only be used as an external authenticator for existing users, which are defined in `users.xml` or in local access control paths.
The username will be extracted from the JWT after validating the token expiration and against the signature. Signature can be validated by:
- static public key
- static JWKS
- received from the JWKS servers

It is mandatory for a JWT tot indicate the name of the ClickHouse user under `"sub"` claim, otherwise it will not be accepted.

A JWT may additionally be verified by checking the JWT payload.
In this case, the occurrence of specified claims from the user settings in the JWT payload is checked.
See [Enabling JWT authentication in `users.xml`](#enabling-jwt-auth-in-users-xml)

To use JWT authentication, JWT validators must be configured in ClickHouse config.


## Enabling JWT validators in ClickHouse {#enabling-jwt-validators-in-clickhouse}

To enable JWT validators, add `token_validators` section in `config.xml`. This section may contain several JWT verifiers, minimum is 1.

### Verifying JWT signature using static key {$verifying-jwt-signature-using-static-key}

**Example**
```xml
<clickhouse>
<!- ... -->
<jwt_validators>
<my_static_key_validator>
<algo>HS256</algo>
<static_key>my_static_secret</static_key>
</my_static_key_validator>
</jwt_validators>
</clickhouse>
```

#### Parameters:

- `algo` - Algorithm for validate signature. Supported:

| HMAC | RSA | ECDSA | PSS | EdDSA |
|-------| ----- | ------ | ----- | ------- |
| HS256 | RS256 | ES256 | PS256 | Ed25519 |
| HS384 | RS384 | ES384 | PS384 | Ed448 |
| HS512 | RS512 | ES512 | PS512 | |
| | | ES256K | | |
Also support None.
- `static_key` - key for symmetric algorithms. Mandatory for `HS*` family algorithms.
- `static_key_in_base64` - indicates if the `static_key` key is base64-encoded. Optional, default: `False`.
- `public_key` - public key for asymmetric algorithms. Mandatory except for `HS*` family algorithms and `None`.
- `private_key` - private key for asymmetric algorithms. Optional.
- `public_key_password` - public key password. Optional.
- `private_key_password` - private key password. Optional.

### Verifying JWT signature using static JWKS {$verifying-jwt-signature-using-static-jwks}

:::note
Only RS* family algorithms are supported!
:::

**Example**
```xml
<clickhouse>
<!- ... -->
<jwt_validators>
<my_static_jwks_validator>
<static_jwks>{"keys": [{"kty": "RSA", "alg": "RS256", "kid": "mykid", "n": "_public_key_mod_", "e": "AQAB"}]}</static_jwks>
</my_static_jwks_validator>
</jwt_validators>
</clickhouse>
```

#### Parameters:
- `static_jwks` - content of JWKS in json
- `static_jwks_file` - path to file with JWKS

:::note
Only one of `static_jwks` or `static_jwks_file` keys must be present in one verifier
:::

### Verifying JWT signature using JWKS servers {$verifying-jwt-signature-using-static-jwks}

**Example**
```xml
<clickhouse>
<!- ... -->
<jwt_validators>
<basic_auth_server>
<uri>http://localhost:8000/.well-known/jwks.json</uri>
<connection_timeout_ms>1000</connection_timeout_ms>
<receive_timeout_ms>1000</receive_timeout_ms>
<send_timeout_ms>1000</send_timeout_ms>
<max_tries>3</max_tries>
<retry_initial_backoff_ms>50</retry_initial_backoff_ms>
<retry_max_backoff_ms>1000</retry_max_backoff_ms>
<refresh_ms>300000</refresh_ms>
</basic_auth_server>
</jwt_validators>
</clickhouse>
```

#### Parameters:

- `uri` - JWKS endpoint. Mandatory.
- `refresh_ms` - Period for resend request for refreshing JWKS. Optional, default: 300000.

Timeouts in milliseconds on the socket used for communicating with the server (optional):
- `connection_timeout_ms` - Default: 1000.
- `receive_timeout_ms` - Default: 1000.
- `send_timeout_ms` - Default: 1000.

Retry parameters (optional):
- `max_tries` - The maximum number of attempts to make an authentication request. Default: 3.
- `retry_initial_backoff_ms` - The backoff initial interval on retry. Default: 50.
- `retry_max_backoff_ms` - The maximum backoff interval. Default: 1000.

### Verifying access tokens {$verifying-access-tokens}

Access tokens that are not JWT (and thus no data can be extracted from the token directly) need to be resolved by external providers.

**Example**
```xml
<clickhouse>
<!- ... -->
<access_token_processors>
<my_access_token_processor>
<provider>google</provider>
</my_access_token_processor>
</access_token_processors>
</clickhouse>
```

#### Parameters:

- `provider` - name of provider that will be used for token processing. Mandatory parameter. Possible options: `google`.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"and 'azure'"



### Enabling JWT authentication in `users.xml` {#enabling-jwt-auth-in-users-xml}

In order to enable JWT authentication for the user, specify `jwt` section instead of `password` or other similar sections in the user definition.

Parameters:
- `claims` - An optional string containing a json object that should be contained in the token payload.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If take me as reference customer and document reader, it's not clean, what exactly can be in payload.
I found docs for Google (https://developers.google.com/wallet/reference/rest/v1/Jwt) and MS (https://learn.microsoft.com/en-us/entra/identity-platform/access-token-claims-reference) and saw there other fields, but no resource_access.
May be add some links here, or samples for different providers?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, I inherited this thing, and I think I will remove it: it can be confusing indeed, and its use case is not obvious. Also, it makes code somewhat more complex


Example (goes into `users.xml`):
```xml
<clickhouse>
<!- ... -->
<my_user>
<!- ... -->
<jwt>
<claims>{"resource_access":{"account": {"roles": ["view-profile"]}}}</claims>
</jwt>
</my_user>
</clickhouse>
```

Here, the JWT payload must contain `["view-profile"]` on path `resource_access.account.roles`, otherwise authentication will not succeed even with a valid JWT.

```
{
...
"resource_access": {
"account": {
"roles": ["view-profile"]
}
},
...
}
```

:::note
JWT authentication cannot be used together with any other authentication method. The presence of any other sections like `password` alongside `jwt` will force ClickHouse to shut down.
:::

### Enabling JWT authentication using SQL {#enabling-jwt-auth-using-sql}

When [SQL-driven Access Control and Account Management](/docs/en/guides/sre/user-management/index.md#access-control) is enabled in ClickHouse, users identified by JWT authentication can also be created using SQL statements.

```sql
CREATE USER my_user IDENTIFIED WITH jwt CLAIMS '{"resource_access":{"account": {"roles": ["view-profile"]}}}'
```

Or without additional JWT payload checks:

```sql
CREATE USER my_user IDENTIFIED WITH jwt
```

## JWT authentication examples {#jwt-authentication-examples}

#### Console client

```
clickhouse-client -jwt <token>
```

#### HTTP requests

```
curl 'http://localhost:8080/?' \
-H 'Authorization: Bearer <TOKEN>' \
-H 'Content type: text/plain;charset=UTF-8' \
--data-raw 'SELECT current_user()'
```
:::note
ClickHouse will look for a JWT token in (by priority):
1. `X-ClickHouse-JWT-Token` header.
2. `Authorization` header.
3. `token` request parameter. In this case, the "Bearer" prefix should not exist.
:::
108 changes: 108 additions & 0 deletions docs/en/operations/external-authenticators/tokens.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
---
slug: /en/operations/external-authenticators/oauth
title: "OAuth 2.0"
---
import SelfManaged from '@site/docs/en/_snippets/_self_managed_only_no_roadmap.md';

<SelfManaged />

OAuth 2.0 access tokens can be used to authenticate ClickHouse users. This works in two ways:

- Existing users (defined in `users.xml` or in local access control paths) can be authenticated with access token if this user can be `IDENTIFIED WITH jwt`.
- Use Identity Provider (IdP) as an external user directory and allow locally undefined users to be authenticated with a token if it is valid and recognized by the provider.

Though this authentication method is different from JWT authentication, it works under the same authentication method to maintain better compatibility.

For both of these approaches a definition of `access_token_processors` is mandatory.

## Access Token Processors

To define an access token processor, add `access_token_processors` section to `config.xml`. Example:
```xml
<clickhouse>
<access_token_processors>
<gogoogle>
<provider>Google</provider>
<email_filter>^[A-Za-z0-9._%+-]+@example\.com$</email_filter>
<cache_lifetime>600</cache_lifetime>
</gogoogle>
<azuure>
<provider>azure</provider>
<client_id>CLIENT_ID</client_id>
<tenant_id>TENANT_ID</tenant_id>
</azuure>
</access_token_processors>
</clickhouse>
```

:::note
Different providers have different sets of parameters.
:::

**Parameters**

- `provider` -- name of identity provider. Mandatory, case-insensitive. Supported options: "Google", "Azure".
- `cache_lifetime` -- maximum lifetime of cached token (in seconds). Optional, default: 3600.
- `email_filter` -- Regex for validation of user emails. Optional parameter, only for Google IdP.
- `client_id` -- Azure AD (Entra ID) client ID. Optional parameter, only for Azure IdP.
- `tenant_id` -- Azure AD (Entra ID) tenant ID. Optional parameter, only for Azure IdP.

### Tokens cache
To reduce number of requests to IdP, tokens are cached internally for no longer then `cache_lifetime` seconds.
If token expires sooner than `cache_lifetime`, then cache entry for this token will only be valid while token is valid.
If token lifetime is longer than `cache_lifetime`, cache entry for this token will be valid for `cache_lifetime`.

## IdP as External Authenticator {#idp-external-authenticator}

Locally defined users can be authenticated with an access token. To allow this, `jwt` must be specified as user's authentication method. Example:

```xml
<clickhouse>
<!- ... -->
<users>
<!- ... -->
<my_user>
<!- ... -->

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

<jwt> (open tag)

</jwt>
</my_user>
</users>
</clickhouse>
```

At each login attempt, ClickHouse will attempt to validate token and get user info against every defined access token provider.

When SQL-driven [Access Control and Account Management](/docs/en/guides/sre/user-management/index.md#access-control) is enabled, users that are authenticated with tokens can also be created using the [CREATE USER](/docs/en/sql-reference/statements/create/user.md#create-user-statement) statement.

Query:

```sql
CREATE USER my_user IDENTIFIED WITH jwt;
```

## Identity Provider as an External User Directory {#idp-external-user-directory}

If there is no suitable user pre-defined in ClickHouse, authentication is still possible: Identity Provider can be used as source of user information.
To allow this, add `token` section to the `users_directories` section of the `config.xml` file.

At each login attempt, ClickHouse tries to find the user definition locally and authenticate it as usual.
If the user is not defined, ClickHouse will treat user as externally defined, and will try to validate the token and get user information from the specified processor.
If validated successfully, the user will be considered existing and authenticated. The user will be assigned roles from the list specified in the `roles` section.
All this implies that the SQL-driven [Access Control and Account Management](/docs/en/guides/sre/user-management/index.md#access-control) is enabled and roles are created using the [CREATE ROLE](/docs/en/sql-reference/statements/create/role.md#create-role-statement) statement.

**Example**

```xml
<clickhouse>
<token>
<processor>gogoogle</processor>
<roles>
<token_test_role_1 />
</roles>
</token>
</clickhouse>
```

**Parameters**

- `server` — Name of one of processors defined in `access_token_processors` config section described above. This parameter is mandatory and cannot be empty.
- `roles` — Section with a list of locally defined roles that will be assigned to each user retrieved from the IdP.
Loading
Loading