尝试使用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登录的选项。

image06.png

使用已创建的用户登录。

image07.png

我已确认。

image08.png

最后

如果你对variables.tf和locals.tf感兴趣,我在这里省略了很多解释,请参考这个存储库。

 

terraform/keycloak/*: 今回用いた Keycloak 設定用の Terraform の構成ファイル群

app/kube-prometheus-stack.yaml: kube-prometheus-stack の ArgoCD – Application の定義

广告
将在 10 秒后关闭
bannerAds