ArgoCD, Grafana KeyCloak 로그인 연동하기

목차

Grafana와 ArgoCD를 배포해서 사용 중인데 개별로 사용자 관리를 하는 것이 불필요하다고 생각이 들었습니다.
점점 회사에 사람들이 많아지는 중이고, 그럴 때마다 각각 관리를 하는 건 매우 비효율적이라고 판단했습니다.
마침 KeyCloak으로 kube-apiserver에 대한 접근 제어를 사용하고 있었는데,
비슷한 방식으로 애드온에 대한 계정 관리도 가능하지 않을까 싶었습니다.
마침 구글링을 해보니 몇 가지 사례들이 있었고, 제가 운영하는 리소스에도 적용하면서 그 과정을 정리해보려고 합니다.

ArgoCD와 Grafana의 KeyCloak 로그인 연동

ArgoCD와 Grafana의 KeyCloak 로그인 연동

ArgoCD와 Grafana의 KeyCloak 로그인 연동ArgoCD는 values.yaml의 oidc.config로 KeyCloak에 OIDC 로그인하고, KeyCloak 그룹을 rbac policy.csv에서 admin과 readonly 역할로 연결한다. logoutURL로 ArgoCD 로그아웃 때 KeyCloak 세션도 끊어지도록 설정할 수 있다. Grafana는 환경 변수로 generic OAuth를 설정하고, Dedicated scopes에 추가한 roles를 role_attribute_path로 Admin, Editor, Viewer에 연결한다. 로그아웃은 GF_AUTH_SIGNOUT_REDIRECT_URL을 쓴다.ArgoCD 설정ArgoCDvalues.yaml configs.cmoidc.configArgoCD rbac policy.csvargocd-admin: role:adminargocd-member: role:readonlyGrafana 설정GrafanaenvGF_AUTH_GENERIC_OAUTH_*role_attribute_pathAdmin, Editor, ViewerKeyCloak 24.0.5bitnami 차트로 재설치OIDC: issuer,clientID, clientSecretgroupslogoutURLgeneric OAuth:auth, token, api URLDedicated scopes의 rolesSIGNOUT_REDIRECT_URL
ArgoCD와 Grafana가 KeyCloak 하나에 OIDC 로그인·로그아웃을 연결하고, KeyCloak 그룹과 role이 ArgoCD RBAC와 Grafana 역할로 나뉜다.
그림 관계를 글로 읽기
  1. ArgoCD는 values.yaml configs.cm의 oidc.config(issuer, clientID, clientSecret)로 KeyCloak에 OIDC 로그인한다.
  2. KeyCloak에서 만든 그룹 이름을 policy.csv가 argocd-admin→role:admin, argocd-member→role:readonly로 매핑한다.
  3. logoutURL로 ArgoCD에서 로그아웃할 때 KeyCloak 세션도 끊어지도록 설정할 수 있다.
  4. Grafana는 values.yaml의 평문 client_secret이 막혀 env의 GF_AUTH_GENERIC_OAUTH_* 변수로 auth, token, api URL을 설정한다.
  5. Dedicated scopes에 추가한 roles를 role_attribute_path가 Admin, Editor, Viewer로 나눈다.
  6. GF_AUTH_SIGNOUT_REDIRECT_URL로 로그아웃한다. KeyCloak Client에 Valid post logout redirect URIs를 적용해야 세션이 끊어졌다.

ArgoCD

1. KeyCloak 재설치

먼저 ArgoCD 구성을 변경하기에 앞서서 KeyCloak 재설치를 먼저 진행했습니다.
ArgoCD OIDC 설정 중에 logoutURL이라는 속성을 추가할 수 있는데, ArgoCD에서 로그아웃 하면 KeyCloak에서도 세션이 끊어지게 되도록 설정할 수 있습니다.
KeyCloak에서 로그아웃 동작을 수행할 수 있는 REST API 명세에 맞게 logoutURL을 작성해야 하는데, 기존에 사용 중이었던 KeyCloak의 버전은 17.0.1-legacy이라 버전이 너무 낮아 적절한 REST API 값을 맞춰 넣는 것이 힘들었습니다.

bash
~ helm search repo keycloak
NAME                    CHART VERSION   APP VERSION     DESCRIPTION                                       
bitnami/keycloak        21.4.4          24.0.5          Keycloak is a high performance Java-based ident...
codecentric/keycloak    18.4.4          17.0.1-legacy   Open Source Identity and Access Management For ...
codecentric/keycloakx   2.3.0           22.0.4          Keycloak.X - Open Source Identity and Access Ma...

codecentric에서 제공하는 keycloak 헬름 차트를 쓰고 있었는데, 이번에 bitnami keycloak 차트를 사용해서 24.0.5 버전으로 재설치하게 되었습니다.

2. ArgoCD OIDC 적용

공식 문서를 참고하면 대부분의 설정을 하실 수 있는데, 저는 ArgoCD를 Helm Chart로 배포했기 때문에 Configuring ArgoCD OIDC 부분에서 사용한 것과 같이 직접 Configmap과 Secret을 수정하는 방식이 아닌, values.yaml에 속성을 수정해서 재배포 했습니다.

yaml
...
...
configs:
  cm:
    url: https://argocd.example.com
    oidc.config: |
      name: Keycloak
      issuer: https://keycloak.exmaple.com/realms/exmaple
      clientID: example
      clientSecret: $oidc.keycloak.clientSecret
      requestedScopes: ["openid", "profile", "email", "groups"]
      logoutURL: https://keycloak.example.com/realms/example/protocol/openid-connect/logout?id_token_hint={{token}}&post_logout_redirect_uri=https://argocd.example.com
...
...
  secret:
    extra:
      oidc.keycloak.clientSecret: ********************************
...
...
  rbac:
    policy.csv: |
      g, argocd-admin, role:admin
      g, argocd-member, role:readonly
...
...

Grafana

Grafana Labs 공식 문서에 가이드가 잘 나와있어서 참고를 했고 내용에 따라 진행하면 대부분의 설정은 진행할 수 있지만 몇 가지 특이점이 있어서 내용을 작성했습니다.

auth.generic_oauth

가이드를 참고해서 values.yaml을 수정하고 배포했더니 오류가 발생했습니다.

bash
Sensitive key 'auth.generic_oauth.client_secret' should not be defined explicitly in values. Use variable expansion instead. You can disable this client-side validation by changing the value of assertNoLeakedSecrets.

grafana 차트의 helper를 사펴보니 아래와 같은 코멘트를 볼 수 있었습니다.
values.yaml에 평문으로 데이터를 넣지 못하게 막혀 있었습니다.

yaml
...
...
{{/*
    Checks whether the user is attempting to store secrets in plaintext
    in the grafana.ini configmap
*/}}
{{/* grafana.assertNoLeakedSecrets checks for sensitive keys in values */}}
...
...

구글링을 해보니 환경 변수로 처리가 가능해서 values.yaml을 수정해서 배포했습니다.

yaml
# kube-prometheus-stack/values.yaml
...
...
# 오류 발생
grafana:
  grafana.ini:
    auth.generic_oauth:
      enabled: true
      name: Keycloak
      allow_sign_up: true
      client_id: grafana-client
      client_secret: ********************************
      scopes: openid email profile offline_access roles
      email_attribute_path: email
      login_attribute_path: username
      name_attribute_path: full_name
      auth_url: https://keycloak.example.com/realms/example/protocol/openid-connect/auth
      token_url: https://keycloak.exmaple.com/realms/example/protocol/openid-connect/token
      api_url: https://keycloak.example.com/realms/example/protocol/openid-connect/userinfo
      role_attribute_path: contains(roles[*], 'admin') && 'Admin' || contains(roles[*], 'editor') && 'Editor' || 'Viewer'
      signout_redirect_url: https://keycloak.example.com/realms/example/protocol/openid-connect/logout?post_logout_redirect_uri=https%3A%2F%2Fhttps://grafana.example.com%2Flogin
...
...

# 개선
...
...
grafana:
  env:
    GF_SERVER_DOMAIN: "https://grafana.example.com"
    GF_SERVER_ROOT_URL: "https://grafana.example.com"
    GF_AUTH_GENERIC_OAUTH_ENABLED: "true"
    GF_AUTH_GENERIC_OAUTH_NAME: "Keycloak"
    GF_AUTH_GENERIC_OAUTH_ALLOW_SIGN_UP: "true"
    GF_AUTH_GENERIC_OAUTH_CLIENT_ID: "grafana-client"
    GF_AUTH_GENERIC_OAUTH_CLIENT_SECRET: "********************************"
    GF_AUTH_GENERIC_OAUTH_SCOPES: "openid email profile offline_access roles"
    GF_AUTH_GENERIC_OAUTH_AUTH_URL: "https://keycloak.example.com/realms/example/protocol/openid-connect/auth"
    GF_AUTH_GENERIC_OAUTH_TOKEN_URL: "https://keycloak.example.com/realms/example/protocol/openid-connect/token"
    GF_AUTH_GENERIC_OAUTH_API_URL: "https://keycloak.example.com/realms/example/protocol/openid-connect/userinfo"
    GF_AUTH_GENERIC_OAUTH_ROLE_ATTRIBUTE_PATH: "contains(roles[*], 'admin') && 'Admin' || contains(roles[*], 'editor') && 'Editor' || 'Viewer'"
    GF_AUTH_SIGNOUT_REDIRECT_URL: 'https://keycloak.example.com/realms/example/protocol/openid-connect/logout?post_logout_redirect_uri=https://grafana.example.com/login&client_id=grafana-client'
...
...

signout_redirect_url

ArgoCD와 마찬가지로 로그아웃을 하면 KeyCloak의 세션도 끊어지도록 설정하고 싶었습니다.
Grafana 변수에도 GF_AUTH_SIGNOUT_REDIRECT_URL에 대해 적용을 했는데 KeyCloak 세션이 끊어지지 않는 문제가 있었는데요.
Grafana에 대한 Client 설정을 할 때, Valid post logout redirect URIs를 적용해주었더니 해결되었습니다.

본문 이미지 1

Client scopes

공식 문서만 참고해서 연동하게 되면 모든 사용자에게 Viewer 권한이 할당되는 것을 볼 수 있었습니다.

여러 가지 방법을 시도해 보면서 제가 해결한 방법은 Dedicated scopes에 'roles'라는 객체를 새로 추가하고, 모든 옵션을 활성화시켜주었더니 사용자에게 적절한 권한이 할당되었습니다.

본문 이미지 2

레퍼런스

https://honglab.tistory.com/265#fromHistory
https://kmaster.tistory.com/77
https://ksr930.tistory.com/277
https://enginnersnack.tistory.com/16

도식

100%

끌어서 이동, 두 손가락이나 휠로 확대·축소. 키보드: 화살표 이동, + − 확대·축소, 0 맞춤, Esc 닫기. 같은 내용은 그림 아래 “그림 관계를 글로 읽기”에 있다.