Feast Kubernetes 认证与 RBAC 授权配置完全指南:基于 Token Access Review 的组/命名空间/角色鉴权
【免费下载链接】feastThe Open Source Feature Store for AI/ML项目地址: https://gitcode.com/GitHub_Trending/fe/feast
导读
本文基于 Feast 官方文档 docs/reference/auth/kubernetes_auth_setup.md 展开,系统讲解 Feast 如何从 Kubernetes 认证令牌(Token)中提取用户组(Groups)、命名空间(Namespaces)与角色(Roles),并借助Permission框架实现细粒度访问控制。你将掌握:Feast Operator 默认的"认证默认开启"行为、如何在FeatureStoreCR 中切换认证模式、如何设计角色/组/命名空间四类授权策略,以及如何在 SDK、CLI、REST/gRPC 中注入 Kubernetes 用户令牌。读完即可在自己的集群环境中完成从认证到授权的完整落地。
一、整体架构:Feast 如何从 Kubernetes Token 提取身份信息
Feast 的 Kubernetes 认证建立在"客户端持有有效 Kubernetes Bearer Token,服务端负责验证并提取身份"的模型之上。服务端通过Token Access Review API与RBAC 资源完成三方面信息提取:
- Groups(用户组):与 User/ServiceAccount 直接关联的组,以及从关联命名空间中推导出的组;
- Namespaces(命名空间):与 User/ServiceAccount 关联的 Kubernetes 命名空间;
- Roles(角色):与 User/ServiceAccount 关联的 Kubernetes 角色。
这套逻辑的核心实现位于 sdk/python/feast/permissions/auth/kubernetes_token_parser.py 中的KubernetesTokenParser类。从源码结构看,它的工作流程分为两条路径:
- ServiceAccount 路径:尝试把令牌当作 JWT 解码(
jwt.decode且不校验签名),从sub声明中解析出system:serviceaccount:NAMESPACE:SA_NAME格式的服务账号身份,再通过查询当前命名空间下的RoleBinding得到其绑定的Role列表; - 普通用户路径:当 JWT 解码失败(说明是用户令牌),回退到
TokenReview结果中的username,并通过全局RoleBinding与ClusterRoleBinding同时解析"直接绑定给用户"和"绑定给用户所属组"的角色。
在提取组与命名空间时,KubernetesTokenParser调用AuthenticationV1Api().create_token_review(...)发起 Token Access Review,从响应status.user.groups中取组;命名空间则分两种情况推导:
- 对 ServiceAccount,从
system:serviceaccount:<namespace>:<name>形式的用户名中截取命名空间,并进一步查询该命名空间下RoleBinding中绑定到Group的组名,一并并入 groups; - 对普通用户,通过扫描
dashboard-permissions-*(带opendatahub.io/dashboard=true标签)与admin类型的RoleBinding,推导出用户被授予数据科学项目(Data Science Project)访问权的命名空间集合。
需要强调:Feast 本身不负责颁发令牌(参见 docs/getting-started/components/authz_manager.md 中的说明),客户端负责管理认证令牌并将其随请求传入,服务端只负责校验令牌并提取用户详情。
二、Operator 默认行为:认证默认开启,授权逐步收紧
当使用 Feast Operator 部署时,Kubernetes 认证默认开启。即使在FeatureStoreCR 中没有显式配置authz,Operator 也会自动为所有部署的服务应用kubernetes认证。这意味着:
- 所有发往 Feast 服务的 HTTP/gRPC 请求必须携带合法的 Kubernetes Bearer Token(放在
Authorization请求头中); - 服务端通过 Kubernetes Token Access Review API 校验令牌,并提取用户名、组、命名空间与角色;
- 如果没有任何
Permission对象被定义,通过认证的用户将获得全部访问权限(同时在日志中输出一条警告,提示管理员尽快定义细粒度权限); - 未认证的请求会收到
401 Unauthorized响应。
该设计遵循"Authenticated by Default, Authorized Gradually"(默认认证、逐步授权)的安全模型,确保 Feast 部署不会在未认证状态下被意外暴露,同时允许团队渐进式引入细粒度 RBAC。
2.1 关闭认证:noAuth: true
如果需要在本地开发或测试环境中以无认证方式运行,可以在FeatureStoreCR 中显式设置noAuth: true:
apiVersion: feast.dev/v1 kind: FeatureStore metadata: name: my-feature-store spec: feastProject: my_project authz: noAuth: true⚠️警告:
noAuth: true会关闭所有认证与授权校验,所有端点将变为公开可访问。仅限本地开发或测试环境使用,生产环境请使用kubernetes或oidc认证。
2.2 认证模式选择速查表
spec.authz设置 | 行为 |
|---|---|
| (未指定) | Kubernetes 认证开启(默认) |
kubernetes: {} | Kubernetes 认证开启(显式声明) |
oidc: { ... } | OIDC 认证开启 |
noAuth: true | 关闭所有认证 |
需要说明的是,Feast 还内置了基于 OIDC 的认证(OidcAuthConfig,见 sdk/python/feast/permissions/auth_model.py),当客户端auth_config的type为oidc时走 OIDC 客户端管理器获取令牌;本文聚焦 Kubernetes 认证路径。
三、Kubernetes RBAC 环境与 Feast 权限的对应关系
要让 Kubernetes RBAC 环境与 Feast 的 RBAC 配置对齐,需要遵循以下规则。
3.1 基于角色的认证设置(Role-based)
- Feast
Permission实例中定义的角色名,必须与 Kubernetes RBACRole的名称一一对应; - Kubernetes RBAC
Role必须与 Feast 服务位于同一命名空间; - 客户端应用可以运行在不同命名空间,使用自己的专用
ServiceAccount; - 将客户端
ServiceAccount关联到该 RBACRole的RoleBinding,必须定义在 Feast 服务所在的命名空间中。
从源码实现看,这一点与KubernetesTokenParser.get_roles()的行为完全吻合:它只查询list_namespaced_role_binding(current_namespace)(Feast 服务所在命名空间),匹配subject.kind == "ServiceAccount"、subject.name == service_account_name且subject.namespace == service_account_namespace的绑定,取其role_ref.name作为用户角色。
3.2 基于组与命名空间的认证设置(Group/Namespace-based)
- Feast
Permission实例中定义的组名与命名空间名,必须与 Kubernetes 中实际的Group与Namespace名称对应; - 用户或服务账号必须位于 Feast
Permission实例定义的组或命名空间中; - 客户端应用可以运行在不同命名空间,使用自己的专用
ServiceAccount或用户; - Feast 服务根据
Permission实例中定义的组与命名空间关联关系来授予访问权限。
四、策略类型(Policy Types):四类鉴权策略详解
Feast 的权限框架通过策略(Policy)类描述"谁能做什么"。四类策略的实现均位于 sdk/python/feast/permissions/policy.py,全部继承自抽象基类Policy,核心方法是validate_user(user) -> (bool, str),返回"是否匹配"以及不匹配时的原因说明。
4.1 RoleBasedPolicy(基于角色)
按用户角色成员关系授权,用户至少拥有配置角色中的一个即可通过。
from feast.permissions.policy import RoleBasedPolicy policy = RoleBasedPolicy(roles=["data-team", "ml-engineers"])实现上,validate_user调用user.has_matching_role(self.roles);若用户命中了角色绑定(含直接绑定与组绑定,见get_user_roles()),即可通过校验。
4.2 GroupBasedPolicy(基于用户组)
按用户组归属授权,用户至少加入其中一个组即可通过。
from feast.permissions.policy import GroupBasedPolicy policy = GroupBasedPolicy(groups=["data-team", "ml-engineers"])4.3 NamespaceBasedPolicy(基于命名空间)
按用户关联的命名空间授权,用户至少位于其中一个被允许的命名空间即可通过。
from feast.permissions.policy import NamespaceBasedPolicy policy = NamespaceBasedPolicy(namespaces=["production", "staging"])4.4 CombinedGroupNamespacePolicy(组与命名空间组合)
当用户被加入允许的组 OR 允许的命名空间(二者满足其一)时授权通过。
from feast.permissions.policy import CombinedGroupNamespacePolicy policy = CombinedGroupNamespacePolicy( groups=["data-team"], namespaces=["production"] )源码中该策略的判定逻辑为has_matching_group(groups) or has_matching_namespace(namespaces),不满足时返回原因 "User must be in at least one of the permitted groups or namespaces"。
此外,sdk/python/feast/permissions/policy.py 中还提供了一个AllowAll策略实例,任何用户都能通过校验,作为未定义权限时的默认兜底行为。
五、服务端与客户端配置
5.1 服务端配置
使用 Kubernetes 认证时,服务端会自动提取组、命名空间与角色,无需在既有 Kubernetes 认证配置之外追加任何额外配置。认证模式的切换(kubernetes / oidc / no_auth)通过服务端feature_store.yaml中的auth段完成,AuthConfig基类定义了type: Literal["oidc", "kubernetes", "no_auth"] = "no_auth"字段。
5.2 客户端配置
对于非 ServiceAccount 的外部用户,需要在客户端配置中提供用户令牌。令牌的提供方式详见下一节,也可直接阅读官方文档 docs/reference/auth/user_token_provisioning.md。
六、用户令牌的多种提供方式(客户端视角)
Feast 客户端支持从 CLI、API 与 SDK 三种途径注入 Kubernetes 用户令牌。
6.1 SDK 配置(Python)
方法一:在FeatureStore中直接配置
from feast import FeatureStore from feast.permissions.auth_model import KubernetesAuthConfig # Create auth config with user token auth_config = KubernetesAuthConfig( type="kubernetes", user_token="your-kubernetes-user-token-here" ) # Initialize FeatureStore with auth config fs = FeatureStore( repo_path="path/to/feature_repo", auth_config=auth_config )方法二:环境变量
import os from feast import FeatureStore # Set environment variable os.environ["LOCAL_K8S_TOKEN"] = "your-kubernetes-user-token-here" # FeatureStore will automatically use the token fs = FeatureStore("path/to/feature_repo")方法三:配置文件
在feature_store.yaml中声明auth段:
project: my-project auth: type: kubernetes user_token: "your-kubernetes-user-token-here"随后FeatureStore会自动从 YAML 读取认证配置:
from feast import FeatureStore # FeatureStore will read auth config from feature_store.yaml fs = FeatureStore("path/to/feature_repo")注意:
KubernetesAuthConfig类通过ConfigDict(arbitrary_types_allowed=True, extra="allow")允许额外字段(见 sdk/python/feast/permissions/auth_model.py),因此user_token字段从 YAML 加载时会被正确识别。
6.2 CLI 使用
方法一:环境变量
# Set the token as environment variable export LOCAL_K8S_TOKEN="your-kubernetes-user-token-here" # Use Feast CLI commands feast apply feast materialize feast get-online-features \ --features feature1,feature2 \ --entity-rows '{"entity_id": "123"}'方法二:配置文件
在feature_store.yaml中配置auth段(同上),然后直接执行 CLI 命令即可:
feast apply feast materialize feast get-online-features \ --features feature1,feature2 \ --entity-rows '{"entity_id": "123"}'6.3 API 使用(REST/gRPC)
REST API —— 方法一:Authorization 请求头
import requests # For REST API headers = { "Authorization": "Bearer your-kubernetes-user-token-here", "Content-Type": "application/json" } # Get features response = requests.get( "http://feast-server/features", headers=headers ) # Get online features response = requests.post( "http://feast-server/get-online-features", headers=headers, json={ "features": ["feature1", "feature2"], "entity_rows": [{"entity_id": "123"}] } )REST API —— 方法二:requests Session
import requests from requests.auth import HTTPBearerAuth # Create session with auth session = requests.Session() session.auth = HTTPBearerAuth("your-kubernetes-user-token-here") # Make requests response = session.get("http://feast-server/features")gRPC API —— 方法一:gRPC Metadata
import grpc from feast.protos.feast.serving.ServingService_pb2_grpc import ServingServiceStub from feast.protos.feast.serving.ServingService_pb2 import GetOnlineFeaturesRequest # Create gRPC channel channel = grpc.insecure_channel('feast-server:6565') stub = ServingServiceStub(channel) # Create metadata with auth token metadata = [('authorization', 'Bearer your-kubernetes-user-token-here')] # Create request request = GetOnlineFeaturesRequest( features=["feature1", "feature2"], entity_rows=[{"entity_id": "123"}] ) # Make gRPC call response = stub.GetOnlineFeatures(request, metadata=metadata)gRPC API —— 方法二:gRPC Interceptor(统一注入)
import grpc from feast.protos.feast.serving.ServingService_pb2_grpc import ServingServiceStub class AuthInterceptor(grpc.UnaryUnaryClientInterceptor): def __init__(self, token): self.token = token def intercept_unary_unary(self, continuation, client_call_details, request): # Add auth metadata metadata = list(client_call_details.metadata or []) metadata.append(('authorization', f'Bearer {self.token}')) # Update call details client_call_details = client_call_details._replace(metadata=metadata) return continuation(client_call_details, request) # Create channel with interceptor channel = grpc.insecure_channel('feast-server:6565') interceptor = AuthInterceptor("your-kubernetes-user-token-here") channel = grpc.intercept_channel(channel, interceptor) # Use the channel stub = ServingServiceStub(channel)6.4 编程式 SDK 用法:从 Kubernetes 配置获取令牌
对于运行在集群内、需要动态获取令牌的客户端,可以结合 Kubernetes Python SDK 从 Secret 等安全存储读取令牌:
from feast import FeatureStore from feast.permissions.auth_model import KubernetesAuthConfig from kubernetes import client, config # Load kubeconfig and get token config.load_kube_config() v1 = client.CoreV1Api() # Get token from Kubernetes API or secure storage def get_token_from_k8s(): # Example: Get token from secret secret = v1.read_namespaced_secret( name="user-token", namespace="default" ) return secret.data["token"].decode("utf-8") user_token = get_token_from_k8s() auth_config = KubernetesAuthConfig( type="kubernetes", user_token=user_token ) fs = FeatureStore( repo_path="path/to/feature_repo", auth_config=auth_config )6.5 令牌解析优先级
客户端获取令牌的完整顺序由KubernetesAuthClientManager.get_token()决定(见 sdk/python/feast/permissions/client/kubernetes_auth_client_manager.py),与官方文档一致:
- 服务间通信:
INTRA_COMMUNICATION_BASE64环境变量(服务到服务调用,此时会生成一个alg: none的占位 JWT); - 直接配置:
KubernetesAuthConfig中的user_token字段,或feature_store.yaml中的auth.user_token; - 服务账号令牌文件:
/var/run/secrets/kubernetes.io/serviceaccount/token(适用于 Pod 内运行); - 环境变量:
LOCAL_K8S_TOKEN。
同时,客户端管理器工厂AuthenticationClientManagerFactory(见 sdk/python/feast/permissions/client/auth_client_manager.py)会依据auth_config.type(kubernetes/oidc)选择对应的客户端管理器;若检测到INTRA_COMMUNICATION_BASE64,则优先走内部通信专用管理器。
七、完整实战:权限定义与下发
7.1 基础权限示例
在permissions.py中组合使用全部四类策略定义权限:
from feast.feast_object import ALL_RESOURCE_TYPES from feast.permissions.action import READ, AuthzedAction, ALL_ACTIONS from feast.permissions.permission import Permission from feast.permissions.policy import ( RoleBasedPolicy, GroupBasedPolicy, NamespaceBasedPolicy, CombinedGroupNamespacePolicy ) # Role-based permission role_perm = Permission( name="role_permission", types=ALL_RESOURCE_TYPES, policy=RoleBasedPolicy(roles=["reader-role"]), actions=[AuthzedAction.DESCRIBE] + READ ) # Group-based permission (new) data_team_perm = Permission( name="data_team_permission", types=ALL_RESOURCE_TYPES, policy=GroupBasedPolicy(groups=["data-team", "ml-engineers"]), actions=[AuthzedAction.DESCRIBE] + READ ) # Namespace-based permission (new) prod_perm = Permission( name="production_permission", types=ALL_RESOURCE_TYPES, policy=NamespaceBasedPolicy(namespaces=["production"]), actions=[AuthzedAction.DESCRIBE] + READ ) # Combined permission (new) dev_staging_perm = Permission( name="dev_staging_permission", types=ALL_RESOURCE_TYPES, policy=CombinedGroupNamespacePolicy( groups=["dev-team"], namespaces=["staging"] ), actions=ALL_ACTIONS )补充说明代码中用到的动作枚举(定义于 sdk/python/feast/permissions/action.py):
| 动作 | 含义 |
|---|---|
CREATE | 创建实例 |
DESCRIBE | 访问实例状态 |
UPDATE | 更新实例状态 |
DELETE | 删除实例 |
READ_ONLINE/READ_OFFLINE | 仅读取在线/离线存储 |
WRITE_ONLINE/WRITE_OFFLINE | 仅写入在线/离线存储 |
READ = [READ_OFFLINE, READ_ONLINE],WRITE = [WRITE_OFFLINE, WRITE_ONLINE],CRUD = [CREATE, DESCRIBE, UPDATE, DELETE],ALL_ACTIONS覆盖全部 8 个动作。
7.2 下发权限
在服务端(或在具备权限的客户端)通过 CLI/API/SDK 执行:
feast applyfeast apply会将permissions.py中定义的Permission对象写入注册表,之后服务端便会对每个请求按"资源类型 + 动作 + 策略"进行鉴权。未定义任何Permission时,认证通过的用户默认拥有全部权限,但服务端会记录一条 "No permissions defined" 警告日志。
八、故障排查(Troubleshooting)
常见问题
升级后出现 401 Unauthorized
- Feast Operator 现在默认启用 Kubernetes 认证;如果既有
FeatureStoreCR 未指定authz,升级会自动开启认证。 - 快速修复(仅测试用):在
FeatureStoreCR 中追加authz.noAuth: true以恢复此前的无认证行为。 - 推荐做法:更新客户端应用,让所有请求携带有效的 Kubernetes Bearer Token。
- Feast Operator 现在默认启用 Kubernetes 认证;如果既有
Token Access Review 失败
- 检查 Feast 服务是否具备所需的 RBAC 权限(其运行 Deployment 必须被授予查询
RoleBinding、执行 TokenReview 等资源的权限,见 sdk/python/feast/permissions/auth/kubernetes_token_parser.py 中get_roles的注释说明); - 确认令牌有效且未过期;
- 以 debug 模式查看服务端日志中的详细错误信息。
- 检查 Feast 服务是否具备所需的 RBAC 权限(其运行 Deployment 必须被授予查询
未提取到组/命名空间
- 确认令牌包含预期的 claims;
- 检查用户是否已在 Kubernetes/ODH/RHOAI 中正确配置(普通用户的命名空间推导依赖
dashboard-permissions-*与admin类型的RoleBinding)。
权限被拒绝(Permission Denied)
- 确认用户已被加入所需组/命名空间,或已分配所需角色;
- 检查策略配置是否正确;
- 查看权限评估日志。
日志中出现 "No permissions defined" 警告
- 这是 Kubernetes 认证开启但尚未应用任何
Permission对象时的预期行为; - 默认情况下认证用户拥有全部访问权限;如需强制细粒度授权,请通过
permissions.py+feast apply定义权限。
- 这是 Kubernetes 认证开启但尚未应用任何
客户端常见错误
| 错误信息 | 解决方案 |
|---|---|
Missing authentication token | 通过上述任一受支持方式提供令牌(配置项、文件或环境变量) |
Invalid or expired access token | 确认令牌有效且未过期 |
User is not added into the permitted groups / Namespaces | 检查用户是否具备所需组/命名空间访问权限 |
九、迁移指南:从基于角色到基于组/命名空间
如果团队希望从单一的角色模型迁移到更灵活的组/命名空间模型,官方建议按以下步骤渐进推进:
- 识别用户组:确定用户归属的组;
- 映射命名空间:确定用户应访问的命名空间;
- 创建新策略:定义基于组与基于命名空间的策略(对应
GroupBasedPolicy/NamespaceBasedPolicy/CombinedGroupNamespacePolicy); - 渐进测试:先从只读权限开始,逐步扩大范围;
- 监控:持续观察日志,确保认证与授权行为符合预期。
十、最佳实践
- 最小权限原则(Principle of Least Privilege):只授予完成任务所需的最小权限;
- 合理组织用户组:按照职责把用户组织进逻辑组;
- 命名空间隔离:使用命名空间隔离不同环境或团队;
- 定期审计:周期性复查与审计权限配置。
相关文档
- Authorization Manager(授权管理器组件说明)
- Permission Model(权限模型概念)
- RBAC Architecture(RBAC 架构说明)
- User Token Provisioning(用户令牌提供指南)
- 权限概念文档
【免费下载链接】feastThe Open Source Feature Store for AI/ML项目地址: https://gitcode.com/GitHub_Trending/fe/feast
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考