CockroachDB SSO login to the SQL prompt via JWT

Fabio Ghirardello - Aug 30 '23 - - Dev Community

In this post, I walk through how to configure CockroachDB to allow users to login to the SQL prompt using their SSO login.

Instructions on how to configure SSO access for the DBConsole are available here.

The users will request a JSON Web Token (JWT) from their identity provider (IdP) and use that token as the temporary password.

There are many IdPs, and in this example I am using Okta. The workflow is similar for all IdPs, so while the details for how to configure the IdP varies from IdP to IdP, from the point of view of CockroachDB this is entirely transparent.

Prerequisite: IdP (Okta) setup

To generate a JWT, we first need to setup an IdP. Okta offers a developer account, so I signed up and quickly created an App integration.

Here is a quick walk-through:

Create a few users

Navigate to Directory > People and click Add Person.

user

Here's my list: I set password Mazinga123 for all these users.
Later, we'll use the password to request a JWT.

users

Create a Group

Go to Directory > Groups, click Add Group and call it "Bankers".

Then, select that group and assign users to it:

group

Create an App Integration

Navigate to Applications > Applications and click Create App Integration.

Select the "OIDC" as the protocol, and "Native application" as the type.

app1

I call the integration "BankApp". Ensure "Resource Owner Password" is selected.

app2

Under "Assignments", add the "Bankers" group we previously created.

app3

Click Save.
Now, create a Client Secret.

app4

Make sure you jot down your credentials. In my case:

client_id=0oab1arbbieTSsFVJ5d7
client_secret=WbAWHW9UeURzXez99PMGWvuVp4KiGDXTNQrDajvszN2iQ3YyMjGyx0sWts1sM36J
Enter fullscreen mode Exit fullscreen mode

Configure Token fields

Next, we need to edit the Okta JWT by adding the user.login field.
This is the "Username" field in the Person profile.

user2

Go to Security > API, select the "default" Authorization Server, and select the "Claims" tab.
From here, add a claim:

claim1

claim2

You can actually preview what the token will look like.
Check the bottom right hand corner, in the Payload section, the "login" field is now present.

claim4

Finally, jot down the Issuer URI, we will need this URL later to find the .well-known details. Ours is https://dev-85651931.okta.com/oauth2/default

claim3

We are now ready to configure CockroachDB against our Okta BankApp integration.

CockroachDB setup

Start a CockroachDB demo cluster, as it automatically loads a temporary license.

$ cockroach demo --no-example-database
#
# Welcome to the CockroachDB demo database!
#
# You are connected to a temporary, in-memory CockroachDB cluster of 1 node.
#
# This demo session will send telemetry to Cockroach Labs in the background.
# To disable this behavior, set the environment variable
# COCKROACH_SKIP_ENABLING_DIAGNOSTIC_REPORTING=true.
#
# Reminder: your changes to data stored in the demo session will not be saved!
#
# If you wish to access this demo cluster using another tool, you will need
# the following details:
#
#   - Connection parameters:
#      (webui)    http://127.0.0.1:8080/demologin?password=demo9642&username=demo
#      (cli)      cockroach sql --certs-dir=/Users/fabio/.cockroach-demo -u demo -d defaultdb
#      (sql)      postgresql://demo:demo9642@127.0.0.1:26257/defaultdb?sslmode=require&sslrootcert=%2FUsers%2Ffabio%2F.cockroach-demo%2Fca.crt
#   
#   - Username: "demo", password: "demo9642"
#   - Directory with certificate files (for certain SQL drivers/tools): /Users/fabio/.cockroach-demo
#
# You can enter \info to print these details again.
#
# Server version: CockroachDB CCL v23.1.4 (x86_64-apple-darwin19, built 2023/06/16 20:54:39, go1.19.4) (same version as client)
# Cluster ID: f72ea38c-c5b3-437e-bd03-2391e6b5d150
# Organization: Cockroach Demo
#
# Enter \? for a brief introduction.
#
demo@127.0.0.1:26257/defaultdb>  
Enter fullscreen mode Exit fullscreen mode

The relevant documentation to configure CockroachDB with the IdP for SSO login is here.

First, open the .well-known page for our IdP.
In our case, this is URL https://dev-85651931.okta.com/oauth2/default/.well-known/openid-configuration.

Here, we can take the server.jwt_authentication.issuers setting, and by navigating to the URL indicated by the jwkt_uri field, also the server.jwt_authentication.jwks.

-- trivial
SET CLUSTER SETTING server.jwt_authentication.enabled = true;

-- taken from field "issuer" in the .well-known page
SET CLUSTER SETTING server.jwt_authentication.issuers = 'https://dev-85651931.okta.com/oauth2/default';

-- taken by navigating to the URL in field "jwks_uri" in the .well-known page
SET CLUSTER SETTING server.jwt_authentication.jwks = '{"keys":[{"kty":"RSA","alg":"RS256","kid":"6jnGo_xZFr13SVCKgH5Tl8RN9cXqybYBQEo2Vf7Wagw","use":"sig","e":"AQAB","n":"mF3tIoT8h_2lpvYSCE_YopPW9Yp9fx4ddDNYFhFhmcqeMKMLl_JIXjnfs2EMB9zlwBm0hHkphGGWPfsy8wLZob2bVwVj8-3yPK3qRIt7ouW8OhNGn8tmQHB_fSDugPp-A_MuedNAkgeFB4zEcEJbVHjykC6cEYbyyPmhD0VJf0q3ifkg2Fxm8075QcV0iIcl46RHxTToTDeW44gPyMqzLrVq2UxOw-yn_I-o_M065hiONMthdiIR6KvjhO-fqtBbD37BH6XehSe2rtAxUpuLvGnoa25Gl7GkhD7_f6qIa3tmoYMPVvbxQSU8Ly2_AGJ7BCqgOMMyE0knsGLUi-dlBw"}]}';

-- this is the client_id
SET CLUSTER SETTING server.jwt_authentication.audience = '["0oab1arbbieTSsFVJ5d7"]';

-- this is the name of the custom field we added to our token
SET CLUSTER SETTING server.jwt_authentication.claim = 'login';

-- this strips the @bank.com
SET CLUSTER SETTING server.identity_map.configuration = 'https://dev-85651931.okta.com/oauth2/default /^(\S+)(?:@) \1';
Enter fullscreen mode Exit fullscreen mode

Finally, let's create some users, with the name matching the name part of the email address we saved in Okta.

-- 'mrossi@bank.com' is mapped to user 'mrossi', that is, 
-- we strip the '@bank.com' part
CREATE USER mrossi;
CREATE USER akumar;
CREATE USER yfofana;
Enter fullscreen mode Exit fullscreen mode

Demo

We are now ready to request Okta for a JWT.

We use curl, following the example in the Okta doc.

curl -s -X POST \
  -H "Content-type:application/x-www-form-urlencoded" \
  -d "client_id=0oab1arbbieTSsFVJ5d7&client_secret=WbAWHW9UeURzXez99PMGWvuVp4KiGDXTNQrDajvszN2iQ3YyMjGyx0sWts1sM36J&grant_type=password&username=mrossi@bank.com&password=Mazinga123&scope=openid" \
  "https://dev-85651931.okta.com/oauth2/default/v1/token" | jq
Enter fullscreen mode Exit fullscreen mode

Here's the formatted response. We are only interested in the id_token, so copy that string.

{
  "token_type": "Bearer",
  "expires_in": 3600,
  "access_token": "eyJraWQiOiI2am5Hb194WkZyMTNTVkNLZ0g1VGw4Uk45Y1hxeWJZQlFFbzJWZjdXYWd3IiwiYWxnIjoiUlMyNTYifQ.eyJ2ZXIiOjEsImp0aSI6IkFULlMtT0wzdmo2cl9Kem82RFMyOGcyNDRhUnVOSWFKUDBqRXVHVUlLZkFvajQiLCJpc3MiOiJodHRwczovL2Rldi04NTY1MTkzMS5va3RhLmNvbS9vYXV0aDIvZGVmYXVsdCIsImF1ZCI6ImFwaTovL2RlZmF1bHQiLCJpYXQiOjE2OTM0MTI2MTAsImV4cCI6MTY5MzQxNjIxMCwiY2lkIjoiMG9hYjFhcmJiaWVUU3NGVko1ZDciLCJ1aWQiOiIwMHViMWRuNXFjN0pBaFpHOTVkNyIsInNjcCI6WyJvcGVuaWQiXSwiYXV0aF90aW1lIjoxNjkzNDEyNjEwLCJzdWIiOiJtcm9zc2lAYmFuay5jb20ifQ.LqiPT1lwzTVWJ1yCFUfL7toFANAIcx4v9c6fYl5HTApbq3wmrpRCJQ9Jy-UgH-SHp4DxYV-tt-i1I-l3409_nVPTK751CxqNCySBsUqXiUHvsV7ZfNxkzgw_0BxCatirk32T08oJc0wuARyP9a1Pif_BwzawHP46vdhrikGwRXrrdtjV9yDLvzGwiHlS8IyTz0lRcCwVR0tq02EfEpBxFg992HJl-VZ4NMzFPj-D6LgAdo4vpJnY-fgHP-IZo26ijfSD9mvyB2V5lu4GAmcD9bV7LfSH_CilPrGFh481y2-YrBknMxUT_4tmN3LY6TZeBo3nDt4gqRjxPxhdDgcTHg",
  "scope": "openid",
  "id_token": "eyJraWQiOiI2am5Hb194WkZyMTNTVkNLZ0g1VGw4Uk45Y1hxeWJZQlFFbzJWZjdXYWd3IiwiYWxnIjoiUlMyNTYifQ.eyJzdWIiOiIwMHViMWRuNXFjN0pBaFpHOTVkNyIsInZlciI6MSwiaXNzIjoiaHR0cHM6Ly9kZXYtODU2NTE5MzEub2t0YS5jb20vb2F1dGgyL2RlZmF1bHQiLCJhdWQiOiIwb2FiMWFyYmJpZVRTc0ZWSjVkNyIsImlhdCI6MTY5MzQxMjYxMCwiZXhwIjoxNjkzNDE2MjEwLCJqdGkiOiJJRC41M3FaQ3pRVzZNa3dLZXh0d2FFcG1HWDFyX1k1bGFpNEp3TUJlWGl1c2J3IiwiYW1yIjpbInB3ZCJdLCJpZHAiOiIwMG9iMHUzOTI0QXJ1Q3RLZjVkNyIsImF1dGhfdGltZSI6MTY5MzQxMjYxMCwiYXRfaGFzaCI6IllqTV8xUGFxRHpGU3V6bkJPQ3Y4QVEiLCJsb2dpbiI6Im1yb3NzaUBiYW5rLmNvbSJ9.abhdpxyTW7WnQApWBJmiuZo_ziz7UEoKkHnf4nEaHsnDPzen32KwLWV4fNM4lvGl7YSND6lFErcZ_xLhBuqHutiH888UEyLzbuOSSFch0ie63VS0ElKlF6M1sXq_U8vRKikWpxC0dF3z1VTnKbRcfxN7QB6sxYn7iAUWt2xbEkaRTFO5lL4t5lFx8xUUBqtFFgy6im4w4CUyEvOww0DGwTC9gVifPiTa-fxGfN-OLFDyUZPM8F2JMmaYhpVWy-dplQjrmOv96zJ07GbsutPP5BrSb2JRCrEBZY0nTK8LklcVV4uZUbZJtTRDpHgNlmgzqzwlRWrt4dDvybnRZRq3Xg"
}
Enter fullscreen mode Exit fullscreen mode

For the sake of learning, we can use a tool such as token.dev to decode the JWT.
Note our "login" field in the Payload.

token

Armed with our JWT, we can now proceed to login.

$ cockroach sql --url "postgresql://mrossi:eyJraWQiOiI2am5Hb194WkZyMTNTVkNLZ0g1VGw4Uk45Y1hxeWJZQlFFbzJWZjdXYWd3IiwiYWxnIjoiUlMyNTYifQ.eyJzdWIiOiIwMHViMWRuNXFjN0pBaFpHOTVkNyIsInZlciI6MSwiaXNzIjoiaHR0cHM6Ly9kZXYtODU2NTE5MzEub2t0YS5jb20vb2F1dGgyL2RlZmF1bHQiLCJhdWQiOiIwb2FiMWFyYmJpZVRTc0ZWSjVkNyIsImlhdCI6MTY5MzQxMjYxMCwiZXhwIjoxNjkzNDE2MjEwLCJqdGkiOiJJRC41M3FaQ3pRVzZNa3dLZXh0d2FFcG1HWDFyX1k1bGFpNEp3TUJlWGl1c2J3IiwiYW1yIjpbInB3ZCJdLCJpZHAiOiIwMG9iMHUzOTI0QXJ1Q3RLZjVkNyIsImF1dGhfdGltZSI6MTY5MzQxMjYxMCwiYXRfaGFzaCI6IllqTV8xUGFxRHpGU3V6bkJPQ3Y4QVEiLCJsb2dpbiI6Im1yb3NzaUBiYW5rLmNvbSJ9.abhdpxyTW7WnQApWBJmiuZo_ziz7UEoKkHnf4nEaHsnDPzen32KwLWV4fNM4lvGl7YSND6lFErcZ_xLhBuqHutiH888UEyLzbuOSSFch0ie63VS0ElKlF6M1sXq_U8vRKikWpxC0dF3z1VTnKbRcfxN7QB6sxYn7iAUWt2xbEkaRTFO5lL4t5lFx8xUUBqtFFgy6im4w4CUyEvOww0DGwTC9gVifPiTa-fxGfN-OLFDyUZPM8F2JMmaYhpVWy-dplQjrmOv96zJ07GbsutPP5BrSb2JRCrEBZY0nTK8LklcVV4uZUbZJtTRDpHgNlmgzqzwlRWrt4dDvybnRZRq3Xg@localhost:26257/defaultdb?options=--crdb:jwt_auth_enabled=true&sslmode=require" 
#
# Welcome to the CockroachDB SQL shell.
# All statements must be terminated by a semicolon.
# To exit, type: \q.
#
# Server version: CockroachDB CCL v23.1.4 (x86_64-apple-darwin19, built 2023/06/16 20:54:39, go1.19.4) (same version as client)
# Cluster ID: f72ea38c-c5b3-437e-bd03-2391e6b5d150
# Organization: Cockroach Demo
#
# Enter \? for a brief introduction.
#
mrossi@localhost:26257/defaultdb>
Enter fullscreen mode Exit fullscreen mode

Success! Let's try to use that token with another user, akumar:

$ cockroach sql --url "postgresql://akumar:eyJraWQiOiI2am5Hb194WkZyMTNTVkNLZ0g1VGw4Uk45Y1hxeWJZQlFFbzJWZjdXYWd3IiwiYWxnIjoiUlMyNTYifQ.eyJzdWIiOiIwMHViMWRuNXFjN0pBaFpHOTVkNyIsInZlciI6MSwiaXNzIjoiaHR0cHM6Ly9kZXYtODU2NTE5MzEub2t0YS5jb20v
b2F1dGgyL2RlZmF1bHQiLCJhdWQiOiIwb2FiMWFyYmJpZVRTc0ZWSjVkNyIsImlhdCI6MTY5MzQxNjM4NCwiZXhwIjoxNjkzNDE5OTg0LCJqdGkiOiJJRC5feXhFOEd4MG9oZVZvQ1RYRXkwZFNwc0JrWlFZeHFoam9sY0JZZVBLTXFJIiwiYW1yIjpbInB3ZCJdLCJpZHAiOiIwMG9iMHUzOTI0QXJ1Q3RLZjVkNyIsImF1dGhfdGltZSI6M
TY5MzQxNjM4NCwiYXRfaGFzaCI6IlljNF9SUF9UWUxNM3B1RjdjSjlCYmciLCJsb2dpbiI6Im1yb3NzaUBiYW5rLmNvbSJ9.GrviZKNF4HezfhnmpvpZFZgKrYmivMknsZGFjD1eXSx2IoiBaG-t-q8CB9oVL48e3ZQXeXqXIl9PvL84a4ed8VCR1wJWH53xKTsAMGmx3dAJpxdO7PhNK1P0Eg2eY4p76mJ2qHbUGb201As_oC-OQAGkuGEZq
lBHV634Mv3zcdKjFFuUYaFkadMXqvIZomSoWUL5jDM8fqKtOUNoONHilthnOgZ3RxgfzGyeijdFS1_ODscbjbIzamKw6C7u698KG7eohVJMP2dIJr4jo33jpcwztRRQfU5ElcuINqLBw8Gv-_A1MlM4WrPHzsYN_OWgaRM_wd8UzOiyuMLFKirHFA@localhost:26257/defaultdb?options=--crdb:jwt_auth_enabled=true&sslm
ode=require" 
#
# Welcome to the CockroachDB SQL shell.
# All statements must be terminated by a semicolon.
# To exit, type: \q.
#
ERROR: JWT authentication: invalid principal
SQLSTATE: 28000
DETAIL: token issued for [mrossi@bank.com] and login was for akumar
Failed running "sql"
Enter fullscreen mode Exit fullscreen mode

Failed as expected!

Your IdP administrator will be responsible for instructing on how to request a JWT, and what claim to use, so do not worry if all this is overwhelming.

From the CockroachDB side, what matters is that our SQL username matches with what the IdP has stored for a particular user: in this example, we used the email address, but it can be any unique ID that your company has adopted.

References

. . . . . . . . . . . . . . . . . . . . .