- ๐ ๊ฐ์
- ๐ ํ ์คํธ ํ๊ฒฝ ๊ตฌ์ฑ
- ๐ LDAP ๊ฐ๋จํ๊ฒ ์ค์ ํ๊ธฐ
- ๐ Keycloak LDAP Federate
- ๐ ์ดํ๋ฆฌ์ผ์ด์
์ถ๊ฐ (Ldap ์ฌ์ฉ์๋ก Keycloak SSO๋ก Grafana ๋ก๊ทธ์ธํ๊ธฐ)
- 1. Grafana ์ค์น
- 2. Keycloak LDAP Mapper ์ค์
- 3. Keycloak Client ์์ฑ
- 4. LDAP Group Keycloak Role ๋งคํ
- 4. Client Role์ Protocal Mapper๋ฅผ ํตํด Claim์ ์ถ๊ฐ
- 5. Grafana oauth ์ค์ (grafana.ini)
- 6. Grafana ์ LDAP ๊ณ์ ์ผ๋ก ๋ก๊ทธ์ธํ๊ธฐ
- 7. ์ถ๊ฐ) ํธ๋ฌ๋ธ์ํ "IdP did not return a role attribute, please contact your administrator"
๐ ๊ฐ์
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 ์๋ฒ๊ฐ ์ง์ํ๋ ํ์ฅ ๊ธฐ๋ฅ ์กฐํ ๋ฒํผ
- Enable the LDAPv3 password modify extended operation:
์ด์ 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์ ๋งคํ์ด ์ ๋๋์ง ํ๋ฒ ๋ ์ ๊ฒํด๋ณด์.