PKCE와 프라이빗 키 JWT를 사용한 SSO
PKCE와 프라이빗 키 JWT를 사용한 SSO (SSO with PKCE and private key JWT)
OpenID Connect (OIDC) 인증은 사용자가 단일 로그온(SSO, single sign-on) 공급자로 로그인할 때 Nomad 접근 제어 목록(ACL) 토큰을 제공하게 해줘요.
출처: 문서
본문
Nomad의 OIDC SSO 로그인 기능에는 다음 기능이 포함돼요:
- Proof Key for Code Exchange (PKCE라고도 함)는 전통적인 클라이언트 시크릿과 클라이언트 어서션(client assertions) 모두에 추가 보안을 제공해요.
- Private Key JWT (클라이언트 어서션이라고도 함)는 클라이언트 시크릿보다 더 안전한 대안이에요.
이 옵션들은 OIDC 로그인 흐름 중 추가 보안을 제공해요. 요약은 Nomad OIDC 페이지를 참조해요.
PKCE와 Private Key JWT 사용 방법을 보여주기 위해 이 튜토리얼은 Nomad 작업으로 실행되는 오픈 소스 Keycloak 아이덴티티 공급자 애플리케이션을 사용해요. Nomad와 Keycloak을 구성해 Nomad ACL에서 권한을 자동으로 부여하고, Keycloak 공급자가 PKCE를 요구하도록 활성화하며, Keycloak 공급자가 클라이언트 어서션을 사용하도록 재구성할 거예요.
사전 요구 사항 (Prerequisites)
이 튜토리얼에는 다음이 필요해요:
-
로컬에 설치된 Nomad CLI 버전 1.10.0 이상
-
ACL이 활성화되고 부트스트랩된 Nomad v1.10 이상 클러스터. 또한 다음이 필요해요:
- ACL 관리 토큰을
NOMAD_TOKEN환경 변수에 저장 - 클라이언트 노드에 Docker 설치
- ACL 관리 토큰을
-
클라이언트 어서션 인증서 생성을 위해 로컬에 설치된
openssl
Keycloak을 Nomad 작업으로 실행 (Run Keycloak as a Nomad job)
이 작업 명세서는 사용자 이름 admin, 비밀번호 admin으로 Keycloak을 개발 모드로 실행해요. 내용을 파일에 복사해 keycloak.nomad.hcl로 저장해요.
keycloak.nomad.hcl
default = "26.0.8" # newer versions have a certificate upload bug at the moment
}
job "keycloak" {
group "kc" {
network {
port "http" {
static = 8080
}
}
service {
name = "keycloak"
port = "http"
provider = "nomad"
}
task "kc" {
driver = "docker"
config {
image = "quay.io/keycloak/keycloak:${var.keycloak_tag}"
args = ["start-dev", "--hostname", "http://${NOMAD_IP_http}:${NOMAD_PORT_http}"]
ports = ["http"]
# note: keycloak config will be lost unless you use persistent storage.
#volumes = [
# # /opt/keycloak-data on the host machine needs to be owned by user 1000
# "/opt/keycloak-data:/opt/keycloak/data",
#]
}
env {
KC_BOOTSTRAP_ADMIN_USERNAME = "admin"
KC_BOOTSTRAP_ADMIN_PASSWORD = "admin"
PROXY_ADDRESS_FORWARDING = true
KC_PROXY_HEADERS = "xforwarded"
}
resources {
memory = 800
}
}
}
}
keycloak.nomad.hcl
default = "26.0.8" # newer versions have a certificate upload bug at the moment
}
job "keycloak" {
group "kc" {
network {
mode = "host"
port "http" {
static = 8080
}
}
service {
name = "keycloak"
port = "http"
provider = "nomad"
}
task "kc" {
driver = "docker"
config {
image = "quay.io/keycloak/keycloak:${var.keycloak_tag}"
args = ["start-dev", "--hostname", "http://${NOMAD_IP_http}:${NOMAD_PORT_http}"]
ports = ["http"]
network_mode = "host"
# note: keycloak config will be lost unless you use persistent storage.
#volumes = [
# # /opt/keycloak-data on the host machine needs to be owned by user 1000
# "/opt/keycloak-data:/opt/keycloak/data",
#]
}
env {
KC_BOOTSTRAP_ADMIN_USERNAME = "admin"
KC_BOOTSTRAP_ADMIN_PASSWORD = "admin"
PROXY_ADDRESS_FORWARDING = true
KC_PROXY_HEADERS = "xforwarded"
}
resources {
memory = 800
}
}
}
}
작업을 Nomad에 제출해요.
Keycloak에 로그인 (Log in to Keycloak)
Keycloak 웹 주소를 확인해 웹 브라우저에서 열어요. 다음 예시에서 주소는 192.168.1.201:8080이에요.
Job ID Address Tags Node ID Alloc ID
keycloak 192.168.1.201:8080 [] 1885c25d c7f18cc3
Keycloak 로그인 페이지에서 사용자 이름 admin, 비밀번호 admin을 입력한 다음 Sign In을 클릭해요. Keycloak은 Welcome to Keycloak 메시지와 함께 master realm의 랜딩 페이지를 로드해요.
Keycloak 구성 (Configure Keycloak)
이제 Keycloak 웹 UI를 통해 필요한 구성을 할 거예요.
리얼름 만들기 (Create a realm)
Keycloak에서 realm은 사용자, 자격 증명, 역할, 그룹 집합을 관리해요. 테넌트(tenant)와 동등해요.
- 왼쪽 상단에서 Keycloak과 그 아래 master가 표시된 드롭다운 목록을 클릭해요.
- Create realm을 클릭해요.
- Realm name에
Nomad를 입력해요. - Create를 클릭해요.
Keycloak은 새 realm을 만들고 Nomad realm의 랜딩 페이지로 리디렉션하며 Welcome to Nomad 메시지를 표시해요.
그룹 만들기 (Create a group)
Keycloak의 그룹은 사용자에게 적용할 수 있는 속성과 역할 매핑의 집합이에요. 이 그룹의 사용자는 Nomad에서 권한을 받게 돼요.
- 왼쪽 탐색에서 Groups를 클릭해요.
- Create group을 클릭해요.
- Name에
engineering을 입력해요. - Create를 클릭해요.
사용자 만들기 (Create a user)
- 왼쪽 탐색에서 Users를 클릭해요.
- Create new user를 클릭해요.
- 각 라벨이 있는 필드에 다음 값을 입력해요:
| 필드 이름 | 값 |
|---|---|
| Username | testuser |
[email protected] |
|
| First Name | Test |
| Last Name | User |
- Join Groups를 클릭해요.
engineering그룹의 체크 박스를 선택해요.- Join을 클릭해요.
- Create를 클릭해 과정을 완료해요.
다음으로 새 사용자의 비밀번호를 만들어요.
- User details 페이지 상단에서 Credentials를 클릭해요.
- Set password를 클릭해요.
- Password와 Password confirmation 필드에
password를 입력해요. - Temporary를 끄세요.
- Save를 클릭해요.
- 나타나는 프롬프트에서 Save password를 클릭해요.
클라이언트 스코프 만들기 (Create a client scope)
Nomad는 Keycloak의 ID 토큰 응답에서 그룹을 확인해요. Keycloak은 클라이언트 스코프로 이 그룹을 응답에 추가해요.
- 왼쪽 탐색에서 Client scopes를 클릭해요.
- 페이지 상단 근처에서 Create client scope를 클릭해요.
- Name에
scope-for-nomad를 입력해요. - Type 옆 드롭다운을 클릭한 다음 Default를 선택해요.
- Save를 클릭해요.
Keycloak은 Client scope details 페이지로 리디렉션해요.
- 상단 탐색에서 Mappers를 클릭해요.
- Configure a new mapper를 클릭해요.
- Name 열에서 Group Membership을 클릭해요.
- Add mapper 양식의 Name에
nomad-mapper을 입력해요. - Token Claim Name에
kc-groups를 입력해요. - Save를 클릭해요.
클라이언트 만들기 (Create the client)
Keycloak에서 클라이언트는 사용자의 인증을 요청할 수 있는 애플리케이션이나 서비스예요. Nomad는 Keycloak의 클라이언트예요.
- 왼쪽 탐색에서 Clients를 클릭해요.
- 페이지 상단 근처에서 Create client를 클릭해요.
- Client ID에
nomad-oidc를 입력해요. - 계속하려면 Next를 클릭해요.
- Client authentication을 켜세요.
- 계속하려면 Next를 클릭해요.
- Valid redirect URIs에
http://localhost:4649/oidc/callback을 입력해요. 이 URI의 포트가4649라는 점을 참고해요. 이 URI는 Nomad CLI로 로그인 시도용이에요. - 다른 URI를 추가하려면 Add valid redirect URIs를 클릭해요.
https://localhost:4646/ui/settings/tokens를 입력해요. 이 URI의 포트가4646이라는 점을 참고해요. 이 URI는 Nomad 웹 UI로 로그인 시도용이에요.
이제 Valid redirect URIs에 두 개의 URI 값이 있어요. 이 값들은 로그인 후 Keycloak이 사용자를 리디렉션하는 위치예요.
- Save를 클릭해요.
클라이언트 스코프 확인 (Verify scope for client)
- Clients 페이지 상단 탐색에서 Client scopes를 클릭해요.
- Search by name 검색 필드에
scope-for-nomad를 입력해요. - 화살표 버튼을 클릭해 검색해요.
scope-for-nomad클라이언트 스코프의 Assigned type이 Default로 설정되어 있음을 참고해요.
자격 증명 보기 (View credentials)
Nomad는 Keycloak에서 이 클라이언트를 사용할 권한이 있다는 것을 검증하기 위해 시크릿이 필요해요.
- 왼쪽 탐색에서 Clients를 클릭해요.
- Client ID 열에서 nomad-oidc를 클릭해요.
- 상단에서 Credentials를 클릭해요. Client Authenticator 아래 드롭다운은 Keycloak이 기본적으로 클라이언트 ID와 시크릿을 사용한다는 것을 보여줘요.
- Client Secret을 표시해요.
- 이 값을 복사해 저장해요. 다음 섹션에서 사용할 거예요.
Nomad 구성 (Configure Nomad)
Keycloak으로 설정을 완료하려면 Nomad에 다음 구성이 필요해요:
- Keycloak 세부 정보를 사용하는 auth method
- Nomad에서 사용자 권한을 지정하는 정책과 역할
- Keycloak 그룹을 Nomad 역할에 연결하는 바인딩 규칙
auth method 만들기 (Create an auth method)
다음 예시 구성을 파일에 복사한 다음 다음 자리 표시자 값을 대체해요:
_KEYCLOAK_ADDRESS: 이 튜토리얼의 Keycloak 로그인 섹션에서 가져온 Nomad의 Keycloak 서비스 주소._KEYCLOAK_CLIENT_SECRET: 이 튜토리얼의 자격 증명 보기 섹션에서 가져온 클라이언트 시크릿 값.
파일을 auth-method-keycloak.json으로 저장해요.
ListClaimMappings 값이 Keycloak의 kc-groups 그룹을 keycloak_groups라는 이름의 Nomad 바인딩 규칙에서 정의할 값에 매핑한다는 점을 참고해요.
auth-method-keycloak.json
"OIDCDiscoveryURL": "http://_KEYCLOAK_ADDRESS/realms/Nomad",
"OIDCClientSecret": "_KEYCLOAK_CLIENT_SECRET",
"OIDCClientID": "nomad-oidc",
"BoundAudiences": ["nomad-oidc"],
"AllowedRedirectURIs": [
"http://localhost:4649/oidc/callback",
"https://localhost:4646/ui/settings/tokens"
],
"OIDCScopes": [
"openid",
"scope-for-nomad"
],
"ListClaimMappings": {
"kc-groups": "keycloak_groups"
},
"VerboseLogging": true
}
관리 토큰만 auth method를 만들 수 있으므로 NOMAD_TOKEN 환경 변수가 Nomad 관리 토큰으로 설정되어 있는지 확인해요.
Type = management
auth method를 만들어요.
-type=OIDC \
-name=keycloak \
-default=true \
-max-token-ttl=5m \
-token-locality=global \
[email protected]
명령은 다음 예시 출력과 유사한 auth method 정보를 출력해요.
Type = OIDC
Locality = global
Max Token TTL = 5m0s
Token Name Format = ${auth_method_type}-${auth_method_name}
Default = true
Create Index = 382
Modify Index = 382
Auth Method Config
JWT Validation Public Keys = <none>
JWKS URL = <none>
OIDC Discovery URL = http://192.168.1.201:8080/realms/Nomad
OIDC Client ID = nomad-oidc
OIDC Client Secret = redacted
OIDC Enable PKCE = false
OIDC Disable UserInfo = false
OIDC Scopes = openid,scope-for-nomad
Bound audiences = nomad-oidc
Bound issuer = <none>
Allowed redirects URIs = http://localhost:4649/oidc/callback,https://localhost:4646/ui/settings/tokens
Discovery CA pem = <none>
JWKS CA cert = <none>
Signing algorithms = <none>
Expiration Leeway = 0s
NotBefore Leeway = 0s
ClockSkew Leeway = 0s
Claim mappings = <none>
List claim mappings = {kc-groups: keycloak_groups}
정책과 역할 만들기 (Create policy and role)
default 네임스페이스와 Nomad 노드에 접근을 부여하는 ACL 정책을 만들어요.
먼저 정책 파일을 만들어요. 다음 예시 정책을 파일에 복사해 engineering-policy.hcl로 저장해요.
capabilities = ["list-jobs"]
}
정책을 만들어요.
Successfully wrote "engineering-policy" ACL policy!
역할을 만들고 정책에 연결해요.
ID = 6987c982-082d-35ac-aff3-57f24e5829a0
Name = engineering-role
Description = <none>
Policies = engineering-policy
Create Index = 448
Modify Index = 448
바인딩 규칙 만들기 (Create binding rule)
/engineering Keycloak 그룹의 사용자에게 engineering-role이라는 Nomad ACL 역할을 부여해요. 전체 selector 값 주위의 작은따옴표와 /engineering 세그먼트 주위의 백틱을 참고해요. 이는 selector 표현식에 /를 포함해야 하므로 중요해요.
-auth-method=keycloak \
-bind-type=role \
-bind-name=engineering-role \
-selector='`/engineering` in list.keycloak_groups'
명령은 다음 예시 출력과 유사한 바인딩 규칙 정보를 출력해요.
Description = <none>
Auth Method = keycloak
Selector = "`/engineering` in list.keycloak_groups"
Bind Type = role
Bind Name = engineering-role
Create Time = 2025-03-25 20:39:25.923021 +0000 UTC
Modify Time = 2025-03-25 20:39:25.923021 +0000 UTC
Create Index = 451
Modify Index = 451
Nomad에 로그인 (Log in to Nomad)
터미널 세션에서 Nomad에 로그인해요. auth method에 -default=true 플래그가 포함되어 있으므로 추가 인수가 필요 없어요. 이 명령은 Keycloak으로 웹 브라우저 창을 열고 로그인하라는 프롬프트를 표시해요.
Username or email 필드에 testuser, Password 필드에 password를 입력해요.
로그인에 성공하면 Keycloak이 Signed in via your OIDC provider 메시지가 포함된 텍스트가 있는 localhost:4649/oidc/callback 페이지로 리디렉션해요.
터미널을 열고 로그인 명령의 출력을 확인해요. 이제 Keycloak을 통한 인증으로 Nomad에 로그인된 거예요.
Successfully logged in via OIDC and keycloak
Accessor ID = 3b45aa90-eeb7-7b55-11d6-fe5d597b1753
Secret ID = 83e70c65-54b4-e2a3-3158-6ab59b9bb989
Name = OIDC-keycloak
Type = client
Global = true
Create Time = 2025-03-26 14:02:27.365842 +0000 UTC
Expiry Time = 2025-03-26 14:07:27.365842 +0000 UTC
Create Index = 39
Modify Index = 39
Policies = []
Roles
ID Name
4d60501e-57c6-07ba-d06d-72f76ab1f650 engineering-role
OIDC 구성과 문제 해결 정보는 OIDC auth method 페이지를 참조해요.
토큰 테스트 (Test the token)
출력에서 Secret ID를 복사해 Nomad에 작업 상태를 조회해요. 이 예시에서 Secret ID는 83e70c65-54b4-e2a3-3158-6ab59b9bb989이에요.
ID Type Priority Status Submit Date
keycloak service 50 running 2025-03-26T09:51:59-04:00
Secret ID를 다시 복사해 keycloak 작업의 상태를 조회해요. OIDC 토큰에 필요한 권한이 없으므로 이 명령은 실패할 거예요.
Error querying job: Unexpected response code: 403 (Permission denied)
nomad acl auth-method create 명령의 -max-token-ttl=5m 플래그는 토큰 유효 기간이 5분 후에 만료되도록 설정해요. TTL이 만료된 후에는 또 다른 유효한 토큰을 얻기 위해 nomad login을 다시 실행해야 해요.
PKCE 활성화 (Enable PKCE)
PKCE는 Authorization Code 흐름의 확장으로 CSRF와 인증 코드 주입 공격을 방지해요. Nomad v1.10.0부터 Nomad는 PKCE를 지원해요.
PKCE의 추가 보안을 활용하려면 Keycloak에서 활성화해야 해요.
- Keycloak 웹 UI를 엽니다. 주소를 가져오려면
nomad service info keycloak을 실행해요. - 왼쪽 탐색에서 Clients를 클릭해요.
- Client ID 아래에서
nomad-oidc를 클릭해요. - 상단에서 Advanced를 클릭해요.
- Advanced settings까지 스크롤해요.
- Proof Key for Code Exchange Code Challenge Method를 찾아요.
- 드롭다운을 클릭한 다음 S256을 선택해요.
- 페이지 하단까지 스크롤한 다음 Save를 클릭해요.
Keycloak은 이제 모든 인증 시도가 PKCE를 사용하도록 보장해요. nomad login으로 로그인을 시도하면 이제 오류가 반환돼요.
(invalid OIDC complete-auth request: 1 error occurred:
* missing code)
PKCE를 활성화하도록 Nomad 구성 (Configure Nomad to enable PKCE)
Nomad에서 PKCE를 활성화하도록 auth method 구성 파일을 업데이트해요. OIDCEnablePKCE의 강조된 섹션을 파일에 추가한 다음 저장해요.
auth-method-keycloak.json
"OIDCDiscoveryURL": "http://_KEYCLOAK_ADDRESS/realms/Nomad",
"OIDCClientSecret": "_KEYCLOAK_CLIENT_SECRET",
"OIDCEnablePKCE": true,
"OIDCClientID": "nomad-oidc",
"BoundAudiences": ["nomad-oidc"],
"AllowedRedirectURIs": [
"http://localhost:4649/oidc/callback",
"https://localhost:4646/ui/settings/tokens"
],
"OIDCScopes": [
"openid",
"scope-for-nomad"
],
"ListClaimMappings": {
"kc-groups": "keycloak_groups"
},
"VerboseLogging": true
}
auth method를 업데이트해요. 출력에 PKCE가 활성화되어 있다는 것이 표시되는 것을 참고해요.
Name = keycloak
Type = OIDC
# ...
OIDC Discovery URL = http://192.168.1.201:8080/realms/Nomad
OIDC Client ID = nomad-oidc
OIDC Client Secret = redacted
OIDC Enable PKCE = true
OIDC Disable UserInfo = false
# ...
Nomad로 로그인해 기능이 작동하는지 확인해요.
Successfully logged in via OIDC and keycloak
# ...
클라이언트 어서션 활성화 (Enable client assertions)
클라이언트 어서션은 클라이언트가 자신의 정체성의 증명을 확인하기 위해 클라이언트 애플리케이션이 제공하는 토큰이에요.
Keycloak은 클라이언트 어서션을 signed JWT 또는 signed JWT with client secret이라고 해요. Nomad는 두 방법을 모두 지원해요. 클라이언트 어서션은 추가 보안을 제공하며 클라이언트 시크릿 사용의 대안으로 기능해요.
Nomad는 JWT를 만들고 OIDC 공급자가 Nomad의 동반 공개 키로 검증할 수 있는 프라이빗 키로 서명해요. Nomad는 네트워크를 통해 어떤 시크릿 정보도 보내지 않고 유효한 OIDC 클라이언트임을 주장해요.
- Keycloak 웹 UI로 이동해요.
- 왼쪽 탐색에서 Clients를 클릭해요.
- Client ID 아래에서
nomad-oidc를 클릭해요. - 상단에서 Credentials를 클릭해요.
- Client Authenticator 옆 드롭다운을 클릭해요.
사용 가능한 옵션을 확인해 주세요: Signed Jwt, Client Id and Secret, X509 Certificate, Signed Jwt with Client Secret.
Client Id and Secret 인증자를 설정했어요. 다음 섹션에서 클라이언트 어서션을 어떻게 설정할지 선택할 수 있어요:
- 클라이언트 시크릿으로 서명된 JWT (Signed JWT with the client secret)
- Nomad의 내장된(built-in) 프라이빗 키 쌍으로 서명된 JWT
- 새로운 프라이빗 키 쌍으로 서명된 JWT
클라이언트 시크릿으로 서명된 JWT (Signed JWT with the client secret)
이 인증자 방법은 클라이언트 시크릿을 해시 기반 메시지 인증 코드(HMAC, Hash-Based Message Authentication Code) 키로 사용해 클라이언트 어서션 JWT를 서명해요. Nomad는 서명된 JWT를 네트워크로 시크릿 자체를 보내는 대신 Keycloak에 보내요.
Keycloak은 동일한 클라이언트 시크릿을 가지고 있으며, 이를 사용해 JWT 서명을 검증해요. Keycloak은 Nomad가 클라이언트 시크릿을 가지고 있어야 하며, 그렇지 않으면 JWT 서명에 사용할 수 없었을 것임을 알고 있어요.
Signed Jwt with Client Secret 인증자를 활성화하려면 다음 단계를 완료해요:
- Keycloak 웹 UI를 엽니다.
- 왼쪽 탐색에서 Clients를 클릭해요.
- Client ID 아래에서
nomad-oidc를 클릭해요. - 상단에서 Credentials를 클릭해요.
- Client Authenticator 옆 드롭다운을 클릭한 다음 Signed Jwt with Client Secret을 선택해요.
- Signature algorithm 옆 드롭다운을 클릭한 다음 HS256을 선택해요.
- Save를 클릭해요. 나타나는 대화 상자에서 변경을 확인하려면 Yes를 클릭해요.
nomad login으로 로그인을 시도하면 Nomad의 요청에 client_assertion_type이 없어 오류가 발생할 거예요.
(failed to exchange token with provider: Provider.Exchange:
unable to exchange auth code with provider: oauth2:
"invalid_client" "Parameter client_assertion_type is missing")
Nomad에서 클라이언트 어서션을 활성화하도록 auth method 구성 파일을 업데이트해요. OIDCClientAssertion의 강조된 섹션을 파일에 추가한 다음 저장해요.
"KeySource" = "client_secret"은 Nomad가 OIDCClientSecret을 일반 클라이언트 시크릿 대신 클라이언트 어서션 HMAC으로 사용하도록 지시해요.
auth-method-keycloak.json
"OIDCDiscoveryURL": "http://_KEYCLOAK_ADDRESS/realms/Nomad",
"OIDCClientSecret": "_KEYCLOAK_CLIENT_SECRET",
"OIDCClientAssertion": {
"KeySource": "client_secret",
"Algorithm": "HS256"
},
"OIDCEnablePKCE": true,
"OIDCClientID": "nomad-oidc",
"BoundAudiences": ["nomad-oidc"],
"AllowedRedirectURIs": [
"http://localhost:4649/oidc/callback",
"https://localhost:4646/ui/settings/tokens"
],
"OIDCScopes": [
"openid",
"scope-for-nomad"
],
"ListClaimMappings": {
"kc-groups": "keycloak_groups"
},
"VerboseLogging": true
}
auth method를 업데이트해요. 출력에 이제 클라이언트 어서션 속성이 포함되는 것을 참고해요.
Name = keycloak
Type = OIDC
# ...
Auth Method Config
JWT Validation Public Keys = <none>
JWKS URL = <none>
OIDC Discovery URL = http://192.168.1.201:8080/realms/Nomad
OIDC Client ID = nomad-oidc
OIDC Client Secret = redacted
OIDC Client Assertion KeySource = client_secret
OIDC Client Assertion Algorithm = HS256
OIDC Client Assertion Audience = http://192.168.1.201:8080/realms/Nomad
OIDC Enable PKCE = true
OIDC Disable UserInfo = false
# ...
Nomad로 로그인해 기능이 작동하는지 확인해요.
Successfully logged in via OIDC and keycloak
Accessor ID = 001b3ac1-f238-3003-fc94-539fa824b9a9
Secret ID = redacted
Name = OIDC-keycloak
Type = client
# ...
Roles
ID Name
19c19cbf-dd6a-6084-bca4-5505cb9028b8 engineering-role
내장 키 쌍으로 서명된 JWT (Signed JWT with the built-in key pair)
이 인증자 방법은 JWT를 서명하고 검증하기 위해 프라이빗 키와 공개 키를 사용해요. Nomad는 프라이빗 키로 JWT를 서명하고, Keycloak은 Nomad의 공개 키로 JWT를 검증해요.
Keycloak은 JWT 서명이 Nomad에서 온 것인지 검증할 수 있도록 공개 키를 검색하기 위해 Nomad의 JWKS URL에 요청을 보내요. Keycloak은 JWKS URL에 직접 또는 프록시를 통해 접근해야 해요.
Nomad API 프록시 배포 (Deploy the Nomad API proxy)
이 섹션에서는 Nomad의 task API를 사용해 JWKS URL을 제공하는 API 프록시를 배포할 거예요. task API는 예시로 편리하지만 Windows에서는 작동하지 않는다는 점에 주의해요. 이 튜토리얼은 데모 목적으로 HTTP를 사용하지만, 프로덕션 구현에서는 유효한 TLS 인증서를 사용하고 HTTPS를 사용할 것을 권장해요.
다음 내용을 파일에 복사해 jwks-proxy.nomad.hcl로 저장해요.
jwks-proxy.nomad.hcl
# https://hub.docker.com/_/caddy
default = "2.9.1-alpine"
}
job "jwks-proxy" {
group "proxy" {
network {
mode = "host"
port "http" {
static = 4444
}
}
service {
name = "jwks-proxy"
port = "http"
provider = "nomad"
}
task "caddy" {
driver = "docker"
config {
image = "caddy:${var.caddy_tag}"
command = "caddy"
args = ["run", "--config", "${NOMAD_TASK_DIR}/Caddyfile"]
ports = ["http"]
}
identity {
env = true # makes the api.sock unix socket and Nomad token
}
template {
# https://caddyserver.com/docs/caddyfile
destination = "${NOMAD_TASK_DIR}/Caddyfile"
left_delimiter = "[[" # for clarity
right_delimiter = "]]"
data = <<EOF
:[[ env `NOMAD_PORT_http` ]] {
log {
output stdout
}
# only serve the jwks endpoint
reverse_proxy /.well-known/jwks.json {
to unix/[[ env `NOMAD_SECRETS_DIR` ]]/api.sock
# task api always requires a token, even though
# the jwks endpoint is unauthenticated.
header_up X-Nomad-Token "[[ env `NOMAD_TOKEN` ]]"
}
}
EOF
}
}
}
}
프록시 작업을 Nomad에 제출해요.
프록시의 서비스 주소를 얻어요.
Job ID Address Tags Node ID Alloc ID
jwks-proxy 192.168.1.201:4444 [] 1885c25d 668f188c
JWKS URL은 프록시의 서비스 주소와 jwks.json 파일 경로로 구성돼요. 이 예시에서 JWKS URL은 http://192.168.1.201:4444/.well-known/jwks.json이에요.
JWKS URL에 연결을 테스트해요.
{"keys":[{"use":"sig","kty":"RSA","kid":"a33560ad-0321-8006-228d-cc759335609b","alg":"RS256","n":"this-is-the-public-key","e":"AQAB"}]}
Nomad JWKS로 Keycloak 구성 (Configure Keycloak with Nomad JWKS)
Keycloak을 구성하려면 다음 단계를 수행해요:
- Keycloak 웹 UI로 이동해요.
- 왼쪽 탐색에서 Clients를 클릭해요.
- Client ID 아래에서
nomad-oidc를 클릭해요. - 상단에서 Credentials를 클릭해요.
- Client Authenticator 옆 드롭다운을 클릭한 다음 Signed Jwt를 선택해요.
- Signature algorithm 옆 드롭다운을 클릭한 다음 Any algorithm을 선택해요.
- Save를 클릭해요. 그런 다음 나타나는 대화 상자에서 Yes를 클릭해요.
- 상단에서 Keys를 클릭해요.
- Use JWKS URL을 켜세요.
- JWKS URL에 JWKS URL을 입력해요. 이 튜토리얼에서 JWKS URL은
http://192.168.1.201:4444/.well-known/jwks.json이에요. - Save를 클릭해요.
Nomad auth method 구성 업데이트 (Update the Nomad auth method configuration)
Nomad에서 이 기능을 활성화하도록 auth method 구성 파일을 업데이트해요. OIDCClientAssertion의 강조된 섹션을 파일에 추가한 다음 저장해요.
구성에 더 이상 시크릿 값이 포함되지 않는다는 점을 참고해요. Nomad가 프라이빗 키를 가지고 있고 Keycloak이 JWKS URL에서 공개 키를 검색해요.
auth-method-keycloak.json
"OIDCDiscoveryURL": "http://_KEYCLOAK_ADDRESS/realms/Nomad",
"OIDCClientAssertion": {
"KeySource": "nomad"
},
"OIDCEnablePKCE": true,
"OIDCClientID": "nomad-oidc",
"BoundAudiences": ["nomad-oidc"],
"AllowedRedirectURIs": [
"http://localhost:4649/oidc/callback",
"https://localhost:4646/ui/settings/tokens"
],
"OIDCScopes": [
"openid",
"scope-for-nomad"
],
"ListClaimMappings": {
"kc-groups": "keycloak_groups"
},
"VerboseLogging": true
}
auth method를 업데이트해요.
Name = keycloak
Type = OIDC
# ...
Auth Method Config
JWT Validation Public Keys = <none>
JWKS URL = http://192.168.1.201:4444/.well-known/jwks.json
OIDC Discovery URL = http://192.168.1.201:8080/realms/Nomad
OIDC Client ID = nomad-oidc
OIDC Client Secret = <none>
OIDC Client Assertion KeySource = nomad
OIDC Client Assertion Algorithm = RS256
OIDC Client Assertion Audience = http://192.168.1.201:8080/realms/Nomad
OIDC Enable PKCE = true
OIDC Disable UserInfo = false
# ...
Nomad로 로그인해 기능이 작동하는지 확인해요.
Successfully logged in via OIDC and keycloak
# ...
새 키 쌍으로 서명된 JWT (Signed JWT with a new key pair)
이 인증자 방법은 Nomad의 내장 키 대신 JWT 서명에 새 프라이빗·공개 키 쌍 집합을 사용해요.
이 방법으로 키를 직접 관리할 수 있지만 Nomad와 OIDC 공급자 양쪽에서 키를 수동으로 업데이트해야 해요.
키와 인증서 생성 (Generate a key and certificate)
터미널을 열고 openssl을 사용해 새 프라이빗 키를 생성해요.
프라이빗 키로 인증서를 생성해요. 환경에 맞게 -subj 플래그의 subject 값을 업데이트해야 해요.
-subj '/C=US/ST=CA/L=Example/O=ExampleOrg/OU=OrgUnit/CN=example.com' \
-out certificate.pem
Keycloak에 인증서 업로드 (Upload the certificate to Keycloak)
- Keycloak 웹 UI를 엽니다.
- 왼쪽 탐색에서 Clients를 클릭해요.
- Client ID 아래에서
nomad-oidc를 클릭해요. - 상단에서 Keys를 클릭해요.
- Use JWKS URL을 끄세요.
- Import를 클릭해요.
- Archive format 아래 드롭다운을 클릭한 다음 Certificate PEM을 선택해요.
- Import file 아래에서 Browse를 클릭해요. 인증서 파일이 있는 디렉터리로 이동해 선택해요.
- Import를 클릭해요. Keycloak이 인증서 내용으로 양식 필드를 자동으로 채워요.
- Save를 클릭해요.
키와 인증서로 Nomad 업데이트 (Update Nomad with the key and certificate)
텍스트 편집기에서 auth-method-keycloak.json을 열고 아래 강조된 OIDCClientAssertion 블록을 추가해요. 프라이빗 키 파일의 내용을 복사해 PemKey 속성 값에 붙여넣어요. 인증서 파일의 내용을 복사해 PemCert 값에 붙여넣어요.
auth-method-keycloak.json
"OIDCDiscoveryURL": "http://_KEYCLOAK_ADDRESS/realms/Nomad",
"OIDCClientAssertion": {
"KeySource": "private_key",
"PrivateKey": {
"PemKey": "[REDACTED PRIVATE KEY]",
"PemCert": "-----BEGIN CERTIFICATE-----[EXAMPLE_CERTIFICATE_CONTENTS]-----END CERTIFICATE-----"
}
},
"OIDCEnablePKCE": true,
"OIDCClientID": "nomad-oidc",
"BoundAudiences": ["nomad-oidc"],
"AllowedRedirectURIs": [
"http://localhost:4649/oidc/callback",
"https://localhost:4646/ui/settings/tokens"
],
"OIDCScopes": [
"openid",
"scope-for-nomad"
],
"ListClaimMappings": {
"kc-groups": "keycloak_groups"
},
"VerboseLogging": true
}
내용은 한 줄에 있어야 하며 줄바꿈 문자를 포함할 수 없어요. 터미널에서 파일을 편집한다면 tr 명령으로 줄바꿈 문자를 제거할 수 있어요.
두 값을 모두 업데이트한 후 파일을 저장해요.
Nomad에서 이 기능을 활성화하도록 auth method 구성을 업데이트해요. Nomad는 인증서를 사용해 x5t#S256이라는 특수 JWT 헤더를 파생하며, Keycloak은 이를 사용해 저장한 공개 키를 조회해요.
Name = keycloak
Type = OIDC
# ...
Auth Method Config
JWT Validation Public Keys = <none>
JWKS URL = http://192.168.1.201:4444/.well-known/jwks.json
OIDC Discovery URL = http://192.168.1.201:8080/realms/Nomad
OIDC Client ID = nomad-oidc
OIDC Client Secret = <none>
OIDC Client Assertion KeySource = private_key
OIDC Client Assertion Algorithm = RS256
OIDC Client Assertion Audience = http://192.168.1.201:8080/realms/Nomad
OIDC Enable PKCE = true
OIDC Disable UserInfo = false
# ...
Nomad로 로그인해 기능이 작동하는지 확인해요.
Successfully logged in via OIDC and keycloak
# ...
다른 클라이언트 어서션 옵션을 시도해 보려면 Keycloak을 클라이언트 어서션으로 구성 섹션으로 다시 이동해요.
다음 단계 (Next Steps)
이 튜토리얼에서는 Nomad와 Keycloak을 구성해 Nomad의 ACL 시스템으로 권한을 자동으로 부여하는 방법을 배웠어요. 그런 다음 Keycloak 공급자가 PKCE를 요구하도록 활성화하고 Keycloak을 클라이언트 어서션을 사용하도록 재구성했어요.
학습을 계속하려면 다음 자료를 확인해 주세요:
- Nomad의 JWT Auth Method에 대해 더 알아보세요.
- Nomad의 OIDC Auth method에 대해 더 알아보세요.
- Nomad와 함께 Vault를 OIDC 공급자로 사용하는 방법을 배워보세요.