Http Passthrough Auth

When using Gloo Gateway’s external authentication server, it may be convenient to authenticate requests with your own HTTP server. By creating requests from the external authentication server to your own authentication component, Gloo Gateway can use your authentication server to authenticate requests.

Setup

This guide assumes that you have deployed Gloo to the gloo-system namespace and that the glooctl command line utility is installed on your machine. glooctl provides several convenient functions to view, manipulate, and debug Gloo resources; in particular, it is worth mentioning the following command, which we will use each time we need to retrieve the URL of the Gloo Gateway that is running inside your cluster:

glooctl proxy url

Let’s start by creating a Static Upstream that routes to a website; we will send requests to it during this tutorial.


apiVersion: gloo.solo.io/v1
kind: Upstream
metadata:
  name: json-upstream
  namespace: gloo-system
spec:
  static:
    hosts:
      - addr: jsonplaceholder.typicode.com
        port: 80

glooctl create upstream static --static-hosts jsonplaceholder.typicode.com:80 --name json-upstream

Creating an authentication service

In this example, we will be deploying a Http authentication service.

kubectl apply -f - <<EOF
apiVersion: apps/v1
kind: Deployment
metadata:
  name: extauth-httpservice
spec:
  selector:
    matchLabels:
      app: http-extauth
  replicas: 1
  template:
    metadata:
      labels:
        app: http-extauth
    spec:
      containers:
        - name: http-extauth
          image: gcr.io/solo-public/passthrough-http-service-example
          imagePullPolicy: IfNotPresent
          ports:
            - containerPort: 9001
EOF

The source code for the Http service can be found in the Gloo Gateway repository here.

Once we create the authentication service, we also want to apply the following Service to assign it a static cluster IP.

kubectl apply -f - <<EOF
apiVersion: v1
kind: Service
metadata:
  name: example-http-auth-service
  labels:
      app: http-extauth
spec:
  ports:
  - port: 9001
    protocol: TCP
  selector:
      app: http-extauth
EOF

Creating a Virtual Service

Now let’s configure Gloo Gateway to route requests to the upstream we just created. To do that, we define a simple Virtual Service to match all requests that:

Apply the following virtual service:

apiVersion: gateway.solo.io/v1
kind: VirtualService
metadata:
  name: auth-tutorial
  namespace: gloo-system
spec:
  virtualHost:
    domains:
      - 'foo'
    routes:
      - matchers:
        - prefix: /
        routeAction:
          single:
            upstream:
              name: json-upstream
              namespace: gloo-system
        options:
          autoHostRewrite: true

Let’s send a request that matches the above route to the gateway proxy and make sure it works:

curl -H "Host: foo" $(glooctl proxy url)/posts/1

The above command should produce the following output:

{
  "userId": 1,
  "id": 1,
  "title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit",
  "body": "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto"
}

If you are getting a connection error, make sure you are port-forwarding the glooctl proxy url port to port 8080.

Securing the Virtual Service

As we just saw, we were able to reach the upstream without having to provide any credentials. This is because by default Gloo Gateway allows any request on routes that do not specify authentication configuration. Let’s change this behavior. We will update the Virtual Service so that all requests will be authenticated by our own Http auth service.

kubectl apply -f - <<EOF
apiVersion: enterprise.gloo.solo.io/v1
kind: AuthConfig
metadata:
  name: passthrough-auth
  namespace: gloo-system
spec:
  configs:
  - passThroughAuth:
      http:
        # Url of the http auth server to use for auth
        url: http://example-http-auth-service.default.svc.cluster.local:9001
        # Set a connection timeout to external service, default is 5 seconds
        connectionTimeout: 3s
EOF

Passthrough services also allow for failing “open” through the failureModeAllow field. By setting this field to true, the auth service responds with an OK if either your server returns a 5XX response or the request times out.

Once the AuthConfig has been created, we can use it to secure our Virtual Service:

kubectl apply -f - <<EOF
apiVersion: gateway.solo.io/v1
kind: VirtualService
metadata:
  name: auth-tutorial
  namespace: gloo-system
spec:
  virtualHost:
    domains:
      - 'foo'
    routes:
      - matchers:
        - prefix: /
        routeAction:
          single:
            upstream:
              name: json-upstream
              namespace: gloo-system
        options:
          autoHostRewrite: true      
    options:
      extauth:
        configRef:
          name: passthrough-auth
          namespace: gloo-system
EOF

In the above example we have added the configuration to the Virtual Host. Each route belonging to a Virtual Host will inherit its AuthConfig, unless it overwrites or disables it.

Metrics

For more information on how Gloo Gateway handles observability and metrics, view our observability introduction.

Logging

If Gloo Gateway is running on kubernetes, the extauth server logs can be viewed with:

kubectl logs -n gloo-system deploy/extauth -f

If the auth config has been received successfully, you should see the log line:

"logger":"extauth","caller":"runner/run.go:179","msg":"got new config"

Testing the secured Virtual Service

The virtual service that we have created should now be secured using our external authentication service. To test this, we can try our original command, and the request should not be allowed through because of missing authentication.

curl -v -H "Host: foo" $(glooctl proxy url)/posts/1

In fact, if we check the logs of our sample http auth service, we see the following message:

Did not reach the right path, use the /auth to authenticate requests! Make sure to include the header 'authorization: authorize me' too.

To comply with what our sample auth service needs, we can modify the auth service as follows:

kubectl apply -f - <<EOF
apiVersion: enterprise.gloo.solo.io/v1
kind: AuthConfig
metadata:
  name: passthrough-auth
  namespace: gloo-system
spec:
  configs:
  - passThroughAuth:
      http:
        # Url of the http auth server to use for auth
        url: http://example-http-auth-service.default.svc.cluster.local:9001/auth
        # Set a connection timeout to external service, default is 5 seconds
        connectionTimeout: 3s
        request:
          allowedHeaders:
            - authorization
EOF

The sample Http authentication service has been implemented such that any request with the header authorization: authorize me to path /auth will be authorized. We configured our passthrough auth to use the /auth path with the http auth server and to passthrough Authorization header. We can now add this header to our curl request as follows:

curl -H "Host: foo" -H "authorization: authorize me" $(glooctl proxy url)/posts/1

The request should now be authorized!

Http Passthrough Auth Config Options

For more information about configuration options, see the API docs.

apiVersion: enterprise.gloo.solo.io/v1
kind: AuthConfig
metadata:
  name: passthrough-auth
  namespace: gloo-system
spec:
  configs:
  - passThroughAuth:
      http:
        # Url of the http auth server to use for auth
        # This can include path, and can also use https. 
        # In order to use a https passthrough server, provide the cert in the HTTPS_PASSTHROUGH_CA_CERT environment variable to the ext-auth-service pod as a base64-encoded string.
        url: http://example-http-auth-service.default.svc.cluster.local:9001
        # Set a connection timeout to external service, default is 5 seconds
        connectionTimeout: 3s
        # These options will modify the request going to the passthrough auth server
        request:
          # These headers will be copied from the downstream request to the auth server request
          allowedHeaders: string[]
          # These headers will be added to the auth server request, and will overwrite any same headers from `allowedHeaders`.
          headersToAdd: map<string, string>
          
          # The following three "pass-through" options will use the HTTP body to passthrough relevant request components to the auth server.
          # When any of these are set, the body will be a json with the following shape, where the keys will only be included if the appropriate
          # passthrough config is set to true:
          # {
          #  "state": object
          #   "filterMetadata": object
          #   "body": string
          # }
          # If all of the passthrough options are unset or false, the body of the http auth request will be empty.
          
          # Setting this to true will include the ext-auth "state" in the request body
          passThroughState: bool
          # Setting this to true will include the envoy "filter metadata" in the request body.
          passThroughFilterMetadata: bool
          # Setting this to true will include the original http request's body in the body of the auth request.
          # In order for the body to be passed through to the auth service, the settings.extauth.requestBody must be set in the Gloo Gateway Settings CRD so that
          # the request body is buffered and sent to the ext-auth service.
          passThroughBody: bool
        # These options will modify the original request to the upstream if the auth request is authorized
        # or the denied response back to the downstream client if the auth request is not authorized
        response: 
          # This is a list of headers that will be included from the authorization response and sent along with the original upstream request headers.
          # If any of these headers already exist in the original request, the auth server headers will be appended to the original headers
          allowedUpstreamHeaders: string[]
          # This is a list of headers that will be included from the authorization response and sent back to the client in the http resposne when the auth request is denied.
          allowedClientHeadersOnDenied: string[]
          # Setting this to true will allow the http auth server to modify the state by sending back a state object in the http response.
          # The state object should have the JSON shape:
          # { "state": map[string]object }.
          # If the state fails to be set for any reason, the auth request will be denied and an error will be logged in the ext-auth-service pod. 
          readStateFromResponse: bool
          # When set, authorization response headers that have a header in this list are added to the original client request and sent to the upstream
          # when the auth request is successful. These overwrite any request headers that already exist.
          # If this and allowed_upstream_headers are empty, by default, no authorization response headers are added to the upstream request.
          # Header names cannot be included in both allowed_upstream_headers and allowed_upstream_headers_to_overwrite.
          allowedClientHeadersOnDenied: string[]