Keyless deployment to OpenFaaS with OIDC and GitHub Actions

Alex Ellis

We're announcing a new OIDC proxy for OpenFaaS for keyless deployments from GitHub Actions.

In 2021, GitHub released OpenID Connect (OIDC) support for CI jobs running under GitHub Actions. This was a huge step forward for security meaning that any GitHub Action could mint an OIDC token and use it to securely federate into another system without having to store long-lived credentials in the repository.

I wrote a prototype for OpenFaaS shortly after the announcement and a deep dive explaining how it works. I used inlets to set up a HTTPS tunnel, and send the token to my machine for inspection. Various individuals and technical teams have used my content as a reference guide when working with GitHub Actions and OIDC.

See the article: Deploy without credentials with GitHub Actions and OIDC

Since then, custom actions for GCP, AWS and Azure have been created which allow an OIDC token from a GitHub Action to be exchanged for a short-lived access token for their API - meaning you can manage cloud resources securely. For example, see: Configuring OpenID Connect in Amazon Web Services - we have actuated customers who use this approach to deploy to ECR from their self-hosted runners without having to store long-lived credentials in their repositories.

Why OIDC is important for OpenFaaS customers

Before we talk about the new OIDC proxy for OpenFaaS, I should say that OpenFaaS Enterprise also has an IAM feature which includes OIDC support for the CLI, dashboard and API. It supports any trusted OIDC provider, not just GitHub Actions. Rather than acting as a proxy, it actually implements a full fine-grained authorization and permissions policy language that resembles the one you'll be used to from AWS.

However, not everyone needs this level of granularity.

Shaked, the CTO of Kubiya.ai is an OpenFaaS & inlets customer. His team at Kubiya is building a conversational AI for DevOps - if you're ever tried ChatGPT, imagine that it was hooked up to your infrastructure and had superpowers. On a recent call, he told me that their team now has 30 different repositories which deploy OpenFaaS functions to their various AWS EKS clusters. That means that a secret has to be maintained at the organisation level and then consumed via faas-cli login in each job.

It gets a little worse for them - because different branches deploy to different OpenFaaS gateways and to different EKS clusters.

In addition to managing various credentials for each cluster they add - they were uncomfortable with exposing all of their functions on the Internet.

So today the team working on actuated is releasing a new OIDC proxy which can be deployed to any OpenFaaS cluster to avoid the need to manage and share long-lived credentials with GitHub.

Conceptual design of the OIDC proxy for OpenFaaS

Conceptual design of the OIDC proxy for OpenFaaS

About the OIDC proxy for OpenFaaS

  • It can be deployed via Helm
  • Only allows access to a given set of GitHub organisations
  • Uses a standard Ingress record
  • It only accepts OIDC tokens signed from GitHub Actions
  • It only allows requests to the /system endpoints of the OpenFaaS REST API - keeping your functions safe

Best of all, unlike OpenFaaS Enterprise, it's free for all actuated customers - whether they're using OpenFaaS CE, Standard or Enterprise.

Here's what Shaked had to say about the new proxy:

That's great - thank you! Looking forward to it as it will simplify our usage of the openfaas templates and will speed up our development process Shaked, CTO, Kubiya.ai

How to deploy the proxy for OpenFaaS

Here's what you need to do:

  • Install OpenFaaS on a Kubernetes cluster
  • Create a new DNS A or CNAME record for the Ingress to the proxy i.e. oidc-proxy.example.com
  • Install the OIDC proxy using Helm
  • Update your GitHub Actions workflow to use the OIDC token

My cluster is not publicly exposed on the Internet, so I'm using an inlets tunnel to expose the OIDC Proxy from my local KinD cluster. I'll be using the domain minty.exit.o6s.io but you'd create something more like oidc-proxy.example.com for your own cluster.

First Set up your values.yaml for Helm:

# The public URL to access the proxy
publicURL: https://oidc-proxy.example.com

# Comma separated list of repository owners for which short-lived OIDC tokens are authorized.
# For example: alexellis,self-actuated
repositoryOwners: 'alexellis,self-actuated'
ingress:
    host: oidc-proxy.example.com
    issuer: letsencrypt-prod

The chart will create an Ingress record for you using an existing issuer. If you want to use something else like Inlets or Istio to expose the OIDC proxy, then simply set enabled: false under the ingress: section.

Create a secret for the actuated subscription key:

kubectl create secret generic actuated-license \
  -n openfaas \
  --from-file=actuated-license=$HOME/.actuated/LICENSE

Then run:

helm repo add actuated https://self-actuated.github.io/charts/
helm repo update

helm upgrade --install actuated/openfaas-oidc-proxy \
    -f ./values.yaml

For the full setup - see the README for the Helm chart

You can now go to one of your repositories and update the workflow to authenticate to the REST API via an OIDC token.

In order to get an OIDC token within a build, add the id_token: write permission to the permissions list.

name: keyless_deploy

on:
  workflow_dispatch:
  push:
    branches:
    - '*'
jobs:
  keyless_deploy:
    permissions:
      contents: 'read'
      id-token: 'write'

Then set runs-on to actuated to use your faster actuated servers:

-   runs-on: ubuntu-latest
+   runs-on: actuated

Then in the workflow, install the OpenFaaS CLI:

steps:
    - uses: actions/checkout@master
    with:
        fetch-depth: 1
    - name: Install faas-cli
    run: curl -sLS https://cli.openfaas.com | sudo sh

Then get a token:

- name: Get token and use the CLI
    run: |
        OPENFAAS_URL=https://minty.exit.o6s.io
        OIDC_TOKEN=$(curl -sLS "${ACTIONS_ID_TOKEN_REQUEST_URL}&audience=$OPENFAAS_URL" -H "User-Agent: actions/oidc-client" -H "Authorization: Bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN")
        JWT=$(echo $OIDC_TOKEN | jq -j '.value')

Finally, use the token whenever you need it by passing in the --token flag to any of the faas-cli commands:

faas-cli list -n openfaas-fn --token "$JWT"
faas-cli ns --token "$JWT"
faas-cli store deploy printer --name p1 --token "$JWT"

faas-cli describe p1 --token "$JWT"

Since we have a lot of experience with GitHub Actions, we decided to make the above simpler by creating a custom Composite Action. If you check out the code for self-actuated/openfaas-oidc you'll see that it obtains a token, then writes it into an openfaaas config file, so that the --token flag isn't required.

Here's how it changes:

- uses: self-actuated/openfaas-oidc@v1
  with: 
    gateway: https://minty.exit.o6s.io
- name: Check OpenFaaS version
  run: |
    OPENFAAS_CONFIG=$HOME/.openfaas/
    faas-cli version

Here's the complete example:

name: federate

on:
  workflow_dispatch:
  push:
    branches:
    - '*'
jobs:
  auth:
    # Add "id-token" with the intended permissions.
    permissions:
      contents: 'read'
      id-token: 'write'

    runs-on: actuated
    steps:
      - uses: actions/checkout@master
        with:
          fetch-depth: 1
      - name: Install faas-cli
        run: curl -sLS https://cli.openfaas.com | sudo sh
      - uses: self-actuated/openfaas-oidc@v1
        with:
          gateway: https://minty.exit.o6s.io

      - name: Get token and use the CLI
        run: |
          export OPENFAAS_URL=https://minty.exit.o6s.io
          faas-cli store deploy env --name http-header-printer
          faas-cli list

How can we be sure that our functions cannot be invoked over the proxy?

Just add an extra line to test it out:

      - name: Get token and use the CLI
        run: |
          export OPENFAAS_URL=https://minty.exit.o6s.io
          faas-cli store deploy env --name http-header-printer
          sleep 5

          echo | faas-cli invoke http-header-printer

A failed invocation over the proxy

A failed invocation over the proxy

Best of all, now that you're using OIDC, you can now go and delete any of those long lived basic auth credentials from your secrets!

Wrapping up

The new OIDC proxy for OpenFaaS is available for all actuated customers and works with OpenFaaS CE, Standard and Enterprise. You can use it on as many clusters as you like, whilst you have an active subscription for actuated at no extra cost.

In a short period of time, you can set up the Helm chart for the OIDC proxy and no longer have to worry about storing various secrets in GitHub Actions for all your clusters, simply obtain a token and use it to deploy to any cluster - securely. There's no risk that your functions will be exposed on the Internet, because the OIDC proxy only works for the /system endpoints of the OpenFaaS REST API.

An alternative for those who need it

OpenFaaS Enterprise has its own OIDC integration with much more fine-grained permissions implemented. It means that team members using the CLI, Dashboard or API do not need to memorise or share basic authentication credentials with each other, or worry about getting the right password for the right cluster.

An OpenFaaS Enterprise policy can restrict all the way down to read/write permissions on a number of namespaces, and also integrates with OIDC.

See an example: