1---
2title: Envoy
3kind: tutorial
4weight: 8
5---
6
7[Envoy](https://www.envoyproxy.io/docs/envoy/v1.10.0/intro/what_is_envoy) is a L7 proxy and communication bus designed for large modern service oriented architectures. Envoy (v1.7.0+) supports an [External Authorization filter](https://www.envoyproxy.io/docs/envoy/v1.10.0/intro/arch_overview/ext_authz_filter.html) which calls an authorization service to check if the incoming request is authorized or not.
8
9This feature makes it possible to delegate authorization decisions to an external service and also makes the request context available to the service which can then be used to make an informed decision about the fate of the incoming request received by Envoy.
10
11## Goals
12
13The tutorial shows how Envoy’s External authorization filter can be used with OPA as an authorization service to enforce security policies over API requests received by Envoy. The tutorial also covers examples of authoring custom policies over the HTTP request body.
14
15## Prerequisites
16
17This tutorial requires Kubernetes 1.14 or later. To run the tutorial locally, we recommend using [minikube](https://kubernetes.io/docs/getting-started-guides/minikube) in version `v1.0+` with Kubernetes 1.14 (which is the default).
18
19## Steps
20
21### 1. Start Minikube
22
23```bash
24minikube start
25```
26
27### 2. Create ConfigMap containing configuration for Envoy
28
29The Envoy configuration below defines an external authorization filter `envoy.ext_authz` for a gRPC authorization server.
30
31Save the configuration as **envoy.yaml**:
32
33```yaml
34static_resources:
35  listeners:
36    - address:
37        socket_address:
38          address: 0.0.0.0
39          port_value: 8000
40      filter_chains:
41        - filters:
42            - name: envoy.http_connection_manager
43              typed_config:
44                "@type": type.googleapis.com/envoy.config.filter.network.http_connection_manager.v2.HttpConnectionManager
45                codec_type: auto
46                stat_prefix: ingress_http
47                route_config:
48                  name: local_route
49                  virtual_hosts:
50                    - name: backend
51                      domains:
52                        - "*"
53                      routes:
54                        - match:
55                            prefix: "/"
56                          route:
57                            cluster: service
58                http_filters:
59                  - name: envoy.ext_authz
60                    typed_config:
61                      "@type": type.googleapis.com/envoy.config.filter.http.ext_authz.v2.ExtAuthz
62                      with_request_body:
63                        max_request_bytes: 8192
64                        allow_partial_message: true
65                      failure_mode_allow: false
66                      grpc_service:
67                        google_grpc:
68                          target_uri: 127.0.0.1:9191
69                          stat_prefix: ext_authz
70                        timeout: 0.5s
71                  - name: envoy.router
72                    typed_config: {}
73  clusters:
74    - name: service
75      connect_timeout: 0.25s
76      type: strict_dns
77      lb_policy: round_robin
78      load_assignment:
79        cluster_name: service
80        endpoints:
81          - lb_endpoints:
82              - endpoint:
83                  address:
84                    socket_address:
85                      address: 127.0.0.1
86                      port_value: 8080
87admin:
88  access_log_path: "/dev/null"
89  address:
90    socket_address:
91      address: 0.0.0.0
92      port_value: 8001
93```
94
95Create the ConfigMap:
96
97```bash
98kubectl create configmap proxy-config --from-file envoy.yaml
99```
100
101### 3. Define a OPA policy
102
103The following OPA policy restricts access to the `/people` endpoint exposed by our sample app:
104
105* Alice is granted a **guest** role and can perform a `GET` request to `/people`.
106* Bob is granted an **admin** role and can perform a `GET` and `POST` request to `/people`.
107
108The policy also restricts an `admin` user, in this case `bob` from creating an employee with the same `firstname` as himself.
109
110**policy.rego**
111
112```live:example:module:openable
113package envoy.authz
114
115import input.attributes.request.http as http_request
116
117default allow = false
118
119token = {"valid": valid, "payload": payload} {
120    [_, encoded] := split(http_request.headers.authorization, " ")
121    [valid, _, payload] := io.jwt.decode_verify(encoded, {"secret": "secret"})
122}
123
124allow {
125    is_token_valid
126    action_allowed
127}
128
129is_token_valid {
130  token.valid
131  token.payload.nbf <= time.now_ns()
132  time.now_ns() < token.payload.exp
133}
134
135action_allowed {
136  http_request.method == "GET"
137  token.payload.role == "guest"
138  glob.match("/people*", [], http_request.path)
139}
140
141action_allowed {
142  http_request.method == "GET"
143  token.payload.role == "admin"
144  glob.match("/people*", [], http_request.path)
145}
146
147action_allowed {
148  http_request.method == "POST"
149  token.payload.role == "admin"
150  glob.match("/people", [], http_request.path)
151  lower(input.parsed_body.firstname) != base64url.decode(token.payload.sub)
152}
153```
154
155Store the policy in Kubernetes as a Secret.
156
157```bash
158kubectl create secret generic opa-policy --from-file policy.rego
159```
160
161In the next step, OPA is configured to query for the `data.envoy.authz.allow` decision. If the response is `true` the operation is
162allowed, otherwise the operation is denied. Sample input received by OPA is shown below:
163
164```live:example:query:hidden
165data.envoy.authz.allow
166```
167
168```live:example:input
169{
170  "attributes": {
171      "request": {
172          "http": {
173              "method": "GET",
174                "path": "/people",
175                "headers": {
176                  "authorization": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiZ3Vlc3QiLCJzdWIiOiJZV3hwWTJVPSIsIm5iZiI6MTUxNDg1MTEzOSwiZXhwIjoxNjQxMDgxNTM5fQ.K5DnnbbIOspRbpCr2IKXE9cPVatGOCBrBQobQmBmaeU"
177                }
178            }
179        }
180    }
181}
182```
183
184With the input value above, the answer is:
185
186```live:example:output
187```
188
189An example of the complete input received by OPA can be seen [here](https://github.com/open-policy-agent/opa-istio-plugin#example-input).
190
191> In typical deployments the policy would either be built into the OPA container
192> image or it would fetched dynamically via the [Bundle
193> API](https://www.openpolicyagent.org/docs/latest/bundles/). ConfigMaps are
194> used in this tutorial for test purposes.
195
196### 4. Create App Deployment with OPA and Envoy sidecars
197
198Our deployment contains a sample Go app which provides information about employees in a company. It exposes a `/people` endpoint to `get` and `create` employees. More information can on the app be found [here](https://github.com/ashutosh-narkar/go-test-server).
199
200OPA is started with a configuration that sets the listening address of Envoy External Authorization gRPC server and specifies the name of the policy decision to query. More information on the configuration options can be found [here](https://github.com/open-policy-agent/opa-istio-plugin#configuration).
201
202Save the deployment as **deployment.yaml**:
203
204```yaml
205kind: Deployment
206apiVersion: apps/v1
207metadata:
208  name: example-app
209  labels:
210    app: example-app
211spec:
212  replicas: 1
213  selector:
214    matchLabels:
215      app: example-app
216  template:
217    metadata:
218      labels:
219        app: example-app
220    spec:
221      initContainers:
222        - name: proxy-init
223          image: openpolicyagent/proxy_init:v2
224          args: ["-p", "8000", "-u", "1111"]
225          securityContext:
226            capabilities:
227              add:
228              - NET_ADMIN
229            runAsNonRoot: false
230            runAsUser: 0
231      containers:
232        - name: app
233          image: openpolicyagent/demo-test-server:v1
234          ports:
235            - containerPort: 8080
236        - name: envoy
237          image: envoyproxy/envoy:v1.10.0
238          securityContext:
239            runAsUser: 1111
240          volumeMounts:
241          - readOnly: true
242            mountPath: /config
243            name: proxy-config
244          args:
245          - "envoy"
246          - "--config-path"
247          - "/config/envoy.yaml"
248        - name: opa
249          # Note: openpolicyagent/opa:latest-istio is created by retagging
250          # the latest released image of OPA-Istio.
251          image: openpolicyagent/opa:{{< current_opa_istio_docker_version >}}
252          securityContext:
253            runAsUser: 1111
254          volumeMounts:
255          - readOnly: true
256            mountPath: /policy
257            name: opa-policy
258          args:
259          - "run"
260          - "--server"
261          - "--set=plugins.envoy_ext_authz_grpc.addr=:9191"
262          - "--set=plugins.envoy_ext_authz_grpc.query=data.envoy.authz.allow"
263          - "--set=decision_logs.console=true"
264          - "--ignore=.*"
265          - "/policy/policy.rego"
266      volumes:
267        - name: proxy-config
268          configMap:
269            name: proxy-config
270        - name: opa-policy
271          secret:
272            secretName: opa-policy
273```
274
275```bash
276kubectl apply -f deployment.yaml
277```
278
279> The `proxy-init` container installs iptables rules to redirect all container traffic through the Envoy proxy sidecar. More information can be found [here](https://github.com/open-policy-agent/contrib/tree/master/envoy_iptables).
280
281### 5. Create a Service to expose HTTP server
282
283```bash
284kubectl expose deployment example-app --type=NodePort --name=example-app-service  --port=8080
285```
286
287Set the `SERVICE_URL` environment variable to the service's IP/port.
288
289**minikube:**
290
291```bash
292export SERVICE_PORT=$(kubectl get service example-app-service -o jsonpath='{.spec.ports[?(@.port==8080)].nodePort}')
293export SERVICE_HOST=$(minikube ip)
294export SERVICE_URL=$SERVICE_HOST:$SERVICE_PORT
295echo $SERVICE_URL
296```
297
298**minikube (example):**
299
300```bash
301192.168.99.113:31056
302```
303
304### 6. Exercise the OPA policy
305
306For convenience, we’ll want to store Alice's and Bob's tokens in environment variables.
307
308```bash
309export ALICE_TOKEN="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiZ3Vlc3QiLCJzdWIiOiJZV3hwWTJVPSIsIm5iZiI6MTUxNDg1MTEzOSwiZXhwIjoxNjQxMDgxNTM5fQ.K5DnnbbIOspRbpCr2IKXE9cPVatGOCBrBQobQmBmaeU"
310export BOB_TOKEN="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiYWRtaW4iLCJzdWIiOiJZbTlpIiwibmJmIjoxNTE0ODUxMTM5LCJleHAiOjE2NDEwODE1Mzl9.WCxNAveAVAdRCmkpIObOTaSd0AJRECY2Ch2Qdic3kU8"
311```
312
313Check that `Alice` can get employees **but cannot** create one.
314
315```bash
316curl -i -H "Authorization: Bearer "$ALICE_TOKEN"" http://$SERVICE_URL/people
317curl -i -H "Authorization: Bearer "$ALICE_TOKEN"" -d '{"firstname":"Charlie", "lastname":"OPA"}' -H "Content-Type: application/json" -X POST http://$SERVICE_URL/people
318```
319
320Check that `Bob` can get employees and also create one.
321
322```bash
323curl -i -H "Authorization: Bearer "$BOB_TOKEN"" http://$SERVICE_URL/people
324curl -i -H "Authorization: Bearer "$BOB_TOKEN"" -d '{"firstname":"Charlie", "lastname":"Opa"}' -H "Content-Type: application/json" -X POST http://$SERVICE_URL/people
325```
326
327Check that `Bob` **cannot** create an employee with the same firstname as himself.
328
329```bash
330curl -i  -H "Authorization: Bearer "$BOB_TOKEN"" -d '{"firstname":"Bob", "lastname":"Rego"}' -H "Content-Type: application/json" -X POST http://$SERVICE_URL/people
331```
332
333## Wrap Up
334
335Congratulations for finishing the tutorial !
336
337This tutorial showed how to use OPA as an External authorization service to enforce custom policies by leveraging Envoy’s External authorization filter.
338
339This tutorial also showed a sample OPA policy that returns a `boolean` decision to indicate whether a request should be allowed or not.
340
341Envoy's external authorization filter allows optional response headers and body to be sent to the downstream client or upstream. An example of a rule that returns an object that not only indicates if a request is allowed or not but also provides optional response headers, body and HTTP status that can be sent to the downstream client or upstream can be seen [here](https://github.com/open-policy-agent/opa-istio-plugin#example-policy-with-object-response).
342