github.com/projectcontour/contour@v1.28.2/site/content/docs/1.26/guides/external-authorization.md (about) 1 --- 2 title: External Authorization Support 3 --- 4 5 Starting in version 1.9, Contour supports routing client requests to an 6 external authorization server. This feature can be used to centralize 7 client authorization so that applications don't have to implement their 8 own authorization mechanisms. 9 10 ## Authorization Architecture 11 12 An external authorization server is a server that implements the Envoy 13 external authorization [GRPC protocol][3]. Contour supports any server 14 that implements this protocol. 15 16 You can bind an authorization server to Contour by creating a 17 [`ExtensionService`][4] resource. 18 This resource tells Contour the service exists, and that it should 19 program Envoy with an upstream cluster directing traffic to it. 20 Note that the `ExtensionService` resource just binds the server; at this 21 point Contour doesn't assume that the server is an authorization server. 22 23 Once you have created `ExtensionService` resource, you can bind it to a 24 particular application by referencing it in a [`HTTPProxy`][5] resource. 25 In the `virtualhost` field, a new `authorization` field specifies the name 26 of an `ExtensionService` to bind for the virtual host. 27 When you specify a resource name here, Contour will program Envoy to 28 send authorization checks to the extension service cluster before routing 29 the request to the upstream application. 30 31 ## Authorization Request Flow 32 33 It is helpful to have a mental model of how requests flow through the various 34 servers involved in authorizing HTTP requests. 35 The flow diagram below shows the actors that participate in the successful 36 authorization of an HTTP request. 37 Note that in some cases, these actors can be combined into a single 38 application server. 39 For example, there is no requirement for the external authorization server to 40 be a separate application from the authorization provider. 41 42 43 <p align="center"> 44 <img src="/img/uml/client-auth-sequence-ext.png" alt="client authorization sequence diagram"/> 45 </p> 46 47 A HTTP Client generates an HTTP request and sends it to 48 an Envoy instance that Contour has programmed with an external 49 authorization configuration. 50 Envoy holds the HTTP request and sends an authorization check request 51 to the Authorization server that Contour has bound to the virtual host. 52 The Authorization server may be able to verify the request locally, but in 53 many cases it will need to make additional requests to an Authorization 54 Provider server to verify or obtain an authorization token. 55 56 In this flow, the ExtAuth server is able to authorize the request, and sends an 57 authorization response back to the Proxy. 58 The response includes the authorization status, and a set of HTTP headers 59 modifications to make to the HTTP request. 60 Since this authorization was successful, the Proxy modifies the request and 61 forwards it to the application. 62 If the authorization was not successful, the Proxy would have immediately 63 responded to the client with an HTTP error. 64 65 ## Using the Contour Authorization Server 66 67 The Contour project has built a simple authorization server named 68 [`contour-authserver`][1]. `contour-authserver` supports an authorization 69 testing server, and an HTTP basic authorization server that accesses 70 credentials stored in [htpasswd][2] format. 71 72 To get started, ensure that Contour is deployed and that you have 73 [cert-manager][6] installed in your cluster so that you can easily issue 74 self-signed TLS certificates. 75 76 At this point, we should also create a cluster-wide self-signed certificate 77 issuer, just to make it easier to provision TLS certificates later: 78 79 ```bash 80 $ kubectl apply -f - <<EOF 81 apiVersion: cert-manager.io/v1 82 kind: ClusterIssuer 83 metadata: 84 name: selfsigned 85 spec: 86 selfSigned: {} 87 EOF 88 clusterissuer.cert-manager.io/selfsigned created 89 ``` 90 91 ### Deploying the Authorization Server 92 93 The first step is to deploy `contour-authserver` to the `projectcontour-auth` 94 namespace. 95 To do this, we will use [`kustomize`][8] to build a set of YAML object that we can 96 deploy using kubectl. 97 In a new directory, create the following `kustomization.yaml` file: 98 99 ```yaml 100 apiVersion: kustomize.config.k8s.io/v1beta1 101 kind: Kustomization 102 103 namespace: projectcontour-auth 104 105 resources: 106 - github.com/projectcontour/contour-authserver/config/htpasswd 107 108 patchesJson6902: 109 - target: 110 group: cert-manager.io 111 version: v1 112 kind: Certificate 113 name: htpasswd 114 namespace: projectcontour-auth 115 patch: |- 116 - op: add 117 path: /spec/issuerRef/kind 118 value: ClusterIssuer 119 120 images: 121 - name: contour-authserver:latest 122 newName: ghcr.io/projectcontour/contour-authserver 123 newTag: v4 124 ``` 125 126 Note that the kustomization patches the `Certificate` resource to use the 127 "selfsigned" `ClusterIssuer` that we created earlier. 128 This is required because the `contour-authserver` deployment includes a 129 request for a self-signed TLS server certificate. 130 In a real deployment, this certificate should be requested from a real trusted 131 certificate issuer. 132 133 Now create the `projectcontour-auth` namespace, build the deployment YAML, 134 and apply to your cluster: 135 136 ```bash 137 $ kubectl create namespace projectcontour-auth 138 namespace/projectcontour-auth created 139 $ kubectl apply -f <(kustomize build .) 140 serviceaccount/htpasswd created 141 clusterrole.rbac.authorization.k8s.io/contour:authserver:htpasswd created 142 clusterrolebinding.rbac.authorization.k8s.io/contour:authserver:htpasswd created 143 service/htpasswd created 144 deployment.apps/htpasswd created 145 certificate.cert-manager.io/htpasswd created 146 ``` 147 148 At this point, `contour-authserver` is deployed and is exposed to 149 the cluster as the Service `projectcontour-auth/htpasswd`. 150 It has a self-signed TLS certificate and is accepting secure connections 151 on port 9443. 152 153 In the default configuration, `contour-authserver` will accept htpasswd data 154 from secrets with the `projectcontour.io/auth-type: basic` annotation. 155 Most systems install the Apache [`htpasswd`][7] tool, which we can use 156 to generate the password file: 157 158 ```bash 159 $ touch auth 160 $ htpasswd -b auth user1 password1 161 Adding password for user user1 162 $ htpasswd -b auth user2 password2 163 Adding password for user user2 164 $ htpasswd -b auth user3 password3 165 Adding password for user user3 166 ``` 167 168 Once we have some password data, we can populate a Kubernetes secret with it. 169 Note that the password data must be in the `auth` key in the secret, and that 170 the secret must be annotated with the `projectcontour.io/auth-type` key. 171 172 ```bash 173 $ kubectl create secret generic -n projectcontour-auth passwords --from-file=auth 174 secret/passwords created 175 $ kubectl annotate secret -n projectcontour-auth passwords projectcontour.io/auth-type=basic 176 secret/passwords annotated 177 ``` 178 179 ### Creating an Extension Service 180 181 Now that `contour-authserver` is deployed, the next step is to create a 182 `ExtensionService` resource. 183 184 ```yaml 185 apiVersion: projectcontour.io/v1alpha1 186 kind: ExtensionService 187 metadata: 188 name: htpasswd 189 namespace: projectcontour-auth 190 spec: 191 protocol: h2 192 services: 193 - name: htpasswd 194 port: 9443 195 ``` 196 197 The `ExtensionService` resource must be created in the same namespace 198 as the services that it binds. 199 This policy ensures that the creator of the `ExtensionService` also has 200 the authority over those services. 201 202 ```bash 203 $ kubectl apply -f htpasswd.yaml 204 extensionservice.projectcontour.io/htpasswd created 205 ``` 206 207 ### Deploying a Sample Application 208 209 To demonstrate how to use the authorization server in a `HTTPProxy` resource, 210 we first need to deploy a simple echo application. 211 212 ```yaml 213 apiVersion: apps/v1 214 kind: Deployment 215 metadata: 216 name: ingress-conformance-echo 217 spec: 218 replicas: 1 219 selector: 220 matchLabels: 221 app.kubernetes.io/name: ingress-conformance-echo 222 template: 223 metadata: 224 labels: 225 app.kubernetes.io/name: ingress-conformance-echo 226 spec: 227 containers: 228 - name: conformance-echo 229 image: agervais/ingress-conformance-echo:latest 230 ports: 231 - name: http-api 232 containerPort: 3000 233 readinessProbe: 234 httpGet: 235 path: /health 236 port: 3000 237 --- 238 apiVersion: v1 239 kind: Service 240 metadata: 241 name: ingress-conformance-echo 242 spec: 243 ports: 244 - name: http 245 port: 80 246 targetPort: http-api 247 selector: 248 app.kubernetes.io/name: ingress-conformance-echo 249 ``` 250 251 This echo server will respond with a JSON object that reports information about 252 the HTTP request it received, including the request headers. 253 254 ```bash 255 $ kubectl apply -f echo.yaml 256 deployment.apps/ingress-conformance-echo created 257 service/ingress-conformance-echo created 258 ``` 259 260 Once the application is running, we can expose it to Contour with a `HTTPProxy` 261 resource. 262 263 ```yaml 264 apiVersion: cert-manager.io/v1 265 kind: Certificate 266 metadata: 267 name: ingress-conformance-echo 268 spec: 269 dnsNames: 270 - local.projectcontour.io 271 secretName: ingress-conformance-echo 272 issuerRef: 273 name: selfsigned 274 kind: ClusterIssuer 275 --- 276 apiVersion: projectcontour.io/v1 277 kind: HTTPProxy 278 metadata: 279 name: echo 280 spec: 281 virtualhost: 282 fqdn: local.projectcontour.io 283 tls: 284 secretName: ingress-conformance-echo 285 routes: 286 - services: 287 - name: ingress-conformance-echo 288 port: 80 289 ``` 290 291 _Note that we created a TLS secret and exposed the application over HTTPS._ 292 293 ```bash 294 $ kubectl apply -f echo-proxy.yaml 295 certificate.cert-manager.io/ingress-conformance-echo created 296 httpproxy.projectcontour.io/echo created 297 $ kubectl get proxies echo 298 NAME FQDN TLS SECRET STATUS STATUS DESCRIPTION 299 echo local.projectcontour.io ingress-conformance-echo valid valid HTTPProxy 300 ``` 301 302 We can verify that the application is working by requesting any path: 303 304 ```bash 305 $ curl -k https://local.projectcontour.io/test/$((RANDOM)) 306 {"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"]}} 307 ``` 308 309 ### Using the Authorization Server 310 311 Now that we have a working application exposed by a `HTTPProxy` resource, we 312 can add HTTP basic authorization by binding to the `ExtensionService` that we 313 created earlier. 314 The simplest configuration is to add an `authorization` field that names the 315 authorization server `ExtensionService` resource that we created earlier. 316 317 ```yaml 318 apiVersion: projectcontour.io/v1 319 kind: HTTPProxy 320 metadata: 321 name: echo 322 spec: 323 virtualhost: 324 fqdn: local.projectcontour.io 325 tls: 326 secretName: ingress-conformance-echo 327 authorization: 328 extensionRef: 329 name: htpasswd 330 namespace: projectcontour-auth 331 routes: 332 - services: 333 - name: ingress-conformance-echo 334 port: 80 335 ``` 336 337 ```bash 338 $ kubectl apply -f echo-auth.yaml 339 httpproxy.projectcontour.io/echo configured 340 ``` 341 342 Now, when we make the same HTTP request, we find the response requests 343 authorization: 344 345 ```bash 346 $ curl -k -I https://local.projectcontour.io/test/$((RANDOM)) 347 HTTP/2 401 348 www-authenticate: Basic realm="default", charset="UTF-8" 349 date: Fri, 02 Oct 2020 00:27:49 GMT 350 server: envoy 351 ``` 352 353 Providing a user credential from the password file that we created 354 earlier allows the request to succeed. Note that `contour-authserver` 355 has injected a number of headers (prefixed with `Auth-`) to let the 356 application know how the request has been authorized. 357 358 ```bash 359 $ curl -k --user user1:password1 https://local.projectcontour.io/test/$((RANDOM)) 360 {"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"]}} 361 ``` 362 363 ## Global External Authorization 364 365 Starting from version 1.25, Contour supports global external authorization. This allows you to setup a single external authorization configuration for all your virtual hosts (HTTP and HTTPS). 366 367 To get started, ensure you have `contour-authserver` and the `ExtensionService` deployed as described above. 368 369 ### Global Configuration 370 371 Define the global external authorization configuration in your contour config. 372 373 ```yaml 374 globalExtAuth: 375 extensionService: projectcontour-auth/htpasswd 376 failOpen: false 377 authPolicy: 378 context: 379 header1: value1 380 header2: value2 381 responseTimeout: 1s 382 ``` 383 384 Setup a HTTPProxy without TLS 385 ```yaml 386 apiVersion: projectcontour.io/v1 387 kind: HTTPProxy 388 metadata: 389 name: echo 390 spec: 391 virtualhost: 392 fqdn: local.projectcontour.io 393 routes: 394 - services: 395 - name: ingress-conformance-echo 396 port: 80 397 ``` 398 399 ``` 400 $ kubectl apply -f echo-proxy.yaml 401 httpproxy.projectcontour.io/echo created 402 ``` 403 404 When we make a HTTP request without authentication details, we can see that the endpoint is secured and returns a 401. 405 406 ``` 407 $ curl -k -I http://local.projectcontour.io/test/$((RANDOM)) 408 HTTP/1.1 401 Unauthorized 409 www-authenticate: Basic realm="default", charset="UTF-8" 410 vary: Accept-Encoding 411 date: Mon, 20 Feb 2023 13:45:31 GMT 412 ``` 413 414 If you add the username and password to the same request you can verify that the request succeeds. 415 ``` 416 $ curl -k --user user1:password1 http://local.projectcontour.io/test/$((RANDOM)) 417 {"TestId":"","Path":"/test/27748","Host":"local.projectcontour.io","Method":"GET","Proto":"HTTP/1.1","Headers":{"Accept":["*/*"],"Auth-Context-Header1":["value1"],"Auth-Context-Header2":["value2"],"Auth-Context-Routq":["global"],"Auth-Handler":["htpasswd"],"Auth-Realm":["default"],"Auth-Username":["user1"],"Authorization":["Basic dXNlcjE6cGFzc3dvcmQx"],"User-Agent":["curl/7.86.0"],"X-Envoy-Expected-Rq-Timeout-Ms":["15000"],"X-Envoy-Internal":["true"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Proto":["http"],"X-Request-Id":["b6bb7036-8408-4b03-9ce5-7011d89799b4"],"X-Request-Start":["t=1676900780.118"]}} 418 ``` 419 420 Global external authorization can also be configured with TLS virtual hosts. Update your HTTPProxy by adding `tls` and `secretName` to it. 421 422 ```yaml 423 apiVersion: projectcontour.io/v1 424 kind: HTTPProxy 425 metadata: 426 name: echo 427 spec: 428 virtualhost: 429 fqdn: local.projectcontour.io 430 tls: 431 secretName: ingress-conformance-echo 432 routes: 433 - services: 434 - name: ingress-conformance-echo 435 port: 80 436 ``` 437 438 ``` 439 $ kubectl apply -f echo-proxy.yaml 440 httpproxy.projectcontour.io/echo configured 441 ``` 442 443 you can verify the HTTPS requests succeeds 444 ``` 445 $ curl -k --user user1:password1 https://local.projectcontour.io/test/$((RANDOM)) 446 {"TestId":"","Path":"/test/13499","Host":"local.projectcontour.io","Method":"GET","Proto":"HTTP/1.1","Headers":{"Accept":["*/*"],"Auth-Context-Header1":["value1"],"Auth-Context-Header2":["value2"],"Auth-Context-Routq":["global"],"Auth-Handler":["htpasswd"],"Auth-Realm":["default"],"Auth-Username":["user1"],"Authorization":["Basic dXNlcjE6cGFzc3dvcmQx"],"User-Agent":["curl/7.86.0"],"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":["2b3edbed-3c68-44ef-a659-2e1245d7fe13"],"X-Request-Start":["t=1676901557.918"]}} 447 ``` 448 449 ### Excluding a virtual host from global external authorization 450 451 You can exclude a virtual host from the global external authorization policy by setting the `disabled` flag to true under `authPolicy`. 452 453 ```yaml 454 apiVersion: projectcontour.io/v1 455 kind: HTTPProxy 456 metadata: 457 name: echo 458 spec: 459 virtualhost: 460 fqdn: local.projectcontour.io 461 tls: 462 secretName: ingress-conformance-echo 463 authorization: 464 authPolicy: 465 disabled: true 466 routes: 467 - services: 468 - name: ingress-conformance-echo 469 port: 80 470 ``` 471 472 ``` 473 $ kubectl apply -f echo-proxy.yaml 474 httpproxy.projectcontour.io/echo configured 475 ``` 476 477 You can verify that an insecure request succeeds without being authorized. 478 479 ``` 480 $ curl -k https://local.projectcontour.io/test/$((RANDOM)) 481 {"TestId":"","Path":"/test/51","Host":"local.projectcontour.io","Method":"GET","Proto":"HTTP/1.1","Headers":{"Accept":["*/*"],"User-Agent":["curl/7.86.0"],"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":["18716e12-dcce-45ba-a3bb-bc26af3775d2"],"X-Request-Start":["t=1676901847.802"]}} 482 ``` 483 484 ### Overriding global external authorization for a HTTPS virtual host 485 486 You may want a different configuration than what is defined globally. To override the global external authorization, add the `authorization` block to your TLS enabled HTTPProxy as shown below 487 488 ```yaml 489 apiVersion: projectcontour.io/v1 490 kind: HTTPProxy 491 metadata: 492 name: echo 493 spec: 494 virtualhost: 495 fqdn: local.projectcontour.io 496 tls: 497 secretName: ingress-conformance-echo 498 authorization: 499 extensionRef: 500 name: htpasswd 501 namespace: projectcontour-auth 502 routes: 503 - services: 504 - name: ingress-conformance-echo 505 port: 80 506 ``` 507 508 ``` 509 $ kubectl apply -f echo-proxy.yaml 510 httpproxy.projectcontour.io/echo configured 511 ``` 512 513 You can verify that the endpoint has applied the overridden external authorization configuration. 514 515 ``` 516 $ curl -k --user user1:password1 https://local.projectcontour.io/test/$((RANDOM)) 517 {"TestId":"","Path":"/test/4514","Host":"local.projectcontour.io","Method":"GET","Proto":"HTTP/1.1","Headers":{"Accept":["*/*"],"Auth-Context-Overriden_message":["overriden_value"],"Auth-Handler":["htpasswd"],"Auth-Realm":["default"],"Auth-Username":["user1"],"Authorization":["Basic dXNlcjE6cGFzc3dvcmQx"],"User-Agent":["curl/7.86.0"],"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":["8a02d6ce-8be0-4e87-8ed8-cca7e239e986"],"X-Request-Start":["t=1676902237.111"]}} 518 ``` 519 520 NOTE: You can only override the global external configuration on a HTTPS virtual host. 521 522 ## Caveats 523 524 There are a few caveats to consider when deploying external 525 authorization: 526 527 1. Only one external authorization server can be configured on a virtual host 528 1. HTTP hosts are only supported with global external authorization. 529 1. External authorization cannot be used with the TLS fallback certificate (i.e. client SNI support is required) 530 531 [1]: https://github.com/projectcontour/contour-authserver 532 [2]: https://httpd.apache.org/docs/current/misc/password_encryptions.html 533 [3]: https://www.envoyproxy.io/docs/envoy/latest/api-v3/service/auth/v3/external_auth.proto 534 [4]: ../config/api/#projectcontour.io/v1alpha1.ExtensionService 535 [5]: ../config/api/#projectcontour.io/v1.HTTPProxy 536 [6]: https://cert-manager.io/ 537 [7]: https://httpd.apache.org/docs/current/programs/htpasswd.html 538 [8]: https://kubernetes-sigs.github.io/kustomize/