OpenID Connect
OpenID Connect (OIDC) 인증 방법 (OpenID Connect (OIDC) Authentication Method)
oidc 방법을 사용해 OIDC로 Nomad에 인증해요. 이 방법은 사용자의 웹 브라우저를 통해 구성된 OIDC 공급자로 인증할 수 있게 해요. 이 방법은 Nomad UI나 명령줄에서 시작할 수 있어요.
출처: 문서
본문
전제 조건 (Prerequisites)
- OIDC 개념에 대한 기본 지식.
- Nomad Access Control List 기본.
OIDC auth method를 만드는 데 필요한 파라미터는 auth-method create를 참고해요.
JWT 검증 (JWT Verification)
Nomad는 OIDC discovery를 사용해 발급자(issuer)의 공개 키로 JWT 서명을 검증해요. Nomad는 인증 중 OIDC Discovery URL에서 먼저 키를 가져온 다음 iss와 aud 같은 OIDC 검증 기준을 적용해요.
OIDC 인증 (OIDC Authentication)
Nomad에는 두 가지 내장 OIDC 로그인 흐름이 포함돼 있어요: Nomad UI와 nomad login을 사용하는 CLI.
리다이렉트 URI (Redirect URIs)
리다이렉트 URI를 올바르게 설정하는 것은 OIDC auth method 구성의 중요한 부분이에요. Nomad와 OIDC 공급자 모두에서 이를 구성해야 하며, 두 구성이 일치해야 해요.
auth method 구성의 AllowedRedirectURIs 파라미터로 auth method의 리다이렉트 URI를 지정해요. Nomad UI와 CLI는 서로 다른 리다이렉트 URI를 사용하므로, 설치 방식에 따라 하나 또는 둘 다를 구성해야 해요.
참고: 리다이렉트 URI는 콜백 주소(callback address)와 같은 의미로 사용돼요.
UI로 로그인하려면 리다이렉트 URI http://{host:port}/ui/settings/tokens가 필요해요.
CLI로 로그인하려면 리다이렉트 URI http://{host:port}/oidc/callback이 필요해요.
OIDC 로그인
Nomad UI
- Nomad 홈페이지에서 공급자 링크 중 하나를 선택하거나
/ui/settings/tokens로 직접 이동해요. - 선택한 OIDC auth method의 버튼 중 하나를 클릭해요.
- 구성된 공급자로 인증을 완료해요.
CLI
로그인하려면 nomad login -method=oidc 명령을 실행해요. -oidc-callback-addr 플래그를 지정하지 않으면 기본값은 localhost:4649예요.
Complete the login via your OIDC provider. Launching browser to:
https://myco.auth0.com/authorize?redirect_uri=http%3A%2F%2Flocalhost%3A4649%2Foidc%2Fcallback&client_id=r3qXc2bix9eF...
브라우저가 생성된 URL로 열려 공급자의 로그인을 완료해요. 브라우저가 자동으로 열리지 않으면 URL을 수동으로 입력해요.
클라이언트 어서션 (Client assertions)
"private key JWT"라고도 알려진 클라이언트 어서션은 클라이언트 시크릿에 비해 더 안전한 인증 메커니즘을 제공해요.
단순한 시크릿을 보내는 대신 Nomad는 JWT를 구성하고 OIDC 공급자가 연관된 공개 키(또는 동일한 HMAC)로 검증할 수 있는 RSA 개인 키(또는 HMAC)로 서명해요. 이렇게 하여 Nomad는 네트워크로 비밀 정보를 전송하지 않고 유효한 OIDC 클라이언트임을 "어서션"해요.
다음은 auth method 구성의 일부 예제예요. 클라이언트 어서션 기능에만 초점을 맞춘 것이며, 완전하고 기능적인 예제는 아니에요.
Nomad 키링
Keycloak에 대한 이 예제에서 Nomad는 자체 내부 개인 키로 JWT에 서명해요. Nomad의 jwks.json 엔드포인트가 제시하는 대로 JWT의 "kid" 헤더를 키 ID로 설정해요.
이것은 아마도 가장 안전한 옵션이에요. 개인 키를 가진 것은 Nomad뿐이기 때문이에요.
"OIDCDiscoveryURL": "https://your-keycloak-instance.com/realms/nomad",
"OIDCClientID": "{your-client-id}",
"BoundAudiences": ["{your-client-id}"],
"OIDCClientAssertion": {
"Audience": ["https://your-keycloak-instance.com/realms/nomad"],
"KeySource": "nomad"
}
}
두 "audience" 필드의 구별에 주목해요:
BoundAudiences는 흔히 애플리케이션 클라이언트 ID(Nomad가 클라이언트)인데, Nomad는 이를 OIDC 공급자가 Nomad로 보내는 것과 대조해 검증해요. Nomad는 이를 사용해 요청이 Nomad를 위한 것이지 다른 클라이언트를 위한 것이 아님을 확인해요. 이는 클라이언트 어서션뿐 아니라 모든 OIDC 구성에 적용돼요.OIDCClientAssertion.Audience는 OIDC 공급자예요. 클라이언트 어서션 JWT의 대상 audience이기 때문이에요. 공급자는 이를 사용해 요청이 자신을 위한 것이지 다른 공급자를 위한 것이 아님을 확인해요. 이는 흔히OIDCDiscoveryURL과 같으므로 기본값으로 그 값을 사용해요. 이는 모든 클라이언트 어서션 구성에 적용돼요.
이 옵션은 OIDC 공급자가 직접 또는 프록시를 통해 Nomad의 JWKS에 네트워크 접근할 수 있어야 하지만, 그 외에는 Nomad의 내장 keyring을 넘어 키 자료를 추가로 관리할 필요가 없어요.
사용자 제공 키
이 Microsoft Entra ID(이전 Azure Active Directory) 예제는 Nomad와 별도로 생성된 RSA 개인 키로 JWT에 서명해요.
PemKey값은 PEM 형식의 RSA 개인 키 내용이에요.PemCert값은 키 또는 CA의 X509 인증서 내용이에요. Nomad는 이 인증서를 사용해 x5t#S256 지문 헤더를 유도해요.
"OIDCDiscoveryURL": "https://login.microsoftonline.com/{tenant}/v2.0",
"OIDCClientID": "{app-client-id}",
"BoundAudiences": ["{app-client-id}"],
"OIDCClientAssertion": {
"KeySource": "private_key",
"KeyAlgorithm": "RS256",
"PrivateKey": {
"PemKey": "[REDACTED PRIVATE KEY]",
"PemCert": "-----BEGIN CERTIFICATE-----\nMIID...the-rest-of-the-cert...GUCk=\n-----END CERTIFICATE-----"
}
}
}
이 접근 방식을 구현한다면 인증서를 Entra ID 앱에 업로드해야 한다는 점에 유의해요. 그래야 로그인 시 Entra ID가 "x5t#S256" 헤더를 사용해 저장된 공개 키를 조회할 수 있어요.
PemKeyFile 및 PemCertFile 옵션을 사용해 키와/또는 인증서를 Nomad 서버 디스크의 파일 이름으로 구성할 수도 있어요. 이 접근 방식은 auth method를 업데이트할 필요 없이 키/인증서를 회전할 수 있게 하지만, 파일은 Nomad 리더가 될 수 있는 모든 서버의 디스크에 존재해야 해요.
또는 OIDC 공급자의 요구 사항에 따라 인증서를 제공하는 대신 KeyID를 직접 제공할 수도 있어요.
이 접근 방식은 다음 시나리오에서 자체 RSA 키를 사용할 수 있게 해요:
- OIDC 공급자가 JWKS를 지원하지 않는 경우
- 네트워크 토폴로지가 프록시를 통해서도 공급자와 Nomad JWKS 사이의 연결을 허용하지 않는 경우
- 이 목적에만 특별히 사용되는 서명 키를 원하는 경우
클라이언트 시크릿 HMAC
이 예제는 OIDCClientSecret을 HMAC 키로 사용해 JWT에 서명해요. 이 구성은 JWT가 시간 바인딩(time-bound)되고 네트워크로 시크릿 자체를 보내는 대신 시크릿으로 서명되므로, 단순 클라이언트 시크릿보다 약간 더 안전해요. 일반 클라이언트 시크릿과 마찬가지로 Nomad와 OIDC 공급자 모두 동일한 시크릿을 가져야 해요.
"OIDCDiscoveryURL": "https://your-oidc-provider.com/oidc-discovery-url",
"OIDCClientID": "your-client-id",
"OIDCClientSecret": "long-secret-id-has-to-be-at-least-32-bytes",
"OIDCClientAssertion": {
"KeySource": "client_secret"
}
}
OIDC 구성 문제 해결 (OIDC Configuration Troubleshooting)
OIDC에 필요한 구성의 양은 비교적 적지만, 왜 작동하지 않는지 디버깅하는 것은 까다로울 수 있어요. 다음은 OIDC 설정을 위한 팁이에요.
- OIDC 검증 실패에 대한 중요한 정보를 위해 Nomad 서버의 로그 출력을 모니터링해요.
- Nomad와 공급자에서 리다이렉트 URI가 올바른지 확인해요. URI는 정확히 일치해야 해요. http/https, 127.0.0.1/localhost, 포트 번호, 후행 슬래시 존재 여부를 확인해요.
BoundAudiences옵션은 일반적으로 필요하지 않아요. OIDC 공급자는client_id를 audience로 사용하며 OIDC 검증은 이를 기대해요.- 필요한 모든 정보를 받기 위해 공급자에서 요구하는 범위(scope)를 확인해요.
profile과groups범위를 요청해야 하는 경우가 많으며, auth method 구성에서OIDCScopes=["profile", "groups"]로 설정할 수 있어요. - 로그에서 클레임 관련 오류가 보이면 공급자의 문서를 주의 깊게 검토하여 클레임의 이름과 구조를 확인해요. 공급자에 따라 검사할 수 있는 JWT를 얻기 위해 간단한
curlimplicit grant 요청을 구성할 수 있어요. 이 예제는 JSON 응답의access_token필드에 있는 JWT를 디코딩해요.
- 디버그 수준 로그를 사용할 때는 auth method 구성의 VerboseLogging 옵션으로 수신된 OIDC 토큰을 기록해요. 이는 공급자 설정을 디버깅하고 수신된 클레임이 기대한 것인지 확인할 때 유용해요. 클레임 데이터는 그대로 기록되며 민감한 정보를 포함할 수 있으므로 프로덕션에서 이 옵션을 사용하지 마세요.
- 클라이언트 어서션의 경우
VerboseLogging이 활성화되면 auth method가 생성될 때와 누군가 로그인 시도를 할 때 Nomad 리더 서버가 JWT를 기록해요. 이 JWT는 시간 바인딩 때문에 OIDC 공급자로 보내지는 것과 100% 동일하지는 않지만, JWT 헤더와 클레임을 확인해 OIDC 공급자의 요구 사항과 비교할 수 있어요.
클레임 매핑을 통한 신뢰된 아이덴티티 속성 (Trusted Identity Attributes via Claim Mappings)
인증 단계는 JWT 클레임의 데이터를 바인딩 규칙 선택자(selector)와 바인드 이름 보간에 사용할 신뢰된 아이덴티티 속성으로 반환할 수 있어요.
ClaimMappings 및 ListClaimMappings 속성은 Nomad가 클레임을 아이덴티티 속성에 매핑하는 방법을 제어해요. 둘 다 복사할 항목의 맵이며, 요소 형식은 "<JWT claim>":"<attribute suffix>"이에요.
단일 값을 매핑하려면 ClaimMappings을, 값 목록을 매핑하려면 ListClaimMappings을 사용해요.
이 예제에는 ClaimMappings과 ListClaimMappings이 포함돼 있어요. 이 구성은 Nomad가 JWT 클레임 "givenName"과 "surname"의 값을 각각 "value.first_name"과 "value.last_name"이라는 이름의 속성에 복사하도록 지시해요. 또한 Nomad는 JWT 클레임 "groups"의 값 목록을 "list.roles"라는 속성에 복사해야 해요.
"Name": "example-auth-method",
"Type": "<jwt|oidc>",
"Description": "Example auth method",
"Config": {
"ClaimMappings": {
"givenName": "first_name",
"surname": "last_name"
},
"ListClaimMappings": {
"groups": "roles"
}
}
}
다음 표는 결과 속성과 규칙 바인딩에서 사용할 수 있는 방법을 보여줘요:
| 속성 | 지원되는 선택자 작업 | 보간 가능 여부 |
|---|---|---|
value.first_name |
Equal, Not Equal, In, Not In, Matches, Not Matches | 예 |
value.last_name |
Equal, Not Equal, In, Not In, Matches, Not Matches | 예 |
list.groups |
In, Not In, Is Empty, Is Not Empty | 아니요 |
선택자 사용에 대한 더 많은 예제는 binding-rule 문서를 참고해요.
클레임 명세 및 JSON Pointer
ClaimMappings 및 ListClaimMappings 필드를 사용해 JWT 내부의 데이터를 가리켜요. 원하는 키가 JWT의 최상위 수준에 있다면 이름을 직접 제공할 수 있어요. 더 낮은 수준에 중첩되어 있다면 JSON Pointer를 사용할 수 있어요.
이 예제는 디코딩된 JWT 클레임을 보여줘요.
"division": "North America",
"groups": {
"primary": "Engineering",
"secondary": "Software"
},
"iss": "https://my-corp-app-name.auth0.com/",
"sub": "auth0|eiw7OWoh5ieSh7ieyahC3ief0uyuraphaengae9d",
"aud": "V1RPi2MYptMV1RPi2MYptMV1RPi2MYpt",
"iat": 1589224148,
"exp": 1589260148,
"nonce": "eKiihooH3Fah8Ieshah4leeti6ien3"
}
다음 구문을 사용해 데이터를 참조해요:
- 최상위 키: 직접 참조를 사용해요. 예를 들어
"division"은"North America"를 나타내요. - 중첩 키: JSON Pointer 구문을 사용해요. 예를 들어
"/groups/primary"는"Engineering"을 나타내요.
어떤 유효한 JSON Pointer든 선택자로 사용할 수 있어요. 구문에 대한 전체 설명은 JSON Pointer RFC를 참고해요.