After spending a considerable amount of time trying to piece together a combination of outdated advice on various forums with Istio and Envoy documentation I decided to put together everything I learned from this experience into one place.
This was our journey to leverage Envoy ext_authz
filters for authentication and authorization to the cluster.
Prerequisites
- Kubernetes 1.17 cluster
- Istio 1.6 used for ingress and service mesh
- APIs that are exposed to the internet and need to be authenticated and authorized at ingress because you want to remove authorization from the responsibilities of the back-end services
Using Istio
The most basic functionality needed for an effective API gateway is:
- routing specific paths to different services running in the cluster
- doing URL re-write in case the URL path differs between external API and the the service the request is being routed to
- performing authentication and authorization
- performing rate limiting (not discussed here)
As it happens, all of this can be accomplished with Istio ingress and some Envoy filters. And while there are some examples of doing this, we were not able to find any that worked with Istio 1.6 specifically.
Routing API requests
This is pretty straight forward and is actually very well documented. Here is a sample Istio VirtualService
using the bookfino example:
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: bookinfo
namespace: bookinfo
spec:
hosts:
- "api.bookinfo.demo.app"
gateways:
- istio-system/istio-gateway
http:
- match:
- uri:
prefix: "/api/v1/products"
route:
- destination:
host: productpage
port:
number: 9080
In this file we are creating a new VirtualService
in the bookinfo
namespace where our services are running. It will expose the APIs on the https://api.bookinfo.demo.app endpoint using an existing Istio ingress gateway (in this example called istio-gateway
). It will only accept requests starting with /api/v1/products
and route the requests to the destination service productpage
on port 9080
.
Re-writing request URL
Next we need to satisfy the requirement for rewriting URLs. In this scenario we need to allow 2 additional URL paths to route to the same API endpoint in the service
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: bookinfo
namespace: bookinfo
spec:
hosts:
- "api.bookinfo.demo.app"
gateways:
- istio-system/istio-gateway
http:
- match:
- uri:
prefix: "/api/products"
- uri:
prefix: "/api/v1/products"
- uri:
prefix: "/api/v2/products"
rewrite:
uri: "/api/v1/products"
route:
- destination:
host: productpage
port:
number: 9080
The new lines that were added will match 2 additional URL paths starting with either /api/products
or /api/v2/products
as well as the original /api/v1/products
and rewrite all to /api/v1/products
because that is still the URL that our productpage
service is expecting.
Creating Envoy filter
Now that we have our API routing figured out, we need to make sure that it’s adequately secured. To accomplish this we will use Envoy ExtAuthz
filter. It will look something like this:
apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
name: authn-filter
namespace: istio-system
spec:
workloadSelector:
labels:
istio: ingressgateway
configPatches:
- applyTo: HTTP_FILTER
match:
context: GATEWAY
listener:
filterChain:
filter:
name: "envoy.http_connection_manager"
subFilter:
name: "envoy.router"
patch:
operation: INSERT_BEFORE
value:
name: envoy.ext_authz
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthz
failure_mode_allow: false
http_service:
server_uri:
uri: "http://auth.default.svc.cluster.local:8000"
cluster: "outbound|8000||auth.default.svc.cluster.local"
timeout: 3s
Lets break it down. We are going to create this EnvoyFilter in the istio-system
namespace and target a specific workload with a label istio: ingressgateway
. As the label suggests, this will target Istio's ingress gateway pods. Next we are going to patch HTTP_FILTER
configuration. We will insert our new filter before envoy.router
sub filter under the envoy.http_connection_manager
filter. Our filter is http
but a gRPC
version is also available. Finally we are going to specify our authorization server location with the URI and cluster index (in this case are using a service named auth
in the default
namespace).
So what will this do when a request comes into the cluster? This will take the request and forward the header to the auth
service. What it expects in response is HTTP 200 status code if the request is allowed to proceed or any other response if the request needs to get rejected.
Creating Exceptions
The Envoy filter that was created above will now apply to all of the traffic coming through the ingress gateway. But what if we have other workloads (or even specific end-points) in the cluster that need to bypass authentication / authorization. We do that by creating exceptions.
apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
name: bypass-auth-filter
namespace: istio-system
spec:
workloadSelector:
labels:
istio: ingressgateway
configPatches:
- applyTo: VIRTUAL_HOST
match:
routeConfiguration:
vhost:
name: "unsecured.demo.app:443"
patch:
operation: MERGE
value:
per_filter_config:
envoy.ext_authz:
disabled: true
- applyTo: HTTP_ROUTE
match:
routeConfiguration:
vhost:
name: "api.bookinfo.demo.app:443"
route:
name: "ignore-authz-filter"
patch:
operation: MERGE
value:
per_filter_config:
envoy.ext_authz:
disabled: true
In this example we create another EnvoyFilter
following the same pattern as the first one. It will also target Istio's ingress gateway and will patch the configuration to accomplish the following:
- Disable
envoy.ext_authz
filter for theunsecured.demo.app:443
virtual host - Disable
envoy.ext_authz
filter for a route namedignore-authz-filter
in theapi.bookinfo.demo.app:443
virtual host
In order to take advantage of the named route we need to modify our VirtualService and add a name: ignore-authz-filter
to the appropriate route
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: bookinfo
namespace: bookinfo
spec:
hosts:
- "api.bookinfo.demo.app"
gateways:
- istio-system/istio-gateway
http:
- match:
- uri:
prefix: /api/v1/products
route:
- destination:
host: productpage
port:
number: 9080
- match:
- uri:
prefix: /api/unsecured
name: ignore-authz-filter
route:
- destination:
host: productpage
port:
number: 9080
Viewing current Envoy configuration
In order to make sure desired configuration has been applied to the Envoy proxy on the Istio ingress gateway we can take advantage of the Istio dashboards. You can get the name of your Istio ingress gateway pod by running the following command
kubectl get pods -n istio-system -l istio=ingressgateway
Then you can use that pod name to launch Envoy dashboard
istioctl dashboard envoy <istio-ingressgateway-pod-name> -n istio-system