Jenkins·Argo CD·Keycloak·OpenLDAP SSO와 RBAC: 안전한 구성 기준

반응형

가시다님의 CI/CD 스터디를 따라 Jenkins와 Argo CD를 학습한 뒤, OpenLDAP identity를 Keycloak에 연동하고 두 도구의 권한까지 연결하는 부록 실습을 진행했다. 관련 자료는 gasida GitHub에서 확인할 수 있다.

이 글은 과거 명령을 그대로 재현하는 설치 recipe보다 현재 다시 구성할 때 놓치면 안 되는 identity·TLS·권한 경계에 초점을 맞춘다. version과 controller lifecycle이 바뀌는 Kubernetes 환경에서는 이 기준이 더 오래 남는다.

먼저 전체 흐름을 한 줄로 본다

OpenLDAP
  └─ user·group source
       ▼
Keycloak
  └─ LDAP federation + OIDC issuer
       ├─ Jenkins: authentication ─> Jenkins authorization
       └─ Argo CD: authentication ─> Argo CD RBAC·AppProject

OpenLDAP는 user와 group의 원본을 제공하고, Keycloak은 이를 federation해 OIDC token을 발급한다. Jenkins와 Argo CD는 token으로 사용자를 인증한 뒤 각 application 내부에서 별도로 권한을 결정한다.

SSO login에 성공했다는 사실은 admin 권한을 얻었다는 뜻이 아니다.

2026년 기준: ingress-nginx 실습은 그대로 복원하지 않는다

Kubernetes project는 ingress-nginx를 2026년 3월에 retire했다. 이후 release, bug fix와 security update가 제공되지 않는다. 예전 lab의 remote manifest를 다시 적용해 ingress-nginx를 설치하는 방식은 현재 운영 기준으로 적절하지 않다.

새 환경에서는 다음 순서로 선택한다.

  1. 이미 조직이 운영하는 maintained ingress·Gateway implementation이 있는지 확인한다.
  2. Gateway API 지원 범위와 TLS·OIDC routing 요구를 비교한다.
  3. controller version과 image digest를 pin하고 release note를 검토한다.
  4. chart를 render해 cluster-scoped resource, RBAC와 webhook을 review한다.
  5. staging에서 upgrade·rollback과 traffic path를 검증한다.

Gateway API는 specification이고 실제 data plane은 implementation이 제공한다. “Ingress를 Gateway로 이름만 바꾸면 된다”는 의미가 아니다. 조직의 cloud load balancer, controller와 운영 역량에 맞춰 migration한다.

실습 환경의 보안 경계

local lab이라도 다음 원칙은 유지한다.

  • internet에 직접 공개하지 않는다.
  • source IP 또는 VPN·local network로 접근 범위를 제한한다.
  • self-signed certificate라면 SAN이 포함된 lab CA를 만들고 client에 trust한다.
  • TLS certificate 검증을 끄지 않는다.
  • bootstrap credential은 매번 생성하고 secret store로 전달한다.
  • image와 chart를 floating tag로 두지 않는다.
  • 실습 종료 뒤 token·client secret·certificate를 폐기한다.

브라우저는 외부 hostname을 보고, Pod는 cluster 내부 service name을 보게 만들면 OIDC issuer가 어긋나기 쉽다. issuer는 token의 iss claim과 discovery URL, browser redirect와 server-side validation에서 동일한 canonical HTTPS URL이어야 한다.

임시 host mapping이나 고정 ClusterIP를 여러 곳에 심는 방식보다 split-horizon DNS 또는 일관된 internal DNS·routing을 설계하는 편이 낫다. ClusterIP는 service lifecycle에서 바뀔 수 있으므로 identity endpoint의 영구 주소로 하드코딩하지 않는다.

OpenLDAP: identity 원본을 안전하게 제공한다

lab에서 LDAP container 하나에 administrator bind와 평문 LDAP를 넣으면 빠르게 동작하지만 운영 model은 달라야 한다.

connection

  • LDAPS 또는 StartTLS를 사용한다.
  • Keycloak truststore가 LDAP certificate chain을 신뢰하게 한다.
  • network policy와 firewall로 Keycloak만 directory에 접근하게 한다.
  • certificate hostname과 SAN을 확인한다.

bind identity

Keycloak federation에는 directory 전체 관리자 대신 필요한 base DN을 search할 수 있는 read-only bind identity를 사용한다. password는 manifest나 UI screenshot에 남기지 않고 secret manager를 통해 주입한다.

user와 group schema

  • user base DN과 group base DN
  • username attribute와 unique ID
  • group membership attribute
  • nested group 지원 여부
  • disabled user와 deletion 처리
  • synchronization interval과 failure behavior

이 항목을 먼저 고정한다. group DN의 전체 path와 짧은 group name을 혼용하면 token claim과 application RBAC mapping이 어긋날 수 있다.

container image를 사용한다면 유지보수 상태, supported OpenLDAP version과 image digest를 확인한다. 오래된 tutorial image를 운영 표준으로 승격하지 않는다.

Keycloak: federation과 OIDC 발급을 분리해 본다

Keycloak LDAP provider는 user를 lookup하고 필요하면 local database로 import·sync한다. READ_ONLY, WRITABLE, UNSYNCED mode는 password와 attribute 변경 책임을 바꾸므로 directory owner와 합의해야 한다.

처음 구성할 때 확인할 항목은 다음과 같다.

  • LDAP connection TLS와 truststore
  • read-only bind DN의 scope
  • user·group mapper
  • import users와 sync mode
  • full sync·changed users sync 주기
  • LDAP 장애 때 login이 어떻게 실패하는지
  • deleted·disabled user가 application session에 반영되는 시점

Keycloak start-dev는 local 개발용이다. production에서는 hostname, TLS, database, proxy header, health·metric, cache와 resource를 명시한 production mode를 사용한다. bootstrap administrator는 최초 구성에만 쓰고 일상 운영 account와 분리한다.

OIDC client는 application마다 따로 만든다

Jenkins와 Argo CD에 같은 OIDC client를 공유하지 않는다. client별로 다음을 분리한다.

  • client ID와 client secret
  • exact redirect URI
  • allowed web origin
  • logout redirect
  • token lifetime
  • audience
  • group claim mapper

wildcard redirect와 broad web origin은 작은 오타를 줄여 주는 편의 기능이 아니라 redirect attack과 token 노출 범위를 넓히는 설정이 될 수 있다.

group claim은 예를 들어 /platform/developers처럼 full path를 쓸지 developers처럼 leaf name을 쓸지 하나로 정한다. token의 실제 claim을 확인한 뒤 application rule과 정확히 맞춘다. group이 token에 포함됐다는 사실만으로 권한을 주지 않고 issuer, audience, signature, expiry도 정상 검증해야 한다.

Jenkins: 인증과 권한을 반드시 두 단계로 둔다

Jenkins의 security realm은 누가 로그인했는지를 결정한다. authorization strategy는 그 사용자가 무엇을 할 수 있는지 결정한다. OIDC plugin을 설정했다고 authorization이 자동으로 안전해지지 않는다.

Keycloak login
   └─ Jenkins security realm: identity·groups 확인
          └─ Matrix or Role Strategy: permission 결정

처음 연동할 때 모든 logged-in user에게 control 권한을 주는 mode를 남기지 않는다. Matrix Authorization 또는 Role Strategy로 최소 권한을 설계한다.

  • platform admin: controller configuration과 credential 관리
  • job maintainer: 지정 folder의 job create·configure
  • developer: 지정 folder의 build·read
  • viewer: 필요한 job과 build read
  • anonymous: 기본적으로 권한 없음

plugin으로 받은 group name이 Jenkins가 해석하는 group과 같은지 test account로 확인한다. Overall/Administer, Script Console과 Credentials permission은 사실상 controller 전체를 장악할 수 있으므로 매우 좁게 준다.

lockout을 막기 위해 break-glass account와 JCasC rollback 절차를 미리 검증하되, 평상시 공유 account로 사용하지 않는다.

Argo CD: OIDC login 뒤 custom RBAC를 적용한다

Argo CD OIDC 설정은 client secret 값을 ConfigMap에 직접 넣지 않고 Secret reference를 사용한다.

data:
  oidc.config: |
    name: Keycloak
    issuer: CANONICAL_HTTPS_KEYCLOAK_ISSUER
    clientID: argocd
    clientSecret: $oidc.keycloak.clientSecret
    requestedScopes:
      - openid
      - profile
      - email
      - groups

domain과 realm은 실제 canonical issuer로 바꾼다. client secret은 별도 Secret lifecycle로 관리하며 rendered output이나 Git에 평문으로 넣지 않는다.

RBAC의 안전한 baseline은 모든 인증 사용자에게 broad read를 주는 built-in role이 아니라, 권한이 없는 custom default role에서 시작하는 것이다.

data:
  policy.default: role:authenticated
  scopes: '[groups]'
  policy.csv: |
    p, role:team-reader, applications, get, team-*/*, allow
    p, role:team-syncer, applications, sync, team-*/*, allow
    g, /platform/developers, role:team-reader
    g, /platform/release-engineers, role:team-reader
    g, /platform/release-engineers, role:team-syncer

이 policy는 구조를 설명하는 예시다. 실제 AppProject와 application naming에 맞춰 object와 action을 더 좁힌다. 특히 일반 개발 group을 built-in administrator role에 직접 연결하지 않는다.

Argo CD의 policy.default로 부여된 allow는 뒤의 deny rule로 제거할 수 없다는 점도 중요하다. default role은 정말 필요한 최소 permission만 가져야 한다.

AppProject에서 다음 경계도 함께 설정한다.

  • 허용 Git source repository
  • 배포할 destination cluster·namespace
  • cluster-scoped resource allowlist·denylist
  • namespace-scoped resource
  • project role과 sync window

SSO group mapping만으로 repository·cluster destination 경계까지 자동으로 좁아지지는 않는다.

version과 manifest를 검토하는 방법

blog에서 최신 version 숫자를 복사하기보다 배포 시점에 공식 release와 compatibility를 확인한다. installation command를 실행하기 전에 최소한 다음 evidence를 남긴다.

  • chart metadata와 dependency
  • pinned application·controller image
  • rendered manifest diff
  • cluster-scoped RBAC와 admission webhook
  • Secret reference와 external secret path
  • deprecated API
  • NetworkPolicy·Pod security context
  • rollback 대상 chart와 configuration backup

remote URL의 main branch manifest를 바로 cluster에 적용하지 않는다. content가 바뀔 수 있고 review evidence와 rollback artifact가 남지 않는다.

반드시 해 볼 negative test

성공 login 한 번으로 SSO·RBAC 검증을 끝내면 위험하다.

test 기대 결과
LDAP에 없는 user Keycloak login 거절
disabled user 신규 login 거절, session revocation 정책대로 처리
잘못된 password login 거절, 민감한 이유 노출 없음
developers group Jenkins 지정 folder read·build만 허용
developers group Argo CD 지정 project read만 허용
release group 승인된 application sync만 허용
group 없는 authenticated user default role 외 권한 없음
잘못된 issuer·audience token application에서 거절
만료 token refresh 또는 재인증, 무기한 session 금지
TLS trust 실패 connection 거절, 검증 우회로 복구하지 않음

권한을 추가한 test뿐 아니라 삭제한 뒤 즉시 또는 정한 session lifetime 안에 사라지는지도 확인한다. offboarding이 느리면 SSO의 중앙 관리 이점이 줄어든다.

운영 점검표

  • maintained ingress·Gateway implementation을 쓰는가?
  • canonical HTTPS issuer가 browser와 cluster에서 동일하게 해석되는가?
  • OpenLDAP는 TLS와 read-only bind identity를 사용하는가?
  • Keycloak LDAP sync·failure·deletion behavior가 문서화됐는가?
  • Jenkins와 Argo CD client·secret·redirect URI가 분리됐는가?
  • group claim format과 application mapping이 일치하는가?
  • Jenkins authentication과 authorization이 별도 설정됐는가?
  • Argo CD custom default role과 AppProject 경계가 최소 권한인가?
  • chart·image version을 pin하고 rendered manifest를 review하는가?
  • bootstrap·break-glass credential에 owner·expiry·audit가 있는가?
  • positive·negative·offboarding test를 자동화했는가?

이 구성의 목표는 로그인 화면을 하나로 합치는 것이 아니다. identity source, token issuer와 application authorization의 책임을 분리하고, 사용자가 맡은 일만 할 수 있게 만드는 것이다.

Argo CD 접근 제어 설계는 RBAC와 AppProject를 더 자세히 다룬다. Vault 시크릿 관리는 client secret과 short-lived credential lifecycle로 연결되고, Argo CD cluster bootstrap은 Greenfield cluster를 GitOps로 구성하는 흐름을 다룬다.

참고 자료

반응형
KEEP READING
카테고리 전체 보기 →

댓글