Question - Mixer Attribute Generation

I have JWT authentication setup on the istio-ingressgateway layer so that public clients have to present a JWT for authentication before their request is proxied internally within the service mesh. I also want the incoming request to have to meet an OPA Policy in order for the request to be authorized. The request.auth.* attributes aren’t being generated downstream from the ingressgateway because there isn’t a JWT authentication policy on services within the mesh. How can I get the request.auth.claims attribute generated so that Mixer can use it without having an authentication policy on the service (productpage in this case) that the authorization check should take place on?

Here are the configurations I have so far:

apiVersion: "config.istio.io/v1alpha2"
kind: opa
metadata:
 name: opa-handler
 namespace: istio-system
spec:
 policy:
   - |+
    package mixerauthz

    default allow = false

    allow {
      input.subject.user = "jwhitaker@solutionreach.com"
      input.action.method = "GET" 
    }
 checkMethod: "data.mixerauthz.allow"
 failClose: true
---
apiVersion: "config.istio.io/v1alpha2"
kind: authorization
metadata:
 name: authz-instance
 namespace: istio-system
spec:
 action:
  method: request.method | ""
  namespace: destination.namespace | "default"
  path: request.path | "/"
  properties:
   version: destination.labels["version"] | ""
 subject:
   user: request.auth.claims["sub"] | ""
   groups: request.auth.claims["groups"] | ""
   properties:
    iss: request.auth.claims["iss"] | ""
---
apiVersion: "config.istio.io/v1alpha2"
kind: rule
metadata:
 name: opa-rule 
 namespace: istio-system
spec:
 match: match(destination.service.name, "productpage")
 actions:
 - handler: opa-handler.opa.istio-system
   instances:
   - authz-instance.authorization.istio-system
---
apiVersion: "authentication.istio.io/v1alpha1"
kind: "Policy"
metadata:
  name: "user-auth-example"
  namespace: istio-system 
spec:
  targets:
  - name: istio-ingressgateway 
  origins:
  - jwt:
      issuer: "<issuer>"
      jwksUri: "<jwks-uri>"
  principalBinding: USE_ORIGIN
---
apiVersion: "authentication.istio.io/v1alpha1"
kind: "Policy"
metadata:
  name: "default"
  namespace: default
spec:
  peers:
  - mtls: {}
---
apiVersion: "networking.istio.io/v1alpha3"
kind: "DestinationRule"
metadata:
  name: "default"
  namespace: "default"
spec:
  host: "*.default.svc.cluster.local"
  trafficPolicy:
    tls:
      mode: ISTIO_MUTUAL

The use case I am trying to satisfy is this…

I would like to express an authentication policy where every service->service call within the service mesh occurs over mutual TLS, but clients outside the kubernetes cluster should present JWT authentication at the istio-ingressgateway layer. The idea here is that we should be able to trust service calls within the service mesh, but public clients outside of the kubernetes cluster need to be authorized against an authorization system in our companies cloud. We may want to enforce RBAC authorization between services within the service mesh at a later time, but for now we just want public client calls into the service mesh to require JWT authentication, but calls between services within the mesh to use mTLS.

Hi!

+cc @diemtvu

I think you may use a more restrictive match condition in the rule, e.g. source.labels["app"] == "ingress-gateway". Rules assume that the gateway workload is the source of traffic.

It’s not getting the request.auth.claims attribute.

apiVersion: "config.istio.io/v1alpha2"
kind: opa
metadata:
 name: opa-handler
 namespace: istio-system
spec:
 policy:
   - |+
    package mixerauthz

    default allow = false

    allow {
      input.subject.user = "jwhitaker@solutionreach.com"
      input.action.method = "GET" 
    }
 checkMethod: "data.mixerauthz.allow"
 failClose: true
---
apiVersion: "config.istio.io/v1alpha2"
kind: authorization
metadata:
 name: authz-instance
 namespace: istio-system
spec:
 action:
  method: request.method | ""
  namespace: destination.namespace | "default"
  path: request.path | "/"
  properties:
   version: destination.labels["version"] | ""
 subject:
   user: request.auth.claims["sub"] | "jwhitaker@solutionreach.com"
   groups: request.auth.claims["groups"] | ""
   properties:
    iss: request.auth.claims["iss"] | ""
---

The configuration above always works (no 403 Forbidden), because the default value for subject.user is "jwhitaker@solutionreach.com". If I change the config to:

apiVersion: "config.istio.io/v1alpha2"
kind: authorization
metadata:
 name: authz-instance
 namespace: istio-system
spec:
 action:
  method: request.method | ""
  namespace: destination.namespace | "default"
  path: request.path | "/"
  properties:
   version: destination.labels["version"] | ""
 subject:
   user: request.auth.claims["sub"] | ""
   groups: request.auth.claims["groups"] | ""
   properties:
    iss: request.auth.claims["iss"] | ""

I always get “” for the subject.user attribute.

The token I am passing is a token crafted for my user (jwhitaker@solutionreach.com), so it should be authorized.

Looks like your configs set (JWT) authentication on ingress, but authorization (via OPA) on product page (match(destination.service.name, “productpage”)). This would not work, as the attributes are extracted in JWT authentication are only available on that envoy. That’s why you don’t see anything in authorization layer.

To achieve what you want, there are few options:

  1. Change the match condition to match ingress destination. Not on top of my head, but may be using istio-ingressgateway instead of productpage could work. Or you can use label.

  2. Move (JWT) authentication to productpage, with origin_is_optional set to true, so the rejection can be delayed to the authZ layer.

You can also use context.reporter.kind == "outbound" to select ingress gateway only. The match on source and destination kicks in for both ingress and productpage, and the one in productpage is undesirable.