Caddy with Google OAuth2

Caddy is a reverse proxy, used to serve content to the internet from within multiple VMs and containers in my lab. But what if the webpage is not secure? Enter caddy-security.

Caddy with Google OAuth2
Photo by Ed Hardie / Unsplash

Caddy is a reverse proxy, used to serve content to the internet from within multiple VMs and containers in my lab. This website is being hosted through Caddy.

But what if the webpage is not secure? Is there a way to provide SSO across all sites? Enter caddy-security - a plugin for Caddy that allows integration with a variety of OAuth2 providers. For our example we'll be concentrating on Google's authentication provider.


Installing xcaddy

Caddy does not come prebuilt with caddy-security by default. I opted to build Caddy from source and to include caddy-security at build time. Other options are available - Docker images and downloading prebuilt binaries. The latter of which did not work for me.

First of all, download and install Go. I installed version 1.18.1 - some of the dependencies of the caddy-security build require later versions of Go. The latest available from apt preceded this requirement, so I downloaded the binary manually.

Once installed, run the following:

sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/xcaddy/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-xcaddy-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/xcaddy/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-xcaddy.list
sudo apt update
sudo apt install xcaddy

Building Caddy

Simply run the following to build the Caddy binary with caddy-security support

xcaddy build --with github.com/greenpau/caddy-security

Setting up Google Client ID & Secret

Some set up is required on the Google side in order for the authentication handshake to actually work.

You will first need to set up an OAuth consent screen in Google Cloud: https://console.cloud.google.com/apis/credentials/consent

Followed by a credentials entry:

This will produce a client ID and client secret required to configure caddy-security.

Updates to the Caddyfile

The following is an cut down version of my Caddyfile which provides OAuth2 security to this website

{
  order authenticate before respond
  order authorize before basicauth

  security {
    oauth identity provider google {
      realm google
      driver google
      client_id {env.GOOGLE_CLIENT_ID}.apps.googleusercontent.com
      client_secret {env.GOOGLE_CLIENT_SECRET}
      scopes openid email profile
    }

    authentication portal mfportal {
      crypto default token lifetime 3600
      crypto key sign-verify {env.JWT_SHARED_KEY}
      enable identity provider google
      cookie domain mayfield.site

      transform user {
        match realm google
        match email [email protected]
        action add role authp/user
      }

      transform user {
        match realm google
        match email [email protected]
        action add role authp/admin
      }
    }

    authorization policy mfpolicy {
      set auth url https://auth.mayfield.site/oauth2/google/authorization-code-callback
      crypto key verify {env.JWT_SHARED_KEY}
      allow roles authp/admin authp/user
      validate bearer header
      inject headers with claims
    }
  }
}

auth.mayfield.site {
  authenticate with mfportal
}

blog.mayfield.site {
  authorize with mfpolicy
  header x-forwarded-proto https
  reverse_proxy 192.168.1.142:2368
}

Note the different roles that can be added to the user, depending on the email address configured. I'm an admin and Shreena is a user.

Requests hit Caddy, which informs the need to authorize using mfpolicy. This authorization policy depends on an authentication hop via mfportal, which in turn uses the Google identity provider. Once Caddy is happy we have successfully authenticated and have been authorized, it will forward the request on to the server listed on 192.168.1.142.

Caddy Environment

In the Caddyfile you will notice a few values injected via environment variables. As I'm running the service at startup with systemctl on Ubuntu, I ran the following command to edit the environment variables.

systemctl edit caddy
### Editing /etc/systemd/system/caddy.service.d/override.conf
### Anything between here and the comment below will become the new contents of the file

[Service]
Environment="JWT_SHARED_KEY=mf"
Environment="GOOGLE_CLIENT_ID=blahblahblah"
Environment="GOOGLE_CLIENT_SECRET=shhshhshh"

Restarting Caddy via systemctl will pick up these changes and your site should now pass through an authn/z phase.