Access the Jaeger REST API programatically in OpenShift
Tags: openshift, jaeger, automation
Published: 2022-05-05 14:22:59

---
title: Access the Jaeger REST API programatically in OpenShift
published: true
description: How to access the Jaeger REST API programatically in OpenShift? Go ahead and learn how to do it!
tags: openshift, jaeger, automation 
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gmqb4gsob8uji4o43sj9.png
---
Have you ever tried to access [Jaeger](https://www.jaegertracing.io/) programmatically in [OpenShift](https://www.redhat.com/technologies/cloud-computing/openshift)? It can be a little bit difficult. Sometimes, it is needed because you want, for instance, access to the REST API [which is not documented because it is for internal usage](https://www.jaegertracing.io/docs/1.33/apis/#http-json-internal). Or you want (as it is my case) to test something from the UI.

![OpenShift Oauth Proxy](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dfq8kxdv9gtyq0ws1rsl.png)

## Before reading this article
This article is about how to get access to the Jaeger query service when using the “production” deployment strategy and deploying via the [Jaeger Operator](https://www.jaegertracing.io/docs/1.33/operator/). Some steps can be the same if you are using other deployment methods. Even, these steps can be not needed if you disabled the security:

```yaml
apiVersion: jaegertracing.io/v1
kind: Jaeger
metadata:
  name: disable-oauth-proxy
spec:
  ingress:
    security: none
```

Also, take into account this is for OpenShift. So, if you are using Kubernetes without any security proxy or something, you’ll not need to follow these steps to access the Jaeger query endpoints. Or (if you are using another proxy) you’ll need to configure your credentials to access the API. I’ll assume you’re using the [OpenShift Oauth Proxy](https://github.com/openshift/oauth-proxy).

## Getting access to the Jaeger query service
Let’s imagine I deployed the following CRD (`kubectl create -f simple-prod.yaml`) in the `israel` namespace:
```yaml
apiVersion: jaegertracing.io/v1
kind: Jaeger
metadata:
  name: simple-prod
spec:
  strategy: production
  storage:
    type: elasticsearch
    elasticsearch:
      nodeCount: 1
      resources:
        requests:
          cpu: 200m
          memory: 1Gi
        limits:
          memory: 1Gi
```

The result of running `curl` against the created OpenShift route will be a 403 error:
```sh
$ curl https://$(kubectl get route simple-prod -o jsonpath='{.spec.host}') -I
HTTP/1.1 403 Forbidden
Set-Cookie: _oauth_proxy=; Path=/; Domain=simple-prod-israel.apps.mycluster.iblancasa.com; Expires=Thu, 05 May 2022 11:09:34 GMT; HttpOnly; Secure; SameSite
Date: Thu, 05 May 2022 12:09:34 GMT
Content-Type: text/html; charset=utf-8
Set-Cookie: 3f978cf9c2d3ee8d70cad1d11aef9dcd=8d2f11be8db90157d1e73d19b515f187; path=/; HttpOnly; Secure; SameSite=None
```
If you didn’t specify the `-I` option to receive just the headers, you will receive the full HTML code from the logging webpage. Let’s see how to get the “correct” answer from the server.

The first step is to create a [Service Account](https://docs.openshift.com/container-platform/4.10/authentication/understanding-and-creating-service-accounts.html). From the OpenShift documentation:
> A service account is an OpenShift Container Platform account that allows a component to directly access the API.

I’ll name my Service Account as “automation-access”. So, my file `sa.yaml` contains:
```yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: automation-access
```
And create it in OpenShift using `kubectl create -f sa.yaml`:
```sh
$ kubectl create -f sa.yaml
serviceaccount/automation-access created
```

Now, we need to create a Cluster Role Binding. From the [Kubernetes documentation](https://kubernetes.io/docs/reference/access-authn-authz/rbac/):
> A role binding grants the permissions defined in a role to a user or set of users. It holds a list of subjects (users, groups, or service accounts), and a reference to the role being granted. A RoleBinding grants permissions within a specific namespace whereas a ClusterRoleBinding grants that access cluster-wide.

As a simplification, we can use Cluster Role Bindings to establish relationships between, for instance, a Service Account and Cluster Role. In our case, it would be enough with [system:auth-delegator](https://kubernetes.io/docs/reference/access-authn-authz/rbac/#other-component-roles) for the `jaeger-operator` Service Account from the `observability` namespace and [cluster-reader](https://docs.openshift.com/container-platform/4.10/authentication/using-rbac.html#default-roles_using-rbac) for the Service Account we created before. Our `crb.yaml` file will be similar to this one:

```yaml
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: jaeger-operator-with-auth-delegator
  namespace: observability
subjects:
- kind: ServiceAccount
  name: jaeger-operator
  namespace: observability
roleRef:
  kind: ClusterRole
  name: system:auth-delegator
  apiGroup: rbac.authorization.k8s.io
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: automation-access-bind
subjects:
- kind: ServiceAccount
  name: automation-access
  namespace: israel
roleRef:
  kind: ClusterRole
  name: cluster-reader
  apiGroup: rbac.authorization.k8s.io
```

And apply it to our OpenShift cluster using: `kubectl create -f crb.yaml`:
```sh
$ kubectl create -f crb.yaml
clusterrolebinding.rbac.authorization.k8s.io/jaeger-operator-with-auth-delegator created
clusterrolebinding.rbac.authorization.k8s.io/automation-access-bind created
```

Finally, we need to set some extra options to our Jaeger deployment (`jaeger-sar.yaml`):
```yaml
apiVersion: jaegertracing.io/v1
kind: Jaeger
metadata:
  name: simple-prod
spec:
  query:
    options:
      query.bearer-token-propagation: true
  ingress:
    openshift:
      sar: '{"namespace": "israel", "resource": "pods", "verb": "get"}'
      delegateUrls: '{"/":{"namespace": "israel", "resource": "pods", "verb": "get"}}'
    options:
      pass-access-token: true
      pass-user-bearer-token: true
      scope: "user:info user:check-access"
      pass-basic-auth: false
```

Then, we apply it with `kubectl apply -f jaeger-sa.yaml`:
```sh
$ kubectl apply -f jaeger-sar.yaml
Warning: resource jaegers/simple-prod is missing the kubectl.kubernetes.io/last-applied-configuration annotation which is required by kubectl apply. kubectl apply should only be used on resources created declaratively by either kubectl create --save-config or kubectl apply. The missing annotation will be patched automatically.
jaeger.jaegertracing.io/simple-prod configured
```

Ok! We’re almost there. The next step is to get the token that will allow us to access the Jaeger query service programmatically. To get the token, we need to get the name of the [Kubernetes secret](https://kubernetes.io/docs/concepts/configuration/secret/) to use. We can get that information from the following command:
```sh
$ kubectl get sa automation-access -o jsonpath='{.secrets}'
[{"name":"automation-access-token-q2p2x"},{"name":"automation-access-dockercfg-xvzsq"}]
```
The result is the name of 2 secrets. We’ll use the one called `auomation-access-token-<ID>`. If you use [yq](https://mikefarah.gitbook.io/yq/), the command can be:
```sh
$ kubectl get sa automation-access -o yaml | yq eval '.secrets[] | select( .name == "*-token-*")'.name 
automation-access-token-q2p2x
```
And, finally, we extract the token from the secret:
```sh
$ kubectl get secret automation-access-token-q2p2x -o jsonpath='{.data.token}' |  base64 -d
eyJhbGciOiJSUzI1NiIsImtpZCI6Iks3TXcwUWhyb3BVdUVJblhkQmVQT25Fek1KVVlfa2RPV3dDZWpJVXpmekUifQ.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJpc3JhZWwiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlY3JldC5uYW1lIjoiYXV0b21hdGlvbi1hY2Nlc3MtdG9rZW4tcTJwMngiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC5uYW1lIjoiYXV0b21hdGlvbi1hY2Nlc3MiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC51aWQiOiI1NGY1ZDE0MC1lMDEwLTQ5NWYtYWE4Yy0wNmQ3ZjIwNjg3OGIiLCJzdWIiOiJzeXN0ZW06c2VydmljZWFjY291bnQ6aXNyYWVsOmF1dG9tYXRpb24tYWNjZXNzIn0.J5USv-olD1vU0A5tL8TT1SDoH-jf5f58hSIEPnkkiZzTiOjHHx2Vj2v1p0bm26epfTQwLV3TfIsgcsV7tnpC0cR64MmDQe9EuBtNonJqV45nTkSoboWuBfeSxXqjk7Tuj_bcZcutcA0vsVHzAglb-Z0wavx9ETfQJueDWTzF6Cry5HxVxYn6ytogHfbaEgiLN9HNeZBWW4xi7JAzY-2viECrFq1yEIJKzWDihZScFkyX2fu1UDpFLH2ewA8N6bdqSd5FTEna0JS0e8yxQcGo-oXykDW2G_YcLDBSrCpQaji390FgMp_6dEl1yHSazGgCaKwulwOU4Rr6usf807mdBQ
```

This command gets the token from the secret and decodes it. We need to provide this token in the `Authorization` header when doing a `curl` call:
```sh
$ export TOKEN=$(kubectl get secret automation-access-token-q2p2x -o jsonpath='{.data.token}' |  base64 -d)
$ curl https://$(kubectl get route simple-prod -o jsonpath='{.spec.host}') -I -H "Authorization: Bearer $TOKEN"
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Date: Thu, 05 May 2022 12:44:06 GMT
Gap-Auth: system:serviceaccount:israel:automation-sa@cluster.local
Gap-Upstream-Address: localhost:16686
Vary: Accept-Encoding
Set-Cookie: 3f978cf9c2d3ee8d70cad1d11aef9dcd=2e6b6fcf2050268c74e1aabcf71e9bab; path=/; HttpOnly; Secure; SameSite=None
Cache-control: private
```

Thank you for reading! I hope this article helped you! :)