DEV Community

Cover image for Secure Microservice using Istio w/ JWT
Fei Y
Fei Y

Posted on

Secure Microservice using Istio w/ JWT

Istio security pillar provides a comprehensive solution to secure your workloads with strong identity (with optional integration with SPIRE since v1.14), authentication, and authorization (with optional integration with OPA). Istio sidecar (service proxy) and ingressgateway (edge proxy) serve as the PEP for the entire mesh.

According to NIST SP 800-207 that no resource is inherently trusted that 1) every asset (service) MUST have its security posture evaluated via a PEP before a request is granted ... 2) and the evaluation should be continual for as long as the session lasts. Istio typically operates at L7. And in order to implement Defense-in-Depth strategy we also need to complement it with L3/4 enforcement through NetworkPolicy. Please check out additional security considerations, and we won't talk about it. Instead, let me walk you through how to protect the workload using Istio with JWT.

You can acquire a free JWT token through auth0. We'll use it for the demo.

Obviously, we need to enforce mTLS using PeerAuthentication. By default, Istio enableAutoMtls is set to true but it was in PERMISSIVE mode.

apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
  name: default
  namespace: istio-system
spec:
  mtls:
    mode: STRICT
Enter fullscreen mode Exit fullscreen mode

We need to make sure the request MUST be denied at the edge (istio-ingressgateway) either without JWT token or invalid JWT token with the combination of RequestAuthentication and AuthorizationPolicy.

RequesetAuthentication CRD will make sure JWT token adheres to the jwtRules specified.

apiVersion: security.istio.io/v1beta1
kind: RequestAuthentication
metadata:
  name: jwt-authn-gw
  namespace: istio-gateway
spec:
  selector:
    matchLabels:
      istio: ingressgateway
  jwtRules:
  - issuer: "https://dev-wl5b26zy.us.auth0.com/"
    jwksUri: "https://dev-wl5b26zy.us.auth0.com/.well-known/jwks.json"
    audiences:
    - "https://httpbin/api"
    forwardOriginalToken: true
Enter fullscreen mode Exit fullscreen mode

JWT Token typically uses RS256(RSA Signature with SHA-256) as the asymmetric signing algorithm. Istio will make sure the token is indeed valid and tamper-proof by verifying the digital signature through jwksUri. When a request comes in it goes through various HTTP filters, and one of them is envoy.filters.http.jwt_authn. To be more efficient the JWKS will be "cached" in localJwks to improve the performance instead of every time having outbound call to the jwksUri endpoint.

...
{
    "name": "envoy.filters.http.jwt_authn",
    "typedConfig": {
        "@type": "type.googleapis.com/envoy.extensions.filters.http.jwt_authn.v3.JwtAuthentication",
        "providers": {
            "origins-0": {
                "issuer": "https://dev-wl5b26zy.us.auth0.com/",
                "audiences": [
                    "https://httpbin/api"
                ],
                "localJwks": {
                    "inlineString": "..."
                },
                "forward": true,
                "payloadInMetadata": "https://dev-wl5b26zy.us.auth0.com/"
            }
        },
...
}
...
Enter fullscreen mode Exit fullscreen mode

istio_authn filter is right after the jwt_authn filter. And it tries to verify the issuer as well:

{
    "name": "istio_authn",
    "typedConfig": {
        "@type": "type.googleapis.com/istio.envoy.config.filter.http.authn.v2alpha1.FilterConfig",
        "policy": {
            "origins": [
                {
                    "jwt": {
                        "issuer": "https://dev-wl5b26zy.us.auth0.com/"
                    }
                }
            ],
            "originIsOptional": true,
            "principalBinding": "USE_ORIGIN"
        },
        "skipValidateTrustDomain": true
    }
}
Enter fullscreen mode Exit fullscreen mode

Just with RequesetAuthentication will not be enough since a request without any authentication credentials will be accepted but will not have any authenticated identity. That's why it's quintessential to have AuthorizationPolicy in place to enforce.

First, it stipulates that the request will be denied at istio-ingressgateway if it doesn't have the RequestPrincipal.

apiVersion: "security.istio.io/v1beta1"
kind: AuthorizationPolicy
metadata:
  name: deny-jwt-gw
  namespace: istio-gateway
spec:
  selector:
    matchLabels:
      istio: ingressgateway
  action: DENY
  rules:
  - from:
    - source:
        notRequestPrincipals: ["*"]
Enter fullscreen mode Exit fullscreen mode

Second, at the service level it has to satisfy the claims that the workload expects. In order to make it work it's important to have forwardOriginalToken: true to allow JWT token to be forwarded from the istio-ingressgateway to the workload.

apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
  name: require-jwt-httpbin
  namespace: default
spec:
  selector:
    matchLabels:
      app: httpbin
  action: ALLOW
  rules:
  - from:
    - source:
        requestPrincipals: ["https://dev-wl5b26zy.us.auth0.com//8qPXVf5npNa4yXmeyHhnGh5GDgrDK3B5@clients"]
    when:
    - key: request.auth.claims[scope]
      values: ["read:messages"]
    - key: request.auth.claims[aud]
      values: ["https://httpbin/api"]
Enter fullscreen mode Exit fullscreen mode

These enforcement have been "encoded" into workload's sidecar EnvoyProxy HTTP Filter called envoy.filters.http.rbac. Any violation will cause 403 Forbidden with the error RBAC: access denied.

{
    "name": "envoy.filters.http.rbac",
    "typedConfig": {
        "@type": "type.googleapis.com/envoy.extensions.filters.http.rbac.v3.RBAC",
        "rules": {
            "policies": {
                "ns[default]-policy[require-jwt-httpbin]-rule[0]": {
                    "permissions": [
                        {
                            "andRules": {
                                "rules": [
                                    {
                                        "any": true
                                    }
                                ]
                            }
                        }
                    ],
                    "principals": [
                        {
                            "andIds": {
                                "ids": [
                                    {
                                        "orIds": {
                                            "ids": [
                                                {
                                                    "metadata": {
                                                        "filter": "istio_authn",
                                                        "path": [
                                                            {
                                                                "key": "request.auth.principal"
                                                            }
                                                        ],
                                                        "value": {
                                                            "stringMatch": {
                                                                "exact": "https://dev-wl5b26zy.us.auth0.com//8qPXVf5npNa4yXmeyHhnGh5GDgrDK3B5@clients"
                                                            }
                                                        }
                                                    }
                                                }
                                            ]
                                        }
                                    },
...
        },
        "shadowRulesStatPrefix": "istio_dry_run_allow_"
    }
}
Enter fullscreen mode Exit fullscreen mode

Thanks for reading, and learning with me. If you want to run it yourself here is the source code and instructions.

Voila!

Top comments (0)