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/