Configuring JWT for Authentication¶
Pre Read
You should read how Privacera uses JWT for authentication before proceeding with this topic.
You need to enable and configure the JWT token User Identity feature in Privacera. This is a common configuration for
- OLAC and FGAC connectors on Self Managed and Data Plane deployments
- OLAC connectors on PrivaceraCloud
The configuration maps the token issuer value to a set of configurations that are used to validate the JWT token. You can configure multiple token issuers in Privacera.
Setup for JWT public key configuration¶
On the host where Privacera Manager is installed, do the following steps:
Bash | |
---|---|
JWT_CONFIGURATION_LIST
as given in next section. Location of public key files for static public key configuration
For static public key configuration, the public key should be copied in a file in PEM format to the config/custom-properties
directory.
After all the changes are done you need to update the helm chart, apply the changes and also run the post install steps
Run the following command to run the post install steps:
Fallback for Databricks OLAC and FGAC connectors
Copying the vars.jwt-auth.yaml
to the config/custom-vars
directory will enable JWT User Identity for all OLAC and FGAC connectors. If you want to continue using the logged-in user identity for Databricks OLAC and FGAC connectors, then you need to set this property by creating a new files in the config/custom-properties/privacera_spark_custom.properties
directory.
Bash | |
---|---|
true
. Properties | |
---|---|
To enable JWT token for User Identity in PrivaceraCloud, you need to add below properties in s3 application.
Text Only | |
---|---|
1 2 3 4 5 |
|
Reference for JWT_CONFIGURATION_LIST¶
Here is the list of properties that you can configure in the JWT_CONFIGURATION_LIST
property.
Description of properties in JWT_CONFIGURATION_LIST
These properties configure the payload of the JWT token:
-
index
- Description: Index of the JWT configuration. This is a unique identifier for the JWT configuration.
- Required: Yes
- Supported Values: 0, 1, 2, 3 etc.
-
issuer
- Description: Issuer of the JWT Payload. This is a string identifier. The JWT tokens that contain this value in the
iss
field will be validated using this configuration. - Required: Yes
- Description: Issuer of the JWT Payload. This is a string identifier. The JWT tokens that contain this value in the
-
subject
- Description: Subject of the JWT Payload. This is a string identifier. The JWT tokens that contain this value in the
sub
field will be used to enforce access control. - Required: Optional
- Sample Value:
infra_test_user
- Description: Subject of the JWT Payload. This is a string identifier. The JWT tokens that contain this value in the
-
secret
- Description: Secret key to validate the JWT token. This is a string identifier. JWT tokens that include this value in their
secret
field will be validated using this configuration. This is specifically applicable when the JWT token is signed and encrypted using theHS256
algorithm. - Required: Optional
- Sample Value:
mysecret
- Description: Secret key to validate the JWT token. This is a string identifier. JWT tokens that include this value in their
-
userKey
- Description: JWT Payload key for the username.
- Required: Optional
- Default:
client_id
-
groupKey
- Description: JWT Payload key for the group name.
- Required: Optional
- Default:
scope
-
parserType
- Description: Specifies how the scope or group is formatted. Choose one of the following values:
PING_IDENTITY
: Use when scope/group is an array. Example:KEYCLOAK
: Use when scope/group is space separated. Example:
- Required: Yes
- Description: Specifies how the scope or group is formatted. Choose one of the following values:
-
audience
- Description: Audience for whom the JWT token has been issued. This is a string identifier. The JWT tokens that contain this value in the
aud
field will be validated using this configuration. - Required: Optional
- Description: Audience for whom the JWT token has been issued. This is a string identifier. The JWT tokens that contain this value in the
For Static public key configuration
- publickey
- Description: JWT file name that you copied in previous steps. (in this case use Algorithm RS256)
- Required: Required only for Static public Key
For Dynamic Public Key, here are the additional properties. Note that we are providing both the Privacera Manager property | PrivaceraCloud property
.
-
pubKeyProviderEndpoint | privacera.jwt.0.token.publickey.provider.url
- Description: API URL by which we will return public key.
- Format:
https://my-sat-server/<api-to-get-public-key-by-kid>/
orhttps://my-sat-server/<api-to-get-public-key-by-kid>/?kid=
- Privacera code will add
<kid>
at the end above URL and it will become like thishttps://my-sat-server/<api-to-get-public-key-by-kid>/<kid>
orhttps://my-sat-server/<api-to-get-public-key-by-kid>/?kid=<kid>
and that API should return public key of specific key id (kid) mentioned in JWT.
- Format:
- Required: Yes
- Description: API URL by which we will return public key.
-
pubKeyProviderAuthType | privacera.jwt.0.token.publickey.provider.auth.type
- Description: Authorization type as per API URL (
BASIC
/NONE
) - Required: Optional
- Default:
NONE
- Description: Authorization type as per API URL (
-
pubKeyProviderAuthUserName | privacera.jwt.0.token.publickey.provider.auth.username
- Description: Username for JWKS Provider
- Required: Required When
pubKeyProviderAuthType=BASIC
-
pubKeyProviderAuthTypePassword | privacera.jwt.0.token.publickey.provider.auth.password
- Description: Password for JWKS Provider
- Required: Required When
pubKeyProviderAuthType=BASIC
-
pubKeyProviderJsonResponseKey | privacera.jwt.0.token.provider.response.key
- Description: JWKS Response JSON Key to get Public Key
- Required: Yes
- Default: x5c
-
jwtTokenProviderKeyId | privacera.jwt.0.token.provider.key.id
- Description: JWT Headers Key to get public key id to retrieve from JWKS Provider
- Required: Yes
Static public key configuration¶
For static public key configuration, here are some sample configurations that you can put in the vars.jwt-auth.yaml
file. Typically you will have only one configuration, but in some cases you may have multiple configurations. The meaning of these properties is explained in the Reference section .
Dynamic public key configuration¶
Privacera also supports validating JWT tokens using a dynamic public key provider. This is useful when the public key is not static and can change. Here is the configuration for dynamic public key provider which uses Basic Authentication.
Dynamic public key configuration (Without Basic Authentication)¶
Here is the configuration for dynamic public key provider which does not use Basic Authentication.
YAML | |
---|---|
Validating JWT token¶
You can validate the setup with your IDP. However, if you want to validate using your own temporary IDP, then follow the below steps:
Testing Static Key Validation¶
We will walk you through an end to end setup using Python script for generating JWT token and using a Python JWKS server. These utilities are for helping you do an end to end flow. These should not be used in a production environment.
1. Create Python virtual environment and install libraries
-
Create a folder to store the script and Python virtual environment.
-
Create a requirements file to download libraries required by the script. These are open source libraries commonly used for signing and JWT creation
Add the following content to the file.Bash -
Create a Python virtual environment. This is a one time step.
Bash -
Activate the virtual environment so that you can use the virtual environment. This step is required to be done everytime you start a new shell.
Bash -
Install the required libraries. This is a one time step.
Bash -
You can deactivate the Python virtual environment when are you done.
Bash | |
---|---|
2. Generate RSA 256 and EC 256 key-pairs for test purpose
-
We are going to generate a RSA 256 key-pair and an EC 256 key-pair. These are for test purpose to show you how to use RSA and EC keys. Typically, you will be using only one type of signing algorithm in yours setup.
-
Generate a RSA 256 key-pair. This will be used to sign the JWT token. The private key will be encrypted using a password. You need this if you want to sign the token using a RSA 256 key.
-
Generate an EC 256 key pair. This will be used to sign the JWT token. The private key will be encrypted using a password. You need this if you want to sign the token using an EC 256 key.
3. Create the Python script for generating JWT token
-
Create a Python script to generate the JWT token. This script will generate a JWT token and sign it using the private key from the keypairs that we have generated. It will take a command line argument to choose the keypair to use.
Bash -
Before running the script, you can change the user_name variable to the user that you want to use. This user should be present in Privacera. You can also change the groups in the token variable. Run the script and copy the JWT token that is printed. You can generate the token using RSA or EC key by passing the argument to the script.
Bash Use the encoded value from the output of the script as the JWT_TOKEN in the EMR, Databricks or Apache Spark clusterBash -
Static key configuration Copy the public key files to the Privacera Manager configuration directory.
4. Configure static key JWT configuration in Privacera Manager or PrivaceraCloud
Use the generated public keys to configure the static JWT configuration in Privacera Manager or PrivaceraCloud. Follow the steps from top of this document
5. Configure and test OLAC or FGAC plugin using the generated JWT token
The JWT token with the username infra_test_user. You can create a temporary user called infra_test_user in Privacera and create Access policies for that user in privacera_s3
service repo. You can use this user to test the OLAC or FGAC plugin.
6. Test JWT configuration in Privacera Dataserver
Configure Privacera Dataserver to use the JWT configuration. [TOOD: Give Link here]
Follow this step to test the JWT configuration in Privacera Dataserver.
For now this will only work in Self Managed from Privacera release 9.3.0.1 onwards
Bash | |
---|---|
Http Status code
Text Only | |
---|---|
1 2 |
|
Content type: application/json
Response format for Valid Token
JSON | |
---|---|
Response format for Invalid Token
Response format if empty payload OR empty jwt token
Testing Dynamic Key Validation¶
For dynamic key validation, you can create a IDP server using Python and test your configuration.
1. Create Python script to run as JWKS server
-
For testing the dynamic public key configuration, you can use the following script to serve the public key using JWKS endpoint.
Paste the following code in the file.Bash Start the server and keep it running.Python 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153
import base64 import json from datetime import datetime, timedelta from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import serialization, hashes import cryptography.hazmat.primitives.asymmetric.rsa as rsa import cryptography.hazmat.primitives.asymmetric.ec as ec from flask import Flask, request, jsonify from flask_httpauth import HTTPBasicAuth def compute_sha256_thumbprint(public_key_pem): # Load the PEM encoded public key public_key = serialization.load_pem_public_key( public_key_pem, backend=default_backend() ) # Compute SHA-256 hash of the DER encoding of the public key der_encoding = public_key.public_bytes( encoding=serialization.Encoding.DER, format=serialization.PublicFormat.SubjectPublicKeyInfo ) sha256_hash = hashes.Hash(hashes.SHA256(), backend=default_backend()) sha256_hash.update(der_encoding) thumbprint = sha256_hash.finalize() return thumbprint def int_to_base64(n): # Determine the number of bytes required to represent the integer num_bytes = (n.bit_length() + 7) // 8 # Convert the integer to bytes int_bytes = n.to_bytes(num_bytes, byteorder='big') # Encode the bytes using base64 base64_bytes = base64.b64encode(int_bytes) # Convert the base64 bytes to a string base64_string = base64_bytes.decode('utf-8') return base64_string def get_response(kid, pem_file_path, expiry_time_delta): # Read the public key from a PEM file with open(pem_file_path, "rb") as pem_file: public_key_pem = pem_file.read() # Remove BEGIN and END headers and footers pem_lines = public_key_pem.decode('utf-8').split('\n') pem_contents = (''.join(pem_lines) .replace('-----BEGIN PUBLIC KEY-----', '') .replace('-----END PUBLIC KEY-----', '')) # Load the PEM encoded public key public_key = serialization.load_pem_public_key( public_key_pem, backend=default_backend() ) print(f"type={type(public_key)}") # Extract SHA-256 thumbprint thumbprint = compute_sha256_thumbprint(public_key_pem) thumbprint_hex = thumbprint.hex() print("PEM File (Single String without Headers and Footers):") print(pem_contents) # Check the type of the public key if isinstance(public_key, rsa.RSAPublicKey): # Extract modulus and exponent from the public key modulus = public_key.public_numbers().n exponent = public_key.public_numbers().e response = { "kty": "RSA", "use": "sig", "e": int_to_base64(modulus), "n": int_to_base64(exponent), "x5t#S256": thumbprint_hex, "x5c": [pem_contents], "kid": kid, "exp": (datetime.utcnow() + expiry_time_delta).timestamp(), } print(json.dumps(response, indent=4)) return response elif isinstance(public_key, ec.EllipticCurvePublicKey): ec_numbers = public_key.public_numbers() # Extract x and y coordinates x = ec_numbers.x y = ec_numbers.y response = { "kty": "EC", "use": "sig", "x": int_to_base64(x), "y": int_to_base64(y), "x5t#S256": thumbprint_hex, "x5c": [pem_contents], "kid": kid, "exp": (datetime.utcnow() + expiry_time_delta).timestamp(), } return response else: return "Unknown" def main(): USERNAME = 'admin' PASSWORD = 'Welcome@123' kid_dict = { 'kid_1': 'jwt-rs256-public.pem', 'kid_2': 'jwt-ec256-public.pem' } app = Flask(__name__) auth = HTTPBasicAuth() # Verify username and password for basic authentication @auth.verify_password def verify_password(username, password): return username == USERNAME and password == PASSWORD # GET API to retrieve public key by kid @app.route('/get_public_key', methods=['GET']) @auth.login_required def get_public_key(): kid = request.args.get('kid') public_key = kid_dict.get(kid) if public_key is None: return jsonify({'error': 'Public key not found'}), 404 else: return get_response(kid, public_key, timedelta(days=30)) app.run(host="0.0.0.0", port=9090, debug=True) if __name__ == "__main__": print("starting main") main() print("ending main")
Bash -
You can test this endpoint using a curl command as follows, from another shell,
Bash JSON Bash
2. Configure Dynamic public key using the Python JWKS server endpoint
Follow the instruction from the top of this document to configure the dynamic public key configuration in Privacera Manager or PrivaceraCloud.
After Privacera Manager has been run so that it restarts the Privacera Dataserver, you can use the dataserver test endpoint as given above. You can then test the OLAC plugin. Similarly, you will have to upload the newly generated FGAC plugin configuration and us it to test the FGAC plugin.
- Prev topic: Advanced Configuration