从源码理解k8s特性ExternalServiceAccountTokenSigner

背景

k8s 1.32起新增了特性ExternalServiceAccountTokenSigner,用于从外部服务账号生成token和获取public key
在这之前都是在apiserver所在节点本地存储的,默认在/etc/kubernetes/pki/sa.pub和/etc/kubernetes/pki/sa.key

此特性在k8s 1.34默认开启,1.36后锁定为true

简单总结

apiserver 连接一个提供jwt 签名的grpc服务
当client调用serviceaccount token create api时,apiserver会调用此服务生成token

apiserver 会定期从此grpc服务获取public key更新到缓存,当client的serviceaccount token需要校验时候,从缓存获取public key进行验证

源码

参数相关

pkg/controlplane/apiserver/options/options.go中

构建serviceaccount配置
func (o *Options) completeServiceAccountOptions(ctx context.Context, completed *completedOptions) error {
    ...
    构建serviceaccount插件和publickey cache
    plugin, cache, err := plugin.New(ctx, completed.Authentication.ServiceAccountIssuers[0], completed.ServiceAccountSigningEndpoint, 60*time.Second, false)
    ...
    设置serviceaccount插件和publickey cache
    completed.ServiceAccountIssuer = plugin
    completed.Authentication.ServiceAccounts.ExternalPublicKeysGetter = cache
    ...
}

生成相关

pkg/registry/core/serviceaccount/storage/token.go中

serviceaccount token api处理函数入口
func (r *TokenREST) Create(ctx context.Context, name string, obj runtime.Object, createValidation rest.ValidateObjectFunc, options *metav1.CreateOptions) (runtime.Object, error) {
    ...
    构建serviceaccount token claims
    sc, pc, err := token.Claims(*svcacct, pod, secret, node, exp, warnAfter, req.Spec.Audiences)
    ...
    生成serviceaccount token
    tokdata, err := r.issuer.GenerateToken(ctx, sc, pc)
    ...
}

pkg/serviceaccount/externaljwt/plugin/plugin.go中

构建serviceaccount token 插件和publickey cache
func New(ctx context.Context, issuer, socketPath string, keySyncTimeout time.Duration, allowSigningWithNonOIDCKeys bool) (*Plugin, *keyCache, error) {
   ...
   链接serviceaccount token签名服务
    conn, err := grpc.Dial(
        socketPath,
        grpc.WithTransportCredentials(insecure.NewCredentials()),
        grpc.WithAuthority("localhost"),
        grpc.WithDefaultCallOptions(grpc.WaitForReady(true)),
        grpc.WithContextDialer(func(ctx context.Context, path string) (net.Conn, error) {
            return (&net.Dialer{}).DialContext(ctx, "unix", path)
        }),
        grpc.WithChainUnaryInterceptor(externaljwtmetrics.OuboundRequestMetricsInterceptor),
    )
    ...
    构建serviceaccount token 插件
    plugin := newPlugin(issuer, conn, allowSigningWithNonOIDCKeys)
   ...
   定期更新serviceaccount token publickey cache
    go plugin.keyCache.scheduleSync(ctx, keySyncTimeout)

    ...

    return plugin, plugin.keyCache, nil
}

构建serviceaccount token 插件
func newPlugin(iss string, conn *grpc.ClientConn, allowSigningWithNonOIDCKeys bool) *Plugin {
    client := externaljwtv1.NewExternalJWTSignerClient(conn)
    plugin := &Plugin{
        iss:                         iss,
        client:                      client,
        allowSigningWithNonOIDCKeys: allowSigningWithNonOIDCKeys,
        keyCache:                    newKeyCache(client),
    }
    return plugin
}



生成serviceaccount token
func (p *Plugin) GenerateToken(ctx context.Context, claims *jwt.Claims, privateClaims interface{}) (string, error) {
    jwt, err := p.signAndAssembleJWT(ctx, claims, privateClaims)
    ...
}

生成serviceaccount token
func (p *Plugin) signAndAssembleJWT(ctx context.Context, claims *jwt.Claims, privateClaims interface{}) (string, error) {
    ...
    生成serviceaccount token payload
    payload, err := mergeClaims(p.iss, claims, privateClaims)
    ...

    转换serviceaccount token payload为base64
    payloadBase64 := base64.RawURLEncoding.EncodeToString(payload)

    构建serviceaccount token 签名请求
    request := &externaljwtv1.SignJWTRequest{
        Claims: payloadBase64,
    }

    调用serviceaccount token 签名服务
    response, err := p.client.Sign(ctx, request)
    ...
    构建serviceaccount token
    return response.Header + "." + payloadBase64 + "." + response.Signature, nil
}

校验相关

pkg/kubeapiserver/authenticator/config.go中

构建认证配置
func (config Config) New(serverLifecycle context.Context) (authenticator.Request, func(context.Context, *apiserver.AuthenticationConfiguration) error, *spec.SecurityDefinitions, spec3.SecuritySchemes, error) {
    ...
    if len(config.ServiceAccountIssuers) > 0 && config.ServiceAccountPublicKeysGetter != nil {
        serviceAccountAuth, err := newServiceAccountAuthenticator(config.ServiceAccountIssuers, config.ServiceAccountPublicKeysGetter, config.APIAudiences, config.ServiceAccountTokenGetter)
        if err != nil {
            return nil, nil, nil, nil, err
        }
        tokenAuthenticators = append(tokenAuthenticators, serviceAccountAuth)
    }
    ...
}


构建serviceaccount token 认证器
func newServiceAccountAuthenticator(issuers []string, publicKeysGetter serviceaccount.PublicKeysGetter, apiAudiences authenticator.Audiences, serviceAccountGetter serviceaccount.ServiceAccountTokenGetter) (authenticator.Token, error) {
    ...
    tokenAuthenticator := serviceaccount.JWTTokenAuthenticator(issuers, publicKeysGetter, apiAudiences, serviceaccount.NewValidator(serviceAccountGetter))
    return tokenAuthenticator, nil
}

pkg/serviceaccount/jwt.go中

构建serviceaccount token 认证器
func JWTTokenAuthenticator[PrivateClaims any](issuers []string, publicKeysGetter PublicKeysGetter, implicitAuds authenticator.Audiences, validator Validator[PrivateClaims]) authenticator.Token {
    issuersMap := make(map[string]bool)
    for _, issuer := range issuers {
        issuersMap[issuer] = true
    }
    return &jwtTokenAuthenticator[PrivateClaims]{
        issuers:      issuersMap,
        keysGetter:   publicKeysGetter,
        implicitAuds: implicitAuds,
        validator:    validator,
    }
}

认证serviceaccount token
func (j *jwtTokenAuthenticator[PrivateClaims]) AuthenticateToken(ctx context.Context, tokenData string) (*authenticator.Response, bool, error) {
    ...
    获取serviceaccount token publickey
    keys := j.keysGetter.GetPublicKeys(ctx, kid)
    ...
    校验
    for _, key := range keys {
        if err := tok.Claims(key.PublicKey, public, private); err != nil {
            errlist = append(errlist, err)
            continue
        }
        found = true
        break
    }
    ...
}
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容