#1. Keystone은 어떻게 ‘사용자 로그인’을 처리하는가?
(#unscoped_token, #scoped_token, #system_token)

Upscoped_token
: 내가 누군지는 증명했는데 아직 어디서 뭐 할지는 정하지 않은 상태
- 발급 요청
사용자 → Keystone
POST /v3/auth/tokens
{
"auth": {
"identity": {
"methods": ["password"],
"password": {
"user": {
"name": "admin",
"password": "1234",
"domain": { "name": "Default" }
}
}
}
// scope 없음!
}
}
Keystone → 사용자
X-Subject-Token: abc123 ← Unscoped Token 발급
- project / domain / system 범위를 지정하지 않고 신원만 증명된 상태임.
- 실제 서비스(nova, neutron)는 사용 불가, 주로 어떤 프로젝트에 속해있는지 조회할 때 사용
- GET /v3/auth/projects 로 소속 프로젝트 목록 조회 GET /v3/auth/domains 로 접근 가능한 도메인 조회
2. 응답
HTTP 201 Created
X-Subject-Token: abc123
{
"token": {
"methods": ["password"],
"user": {
"id": "u001",
"name": "admin",
"domain": { "name": "Default" }
},
"issued_at": "2026-04-01T09:00:00Z",
"expires_at": "2026-04-01T10:00:00Z",
// roles 없음! catalog 없음! project 없음!
"audit_ids": ["abc-audit-xyz"]
}
}
keystone 내부 처리 과정
① 요청 수신
② DB에서 "Default" 도메인의 "admin" 유저 조회
③ 비밀번호 해시 비교 (bcrypt)
④ 유저가 enabled 상태인지 확인
⑤ 도메인이 enabled 상태인지 확인
⑥ Fernet 키로 토큰 암호화 생성
⑦ X-Subject-Token 헤더에 담아 반환
Scoped Token
: 특정 프로젝트(또는 도메인)에서 작업할 권한까지 받은 상태
발급 방법1: 처음부터 scope 지정
사용자 → Keystone
POST /v3/auth/tokens
{
"auth": {
"identity": { "methods": ["password"], ... },
"scope": {
"project": { ← 프로젝트 범위 지정!
"name": "myProject",
"domain": { "name": "Default" }
}
}
}
}
Keystone → 사용자
X-Subject-Token: xyz789 ← Scoped Token 발급
발급 방법2: Unscoped → Scoped 교환
Unscoped Token으로
POST /v3/auth/tokens
{
"auth": {
"identity": {
"methods": ["token"],
"token": { "id": "abc123" } ← Unscoped Token 사용
},
"scope": {
"project": { "name": "myProject" }
}
}
}
→ Scoped Token 발급!
- 특정 프로젝트 내에서 역할이 부여되어 이걸로 nova, neutron 등 실제 서비스 사용 가능함
- Token 안에 role 정보 포함
keystone 내부 처리 과정
① 신원 확인 (password 또는 token 방식)
② 해당 프로젝트가 존재하고 enabled 상태인지 확인
③ 사용자가 해당 프로젝트에 role assignment가 있는지 DB 조회
④ implied roles 계산
(예: admin 역할 → member, reader 역할도 자동 포함)
⑤ 서비스 카탈로그 생성 (해당 프로젝트 기준 endpoint 목록)
⑥ Fernet 토큰으로 암호화하여 발급
응답:
HTTP 201 Created
X-Subject-Token: xyz789
{
"token": {
"methods": ["password"],
"roles": [
{ "name": "admin" },
{ "name": "member" }, ← implied role 자동 포함
{ "name": "reader" } ← implied role 자동 포함
],
"project": {
"id": "p001",
"name": "myProject",
"domain": { "name": "Default" }
},
"catalog": [ ... ], ← 서비스 카탈로그 포함!
"issued_at": "2026-04-01T09:00:00Z",
"expires_at": "2026-04-01T10:00:00Z"
}
}
- Implied Roles (역할 상속) 구조
admin
└─ member (implied)
└─ reader (implied)
admin 역할을 받으면 member, reader도 자동으로 가짐
Nova의 policy.yaml이 "role:reader"를 요구하는 API도
admin 유저가 자동으로 통과
System Token
: 특정 프로젝트가 아니라 OpenStack 전체 시스템에 대한 권한
- 발급 과정
사용자 → Keystone
POST /v3/auth/tokens
{
"auth": {
"identity": { "methods": ["password"], ... },
"scope": {
"system": { "all": true } ← 시스템 전체 범위!
}
}
}
Keystone → 사용자
X-Subject-Token: sys999 ← System Token 발급
- 특정 프로젝트가 아닌 시스템 전체 관리용으로 주로 관리자가 사용
- 사용자/프로젝트/서비스 등 전체 리소스 관리 가능
- 응답
{
"token": {
"roles": [{ "name": "admin" }],
"system": { "all": true }, ← project 대신 system!
"catalog": [ ... ],
"expires_at": "..."
}
}
Scoped Token vs System Token 비교
항목 Project Scoped System Scoped
| 범위 | 특정 프로젝트 | 전체 시스템 |
| 주 사용자 | 일반 사용자 | 클라우드 관리자 |
| 주요 용도 | VM 생성, 네트워크 사용 등 | 서비스 카탈로그 관리, 하이퍼바이저 관리 |
| token 내 필드 | "project": {...} | "system": {"all": true} |
| 도입 버전 | v3.0~ | v3.10~ |
전체 로그인 흐름
[1단계] 사용자 ID/PW 입력
↓
[2단계] Keystone: 신원 확인
- DB에서 유저 조회
- 비밀번호 해시 비교
- 유저/도메인 enabled 체크
↓
[3단계] Unscoped Token 발급
- 신원만 증명, role/catalog 없음
- 주로 프로젝트 목록 조회에 사용
↓
[4단계] GET /v3/auth/projects 로 프로젝트 목록 조회
사용자가 프로젝트 선택 (또는 자동 선택)
↓
[5단계] Scoped Token 발급 (token → scoped 교환)
- 프로젝트 role 포함
- implied roles 계산
- 서비스 카탈로그 포함
↓
[6단계] 실제 서비스 이용
Nova, Neutron 등에 Scoped Token 제출
↓
[7단계] 각 서비스(Nova 등)가 keystonemiddleware로
토큰 검증 (Keystone 호출 또는 캐시 조회)
↓
[8단계] 서비스가 policy.yaml로 인가 판단 후 작업 수행
#2. OpenStack에서 사용자가 CLI로 openstack server list를 입력하는 순간부터 결과가 돌아오기까지 Keystone은 몇 번 관여하고 각각 어떤 역할을 하는가?
(#인증(token 발급), #인가(RBAC/policy), #서비스 카탈로그(endpoint discovery), #토큰 검증)
- 전체흐름
사용자: openstack server list 입력
↓
CLI (python-openstackclient)
↓ clouds.yaml / 환경변수에서 인증 정보 읽기
↓ ① POST /v3/auth/tokens → Keystone (1번 관여: 인증)
↓ ② Scoped Token + catalog 응답 수신
↓ ③ catalog에서 compute endpoint 파싱 (catalog는 1번 응답에 포함)
↓ ④ GET /v2.1/servers/detail → Nova (토큰 헤더 첨부)
↓
Nova (keystonemiddleware)
↓ ⑤ 캐시에 토큰 있음? → 있으면 캐시에서 바로 검증
없으면 → GET /v3/auth/tokens → Keystone (2번 관여: 검증)
↓ ⑥ 토큰 유효 + roles 수신
↓ ⑦ policy.yaml로 RBAC 판단 (Nova가 직접)
↓ ⑧ DB 조회 후 서버 목록 반환
↓
CLI: 결과 테이블 출력
↓
사용자
인증 (token 발급): keystone 1st 관여
Keystone은 사용자 id/pw를 확인(신원 검증) → 해당 프로젝트에 속해있는지 확인 → Scoped Token 생성 & 발급
- 요청 방법
# 방법1: 환경변수
export OS_USERNAME=admin
export OS_PASSWORD=1234
export OS_PROJECT_NAME=myProject
export OS_AUTH_URL=http://keystone:5000/v3
# 방법2: clouds.yaml
~/.config/openstack/clouds.yaml
---
clouds:
mycloud:
auth:
auth_url: http://keystone:5000/v3
username: admin
password: "1234"
project_name: myProject
user_domain_name: Default
project_domain_name: Default
CLI가 ~/.config/openstack/clouds.yaml 또는
환경변수(OS_USERNAME, OS_PASSWORD 등) 읽어서
POST /v3/auth/tokens
{
"auth": {
"identity": {
"methods": ["password"],
"password": {
"user": {
"name": "admin",
"password": "1234",
"domain": { "name": "Default" }
}
}
},
"scope": {
"project": { "name": "myProject" }
}
}
}
응답:
HTTP 201 Created
X-Subject-Token: abc123 ← 이게 Scoped Token
{
"token": {
"roles": [{"name": "admin"}],
"project": {"name": "myProject"},
"catalog": [ ... ], ← 서비스 카탈로그도 함께!
"expires_at": "2026-03-31T12:00:00Z"
}
}
- keystone 내부 처리
① 유저 조회 (DB)
② 비밀번호 검증 (bcrypt 해시 비교)
③ 유저/도메인/프로젝트 enabled 상태 확인
④ 프로젝트에 대한 role assignment 조회
⑤ implied roles 전개 (admin → member → reader)
⑥ 서비스 카탈로그 구성 (프로젝트 기준)
⑦ Fernet 키로 토큰 암호화 후 발급
서비스 카탈로그(Endpoint Discovery): keystone 2nd 관여 (Token 응답 안에 포함)
- 카탈로그 구조 예시
"catalog": [
{
"type": "compute", ← Nova 서비스
"name": "nova",
"endpoints": [
{ "interface": "public",
"url": "http://192.168.0.1:8774/v2.1", ← Nova 주소!
"region": "RegionOne" },
{ "interface": "internal",
"url": "http://10.0.0.1:8774/v2.1",
"region": "RegionOne" },
{ "interface": "admin",
"url": "http://10.0.0.1:8774/v2.1",
"region": "RegionOne" }
]
},
{ "type": "identity", "name": "keystone", "endpoints": [...] }, ← Keystone
{ "type": "network", "name": "neutron", "endpoints": [...] }, ← Neutron
{ "type": "image", "name": "glance", "endpoints": [...] },
{ "type": "volume", "name": "cinder", "endpoints": [...] }
]
- CLI가 카탈로그에서 “type”: "compute" 찾음
- interface: "public" endpoint URL 추출: http://192.168.0.1:8774/v2.1
- 해당 URL로 Nova API 요청 구성: GET http://192.168.0.1:8774/v2.1/servers/detail
⇒ 카탈로그 덕분에 CLI는 Nova 주소를 하드코딩할 필요가 없고 운영환경이 바뀌어도 Keystone의 catalog만 수정하면 모든 클라이언트가 자동으로 올바른 endpoint를 사용하게 됨
nova에 요청
: keystone 직접 관여 없이 CLI와 Nova 직접 통신
GET <http://192.168.0.1:8774/v2.1/servers/detail>
X-Auth-Token: abc123 ← Scoped Token 첨부
이 요청을 받으면 Nova는..
① HTTP 요청 수신
② X-Auth-Token 헤더 존재 확인
③ keystonemiddleware로 토큰 검증 위임
토큰 검증 & 인가 (RBAC/policy): keystone 3rd 관여
: 실제로 Nova 코드가 직접 Keystone을 호출하는 게 아니라 keystonemiddleware라는 WSGI 미들웨어가 Nova 앞단에서 이를 처리
- keystonemiddleware 처리 과정
요청 수신
↓
캐시(memcache)에 해당 토큰 있음?
├─ YES → 캐시에서 토큰 정보 바로 로드 (Keystone 호출 없음)
└─ NO → Keystone에 검증 요청
GET /v3/auth/tokens
X-Auth-Token: nova_service_token
X-Subject-Token: abc123 ← 검증할 사용자 토큰
↓
응답 받아 memcache에 캐싱 (기본 300초)
↓
토큰 정보를 WSGI 환경변수로 Nova 앱에 주입
HTTP_X_USER_NAME = "admin"
HTTP_X_PROJECT_ID = "p001"
HTTP_X_ROLES = "admin,member,reader"
↓
Nova 앱은 이미 검증된 정보만 사용
- keystone이 검증하는 항목
① 토큰 만료 여부 (expires_at 확인)
② 토큰 위조 여부 (Fernet 복호화로 무결성 확인)
③ 토큰이 revoke 되었는지 여부
④ 사용자/프로젝트가 여전히 enabled 상태인지
→ 이상 없으면 토큰 내용(user, roles, project) 그대로 반환
- Fernet 토큰이라면 로컬 복호화도 가능
- UUID 토큰 방식: Nova → Keystone 매번 HTTP 요청 필수
- Fernet 토큰 방식 (현재 기본값): 토큰 자체에 암호화된 정보가 담겨있음 Nova가 Fernet 키를 공유하고 있으면 로컬에서 직접 복호화하여 검증 가능 (Keystone 호출 없이 처리)
- RBAC 인가: Nova의 policy.yaml이 최종 결정
# /etc/nova/policy.yaml
"os_compute_api:servers:index": "role:admin or role:member"
"os_compute_api:servers:create": "role:admin or role:member"
"os_compute_api:servers:delete": "role:admin"
"os_compute_api:os-hypervisors:list": "role:admin"
판단흐름
keystonemiddleware가 주입한 환경변수:
HTTP_X_ROLES = "admin,member,reader"
↓
Nova가 요청된 API 파악:
GET /v2.1/servers/detail
→ "os_compute_api:servers:index" 규칙 적용
↓
policy 규칙: "role:admin or role:member"
↓
사용자 roles에 admin 있음? → YES
↓
접근 허용 → DB 조회 → 서버 목록 반환
인증 vs 인가의 역할 분리
1. Keystone이 하는 것:
이 토큰의 사용자는 admin 역할을 가진다 → 사실(fact) 전달
2. Nova가 하는 것:
admin 역할이면 이 API를 허용한다 → 인가(authorization) 판단
Keystone 관여
순서 관여 역할 누가 요청
| 1번 | 인증 | ID/PW 확인 → Token 발급 | CLI |
| 2번 | 서비스 카탈로그 | Nova endpoint 주소 제공 | CLI (Token 응답에 포함) |
| 3번 | 토큰 검증 + 인가 | Token 유효성 확인 + role 반환 | Nova |
#3. Keystone에서 연합인증을 지원하는 방법은?
(#OIDC, #Apache_HTTP_Server, #IdP)
연합 인증
: 외부 신원 제공자를 믿고 거기서 인증된 사용자에게 OpenStack 토큰을 발급해주는 것
- 필요한 이유
기존 방식: 사용자 → Keystone에 ID/PW 직접 등록
연합인증: 사용자 → Google/회사 LDAP 등 외부 IdP로 로그인
→ Keystone이 그 결과를 믿고 Token 발급
연합인증의 장점
항목 기존 방식 연합인증
| 계정 관리 | Keystone에 별도 등록 필요 | IdP에서 통합 관리 |
| 비밀번호 정책 | 따로 설정 | 회사 정책 그대로 적용 |
| SSO | 불가 | 가능 (한번 로그인으로 여러 서비스) |
| 퇴사자 처리 | Keystone 계정 별도 삭제 | IdP 계정 삭제만으로 자동 차단 |
IdP, OIDC, Apache HTTP Server
1. IdP (Identity Provider, 신원 제공자)
: 사용자의 신원을 직접 관리하고 인증해주는 외부 서버 (e.g. Google, Keycloak, Okta, 회사 내부 LDAP/AD 서버)
- 역할
① 사용자 ID/PW 관리
② 사용자 인증 처리
③ 인증 성공 시 → 인증된 증명서 발급 (JWT 토큰 또는 SAML Assertion)
2. OIDC (OpenID Connect)
: IdP가 사용자 인증 결과를 전달하는 표준 프로토콜(OAuth 2.0 기반의 인증 레이어)
- 주요 토큰
Access Token → 리소스 접근용
ID Token → 사용자 신원 정보 담긴 JWT
{
"sub": "user123",
"email": "user@example.com",
"name": "Hong Gildong",
"groups": ["admin", "developer"]
}
Refresh Token → Access Token 갱신용
- 인증 흐름
① 사용자 → Keystone 연합인증 endpoint 접속
② Apache가 요청 가로채어 → IdP 로그인 페이지로 리다이렉트
③ 사용자가 IdP에서 ID/PW 입력 → 인증 성공
④ IdP → Apache에 Authorization Code 발급
⑤ Apache가 Code를 IdP에 제출 → ID Token + Access Token 교환
⑥ Apache가 ID Token 서명 검증 (IdP의 공개키로 JWT 검증)
⑦ 검증 완료 → 사용자 정보를 환경변수로 Keystone에 전달
3. Apache HTTP Server (mod_auth_openidc)
: keystone은 OIDC를 직접 처리하지 못해서 keystone 앞단에서 OIDC 처리를 대신해주는 웹서버
keystone 대신 IdP와 통신하고 결과를 환경변수로 전달
- 역할
① IdP와 OIDC 통신 처리
② ID Token 검증
③ 사용자 정보를 환경변수로 Keystone에 전달
REMOTE_USER = "user123"
HTTP_OIDC_GROUPS = "admin,developer"
- 설정 예시
# /etc/apache2/sites-enabled/keystone.conf
ServerName keystone.example.com
# mod_auth_openidc 설정
OIDCProviderMetadataURL <https://keycloak.example.com/realms/myrealm/.well-known/openid-configuration>
OIDCClientID keystone
OIDCClientSecret supersecret
OIDCRedirectURI <https://keystone.example.com:5000/redirect_uri>
OIDCCryptoPassphrase randompassphrase
AuthType openid-connect
Require valid-user
연합 인증 전체 흐름
① 사용자가 브라우저로 접속
GET /v3/OS-FEDERATION/identity_providers/keycloak/protocols/openid/auth
② Apache(mod_auth_openidc)가 요청 가로챔
→ IdP(Keycloak) 로그인 페이지로 리다이렉트
GET <https://keycloak.example.com/realms/myrealm/protocol/openid-connect/auth>
?client_id=keystone&response_type=code&redirect_uri=...
③ 사용자가 Keycloak에서 ID/PW 입력
→ Keycloak이 인증 성공 → Authorization Code 발급
→ 사용자 브라우저를 Apache redirect_uri로 리다이렉트
④ Apache가 Code를 받아 Keycloak에서 ID Token 교환
POST .../token
grant_type=authorization_code
code=abc...
client_id=keystone
client_secret=supersecret
Keycloak 응답:
{
"access_token": "...",
"id_token": "eyJhbG...", ← JWT
"token_type": "Bearer"
}
⑤ Apache가 ID Token(JWT) 검증
- Keycloak의 공개키(JWKS endpoint)로 서명 검증
- 만료 시각(exp) 확인
- issuer(iss) 확인
⑥ 검증 완료 후 Apache가 환경변수로 Keystone에 전달
REMOTE_USER = "user123"
HTTP_OIDC_GROUPS = "admin,developer"
HTTP_OIDC_EMAIL = "user@example.com"
HTTP_OIDC_ISS = "<https://keycloak>.../myrealm"
⑦ Keystone이 환경변수 수신 → Mapping Rules 적용
"admin 그룹 소속" → "openstack-admins 그룹으로 매핑"
→ Ephemeral User 생성 (DB에 저장되지 않는 임시 사용자)
→ Unscoped Token 발급
⑧ 사용자가 Unscoped Token으로 프로젝트 목록 조회
GET /v3/auth/projects
X-Auth-Token: unscoped_token_xyz
⑨ 프로젝트 선택 → Scoped Token 발급
→ 실제 OpenStack 서비스 사용 (Nova, Neutron 등)
- ephemeral user (임시 사용자)
- 일반 로그인: Keystone DB에 유저 레코드 영구 저장
- 연합인증: 매 로그인 시 Ephemeral User를 메모리에 임시 생성 → 토큰이 만료되면 사라지고 Keystone DB에 흔적 없음
장점
- IdP에서 계정이 삭제되면 즉시 OpenStack 접근 불가
- Keystone DB에 유저 데이터 쌓이지 않음
- IdP가 단일 진실의 원천(Single Source of Truth)
Keystone Mapping Rules: IdP 그룹 → OpenStack Role 매핑
: IdP에서 받은 그룹/속성 정보를 OpenStack의 그룹/역할로 변환한다.
{
"rules": [
{
"local": [
{
"user": { "name": "{0}" },
"group": { "domain": { "name": "Default" },
"name": "openstack-admins" }
}
],
"remote": [
{ "type": "REMOTE_USER" },
{
"type": "HTTP_OIDC_GROUPS",
"any_one_of": ["admin", "openstack-admin"]
}
]
},
{
"local": [
{
"user": { "name": "{0}" },
"group": { "domain": { "name": "Default" },
"name": "openstack-members" }
}
],
"remote": [
{ "type": "REMOTE_USER" },
{
"type": "HTTP_OIDC_GROUPS",
"any_one_of": ["developer", "member"]
}
]
}
]
}
- 해석
IdP의 "admin" 또는 "openstack-admin" 그룹 소속
→ OpenStack의 "openstack-admins" 그룹으로 매핑
→ openstack-admins 그룹이 가진 role assignment 자동 상속
IdP의 "developer" 또는 "member" 그룹 소속
→ OpenStack의 "openstack-members" 그룹으로 매핑
→ openstack-members 그룹이 가진 role assignment 자동 상속
'CS > openstack' 카테고리의 다른 글
| [Openstack] Glance - 디스크 포맷별 특징 (0) | 2026.05.15 |
|---|---|
| [Openstack] 오픈스택 강의2 (1) | 2026.05.15 |
| [Openstack] 오픈스택 강의1 - 클라우드 인프라 서비스의 구조 (1) | 2026.05.15 |