Skip to content

Keycloak に入門し、Terraform の Keycloak Provider を使ってみる

はじめに

業務で Keycloak について知る必要があったため、学習した内容を備忘録として書きます。

まずはじめに、Getting started / Docker にあるチュートリアルを実施し、そのあとKeycloak Provider - mrparkers/keycloak を使ってレルムなどを Terraform で管理できるようにします。

成果物

https://github.com/kntks/blog-code/tree/main/2024/02/learn-keycloak-basic

環境

バージョン
MacVentura 13.2.1
Keycloak23.0.6
Docker25.0.3
Docker Composev2.24.5
Terraform1.7.3

Keycloakとは?

Keycloakは、オープンソースの認証・認可サービス(IAM)です。IDaaS (Identity as a Service) としても知られ、アプリケーションへのアクセスを安全に管理するための機能を提供します。

主な機能

  • 認証: ユーザーが自分のIDとパスワードを使用してアプリケーションにログインできるようにします。
  • 認可: ユーザーがアクセスできるアプリケーションやリソースを制御します。
  • シングルサインオン (SSO): 複数のアプリケーションに一度ログインすれば、他のアプリケーションにもシームレスにログインできるようになります。
  • アカウント管理: ユーザーのアカウント作成、パスワードリセット、プロファイル編集などを管理できます。
  • フェデレーション: SAML、OIDC、LDAPなどの標準プロトコルをサポートし、既存の認証システムと統合できます。

keycloak/keycloak - GitHub

Getting Started をやってみる

Docker で立ち上げる

Getting Started / Docker を参考に compose.yaml ファイルを書きます。

Docker イメージは、quay.io/repository/keycloak にあります。

compose.yaml
services:
keycloak:
image: quay.io/keycloak/keycloak:23.0.6
ports:
- target: 8080
published: 8080
protocol: tcp
mode: host
environment:
- KEYCLOAK_ADMIN=admin
- KEYCLOAK_ADMIN_PASSWORD=admin
command: ["start-dev"]

docker compose コマンドで立ち上げた後、http://localhost:8080/admin にアクセスします。

Terminal window
docker compose up

Username と Password は、compose.yaml で設定した環境変数の値なので、どちらも admin です。 admin-login

レルムを作成する

Keycloak のレルムは、テナントと同様の役割を果たし、管理者はレルムごとにアプリケーションとユーザーをグループ分けして管理できます。

Keycloak をはじめて利用するとき、master レルムのみ存在しますが、アプリケーションの管理に master レルムを使用することは推奨されていません。

master レルムは、他のレルムを作成するときや管理するときに使用します。

The master realm - Keycloak Documentation
getting-started-docker

画面左上のドロップメニューから Create realm ボタンを押します。 create-realm-button

Create ボタンを押してレルムを作成します。 create-realm

ユーザーを作成する

Users のページにいき、Add user ボタンを押します。 users-page

UsernameFirst nameLast name を入力し、Create ボタンを押します。 create-user

無事完成しました。 user-created

パスワードを設定する

先ほど作成したユーザーのページから Credentials タブをクリックし、Set password ボタンをクリックします。 credential-tab

パスワードを設定し、Save ボタンを押します。
今回はテスト用なので、Temporary は off にします。 password-dialog

作成したユーザーでサインインする

http://localhost:8080/realms/myrealm/account をブラウザで開きます。

右上の Sign in をクリックします。 account-management

先ほど作成した myuser でサインインする myrealm-login

これでログインできるようになりました。 myuser-account-page

Keycloak の用語を整理する

単語説明
usersユーザーはシステムにログインできるエンティティです。メール、ユーザー名、住所、電話番号、誕生日などの属性を持つことができます。グループのメンバーシップが割り当てられ、特定の役割が割り当てられることがあります。
authenticationユーザーの識別と検証のプロセス。
authorizationユーザーへのアクセス権限を付与するプロセス。
credentialsKeycloakがユーザーの身元を確認するために使用するデータの断片。たとえばパスワード、ワンタイムパスワード、デジタル証明書、または指紋などがあります。
rolesロールはユーザーのタイプやカテゴリを識別します。管理者、ユーザー、マネージャー、従業員などがあります。アプリケーションは、個々のユーザーではなく、特定の役割にアクセス権限と権限を割り当てることがよくあります。
user role mappingユーザーのロールマッピングは、ロールとユーザーとのマッピングを定義します。ユーザーはゼロ以上のロールに関連付けることができます。このロールマッピング情報はトークンやアサーションにカプセル化されるため、アプリケーションは管理するさまざまなリソースのアクセス許可を決定できます。
composite rolesコンポジットロールは他のロールと関連付けることができるロールです。たとえば、スーパーユーザーコンポジットロールはsales-adminおよびorder-entry-adminロールに関連付けることができます。ユーザーがスーパーユーザーロールにマッピングされると、sales-adminおよびorder-entry-adminロールも継承されます。
groupsグループはユーザーのグループを管理します。グループに属性を定義できます。グループにロールをマップすることもできます。グループのメンバーになるユーザーは、そのグループが定義する属性とロールマッピングを継承します。
realmsレルムは一連のユーザー、資格情報、ロール、およびグループを管理します。ユーザーはレルムに所属し、レルムにログインします。レルムはお互いに隔離されており、それぞれが管理および認証するユーザーのみを管理できます。
clientsクライアントはKeycloakにユーザーの認証を要求できるエンティティです。ほとんどの場合、クライアントは自分自身を保護し、シングルサインオンソリューションを提供するためにKeycloakを使用したいアプリケーションやサービスです。クライアントは、単にアイデンティティ情報やアクセストークンをリクエストしたいエンティティでもあります。
client adaptersクライアントアダプターは、Keycloakと通信してセキュリティを確保するために、アプリケーション環境にインストールするプラグインです。Keycloakには、さまざまなプラットフォーム用のいくつかのアダプターがダウンロードできます。カバーしていない環境用のサードパーティーのアダプターも入手できます。調べてみると、アダプターはJavaScriptしかない。 Downloads 2.4. Keycloak JavaScript adapter
consent同意は、クライアントが認証プロセスに参加する前に、管理者がユーザーにクライアントへの許可を与えることを望む場合のことを指します。ユーザーが資格情報を提供した後、Keycloakはログインを要求しているクライアントと、ユーザーから要求されているアイデンティティ情報を識別する画面を表示します。ユーザーはリクエストを許可するかどうかを決定できます。
client scopesクライアントが登録されると、そのクライアント用にプロトコルマッパーとロールスコープマッピングを定義する必要があります。いくつかの共通の設定を共有して新しいクライアントを作成しやすくするために、クライアントスコープを保存することがしばしば役立ちます。これは、スコープパラメーターの値に基づいて条件付きでいくつかのクレームやロールを要求するためにも役立ちます。Keycloakはこれに対してクライアントスコープの概念を提供します。
client roleクライアントは、それに固有のロールを定義できます。これは基本的に、クライアント専用のロール名前空間です。
identity tokenユーザーに関するアイデンティティ情報を提供するトークン。OpenID Connect仕様の一部。
access tokenHTTPリクエストの一部として提供されるトークンで、サービスへのアクセスを許可します。これはOpenID ConnectおよびOAuth 2.0の仕様の一部です。
assertionユーザーに関する情報。これは、認証されたユーザーのアイデンティティメタデータを提供するSAML認証応答に含まれるXMLブロブに通常関連します。
service account各クライアントには、アクセストークンを取得できる組み込みのサービスアカウントがあります。
direct grantクライアントがREST呼び出しを介してユーザーの代わりにアクセストークンを取得する方法。
protocol mappers各クライアントでは、OIDCトークンまたはSAMLアサーションに格納されるクレームやアサーションをカスタマイズできます。これは、クライアントごとにプロトコルマッパーを作成および構成することで行います。
sessionユーザーがログインすると、ログインセッションを管理するためにセッションが作成されます。セッションには、ユーザーがいつログインしたか、そのセッション中にシングルサインオンに参加したアプリケーションが何であるかなどの情報が含まれます。管理者とユーザーの両方がセッション情報を表示および管理できます。
user federation providerKeycloakはユーザーを保存および管理できます。多くの場合、企業はすでにユーザーおよび認証情報を保存しているLDAPやActive Directoryサービスを持っています。Keycloakは、これらの外部ストアから認証情報を検証し、ID情報を取り込むことができます。
identity providerIDPはユーザーを認証できるサービスです。KeycloakはIDPです。
identity provider federationKeycloakは、1つ以上のIDPに認証を委任するように構成できます。FacebookやGoogle+を介したソーシャルログインがIDPフェデレーションの例です。OpenID ConnectやSAML 2.0のIDPに認証を委任するようにKeycloakをフックすることもできます。
identity provider mappersIDPフェデレーションを行う際、受信トークンやアサーションをユーザーやセッション属性にマッピングできます。これにより、外部IDPからのアイデンティティ情報を要求するクライアントからの認証を実装できます。

引用:Core concepts and terms - Keycloak Documentation

Keycloak Providerを使ってみる

Keycloak Provider のドキュメントでは、Keycloak 全体を Terraformで管理したい場合、master レルムに OpenId Connect の Client を作成し、client credentials を使ったセットアップを推奨しています。

実際に master レルムに OpenId Connect の Client を作成します。

Keycloak側のセットアップ

Client の作成

admin ユーザでログインし、master レルムに移動します。そのあと Create Client ボタンを押します。 master-realm-create-client-button

General settings は以下の画像のように設定します。 master-realm-create-oidc-client-general-settings

Capability config も同様に以下の画像のように設定します。 master-realm-create-oidc-client-capability-config

3番目の Login settings については何も入力せず、Save ボタンを押します。

Client Secretをコピーする

Client 作成後、Credentials タブをクリックすると、Client Secret をコピーできます。 master-realm-oidc-client-secret

Client にロールを割り当てる

Service accounts role タブから Assign role をクリックします。 master-realm-oidc-service-account

admin を選択し、Assign ボタンを押します master-realm-oidc-assign-role

Terraform側のセットアップ

ここから Terraform のコードを書いていきます。

Terminal window
mise install terraform 1.7.3
mise local terraform 1.7.3
Terminal window
$ tree terraform
terraform
├── backend.tf
├── main.tf
├── provider.tf
├── terraform.tfvars
└── variables.tf

設定ファイル

terraform/backend.tf
terraform {
backend "local" {
path = "./terraform.tfstate"
}
}
terraform/main.tf
locals {
realm_id = "terraform"
}
terraform/provider.tf
terraform {
required_version = "~> 1.7.0"
required_providers {
keycloak = {
source = "mrparkers/keycloak"
version = ">= 4.4.0"
}
}
}
provider "keycloak" {
client_id = var.client_id
client_secret = var.client_secret
url = var.url
}
terraform/terraform.tfvars
client_id = "terraform"
client_secret = "" # credentialsを貼り付ける
url = "http://localhost:8080"
terraform/variables.tf
variable "client_id" {
type = string
}
variable "client_secret" {
type = string
}
variable "url" {
type = string
}
Terminal window
$ terraform init
$ terraform plan
No changes. Your infrastructure matches the configuration.

設定を取り込む

Terraform 1.5から import ブロック が使えるようになり、terraform plan コマンドの -generate-config-out オプションを使うことで、既存の設定から Terraform のコードを生成できます。

Getting Startedをやってみるのセクションで作成したデータを取り込んでみます。

terraform/main.tf
locals {
realm_id = "terraform"
}
import {
to = keycloak_realm.myrealm
id = "myrealm"
}
import {
to = keycloak_user.myuser
id = "myrealm/19aa1dc2-0c4c-4e56-9cda-b251a9c8c31d"
}
Terminal window
terraform plan -generate-config-out=generated.tf
terraform/generate.tf
terraform/generate.tf
# __generated__ by Terraform
# Please review these resources and move them into your main configuration files.
# __generated__ by Terraform from "myrealm/19aa1dc2-0c4c-4e56-9cda-b251a9c8c31d"
resource "keycloak_user" "myuser" {
attributes = {}
email = null
email_verified = false
enabled = true
first_name = "foo"
last_name = "bar"
realm_id = "myrealm"
required_actions = []
username = "myuser"
}
# __generated__ by Terraform from "myrealm"
resource "keycloak_realm" "myrealm" {
access_code_lifespan = "1m0s"
access_code_lifespan_login = "30m0s"
access_code_lifespan_user_action = "5m0s"
access_token_lifespan = "5m0s"
access_token_lifespan_for_implicit_flow = "15m0s"
account_theme = null
action_token_generated_by_admin_lifespan = "12h0m0s"
action_token_generated_by_user_lifespan = "5m0s"
admin_theme = null
attributes = {}
browser_flow = "browser"
client_authentication_flow = "clients"
client_session_idle_timeout = "0s"
client_session_max_lifespan = "0s"
default_default_client_scopes = []
default_optional_client_scopes = []
default_signature_algorithm = "RS256"
direct_grant_flow = "direct grant"
display_name = null
display_name_html = null
docker_authentication_flow = "docker auth"
duplicate_emails_allowed = false
edit_username_allowed = false
email_theme = null
enabled = true
internal_id = "9bfe15a2-1b59-46de-a41d-f756e4ddb616"
login_theme = null
login_with_email_allowed = true
oauth2_device_code_lifespan = "10m0s"
oauth2_device_polling_interval = 5
offline_session_idle_timeout = "720h0m0s"
offline_session_max_lifespan = "1440h0m0s"
offline_session_max_lifespan_enabled = false
password_policy = null
realm = "myrealm"
refresh_token_max_reuse = 0
registration_allowed = false
registration_email_as_username = false
registration_flow = "registration"
remember_me = false
reset_credentials_flow = "reset credentials"
reset_password_allowed = false
revoke_refresh_token = false
ssl_required = "external"
sso_session_idle_timeout = "30m0s"
sso_session_idle_timeout_remember_me = "0s"
sso_session_max_lifespan = "10h0m0s"
sso_session_max_lifespan_remember_me = "0s"
user_managed_access = false
verify_email = false
otp_policy {
algorithm = "HmacSHA1"
digits = 6
initial_counter = 0
look_ahead_window = 1
period = 30
type = "totp"
}
web_authn_passwordless_policy {
acceptable_aaguids = []
attestation_conveyance_preference = "not specified"
authenticator_attachment = "not specified"
avoid_same_authenticator_register = false
create_timeout = 0
relying_party_entity_name = "keycloak"
relying_party_id = null
require_resident_key = "not specified"
signature_algorithms = ["ES256"]
user_verification_requirement = "not specified"
}
web_authn_policy {
acceptable_aaguids = []
attestation_conveyance_preference = "not specified"
authenticator_attachment = "not specified"
avoid_same_authenticator_register = false
create_timeout = 0
relying_party_entity_name = "keycloak"
relying_party_id = null
require_resident_key = "not specified"
signature_algorithms = ["ES256"]
user_verification_requirement = "not specified"
}
}

generate-config-out オプションによってコード生成ができたので terraform apply コマンドで stateファイルを更新します。

Terminal window
$ terraform apply
...
Apply complete! Resources: 2 imported, 0 added, 0 changed, 0 destroyed.

import ブロックをそのまま残して git 管理しても良いのですが、ここでは必要ないので削除して generate.tf に生成したコードを main.tf に移してリファクタします。

generate.tf も消します。

参考: