External Authorization Filter

The External Authorization sandbox demonstrates Envoy’s ext_authz filter capability to delegate authorization of incoming requests through Envoy to an external services.

While ext_authz can also be employed as a network filter, this sandbox is limited to exhibit ext_authz HTTP Filter, which supports to call HTTP or gRPC service.

The setup of this sandbox is very similar to front-proxy deployment, however calls to upstream service behind the proxy will be checked by an external HTTP or gRPC service. In this sandbox, for every authorized call, the external authorization service adds additional x-current-user header entry to the original request headers to be forwarded to the upstream service.

Running the Sandbox

The following documentation runs through the setup of Envoy described above.

Step 1: Install Docker

Ensure that you have a recent versions of docker and docker-compose installed.

A simple way to achieve this is via the Docker Desktop.

Step 2: Clone the Envoy repo

If you have not cloned the Envoy repo, clone it with:

SSH

HTTPS

  1. git clone git@github.com:envoyproxy/envoy
  1. git clone https://github.com/envoyproxy/envoy.git

Step 3: Start all of our containers

To build this sandbox example and start the example services, run the following commands:

  1. $ pwd
  2. envoy/examples/ext_authz
  3. $ docker-compose pull
  4. $ docker-compose up --build -d
  5. $ docker-compose ps
  6. Name Command State Ports
  7. ---------------------------------------------------------------------------------------------------------------------------------------
  8. ext_authz_ext_authz-grpc-service_1 /app/server -users /etc/us Up
  9. ext_authz_ext_authz-http-service_1 docker-entrypoint.sh node Up
  10. ext_authz_front-envoy_1 /docker-entrypoint.sh /bin Up 10000/tcp, 0.0.0.0:8000->8000/tcp, 0.0.0.0:8001->8001/tcp
  11. ext_authz_upstream-service_1 python3 /app/service/server.py Up

Note

This sandbox has multiple setup controlled by FRONT_ENVOY_YAML environment variable which points to the effective Envoy configuration to be used. The default value of FRONT_ENVOY_YAML can be defined in the .env file or provided inline when running the docker-compose up command. For more information, pease take a look at environment variables in Compose documentation.

By default, FRONT_ENVOY_YAML points to config/grpc-service/v3.yaml file which bootstraps front-envoy with ext_authz HTTP filter with gRPC service V3 (this is specified by transport_api_version field). The possible values of FRONT_ENVOY_YAML can be found inside the envoy/examples/ext_authz/config directory.

For example, to run Envoy with ext_authz HTTP filter with HTTP service will be:

  1. $ pwd
  2. envoy/examples/ext_authz
  3. $ docker-compose pull
  4. $ # Tearing down the currently running setup
  5. $ docker-compose down
  6. $ FRONT_ENVOY_YAML=config/http-service.yaml docker-compose up --build -d
  7. $ # Or you can update the .env file with the above FRONT_ENVOY_YAML value, so you don't have to specify it when running the "up" command.

Step 4: Access the upstream-service behind the Front Envoy

You can now try to send a request to upstream-service via the front-envoy as follows:

  1. $ curl -v localhost:8000/service
  2. * Trying 127.0.0.1...
  3. * TCP_NODELAY set
  4. * Connected to localhost (127.0.0.1) port 8000 (#0)
  5. > GET /service HTTP/1.1
  6. > Host: localhost:8000
  7. > User-Agent: curl/7.58.0
  8. > Accept: */*
  9. >
  10. < HTTP/1.1 403 Forbidden
  11. < date: Fri, 19 Jun 2020 15:02:24 GMT
  12. < server: envoy
  13. < content-length: 0

As observed, the request failed with 403 Forbidden status code. This happened since the ext_authz filter employed by Envoy rejected the call. To let the request reach the upstream service, you need to provide a Bearer token via the Authorization header.

Note

A complete list of users is defined in envoy/examples/ext_authz/auth/users.json file. For example, the token1 used in the below example is corresponding to user1.

An example of successful requests can be observed as follows:

  1. $ curl -v -H "Authorization: Bearer token1" localhost:8000/service
  2. * Trying 127.0.0.1...
  3. * TCP_NODELAY set
  4. * Connected to localhost (127.0.0.1) port 8000 (#0)
  5. > GET /service HTTP/1.1
  6. > Host: localhost:8000
  7. > User-Agent: curl/7.58.0
  8. > Accept: */*
  9. > Authorization: Bearer token1
  10. >
  11. < HTTP/1.1 200 OK
  12. < content-type: text/html; charset=utf-8
  13. < content-length: 24
  14. < server: envoy
  15. < date: Fri, 19 Jun 2020 15:04:29 GMT
  16. < x-envoy-upstream-service-time: 2
  17. <
  18. * Connection #0 to host localhost left intact
  19. Hello user1 from behind Envoy!

We can also employ Open Policy Agent server (with envoy_ext_authz_grpc plugin enabled) as the authorization server. To run this example:

  1. $ pwd
  2. envoy/examples/ext_authz
  3. $ docker-compose pull
  4. $ # Tearing down the currently running setup
  5. $ docker-compose down
  6. $ FRONT_ENVOY_YAML=config/opa-service/v2.yaml docker-compose up --build -d

And sending a request to the upstream service (via the Front Envoy) gives:

  1. $ curl localhost:8000/service --verbose
  2. * Trying ::1...
  3. * TCP_NODELAY set
  4. * Connected to localhost (::1) port 8000 (#0)
  5. > GET /service HTTP/1.1
  6. > Host: localhost:8000
  7. > User-Agent: curl/7.64.1
  8. > Accept: */*
  9. >
  10. < HTTP/1.1 200 OK
  11. < content-type: text/html; charset=utf-8
  12. < content-length: 28
  13. < server: envoy
  14. < date: Thu, 02 Jul 2020 06:29:58 GMT
  15. < x-envoy-upstream-service-time: 2
  16. <
  17. * Connection #0 to host localhost left intact
  18. Hello OPA from behind Envoy!

From the logs, we can observe the policy decision message from the Open Policy Agent server (for the above request against the defined policy in config/opa-service/policy.rego):

  1. $ docker-compose logs ext_authz-opa-service | grep decision_id -A 30
  2. ext_authz-opa-service_1 | "decision_id": "8143ca68-42d8-43e6-ade6-d1169bf69110",
  3. ext_authz-opa-service_1 | "input": {
  4. ext_authz-opa-service_1 | "attributes": {
  5. ext_authz-opa-service_1 | "destination": {
  6. ext_authz-opa-service_1 | "address": {
  7. ext_authz-opa-service_1 | "Address": {
  8. ext_authz-opa-service_1 | "SocketAddress": {
  9. ext_authz-opa-service_1 | "PortSpecifier": {
  10. ext_authz-opa-service_1 | "PortValue": 8000
  11. ext_authz-opa-service_1 | },
  12. ext_authz-opa-service_1 | "address": "172.28.0.6"
  13. ext_authz-opa-service_1 | }
  14. ext_authz-opa-service_1 | }
  15. ext_authz-opa-service_1 | }
  16. ext_authz-opa-service_1 | },
  17. ext_authz-opa-service_1 | "metadata_context": {},
  18. ext_authz-opa-service_1 | "request": {
  19. ext_authz-opa-service_1 | "http": {
  20. ext_authz-opa-service_1 | "headers": {
  21. ext_authz-opa-service_1 | ":authority": "localhost:8000",
  22. ext_authz-opa-service_1 | ":method": "GET",
  23. ext_authz-opa-service_1 | ":path": "/service",
  24. ext_authz-opa-service_1 | "accept": "*/*",
  25. ext_authz-opa-service_1 | "user-agent": "curl/7.64.1",
  26. ext_authz-opa-service_1 | "x-forwarded-proto": "http",
  27. ext_authz-opa-service_1 | "x-request-id": "b77919c0-f1d4-4b06-b444-5a8b32d5daf4"
  28. ext_authz-opa-service_1 | },
  29. ext_authz-opa-service_1 | "host": "localhost:8000",
  30. ext_authz-opa-service_1 | "id": "16617514055874272263",
  31. ext_authz-opa-service_1 | "method": "GET",
  32. ext_authz-opa-service_1 | "path": "/service",

Trying to send a request with method other than GET gives a rejection:

  1. $ curl -X POST localhost:8000/service --verbose
  2. * Trying ::1...
  3. * TCP_NODELAY set
  4. * Connected to localhost (::1) port 8000 (#0)
  5. > PUT /service HTTP/1.1
  6. > Host: localhost:8000
  7. > User-Agent: curl/7.64.1
  8. > Accept: */*
  9. >
  10. < HTTP/1.1 403 Forbidden
  11. < date: Thu, 02 Jul 2020 06:46:13 GMT
  12. < server: envoy
  13. < content-length: 0