Secure Serverless with OpenFaaS
Tags: kubernetes, serverless, devops
Published: 2021-05-12 18:38:58

During my career as a professional in the Software Industry, I've witnessed the rise of DevOps. DevOps is the practice of merging your development team (programmers) with your operations team (think the server admins and net-ops guys).

While learning about servers has its ups and downs. You get to celebrate the triumphs of finally getting your hosting platform up and running while dealing with the frustration that is the nuance of configuration files. It's probably safe to say that when you're in college or boot-camp learning to become a developer, your ideal day-in-the-life is not building up your development platform and spending days delving through configuration files and ACL permissions while hoping your service comes up in the end.

## Enter Serverless

Serverless is a buzz word these days that often gets misunderstood. Does serverless mean that there are no servers? Quite the contrary. There are servers in a serverless platform. The main difference is that there isn't a server that you have to actively manage. Any services deployed on a serverless platform are scaled up and executed when they're requested and scaled down once the execution is completed and the service is no longer needed.

The biggest payoff with this architecture from a development standpoint is that you no longer need to be concerned about provisioning infrastructure. Instead, you specify an event type (i.e. HTTP request, Message Bus, CRON trigger), and a handler function (your code) and the platform builds out the application in a container and handles the execution for you.

## Where Does OpenFaaS fit into this?

[OpenFaas](https://www.openfaas.com/) is a service which allows you to deploy serverless functions on a self-hosted cloud environment (i.e. Kubernetes). This tutorial will cover how to deploy OpenFaaS to Kubernetes and configure the portal with authentication.

> ⚠ The authentication method utilized in this tutorial is not officially supported by OpenFaas. I'm using the Open Source community edition. In order to support OIDC or OAUTH, you need an enterprise license.

## Setting up OpenFaaS

OpenFaaS provides a [helm chart](https://github.com/openfaas/faas-netes/tree/master/chart/openfaas) which can be used for setting up an OpenFaaS Kubernetes deployment quickly. This tutorial will utilize a combination of the OpenFaaS helm chart and the [OAUTH2 Proxy](https://github.com/k8s-at-home/charts/tree/master/charts/stable/oauth2-proxy) chart to set up authentication with Keycloak

## OAuth2 Proxy Set-Up

Assuming you have a Keycloak server set up and registered a new client to your Keycloak realm, you will need the client-id and the client-secret in order to set up the OAuth proxy.

The following Ansible task can aid in setting up a Keycloak client.

```yaml
    - name: Create Keycloak client
      community.general.keycloak_client:
        state: present
        auth_client_id: admin-cli
        auth_keycloak_url: "{{ keycloak_server }}/auth"
        auth_username: "{{ keycloak_user }}"
        auth_password: "{{ keycloak_password }}"
        auth_realm: master
        name: Openfaas Portal
        client_id: openfaas
        client_authenticator_type: client-secret
        secret: "{{ client_secret }}"
        realm: hausnet
        protocol_mappers:
          - config:
              included.client.audience: "openfaas-portal"
              id.token.claim: "false"
              access.token.claim: "true"
            name: openfaas-portal-mapper
            protocol: openid-connect
            protocolMapper: oidc-audience-mapper
          - name: "openfaas-group-mapper"
            protocol: "openid-connect"
            protocolMapper: "oidc-group-membership-mapper"
            config: 
              id.token.claim: "true"
              access.token.claim: "true"
              claim.name: "groups"
              userinfo.token.claim: "true"
        redirect_uris:
          - "{{ openfaas_endpoint }}/*"
        web_origins:
          - "*"
        validate_certs: no
```

> ℹ The variables in use by the ansible role
> - `openfaas_endpoint` the URL which OpenFaaS will be listening
> - `keycloak_server` the URL where Keycloak will be listening
> - `keycloak_user` the username used to authenticate with Keycloak in order to create the keycloak client
> - `keycloak_password` the password of the user used to authenticate with Keycloak in order to create the Keycloak client
> - `client_secret` the secret used for authentication. This value will be set when the client is created in Keycloak

The following chart demonstrates how to set up the OAuth proxy using Terraform.

```tf
resource "helm_release" "rel_oauth2_proxy" {
  repository = "https://k8s-at-home.com/charts/"
  chart = "oauth2-proxy"
  name = "auth"
  namespace = "openfaas"

  values = [ 
    <<YAML
    extraArgs:
      provider: keycloak
      email-domain: "*"
      scope: "openid profile email"
      login-url: https://${vars.keycloak}/auth/realms/${vars.realm}/protocol/openid-connect/auth
      redeem-url: https://${vars.keycloak}/auth/realms/${vars.realm}/protocol/openid-connect/token
      validate-url: https://${vars.keycloak}/auth/realms/${vars.realm}/protocol/openid-connect/userinfo
      keycloak-group: /admin
      ssl-insecure-skip-verify: true
    ingress:
      enabled: true
      hosts:
        - ${vars.openfaas_domain}
      path: /oauth2
      annotations:
        kubernetes.io/ingress.class: nginx
        cert-manager.io/cluster-issuer: cluster-issuer
        nginx.ingress.kubernetes.io/ssl-redirect: 'true'
      tls:
        - secretName: oauth-proxy-tls
          hosts:
            - ${vars.openfaas_domain}
    extraEnv:
      - name: OAUTH2_PROXY_CLIENT_ID
        value: ${vars.client_id}
      - name: OAUTH2_PROXY_CLIENT_SECRET
        value: ${vars.client_secret}
      - name: OAUTH2_PROXY_COOKIE_SECRET
        value: ${vars.encryption_key}
    YAML
   ]

  set {
    name = "proxyVarsAsSecrets"
    value = "false"
  }
}
```

> ℹ `client_id`, `client_secret`, `keycloak`, and `realm` are variables which are provided using [Terraform variables](https://www.terraform.io/docs/language/values/variables.html)

## Deploying OpenFaaS

With the OAuth2 Proxy deployed, it's now time to deploy OpenFaas

```tf
resource "helm_release" "rel_openfaas" {
  repository = "https://openfaas.github.io/faas-netes/"
  name = "openfaas"
  chart="openfaas"
  namespace = "openfaas"

  set {
      name = "functionNamespace"
      value = "openfaas-fn"
  }

  set {
      name = "generateBasicAuth"
      value = "false"
  }

  set {
      name = "basic_auth"
      value = "false"
  }

  set {
      name = "serviceType"
      value = "ClusterIP"
  }
}
```

> ⚠ OpenFaaS has an [official method of handling OIDC/OAuth authentication](https://docs.openfaas.com/reference/authentication/#single-sign-on-sso-and-oidc); however, that's behind a pay-wall. Here we're turning off authentication since it's being outsourced to our OAuth2 proxy created in the previous step.

We set up the Kubernetes Ingress to expose access to OpenFaas through our OAuth2 proxy. Below is an example on how to set up the ingress using Kubernetes manifests:

```yaml
apiVersion: networking.k8s.io/v1
kind: Ingress 
metadata:
  name: openfaas-gateway
  namespace: openfaas
  annotations:
    kubernetes.io/ingress-class: nginx
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
    nginx.ingress.kubernetes.io/auth-url: "https://$host/oauth2/auth"
    nginx.ingress.kubernetes.io/auth-signin: "https://$host/oauth2/start?rd=$escaped_request_uri"
    cert-manager.io/cluster-issuer: cluster-issuer
    nginx.ingress.kubernetes.io/upstream-vhost: $service_name.$namespace.svc.cluster.local:8080
    nginx.ingress.kubernetes.io/configuration-snippet: |
      proxy_set_header Origin "";
      proxy_hide_header l5d-remote-ip;
      proxy_hide_header l5d-server-id;
spec:
  rules:
    - host: "functions.haus.net"
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: gateway
                port:
                  number: 8080
  tls:
    - hosts:
      - "functions.haus.net"
      secretName: openfaas-tls-cert
```

Once the deployment completes, OpenFaaS should be available from the ingress endpoint. In this example, I set the endpoint to `https://functions.haus.net`.

![auth-portal](https://lh3.googleusercontent.com/f84wQjIBvAOhlyPiaqt9DtOkJBIv6Vw373FR8V-ZUuBI3AjvfPSQ63b-Ayo8LKNDfVTkW9rmLNO1M8i8JtCy0vrCkcBB0CfUYsi6uF17H5AbzSe8k1ohUNidBAJ-bd1Z6gzflc7xang=w2400)

![openfaas-dashboard](https://lh3.googleusercontent.com/trSodhivLFNn4OtJS5KXoAYb_bfc-ji11Yn90GIt-KnccLskPRNFmgXP2KjW03SmmPJNW1c3dog170wFMj4TPto2kQ9sLpuqLb-_knpEM5OnxNarxwSvcmxjRJXra5OVvTeac7O3mm8=w2400)