Stack Labs Blog moves to Dev.to | Le Blog Stack Labs déménage sur Dev.to 🚀

6 février 2019 | Cloud | Kevin Davin

Istio - Delegate Authentication and Authorization to Istio ⛵️

Estimated read time: 4 minutes

🇬🇧 Article in English

Istio provide in its data-plane a powerful proxy named Envoy. We can use it to do a lot of things. One of them is to handle JWT authentication and authorization to service. We will see how to do that !

One of the many responsibilities of Istio could be to delegate the authentication and authorization. We can do that with a bit of YAML very simply.

Requirements

In these examples, we are using a few applications built with Maven and Jib. The code source of these examples are provided here and we are deploying this on Google Kubernetes Engine or Docker for Mac with Kubernetes.

We have deployed two services: ui and search. The ui is accessible through localhost in a Docker for Mac environment.

$ curl localhost
{"hello":"World","from":"ui (v1) => search (v1)","date":"2019-01-27T13:06:06.722Z"}

Restrict access to authenticated users only

The first step will be to restrict services access to authenticated users only. To do that, you have to apply the following yaml configuration:

apiVersion: "authentication.istio.io/v1alpha1"
kind: "Policy"
metadata:
  name: auth-token
spec:
  targets:
    - name: ui
  origins:
    - jwt:
        issuer: "testing@secure.istio.io"
        jwksUri: "https://raw.githubusercontent.com/davinkevin/istio-best-friends-k8s-demo/master/security/jwt/jwks.json"
  principalBinding: USE_ORIGIN

With this applied, your application originaly accessible through localhost is restricted by the configuration:

$ curl localhost
Origin authentication failed.

You can access it with a valid JWT token. To do this you can use this one generated and valid for a very long time

$ TOKEN=$(curl https://raw.githubusercontent.com/davinkevin/istio-best-friends-k8s-demo/master/security/jwt/token.standard.jwt -s)
$ curl --header "Authorization: Bearer $TOKEN" localhost
{"hello":"SnowCamp 2019","from":"ui (v1) => search (v1)","date":"2019-01-27T13:21:34.288Z"}

Restrict to users who have the defined claim

Now, our task will be to allow only users with specific claim (attributes inside the JWT token) to access the ui on the GET / endpoint.

The previously used token had the following value:

eyJhbGciOiJSUzI1NiIsImtpZCI6IkRIRmJwb0lVcXJZOHQyenBBMnFYZkNtcjVWTzVaRXI0UnpIVV8tZW52dlEiLCJ0eXAiOiJKV1QifQ.eyJleHAiOjQ2ODU5ODk3MDAsImZvbyI6ImJhciIsImlhdCI6MTUzMjM4OTcwMCwiaXNzIjoidGVzdGluZ0BzZWN1cmUuaXN0aW8uaW8iLCJzdWIiOiJ0ZXN0aW5nQHNlY3VyZS5pc3Rpby5pbyJ9.CfNnxWP2tcnR9q0vxyxweaF3ovQYHYZl82hAUsn21bwQd9zP7c-LS9qd_vpdLG4Tn1A15NxfCjp5f7QNBUo-KC9PJqYpgGbaXhaGx7bEdFWjcwv3nZzvc7M__ZpaCERdwU7igUmJqYGBYQ51vr2njU9ZimyKkfDe3axcyiBZde7G6dabliUosJvvKOPcKIWPccCgefSj_GNfwIip3-SsFdlR7BtbVUcqR-yv-XOxJ3Uc1MI0tz3uMiiZcyPV7sNCU4KRnemRIMHVOfuvHsU60_GhGbiSFzgPTAa9WTltbnarTbxudb_YEOx12JiwYToeX0DCPb43W1tzIBxgm8NxUg

If we decode this token (manually or thanks to jwt.io, we can see the following result for the claim part:

{
  "exp": 4685989700,
  "foo": "bar",
  "iat": 1532389700,
  "iss": "testing@secure.istio.io",
  "sub": "testing@secure.istio.io"
}

Our goal is to be able to access the endpoint / with GET verb only if one claim of our token JWT is role: customer. The claim part should look like this:

{
  "exp": 4685989700,
  "role": "customer",
  "iat": 1532389700,
  "iss": "testing@secure.istio.io",
  "sub": "testing@secure.istio.io"
}

To do so, we will use the RBAC logic at the Istio level. RBAC stands for Role Base Access Control.

First, we have to declare RbacConfig in order to define which services (or namespaces) will rely on RBAC.

apiVersion: "rbac.istio.io/v1alpha1"
kind: RbacConfig
metadata:
  name: default
  namespace: istio-system
spec:
  mode: 'ON_WITH_INCLUSION'
  inclusion:
    services:
      - "ui.default.svc.cluster.local"

In this case, this will enable RBAC system ONLY on your service.

Note: The RBAC element is a singleton in your cluster, so you have to use the FQDN of your service.

Then, we have to define the ServiceRole representing what a role can do

apiVersion: rbac.istio.io/v1alpha1
kind: ServiceRole
metadata:
  name: customer
spec:
  rules:
    - services: ["ui.default.svc.cluster.local"]
      paths: ["/"]
      methods: ["GET"]

The last object is the ServiceRoleBinding defining the condition to access a specific role.

apiVersion: rbac.istio.io/v1alpha1
kind: ServiceRoleBinding
metadata:
  name: bind-customer
spec:
  subjects:
    - properties:
        request.auth.claims[group]: "customer"
  roleRef:
    kind: ServiceRole
    name: customer

Note: Don’t mix-up the Role at the business level and the ServiceRole at the Istio level. In this example, we use the same word customer and role, but they can be different !

With these three files applied, you can try to access the ui service. To do so, you can use the JWT token here generated for this example

$ TOKEN=$(curl https://raw.githubusercontent.com/davinkevin/istio-best-friends-k8s-demo/master/security/jwt/token.customer-only.jwt -s)
$ curl --header "Authorization: Bearer $TOKEN" localhost
{"hello":"SnowCamp 2019","from":"ui (v1) => search (v1)","date":"2019-01-27T13:21:34.288Z"}

We are accessing the ui service thanks to this token 🤩.

You can try with the previous token to check if the limitation is still effective for user without role claim.

$ TOKEN=$(curl https://raw.githubusercontent.com/davinkevin/istio-best-friends-k8s-demo/master/security/jwt/token.standard.jwt -s)
$ curl --header "Authorization: Bearer $TOKEN" localhost
RBAC: access denied

🎉 You have successfully defined a Role Base Access Control on a specific service !

Note: For now, in Istio 1.0.X, this solution isn’t working for a claim defined as array. An issue has been opened and is now solved. This feature will be available only in Istio 1.1+.