如何验证GitHub Action请求来源?GITHUB_TOKEN验证失败求助
解决GitHub Action请求自有服务器的来源验证问题
为什么你的GITHUB_TOKEN方案失败
secrets.GITHUB_TOKEN是GitHub生成的临时仓库级Token,仅设计用于Action运行环境内部操作(如拉取代码、调用当前仓库API),其受众(audience)绑定GitHub自身,外部服务器直接使用该Token调用GitHub API时,会因身份验证上下文不匹配被拒绝,返回「Bad credentials」。- 即使给Token配置了足够权限,外部调用的安全限制依然会阻断请求,这是GitHub的安全设计,而非单纯的权限不足问题。
推荐解决方案:OIDC身份验证(适配多仓库场景)
这种方式无需在每个仓库配置固定密钥,通过GitHub的OIDC服务生成可验证的身份令牌,服务器直接验证令牌合法性即可确认请求来自合法的Action。
步骤1:配置组织级OIDC
- 进入GitHub组织设置 → Security → GitHub Actions → OIDC,启用OIDC。
- 在「Allowed audiences」中添加你的服务器域名(如
https://your-server-domain.com)。
步骤2:配置Workflow权限
在Workflow中显式声明OIDC令牌的读取权限,也可在组织设置中统一配置:
jobs: fetch-server-data: runs-on: ubuntu-latest permissions: id-token: write # 必须开启才能获取ID Token contents: read # 根据需求添加其他权限 steps: # 获取GitHub OIDC ID Token - name: Generate ID Token id: get-token uses: actions/github-script@v6 with: script: | const token = await core.getIDToken('https://your-server-domain.com'); core.setOutput('token', token); # 向自有服务器发送请求,携带ID Token - name: Request protected data run: | curl -X POST https://your-server-domain.com/api/your-endpoint \ -H "Authorization: Bearer ${{ steps.get-token.outputs.token }}"
步骤3:服务器端验证Token(Gin示例)
使用JWT库验证Token签名、有效期及声明信息,确认请求来自你的组织仓库:
package main import ( "context" "fmt" "net/http" "strings" "github.com/gin-gonic/gin" "github.com/golang-jwt/jwt/v5" "github.com/lestrrat-go/jwx/jwk" ) var jwksSet jwk.Set // 初始化时从GitHub获取公钥集 func init() { var err error jwksSet, err = jwk.Fetch(context.Background(), "https://token.actions.githubusercontent.com/.well-known/jwks.json") if err != nil { panic(fmt.Sprintf("failed to fetch JWKS: %v", err)) } } // 验证GitHub ID Token合法性 func verifyToken(tokenStr string) (bool, error) { token, err := jwt.Parse(tokenStr, func(token *jwt.Token) (interface{}, error) { // 验证签名算法为RSA if _, ok := token.Method.(*jwt.SigningMethodRSA); !ok { return nil, fmt.Errorf("invalid signing method: %v", token.Header["alg"]) } // 通过密钥ID从JWKS中获取公钥 kid, ok := token.Header["kid"].(string) if !ok { return nil, fmt.Errorf("kid not found in token header") } key, ok := jwksSet.LookupKeyID(kid) if !ok { return nil, fmt.Errorf("public key not found for kid: %s", kid) } var pubkey interface{} if err := key.Raw(&pubkey); err != nil { return nil, fmt.Errorf("failed to parse public key: %v", err) } return pubkey, nil }) if err != nil { return false, err } // 验证Token有效性及声明 if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid { // 验证受众匹配服务器域名 if aud := claims["aud"].(string); aud != "https://your-server-domain.com" { return false, fmt.Errorf("invalid audience") } // 验证仓库属于指定组织 if repo := claims["repository"].(string); !strings.HasPrefix(repo, "my_org/") { return false, fmt.Errorf("repository not in allowed organization") } // 可额外验证Workflow名称、运行环境等字段 return true, nil } return false, fmt.Errorf("invalid token") } // 处理请求的Handler func dataHandler(c *gin.Context) { authHeader := c.GetHeader("Authorization") if authHeader == "" || !strings.HasPrefix(authHeader, "Bearer ") { c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "missing valid authorization header"}) return } tokenStr := strings.TrimPrefix(authHeader, "Bearer ") valid, err := verifyToken(tokenStr) if !valid || err != nil { c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "invalid request", "details": err.Error()}) return } // 验证通过,返回数据 c.JSON(http.StatusOK, gin.H{"data": "your protected data"}) } func main() { r := gin.Default() r.POST("/api/your-endpoint", dataHandler) if err := r.Run(":8080"); err != nil { panic(fmt.Sprintf("server failed to start: %v", err)) } }
备选方案:修正GITHUB_TOKEN使用(不推荐)
如果一定要用GITHUB_TOKEN,需注意以下两点,但仍存在外部调用的安全限制风险:
- 显式配置Token权限:在Workflow中添加
permissions字段,确保Token有访问目标API的权限:permissions: actions: read # 访问actions/runs端点需要此权限 contents: read - 服务器调用API时使用正确的请求头:
curl -H "Authorization: token $GITHUB_TOKEN" \ -H "Accept: application/vnd.github.v3+json" \ https://api.github.com/repos/my_org/repo_name/actions/runs/run_id
内容的提问来源于stack exchange,提问作者afraid.jpg
相关产品推荐
相关产品推荐

