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/