Skip to content

OIDC (OpenID Connect)

OpenID Connect (OIDC) enables token-based access to the boring-registry on top of OAuth 2.0 by delegating authentication to an external Identity Provider (IdP).
This document provides instructions for configuring OIDC to authorize to the boring-registry with teraform login or tofu login using the server-side login protocol

Any generic OIDC compatible provider such as Authelia, Authentik, Keycloak, Okta, or Zitadel should work.

Overview

The boring-registry needs to be configured to redirect the CLI client to the OIDC provider through remote service discovery and the server-side login protocol.
The client then retrieves an OIDC token and saves it locally.
This token is then passed along requests to the boring-registry, where the token is validated and consequently provides access to authorized clients.

Configuration

The following configuration options are available:

Flag Environment Variable Description
--auth-oidc-clientid BORING_REGISTRY_AUTH_OIDC_CLIENTID OIDC client identifier
--auth-oidc-issuer BORING_REGISTRY_AUTH_OIDC_ISSUER OIDC issuer URL
--auth-oidc-scopes BORING_REGISTRY_AUTH_OIDC_SCOPES List of OAuth2 scopes
--login-grant-types BORING_REGISTRY_LOGIN_GRANT_TYPES An array describing a set of OAuth 2.0 grant types (default [authz_code])
--login-ports BORING_REGISTRY_LOGIN_PORTS Inclusive range of TCP ports that the Terraform/OpenTofu CLI may use (default [10000,10010])

The remote service discovery resource can be verified after configuring OIDC with:

$ curl -s https://boring-registry.example.com:5601/.well-known/terraform.json | jq
{
  "login.v1": {
    "client": "boring-registry",
    "grant_types": [
      "authz_code"
    ],
    "authz": "https://idp.example.com/oauth2/boring-registry/v1/authorize",
    "token": "https://idp.example.com/oauth2/boring-registry/v1/token",
    "ports": [
      10000,
      10010
    ],
  },
  "modules.v1": "/v1/modules/",
  "providers.v1": "/v1/providers/"
}

Once a login was performed with terraform login boring-registry.example.com:5601 or tofu login boring-registry.example.com:5601, the OIDC token is stored locally.
With a command similar to jq -r '.credentials."boring-registry.example.com:5601".token' ~/.terraform.d/credentials.tfrc.json, the token can be inspected.

To aid debugging, the resulting JWT token can be inspected for example at jwt.io.

Authentik

As the readers are most-likely familiar with Terraform, an example configuration for Authentik is given using the Authentik provider.

data "authentik_flow" "default-authentication-flow" {
  slug = "default-authentication-flow"
}
data "authentik_flow" "default-authorization-flow" {
  slug = "default-provider-authorization-implicit-consent"
}

data "authentik_flow" "default-invalidation-flow" {
  slug = "default-provider-invalidation-flow"
}

data "authentik_certificate_key_pair" "generated" {
  name = "authentik Self-signed Certificate"
}

resource "authentik_provider_oauth2" "boring-registry" {
  name        = "boring-registry"
  client_id   = "boring-registry"
  client_type = "public"

  authentication_flow   = data.authentik_flow.default-authentication-flow.id
  authorization_flow    = data.authentik_flow.default-authorization-flow.id
  invalidation_flow     = data.authentik_flow.default-invalidation-flow.id
  access_token_validity = "hours=2"
  allowed_redirect_uris = [
    {
      matching_mode = "regex",
      url           = "http:\\/\\/localhost:\\d+\\/login",
    }
  ]
  signing_key = data.authentik_certificate_key_pair.generated.id
}

resource "authentik_application" "boring-registry" {
  name              = "boring-registry"
  slug              = "boring-registry"
  protocol_provider = authentik_provider_oauth2.boring-registry.id
}

The boring-registry is configured with the following flags.
Some flags were left out for clarity:

boring-registry server \
    --auth-oidc-clientid=boring-registry \
    --auth-oidc-issuer=https://authentik.example.com/application/o/boring-registry/

Okta

The boring-registry has provided unofficial and undocumented support for Okta for years, but the Okta-specific implementation is deprecated in favor of the generic OIDC implementation.
The following documentation shows how to integrate Okta with the generic OIDC implementation.

As the readers are most-likely familiar with Terraform, an example configuration for Okta is given using the Okta provider.

resource "okta_app_oauth" "boring-registry" {
  type           = "browser"
  label          = "boring-registry"
  consent_method = "TRUSTED"
  redirect_uris = [
    for port in range(10000, 10011) : "http://localhost:${port}/login"
  ]
  grant_types = [
    "authorization_code",
  ]
  response_types = [
    "code",
  ]
  token_endpoint_auth_method = "none"
}

resource "okta_auth_server" "boring-registry" {
  audiences   = [okta_app_oauth.boring-registry.client_id]
  description = "Auth server for boring-registry"
  name        = "boring-registry"
  issuer_mode = "ORG_URL"
}

resource "okta_auth_server_policy" "all" {
  auth_server_id   = okta_auth_server.boring-registry.id
  name             = "all-access"
  priority         = 1
  description      = "Provide everyone access" # This is just an example!
  client_whitelist = [okta_app_oauth.boring-registry.id]
}

resource "okta_auth_server_policy_rule" "all_access" {
  auth_server_id                = okta_auth_server.boring-registry.id
  policy_id                     = okta_auth_server_policy.all.id
  name                          = "all-access"
  priority                      = 1
  access_token_lifetime_minutes = 120
  grant_type_whitelist          = ["authorization_code"]
  group_whitelist               = ["EVERYONE"]
  scope_whitelist               = ["*"]
}

output "issuer" {
  value = okta_auth_server.boring-registry.issuer
}

output "client_id" {
  value = okta_app_oauth.boring-registry.client_id
}

The boring-registry is then configured with the following flags.
Some flags were left out for clarity:

boring-registry server  \
    --auth-oidc-clientid=0oamy5gronmOjHTiR5d7 \
    --auth-oidc-issuer=https://dev-05229648.okta.com/oauth2/ausmy5p653yTgxZqb5d7 \
    --auth-oidc-scopes=openid

The --auth-oidc-scopes=openid flag is provided, as Okta otherwise complains that no scopes were passed in the initial request.