Keystone 深度解析:OpenStack 统一认证与授权体系

Keystone 深度解析:OpenStack 统一认证与授权体系

定位与职责

Keystone 是 OpenStack 的身份认证服务,所有其他组件都依赖它完成:

  • 认证(Authentication):你是谁?验证用户名/密码或 Token
  • 授权(Authorization):你能做什么?基于 RBAC 的权限控制
  • 服务目录(Service Catalog):各组件的 API Endpoint 注册中心

没有 Keystone,OpenStack 的任何 API 都无法调用。


核心概念模型

1
2
3
4
5
Domain(域)
└── Project(项目/租户)
└── User(用户)
└── Role Assignment(角色绑定)
└── Role(角色)→ Policy Rules(权限规则)

Domain

  • 最高级别的隔离单元,对应一个组织或部门
  • 默认域:Default
  • 不同 Domain 下的用户名可以重复

Project(Tenant)

  • 资源隔离的基本单位,所有资源(VM、网络、卷)都归属于某个 Project
  • 旧版本叫 Tenant,新版本统一叫 Project

User & Group

  • User:具体的操作主体
  • Group:用户集合,可以批量绑定角色

Role

  • 角色是权限的载体,通过 policy.yaml 定义每个角色能执行哪些 API 操作
  • 内置角色:adminmemberreader(Keystone 默认三级角色体系)

Token 机制

Fernet Token(当前主流)

1
Fernet Token = Base64(IV + Timestamp + UserID + ProjectID + ... ) + HMAC签名

特点:

  • 无状态:Token 本身携带所有信息,不需要数据库查询验证
  • 轻量:约 255 字节
  • 有时效:默认 1 小时过期
  • 密钥轮转:通过 fernet_keys/ 目录管理密钥,支持滚动更新
1
2
3
4
5
# 密钥目录结构
/etc/keystone/fernet-keys/
├── 0 # 备用密钥(解密用)
├── 1 # 主密钥(加密用)
└── 2 # 备用密钥(解密用)

Token 获取流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
POST /v3/auth/tokens
{
"auth": {
"identity": {
"methods": ["password"],
"password": {
"user": {"name": "admin", "password": "xxx", "domain": {"name": "Default"}}
}
},
"scope": {"project": {"name": "myproject", "domain": {"name": "Default"}}}
}
}

响应 Header: X-Subject-Token: gAAAAABh...(Fernet Token)

源码关键路径

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
keystone/
├── api/ # Flask-RESTful API 路由层
│ └── v3/
│ ├── auth/ # POST /v3/auth/tokens
│ ├── users.py # 用户 CRUD
│ └── projects.py
├── token/ # Token 核心逻辑
│ ├── provider.py # Token 提供者接口
│ └── providers/
│ └── fernet/ # Fernet Token 实现
├── identity/ # 用户/组/域管理
├── assignment/ # 角色分配
├── resource/ # Project/Domain 管理
└── middleware/
└── auth_token.py # WSGI 中间件,其他服务用它验证 Token

核心认证流程源码追踪

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# keystone/api/v3/auth/tokens.py
class Tokens(ks_flask.ResourceBase):
def post(self):
# 1. 解析请求体
auth_data = self.request_body_json.get('auth')
# 2. 调用认证插件(password/token/application_credential)
token = authentication.authenticate(auth_data)
# 3. 生成 Fernet Token
return token

# keystone/token/providers/fernet/core.py
class Provider(common.BaseProvider):
def generate_id_and_issued_at(self, token):
# 序列化 token payload → Fernet 加密 → Base64
serialized = self._token_formatter.create_token(...)
return serialized, issued_at

RBAC 权限模型

policy.yaml 结构

1
2
3
4
5
6
7
8
9
# /etc/nova/policy.yaml 示例
# 规则定义
"context_is_admin": "role:admin"
"project_member": "role:member and project_id:%(project_id)s"

# API 权限绑定
"os_compute_api:servers:create": "rule:project_member"
"os_compute_api:servers:delete": "rule:project_member"
"os_compute_api:os-admin-actions:reset_state": "rule:context_is_admin"

Keystone 默认三级角色

1
2
3
admin(系统管理员)
└── member(项目成员,可操作资源)
└── reader(只读,只能查看)

从 Wallaby 版本开始推行 Secure RBAC,引入 system scopeproject scope 的区分,避免 admin 权限过于宽泛。


服务目录(Service Catalog)

每个 OpenStack 服务启动时需要在 Keystone 注册自己的 Endpoint:

1
2
3
4
5
6
7
# 注册 Nova 服务
openstack service create --name nova --description "Compute" compute

# 注册 Endpoint(三种类型)
openstack endpoint create --region RegionOne compute public http://controller:8774/v2.1
openstack endpoint create --region RegionOne compute internal http://controller:8774/v2.1
openstack endpoint create --region RegionOne compute admin http://controller:8774/v2.1

Token 中携带 Service Catalog,客户端通过它找到各服务的 API 地址,无需硬编码。


高可用部署

1
2
3
4
5
6
7
8
9
10
11
12
13
14
                 VIP: 10.0.0.10

┌──────────┴──────────┐
│ HAProxy │
└──────────┬──────────┘

┌──────────────┼──────────────┐
│ │ │
keystone-1 keystone-2 keystone-3
(uwsgi) (uwsgi) (uwsgi)
│ │ │
└──────────────┼──────────────┘

MySQL Galera Cluster

Keystone 是无状态服务(Fernet Token),天然支持水平扩展,HA 部署只需在前面加负载均衡即可。


二次开发要点

自定义认证插件

1
2
3
4
5
6
7
8
9
10
11
12
# 实现自定义认证方式(如短信验证码)
from keystone.auth import plugins as auth_plugins

class SMSAuth(auth_plugins.AuthMethodHandler):
def authenticate(self, auth_payload):
phone = auth_payload.get('phone')
code = auth_payload.get('code')
# 验证短信验证码逻辑
if verify_sms_code(phone, code):
user = get_user_by_phone(phone)
return None, user, None # (domain, user, project)
raise exception.Unauthorized()

常见运维问题

问题 原因 解决
Token 验证失败 Fernet 密钥不同步 同步所有节点的 /etc/keystone/fernet-keys/
服务无法调用其他组件 Endpoint 注册错误 检查 openstack endpoint list
权限拒绝 policy.yaml 规则错误 检查对应服务的 policy 文件
Token 过期时间太短 默认 3600s 修改 [token] expiration 配置