從源碼理解k8s特性ExternalServiceAccountTokenSigner

背景

k8s 1.32起新增了特性ExternalServiceAccountTokenSigner,用于從外部服務(wù)賬號生成token和獲取public key
在這之前都是在apiserver所在節(jié)點本地存儲的,默認在/etc/kubernetes/pki/sa.pub和/etc/kubernetes/pki/sa.key

此特性在k8s 1.34默認開啟,1.36后鎖定為true

簡單總結(jié)

apiserver 連接一個提供jwt 簽名的grpc服務(wù)
當client調(diào)用serviceaccount token create api時,apiserver會調(diào)用此服務(wù)生成token

apiserver 會定期從此grpc服務(wù)獲取public key更新到緩存,當client的serviceaccount token需要校驗時候,從緩存獲取public key進行驗證

源碼

參數(shù)相關(guān)

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

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

生成相關(guān)

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

serviceaccount token api處理函數(shù)入口
func (r *TokenREST) Create(ctx context.Context, name string, obj runtime.Object, createValidation rest.ValidateObjectFunc, options *metav1.CreateOptions) (runtime.Object, error) {
    ...
    構(gòu)建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中

構(gòu)建serviceaccount token 插件和publickey cache
func New(ctx context.Context, issuer, socketPath string, keySyncTimeout time.Duration, allowSigningWithNonOIDCKeys bool) (*Plugin, *keyCache, error) {
   ...
   鏈接serviceaccount token簽名服務(wù)
    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),
    )
    ...
    構(gòu)建serviceaccount token 插件
    plugin := newPlugin(issuer, conn, allowSigningWithNonOIDCKeys)
   ...
   定期更新serviceaccount token publickey cache
    go plugin.keyCache.scheduleSync(ctx, keySyncTimeout)

    ...

    return plugin, plugin.keyCache, nil
}

構(gòu)建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)
    ...

    轉(zhuǎn)換serviceaccount token payload為base64
    payloadBase64 := base64.RawURLEncoding.EncodeToString(payload)

    構(gòu)建serviceaccount token 簽名請求
    request := &externaljwtv1.SignJWTRequest{
        Claims: payloadBase64,
    }

    調(diào)用serviceaccount token 簽名服務(wù)
    response, err := p.client.Sign(ctx, request)
    ...
    構(gòu)建serviceaccount token
    return response.Header + "." + payloadBase64 + "." + response.Signature, nil
}

校驗相關(guān)

pkg/kubeapiserver/authenticator/config.go中

構(gòu)建認證配置
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)
    }
    ...
}


構(gòu)建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中

構(gòu)建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
    }
    ...
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容