How to access OpenSearch using OAuth

This guide shows how to authenticate OpenSearch requests with OAuth tokens issued by the Canonical Identity Platform (Hydra).

Prerequisites

  • Minimum 4 CPUs, 16 GB RAM

  • Network access between the host and LXD containers

  • MicroK8s installed locally (for the Identity Platform)

  • A working LXD cloud with Juju bootstrapped (see the Environment setup stage of our tutorial for more details)

Deploy OpenSearch on LXD

Deploy OpenSearch and the data-integrator charm in a new model:

juju add-model opensearch-model localhost/localhost
juju deploy opensearch -n 3 --channel 2/stable
juju deploy data-integrator --channel=stable \
  --config index-name=admin-index \
  --config extra-user-roles=admin

Wait until all units become active:

juju status --watch 5s

Deploy the Identity Platform on MicroK8s

The Identity Platform runs on Kubernetes. The steps below use MicroK8s, but any Juju-supported K8s cluster will work.

Prepare MicroK8s

Install MicroK8s and enable the required addons:

sudo snap install microk8s --classic
sudo microk8s enable hostpath-storage dns
sudo microk8s enable metallb:10.0.0.2-10.0.0.5

Note

The MetalLB address range must provide at least two IP addresses (one for traefik-public and one for traefik-admin). If you are also running COS Lite or other LoadBalancer services on the same MicroK8s instance, increase the range accordingly.

Add MicroK8s to your Juju controller:

sudo microk8s config > microk8s-cluster.yaml
export KUBECONFIG="$PWD/microk8s-cluster.yaml"
juju add-k8s microk8s-cluster -c <controller-name>

Deploy Identity Platform

Create a model on the MicroK8s cloud and deploy the Identity Platform bundle:

juju add-model -c <controller-name> oauth microk8s-cluster/localhost
juju deploy identity-platform --channel edge --trust true

Wait until all units become active. The kratos-external-idp-integrator will remain blocked — this is expected and does not affect OAuth functionality.

Create cross-model integrations

The Identity Platform and OpenSearch run in separate models (and potentially on different clouds). Use Juju cross-model offers to connect them.

Offer certificates and OAuth from the Identity Platform model

Switch to the Identity Platform model and offer the certificates and OAuth endpoints:

juju switch oauth
juju offer self-signed-certificates:certificates
juju offer hydra:oauth

Consume and integrate in the OpenSearch model

Switch to the OpenSearch model, consume the offers, and integrate them with OpenSearch:

juju switch opensearch-model
juju consume admin/oauth.self-signed-certificates
juju consume admin/oauth.hydra
juju integrate opensearch admin/oauth.self-signed-certificates
juju integrate opensearch admin/oauth.hydra

Create an OAuth client and obtain a token

With the Identity Platform running, create an OAuth client in Hydra and use it to request an access token.

Create a client in Hydra

Create an OAuth client:

juju switch oauth
juju run hydra/leader create-oauth-client \
  grant-types='["client_credentials"]' \
  audience='["opensearch"]' \
  scope='["openid","profile","email","phone","offline"]'

Record the client-id and client-secret from the output.

Get the Hydra public URL

Retrieve the proxied endpoints from Traefik:

juju run traefik-public/0 show-proxied-endpoints

Note the hydra.url value (e.g. https://10.0.0.3/oauth-hydra).

Set environment variables:

export OAUTH_CLIENT_ID=<client-id>
export OAUTH_CLIENT_SECRET=<client-secret>
export HYDRA_URL=<hydra-url>

Retrieve the CA certificate

Both Hydra and OpenSearch serve TLS certificates issued by the self-signed-certificates charm, so the same CA verifies both. Save it to a file:

juju run self-signed-certificates/leader get-ca-certificate --format=json \
  | jq -r '.[].results."ca-certificate"' > cert.pem

Note

Use --cacert cert.pem with every curl command below so that TLS certificates are verified. Do not use curl -k (--insecure), which disables verification entirely and exposes the request, including the client secret and the access token.

Fetch an access token

Request an access token from Hydra using the client credentials:

curl --cacert cert.pem -u "${OAUTH_CLIENT_ID}:${OAUTH_CLIENT_SECRET}" \
  -X POST "${HYDRA_URL}/oauth2/token" \
  -d "scope=openid" \
  -d "grant_type=client_credentials" \
  -d "audience=opensearch"

Export the token from the JSON response:

export OAUTH_ACCESS_TOKEN=<access-token>

Test the token (before role mapping)

Switch to the OpenSearch model and query the cluster with the token:

juju switch opensearch-model
export OPENSEARCH_ADDRESS="$(juju status | grep opensearch/0 | awk -F' ' '{print $5}')"

curl --cacert cert.pem -H "Authorization: Bearer ${OAUTH_ACCESS_TOKEN}" \
  "https://${OPENSEARCH_ADDRESS}:9200/_cat/indices"

Expect a 403 security_exception — the client has no mapped roles yet.

{"error":{"root_cause":[{"type":"security_exception","reason":"no permissions for [indices:monitor/settings/get] and User [name=e9c3b483-90be-4843-b821-1152e40aaa0a, backend_roles=[], requestedTenant=null]"}],"type":"security_exception","reason":"no permissions for [indices:monitor/settings/get] and User [name=e9c3b483-90be-4843-b821-1152e40aaa0a, backend_roles=[], requestedTenant=null]"},"status":403}

Get a username from the Data Integrator

Retrieve credentials from the data-integrator charm:

juju run data-integrator/0 get-credentials

Note the username field (e.g. opensearch-client_4):

export DATA_INTEGRATOR_USER=<username>

Configure role mapping

Map the OAuth client ID to the Data Integrator user:

juju config opensearch roles_mapping="{\"$OAUTH_CLIENT_ID\":\"$DATA_INTEGRATOR_USER\"}"

Wait for the charm to apply the change:

juju status --watch 5s

Verify access

Re-run the same request — it should now succeed:

curl --cacert cert.pem -H "Authorization: Bearer ${OAUTH_ACCESS_TOKEN}" \
  "https://${OPENSEARCH_ADDRESS}:9200/_cat/indices"

You should now receive a 200 OK response with a list of indices, for example:

green  open .plugins-ml-config           QnsDThyaTAKw8cASRYeQMw 1 0  1 0   4kb   4kb
green  open .opensearch-observability    coXcpdLWSOqbSQ136tbADg 1 0  0 0  208b  208b
green  open top_queries-2025.08.29-70656 GDHtcml_R6Okh2siIKgmPw 1 0 40 6 108kb 108kb
green  open .opendistro_security         RPVY1SdfT_KzAPAX-aUCuw 1 0 10 1  71kb  71kb
yellow open admin-index                  1BQKqmjTQVa6_CeBTi53Gw 1 1  0 0  208b  208b
green  open .charm_node_lock             8KbPHHy3QneIW8uWbTuBhQ 1 0  1 0 4.1kb 4.1kb

Next steps