본문 바로가기
CS/openstack

[Openstack] Keystone

by 민지기il 2026. 5. 15.

#1. Keystone은 어떻게 ‘사용자 로그인’을 처리하는가?

(#unscoped_token, #scoped_token, #system_token)

Upscoped_token

: 내가 누군지는 증명했는데 아직 어디서 뭐 할지는 정하지 않은 상태

  1. 발급 요청
사용자 → 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 전체 시스템에 대한 권한

  1. 발급 과정
사용자 → Keystone
  POST /v3/auth/tokens
  {
    "auth": {
      "identity": { "methods": ["password"], ... },
      "scope": {
        "system": { "all": true }  ← 시스템 전체 범위!
      }
    }
  }

Keystone → 사용자
  X-Subject-Token: sys999  ← System Token 발급
  • 특정 프로젝트가 아닌 시스템 전체 관리용으로 주로 관리자가 사용
  • 사용자/프로젝트/서비스 등 전체 리소스 관리 가능
  1. 응답
{
  "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": [...] }
]
  1. CLI가 카탈로그에서 “type”: "compute" 찾음
  2. interface: "public" endpoint URL 추출: http://192.168.0.1:8774/v2.1
  3. 해당 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 자동 상속