๐ŸŒฑ Infra/KeyCloak

[keycloak ๋ง›๋ณด๊ธฐ#8] Federation with LDAP (Kubernetes/minikube) + Grafana ์—ฐ๋™๊นŒ์ง€

mini_world 2025. 2. 23. 00:22

๐Ÿ“Œ ๊ฐœ์š”

 

LDAP์ด๋ž€? 

* ์ฐธ๊ณ ์ž๋ฃŒ: https://www.okta.com/identity-101/what-is-ldap/

LDAP(Lightweight Directory Access Protocol)์ด๋ž€, ๋””๋ ‰ํ„ฐ๋ฆฌ ์„œ๋น„์Šค๋ฅผ ์œ„ํ•œ ํ”„๋กœํ† ์ฝœ์ด๋‹ค.
์ผ๋ฐ˜์ ์œผ๋กœ ํšŒ์‚ฌ์—์„œ ๋ถ€์„œ ๋ฐ ์‚ฌ์šฉ์ž๋ฅผ ๊ด€๋ฆฌํ•˜๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉํ•˜๋ฉฐ, ์‚ฌ์šฉ์ž/๊ทธ๋ฃน/๊ถŒํ•œ ๋“ฑ์˜ ์ •๋ณด๋ฅผ ์ €์žฅ ๋ฐ ์กฐํšŒ ํ•  ์ˆ˜ ์žˆ๋‹ค.

dc=example,dc=org (ํšŒ์‚ฌ)
    โ”œโ”€โ”€ ou=Development (๊ฐœ๋ฐœ๋ถ€์„œ)
    โ”‚   โ”œโ”€โ”€ cn=developer1
    โ”‚   โ””โ”€โ”€ cn=developer2
    โ”œโ”€โ”€ ou=Sales (์˜์—…๋ถ€์„œ)
    โ”‚   โ”œโ”€โ”€ cn=sales1
    โ”‚   โ””โ”€โ”€ cn=sales2
    โ””โ”€โ”€ ou=HR (์ธ์‚ฌ๋ถ€์„œ)
        โ”œโ”€โ”€ cn=hr1
        โ””โ”€โ”€ cn=hr2

dc, ou, cn ๋“ฑ ๊ฐ ๊ฐ์ฒด๋“ค์— ๋Œ€ํ•ด ๊ฐ„๋‹จํžˆ ์„ค๋ช…ํ•˜์ž๋ฉด ์•„๋ž˜์™€ ๊ฐ™๋‹ค.

  • dc (Domain Component): ๋„๋ฉ”์ธ ๊ตฌ์„ฑ์š”์†Œ
  • ou (Organizational Unit): ์กฐ์ง ๋‹จ์œ„/๋ถ€์„œ
  • cn (Common Name): ์ผ๋ฐ˜์ ์ธ ์ด๋ฆ„
  • uid (User ID): ์‚ฌ์šฉ์ž ์‹๋ณ„์ž
  • gid (Group ID): ๊ทธ๋ฃน ์‹๋ณ„์ž

์„ค์น˜ ํ›„ ์‚ฌ์šฉ์ž๋ฅผ ์ƒ์„ฑํ• ๋•Œ ๊ฐ ๊ฐ์ฒด๋ฅผ ๋งŒ๋‚˜๊ฒŒ ๋  ์˜ˆ์ •์ด๋‹ˆ ์•ฝ๊ฐ„์€ ์ต์ˆ™ํ•ด์ง€๋„๋ก ํ•˜์ž!

 

LDAP๊ณผ Keycloak์˜ Federate (์—ฐ๋™)

LDAP์€ ๊ทธ๋ฃน ๋ฐ ์‚ฌ์šฉ์ž ์ •๋ณด๋ฅผ ์ €์žฅํ•˜๊ณ  ๊ด€๋ฆฌํ•˜๋Š” ์‹œ์Šคํ…œ์ด๋‹ค.
LDAP์˜ ์‚ฌ์šฉ์ž/๊ทธ๋ฃน์„ ketcloakํ•˜๊ณ  ์—ฐ๋™(federate)ํ•ด์„œ ์—ฌ๋Ÿฌ ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์— SSO(single-sing-on)ํ•  ์ˆ˜ ์žˆ๋„๋ก ๊ตฌ์„ฑํ•  ์ˆ˜ ์žˆ๋‹ค.

  • LDAP: ์‚ฌ์šฉ์ž ๋ฐ ๊ทธ๋ฃน์˜ ์ค‘์•™ ์ €์žฅ์†Œ ์—ญํ• 
  • Keycloak: ์ธ์ฆ/์ธ๊ฐ€ ์ฒ˜๋ฆฌ ๋ฐ SSO ์ œ๊ณต ์—ญํ• 

 

๋ฐ”๋กœ ํ…Œ์ŠคํŠธ ํ•ด๋ณด์ž ๐Ÿ˜

 

๐Ÿ“Œ ํ…Œ์ŠคํŠธ ํ™˜๊ฒฝ ๊ตฌ์„ฑ

1. minikube ์‹คํ–‰

helm์œผ๋กœ ์‰ฝ๊ฒŒ ์„ค์น˜ํ•  ์ˆ˜ ์žˆ๊ธฐ๋•Œ๋ฌธ์—, ๋กœ์ปฌ์— minikube๋ฅผ ์„ค์น˜ํ•œ๋‹ค.
minikube๋Š” ๊ฐ ํ™˜๊ฒฝ๋งˆ๋‹ค ์„ค์น˜ ๋งค๋‰ด์–ผ์„ ์•„์ฃผ ์ž์„ธํžˆ ์‰ฝ๊ณ  ๊ฐ„๋‹จํ•˜๊ฒŒ ์„ค๋ช…ํ•ด ๋†“์•˜์œผ๋‹ˆ ๋งํฌ๋ฅผ ๋”ฐ๋ผ์„œ ์„ค์น˜ํ•˜๋ฉด ๋œ๋‹ค.

minikube start

kubectl ๋ช…๋ น์–ด๊ฐ€ ์—†๋‹ค๋ฉด ์—ฌ๊ธฐ ๋งํฌ๋ฅผ ๋”ฐ๋ผ์„œ ์„ค์น˜ํ•œ๋‹ค.

 

2. Keycloak ์‹คํ–‰

๊ฐ€์žฅ ๋งŽ์ด ์‚ฌ์šฉํ•˜๋Š” bitnami keycloak helm chart๋ฅผ ์ด์šฉํ•œ๋‹ค.
์•„๋ž˜ ๋ช…๋ น์–ด๋กœ ์„ค์น˜ํ•œ๋‹ค.

helm install keycloak oci://registry-1.docker.io/bitnamicharts/keycloak \
  --set auth.adminUser=admin \
  --set auth.adminPassword=admin \
  --set postgresql.auth.username=bn_keycloak \
  --set postgresql.auth.password=password \
  --set postgresql.auth.database=keycloak \
  --set postgresql.auth.postgresPassword=password

์„ค์น˜๊ฐ€ ์™„๋ฃŒ ๋˜์—ˆ๋‹ค๋ฉด ํฌํŠธ ํฌ์›Œ๋”ฉ์„ ํ†ตํ•ด ๋กœ์ปฌ ๋ธŒ๋ผ์šฐ์ €์—์„œ ์ ‘๊ทผ ๊ฐ€๋Šฅํ•˜๋„๋ก ์„ค์ •ํ•œ๋‹ค. 

# Keycloak ์„œ๋น„์Šค๋กœ ํฌํŠธํฌ์›Œ๋”ฉ
kubectl port-forward svc/keycloak 8082:80 &

๋ธŒ๋ผ์šฐ์ €๋กœ localhost:8082 ์ ‘์†ํ•ด์„œ ๋กœ๊ทธ์ธ ํ•œ๋‹ค.

 

3. OpenLDAP ์‹คํ–‰

* ์ฐธ๊ณ ๋งํฌ: https://artifacthub.io/packages/helm/helm-openldap/openldap

LDAP์„ ์ œ๊ณตํ•˜๋Š” ์—ฌ๋Ÿฌ ์†”๋ฃจ์…˜์ด ์žˆ๋Š”๋ฐ, ๊ทธ ์ค‘ ๋Œ€ํ‘œ์ ์ธ๊ฒŒ Windows Active Directory์ด๊ณ , 
์˜คํ”ˆ์†Œ์Šค๋กœ๋Š” OpenLDAP์ด ์žˆ๋‹ค. ์•„๋ž˜ ๋ช…๋ ์–ด ๋Œ€๋กœ ๊ฐ„๋‹จํ•˜๊ฒŒ OpenLDAP์„ ์„ค์น˜ํ•ด๋ณด์ž.

git clone https://github.com/jp-gouin/helm-openldap.git
cd helm-openldap
helm install openldap .

์„ค์น˜๊ฐ€ ์™„๋ฃŒ ๋˜์—ˆ๋‹ค๋ฉด ํฌํŠธ ํฌ์›Œ๋”ฉ์„ ํ†ตํ•ด ๋กœ์ปฌ ๋ธŒ๋ผ์šฐ์ €์—์„œ ์ ‘๊ทผ ๊ฐ€๋Šฅํ•˜๋„๋ก ์„ค์ •ํ•œ๋‹ค. 
openLDAP webadmin์— ์ ‘์†ํ•˜๋ ค๋ฉด ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์•Œ์•„์•ผ ํ•˜๊ธฐ๋–„๋ฌธ์— ๋น„๋ฐ€๋ฒˆํ˜ธ ํ™•์ธ ๋ช…๋ น์–ด๋กœ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ํ™•์ธํ•œ๋‹ค.

# openldap admin์œผ๋กœ ํฌํŠธ ํฌ์›Œ๋”ฉ
kubectl port-forward svc/openldap-phpldapadmin 8083:80 &

# ๋น„๋ฐ€๋ฒˆํ˜ธ ํ™•์ธ ๋ช…๋ น์–ด
kubectl get secret --namespace default openldap -o jsonpath="{.data.LDAP_ADMIN_PASSWORD}" | base64 --decode; echo
kubectl get secret --namespace default openldap -o jsonpath="{.data.LDAP_CONFIG_ADMIN_PASSWORD}" | base64 --decode; echo

๋ธŒ๋ผ์šฐ์ €๋กœ localhost:8083 ์ ‘์†ํ•ด์„œ ๋กœ๊ทธ์ธ ํ•œ๋‹ค.

 

๐Ÿ“Œ LDAP ๊ฐ„๋‹จํ•˜๊ฒŒ ์„ค์ •ํ•˜๊ธฐ

์œ„ ๊ฐœ์š”์—์„œ ์–ธ๊ธ‰ํ•œ๊ฒƒ๊ณผ ๊ฐ™์ด LDAP์€ ์กฐ์ง (์‚ฌ์šฉ์ž/๊ทธ๋ฃน) ์ค‘์•™ ์ €์žฅ์†Œ ์—ญํ• ์ด๋‹ค.
OpenLDAP์— ์ ‘์†ํ•˜์—ฌ ๊ธฐ๋ณธ์ ์ธ ๊ฐ์ฒด๋“ค์„ ์ƒ์„ฑํ•ด๋ณด์ž.

1. Group ์ƒ์„ฑ

๊ทธ๋ฃน(Groups)์€ ์‚ฌ์šฉ์ž๋ฅผ ๋ฌถ๋Š” ์‚ฌ์šฉ์ž๋ฅผ ๋ฌถ๋Š” ๋‹จ์œ„์ด๋ฉฐ, ๊ถŒํ•œ๊ณผ ์ •์ฑ…์„ ๊ด€๋ฆฌํ•˜๋Š”๋ฐ ์‚ฌ์šฉํ•œ๋‹ค.
admin, read-only์ฒ˜๋Ÿผ ๊ถŒํ•œ์„ ๋ถ„๋ฆฌํ•˜๊ณ  ์ •์ฑ…์„ ์ ์šฉํ•˜๋Š” ๋‹จ์œ„๊ฐ€ ๋œ๋‹ค.

ou=groups์— ๊ณตํ†ต์œผ๋กœ ์‚ฌ์šฉํ•  ๊ทธ๋ฃน์„ 3๊ฐœ ์ƒ์„ฑํ•œ๋‹ค.

  • administrator
  • editor
  • readonlyuser

 

2. OU ์ƒ์„ฑ (Sales/Marketing ์กฐ์ง)

ou๋Š” ํŠธ๋ฆฌ ํ˜•ํƒœ๋กœ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋‹ค. ์ผ๋ฐ˜์ ์œผ๋กœ๋Š” ์กฐ์ง์˜ ๋ถ€์„œ/ํŒ€ ํ˜•ํƒœ๋กœ ๋งŽ์ด ๊ตฌ์„ฑํ•œ๋‹ค.

# OU๊ตฌ์„ฑ ์˜ˆ์‹œ
dc=example,dc=org
โ”œโ”€โ”€ ou=headquarters
โ”‚   โ”œโ”€โ”€ ou=development
โ”‚   โ”‚   โ”œโ”€โ”€ ou=web-team
โ”‚   โ”‚   โ””โ”€โ”€ ou=mobile-team
โ”‚   โ”œโ”€โ”€ ou=marketing
โ”‚   โ”‚   โ”œโ”€โ”€ ou=digital
โ”‚   โ”‚   โ””โ”€โ”€ ou=branding
โ”‚   โ””โ”€โ”€ ou=sales
โ”‚   โ”‚   โ”œโ”€โ”€ ou=domestic
โ”‚   โ”‚   โ””โ”€โ”€ ou=international
โ”œโ”€โ”€ ou=branch-busan-office
โ”‚   โ””โ”€โ”€ ou=sales
โ”‚   โ”‚   โ”œโ”€โ”€ ou=retail
โ”‚   โ”‚   โ””โ”€โ”€ ou=wholesale
โ””โ”€โ”€ ou=groups
    โ”œโ”€โ”€ ou=admin-groups
    โ””โ”€โ”€ ou=user-groups

์šฐ๋ฆฌ๋Š” ํ…Œ์ŠคํŠธ์ด๋ฏ€๋กœ ๊ฐ„๋‹จํ•˜๊ฒŒ 2๊ฐœ์˜ ou๋ฅผ ์ƒ์„ฑํ•œ๋‹ค.

  • sales
  • marketing

 

3. User ์ƒ์„ฑ 

์ด์ œ ์กฐ์ง์— ์†ํ•  ์‚ฌ์šฉ์ž๋ฅผ ๋งŒ๋“ค์–ด๋ณด์ž. sales, marketing ou์— ์‚ฌ์šฉ์ž๋ฅผ ๋งŒ๋“ค๊ณ , ์œ„์—์„œ ์ƒ์„ฑํ•œ Group์— ํ•œ๋ช…์”ฉ ๋งคํ•‘ํ•œ๋‹ค.

๊ฐ OU์— 3๋ช…์”ฉ ์ด 6๋ช…์˜ ์‚ฌ์šฉ์ž๊ฐ€ ์ƒ์„ฑ ์™„๋ฃŒ ๋˜์—ˆ๋‹ค๋ฉด, ๋‹ค์Œ๋‹จ๊ณ„๋กœ ๋„˜์–ด๊ฐ€๋ณด์ž!

 

 

 

๐Ÿ“Œ Keycloak LDAP Federate

Realm ๋‹จ์œ„๋กœ LDAP ์—ฐ๋™์ด ๊ฐ€๋Šฅํ•˜๋‹ค.
๋จผ์ €, my-LDAP์ด๋ผ๋Š” Realm์„ ์ƒ์„ฑํ•˜์ž.

1. LDAP Realm ์ƒ์„ฑ 

 

2. Keycloak LDAP ์—ฐ๋™

์ƒ์„ฑํ•œ my-LDAP realm์—์„œ LDAP์—ฐ๋™์„ ์ง„ํ–‰ํ•œ๋‹ค.
์™ผ์ชฝ ํ•˜๋‹จ  [User Federation] -> [Add Ldap providers]๋ฅผ ํด๋ฆญํ•œ๋‹ค. 

์ด์ œ ๊ฐ ํ•ญ๋ชฉ์„ ์‚ดํŽด๋ณด๋ฉด์„œ ์„ค์ •ํ•ด๋ณด์ž. ์„ค์ •ํ•ด์•ผํ•  ๋ถ€๋ถ„์€ ๋นจ๊ฐ„ ๋„ค๋ชจ๋กœ ํ‘œ์‹œํ•ด๋‘์—ˆ๊ณ , 
์„ค๋ช…์€ ๊ฐ ์บก์ณ ํ•˜์œ„์— ๋‹ฌ์•„๋†“์•˜๋‹ค.

  • Vendor: Other (openldap ์ด๋ฏ€๋กœ other ์„ ํƒ)
  • Connection URL: ldap://openldap.default:389 (LDAP ์„œ๋ฒ„ ์ฃผ์†Œ)
  • Bind DN(Distinguished Name): cn=admin,dc=example,dc=org (๊ด€๋ฆฌ์ž ๊ณ„์ •)
  • Bind Credential: (๊ด€๋ฆฌ์ž ๋น„๋ฐ€๋ฒˆํ˜ธ)

์—ฌ๊ธฐ์„œ ๋งํ•˜๋Š” DN์€ ์•„๋ž˜์™€ ๊ฐ™์€ ํ˜•ํƒœ๋กœ ์‚ฌ์šฉ๋œ๋‹ค.

cn=lee.leader,ou=marketing,ou=headquarters,dc=example,dc=org
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
     โ”‚           โ”‚              โ”‚             โ”‚         โ”‚
     โ”‚           โ”‚              โ”‚             โ”‚         โ””โ”€ ์ตœ์ƒ์œ„ ๋„๋ฉ”์ธ
     โ”‚           โ”‚              โ”‚             โ””โ”€ ๋„๋ฉ”์ธ
     โ”‚           โ”‚              โ””โ”€ ๋ณธ์‚ฌ
     โ”‚           โ””โ”€ ๋งˆ์ผ€ํŒ… ๋ถ€์„œ
     โ””โ”€ ์‚ฌ์šฉ์ž ์ด๋ฆ„

 

  • Edit mode (WRITABLE): LDAP ์„œ๋ฒ„์— ๋Œ€ํ•œ ์ ‘๊ทผ ๋ชจ๋“œ ์„ค์ • (WRITABLE/READ_ONLY/UNSYNCED)
  • Users DN: ์‚ฌ์šฉ์ž๋“ค์ด ์ €์žฅ๋œ ๊ธฐ๋ณธ DN ์œ„์น˜
  • Username LDAP attribute: ์‚ฌ์šฉ์ž ์ด๋ฆ„์œผ๋กœ ์‚ฌ์šฉ๋  LDAP ์†์„ฑ (cn (Common Name)์ด ์ผ๋ฐ˜์ ์œผ๋กœ ์‚ฌ์šฉ๋จ)
  • RDN LDAP attribute: ์—”ํŠธ๋ฆฌ์˜ ๊ณ ์œ  ์‹๋ณ„์ž๋กœ ์‚ฌ์šฉํ•  ์†์„ฑ
  • UUID LDAP attribute: ์‚ฌ์šฉ์ž์˜ ๊ณ ์œ  ์‹๋ณ„์ž๋กœ ์‚ฌ์šฉ๋˜๋Š” ์†์„ฑ (objectGUID๋Š” ์ „์—ญ์ ์œผ๋กœ ๊ณ ์œ ํ•œ ์‹๋ณ„์ž)
  • User object classes: LDAP ์‚ฌ์šฉ์ž ๊ฐ์ฒด์˜ ํด๋ž˜์Šค ์ •์˜ (person, organizationalPerson, user๋Š” ํ‘œ์ค€ ์‚ฌ์šฉ์ž ๊ฐ์ฒด ํด๋ž˜์Šค
  • User LDAP filter: ์‚ฌ์šฉ์ž ๊ฒ€์ƒ‰ ์‹œ ์ ์šฉํ•  ํ•„ํ„ฐ (ํŠน์ • ์กฐ๊ฑด์œผ๋กœ ์‚ฌ์šฉ์ž๋ฅผ ํ•„ํ„ฐ๋งํ•  ๋•Œ ์‚ฌ์šฉ)
  • Search scope (Subtree): LDAP ๊ฒ€์ƒ‰ ๋ฒ”์œ„ ์„ค์ • (Subtree: ํ•˜์œ„ ๋ชจ๋“  ๋ ˆ๋ฒจ ๊ฒ€์ƒ‰/OneLevel: ์ง๊ณ„ ํ•˜์œ„ ๋ ˆ๋ฒจ๋งŒ ๊ฒ€์ƒ‰)
  • Read timeout: LDAP ๊ฒ€์ƒ‰ ์ž‘์—…์˜ ์ œํ•œ ์‹œ๊ฐ„ ์„ค์ •
  • Pagination: ๋Œ€๋Ÿ‰์˜ ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ๋ฅผ ํŽ˜์ด์ง€ ๋‹จ์œ„๋กœ ๊ฐ€์ ธ์˜ฌ์ง€ ์„ค์ • (๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ ํšจ์œจํ™”๋ฅผ ์œ„ํ•ด ON ๊ถŒ์žฅ)
  • Referral: LDAP ๋ฆฌํผ๋Ÿด(๋‹ค๋ฅธ LDAP ์„œ๋ฒ„๋กœ์˜ ์ฐธ์กฐ) ์ฒ˜๋ฆฌ ๋ฐฉ์‹ ์„ค์ •

  • Synchronization settings (๋™๊ธฐํ™” ์„ค์ •)
    • Import users: LDAP์˜ ์‚ฌ์šฉ์ž๋ฅผ Keycloak์œผ๋กœ ๊ฐ€์ ธ์˜ฌ์ง€ ์—ฌ๋ถ€๋ฅผ ์„ค์ • (ON/OFF)
    • Sync Registrations: Keycloak์—์„œ ์ƒ์„ฑ๋œ ์‚ฌ์šฉ์ž๋ฅผ LDAP์— ๋™๊ธฐํ™”ํ• ์ง€ ์—ฌ๋ถ€๋ฅผ ์„ค์ • (ON/OFF)
    • Batch size: ํ•œ ๋ฒˆ์— ๋™๊ธฐํ™”ํ•  ์‚ฌ์šฉ์ž์˜ ์ˆ˜๋ฅผ ์ง€์ • (์„ฑ๋Šฅ๊ณผ ๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ๋Ÿ‰ ์กฐ์ ˆ)
    • Periodic full sync: ์ „์ฒด ์‚ฌ์šฉ์ž ๋ฐ์ดํ„ฐ๋ฅผ ์ฃผ๊ธฐ์ ์œผ๋กœ ๋™๊ธฐํ™”ํ• ์ง€ ์„ค์ • (ON/OFF)
    • Periodic changed users sync: ๋ณ€๊ฒฝ๋œ ์‚ฌ์šฉ์ž๋งŒ ์ฃผ๊ธฐ์ ์œผ๋กœ ๋™๊ธฐํ™”ํ• ์ง€ ์„ค์ • (ON/OFF)
  • Kerberos integration (Kerberos ํ†ตํ•ฉ)
    • Allow Kerberos authentication: Kerberos ์ธ์ฆ ํ—ˆ์šฉ ์—ฌ๋ถ€ ์„ค์ • (ON/OFF)
    • Use Kerberos for password authentication: ๋น„๋ฐ€๋ฒˆํ˜ธ ์ธ์ฆ์— Kerberos ์‚ฌ์šฉํ• ์ง€ ์—ฌ๋ถ€ ์„ค์ • (ON/OFF)

* ์ฐธ๊ณ : Kerberos๋Š” ๋„คํŠธ์›Œํฌ ์ธ์ฆ ํ”„๋กœํ† ์ฝœ๋กœ, ์‹ฑ๊ธ€ ์‚ฌ์ธ์˜จ(SSO)์„ ๊ตฌํ˜„ํ•˜๋Š” ๋ฐ ์‚ฌ์šฉ๋˜๋Š” ๋ณด์•ˆ ์‹œ์Šคํ…œ์ด๋‹ค.

  • Cache settings (์บ์‹œ ์„ค์ •)
    • Cache policy: LDAP ์กฐํšŒ ๊ฒฐ๊ณผ๋ฅผ ์บ์‹œํ•˜๋Š” ์ •์ฑ… ์„ค์ • (DEFAULT/EVICT_DAILY/EVICT_WEEKLY/MAX_LIFESPAN/NO_CACHE)
  • Advanced settings (๊ณ ๊ธ‰ ์„ค์ •)
    • Enable the LDAPv3 password modify extended operation: 
      LDAPv3์˜ ํ™•์žฅ๋œ ๋น„๋ฐ€๋ฒˆํ˜ธ ์ˆ˜์ • ์ž‘์—… ํ™œ์„ฑํ™” ์—ฌ๋ถ€ (ON/OFF)
    • Validate password policy: LDAP ์„œ๋ฒ„์˜ ๋น„๋ฐ€๋ฒˆํ˜ธ ์ •์ฑ… ๊ฒ€์ฆ ํ™œ์„ฑํ™” ์—ฌ๋ถ€ (ON/OFF)
    • Trust Email: LDAP์—์„œ ๊ฐ€์ ธ์˜จ ์ด๋ฉ”์ผ ์ฃผ์†Œ๋ฅผ ์‹ ๋ขฐํ• ์ง€ ์—ฌ๋ถ€ ์„ค์ • (ON์ด๋ฉด ์ด๋ฉ”์ผ ๊ฒ€์ฆ ์ ˆ์ฐจ ์ƒ๋žต) (ON/OFF)
    • Connection trace: LDAP ์—ฐ๊ฒฐ ๋””๋ฒ„๊น…์„ ์œ„ํ•œ ์ƒ์„ธ ๋กœ๊ทธ ํ™œ์„ฑํ™” ์—ฌ๋ถ€ (ON/OFF)
    • Query Supported Extensions: LDAP ์„œ๋ฒ„๊ฐ€ ์ง€์›ํ•˜๋Š” ํ™•์žฅ ๊ธฐ๋Šฅ ์กฐํšŒ ๋ฒ„ํŠผ

์ด์ œ LDAP ์—ฐ๋™์ด ์™„๋ฃŒ ๋˜์—ˆ๋‹ค. 

Users ํƒญ์œผ๋กœ ์ด๋™ํ•ด์„œ ์‚ฌ์šฉ์ž๊ฐ€ ๋ณด์ด๋Š”์ง€ ์ ๊ฒ€ํ•ด๋ณด์ž.

 

์‚ฌ์šฉ์ž๊ฐ€ ์ž˜ ๋ณด์ธ๋‹ค.
์—ฌ๊ธฐ๊นŒ์ง€๋งŒ ํ•˜๋ฉด ์‹ค์ œ ํ™˜๊ฒฝ์—์„œ ์–ด๋–ป๊ฒŒ ์“ฐ์ด๋Š”์ง€ ์ดํ•ดํ•˜๊ธฐ ์‰ฝ์ง€ ์•Š์œผ๋‹ˆ, ์ƒ˜ํ”Œ ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์œผ๋กœ ํ…Œ์ŠคํŠธ๋ฅผ ํ•ด๋ณด์ž.

 

 

 

๐Ÿ“Œ ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ถ”๊ฐ€ (Ldap ์‚ฌ์šฉ์ž๋กœ Keycloak SSO๋กœ Grafana ๋กœ๊ทธ์ธํ•˜๊ธฐ)

 

1. Grafana ์„ค์น˜

* ์„ค์น˜๋งํฌ: https://grafana.com/docs/grafana/latest/setup-grafana/installation/helm/

๋„๋ฆฌ ์‚ฌ์šฉํ•˜๋Š” Grafana๋ฅผ ์„ค์น˜ํ•˜๊ณ , ์‚ฌ์šฉ์ž ๋กœ๊ทธ์ธ๊นŒ์ง€ ํ…Œ์ŠคํŠธ ํ•ด๋ณด์ž.

# helm ์ €์žฅ์†Œ ๋“ฑ๋ก ๋ฐ ์—…๋ฐ์ดํŠธ
helm repo add grafana https://grafana.github.io/helm-charts
helm repo update

# helm ๋‹ค์šด๋ฐ›๊ณ  ์••์ถ•ํ’€๊ธฐ
helm fetch grafana/grafana
tar -zxvf grafana-8.10.1.tgz

# ์„ค์น˜ํ•˜๊ธฐ
cd grafana
helm install grafana .

์„ค์น˜ํ–ˆ๋‹ค๋ฉด ์—ญ์‹œ ํฌํŠธ ํฌ์›Œ๋”ฉ์„ ํ†ตํ•ด ๋กœ์ปฌ ๋ธŒ๋ผ์šฐ์ €์—์„œ ์ ‘์† ๊ฐ€๋Šฅํ•˜๋„๋ก ํ•˜์ž.

# Grafana ์„œ๋น„์Šค๋กœ ํฌํŠธํฌ์›Œ๋”ฉ
kubectl port-forward svc/grafana 8084:80 &

# Grafana admin ๋น„๋ฐ€๋ฒˆํ˜ธ ํ™•์ธ
kubectl get secret --namespace default grafana -o jsonpath="{.data.admin-password}" | base64 --decode ; echo

๋ธŒ๋ผ์šฐ์ €๋กœ localhost:8084 ์ ‘์†ํ•ด์„œ ๋กœ๊ทธ์ธ ํ•œ๋‹ค.

 

2. Keycloak LDAP Mapper ์„ค์ •

์œ„ Keycloak ๋‹จ๊ณ„์—์„œ ์‚ฌ์šฉ์ž ์ •๋ณด๋ฅผ ๊ฐ€์ ธ์˜ค๋Š”๊ฒƒ์€ ํ™•์ธํ–ˆ์ง€๋งŒ, ์ž˜ ๋ณด๋ฉด ์‚ฌ์šฉ์ž์— ๊ทธ๋ฃน์ •๋ณด๊ฐ€ ๋น ์ ธ์žˆ๋‹ค.
LDAP์˜ Group์ •๋ณด๋ฅผ ๊ฐ€์ ธ์˜ค๊ธฐ์œ„ํ•ด์„œ๋Š” ์ถ”๊ฐ€์ ์ธ Mapper๋ฅผ ์„ค์ •ํ•ด์ฃผ์–ด์•ผ ํ•œ๋‹ค.

- ๊ทธ๋ฃน ์ •๋ณด ๋™๊ธฐํ™” (group-ldap-mapper)

๋จผ์ € LDAP์˜ ๊ทธ๋ฃน์„ ๊ฐ€์ ธ์™€ ๋ณด์ž.
LDAP์—ฐ๋™ํ•œ ๊ณณ์—์„œ ์ƒˆ๋กœ์šด ๋งคํผ๋ฅผ ์ถ”๊ฐ€ํ•œ๋‹ค.

์ด ๋งคํผ๋Š” ๊ทธ๋ฃน์„ ๋™๊ธฐํ™” ์‹œํ‚ค๊ธฐ ์œ„ํ•ด ์„ค์ •ํ•œ๋‹ค.
LDAP ๊ธฐ๋ณธ์„ค์ •์ด์•„๋‹Œ openLDAP ๊ทธ๋ฃน(posixGroup)์„ค์ •์— ๋งž์ถฐ์ ธ ์žˆ์œผ๋‹ˆ AD๋“ฑ ๋‹ค๋ฅธ LDAP์„œ๋ฒ„๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค๋ฉด,
์„ค์ •๋˜์–ด์žˆ๋Š” LDAP ์ •๋ณด๋ฅผ ์ž˜ ๋ณด๊ณ  ์‚ฌ์šฉํ•ด์•ผ ํ•œ๋‹ค.

  • Name: group-ldap-mapper
  • Mapper type: group-ldap-mapper
  • Group Name LDAP Attribute: cn
  • Group Object Class: posixGroup
  • Preserve Group Ingeritance: off
  • Ignore Missing Group: off
  • Membership LDAP Attribute: gidNumber
  • Membership Attribute Type: UID
  • Membership User LDAP Attribute
  • LDAP FIlter: (๋นˆ๊ฐ’)
  • Mode: READ_ONLY
  • User Groups Retrieve Strategy: LODA_GROUPS_BY_MEMBER_ATTRIBUTE

์ด๋ ‡๊ฒŒ ์„ค์ •ํ•˜๊ณ ๋‚˜์„œ ๋™๊ธฐํ™”๋ฅผ ํ•ด์ค€๋‹ค.

OpenLDAP์—์„œ ์„ค์ •ํ–ˆ๋˜๊ฒƒ๊ณผ ๊ฐ™์ด 3๊ฐœ์˜ ๊ทธ๋ฃน์ด ๋ณด์ธ๋‹ค.

๋‹ค๋งŒ, ์•ˆ์— ๋“ค์–ด๊ฐ€๋ฉด ์œ ์ €๊ฐ€ ํ•˜๋‚˜๋„ ์—†๋Š”๋ฐ ๊ทธ ์ด์œ ๋Š” ์•„์ง ์‚ฌ์šฉ์ž์˜ gidNumber๋ฅผ ๊ฐ€์ ธ์˜ค์ง€ ์•Š์•˜๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

 

- ์‚ฌ์šฉ์ž์˜ ๊ทธ๋ฃน ์ •๋ณด ๋™๊ธฐํ™” (user-attribute-ldap-mapper)

์ด์ œ ์‚ฌ์šฉ์ž์˜ gidNumber๋ฅผ ๊ฐ€์ ธ์˜ค๊ธฐ ์œ„ํ•ด ๋งคํผ๋ฅผ ํ•˜๋‚˜ ๋” ์ƒ์„ฑํ•œ๋‹ค.

LDAP์—์„œ ์‚ฌ์šฉ์ž์™€ ๊ทธ๋ฃน ์ •๋ณด๋Š” gidMember ๊ฐ’์œผ๋กœ ๋งคํ•‘๋˜๊ธฐ๋•Œ๋ฌธ์—,
keycloak์—์„œ๋„ ์ด ์ •๋ณด๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ์–ด์•ผ ์‚ฌ์šฉ์ž๋ฅผ ๊ทธ๋ฃน์— ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ๋งคํ•‘ํ•  ์ˆ˜ ์žˆ๋‹ค.

์ด์ œ Sync all users๋ฅผ ํด๋ฆญํ•˜๋ฉด ์‚ฌ์šฉ์ž ์ •๋ณด๋ฅผ ์—…๋ฐ์ดํŠธ ํ•˜๋Š”๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

์ด์ œ ๊ทธ๋ฃน์— ์‚ฌ์šฉ์ž๊ฐ€ ํ• ๋‹น๋˜์–ด์žˆ๋‹ค!

 

3. Keycloak Client ์ƒ์„ฑ

Keycloak์œผ๋กœ Grafana ์ธ์ฆ/์ธ๊ฐ€๋ฅผ ์ฒ˜๋ฆฌํ•  ์˜ˆ์ •์ด๋‹ˆ, Client๋ฅผ ์ƒ์„ฑํ•œ๋‹ค.
๋ฌผ๋ก  ์œ„์—์„œ ๋งŒ๋“  my-LDAP Realm์—์„œ ๋งŒ๋“ค์–ด์•ผ ํ•œ๋‹ค.

  • Client type: OpenID Connect
  • ClientID: grafana
  • Name: grafana
  • Client authentication: on
  • Root URL: http://127.0.0.1:8084
  • Home URL: http://127.0.0.1:8084
  • Valid redirect URIs: http://127.0.0.1:8084/login/generic_oauth
  • Web origins: http://127.0.0.1:8084

 

4. LDAP Group Keycloak Role ๋งคํ•‘

์ด์ œ ์•ž์—์„œ ์„ค์ •ํ•œ LDAP Group๊ณผ Client์˜ Role์„ ์—ฐ๊ฒฐํ•ด์ฃผ์–ด์•ผ ํ•œ๋‹ค.
์šฐ์„  Client Roleํƒญ์—์„œ Role์„ ์ƒ์„ฑํ•œ๋‹ค.

  • GrafanaAdmin
  • GrafanaEditor
  • GrafanaViewer

์ด์ œ Group์œผ๋กœ ๊ฐ€์„œ ๊ฐ ๊ทธ๋ฃน๊ณผ ๋งค์นญ๋˜๋Š” Client Role์„ ๋งคํ•‘ํ•œ๋‹ค.

  • administrartor(LDAP Group) --[mapping]-- GrafanaAdmin(ClientRole)
  • editor(LDAP Group) --[mapping]-- GrafanaEditor(ClientRole)
  • readonlyuser(LDAP Group) --[mapping]-- GrafanaViewer(ClientRole)

์ด๋ ‡๊ฒŒ ํ•˜๋ฉด LDAP Group์„ค์ •๊ณผ Client Role์„ค์ •์ด ๋งคํ•‘๋œ๋‹ค.
๋งคํ•‘์ด ์ž˜ ๋˜์—ˆ๋Š”์ง€ ํ™•์ธํ•˜๋ ค๋ฉด [์‚ฌ์šฉ์ž -> Role mappingํƒญ]์—์„œ Hide inherited roles๋ฅผ ํ•ด์ œํ•˜๊ณ  ๋ณด๋ฉด ๋œ๋‹ค.

 

4. Client Role์„ Protocal Mapper๋ฅผ ํ†ตํ•ด Claim์— ์ถ”๊ฐ€

์ด์ œ Role์ด ์‚ฌ์šฉ์ž ์ •๋ณด์— ํฌํ•จ๋˜์–ด์žˆ๋‹ค.
์ด์ œ Protocal Mapper๋ฅผ ํ†ตํ•ด Claim์— ์ถ”๊ฐ€ํ•ด์ฃผ์–ด์•ผ ํ•œ๋‹ค. ("์–ด๋–ค ์ •๋ณด๋ฅผ" "์–ด๋–ค ํ˜•์‹์œผ๋กœ" ํ† ํฐ์— ๋„ฃ์„์ง€ ์ง€์ •)

์ด์ œ ์ •๋ง ๋๋‚ฌ๋‹ค!!
๋งˆ์ง€๋ง‰์œผ๋กœ client secret์„ ์ €์žฅํ•œ๋‹ค.

 

5. Grafana oauth ์„ค์ • (grafana.ini)

* Grafana Keycloak SSO ๋ฌธ์„œ: https://grafana.com/docs/grafana/latest/setup-grafana/configure-security/configure-authentication/keycloak/

์ด์ œ ์œ„์—์„œ ๋ฐ›์•„๋†“์€ grafana helm charts๋ฅผ ์ˆ˜์ •ํ•ด์ฃผ์–ด์•ผ ํ•œ๋‹ค.
grafana ๋””๋ ‰ํ† ๋ฆฌ ๋‚ด values.yaml์„ ์ฐพ์•„ ์•„๋ž˜ ๋‚ด์šฉ์„ ์ถ”๊ฐ€ํ•œ๋‹ค. ์ฃผ์˜ํ•ด์•ผํ• ์ ์€ ๊ธฐ์กด ์ •๋ณด๋ฅผ ๊ฑด๋“œ๋ฆฌ์ง€ ์•Š๋Š”๊ฒƒ์ด๋‹ค!

  server:
    # Grafana์˜ ์™ธ๋ถ€ ์ ‘๊ทผ URL ์„ค์ •
    root_url: http://127.0.0.1:8084

  auth.generic_oauth:
    # OAuth ์ธ์ฆ ํ™œ์„ฑํ™” ์—ฌ๋ถ€
    enabled: true
    # OAuth ์ œ๊ณต์ž ์ด๋ฆ„ ์„ค์ •
    name: Keycloak-OAuth
    # ์ƒˆ๋กœ์šด ์‚ฌ์šฉ์ž์˜ ์ž๋™ ํšŒ์›๊ฐ€์ž… ํ—ˆ์šฉ
    allow_sign_up: true
    # OAuth ํด๋ผ์ด์–ธํŠธ ID
    client_id: grafana
    # OAuth ํด๋ผ์ด์–ธํŠธ ์‹œํฌ๋ฆฟ ํ‚ค
    client_secret: "Qi3lPg5eU9HA8Hc3LHyseWuhAH936qYU"
    # ์š”์ฒญํ•  OAuth ์Šค์ฝ”ํ”„ (๊ถŒํ•œ ๋ฒ”์œ„)
    scopes: "openid email profile offline_access roles"
    # ๋กœ๊ทธ์ธ์— ์‚ฌ์šฉํ•  ์‚ฌ์šฉ์ž ์†์„ฑ ๊ฒฝ๋กœ
    login_attribute_path: "username"
    # ์‚ฌ์šฉ์ž ์ด๋ฆ„์œผ๋กœ ์‚ฌ์šฉํ•  ์†์„ฑ ๊ฒฝ๋กœ
    name_attribute_path: "name"
    # ์ด๋ฉ”์ผ๋กœ ์‚ฌ์šฉํ•  ์†์„ฑ ๊ฒฝ๋กœ
    email_attribute_path: "email"
    # OAuth ์ธ์ฆ ์—”๋“œํฌ์ธํŠธ URL (์‚ฌ์šฉ์ž์ธ์ฆ ๊ฒฝ๋กœ)
    auth_url: "http://127.0.0.1:8082/realms/my-LDAP/protocol/openid-connect/auth"
    # OAuth ํ† ํฐ ๋ฐœ๊ธ‰ ์—”๋“œํฌ์ธํŠธ URL
    token_url: http://keycloak.default.svc.cluster.local/realms/my-LDAP/protocol/openid-connect/token
    # ์‚ฌ์šฉ์ž ์ •๋ณด๋ฅผ ๊ฐ€์ ธ์˜ฌ API URL
    api_url: http://keycloak.default.svc.cluster.local/realms/my-LDAP/protocol/openid-connect/userinfo
    # ์‚ฌ์šฉ์ž ์—ญํ•  ๋งคํ•‘ ๊ทœ์น™ (GrafanaAdmin -> Admin, GrafanaViewer -> Editor, ๊ธฐํƒ€ -> Viewer)
    role_attribute_path: "contains(roles[*], 'GrafanaAdmin') && 'Admin' || contains(roles[*], 'GrafanaEditor') && 'Editor' || contains(roles[*], 'GrafanaViewer') && 'Viewer'"
    # Grafana ๊ด€๋ฆฌ์ž ๊ถŒํ•œ ํ• ๋‹น ํ—ˆ์šฉ
    allow_assign_grafana_admin: true
    # Grafana ์กฐ์ง ์—ญํ•  ๋™๊ธฐํ™” ๊ฑด๋„ˆ๋›ฐ๊ธฐ ๋น„ํ™œ์„ฑํ™”
    skip_org_role_sync: false
    # ์—„๊ฒฉํ•œ ์—ญํ•  ์†์„ฑ ๋งค์นญ ์‚ฌ์šฉ
    role_attribute_strict: true

 

์ด๋ ‡๊ฒŒ ์ˆ˜์ •ํ–ˆ๋‹ค๋ฉด ์ด์ œ helm์„ ์—…๋ฐ์ดํŠธ ํ•œ๋‹ค.

helm upgrade grafana . -f values.yaml --set assertNoLeakedSecrets=false

ํ—ฌ๋ฆ„ ์—…๋ฐ์ดํŠธ ํ›„ ํ„ฐ๋„๋ง์ด ๋Š๊ฒจ์žˆ์„ํ…๋ฐ ํ•œ๋ฒˆ ๋” ์—ฐ๊ฒฐํ•ด์ฃผ์ž!

kubectl port-forward svc/grafana 8084:80

 

6. Grafana ์— LDAP ๊ณ„์ •์œผ๋กœ ๋กœ๊ทธ์ธํ•˜๊ธฐ

์ด์ œ ๋“œ๋””์–ด ์ ‘์†ํ•ด๋ณด์ž

Sign in with Keycloak-OAuth์„ ํด๋ฆญํ•˜๋ฉด Keycloak ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€๋กœ redirect ๋œ๋‹ค.

LDAP ๊ณ„์ •์— ์ด๋ฉ”์ผ ์„ค์ •์ด ์•ˆ๋˜์–ด์žˆ๊ธฐ ๋•Œ๋ฌธ์—, ์ด๋ฉ”์ผ ์ •๋ณด๋ฅผ ์ถ”๊ฐ€ํ•˜๊ณ  Submitํ•˜๋ฉด Grafana์— ๋กœ๊ทธ์ธ ๋˜๋ฉฐ,
์‚ฌ์šฉ์ž Profile์„ ํ™•์ธํ•˜๋ฉด admin์— ๋งคํ•‘๋˜์–ด์žˆ๋Š”๊ฑธ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

 

7. ์ถ”๊ฐ€) ํŠธ๋Ÿฌ๋ธ”์ŠˆํŒ…  "IdP did not return a role attribute, please contact your administrator"

# Grafana logs
logger=context userId=0 orgId=0 uname= t=2025-02-22T20:43:15.60593592Z level=info msg="Request Completed" method=GET path=/login/generic_oauth status=302 remote_addr=127.0.0.1 time_ms=0 duration=683.167ยตs size=309 referer=http://127.0.0.1:8084/login handler=/login/:name status_source=server
logger=oauth.generic_oauth t=2025-02-22T20:43:15.709432879Z level=warn msg="Failed to extract role" err="[oauth.role_attribute_strict_violation] idP did not return a role attribute, but role_attribute_strict is set"
logger=authn.service t=2025-02-22T20:43:15.709525962Z level=error msg="Failed to authenticate request" client=auth.client.generic_oauth error="[auth.oauth.userinfo.error] failed to get user info: [oauth.role_attribute_strict_violation] could not evaluate any valid roles using IdP provided data"
logger=context userId=0 orgId=0 uname= t=2025-02-22T20:43:15.712088504Z level=info msg="Request Completed" method=GET path=/login/generic_oauth status=302 remote_addr=127.0.0.1 time_ms=28 duration=28.130417ms size=29 referer= handler=/login/:name status_source=server

๋งŒ์•ฝ IdP did not return a role attribute, please contact your administrator ์˜ค๋ฅ˜๋ฅผ ๋งŒ๋‚ฌ๋‹ค๋ฉด,
์•„๋ž˜ ๋ช…๋ น์–ด๋ฅผ ํ†ตํ•ด Claim์œผ๋กœ roles ์ถ”๊ฐ€๊ฐ€ ์ž˜ ๋œ๊ฑด์ง€ ํ•œ๋ฒˆ ๋” ์ ๊ฒ€ํ•ด๋ณด์ž.

curl -X POST "http://localhost:8082/realms/my-LDAP/protocol/openid-connect/token" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "client_id=grafana" \
-d "client_secret=Qi3lPg5eU9HA8Hc3LHyseWuhAH936qYU" \
-d "username=sadministrator" \
-d "password=1234" \
-d "grant_type=password" | jq -r '.access_token' | jwt decode -

๋งŒ์•ฝ ์ด๋ถ€๋ถ„์ด ์ •์ƒ์ด๋ผ๋ฉด helm charts values์˜ role_attribute_path๊ฐ€ ์ž˜๋ชป๋˜์—ˆ์„ ๊ฐ€๋Šฅ์„ฑ์ด ํฌ๋‹ค.
Roles์™€ ๋งคํ•‘์ด ์ž˜ ๋˜๋Š”์ง€ ํ•œ๋ฒˆ ๋” ์ ๊ฒ€ํ•ด๋ณด์ž.

728x90