Giter Club home page Giter Club logo

envoy_external_authorization's Introduction

Envoy External Authorization server (envoy.ext_authz) HelloWorld

Recently, wanted to understand and use the external authorization server since i specialize in authn/authz quite a bit for my job. In digging into it earlier today, i found a number of amazing sample that this post is based on:

This post is about how to get a basic "hello world" app using envoy.ext_authz where any authorization decision a envoy request makes is handled by an external gRPC service you would run. You can pretty much offload each decision to let a request through based on some very specific rule you define. You of course do not have to use an external server for simple checks like JWT authentication based on claims or issuer (for that just use Envoy's built-in JWT-Authentication). Use this if you run Envoy directly and wish to make a decision based on some other complex criteria not covered by the others.

This tutorial runs an an Envoy Proxy, a simple http backend and a gRPC service which envoy delegates the authorization check to. You can take pertty much anyting out of the original inbound request context (headers, etc) to make a allow/deny decision on as well as append/alter headers)

Before we get started, a word from our sponsors ...here are some of the other references you maybe interested in

Architecture

Well...its pretty straight forward as you'd expect

  1. Client makes HTTP request
  2. Envoy sends inbound request to an external Authorization server
  3. External authorization server makes a decision given the request context
  4. If authorized, the request is sent through

Steps 2,3 is encapsulated as a gRPC proto external_auth.proto where the request response context is set:

// A generic interface for performing authorization check on incoming
// requests to a networked service.
service Authorization {
  // Performs authorization check based on the attributes associated with the
  // incoming request, and returns status `OK` or not `OK`.
  rpc Check(v2.CheckRequest) returns (v2.CheckResponse);
}

What that means is our gRPC external server needs to implement the Check() service..

images/ascii.png

Setup

Anyway, lets get started. You'll need:

  • Go 11+
  • Get Envoy: The tutorial runs envoy directly here but you can use the docker image as well.

Start with docker-compose

You can simple use docker-compose file the testing.

docker-compose up --remove-orphans --build --force-recreate

Start Backend

The backend here is a simple http webserver that will print the inbound headers and add one in the response (X-Custom-Header-From-Backend).

$ go run backend_server/http_server.go 

Start External Authorization server

$ go run authz_server/grpc_server.go

The core of the authorization server isn't really anything special...i've just hardcoded it to look for a header value of 'foo' through...you can add on any bit of complex handling here you want.

func (a *AuthorizationServer) Check(ctx context.Context, req *auth.CheckRequest) (*auth.CheckResponse, error) {
	log.Println(">>> Authorization called check()")
	authHeader, ok := req.Attributes.Request.Http.Headers["authorization"]
	var splitToken []string

	if ok {
		splitToken = strings.Split(authHeader, "Bearer ")
	}
	if len(splitToken) == 2 {
		token := splitToken[1]

		if token == "foo" {
			return &auth.CheckResponse{
				Status: &rpcstatus.Status{
					Code: int32(rpc.OK),
				},
				HttpResponse: &auth.CheckResponse_OkResponse{
					OkResponse: &auth.OkHttpResponse{
						Headers: []*core.HeaderValueOption{
							{
								Header: &core.HeaderValue{
									Key:   "x-custom-header-from-authz",
									Value: "some value",
								},
							},
						},
					},
				},
			}, nil
		} else {
			return &auth.CheckResponse{
				Status: &rpcstatus.Status{
					Code: int32(rpc.PERMISSION_DENIED),
				},
				HttpResponse: &auth.CheckResponse_DeniedResponse{
					DeniedResponse: &auth.DeniedHttpResponse{
						Status: &envoy_type.HttpStatus{
							Code: envoy_type.StatusCode_Unauthorized,
						},
						Body: "PERMISSION_DENIED",
					},
				},
			}, nil

		}

	}
	return &auth.CheckResponse{
		Status: &rpcstatus.Status{
			Code: int32(rpc.UNAUTHENTICATED),
		},
		HttpResponse: &auth.CheckResponse_DeniedResponse{
			DeniedResponse: &auth.DeniedHttpResponse{
				Status: &envoy_type.HttpStatus{
					Code: envoy_type.StatusCode_Unauthorized,
				},
				Body: "Authorization Header malformed or not provided",
			},
		},
	}, nil
}

Start Envoy

$ envoy -c basic.yaml -l info

The envoy confg settings describe ext-authz as well as a set of custom headers to send to the client and the authorization checker (i'll discuss that bit later on in the doc)

    filter_chains:
    - filters:
      - name: envoy.http_connection_manager
        typed_config:  
          "@type": type.googleapis.com/envoy.config.filter.network.http_connection_manager.v2.HttpConnectionManager
          stat_prefix: ingress_http
          codec_type: AUTO
          route_config:
            name: local_route
            virtual_hosts:
            - name: local_service
              domains: ["*"]
              routes:
              - match:
                  prefix: "/"
                route:
                  # host_rewrite: server.domain.com
                  cluster: service_backend
                request_headers_to_add:
                  - header:
                      key: x-custom-to-backend
                      value: value-for-backend-from-envoy
                # per_filter_config:
                #   envoy.ext_authz:
                #     check_settings:
                #       context_extensions:
                #         x-forwarded-host: original-host-as-context

          http_filters:
          - name: envoy.ext_authz
            config:
              grpc_service:
                envoy_grpc:
                  cluster_name: ext-authz
                timeout: 0.5s
          - name: envoy.lua
            config:
              inline_code: |
                function envoy_on_request(request_handle)
                  request_handle:logInfo('>>> LUA envoy_on_request Called')
                  --buf = request_handle:body()
                  --bufbytes = buf:getBytes(0, buf:length())
                  --request_handle:logInfo(bufbytes)
                end
                
                function envoy_on_response(response_handle)
                  response_handle:logInfo('>>> LUA envoy_on_response Called')
                  response_handle:headers():add("X-Custom-Header-From-LUA", "bar")
                end

  clusters:
  - name: ext-authz
    type: static
    http2_protocol_options: {}
    load_assignment:
      cluster_name: ext-authz
      endpoints:
      - lb_endpoints:
        - endpoint:
            address:
              socket_address:
                address: 127.0.0.1
                port_value: 50051                

The moment you start envoy, it will start sending gRPC healthcheck requests to the backend. That bit isn't related to authorization services but i thouht it'd be nice to add into envoy's config. For more info, see the part where the backend requests are made here in this the generic grpc_health_proxy

Send Requests

  1. No Header
$ curl -vv -w "\n"  http://localhost:8080/

> GET / HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.66.0
> Accept: */*
> 

< HTTP/1.1 401 Unauthorized
< content-length: 46
< content-type: text/plain
< date: Sat, 09 Nov 2019 17:22:39 GMT
< server: envoy
< 
Authorization Header malformed or not provided

Send Incorrect Header

$ curl -vv -H "Authorization: Bearer bar" -w "\n"  http://localhost:8080/

> GET / HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.66.0
> Accept: */*
> Authorization: Bearer bar

* Mark bundle as not supporting multiuse
< HTTP/1.1 401 Unauthorized
< content-length: 17
< content-type: text/plain
< date: Sat, 09 Nov 2019 17:25:14 GMT
< server: envoy
< 

PERMISSION_DENIED

Send Correct Header

$ curl -vv -H "Authorization: Bearer foo" -w "\n"  http://localhost:8080/

> GET / HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.66.0
> Accept: */*
> Authorization: Bearer foo

< HTTP/1.1 200 OK
< x-custom-header-from-backend: from backend
< date: Sat, 09 Nov 2019 17:26:06 GMT
< content-length: 2
< content-type: text/plain; charset=utf-8
< x-envoy-upstream-service-time: 0
< x-custom-header-from-lua: bar
< server: envoy

ok

Send Custom Headers to ext_authz server

If you want to add a custom metadata/header to just the authorization server that was not included in the original request (eg to address envoy issue #3876, consider using the attribute_context extension

In the configuration above, if you send a request fom the with these headers

Client:

$ curl -vv -H "Authorization: Bearer foo" -H "Host: s2.domain.com" -H "foo: bar" http://localhost:8080/

	> GET / HTTP/1.1
	> Host: s2.domain.com
	> User-Agent: curl/7.66.0
	> Accept: */*
	> Authorization: Bearer foo
	> foo: bar
	> 
	* Mark bundle as not supporting multiuse
	< HTTP/1.1 200 OK
	< x-custom-header-from-backend: from backend
	< date: Mon, 11 Nov 2019 19:19:44 GMT
	< content-length: 2
	< content-type: text/plain; charset=utf-8
	< x-envoy-upstream-service-time: 0
	< x-custom-header-from-lua: bar	
	< server: envoy
	< 

	ok

External Authorization server will see an additional context value sent "x-forwarded-host" which you can use to make decision.

$ go run authz_server/grpc_server.go
	2019/11/11 11:19:39 Starting gRPC Server at :50051
	2019/11/11 11:19:42 Handling grpc Check request
	2019/11/11 11:19:44 >>> Authorization called check()
	2019/11/11 11:19:44 Inbound Headers: 
	2019/11/11 11:19:44 {
	":authority": "s2.domain.com",
	":method": "GET",
	":path": "/",
	"accept": "*/*",
	"authorization": "Bearer foo",
	"foo": "bar",
	"user-agent": "curl/7.66.0",
	"x-forwarded-proto": "http",
	"x-request-id": "86c79873-b145-4e82-8e7c-800ecb0ba931"
	}
	
	2019/11/11 11:19:44 Context Extensions: 
	2019/11/11 11:19:44 {
	"x-forwarded-host": "original-host-as-context"
	}

Finally, the backend system will not see that custom header but all the others you specified

$ go run backend_server/http_server.go 
	2019/11/11 11:19:42 Starting Server..
	2019/11/11 11:19:44 / called
	GET / HTTP/1.1
	Host: server.domain.com
	Accept: */*
	Authorization: Bearer foo
	Content-Length: 0
	Foo: bar
	User-Agent: curl/7.66.0
	X-Custom-Header-From-Authz: some value
	X-Custom-To-Backend: value-for-backend-from-envoy
	X-Envoy-Expected-Rq-Timeout-Ms: 15000
	X-Forwarded-Proto: http
	X-Request-Id: 86c79873-b145-4e82-8e7c-800ecb0ba931

Thats it...but realistically, you probably would be fine with using Envoy's built-in capabilities or with Open Policy Agent or even Istio Authorization. This repo is just a demo of stand-alone Envoy.


References

Google Issued OpenID Connect tokens

envoy_external_authorization's People

Contributors

umutcomlekci avatar

Stargazers

 avatar  avatar  avatar

Watchers

 avatar  avatar

envoy_external_authorization's Issues

Envoy Docker file is failing to build

docker build -f Dockerfile-envoy -t envoy:latest .
Sending build context to Docker daemon  214.5kB
Step 1/2 : FROM envoyproxy/envoy:latest
manifest for envoyproxy/envoy:latest not found: manifest unknown: manifest unknown

I was able to fix this by forcing the from line to

envoyproxy/envoy-alpine:v1.11.1

results

docker build -f Dockerfile-envoy -t envoy:latest .
Sending build context to Docker daemon  214.5kB
Step 1/2 : FROM envoyproxy/envoy-alpine:v1.11.1
v1.11.1: Pulling from envoyproxy/envoy-alpine
921b31ab772b: Pull complete 
388b0cd25b3f: Pull complete 
3c9570bc42dc: Pull complete 
f7d31b934134: Pull complete 
ec584791657b: Pull complete 
03fd1708351f: Pull complete 
Digest: sha256:aa41a74db6f34b620bff9bbe08c50cd94f9bb09faa9e1199f393d99510e46802
Status: Downloaded newer image for envoyproxy/envoy-alpine:v1.11.1
 ---> cbca216b965b
Step 2/2 : COPY basic.yaml /etc/envoy/envoy.yaml
 ---> 9ed7fa498c86
Successfully built 9ed7fa498c86
Successfully tagged envoy:latest

Envoy config doesn't like 0.0.0.1

Results found on both envoy image versions I tried (1.11.1 and 1.14.1)

docker-compose up --remove-orphans --build --force-recreate
...
envoy_1    | [2020-04-13 13:59:48.274][1][info][main] [source/server/server.cc:340] admin address: 0.0.0.1:9000
envoy_1    | [2020-04-13 13:59:48.275][1][critical][main] [source/server/server.cc:95] error initializing configuration '/etc/envoy/envoy.yaml': cannot bind '0.0.0.1:9000': Cannot assign requested address
envoy_1    | [2020-04-13 13:59:48.275][1][info][main] [source/server/server.cc:606] exiting
envoy_1    | cannot bind '0.0.0.1:9000': Cannot assign requested address
test_envoy_1 exited with code 1

When I changed your envoy config to use 0.0.0.0 and it seems to work fine

admin:
  access_log_path: /dev/null
  address:
    socket_address:
      address: 0.0.0.0
      port_value: 9000

and I run compose

envoy_1    | [2020-04-13 14:03:39.636][1][info][main] [source/server/server.cc:322] admin address: 0.0.0.0:9000
envoy_1    | [2020-04-13 14:03:39.639][1][info][main] [source/server/server.cc:432] runtime: layers:
envoy_1    |   - name: base
envoy_1    |     static_layer:
envoy_1    |       {}
envoy_1    |   - name: admin
envoy_1    |     admin_layer:
envoy_1    |       {}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.