Higress实现无role鉴权

408 阅读15分钟

最近Higress社区上提出了一个issue#2154,因为当前实现的 provider 只支持 ak/sk 这种鉴权方式,严格来说不是很完备,有些情况下用户更希望通过 role 鉴权方式(无 AK ),是否可以支持 litellm 这样支持更灵活的鉴权方式.以下是自己的一些调研报告

Litellm的无role鉴权架构

AWS Bedrock Role鉴权时序图

sequenceDiagram  
    participant Client as 客户端应用  
    participant LiteLLM as LiteLLM SDK  
    participant SecretMgr as 密钥管理器  
    participant STS as AWS STS  
    participant Bedrock as AWS Bedrock  
  
    Note over Client,Bedrock: Web Identity Token方式的Role鉴权  
  
    Client->>LiteLLM: completion(model="bedrock/...", aws_web_identity_token, aws_role_name, aws_session_name)  
      
    LiteLLM->>LiteLLM: 检查参数完整性  
    Note right of LiteLLM: 验证aws_web_identity_token、aws_role_name、aws_session_name存在  
      
    LiteLLM->>SecretMgr: get_secret(aws_web_identity_token)  
    SecretMgr-->>LiteLLM: 返回OIDC Token  
      
    alt OIDC Token获取失败  
        LiteLLM-->>Client: 抛出BedrockError(401)  
    end  
      
    LiteLLM->>STS: boto3.client("sts")  
    LiteLLM->>STS: assume_role_with_web_identity(RoleArn, RoleSessionName, WebIdentityToken)  
    STS-->>LiteLLM: 返回临时凭证(AccessKeyId, SecretAccessKey, SessionToken)  
      
    LiteLLM->>Bedrock: 使用临时凭证创建bedrock-runtime客户端  
    LiteLLM->>Bedrock: 发送API请求  
    Bedrock-->>LiteLLM: 返回响应  
    LiteLLM-->>Client: 返回标准化响应

AWS Bedrock Role鉴权实现

  1. Web Identity Token方式的STS AssumeRole

在AWS Bedrock的客户端初始化中,实现了基于OIDC token的角色假设机制: common_utils.py:190-223

这段代码的核心流程是:

  1. 检查参数完整性:验证aws_web_identity_tokenaws_role_nameaws_session_name都存在
  2. 获取OIDC token:通过get_secret()从密钥管理器获取Web Identity Token
  3. 调用STS服务:使用assume_role_with_web_identityAPI获取临时凭证
  4. 创建Bedrock客户端:使用临时凭证(AccessKeyId、SecretAccessKey、SessionToken)创建bedrock-runtime客户端
  5. 标准AssumeRole方式

当只提供角色名称和会话名称时,使用标准的AssumeRole机制: common_utils.py:224-245

这种方式需要现有的AWS凭证来假设目标角色,适用于跨账户访问场景。

  1. Pass-through端点的自动签名

在Bedrock的pass-through端点中,系统自动处理AWS签名认证: llm_passthrough_endpoints.py:421-433

这里使用了AWS SigV4签名算法,自动为请求添加认证头。

Vertex AI Role鉴权时序图

sequenceDiagram  
    participant Client as 客户端应用  
    participant LiteLLM as LiteLLM SDK  
    participant SecretMgr as 密钥管理器  
    participant GCPAuth as Google Cloud Auth  
    participant VertexAI as Vertex AI API  
  
    Note over Client,VertexAI: Service Account方式的Role鉴权  
  
    Client->>LiteLLM: completion(model="vertex_ai/...", vertex_credentials)  
      
    LiteLLM->>LiteLLM: 凭证优先级检查  
    Note right of LiteLLM: 1. vertex_credentials参数<br/>2. vertex_ai_credentials参数<br/>3. VERTEXAI_CREDENTIALS环境变量  
      
    alt 使用Service Account JSON  
        LiteLLM->>LiteLLM: 加载JSON文件或字符串  
        LiteLLM->>GCPAuth: 使用Service Account凭证  
    else 使用环境变量  
        LiteLLM->>SecretMgr: get_secret("VERTEXAI_CREDENTIALS")  
        SecretMgr-->>LiteLLM: 返回凭证  
        LiteLLM->>GCPAuth: 使用环境变量凭证  
    end  
      
    LiteLLM->>LiteLLM: _ensure_access_token_async()  
    LiteLLM->>GCPAuth: 获取访问令牌  
    GCPAuth-->>LiteLLM: 返回Bearer Token  
      
    LiteLLM->>LiteLLM: _get_token_and_url()  
    Note right of LiteLLM: 构建API URL和认证头  
      
    LiteLLM->>VertexAI: 发送API请求(带Bearer Token)  
    VertexAI-->>LiteLLM: 返回响应  
    LiteLLM-->>Client: 返回标准化响应

Vertex AI Role鉴权实现

  1. 凭证获取的优先级机制

在主要的completion函数中,Vertex AI的凭证处理遵循优先级顺序: main.py:2393-2397

优先级顺序为:

  1. 函数参数中的vertex_credentials
  2. 函数参数中的vertex_ai_credentials(别名)
  3. 环境变量VERTEXAI_CREDENTIALS
  4. Service Account JSON文件的使用示例

文档中展示了如何使用Service Account JSON文件: vertex.md:37-51

这种方式通过加载JSON文件并转换为字符串传递给completion函数。

  1. 环境变量配置方式

支持通过环境变量进行配置: vertex.md:741-761

主要环境变量包括:

  • GOOGLE_APPLICATION_CREDENTIALS:Service Account文件路径
  • VERTEXAI_LOCATION:Vertex AI部署区域
  • VERTEXAI_PROJECT:项目ID(可选)
  1. 统一认证参数映射机制

litellm实现了统一的认证参数映射,确保不同云提供商的认证参数正确映射: test_completion.py:3959-3964

这个机制通过各自的Config类获取特殊认证参数映射

Service Account JSON格式

Service Account JSON文件是Google Cloud Platform用于身份认证的标准格式。测试代码:test_amazing_vertex_completion.py:95-101

以下是完整的Service Account JSON结构:

{  
  "type": "service_account",  
  "// 账户类型,固定值为service_account,表示这是一个服务账户凭证文件": "",  
    
  "project_id": "your-gcp-project-id",  
  "// Google Cloud项目ID,用于标识该服务账户所属的GCP项目": "",  
    
  "private_key_id": "a1b2c3d4e5f6...",  
  "// 私钥ID,用于标识特定的私钥版本,通常是一个十六进制字符串": "",  
    
  "private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC...\n-----END PRIVATE KEY-----\n",  
  "// RSA私钥,用于JWT签名认证,包含完整的PEM格式私钥内容": "",  
    
  "client_email": "service-account-name@your-project-id.iam.gserviceaccount.com",  
  "// 服务账户的邮箱地址,格式为{account-name}@{project-id}.iam.gserviceaccount.com": "",  
    
  "client_id": "123456789012345678901",  
  "// 客户端ID,是服务账户的唯一数字标识符": "",  
    
  "auth_uri": "https://accounts.google.com/o/oauth2/auth",  
  "// OAuth2认证URI,用于获取授权码的端点地址": "",  
    
  "token_uri": "https://oauth2.googleapis.com/token",  
  "// 令牌获取URI,用于交换访问令牌的端点地址": "",  
    
  "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",  
  "// 认证提供商的X.509证书URL,用于验证JWT签名": "",  
    
  "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/service-account-name%40your-project-id.iam.gserviceaccount.com",  
  "// 客户端X.509证书URL,包含该服务账户的公钥证书": "",  
    
  "universe_domain": "googleapis.com"  
  "// 宇宙域,指定API调用的目标域,通常为googleapis.com": ""  
}

当前Higress鉴权架构分析

Higress目前已经具备了多种的鉴权能力:

  1. 外部认证服务集成能力

ext-auth插件提供了完整的外部认证服务集成框架 ,支持两种endpoint模式:envoy模式和forward_auth模式,可以灵活地将认证请求转发给外部认证服务处理

  1. 基于Consumer的角色控制

jwt-auth插件已经实现了基于consumer的细粒度角色控制机制 ,支持在路由或域名级别配置允许访问的consumer列表,这为role模式鉴权提供了基础架构。

  1. OIDC标准认证流程

oidc插件实现了完整的OpenID Connect认证流程 ,支持多种OIDC提供商,并具备黑白名单匹配规则。

Role模式鉴权(无ak)实现建议

方案一:基于ext-auth插件扩展

利用现有的ext-auth插件架构,实现类似AWS Bedrock的STS AssumeRole机制:

  1. Web Identity Token方式:在外部认证服务中实现OIDC token验证和角色映射逻辑,类似AWS的assume_role_with_web_identity
  2. 临时凭证生成:认证服务验证身份后,生成带有角色信息的临时token
  3. 请求头传递:利用ext-auth的authorization_response.allowed_upstream_headers配置 ,将角色信息传递给后端服务

方案二:扩展jwt-auth插件

参考Vertex AI的Service Account认证模式,扩展现有jwt-auth插件:

  1. 角色与Consumer映射:将云服务的Service Account概念映射到higress的consumer机制
  2. 动态角色验证:扩展JWT验证逻辑,支持从外部身份提供商获取角色信息
  3. 细粒度权限控制:利用现有的allow配置 ,实现基于角色的访问控制

方案三:新建role-auth插件

结合AWS和Vertex AI的优势,创建专门的role-auth插件:

  1. 多种认证源支持:支持Web Identity Token、Service Account JSON、环境变量等多种认证方式
  2. 角色假设流程:实现完整的角色假设和临时凭证管理机制
  3. 黑白名单模式:参考ext-auth的匹配规则机制 ,支持灵活的访问控制策略

实现关键点

  1. 认证优先级机制

参考Vertex AI的凭证获取优先级,需要支持功能如下:

  • 插件配置中的角色凭证
  • 请求头中的角色token
  • 环境变量配置的默认角色
  1. 失败处理模式

借鉴ext-auth的failure_mode_allow机制 ,为role认证提供降级处理能力。

  1. 请求上下文传递

利用higress的context机制 ,在认证成功后将角色信息传递给后续处理流程。

实现方案(方案三)

sequenceDiagram  
    participant Client as "客户端"  
    participant Gateway as "Higress网关"  
    participant RoleAuth as "role-auth插件"  
    participant STS as "STS服务"  
    participant OIDC as "OIDC验证器"  
    participant RoleValidator as "角色验证器"  
    participant Upstream as "上游服务"  
  
    Note over Client,Upstream: Web Identity Token认证流程  
  
    Client->>Gateway: 发送请求 (带Web Identity Token)  
    Note right of Client: Headers:<br/>x-web-identity-token: <token><br/>x-role-arn: arn:aws:iam::xxx:role/xxx<br/>x-session-name: session-name  
  
    Gateway->>RoleAuth: 拦截请求进行认证  
    Note right of RoleAuth: onHttpRequestHeaders()  
  
    RoleAuth->>RoleAuth: 检查黑白名单规则  
    Note right of RoleAuth: config.MatchRules.IsAllowedByMode()  
  
    alt 请求在白名单中  
        RoleAuth->>Gateway: 跳过认证,继续处理  
        Gateway->>Upstream: 转发请求  
    else 需要进行认证  
        RoleAuth->>RoleAuth: 提取认证信息  
        Note right of RoleAuth: extractAuthInfo()<br/>优先级:<br/>1. Web Identity Token<br/>2. Role Token<br/>3. Authorization Bearer<br/>4. 默认角色  
  
        RoleAuth->>RoleAuth: 识别为Web Identity Token类型  
        Note right of RoleAuth: AuthType: WebIdentityTokenType  
  
        RoleAuth->>STS: 调用AssumeRoleWithWebIdentity  
        Note right of RoleAuth: handleWebIdentityToken()<br/>POST /sts with:<br/>- WebIdentityToken<br/>- RoleArn<br/>- RoleSessionName  
  
        STS->>OIDC: 验证Web Identity Token  
        Note right of STS: oidcVerifier.VerifyToken()  
  
        OIDC->>OIDC: 获取OIDC配置  
        Note right of OIDC: GET /.well-known/openid_configuration  
  
        OIDC->>OIDC: 获取JWKS公钥  
        Note right of OIDC: GET /jwks_uri  
  
        OIDC->>OIDC: 验证JWT签名和Claims  
        Note right of OIDC: 验证签名、过期时间、issuer等  
  
        alt Token验证失败  
            OIDC-->>STS: 返回验证失败  
            STS-->>RoleAuth: 返回401错误  
            RoleAuth->>RoleAuth: 处理认证失败  
            Note right of RoleAuth: handleAuthFailure()  
            alt failure_mode_allow=true  
                RoleAuth->>Gateway: 添加失败标记继续处理  
                Gateway->>Upstream: 转发请求 (带failure header)  
            else failure_mode_allow=false  
                RoleAuth-->>Client: 返回401未授权  
            end  
        else Token验证成功  
            OIDC-->>STS: 返回OIDCClaims (subject, issuer等)  
  
            STS->>RoleValidator: 验证角色权限  
            Note right of STS: roleValidator.ValidateRole()  
  
            RoleValidator->>RoleValidator: 检查subject是否可假设该角色  
            Note right of RoleValidator: 检查roleMapping[subject]  
  
            alt 角色验证失败  
                RoleValidator-->>STS: 返回权限不足错误  
                STS-->>RoleAuth: 返回403错误  
                RoleAuth-->>Client: 返回403禁止访问  
            else 角色验证成功  
                RoleValidator-->>STS: 验证通过  
  
                STS->>STS: 生成临时凭证  
                Note right of STS: generateTemporaryCredentials()<br/>生成:<br/>- AccessKeyId<br/>- SecretAccessKey<br/>- SessionToken (JWT)<br/>- Expiration  
  
                STS-->>RoleAuth: 返回临时凭证  
                Note left of STS: AssumeRoleResponse:<br/>- Credentials<br/>- AssumedRoleUser  
  
                RoleAuth->>RoleAuth: 设置角色信息到请求头  
                Note right of RoleAuth: setRoleHeaders()<br/>添加到上游请求:<br/>- x-user-role<br/>- x-access-key-id<br/>- x-session-token<br/>- x-role-expiry  
  
                RoleAuth->>Gateway: 继续请求处理  
                Gateway->>Upstream: 转发带角色信息的请求  
                Upstream-->>Gateway: 返回响应  
                Gateway-->>Client: 返回最终响应  
            end  
        end  
    end  
  
    Note over Client,Upstream: Role Token认证流程(简化)  
  
    Client->>Gateway: 发送请求 (带Role Token)  
    Note right of Client: Headers:<br/>x-role-token: <token>  
  
    Gateway->>RoleAuth: 拦截请求  
    RoleAuth->>RoleAuth: 识别为Role Token类型  
    RoleAuth->>RoleAuth: 调用角色Token验证服务  
    Note right of RoleAuth: handleRoleToken()<br/>验证token并获取角色信息  
  
    alt Token有效且角色被允许  
        RoleAuth->>RoleAuth: 设置角色信息到请求头  
        RoleAuth->>Gateway: 继续处理  
        Gateway->>Upstream: 转发请求  
    else Token无效或角色不被允许  
        RoleAuth-->>Client: 返回认证失败  
    end  
  
    Note over Client,Upstream: Service Account认证流程(简化)  
  
    Client->>Gateway: 发送请求 (带Service Account Token)  
    Note right of Client: Authorization: Bearer <sa-token>  
  
    Gateway->>RoleAuth: 拦截请求  
    RoleAuth->>RoleAuth: 识别为Service Account类型  
    RoleAuth->>RoleAuth: 调用Service Account验证服务  
    Note right of RoleAuth: handleServiceAccount()<br/>验证Google Service Account  
  
    alt 验证成功  
        RoleAuth->>RoleAuth: 设置Service Account信息到请求头  
        RoleAuth->>Gateway: 继续处理  
        Gateway->>Upstream: 转发请求  
    else 验证失败  
        RoleAuth-->>Client: 返回认证失败  
    end  

关键实现要点

  1. 认证优先级机制:插件按照Web Identity Token → Role Token → Service Account Token → 默认角色的优先级顺序提取认证信息

  2. 黑白名单匹配:参考现有ext-auth插件的匹配规则机制,支持域名、路径和方法的灵活匹配

  3. 失败处理模式:借鉴ext-auth插件的failure_mode_allow机制,提供降级处理能力

  4. STS服务集成:当前Higress已在配置中预留STS服务端口配置,通过设置非零端口值启用STS服务

创建一个新的role-auth插件,结合AWS Bedrock和Vertex AI的优势,实现无AK的角色鉴权机制。

  1. 插件主文件
 // plugins/wasm-go/extensions/role-auth/main.go
package main  
  
import (  
        "encoding/json"  
        "fmt"  
        "net/http"  
        "strings"  
        "time"  
  
        "role-auth/config"  
        "role-auth/util"  
  
        "github.com/alibaba/higress/plugins/wasm-go/pkg/wrapper"  
        "github.com/higress-group/proxy-wasm-go-sdk/proxywasm"  
        "github.com/higress-group/proxy-wasm-go-sdk/proxywasm/types"  
)  
  
func main() {  
        wrapper.SetCtx(  
                "role-auth",  
                wrapper.ParseConfigBy(config.ParseConfig),

                wrapper.ProcessRequestHeadersBy(onHttpRequestHeaders),

        )  
}  
  
const (  
        HeaderAuthorization     = "authorization"  
        HeaderRoleToken        = "x-role-token"  
        HeaderWebIdentityToken = "x-web-identity-token"  
        HeaderRoleArn          = "x-role-arn"  
        HeaderSessionName      = "x-session-name"  
        HeaderUserRole         = "x-user-role"  
        HeaderRoleExpiry       = "x-role-expiry"  
        HeaderFailureModeAllow = "x-role-auth-failure-mode-allowed"  
)  
  
func onHttpRequestHeaders(ctx wrapper.HttpContext, config config.RoleAuthConfig, log wrapper.Log) types.Action {  
        // 检查黑白名单规则,跳过不需要认证的请求
        if config.MatchRules.IsAllowedByMode(ctx.Host(), ctx.Method(), wrapper.GetRequestPathWithoutQuery()) {  
                ctx.DontReadRequestBody()  
                return types.ActionContinue  
        }  

        ctx.DisableReroute()

        ctx.DontReadRequestBody()  
  
        return checkRoleAuth(ctx, config, log)  
}  
  
func checkRoleAuth(ctx wrapper.HttpContext, config config.RoleAuthConfig, log wrapper.Log) types.Action {  
        // 按优先级获取认证信息
        authInfo := extractAuthInfo(ctx, config, log)  
        if authInfo == nil {  
                log.Errorf("failed to extract authentication information")  
                return handleAuthFailure(ctx, config, http.StatusUnauthorized, "Missing authentication information")  
        }  
  
        // 根据认证类型处理
        switch authInfo.AuthType {  
        case config.WebIdentityTokenType:  
                return handleWebIdentityToken(ctx, config, authInfo, log)  
        case config.ServiceAccountType:  
                return handleServiceAccount(ctx, config, authInfo, log)  
        case config.RoleTokenType:  
                return handleRoleToken(ctx, config, authInfo, log)  
        default:  
                log.Errorf("unsupported authentication type: %s", authInfo.AuthType)  
                return handleAuthFailure(ctx, config, http.StatusBadRequest, "Unsupported authentication type")  
        }  
}  
  
type AuthInfo struct {  
        AuthType         string  
        Token           string  
        RoleArn         string  
        SessionName     string  
        ServiceAccount  string  
        ProjectID       string  
}  
  
func extractAuthInfo(ctx wrapper.HttpContext, config config.RoleAuthConfig, log wrapper.Log) *AuthInfo {  
        // 优先级1: 请求头中的Web Identity Token
        if webToken, _ := proxywasm.GetHttpRequestHeader(HeaderWebIdentityToken); webToken != "" {  
                if roleArn, _ := proxywasm.GetHttpRequestHeader(HeaderRoleArn); roleArn != "" {  
                        sessionName, _ := proxywasm.GetHttpRequestHeader(HeaderSessionName)  
                        if sessionName == "" {  
                                sessionName = fmt.Sprintf("higress-session-%d", time.Now().Unix())  
                        }  
                        return &AuthInfo{  
                                AuthType:    config.WebIdentityTokenType,

                                Token:       webToken,

                                RoleArn:     roleArn,

                                SessionName: sessionName,

                        }

                }

        }  
  
        // 优先级2: 请求头中的Role Token
        if roleToken, _ := proxywasm.GetHttpRequestHeader(HeaderRoleToken); roleToken != "" {  
                return &AuthInfo{  
                        AuthType: config.RoleTokenType,

                        Token:    roleToken,

                }

        }  
  
        // 优先级3: Authorization头中的Bearer token
        if authHeader, _ := proxywasm.GetHttpRequestHeader(HeaderAuthorization); authHeader != "" {  
                if strings.HasPrefix(authHeader, "Bearer ") {  
                        token := strings.TrimPrefix(authHeader, "Bearer ")  
                        // 检查是否为Service Account token格式
                        if isServiceAccountToken(token) {  
                                return &AuthInfo{  
                                        AuthType: config.ServiceAccountType,                                        Token:    token,                                }                        }  
                        // 默认作为Role Token处理
                        return &AuthInfo{  
                                AuthType: config.RoleTokenType,

                                Token:    token,

                        }

                }

        }  
  
        // 优先级4: 配置中的默认角色
        if config.DefaultRole != "" {  
                return &AuthInfo{  
                        AuthType: config.RoleTokenType,

                        Token:    config.DefaultRole,

                }

        }  
  
        return nil  
}  
  
func handleWebIdentityToken(ctx wrapper.HttpContext, config config.RoleAuthConfig, authInfo *AuthInfo, log wrapper.Log) types.Action {  
        // 构建STS AssumeRoleWithWebIdentity请求
        stsRequest := util.STSAssumeRoleRequest{

                WebIdentityToken: authInfo.Token,

                RoleArn:         authInfo.RoleArn,

                RoleSessionName: authInfo.SessionName,

                DurationSeconds: config.TokenDuration,

        }  
  
        // 调用外部STS服务
        callback := func(numHeaders, bodySize, numTrailers int) {  
                responseBody, err := proxywasm.GetHttpCallResponseBody(0, bodySize)  
                if err != nil {  
                        log.Errorf("failed to get STS response body: %v", err)  
                        handleAuthFailure(ctx, config, http.StatusInternalServerError, "STS service error")  
                        return  
                }  
  
                var stsResponse util.STSAssumeRoleResponse  
                if err := json.Unmarshal(responseBody, &stsResponse); err != nil {  
                        log.Errorf("failed to parse STS response: %v", err)  
                        handleAuthFailure(ctx, config, http.StatusInternalServerError, "Invalid STS response")  
                        return  
                }  
  
                // 设置角色信息到请求头
                setRoleHeaders(ctx, &stsResponse, config)

                proxywasm.ResumeHttpRequest()

        }  
  
        // 发送HTTP请求到STS服务
        if err := util.CallSTSService(config.STSEndpoint, &stsRequest, callback); err != nil {  
                log.Errorf("failed to call STS service: %v", err)  
                return handleAuthFailure(ctx, config, http.StatusInternalServerError, "STS service unavailable")  
        }  
  
        return types.ActionPause  
}  
  
func handleServiceAccount(ctx wrapper.HttpContext, config config.RoleAuthConfig, authInfo *AuthInfo, log wrapper.Log) types.Action {  
        // 验证Service Account token
        saRequest := util.ServiceAccountRequest{

                Token:     authInfo.Token,

                ProjectID: authInfo.ProjectID,

        }  
  
        callback := func(numHeaders, bodySize, numTrailers int) {  
                responseBody, err := proxywasm.GetHttpCallResponseBody(0, bodySize)  
                if err != nil {  
                        log.Errorf("failed to get service account response: %v", err)  
                        handleAuthFailure(ctx, config, http.StatusInternalServerError, "Service account verification failed")  
                        return  
                }  
  
                var saResponse util.ServiceAccountResponse  
                if err := json.Unmarshal(responseBody, &saResponse); err != nil {  
                        log.Errorf("failed to parse service account response: %v", err)  
                        handleAuthFailure(ctx, config, http.StatusInternalServerError, "Invalid service account response")  
                        return  
                }  
  
                // 设置角色信息
                setServiceAccountHeaders(ctx, &saResponse, config)

                proxywasm.ResumeHttpRequest()

        }  
  
        if err := util.CallServiceAccountService(config.ServiceAccountEndpoint, &saRequest, callback); err != nil {  
                log.Errorf("failed to call service account service: %v", err)  
                return handleAuthFailure(ctx, config, http.StatusInternalServerError, "Service account service unavailable")  
        }  
  
        return types.ActionPause  
}  
  
func handleRoleToken(ctx wrapper.HttpContext, config config.RoleAuthConfig, authInfo *AuthInfo, log wrapper.Log) types.Action {  
        // 验证Role Token
        roleRequest := util.RoleTokenRequest{

                Token: authInfo.Token,

        }  
  
        callback := func(numHeaders, bodySize, numTrailers int) {  
                responseBody, err := proxywasm.GetHttpCallResponseBody(0, bodySize)  
                if err != nil {  
                        log.Errorf("failed to get role token response: %v", err)  
                        handleAuthFailure(ctx, config, http.StatusInternalServerError, "Role token verification failed")  
                        return  
                }  
  
                var roleResponse util.RoleTokenResponse  
                if err := json.Unmarshal(responseBody, &roleResponse); err != nil {  
                        log.Errorf("failed to parse role token response: %v", err)  
                        handleAuthFailure(ctx, config, http.StatusInternalServerError, "Invalid role token response")  
                        return  
                }  
  
                // 检查角色权限
                if !isRoleAllowed(roleResponse.Role, config.AllowedRoles) {  
                        log.Errorf("role %s is not allowed", roleResponse.Role)  
                        handleAuthFailure(ctx, config, http.StatusForbidden, "Role not authorized")  
                        return  
                }  
  
                // 设置角色信息
                setRoleTokenHeaders(ctx, &roleResponse, config)

                proxywasm.ResumeHttpRequest()

        }  
  
        if err := util.CallRoleTokenService(config.RoleTokenEndpoint, &roleRequest, callback); err != nil {  
                log.Errorf("failed to call role token service: %v", err)  
                return handleAuthFailure(ctx, config, http.StatusInternalServerError, "Role token service unavailable")  
        }  
  
        return types.ActionPause  
}  
  
func setRoleHeaders(ctx wrapper.HttpContext, response *util.STSAssumeRoleResponse, config config.RoleAuthConfig) {  
        // 设置用户角色信息到上游请求头
        for _, header := range config.UpstreamHeaders {  
                switch header {  
                case "x-user-role":  
                        proxywasm.AddHttpRequestHeader("x-user-role", response.AssumedRoleUser.Arn)  
                case "x-access-key-id":  
                        proxywasm.AddHttpRequestHeader("x-access-key-id", response.Credentials.AccessKeyId)  
                case "x-session-token":  
                        proxywasm.AddHttpRequestHeader("x-session-token", response.Credentials.SessionToken)  
                case "x-role-expiry":  
                        proxywasm.AddHttpRequestHeader("x-role-expiry", response.Credentials.Expiration)  
                }

        }  
}  
  
func setServiceAccountHeaders(ctx wrapper.HttpContext, response *util.ServiceAccountResponse, config config.RoleAuthConfig) {  
        for _, header := range config.UpstreamHeaders {  
                switch header {  
                case "x-user-role":  
                        proxywasm.AddHttpRequestHeader("x-user-role", response.ServiceAccount)  
                case "x-project-id":  
                        proxywasm.AddHttpRequestHeader("x-project-id", response.ProjectID)  
                case "x-role-expiry":  
                        proxywasm.AddHttpRequestHeader("x-role-expiry", response.TokenExpiry)  
                }

        }  
}  
  
func setRoleTokenHeaders(ctx wrapper.HttpContext, response *util.RoleTokenResponse, config config.RoleAuthConfig) {  
        for _, header := range config.UpstreamHeaders {  
                switch header {  
                case "x-user-role":  
                        proxywasm.AddHttpRequestHeader("x-user-role", response.Role)  
                case "x-user-id":  
                        proxywasm.AddHttpRequestHeader("x-user-id", response.UserID)  
                case "x-role-expiry":  
                        proxywasm.AddHttpRequestHeader("x-role-expiry", response.Expiry)  
                }

        }  
}  
  
func handleAuthFailure(ctx wrapper.HttpContext, config config.RoleAuthConfig, statusCode int, message string) types.Action {  
        if config.FailureModeAllow {  
                if config.FailureModeAllowHeaderAdd {  
                        proxywasm.AddHttpRequestHeader(HeaderFailureModeAllow, "true")  
                }  
                return types.ActionContinue  
        }  
  
        proxywasm.SendHttpResponse(uint32(statusCode), nil, []byte(message), -1)  
        return types.ActionPause  
}  
  
func isServiceAccountToken(token string) bool {  
        // 简单检查Service Account token格式
        return strings.Contains(token, "serviceAccount") || len(token) > 500  
}  
  
func isRoleAllowed(role string, allowedRoles []string) bool {  
        if len(allowedRoles) == 0 {  
                return true // 如果没有限制,允许所有角色
        }  
        for _, allowedRole := range allowedRoles {  
                if role == allowedRole {  
                        return true  
                }        }  
        return false  
}
  1. 配置结构
 // plugins/wasm-go/extensions/role-auth/config/config.go
package config  
  
import (  
        "encoding/json"  
        "errors"  
        "fmt"  
  
        "github.com/alibaba/higress/plugins/wasm-go/extensions/ext-auth/expr"  
        "github.com/tidwall/gjson"  
)  
  
type RoleAuthConfig struct {  
        // 认证类型常量
        WebIdentityTokenType string  
        ServiceAccountType   string  
        RoleTokenType       string  
  
        // 服务端点配置
        STSEndpoint            string `json:"sts_endpoint"`  
        ServiceAccountEndpoint string `json:"service_account_endpoint"`  
        RoleTokenEndpoint     string `json:"role_token_endpoint"`  
  
        // 认证配置
        TokenDuration    int      `json:"token_duration"`     // token有效期(秒)
        DefaultRole      string   `json:"default_role"`       // 默认角色
        AllowedRoles     []string `json:"allowed_roles"`      // 允许的角色列表
        UpstreamHeaders  []string `json:"upstream_headers"`   // 传递给上游的请求头
  
        // 匹配规则
        MatchRules expr.MatchRules `json:"match_rules"`  
  
        // 失败处理
        FailureModeAllow          bool `json:"failure_mode_allow"`  
        Fail  
 }

当前STS配置状况

在Higress中,STS服务只是作为配置选项存在.

需要一个配置项表明STS服务是可选的,通过设置非零端口值来启用.

STS服务实现

  1. STS服务主实现
 // pkg/sts/server.go
package sts  
  
import (  
        "context"  
        "crypto/rand"  
        "encoding/base64"  
        "encoding/json"  
        "fmt"  
        "net/http"  
        "time"  
  
        "github.com/golang-jwt/jwt/v5"  
        "istio.io/pkg/log"  
)  
  
type STSServer struct {  
        port           int  
        jwtSecret      []byte  
        tokenDuration  time.Duration

        oidcVerifier   OIDCVerifier

        roleValidator  RoleValidator  
}  
  
type OIDCVerifier interface {  
        VerifyToken(ctx context.Context, token string) (*OIDCClaims, error)  
}  
  
type RoleValidator interface {  
        ValidateRole(ctx context.Context, roleArn string, subject string) error  
}  
  
type OIDCClaims struct {  
        Subject   string `json:"sub"`  
        Issuer    string `json:"iss"`  
        Audience  string `json:"aud"`  
        ExpiresAt int64  `json:"exp"`  
}  
  
// AWS STS AssumeRoleWithWebIdentity请求结构
type AssumeRoleWithWebIdentityRequest struct {  
        RoleArn          string `json:"RoleArn"`  
        RoleSessionName  string `json:"RoleSessionName"`  
        WebIdentityToken string `json:"WebIdentityToken"`  
        DurationSeconds  int    `json:"DurationSeconds,omitempty"`  
}  
  
// AWS STS AssumeRole请求结构
type AssumeRoleRequest struct {  
        RoleArn         string `json:"RoleArn"`  
        RoleSessionName string `json:"RoleSessionName"`  
        DurationSeconds int    `json:"DurationSeconds,omitempty"`  
}  
  
// STS响应结构
type AssumeRoleResponse struct {  
        Credentials      Credentials      `json:"Credentials"`  
        AssumedRoleUser  AssumedRoleUser  `json:"AssumedRoleUser"`  
        PackedPolicySize int              `json:"PackedPolicySize,omitempty"`  
}  
  
type Credentials struct {  
        AccessKeyId     string `json:"AccessKeyId"`  
        SecretAccessKey string `json:"SecretAccessKey"`  
        SessionToken    string `json:"SessionToken"`  
        Expiration      string `json:"Expiration"`  
}  
  
type AssumedRoleUser struct {  
        AssumedRoleId string `json:"AssumedRoleId"`  
        Arn           string `json:"Arn"`  
}  
  
func NewSTSServer(port int, jwtSecret []byte, tokenDuration time.Duration) *STSServer {  
        return &STSServer{  
                port:          port,

                jwtSecret:     jwtSecret,

                tokenDuration: tokenDuration,

                oidcVerifier:  &DefaultOIDCVerifier{},

                roleValidator: &DefaultRoleValidator{},

        }  
}  
  
func (s *STSServer) Start() error {  
        mux := http.NewServeMux()  
          
        // AWS STS兼容的端点
        mux.HandleFunc("/", s.handleSTSRequest)  
          
        // 健康检查端点
        mux.HandleFunc("/health", s.handleHealth)  
        server := &http.Server{  
                Addr:    fmt.Sprintf(":%d", s.port),  
                Handler: mux,

        }  
          
        log.Infof("Starting STS server on port %d", s.port)  
        return server.ListenAndServe()  
}  
  
func (s *STSServer) handleSTSRequest(w http.ResponseWriter, r *http.Request) {  
        if r.Method != http.MethodPost {  
                http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)  
                return  
        }  
          
        // 解析AWS STS请求
        action := r.FormValue("Action")  
        version := r.FormValue("Version")  
          
        if version != "2011-06-15" {  
                http.Error(w, "Unsupported API version", http.StatusBadRequest)  
                return  
        }  
          
        switch action {  
        case "AssumeRoleWithWebIdentity":  
                s.handleAssumeRoleWithWebIdentity(w, r)  
        case "AssumeRole":  
                s.handleAssumeRole(w, r)  
        default:  
                http.Error(w, "Unsupported action", http.StatusBadRequest)  
        }  
}  
  
func (s *STSServer) handleAssumeRoleWithWebIdentity(w http.ResponseWriter, r *http.Request) {  
        // 解析请求参数
        roleArn := r.FormValue("RoleArn")  
        roleSessionName := r.FormValue("RoleSessionName")  
        webIdentityToken := r.FormValue("WebIdentityToken")  
        durationSeconds := 3600 // 默认1小时
          
        if roleArn == "" || roleSessionName == "" || webIdentityToken == "" {  
                http.Error(w, "Missing required parameters", http.StatusBadRequest)  
                return  
        }  
          
        // 验证OIDC token
        claims, err := s.oidcVerifier.VerifyToken(r.Context(), webIdentityToken)  
        if err != nil {  
                log.Errorf("Failed to verify OIDC token: %v", err)  
                http.Error(w, "Invalid web identity token", http.StatusUnauthorized)  
                return  
        }  
          
        // 验证角色权限
        if err := s.roleValidator.ValidateRole(r.Context(), roleArn, claims.Subject); err != nil {  
                log.Errorf("Role validation failed: %v", err)  
                http.Error(w, "Access denied", http.StatusForbidden)  
                return  
        }  
          
        // 生成临时凭证
        credentials, err := s.generateTemporaryCredentials(roleArn, roleSessionName, claims.Subject, durationSeconds)  
        if err != nil {  
                log.Errorf("Failed to generate credentials: %v", err)  
                http.Error(w, "Internal server error", http.StatusInternalServerError)  
                return  
        }  
          
        // 构建响应
        response := AssumeRoleResponse{                Credentials: *credentials,                AssumedRoleUser: AssumedRoleUser{  
                        AssumedRoleId: fmt.Sprintf("AROA%s:%s", generateRandomString(16), roleSessionName),  
                        Arn:           fmt.Sprintf("%s/%s", roleArn, roleSessionName),  
                },

        }  
          
        // 返回XML格式响应(AWS STS标准)
        s.writeXMLResponse(w, &response)  
}  
  
func (s *STSServer) handleAssumeRole(w http.ResponseWriter, r *http.Request) {  
        // 标准AssumeRole实现
        roleArn := r.FormValue("RoleArn")  
        roleSessionName := r.FormValue("RoleSessionName")  
        durationSeconds := 3600  
          
        if roleArn == "" || roleSessionName == "" {  
                http.Error(w, "Missing required parameters", http.StatusBadRequest)  
                return  
        }  
          
        // 从请求头获取当前凭证信息
        authHeader := r.Header.Get("Authorization")  
        if authHeader == "" {  
                http.Error(w, "Missing authorization header", http.StatusUnauthorized)  
                return  
        }  
          
        // 验证当前凭证并获取主体信息
        subject, err := s.validateCurrentCredentials(authHeader)  
        if err != nil {  
                log.Errorf("Failed to validate current credentials: %v", err)  
                http.Error(w, "Invalid credentials", http.StatusUnauthorized)  
                return  
        }  
          
        // 验证角色权限
        if err := s.roleValidator.ValidateRole(r.Context(), roleArn, subject); err != nil {  
                log.Errorf("Role validation failed: %v", err)  
                http.Error(w, "Access denied", http.StatusForbidden)  
                return  
        }  
          
        // 生成临时凭证
        credentials, err := s.generateTemporaryCredentials(roleArn, roleSessionName, subject, durationSeconds)  
        if err != nil {  
                log.Errorf("Failed to generate credentials: %v", err)  
                http.Error(w, "Internal server error", http.StatusInternalServerError)  
                return  
        }  
          
        // 构建响应
        response := AssumeRoleResponse{                Credentials: *credentials,                AssumedRoleUser: AssumedRoleUser{  
                        AssumedRoleId: fmt.Sprintf("AROA%s:%s", generateRandomString(16), roleSessionName),  
                        Arn:           fmt.Sprintf("%s/%s", roleArn, roleSessionName),  
                },

        }  

        s.writeXMLResponse(w, &response)  
}  
  
func (s *STSServer) generateTemporaryCredentials(roleArn, sessionName, subject string, durationSeconds int) (*Credentials, error) {  
        // 生成临时访问密钥
        accessKeyId := "ASIA" + generateRandomString(16)  
        secretAccessKey := generateRandomString(40)  
          
        // 生成会话令牌(JWT)
        now := time.Now()

        expiration := now.Add(time.Duration(durationSeconds) * time.Second)  
        token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{  
                "iss":      "higress-sts",  
                "sub":      subject,  
                "aud":      "higress",  
                "exp":      expiration.Unix(),  
                "iat":      now.Unix(),  
                "role_arn": roleArn,  
                "session":  sessionName,  
                "aki":      accessKeyId,  
        })  
        sessionToken, err := token.SignedString(s.jwtSecret)  
        if err != nil {  
                return nil, fmt.Errorf("failed to sign session token: %w", err)  
        }  
          
        return &Credentials{  
                AccessKeyId:     accessKeyId,                SecretAccessKey: secretAccessKey,                SessionToken:    sessionToken,                Expiration:      expiration.UTC().Format(time.RFC3339),  
        }, nil  
}  
  
func (s *STSServer) validateCurrentCredentials(authHeader string) (string, error) {  
        // 简化实现:从Authorization头解析JWT token
        if len(authHeader) > 7 && authHeader[:7] == "Bearer " {  
                tokenString := authHeader[7:]  
                  
                token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {  
                        if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {  
                                return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])  
                        }  
                        return s.jwtSecret, nil  
                })  
                  
                if err != nil {  
                        return "", err  
                }  
                  
                if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid {  
                        if sub, ok := claims["sub"].(string); ok {  
                                return sub, nil  
                        }

                }

        }  
          
        return "", fmt.Errorf("invalid authorization header")  
}  
  
func (s *STSServer) writeXMLResponse(w http.ResponseWriter, response *AssumeRoleResponse) {  
        w.Header().Set("Content-Type", "text/xml")  
          
        // 简化的XML响应
        xml := fmt.Sprintf(`<?xml version="1.0" encoding="UTF-8"?>  
<AssumeRoleWithWebIdentityResponse xmlns="https://sts.amazonaws.com/doc/2011-06-15/">  
    <AssumeRoleWithWebIdentityResult>  
        <Credentials>  
            <AccessKeyId>%s</AccessKeyId>  
            <SecretAccessKey>%s</SecretAccessKey>  
            <SessionToken>%s</SessionToken>  
            <Expiration>%s</Expiration>  
        </Credentials>  
        <AssumedRoleUser>  
            <AssumedRoleId>%s</AssumedRoleId>  
            <Arn>%s</Arn>  
        </AssumedRoleUser>  
    </AssumeRoleWithWebIdentityResult>  
</AssumeRoleWithWebIdentityResponse>`,  
                response.Credentials.AccessKeyId,

                response.Credentials.SecretAccessKey,

                response.Credentials.SessionToken,

                response.Credentials.Expiration,

                response.AssumedRoleUser.AssumedRoleId,

                response.AssumedRoleUser.Arn,

        )  
          
        w.Write([]byte(xml))  
}  
  
func (s *STSServer) handleHealth(w http.ResponseWriter, r *http.Request) {  
        w.WriteHeader(http.StatusOK)  
        w.Write([]byte("OK"))  
}  
  
func generateRandomString(length int) string {  
        bytes := make([]byte, length)  
        rand.Read(bytes)  
        return base64.URLEncoding.EncodeToString(bytes)[:length]  
}
  1. OIDC验证器实现
 // pkg/sts/oidc.go
package sts  
  
import (  
        "context"  
        "crypto/rsa"  
        "encoding/json"  
        "fmt"  
        "net/http"  
        "strings"  
        "time"  
  
        "github.com/golang-jwt/jwt/v5"  
)  
  
type DefaultOIDCVerifier struct {  
        httpClient *http.Client  
        jwksCache  map[string]*JWKSResponse  
}  
  
type JWKSResponse struct {  
        Keys []JWK `json:"keys"`  
}  
  
type JWK struct {  
        Kty string `json:"kty"`  
        Use string `json:"use"`  
        Kid string `json:"kid"`  
        N   string `json:"n"`  
        E   string `json:"e"`  
}  
  
type OIDCConfiguration struct {  
        Issuer                string `json:"issuer"`  
        AuthorizationEndpoint string `json:"authorization_endpoint"`  
        TokenEndpoint         string `json:"token_endpoint"`  
        JwksURI               string `json:"jwks_uri"`  
        UserInfoEndpoint      string `json:"userinfo_endpoint"`  
}  
  
func NewDefaultOIDCVerifier() *DefaultOIDCVerifier {  
        return &DefaultOIDCVerifier{  
                httpClient: &http.Client{  
                        Timeout: 10 * time.Second,  
                },  
                jwksCache: make(map[string]*JWKSResponse),  
        }  
}  
  
func (v *DefaultOIDCVerifier) VerifyToken(ctx context.Context, token string) (*OIDCClaims, error) {  
        // 解析JWT token而不验证签名(用于获取issuer和kid)
        parser := jwt.NewParser(jwt.WithoutClaimsValidation())        
        parsedToken, _, err := parser.ParseUnverified(token, jwt.MapClaims{})  
        if err != nil {  
                return nil, fmt.Errorf("failed to parse token: %w", err)  
        }  
        claims, ok := parsedToken.Claims.(jwt.MapClaims)  
        if !ok {  
                return nil, fmt.Errorf("invalid token claims")  
        }  
          
        issuer, ok := claims["iss"].(string)  
        if !ok {  
                return nil, fmt.Errorf("missing issuer in token")  
        }  
          
        // 获取kid(key ID)
        kid, ok := parsedToken.Header["kid"].(string)  
        if !ok {  
                return nil, fmt.Errorf("missing kid in token header")  
        }  
          
        // 获取OIDC配置
        oidcConfig, err := v.getOIDCConfiguration(ctx, issuer)  
        if err != nil {  
                return nil, fmt.Errorf("failed to get OIDC configuration: %w", err)  
        }  
          
        // 获取JWKS
        jwks, err := v.getJWKS(ctx, oidcConfig.JwksURI)  
        if err != nil {  
                return nil, fmt.Errorf("failed to get JWKS: %w", err)  
        }  
          
        // 找到对应的公钥
        publicKey, err := v.getPublicKey(jwks, kid)  
        if err != nil {  
                return nil, fmt.Errorf("failed to get public key: %w", err)  
        }  
          
        // 验证JWT签名
        validatedToken, err := jwt.Parse(token, func(token *jwt.Token) (interface{}, error) {  
                if _, ok := token.Method.(*jwt.SigningMethodRSA); !ok {  
                        return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])  
                }  
                return publicKey, nil  
        })  
          
        if err != nil {  
                return nil, fmt.Errorf("failed to validate token: %w", err)  
        }  
          
        if !validatedToken.Valid {  
                return nil, fmt.Errorf("token is not valid")  
        }  
          
        // 提取claims
        validatedClaims, ok := validatedToken.Claims.(jwt.MapClaims)  
        if !ok {  
                return nil, fmt.Errorf("invalid validated token claims")  
        }  
          
        // 验证基本claims
        if err := v.validateClaims(validatedClaims, issuer); err != nil {  
                return nil, fmt.Errorf("claims validation failed: %w", err)  
        }  
          
        // 构建OIDCClaims
        oidcClaims := &OIDCClaims{  
                Subject: validatedClaims["sub"].(string),  
                Issuer:  validatedClaims["iss"].(string),  
        }  
          
        if aud, ok := validatedClaims["aud"].(string); ok {  
                oidcClaims.Audience = aud

        }  
          
        if exp, ok := validatedClaims["exp"].(float64); ok {  
                oidcClaims.ExpiresAt = int64(exp)  
        }  
          
        return oidcClaims, nil  
}  
  
func (v *DefaultOIDCVerifier) getOIDCConfiguration(ctx context.Context, issuer string) (*OIDCConfiguration, error) {  
        // 构建OIDC配置端点URL
        configURL := strings.TrimSuffix(issuer, "/") + "/.well-known/openid_configuration"  
          
        req, err := http.NewRequestWithContext(ctx, "GET", configURL, nil)  
        if err != nil {  
                return nil, fmt.Errorf("failed to create request: %w", err)  
        }  
        resp, err := v.httpClient.Do(req)  
        if err != nil {  
                return nil, fmt.Errorf("failed to fetch OIDC configuration: %w", err)  
        }  
        defer resp.Body.Close()  
          
        if resp.StatusCode != http.StatusOK {  
                return nil, fmt.Errorf("OIDC configuration request failed with status: %d", resp.StatusCode)  
        }  
          
        var config OIDCConfiguration  
        if err := json.NewDecoder(resp.Body).Decode(&config); err != nil {  
                return nil, fmt.Errorf("failed to decode OIDC configuration: %w", err)  
        }  
          
        return &config, nil  
}  
  
func (v *DefaultOIDCVerifier) getJWKS(ctx context.Context, jwksURI string) (*JWKSResponse, error) {  
        // 检查缓存
        if jwks, exists := v.jwksCache[jwksURI]; exists {  
                return jwks, nil  
        }  
          
        req, err := http.NewRequestWithContext(ctx, "GET", jwksURI, nil)  
        if err != nil {  
                return nil, fmt.Errorf("failed to create JWKS request: %w", err)  
        }  
        resp, err := v.httpClient.Do(req)  
        if err != nil {  
                return nil, fmt.Errorf("failed to fetch JWKS: %w", err)  
        }  
        defer resp.Body.Close()  
          
        if resp.StatusCode != http.StatusOK {  
                return nil, fmt.Errorf("JWKS request failed with status: %d", resp.StatusCode)  
        }  
          
        var jwks JWKSResponse  
        if err := json.NewDecoder(resp.Body).Decode(&jwks); err != nil {  
                return nil, fmt.Errorf("failed to decode JWKS: %w", err)  
        }  
          
        // 缓存JWKS(简单实现,生产环境应考虑TTL)
        v.jwksCache[jwksURI] = &jwks  
          
        return &jwks, nil  
}  
  
func (v *DefaultOIDCVerifier) getPublicKey(jwks *JWKSResponse, kid string) (*rsa.PublicKey, error) {  
        for _, key := range jwks.Keys {  
                if key.Kid == kid && key.Kty == "RSA" {  
                        return v.parseRSAPublicKey(key)  
                }        }  
        return nil, fmt.Errorf("public key not found for kid: %s", kid)  
}  
  
func (v *DefaultOIDCVerifier) parseRSAPublicKey(jwk JWK) (*rsa.PublicKey, error) {  
        // 这里需要实现JWK到RSA公钥的转换
        // 简化实现,生产环境应使用专门的JWK库
        return nil, fmt.Errorf("RSA public key parsing not implemented")  
}  
  
func (v *DefaultOIDCVerifier) validateClaims(claims jwt.MapClaims, expectedIssuer string) error {  
        // 验证issuer
        if iss, ok := claims["iss"].(string); !ok || iss != expectedIssuer {  
                return fmt.Errorf("invalid issuer")  
        }  
          
        // 验证过期时间
        if exp, ok := claims["exp"].(float64); ok {  
                if time.Now().Unix() > int64(exp) {  
                        return fmt.Errorf("token has expired")  
                }  
        } else {  
                return fmt.Errorf("missing exp claim")  
        }  
          
        // 验证生效时间
        if iat, ok := claims["iat"].(float64); ok {  
                if time.Now().Unix() < int64(iat) {  
                        return fmt.Errorf("token not yet valid")  
                }

        }  
          
        // 验证subject
        if _, ok := claims["sub"].(string); !ok {  
                return fmt.Errorf("missing sub claim")  
        }  
          
        return nil  
}

alibaba/higresshelm/core/values.yaml

324    token:
325      aud: istio-ca
326
327  sts:
328    # -- The service port used by Security Token Service (STS) server to handle token exchange requests.
329    # Setting this port to a non-zero value enables STS server.
330    servicePort: 0
  1. 角色验证器实现
 // pkg/sts/role_validator.go
package sts  
  
import (  
        "context"  
        "fmt"  
        "strings"  
)  
  
type DefaultRoleValidator struct {  
        roleMapping map[string][]string // subject -> allowed roles
}  
  
func NewDefaultRoleValidator() *DefaultRoleValidator {  
        return &DefaultRoleValidator{  
                roleMapping: make(map[string][]string),  
        }  
}  
  
func (rv *DefaultRoleValidator) ValidateRole(ctx context.Context, roleArn string, subject string) error {  
        // 检查角色ARN格式
        if !strings.HasPrefix(roleArn, "arn:aws:iam::") {  
                return fmt.Errorf("invalid role ARN format: %s", roleArn)  
        }  
          
        // 检查subject是否有权限假设该角色
        allowedRoles, exists := rv.roleMapping[subject]  
        if !exists {  
                return fmt.Errorf("subject %s has no role mappings", subject)  
        }  
          
        for _, allowedRole := range allowedRoles {  
                if allowedRole == roleArn || allowedRole == "*" {  
                        return nil  
                }

        }  
          
        return fmt.Errorf("subject %s is not authorized to assume role %s", subject, roleArn)  
}  
  
func (rv *DefaultRoleValidator) AddRoleMapping(subject string, roles []string) {  
        rv.roleMapping[subject] = roles  
}
  1. STS服务启动器
 // pkg/sts/bootstrap.go
package sts  
  
import (  
        "fmt"  
        "time"  
)  
  
func StartSTSServer(port int, jwtSecret string) error {  
        if port <= 0 {  
                return fmt.Errorf("invalid port: %d", port)  
        }  
          
        server := NewSTSServer(port, []byte(jwtSecret), time.Hour)  
          
        // 设置OIDC验证器
        server.oidcVerifier = NewDefaultOIDCVerifier()  
          
        // 设置角色验证器
        roleValidator := NewDefaultRoleValidator()  
        // 添加一些示例角色映射
        roleValidator.AddRoleMapping("user@example.com", []string{  
                "arn:aws:iam::123456789012:role/HigressRole",  
                "arn:aws:iam::123456789012:role/ReadOnlyRole",  
        })

        server.roleValidator = roleValidator  
          
        return server.Start()  
}

这个的OIDC验证器实现包含了:

  1. OIDC配置获取:从.well-known/openid_configuration端点获取OIDC提供商配置
  2. JWKS获取和缓存:获取JSON Web Key Set用于验证JWT签名
  3. JWT签名验证:使用RSA公钥验证JWT签名
  4. Claims验证:验证token的有效期、issuer、subject等标准claims
  5. 角色验证器:验证用户是否有权限假设指定角色