github.com/projectcontour/contour@v1.28.2/site/content/guides/external-authorization.md (about) 1 --- 2 title: External Authorization Support 3 layout: page 4 --- 5 6 Starting in version 1.9, Contour supports routing client requests to an 7 external authorization server. This feature can be used to centralize 8 client authorization so that applications don't have to implement their 9 own authorization mechanisms. 10 11 ## Authorization Architecture 12 13 An external authorization server is a server that implements the Envoy 14 external authorization [GRPC protocol][3]. Contour supports any server 15 that implements this protocol. 16 17 You can bind an authorization server to Contour by creating a 18 [`ExtensionService`][4] resource. 19 This resource tells Contour the service exists, and that it should 20 program Envoy with an upstream cluster directing traffic to it. 21 Note that the `ExtensionService` resource just binds the server; at this 22 point Contour doesn't assume that the server is an authorization server. 23 24 Once you have created `ExtensionService` resource, you can bind it to a 25 particular application by referencing it in a [`HTTPProxy`][5] resource. 26 In the `virtualhost` field, a new `authorization` field specifies the name 27 of an `ExtensionService` to bind for the virtual host. 28 When you specify a resource name here, Contour will program Envoy to 29 send authorization checks to the extension service cluster before routing 30 the request to the upstream application. 31 32 ## Authorization Request Flow 33 34 It is helpful to have a mental model of how requests flow through the various 35 servers involved in authorizing HTTP requests. 36 The flow diagram below shows the actors that participate in the successful 37 authorization of an HTTP request. 38 Note that in some cases, these actors can be combined into a single 39 application server. 40 For example, there is no requirement for the external authorization server to 41 be a separate application from the authorization provider. 42 43 44 <p align="center"> 45 <img src="/img/uml/client-auth-sequence-ext.png" alt="client authorization sequence diagram"/> 46 </p> 47 48 A HTTP Client generates an HTTP request and sends it to 49 an Envoy instance that Contour has programmed with an external 50 authorization configuration. 51 Envoy holds the HTTP request and sends an authorization check request 52 to the Authorization server that Contour has bound to the virtual host. 53 The Authorization server may be able to verify the request locally, but in 54 many cases it will need to make additional requests to an Authorization 55 Provider server to verify or obtain an authorization token. 56 57 In this flow, the ExtAuth server is able to authorize the request, and sends an 58 authorization response back to the Proxy. 59 The response includes the authorization status, and a set of HTTP headers 60 modifications to make to the HTTP request. 61 Since this authorization was successful, the Proxy modifies the request and 62 forwards it to the application. 63 If the authorization was not successful, the Proxy would have immediately 64 responded to the client with an HTTP error. 65 66 ## Using the Contour Authorization Server 67 68 The Contour project has built a simple authorization server named 69 [`contour-authserver`][1]. `contour-authserver` supports an authorization 70 testing server, and an HTTP basic authorization server that accesses 71 credentials stored in [htpasswd][2] format. 72 73 To get started, ensure that Contour is deployed and that you have 74 [cert-manager][6] installed in your cluster so that you can easily issue 75 self-signed TLS certificates. 76 77 At this point, we should also create a cluster-wide self-signed certificate 78 issuer, just to make it easier to provision TLS certificates later: 79 80 ```bash 81 $ kubectl apply -f - <<EOF 82 apiVersion: cert-manager.io/v1 83 kind: ClusterIssuer 84 metadata: 85 name: selfsigned 86 spec: 87 selfSigned: {} 88 EOF 89 clusterissuer.cert-manager.io/selfsigned created 90 ``` 91 92 ### Deploying the Authorization Server 93 94 The first step is to deploy `contour-authserver` to the `projectcontour-auth` 95 namespace. 96 To do this, we will use [`kustomize`][8] to build a set of YAML object that we can 97 deploy using kubectl. 98 In a new directory, create the following `kustomization.yaml` file: 99 100 ```yaml 101 apiVersion: kustomize.config.k8s.io/v1beta1 102 kind: Kustomization 103 104 namespace: projectcontour-auth 105 106 resources: 107 - github.com/projectcontour/contour-authserver/config/htpasswd 108 109 patchesJson6902: 110 - target: 111 group: cert-manager.io 112 version: v1 113 kind: Certificate 114 name: htpasswd 115 namespace: projectcontour-auth 116 patch: |- 117 - op: add 118 path: /spec/issuerRef/kind 119 value: ClusterIssuer 120 121 images: 122 - name: contour-authserver:latest 123 newName: docker.io/projectcontour/contour-authserver 124 newTag: v2 125 ``` 126 127 Note that the kustomization patches the `Certificate` resource to use the 128 "selfsigned" `ClusterIssuer` that we created earlier. 129 This is required because the `contour-authserver` deployment includes a 130 request for a self-signed TLS server certificate. 131 In a real deployment, this certificate should be requested from a real trusted 132 certificate issuer. 133 134 Now create the `projectcontour-auth` namespace, build the deployment YAML, 135 and apply to your cluster: 136 137 ```bash 138 $ kubectl create namespace projectcontour-auth 139 namespace/projectcontour-auth created 140 $ kubectl apply -f <(kustomize build .) 141 serviceaccount/htpasswd created 142 clusterrole.rbac.authorization.k8s.io/contour:authserver:htpasswd created 143 clusterrolebinding.rbac.authorization.k8s.io/contour:authserver:htpasswd created 144 service/htpasswd created 145 deployment.apps/htpasswd created 146 certificate.cert-manager.io/htpasswd created 147 ``` 148 149 At this point, `contour-authserver` is deployed and is exposed to 150 the cluster as the Service `projectcontour-auth/htpasswd`. 151 It has a self-signed TLS certificate and is accepting secure connections 152 on port 9443. 153 154 In the default configuration, `contour-authserver` will accept htpasswd data 155 from secrets with the `projectcontour.io/auth-type: basic` annotation. 156 Most systems install the Apache [`htpasswd`][7] tool, which we can use 157 to generate the password file: 158 159 ```bash 160 $ touch auth 161 $ htpasswd -b auth user1 password1 162 Adding password for user user1 163 $ htpasswd -b auth user2 password2 164 Adding password for user user2 165 $ htpasswd -b auth user3 password3 166 Adding password for user user3 167 ``` 168 169 Once we have some password data, we can populate a Kubernetes secret with it. 170 Note that the password data must be in the `auth` key in the secret, and that 171 the secret must be annotated with the `projectcontour.io/auth-type` key. 172 173 ```bash 174 $ kubectl create secret generic -n projectcontour-auth passwords --from-file=auth 175 secret/passwords created 176 $ kubectl annotate secret -n projectcontour-auth passwords projectcontour.io/auth-type=basic 177 secret/passwords annotated 178 ``` 179 180 ### Creating an Extension Service 181 182 Now that `contour-authserver` is deployed, the next step is to create a 183 `ExtensionService` resource. 184 185 ```yaml 186 apiVersion: projectcontour.io/v1alpha1 187 kind: ExtensionService 188 metadata: 189 name: htpasswd 190 namespace: projectcontour-auth 191 spec: 192 protocol: h2 193 services: 194 - name: htpasswd 195 port: 9443 196 ``` 197 198 The `ExtensionService` resource must be created in the same namespace 199 as the services that it binds. 200 This policy ensures that the creator of the `ExtensionService` also has 201 the authority over those services. 202 203 ```bash 204 $ kubectl apply -f htpasswd.yaml 205 extensionservice.projectcontour.io/htpasswd created 206 ``` 207 208 ### Deploying a Sample Application 209 210 To demonstrate how to use the authorization server in a `HTTPProxy` resource, 211 we first need to deploy a simple echo application. 212 213 ```yaml 214 apiVersion: apps/v1 215 kind: Deployment 216 metadata: 217 name: ingress-conformance-echo 218 spec: 219 replicas: 1 220 selector: 221 matchLabels: 222 app.kubernetes.io/name: ingress-conformance-echo 223 template: 224 metadata: 225 labels: 226 app.kubernetes.io/name: ingress-conformance-echo 227 spec: 228 containers: 229 - name: conformance-echo 230 image: agervais/ingress-conformance-echo:latest 231 ports: 232 - name: http-api 233 containerPort: 3000 234 readinessProbe: 235 httpGet: 236 path: /health 237 port: 3000 238 --- 239 apiVersion: v1 240 kind: Service 241 metadata: 242 name: ingress-conformance-echo 243 spec: 244 ports: 245 - name: http 246 port: 80 247 targetPort: http-api 248 selector: 249 app.kubernetes.io/name: ingress-conformance-echo 250 ``` 251 252 This echo server will respond with a JSON object that reports information about 253 the HTTP request it received, including the request headers. 254 255 ```bash 256 $ kubectl apply -f echo.yaml 257 deployment.apps/ingress-conformance-echo created 258 service/ingress-conformance-echo created 259 ``` 260 261 Once the application is running, we can expose it to Contour with a `HTTPProxy` 262 resource. 263 264 ```yaml 265 apiVersion: cert-manager.io/v1 266 kind: Certificate 267 metadata: 268 name: ingress-conformance-echo 269 spec: 270 dnsNames: 271 - local.projectcontour.io 272 secretName: ingress-conformance-echo 273 issuerRef: 274 name: selfsigned 275 kind: ClusterIssuer 276 --- 277 apiVersion: projectcontour.io/v1 278 kind: HTTPProxy 279 metadata: 280 name: echo 281 spec: 282 virtualhost: 283 fqdn: local.projectcontour.io 284 tls: 285 secretName: ingress-conformance-echo 286 routes: 287 - services: 288 - name: ingress-conformance-echo 289 port: 80 290 ``` 291 292 _Note that we created a TLS secret and exposed the application over HTTPS._ 293 294 ```bash 295 $ kubectl apply -f echo-proxy.yaml 296 certificate.cert-manager.io/ingress-conformance-echo created 297 httpproxy.projectcontour.io/echo created 298 $ kubectl get proxies echo 299 NAME FQDN TLS SECRET STATUS STATUS DESCRIPTION 300 echo local.projectcontour.io ingress-conformance-echo valid valid HTTPProxy 301 ``` 302 303 We can verify that the application is working by requesting any path: 304 305 ```bash 306 $ curl -k https://local.projectcontour.io/test/$((RANDOM)) 307 {"TestId":"","Path":"/test/12707","Host":"local.projectcontour.io","Method":"GET","Proto":"HTTP/1.1","Headers":{"Accept":["*/*"],"Content-Length":["0"],"User-Agent":["curl/7.64.1"],"X-Envoy-Expected-Rq-Timeout-Ms":["15000"],"X-Envoy-Internal":["true"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Proto":["https"],"X-Request-Id":["7b87d5d1-8ee8-40e3-81ac-7d74dfd4d50b"],"X-Request-Start":["t=1601596511.489"]}} 308 ``` 309 310 ### Using the Authorization Server 311 312 Now that we have a working application exposed by a `HTTPProxy` resource, we 313 can add HTTP basic authorization by binding to the `ExtensionService` that we 314 created earlier. 315 The simplest configuration is to add an `authorization` field that names the 316 authorization server `ExtensionService` resource that we created earlier. 317 318 ```yaml 319 apiVersion: projectcontour.io/v1 320 kind: HTTPProxy 321 metadata: 322 name: echo 323 spec: 324 virtualhost: 325 fqdn: local.projectcontour.io 326 tls: 327 secretName: ingress-conformance-echo 328 authorization: 329 extensionRef: 330 name: htpasswd 331 namespace: projectcontour-auth 332 routes: 333 - services: 334 - name: ingress-conformance-echo 335 port: 80 336 ``` 337 338 ```bash 339 $ kubectl apply -f echo-auth.yaml 340 httpproxy.projectcontour.io/echo configured 341 ``` 342 343 Now, when we make the same HTTP request, we find the response requests 344 authorization: 345 346 ```bash 347 $ curl -k -I https://local.projectcontour.io/test/$((RANDOM)) 348 HTTP/2 401 349 www-authenticate: Basic realm="default", charset="UTF-8" 350 date: Fri, 02 Oct 2020 00:27:49 GMT 351 server: envoy 352 ``` 353 354 Providing a user credential from the password file that we created 355 earlier allows the request to succeed. Note that `contour-authserver` 356 has injected a number of headers (prefixed with `Auth-`) to let the 357 application know how the request has been authorized. 358 359 ```bash 360 $ curl -k --user user1:password1 https://local.projectcontour.io/test/$((RANDOM)) 361 {"TestId":"","Path":"/test/27132","Host":"local.projectcontour.io","Method":"GET","Proto":"HTTP/1.1","Headers":{"Accept":["*/*"],"Auth-Handler":["htpasswd"],"Auth-Realm":["default"],"Auth-Username":["user1"],"Authorization":["Basic dXNlcjE6cGFzc3dvcmQx"],"Content-Length":["0"],"User-Agent":["curl/7.64.1"],"X-Envoy-Expected-Rq-Timeout-Ms":["15000"],"X-Envoy-Internal":["true"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Proto":["https"],"X-Request-Id":["2c0ae102-4cf6-400e-a38f-5f0b844364cc"],"X-Request-Start":["t=1601601826.102"]}} 362 ``` 363 364 ## Caveats 365 366 There are a few caveats to consider when deploying external 367 authorization: 368 369 1. Only one external authorization server can be configured on a virtual host 370 1. Only HTTPS virtual hosts are supported 371 1. External authorization cannot be used with the TLS fallback certificate (i.e. client SNI support is required) 372 373 [1]: https://github.com/projectcontour/contour-authserver 374 [2]: https://httpd.apache.org/docs/current/misc/password_encryptions.html 375 [3]: https://www.envoyproxy.io/docs/envoy/latest/api-v3/service/auth/v3/external_auth.proto 376 [4]: /docs/{{< param latest_version >}}/config/api/#projectcontour.io/v1alpha1.ExtensionService 377 [5]: /docs/{{< param latest_version >}}/config/api/#projectcontour.io/v1.HTTPProxy 378 [6]: https://cert-manager.io/ 379 [7]: https://httpd.apache.org/docs/current/programs/htpasswd.html 380 [8]: https://kubernetes-sigs.github.io/kustomize/