🫖

keycloak OpenID Connect を使って Gitea にシングルサインオンする

2024/04/09に公開

Gitea では OpenID Connect (OIDC) を使ったユーザ認証に対応しているので、 keycloak などの OIDC provider 側にあるユーザーの認証情報でシングルサインオンが実現できます。Gitea の webUI から設定できますが、なぜか gitea のドキュメントではこれに関する記述がありません。

https://docs.gitea.com/

github ではこちらの issue で指摘されていますが現時点で未対応のようです。OAuth2 provider のページ はありますが、見た感じ gitea を oauth provider として使う際の設定であり上記とは別物になっています。

keycloak について調べている中で gitea との OIDC 連携を試していたのですが、実際やってみるとなかなかまとまった情報が見つからず設定に苦戦したので、メモ書きとして keycloak と gitea の OIDC 連携の方法についてまとめておきます。

セットアップ

検証に使用する keycloak, gitea は簡単のため docker compose で構築します。また、いずれも自己署名証明書を使用した HTTPS 構成として互いにドメイン名で通信します。この辺は特に動作とは関係ない(はず)なので、通常の http でも問題ないと思います。

keycloak

.
├── build
│   └── Dockerfile
├── certs
│   ├── keycloak.centre.com.crt
│   └── keycloak.centre.com.key
└── docker-compose.yml

docker で keycloak を動かす際の設定はドキュメントに記載があるので、こちらを参考にイメージを作成します。
https://www.keycloak.org/server/containers

Dockerfile
FROM quay.io/keycloak/keycloak:latest as builder

# Enable health and metrics support
ENV KC_HEALTH_ENABLED=true
ENV KC_METRICS_ENABLED=true

# Configure a database vendor
ENV KC_DB=postgres

WORKDIR /opt/keycloak
# for demonstration purposes only, please make sure to use proper certificates in production instead
RUN keytool -genkeypair -storepass password -storetype PKCS12 -keyalg RSA -keysize 2048 -dname "CN=keycloak.centre.com" -alias server -ext "SAN:c=DNS:keycloak.centre.com,IP:192.168.3.181" -keystore conf/server.keystore
RUN /opt/keycloak/bin/kc.sh build

FROM quay.io/keycloak/keycloak:latest
COPY --from=builder /opt/keycloak/ /opt/keycloak/

# change these values to point to a running keycloak instance
ENV KC_DB=postgres
ENV KC_DB_URL=jdbc:postgresql://postgres:5432/keycloak
ENV KC_DB_USERNAME=keycloak
ENV KC_DB_PASSWORD=keycloak
ENTRYPOINT ["/opt/keycloak/bin/kc.sh"]
docker-compose.yml
services:
  postgres:
    image: postgres:15.1
    container_name: keycloak-postgres
    environment:
      POSTGRES_DB: keycloak
      POSTGRES_USER: keycloak
      POSTGRES_PASSWORD: keycloak
    ports:
      - 5432:5432
  keycloak:
    image: keycloak-custom
    container_name: keycloak
    environment:
      KC_DB: postgres
      KC_DB_PASSWORD: keycloak
      KC_DB_USERNAME: keycloak
      KC_DB_URL_PORT: 5432
      KC_DB_URL: jdbc:postgresql://postgres:5432/keycloak
      KC_HOSTNAME_URL: https://keycloak.centre.com:8443/
      KC_ADMIN_HOSTNAME_URL: https://keycloak.centre.com:8443/
      KEYCLOAK_ADMIN: admin
      KEYCLOAK_ADMIN_PASSWORD: admin
      TZ: "Asia/Tokyo"
    ports:
      - 8080:8080
      - 8443:8443
    extra_hosts:
      - "gitea.centre.com:192.168.3.180"
    command:
      - "--verbose"
      - "start"
      - --https-certificate-file=/opt/certs/keycloak.centre.com.crt
      - --https-certificate-key-file=/opt/certs/keycloak.centre.com.key
      - --hostname-strict-https=true
    depends_on:
      - postgres
    volumes:
      - type: bind
        source: ./certs
        target: /opt/certs

Gitea

.
├── app.ini
├── build
│   ├── Dockerfile
│   └── ca.crt
├── certs
│   ├── gitea.centre.com.crt
│   └── gitea.centre.com.key
├── docker-compose.yml
└── templates
    └── body_outer_pre.tmpl
docker-compose.yml
services:
  gitea:
    image: gitea-custom
    container_name: gitea
    environment:
      - USER_UID=1000
      - USER_GID=1000
      - GITEA__database__DB_TYPE=postgres
      - GITEA__database__HOST=gitea-postgres:5432
      - GITEA__database__NAME=gitea
      - GITEA__database__USER=gitea
      - GITEA__database__PASSWD=gitea
    restart: always
    volumes:
      - ./gitea_data:/data
      - /etc/timezone:/etc/timezone:ro
      - /etc/localtime:/etc/localtime:ro
      - type: bind
        source: app.ini
        target: /data/gitea/conf/app.ini
      - type: bind
        source: certs/gitea.centre.com.crt
        target: /data/gitea/gitea.centre.com.crt
      - type: bind
        source: certs/gitea.centre.com.key
        target: /data/gitea/gitea.centre.com.key
      - type: bind
        source: templates/body_outer_pre.tmpl
        target: /data/gitea/templates/custom/body_outer_pre.tmpl
    ports:
      - "3000:3000"
      - "222:22"
    depends_on:
      - postgres
    extra_hosts:
      - "keycloak.centre.com:192.168.3.181"
  postgres:
    image: postgres
    container_name: gitea-postgres
    restart: always
    environment:
      - POSTGRES_USER=gitea
      - POSTGRES_PASSWORD=gitea
      - POSTGRES_DB=gitea
    volumes:
      - ./postgres:/var/lib/postgresql/data

app.ini では gitea 全般の設定を記述します。
詳細は https://docs.gitea.com/administration/config-cheat-sheet を参照。

app.ini
APP_NAME = Gitea: Git with a cup of tea
RUN_MODE = prod
RUN_USER = git

[repository]
ROOT = /data/git/repositories

[repository.local]
LOCAL_COPY_PATH = /data/gitea/tmp/local-repo

[repository.upload]
TEMP_PATH = /data/gitea/uploads

[server]
APP_DATA_PATH    = /data/gitea
DOMAIN           = gitea.centre.com
SSH_DOMAIN       = gitea.centre.com
PROTOCOL         = https
HTTP_PORT        = 3000
ROOT_URL         = https://gitea.centre.com:3000/
DISABLE_SSH      = false
SSH_PORT         = 22
SSH_LISTEN_PORT  = 22
LFS_START_SERVER = true
LFS_JWT_SECRET   = cT-Zdo5iQVnlzgDRaZ0FOCRBkizXDhjdk3FXhJhbJS4
OFFLINE_MODE     = false
CERT_FILE        = /data/gitea/gitea.centre.com.crt
KEY_FILE         = /data/gitea/gitea.centre.com.key

[database]
PATH     = /data/gitea/gitea.db
DB_TYPE  = postgres
HOST     = gitea-postgres:5432
NAME     = gitea
USER     = gitea
PASSWD   = gitea
LOG_SQL  = false
SCHEMA   =
SSL_MODE = disable
CHARSET  = utf8

[indexer]
ISSUE_INDEXER_PATH = /data/gitea/indexers/issues.bleve

[session]
PROVIDER_CONFIG = /data/gitea/sessions
PROVIDER        = file

[picture]
AVATAR_UPLOAD_PATH            = /data/gitea/avatars
REPOSITORY_AVATAR_UPLOAD_PATH = /data/gitea/repo-avatars
DISABLE_GRAVATAR              = false
ENABLE_FEDERATED_AVATAR       = true

[attachment]
PATH = /data/gitea/attachments

[log]
MODE      = console
LEVEL     = info
ROUTER    = console
ROOT_PATH = /data/gitea/log

[security]
INSTALL_LOCK                  = true
SECRET_KEY                    =
REVERSE_PROXY_LIMIT           = 1
REVERSE_PROXY_TRUSTED_PROXIES = *
INTERNAL_TOKEN                = eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYmYiOjE2Nzc1MTY0MjB9.faZuqiqenQVKGQKORy8bls2D-grU-c1R2mCxRv5BEJw
PASSWORD_HASH_ALGO            = pbkdf2
MIN_PASSWORD_LENGTH           = 4

[service]
DISABLE_REGISTRATION              = false
REQUIRE_SIGNIN_VIEW               = false
REGISTER_EMAIL_CONFIRM            = false
ENABLE_NOTIFY_MAIL                = false
ALLOW_ONLY_EXTERNAL_REGISTRATION  = false
ENABLE_CAPTCHA                    = false
DEFAULT_KEEP_EMAIL_PRIVATE        = false
DEFAULT_ALLOW_CREATE_ORGANIZATION = true
DEFAULT_ENABLE_TIMETRACKING       = true
NO_REPLY_ADDRESS                  = noreply.localhost

[lfs]
PATH = /data/git/lfs

[mailer]
ENABLED = false

[openid]
ENABLE_OPENID_SIGNIN = true
ENABLE_OPENID_SIGNUP = true

[repository.pull-request]
DEFAULT_MERGE_STYLE = merge

[repository.signing]
DEFAULT_TRUST_MODEL = committer

[ui]
THEMES = gitea,arc-green,plex,aquamarine,dark,dracula,hotline,organizr,space-gray,hotpink,onedark,overseerr,nord
DEFAULT_THEME = dracula

[migrations]
ALLOWED_DOMAINS = gitlab.centre.com
ALLOW_LOCALNETWORKS = true
SKIP_TLS_VERIFY = true

イメージは公式の gitea をベースに自己署名証明書の CA 証明書を追加したものを使用します。
keycloak への通信を https で行わない場合にはこの設定は不要です、

Dockerfile
FROM gitea/gitea

COPY ca.crt /usr/local/share/ca-certificates/ca.crt
RUN apk add --no-cache ca-certificates && update-ca-certificates

body_outer_pre.tmpl は gitea UI のテーマを追加するために設定しています(なくてもよい)。

body_outer_pre.tmpl
{{ if .IsSigned }}
  {{ if and (ne .SignedUser.Theme "gitea") (ne .SignedUser.Theme "arc-green") }}
    <link rel="stylesheet" href="https://theme-park.dev/css/base/gitea/{{.SignedUser.Theme}}.css">
  {{end}}
{{ else if and (ne DefaultTheme "gitea") (ne DefaultTheme "arc-green") }}
  <link rel="stylesheet" href="https://theme-park.dev/css/base/gitea/{{DefaultTheme}}.css">
{{end}}

シングルサインオンの設定

keycloak 側で作成したユーザで gitea 側にシングルサインオン (SSO) するためにそれぞれのサーバで設定を追加していきます。基本的には以下の記事で記載されている手順と同じです。
https://www.talkingquickly.co.uk/gitea-sso-with-keycloak-openldap-openid-connect

keycloak 側の設定

docker compose で立てた keycloak は https://keycloak.centre.com:8443/ でダッシュボードにアクセスできます。admin ユーザでログインし、検証用の realm gitea を作成します。

次に Clients > Create client で Gitea と接続するための client を作成します。
Client Type は OpenID Connect を選択。Client ID と Name には分かりやすい名前をつけます(ここでは gitea)。

Client authentication を on に指定。

Root URL, Home URL には gitea のホスト名、ポートを指定。
Valid redirect URLs は [protocol]://[host]/user/auth2/keycloak/callback を指定。この値は後で gitea 側の設定の際に表示される url を指定しますが、この段階で設定しても ok。

次に Users > Add user から SSO 用のユーザを作成。Required user actions は色々選択できますが空欄で良く、最小限 Username を指定すれば ok です。
作成したユーザを選択し、Credentials タブの Set password から初期パスワードを設定します。
また、作成した client を選択し、Credentials タブの Client Secret の値を後ほど使うので確認しておきます。

gitea 側の設定

gitea は https://[ドメイン名]:3000 で UI にアクセスできます。初期ユーザは設定されていないため、最初に登録したユーザが Admin 権限を持つユーザとなります。
上記で作成したユーザでログインし、右上のアイコンから Site Administration を選択。
Admin Settings > Identity & Access > Authentication sources > Add Authentication sources から OIDC の設定を行います。

  • Authentication Type: OAuth2
  • Authentication Name: わかりやすいものを指定(ここでは keycloak)
  • Client Key: keycloak 側で設定した Client ID を指定 (ここでは gitea)
  • Client Secret: 上記で確認した Client Secret を入力
  • OpenID Connect Auto Discovery URL: [protocol]://[hostname]:[port]/realms/[realm_name]/.well-known/openid-configuration を指定。

なお Discovery URL と keycloak 側で指定した callback URL はページ下部に表示されています。

OAuth2 Authentication:
When registering a new OAuth2 authentication, the callback/redirect URL should be: https://gitea.centre.com:3000/user/oauth2/keycloak/callback

OAuth2 Provider
OpenID Connect
Use the OpenID Connect Discovery URL (<server>/.well-known/openid-configuration) to specify the endpoints

その他の項目についてはいったん空欄のままで作成。

動作確認

gitea の admin ユーザを一度サインアウトし、再度サインイン画面に入ると SSO 用の keycloak でログイン が追加されています。

クリックすると keycloak 側のログイン画面にリダイレクトされるので、先ほど作成した SSO 用ユーザの username とパスワードを入力してログインします。
ユーザの email や first name を設定していない場合はこのタイミングで設定が必要になります。

認証に成功すると入力したユーザのアカウントを gitea 側で登録するための画面に戻ります。
入力したユーザがまだ gitea 側に存在しない場合は 新規アカウント登録 タブでユーザ名とメールアドレスを登録します。

上記で SSO ユーザの登録が完了します。admin ユーザで gitea のユーザ一覧を見ると上記で登録したアカウントが追加されていることが確認できます。

2 回目以降のサインインでは、キャッシュが残っている間はサインイン画面で keycloak でログイン をクリックすることでユーザ名やパスワードを入力せずに Gitea にログインすることが可能です。

ユーザに Gitea Admin 権限を設定する

keycloak と gitea でそれぞれ設定を追加することで、上記の手順で gitea に登録するユーザに Admin 権限を追加することができます。

参考

まず keycloak 側で Realm roles > Create role で admin 権限用の role を作成します。role name は何でもいいですが、ここでは gitea_admin に指定。
次に gitea に登録時に admin 権限を設定したいユーザを作成して role にマッピングします。ここでは gitea_admin_user というユーザを作成し、Role mapping タブの Assign Role より上記の gitea_admin ロールを割り当てます。

次に Client > gitea (client ID) > Client scopes タブ > gitea-dedicated を選択し、Mapper タブで Add predefined mapper を指定します。いくつか mapper が表示されますが、この中より groups を追加します。

gitea 側の設定では先ほど作成した OIDC の Authentication sources を編集して以下の項目を追加します。

これで設定が完了したので gitea_admin_user ユーザで gitea に SSO すると、追加されたユーザに admin 権限が付加されていることが確認できます。

ユーザを org や team に追加する

gitea では gitlab 等と同様に organization や team の概念があります。OIDC 連携では keycloak 側の role に基づいて、SSO で追加したユーザを自動で organization や team に追加することができます。

これを行うには、先ほど作成した OIDC の Authentication sources を編集し、Map claimed groups to Organization teams. (Optional - requires claim name above) に json 形式で keycloak 側の role と gitea 側の org, team をマッピングする設定を追加します。

上記の例では keycloak 側で作成した Gitea_dev Role に属するユーザを SSO で登録した際、gitea 側の Organization Org1 内の team Team1 に自動で登録する設定になります。
LDAP の例ですが、マッピングの指定方法は以下が参考になります。

https://forum.gitea.com/t/map-ldap-groups-to-organization-teams/6385

ここで指定した Organization, team は ユーザ登録前に事前に gitea 内で作成しておく必要があります。
keycloak 側では、admin のときと同様にマッピング内で指定した role を作成し、対象の user に role を割り当てる必要があります。

keycloak 側で Gitea_dev role に追加したユーザ dev-user で SSO すると、Organization の Org1 にユーザが追加されていることが確認できます。

また、Org1 配下のチーム Team1 に追加されていることも確認できます。

事前に gitea 側で organization や team を作成する必要はありますが、この方法を使うと keycloak 側での role に基づいてユーザを適切な org や team に配置でき、どのユーザをどの組織に割り当てるかという設定は keycloak 側の role に集約することができます。

なお、ユーザ作成時に keycloak 側の設定に基づいて organization や team を自動で作成できると便利なので方法がないか探しましたが、今のところ見つかってません。現時点では gitea 公式の機能としては実装されてないようです。

Discussion