本文发布于Cylon的收藏册,转载请著名原文链接~

本文使用 helm 方式部署 grafana 9 并同样将 keycloak 部署在 kubernetes 集群之上;接下来使用 keycloak 作为 grafana authentication,并实现 oauth2 的用户权限管理。

在Kubernetes 集群之上使用helm部署keycloak

在 kubernetes 集群安装 keycloak 有两种方式:

下面使用 offical 提供的方式进行部署

1
kubectl create -f https://raw.githubusercontent.com/keycloak/keycloak-quickstarts/latest/kubernetes/keycloak.yaml

helm 部署完成后默认密码是存储在 secret 中,上面方式安装的密码默认为 admin/admin

Keycloak configuration

创建realm

Realm 管理这一组用户(users), 凭据(credentials), 角色(roles) 和 组(groups),realm之间是相互隔离,一个用户属于并登录到某个 realm,只能管理和验证其控制的用户。

下面为 grafana 创建一个 realm,如果你的环境已经存在通用的 realm,则可以使用这个 realm,默认 keycloak 的 realm 是 master,超级管理员属于这个 realm。

image-20231219231812810

创建 client

Client 是可以请求Keycloak对用户进行身份验证的实体。最常见用途是希望使用Keycloak来保护自己并提供单点登录(SSO)解决方案的应用程序和服务。客户端也可以是只希望请求身份信息或访问令牌的实体,以便它们可以安全地调用由 Keycloak 保护的网络上的其他服务。因此,我们需要为 grafana 创建一个 client

根据 grafana 官方的指南,我们创建标准需如下所示

  • Client ID: grafana-oauth
  • Enabled: ON
  • Client Protocol: openid-connect
  • Access Type: confidential
  • Standard Flow Enabled: ON
  • Implicit Flow Enabled: OFF
  • Direct Access Grants Enabled: ON
  • Root URL: <grafana_root_url>
  • Valid Redirect URIs: <grafana_root_url>/login/generic_oauth
  • Web Origins: <grafana_root_url>
  • Admin URL: <grafana_root_url>
  • Base URL: <grafana_root_url>

image-20231219233419049

图:创建一个 grafana-oauth client

image-20231219233506788

图:配置 capability config

Realm settings => Client policies => Policies

image-20231219233643632

图:创建 client policy

image-20231219233743333

图:创建 access type

准备grafana chart包

添加 chart repo

1
helm repo add grafana https://grafana.github.io/helm-charts

选择 chart 包下载

1
2
3
4
# 查看仓库中所有版本
helm search repo grafana/grafana -l
# 下载指定版本的chart包
helm pull grafana/grafana --version 6.57.4

修改 values.yaml 中 grafana.ini 部分,增加

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
   server:
    domain: "{{ if (and .Values.ingress.enabled .Values.ingress.hosts) }}{{ .Values.ingress.hosts | first }}{{ else }}''{{ end }}"
  server:
      # The full public facing url you use in browser, used for redirects and emails
    root_url: http://10.0.0.4:30033/
    serve_from_sub_path: false
  auth:
    signout_redirect_url: http://10.0.0.5:30043/auth/realms/sso/protocol/openid-connect/logout?post_logout_redirect_uri=http%3A%2F%210.0.0.5:30033%2Flogin
  auth.generic_oauth:
    enabled: true
    name: grafana-oauth
    allow_sign_up: true
    client_id: grafana-oauth
    client_secret: TF1FjfEfoeBdLa3MfpCjhC1TgChWYyPV
    scopes: openid email profile offline_access roles
    auth_url: http://10.0.0.5:30043/realms/sso/protocol/openid-connect/auth
    token_url: http://10.0.0.5:30043/realms/sso/protocol/openid-connect/token
    api_url: http://10.0.0.5:30043/realms/sso/protocol/openid-connect/userinfo
    role_attribute_path: contains(roles[*], 'admin') && 'Admin' || contains(roles[*], 'editor') && 'Editor' || 'Viewer'

这里主要修改三个部分

  • server
    • root_url: 是在使用 keycloak 跳转时,redirect_uri 的地址,默认是 localhost:3000
    • serve_from_sub_path: 从 root_url 设置中指定的子路径提供 Grafana 服务。 出于兼容性原因,默认情况下它设置为 false。
  • auth: 配置登出时请求的 API,这个按照官方给出的配置即可。
  • auth.generic_oauth: keycloak 的配置,这个按照官方给出的配置即可。

troubleshooting

Invalid redirect uri

1
Invalid redirect uri for “Valid Redirect URIs

image-20231225230735630

图:Invalid redirect uri错误

遇到 Invalid redirect uri 错误时,检查 Access settings 配置,并将 grafana 的 auth.generic_oauth 按照下面配置即可

image-20231225235825426

server 段

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
    [server]
    # Protocol (http, https, h2, socket)
    protocol = http

    # The ip address to bind to, empty will bind to all interfaces
    http_addr =

    # The http port to use
    http_port = 3000

    # The public facing domain name used to access grafana from a browser
    domain = 192.168.64.57

    # Redirect to correct domain if host header does not match domain
    # Prevents DNS rebinding attacks
    enforce_domain = false

    # The full public facing url
    # root_url = %(protocol)s://%(domain)s:%(http_port)s/
    root_url = http://192.168.64.57:30606

    # Serve Grafana from subpath specified in `root_url` setting. By default it is set to `false` for compatibility reasons.
    serve_from_sub_path = false

    # Log web requests
    router_logging = false

    # the path relative working path
    static_root_path = public

    # enable gzip
    enable_gzip = false

    # https certs & key file
    cert_file =
    cert_key =

    # Unix socket path
    socket = /tmp/grafana.sock

    # CDN Url
    cdn_url =

    # Sets the maximum time in minutes before timing out read of an incoming request and closing idle connections.
    # `0` means there is no timeout for reading the request.
    read_timeout = 0

Generic OAuth 部分

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
 #################################### Generic OAuth #######################
    [auth.generic_oauth]
    name = OAuth
    enabled = true
    allow_sign_up = true
    client_id = Grafana
    client_secret = ad35e16d-96d1-46ab-88d8-7cdb1512b608
    scopes = openid profile email
    email_attribute_name = email:primary
    email_attribute_path =
    login_attribute_path =
    name_attribute_path =
    role_attribute_path =
    id_token_attribute_name =
    auth_url = http://192.168.64.57:30708/auth/realms/devops/protocol/openid-connect/auth
    token_url = http://192.168.64.57:30708/auth/realms/devops/protocol/openid-connect/token
    api_url = http://192.168.64.57:30708/auth/realms/devops/protocol/openid-connect/userinfo
    allowed_domains =
    team_ids =
    allowed_organizations =
    tls_skip_verify_insecure = false
    tls_client_cert =
    tls_client_key =
    tls_client_ca =

login.OAuthLogin(state mismatch)

image-20231226000413815

图:登录后错误

这个错误是没有配置对应user的状态或者没有配置email导致,如果配置了 email,那么吧用户放置到对应组中就恢复了

image-20231226000619866

图:用户与组

配置权限

如果使用了 role 作为权限分配,那么按照官方的配置即可,如果使用 group,则需要使用下面的规则

1
role_attribute_path = contains(groups[], 'admin') && 'Admin' || contains(groups[], 'editer') && 'Editor' || contains(groups[*], 'viewer') && 'Viewer' 

这个表达式是一个条件表达式,用于根据用户所属的组分配角色属性值。下面是对每个字符和子表达式的翻译解释:

  • role_attribute_path : 角色属性路径,表示根据条件表达式的结果来确定角色属性值。
  • contains(groups[*], 'admin'): 检查用户所属的组列表中是否包含 admin 组。
  • &&: 逻辑与运算符,用于组合多个条件,表示两个条件都为真时整个表达式为真。
  • Admin: 如果 contains(groups[*], 'admin') 为真,将角色属性设置为 Admin
  • ||: 逻辑或运算符,用于组合多个条件,表示任意一个条件为真时整个表达式为真。
  • contains(groups[*], 'editer'): 检查用户所属的组列表中是否包含 editer 组。
  • 'Editor': 如果 contains(groups[*], 'editer') 为真,将角色属性设置为 Editor
  • contains(groups[*], 'viewer'): 检查用户所属的组列表中是否包含 viewer 组。
  • 'Viewer': 如果 contains(groups[*], 'viewer') 为真,将角色属性设置为 Viewer

Reference

[1] keycloak redirect_uri is always localhost:3000 #39

[2] templates/keycloak.yaml

[3] Configure Keycloak OAuth2 authentication

本文发布于Cylon的收藏册,转载请著名原文链接~

链接:https://www.oomkill.com/2023/12/grafana-keycloak/

版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」 许可协议进行许可。