尝试使用Keycloak提供者将Keycloak配置为Grafana的SSO服务
首先
我之前写了一篇类似于Terraform – Keycloak Provider 的入门文章。
在本篇文章中,我们将使用Keycloak提供程序来创建Keycloak的领域、群组、用户和客户机,作为上述应用部分的扩展,并确认可以使用Keycloak在Grafana上进行单点登录(SSO)。就像往常一样,我们会省略Terraform和Keycloak的基本介绍。
确认Grafana的要求
以下是设定Keycloak所需的参考事项。
这次的情况是
-
- Keycloak 上の admin グループに属するユーザーを Grafana の Admin ロールに割り当て
- Keycloak 上の guest グループに属するユーザーを Grafana の Viewer ロールに割り当て
为了达到这一目的,我们需要在生成的 ID Token 中将 groups 这个声明添加进 Keycloak 的配置中。
Keycloak 配置文件
这次为了满足上述要求,我制作了如下的Terraform配置文件。
#####
# Realm
resource "keycloak_realm" "realm" {
realm = local.realm_id
}
#####
# Groups
resource "keycloak_group" "admin" {
name = "admin"
realm_id = keycloak_realm.realm.id
}
resource "keycloak_group" "guest" {
name = "guest"
realm_id = keycloak_realm.realm.id
}
#####
# Users
resource "keycloak_user" "admin_users" {
for_each = { for i in var.admin_users : i.username => i }
realm_id = keycloak_realm.realm.id
username = each.value.username
enabled = true
email = each.value.email
email_verified = true
first_name = each.value.first_name
last_name = each.value.last_name
initial_password {
value = local.initial_password
}
}
resource "keycloak_user" "guest_users" {
for_each = { for i in var.guest_users : i.username => i }
realm_id = keycloak_realm.realm.id
username = each.value.username
enabled = true
email = each.value.email
email_verified = true
first_name = each.value.first_name
last_name = each.value.last_name
initial_password {
value = local.initial_password
}
}
#####
# Group membership
resource "keycloak_group_memberships" "admin_group_membershop" {
for_each = { for i in var.admin_users : i.username => i }
realm_id = keycloak_realm.realm.id
group_id = keycloak_group.admin.id
members = [
each.value.username
]
}
resource "keycloak_group_memberships" "guest_group_membershop" {
for_each = { for i in var.guest_users : i.username => i }
realm_id = keycloak_realm.realm.id
group_id = keycloak_group.guest.id
members = [
each.value.username
]
}
#####
# OpenID Connect Client Scope
resource "keycloak_openid_client_scope" "groups" {
realm_id = keycloak_realm.realm.id
name = local.scope_name_groups
include_in_token_scope = true
}
resource "keycloak_openid_group_membership_protocol_mapper" "groups_mapper" {
realm_id = keycloak_realm.realm.id
client_scope_id = keycloak_openid_client_scope.groups.id
name = local.scope_name_groups
claim_name = local.scope_name_groups
full_path = false
}
#####
# OpenID Connect Client
resource "keycloak_openid_client" "grafana_client" {
realm_id = keycloak_realm.realm.id
enabled = true
name = local.client_name
client_id = local.client_id
access_type = "CONFIDENTIAL"
standard_flow_enabled = true
valid_redirect_uris = var.valid_redirect_uris
}
resource "keycloak_openid_client_default_scopes" "grafana_client_default_scopes" {
realm_id = keycloak_realm.realm.id
client_id = keycloak_openid_client.grafana_client.id
default_scopes = local.default_scopes
}
我会简单地解释一下。首先,让我们看一下下面的部分。
#####
# Groups
resource "keycloak_group" "admin" {
name = "admin"
realm_id = keycloak_realm.realm.id
}
resource "keycloak_group" "guest" {
name = "guest"
realm_id = keycloak_realm.realm.id
}
为了将 admin(Keycloak)分配为 Admin(Grafana),以及将 guest(Keycloak)分配为 Viewer(Grafana),我们正在创建 Keycloak 的组。
下一个是这个部分
#####
# Users
resource "keycloak_user" "admin_users" {
for_each = { for i in var.admin_users : i.username => i }
realm_id = keycloak_realm.realm.id
username = each.value.username
enabled = true
email = each.value.email
email_verified = true
first_name = each.value.first_name
last_name = each.value.last_name
initial_password {
value = local.initial_password
}
}
resource "keycloak_user" "guest_users" {
for_each = { for i in var.guest_users : i.username => i }
realm_id = keycloak_realm.realm.id
username = each.value.username
enabled = true
email = each.value.email
email_verified = true
first_name = each.value.first_name
last_name = each.value.last_name
initial_password {
value = local.initial_password
}
}
使用variables.tf(vars)和locals.tf中定义的值,创建属于admin/guest组的用户。用户定义如下:
variable "admin_users" {
description = "Member of admin group"
}
variable "guest_users" {
description = "Member of guest group"
}
admin_users = [
{
"username" = "admin",
"email" = "admin@example.com",
"first_name" = "Hoge",
"last_name" = "Fuga"
}
]
guest_users = [
{
"username" = "guest",
"email" = "guest@example.com",
"first_name" = "Hoge",
"last_name" = "Fuga"
}
]
我們要求在創建 Realm 之後設置初始密碼並更換密碼。
locals {
initial_password = "ChangeMe!!"
}
在下面的部分中,我们正在执行创建的admin/guest组和用户的关联。
#####
# Group membership
resource "keycloak_group_memberships" "admin_group_membershop" {
for_each = { for i in var.admin_users : i.username => i }
realm_id = keycloak_realm.realm.id
group_id = keycloak_group.admin.id
members = [
each.value.username
]
}
resource "keycloak_group_memberships" "guest_group_membershop" {
for_each = { for i in var.guest_users : i.username => i }
realm_id = keycloak_realm.realm.id
group_id = keycloak_group.guest.id
members = [
each.value.username
]
}
在下面的部分中,我们将定义包含在ID Token中的groups claim以及对该Claim的映射器的定义。
#####
# OpenID Connect Client Scope
resource "keycloak_openid_client_scope" "groups" {
realm_id = keycloak_realm.realm.id
name = local.scope_name_groups
include_in_token_scope = true
}
resource "keycloak_openid_group_membership_protocol_mapper" "groups_mapper" {
realm_id = keycloak_realm.realm.id
client_scope_id = keycloak_openid_client_scope.groups.id
name = local.scope_name_groups
claim_name = local.scope_name_groups
full_path = false
}
最后,我们为Grafana定义了一个OpenID Connect Client,并在其中包含了默认的scope。
#####
# OpenID Connect Client
resource "keycloak_openid_client" "grafana_client" {
realm_id = keycloak_realm.realm.id
enabled = true
name = local.client_name
client_id = local.client_id
access_type = "CONFIDENTIAL"
standard_flow_enabled = true
valid_redirect_uris = var.valid_redirect_uris
}
resource "keycloak_openid_client_default_scopes" "grafana_client_default_scopes" {
realm_id = keycloak_realm.realm.id
client_id = keycloak_openid_client.grafana_client.id
default_scopes = local.default_scopes
}
Grafana 的设置
请注意,由于个人环境使用kube-prometheus-stack构建Grafana,所以请使用与该环境相适应的配置文件。(请根据您自己的发行版和正在构建的基础设施进行相应调整)
grafana.ini:
server:
root_url: https://<grafana>
auth.generic_oauth:
enabled: true
name: Keycloak
allow_sign_up: true
scopes: openid profile groups email
auth_url: https://<keycloak>/realms/<realm>/protocol/openid-connect/auth
token_url: https://<keycloak>/realms/<realm>/protocol/openid-connect/token
api_url: https://<keycloak>/realms/<realm>/protocol/openid-connect/userinfo
role_attribute_path: contains(groups[*], 'admin') && 'Admin' || contains(groups[*], 'guest') && 'Editor' || 'Viewer'
envValueFrom:
GF_AUTH_GENERIC_OAUTH_CLIENT_ID:
secretKeyRef:
name: grafana-keycloak-secret
key: client_id
GF_AUTH_GENERIC_OAUTH_CLIENT_SECRET:
secretKeyRef:
name: grafana-keycloak-secret
key: client_secret
请按照您自己的环境对 server.root_uri 和 auth.generic_oauth.auth_url 中的URL进行修改。关于 auth.generic_oauth,我原以为会有existingSecret等字段,但似乎并没有。我曾经左右犹豫,但最终还是成功地通过环境变量传递了client_id和client_secret(GF_AUTH_GENERIC_OAUTH_CLIENT_ID,GF_AUTH_GENERIC_OAUTH_CLIENT_SECRET)。
确认
会出现用Keycloak登录的选项。
使用已创建的用户登录。
我已确认。
最后
如果你对variables.tf和locals.tf感兴趣,我在这里省略了很多解释,请参考这个存储库。
terraform/keycloak/*: 今回用いた Keycloak 設定用の Terraform の構成ファイル群
app/kube-prometheus-stack.yaml: kube-prometheus-stack の ArgoCD – Application の定義