OpenID Connect 인증

OpenID Connect 인증 (Authentication using OpenID Connect)

Apache Pulsar는 OAuth 2.0 프로토콜의 구현인 OpenID Connect로 클라이언트를 인증하는 것을 지원해요. 토큰 발급자 역할을 하는 OpenID Connect 호환 Identity Provider 서비스에서 얻은 액세스 토큰으로 Pulsar 클라이언트를 식별하고, 토픽에 메시지를 게시하거나 관리 작업을 수행하는 것 같은 특정 작업을 허용받는 "주체(principal)"(또는 "역할(role)")과 연결할 수 있어요.

출처: 문서

본문

OpenID Connect 구현의 소스 코드는 Apache Pulsar git 저장소의 pulsar-broker-auth-oidc 하위 모듈에 있어요.

note Pulsar의 OpenID Connect 통합은 3.0.0부터 사용할 수 있어요.

OpenID Connect 인증 흐름 (OpenID Connect Authentication Flow)

Identity Provider로 인증한 뒤 Pulsar 클라이언트는 서버에서 액세스 토큰을 받아 이 액세스 토큰을 Pulsar 서버(브로커, 프록시, WebSocket 프록시, 또는 Function Worker)에 전달해 인증해요. AuthenticationProviderOpenID 클래스를 사용할 때 Pulsar 서버는 다음 검증을 순서대로 수행해요.

  1. 토큰의 발급자 클레임(iss)이 허용된 토큰 발급자(openIDAllowedTokenIssuers) 중 하나인지 검증해요.
  2. OpenID Connect Discovery 1.0 스펙에 따라 발급자로부터 OpenID Connect discovery 문서를 가져와 캐시해요.
  3. 그 JSON 문서의 issuer 필드가 토큰의 발급자 클레임과 일치하는지 검증해요.
  4. 2단계에서 얻은 discovery 문서가 제공하는 jwks_uri에서 공개 키 세트를 가져와 캐시해요.
  5. 4단계에서 얻은 공개 키 세트로 토큰의 서명을 검증해요.
  6. aud, exp, iat, nbf 같은 토큰 클레임을 검증해요.
  7. 토큰 검증이 성공하면 Pulsar 서버는 토큰에서 sub 클레임(또는 구성된 openIDRoleClaim)을 추출해 권한 부여의 주체로 사용해요.
  8. 토큰이 만료되면 Pulsar 서버는 클라이언트에게 Identity Provider로 다시 인증하고 새 액세스 토큰을 제공하도록 요청(challenge)해요. 클라이언트가 재인증에 실패하면 Pulsar 서버는 연결을 닫아요.

브로커와 프록시에서 OpenID Connect 인증 활성화 (Enable OpenID Connect Authentication in the Broker and Proxy)

Pulsar 서버가 OpenID Connect로 클라이언트를 인증하도록 구성하려면 conf/broker.confconf/proxy.conf에 다음 파라미터를 추가해요. standalone Pulsar를 사용한다면 conf/standalone.conf 파일에 이 파라미터를 추가해요.

# Configuration to enable authentication
authenticationEnabled=true
authenticationProviders=org.apache.pulsar.broker.authentication.oidc.AuthenticationProviderOpenID
# Required settings for AuthenticationProviderOpenID
# A comma separated list of allowed, or trusted, token issuers. The token issuer is the URL of the token issuer.
PULSAR_PREFIX_openIDAllowedTokenIssuers=https://my-issuer-1.com,https://my-issuer-2.com
# The list of allowed audiences for the token. The audience is the intended recipient of the token. A token with
# at least one of these audience claims will pass the audience validation check.
PULSAR_PREFIX_openIDAllowedAudiences=audience-1,audience-2
# Optional settings (values shown are the defaults)
# The path to the file containing the trusted certificate(s) of the token issuer(s). If not set, uses the default
# trust store of the JVM.
PULSAR_PREFIX_openIDTokenIssuerTrustCertsFilePath=
# The JWT's claim to use for the role/principal during authorization.
PULSAR_PREFIX_openIDRoleClaim=sub
# The leeway, in seconds, to use when validating the token's expiration time.
PULSAR_PREFIX_openIDAcceptedTimeLeewaySeconds=0
# Cache settings
PULSAR_PREFIX_openIDCacheSize=5
PULSAR_PREFIX_openIDCacheRefreshAfterWriteSeconds=64800
PULSAR_PREFIX_openIDCacheExpirationSeconds=86400
PULSAR_PREFIX_openIDHttpConnectionTimeoutMillis=10000
PULSAR_PREFIX_openIDHttpReadTimeoutMillis=10000
# The number of seconds to wait before refreshing the JWKS when a token presents a key ID (kid claim) that is not
# in the cache. This setting is documented below.
PULSAR_PREFIX_openIDKeyIdCacheMissRefreshSeconds=300
# Whether to require that issuers use HTTPS. It is part of the OIDC spec to use HTTPS, so the default is true.
# This setting is for testing purposes and is not recommended for any production environment.
PULSAR_PREFIX_openIDRequireIssuersUseHttps=true
# A setting describing how to handle discovery of the OpenID Connect configuration document when the issuer is not
# in the list of allowed issuers. This setting is documented below.
PULSAR_PREFIX_openIDFallbackDiscoveryMode=DISABLED

note 프록시를 통해 브로커에 연결하는 클라이언트에 OIDC를 사용할 때는 브로커의 openIDAcceptedTimeLeewaySeconds를 프록시의 authenticationRefreshCheckSeconds 구성의 두 배로 설정해야 해요. 프록시가 클라이언트 토큰을 캐시하고 만료됐을 때만 갱신하기 때문이에요. 그래서 특정 경우 브로커에 새 연결을 시작할 때 토큰이 프록시에서는 아직 만료되지 않았지만 브로커에 도달할 때는 만료됐을 수 있어요. 브로커의 openIDAcceptedTimeLeewaySeconds를 올바르게 설정해 이 경계 케이스(edge case)를 완화할 수 있어요.

서명 키 회전 (Signing Key Rotation)

OpenID Connect Discovery 1.0 스펙은 AuthenticationProviderOpenID가 신뢰하는 공개 키를 발견하는 방법을 제공해요. 공개 키는 JSON Web Key (JWK) 세트, 즉 JWKS로 형식화돼요. Identity Provider가 서명 키를 회전시킬 때, JWKS 캐시가 갱신되기 전에 Identity Provider가 새 키로 토큰에 서명을 시작할 가능성이 있어요. 새 키로 서명된 토큰 거부를 피하기 위해, OIDC 인증 제공자는 토큰에 신뢰할 수 있는 발급자 클레임이 있지만 키 ID(kid 클레임)가 발급자의 캐시된 JWKS에 없을 때 JWKS를 갱신하려 시도해요. openIDKeyIdCacheMissRefreshSeconds 설정은 OIDC 인증 제공자가 JWKS 갱신을 시도하기 전에 얼마나 기다릴지 결정해요. 기본값은 300초예요. 즉 JWKS가 캐시에 최소 300초 이상 있어야 누락된 키 ID가 캐시 무효화를 트리거한다는 뜻이에요. openIDKeyIdCacheMissRefreshSeconds 설정은 매번 연결할 때 새 키 ID를 가진 토큰을 제시하는 악성 클라이언트로부터 OIDC 인증 제공자를 보호해요.

Function Worker에서 OpenID Connect 인증 활성화 (Enable OpenID Connect Authentication in the Function Worker)

Pulsar Function Worker가 OpenID Connect로 클라이언트를 인증하도록 구성하려면 conf/functions_worker.yml 파일에 다음 파라미터를 추가해요. 이 설정들에 대한 문서는 를 참고해요.

# Configuration to enable authentication
authenticationEnabled: true
authenticationProviders: ["org.apache.pulsar.broker.authentication.oidc.AuthenticationProviderOpenID"]
properties:
  openIDAllowedTokenIssuers: "https://my-issuer-1.com,https://my-issuer-2.com"
  openIDAllowedAudiences: "audience-1,audience-2"
  openIDTokenIssuerTrustCertsFilePath: ""
  openIDRoleClaim: "sub"
  openIDAcceptedTimeLeewaySeconds: 0
  openIDCacheSize: 5
  openIDCacheRefreshAfterWriteSeconds: 64800
  openIDCacheExpirationSeconds: 86400
  openIDHttpConnectionTimeoutMillis: 10000
  openIDHttpReadTimeoutMillis: 10000
  openIDRequireIssuersUseHttps: true
  openIDFallbackDiscoveryMode: "DISABLED"

Kubernetes로 커스텀 OpenID Connect 통합 활성화 (Enable Custom OpenID Connect Integration with Kubernetes)

Kubernetes에는 내장 OpenID Connect 통합이 있어서 서비스 계정 토큰 볼륨 프로젝션(Service Account Token Volume Projections)을 서명된 JWT로 파드에 쉽게 마운트해 OpenID Connect 액세스 토큰으로 사용할 수 있어요. 유일한 단점은 Kubernetes 토큰 발급자 discovery 기능이 OpenID 스펙과 완전히 호환되지 않는다는 점이에요(문서에서 명시적으로 언급). 이런 불일치를 고려해 Pulsar는 openIDFallbackDiscoveryMode 설정을 사용해 문서화된 방식으로 스펙을 기술적으로 위반하면서 Kubernetes와 통합해요.

이 모드는 openIDAllowedTokenIssuers가 구성한 허용 발급자 세트에 명시적으로 없는 발급자 클레임을 가진 JWT를 OpenID Connect 인증 제공자가 어떻게 처리할지 구성해요. 현재 구현은 Kubernetes API 서버의 OpenID Connect 기능을 사용해 추가 발급자 또는 추가 공개 키를 발견하는 것에 의존해요.

openIDFallbackDiscoveryMode의 사용 가능한 값은 DISABLED, KUBERNETES_DISCOVER_TRUSTED_ISSUER, KUBERNETES_DISCOVER_PUBLIC_KEYS예요. 요약하면 EKS는 지금 KUBERNETES_DISCOVER_TRUSTED_ISSUER가 필요하고, GKE와 AKS는 KUBERNETES_DISCOVER_PUBLIC_KEYS가 필요해요. 구현 세부 사항은 다음과 같아요.

  1. DISABLED: 추가 신뢰 발급자나 공개 키를 발견하지 않아요. 이 설정은 운영자가 신뢰할 모든 발급자를 명시적으로 허용하도록 요구해요. Kubernetes 서비스 계정 토큰 프로젝션이 동작하려면 운영자가 토큰의 iss 클레임에 있는 발급자를 명시적으로 신뢰해야 해요. 발견된 제공자 구성의 검증에서 OIDC 스펙을 명시적으로 따르는 유일한 모드이므로 기본 설정이에요.
  2. KUBERNETES_DISCOVER_TRUSTED_ISSUER: Kubernetes API 서버를 사용해 추가 신뢰 발급자를 발견해요. API 서버의 /.well-known/openid-configuration 엔드포인트에서 발급자를 얻고, 그 발급자가 제공된 토큰의 iss 클레임과 일치하는지 검증한 뒤, 그 발급자의 /.well-known/openid-configuration 엔드포인트로 jwks_uri를 발견해 신뢰 발급자로 취급해요. API 서버의 /openid/v1/jwks 엔드포인트가 제공하는 공개 키가 발급자의 jwks_uri가 제공하는 공개 키와 다른 EKS 환경에서 유용할 수 있어요. 제공자 구성을 발견하는 데 사용된 URL이 토큰의 발급자 클레임과 같지 않으므로 OIDC 호환이 실패해요.
  3. KUBERNETES_DISCOVER_PUBLIC_KEYS: Kubernetes API 서버를 사용해 추가 유효 공개 키 세트를 발견해요. API 서버의 /.well-known/openid-configuration 엔드포인트에서 발급자를 얻고, 그 발급자가 제공된 토큰의 iss 클레임과 일치하는지 검증한 뒤, Kubernetes 클라이언트로 API 서버 엔드포인트를 호출해 공개 키를 얻어요. API 서버가 커스텀 TLS와 인증을 요구하고 Kubernetes 클라이언트가 이를 자동으로 처리하므로 현재 API 서버에서 공개 키를 얻는 데 유용해요. 제공자 구성을 발견하는 데 사용된 URL이 토큰의 발급자 클레임과 같지 않으므로 OIDC 호환이 실패해요.

note KUBERNETES_DISCOVER_TRUSTED_ISSUER 또는 KUBERNETES_DISCOVER_PUBLIC_KEYS로 배포할 때 forbidden: User \"system:serviceaccount:pulsar:superuser\" cannot get path \"/.well-known/openid-configuration/\" 같은 오류를 만날 가능성이 있어요. 이 오류는 https://github.com/kubernetes/kubernetes/issues/117455의 결과로, AuthenticationProviderOpenID 플러그인이 Java Kubernetes 클라이언트를 사용해 Kubernetes API 서버에 연결하기 때문이에요. 최소 침습적인 해결책은 대상 Kubernetes 클러스터에 다음 명령을 실행하는 것이에요.

kubectl patch clusterrole system:service-account-issuer-discovery --patch '{"rules":[{"nonResourceURLs":["/.well-known/openid-configuration/","/.well-known/openid-configuration","/openid/v1/jwks/","/openid/v1/jwks"],"verbs":["get"]}]}'

Pulsar 구성 요소·애플리케이션이 프로젝션된 서비스 계정 토큰으로 다른 Pulsar 구성 요소 인증하기 (Configuring Pulsar Components and Applications to use Projected Service Account Tokens to Authenticate with other Pulsar Components)

Pulsar 구성 요소에서 서비스 계정 토큰 볼륨 프로젝션(Service Account Token Volume Projections)을 활용하려면 Kubernetes 문서에 따라 서비스 계정 토큰을 마운트한 뒤, 다음 구성으로 Pulsar 구성 요소가 토큰을 사용하도록 구성해요.

brokerClientAuthenticationPlugin=org.apache.pulsar.client.impl.auth.AuthenticationToken
brokerClientAuthenticationParameters=file:///path/to/mounted/token

Kubernetes가 토큰을 자동으로 가져오고 갱신해주므로 AuthenticationToken 클라이언트 플러그인을 사용해요. AuthenticationToken은 항상 파일시스템에서 토큰을 읽기 때문에 항상 최신 토큰을 읽는다는 것을 보장해요.

AuthenticationProviderOpenID Connect와 AuthenticationProviderToken 동시 활성화 (Enabling AuthenticationProviderOpenID Connect and AuthenticationProviderToken Simultaneously)

정적 JWT에서 OIDC 인증으로의 마이그레이션을 단순화하기 위해 AuthenticationProviderOpenIDAuthenticationProviderToken을 동시에 구성할 수 있어요. 이렇게 하면 정적 JWT에서 OIDC 토큰으로 원활하게 전환할 수 있어요. AuthenticationProviderToken은 OIDC 토큰을 제공하지 않는 클라이언트를 인증하는 데 사용되고, AuthenticationProviderOpenID는 OIDC 토큰을 제공하는 클라이언트를 인증하는 데 사용돼요.

Pulsar 클라이언트·CLI 도구에서 OIDC 인증 구성 (Configure OIDC authentication in Pulsar Clients and CLI Tools)

Pulsar OAuth2 클라이언트 플러그인은 OIDC의 클라이언트 자격 증명 흐름(Client Credentials Flow)에 의존하는 클라이언트에 사용할 수 있어요. OIDC 인증 제공자와 함께 실행되는 Pulsar 서버와 통합하도록 클라이언트를 구성하려면 OAuth2 Client 문서를 참고해요.

더 알아보기 (Learn more)