K8s-kube-apiserver(请求与认证)
K8s-kube-apiserver(请求与认证)
基于1.25
kube-apiserver请求处理流程:
权限控制体系
认证
kube-apiserver支持多种认证机制,并且支持同时开启多个认证功能,组成认证链路
目前支持8种:
- RequestHeader
- ClientCA
- TokenAuth
- ServiceAccountAuht
- BootstrrapToken
- OIDC
- WebHookTokenAuth
- Anonymous
RequestHeader认证
kubeapiserver通过制定以下参数开启RequestHeader认证
--requestheader-client-ca-file
制定认证代理客户端证书签发所使用的CA证书,必选--requestheader-allowed-names
:指定允许的通用名称(CN)列表。如果设置则客户端中列表中,不然任选--requestheader-username-headers
:指定HTTP Header中用于设置用户名的字段名称列表,API Server按照顺序检查用户身份,第一个匹配结果作为用户名,必选--requestheader-group-headers
:指定在HTTP Header中用于设置用户组名,可选--requestheader-extra-headers-prefix
:指定其他用户信息的Header前缀列表
RequestHeader认证实现原理
所有的Authenticator.Request接口
-
func (a *requestHeaderAuthRequestHandler) AuthenticateRequest(req *http.Request) (*authenticator.Response, bool, error) {
name := headerValue(req.Header, a.nameHeaders.Value())
if len(name) == 0 {
return nil, false, nil
}
groups := allHeaderValues(req.Header, a.groupHeaders.Value())
extra := newExtra(req.Header, a.extraHeaderPrefixes.Value())
// clear headers used for authentication
ClearAuthenticationHeaders(req.Header, a.nameHeaders, a.groupHeaders, a.extraHeaderPrefixes)
return &authenticator.Response{
User: &user.DefaultInfo{
Name: name,
Groups: groups,
Extra: extra,
},
}, true, nil
}
Client CA认证
Client CA认证也被成为TLS双向认证
启用Client CA认证
kube-apiserver 通过制定--client-ca-file参数启用ClientCA认证
ClientCA认证实现原理
-
// AuthenticateRequest authenticates the request using presented client certificates
func (a *Authenticator) AuthenticateRequest(req *http.Request) (*authenticator.Response, bool, error) {
if req.TLS == nil || len(req.TLS.PeerCertificates) == 0 {
return nil, false, nil
}
// Use intermediates, if provided
optsCopy, ok := a.verifyOptionsFn()
// if there are intentionally no verify options, then we cannot authenticate this request
if !ok {
return nil, false, nil
}
if optsCopy.Intermediates == nil && len(req.TLS.PeerCertificates) > 1 {
optsCopy.Intermediates = x509.NewCertPool()
for _, intermediate := range req.TLS.PeerCertificates[1:] {
optsCopy.Intermediates.AddCert(intermediate)
}
}
remaining := req.TLS.PeerCertificates[0].NotAfter.Sub(time.Now())
clientCertificateExpirationHistogram.WithContext(req.Context()).Observe(remaining.Seconds())
chains, err := req.TLS.PeerCertificates[0].Verify(optsCopy)
if err != nil {
return nil, false, fmt.Errorf(
"verifying certificate %s failed: %w",
certificateIdentifier(req.TLS.PeerCertificates[0]),
err,
)
}
var errlist []error
for _, chain := range chains {
user, ok, err := a.user.User(chain)
if err != nil {
errlist = append(errlist, err)
continue
}
if ok {
return user, ok, err
}
}
return nil, false, utilerrors.NewAggregate(errlist)
} -
// CommonNameUserConversion builds user info from a certificate chain using the subject's CommonName
var CommonNameUserConversion = UserConversionFunc(func(chain []*x509.Certificate) (*authenticator.Response, bool, error) {
if len(chain[0].Subject.CommonName) == 0 {
return nil, false, nil
}
return &authenticator.Response{
User: &user.DefaultInfo{
Name: chain[0].Subject.CommonName,
Groups: chain[0].Subject.Organization,
},
}, true, nil
}) 证书的CommonName会被作为用户名,而Organization则被识别为用户组,默认情况下,kubelete利用证书实现身份认证,通过
[root@hcss-ecs-5425 ~]# openssl x509 -in /var/lib/kubelet/pki/kubelet-client-current.pem -text
Certificate:
Data:
Version: 3 (0x2)
Serial Number: 6091825874385188420 (0x548a835b5c433644)
Signature Algorithm: sha256WithRSAEncryption
Issuer: CN=kubernetes
Validity
Not Before: Nov 8 12:53:45 2024 GMT
Not After : Nov 8 12:53:47 2025 GMT
Subject: O=system:nodes, CN=system:node:node
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
Public-Key: (2048 bit)
Modulus:
00:ec:d5:47:0d:10:d0:45:6f:01:d9:3f:f2:65:14:
d4:a2:d8:ec:99:a6:fd:85:26:21:40:e9:df:79:5a:
51:f6:09:c6:cb:a2:63:da:04:8b:3f:a5:a4:62:0b:
74:1c:7e:8d:d8:fd:95:dc:f2:8c:bb:b8:f6:c9:f0:
a5:fb:25:a9:94:ab:17:62:7e:46:84:17:1e:fe:ea:
40:5b:12:63:20:cd:f4:f8:51:ec:fb:c3:0e:9b:cc:
7e:f8:f7:2d:dc:2d:a0:c0:64:08:99:71:82:73:f6:
7e:b0:ed:27:39:f3:7c:4c:c0:27:f0:2e:a6:c1:fd:
70:20:29:f5:b3:07:d8:7a:62:6d:f0:56:5c:f3:cf:
98:59:6a:d1:7d:af:84:6f:b9:af:78:fc:b9:6f:4d:
de:f5:36:8e:b4:23:81:e7:2f:1f:35:d8:c3:65:7b:
02:ad:7f:e8:69:e9:d7:7e:f1:48:45:0d:7e:d4:a7:
ec:62:30:48:b6:b2:42:b2:78:e5:20:ae:6d:b2:a3:
ef:13:fe:76:1a:cb:be:25:b7:19:00:66:14:b7:43:
44:0c:0d:f2:2f:74:89:d9:08:8d:c3:b1:d4:40:0c:
0d:d8:af:39:f5:fd:25:80:bb:36:60:f8:9c:e2:cb:
0f:4b:20:b9:2f:38:77:b8:23:e4:eb:7e:03:df:45:
bd:2d
Exponent: 65537 (0x10001)
X509v3 extensions:
X509v3 Key Usage: critical
Digital Signature, Key Encipherment
X509v3 Extended Key Usage:
TLS Web Client Authentication
X509v3 Basic Constraints: critical
CA:FALSE
X509v3 Authority Key Identifier:
keyid:E2:BF:05:8C:DE:F5:BA:A4:7F:CA:51:0B:26:17:E4:E3:63:14:0F:9C
Signature Algorithm: sha256WithRSAEncryption
16:d6:6a:52:12:99:2b:03:55:d3:a9:59:e6:95:8c:59:83:8b:
7f:ae:01:f0:04:78:32:00:18:05:9a:80:e1:3a:e6:bb:8a:63:
dd:48:a0:9b:9c:90:f0:f7:8c:13:f4:79:71:28:67:74:b9:d4:
8e:bb:ba:56:c4:db:25:4c:61:f7:6c:09:6e:a5:6b:c8:c1:b7:
94:14:20:90:ee:6c:1f:d1:96:32:17:33:7f:c4:de:69:fc:b0:
ac:f9:c3:ba:95:4a:da:bc:39:69:d2:02:3f:99:ed:8c:d7:ae:
e7:f6:a3:50:fa:31:e7:89:2e:29:cf:7a:d2:c1:ea:e4:8d:9a:
fc:cd:83:aa:22:7a:36:41:a9:27:a8:b0:5f:a1:3a:4f:02:18:
f7:d0:b4:6e:27:63:8a:c5:ec:77:0b:b1:2d:c8:1b:a7:3d:fd:
95:bc:3f:1a:cb:c7:d8:da:a7:30:1e:69:ba:48:c4:c6:28:f2:
f9:26:69:fd:b8:01:8d:69:a8:1c:b2:51:ae:cd:01:09:fd:82:
d2:6e:00:2d:d3:65:ee:c1:65:a2:93:68:d0:8d:af:39:1c:c7:
2f:a4:7f:0a:22:c2:2c:33:d3:90:ce:1c:50:6e:85:e0:e7:72:
10:0d:97:5f:5d:3f:a2:c0:02:6d:3f:f7:8d:88:c0:bb:7c:18:
0a:32:c4:11
-----BEGIN CERTIFICATE-----
MIIDHzCCAgegAwIBAgIIVIqDW1xDNkQwDQYJKoZIhvcNAQELBQAwFTETMBEGA1UE
AxMKa3ViZXJuZXRlczAeFw0yNDExMDgxMjUzNDVaFw0yNTExMDgxMjUzNDdaMDIx
FTATBgNVBAoTDHN5c3RlbTpub2RlczEZMBcGA1UEAxMQc3lzdGVtOm5vZGU6bm9k
ZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOzVRw0Q0EVvAdk/8mUU
1KLY7Jmm/YUmIUDp33laUfYJxsuiY9oEiz+lpGILdBx+jdj9ldzyjLu49snwpfsl
qZSrF2J+RoQXHv7qQFsSYyDN9PhR7PvDDpvMfvj3LdwtoMBkCJlxgnP2frDtJznz
fEzAJ/AupsH9cCAp9bMH2HpibfBWXPPPmFlq0X2vhG+5r3j8uW9N3vU2jrQjgecv
HzXYw2V7Aq1/6Gnp137xSEUNftSn7GIwSLayQrJ45SCubbKj7xP+dhrLviW3GQBm
FLdDRAwN8i90idkIjcOx1EAMDdivOfX9JYC7NmD4nOLLD0sguS84d7gj5Ot+A99F
vS0CAwEAAaNWMFQwDgYDVR0PAQH/BAQDAgWgMBMGA1UdJQQMMAoGCCsGAQUFBwMC
MAwGA1UdEwEB/wQCMAAwHwYDVR0jBBgwFoAU4r8FjN71uqR/ylELJhfk42MUD5ww
DQYJKoZIhvcNAQELBQADggEBABbWalISmSsDVdOpWeaVjFmDi3+uAfAEeDIAGAWa
gOE65ruKY91IoJuckPD3jBP0eXEoZ3S51I67ulbE2yVMYfdsCW6la8jBt5QUIJDu
bB/RljIXM3/E3mn8sKz5w7qVStq8OWnSAj+Z7YzXruf2o1D6MeeJLinPetLB6uSN
mvzNg6oiejZBqSeosF+hOk8CGPfQtG4nY4rF7HcLsS3IG6c9/ZW8PxrLx9japzAe
abpIxMYo8vkmaf24AY1pqByyUa7NAQn9gtJuAC3TZe7BZaKTaNCNrzkcxy+kfwoi
wiwz05DOHFBuheDnchANl19dP6LAAm0/942IwLt8GAoyxBE=
-----END CERTIFICATE-----
[root@hcss-ecs-5425 ~]#
-
TokenAuth认证
服务端为了客户端的身份认证,AuthToken是基于Token的认证
启用TokenAuth认证
kube-apiserver通过制定--token-auth-file
启用TokenAuth认证,Token Auth File是一个CSV格式的文件,每个用户在CSV中的格式为“token,user,name,user,uid,group”,Group是可选字段
TokenAuth认证实现原理
基于Token的认证器先实现authentucator.Token接口,进一步被分装为authenticator.Request接口的Authenticator和ProtocolAuthenticator分别用于普通的HTTP BearerToken 和面向Websocket连接的Bearer Token认证
在进行TokenAuth认证时,a.tokens中存储了服务的Token列表
-
type TokenAuthenticator struct {
tokens map[string]*user.DefaultInfo
}
ServiceAccountAuth认证
ServiceAccountAuth被称为服务账号令牌。
K8s有俩种用户:
- Normal users普通用户
- Service Account:服务账号
Service Account主要包含三个内容:Namespace、GA和Token
- Namespace:指定Pod所在的命名空间
- CA:kube-apiserver的CA公钥证书,Pod中的进程用它对kube-apiserver的提供了证书进行验证
- Token:用于身份验证,通过kube-apiserver私钥签发经过Base64编码的Bearer Token
- 它们都被挂载到Pod中
- Namespace的存储路径:/var/run/secrets/kubernetes.io/serviceaccount/namespace
- CA的存储路径:/var/run/secretskubernetes.io/serviceaccount/ca.crt
- Token的存储路径:/var/run/secretskubernetes.io/serviceaccount/ca.crt
启用ServiceAccountAuth认证
kube-apiserver通过以下参数启用ServiceAccountAuth认证:
--service-account-key-file
:指定验证签名使用的公钥文件路径,如果没有指定使用kube-apiserver 的TLS密钥--service-account-signing-key-file
:指定使用私有文件路径,自动为颁发的Token进行签名,可选--service-account-lookup
:验证Service Account Token是否存在etcd,默认true--service-account-isssuer
:用于指定Token颁发者表示,可以是字符串或者URL地址,可以颁发多个issuser
ServiceAccountAuth认证实现原理
ServiceAccountAuth认证也是基于Token进行的
旧版本的K8s,每个Service Account会分配一个secret对象,Secret永久保存有效Token
从1.24开始,不再自动为ServiceAccount分配secret对象,而是通过TokenRequestAPI动态申请Token自动挂载到Pod中
Pod启动的时候,自动配置一个ServiceAccount(如果没有配置,使用相同命名空间的SA)
-
func (j *jwtTokenAuthenticator) AuthenticateToken(ctx context.Context, tokenData string) (*authenticator.Response, bool, error) {
if !j.hasCorrectIssuer(tokenData) {
return nil, false, nil
}
tok, err := jwt.ParseSigned(tokenData)
if err != nil {
return nil, false, nil
}
public := &jwt.Claims{}
private := j.validator.NewPrivateClaims()
// TODO: Pick the key that has the same key ID as `tok`, if one exists.
var (
found bool
errlist []error
)
for _, key := range j.keys {
if err := tok.Claims(key, public, private); err != nil {
errlist = append(errlist, err)
continue
}
found = true
break
}
...
- 当Token通过验证,请求的用户名被设置为system:serviceaccount:
: ,请求的组名有俩个,system:serviceaccounts和system:servicenamecounts: ,UID即是ServiceAccount资源对象的UID
BootstrapToken认证
K8s提供了BootstrapToken认证(引导认证),把客户端的Token和服务端的Token进行匹配,认证通过之后,自动为节点颁发证书
客户端的请求头格式为:Authorization:Bearer xxx
xxx的格式为一个是TokenID.TokenSecret
-
func (a *Authenticator) AuthenticateRequest(req *http.Request) (*authenticator.Response, bool, error) {
auth := strings.TrimSpace(req.Header.Get("Authorization"))
if auth == "" {
return nil, false, nil
}
parts := strings.SplitN(auth, " ", 3)
if len(parts) < 2 || strings.ToLower(parts[0]) != "bearer" {
return nil, false, nil
}
token := parts[1]
// Empty bearer tokens aren't valid
if len(token) == 0 {
// The space before the token case
if len(parts) == 3 {
warning.AddWarning(req.Context(), "", invalidTokenWithSpaceWarning)
}
return nil, false, nil
}
resp, ok, err := a.auth.AuthenticateToken(req.Context(), token)
// if we authenticated successfully, go ahead and remove the bearer token so that no one
// is ever tempted to use it inside of the API server
if ok {
req.Header.Del("Authorization")
}
// If the token authenticator didn't error, provide a default error
if !ok && err == nil {
err = invalidToken
}
return resp, ok, err
} WebSocket的连接请求也是支持Token认证
-
func (a *ProtocolAuthenticator) AuthenticateRequest(req *http.Request) (*authenticator.Response, bool, error) {
// Only accept websocket connections
if !wsstream.IsWebSocketRequest(req) {
return nil, false, nil
}
token := ""
sawTokenProtocol := false
filteredProtocols := []string{}
for _, protocolHeader := range req.Header[protocolHeader] {
for _, protocol := range strings.Split(protocolHeader, ",") {
protocol = strings.TrimSpace(protocol)
if !strings.HasPrefix(protocol, bearerProtocolPrefix) {
filteredProtocols = append(filteredProtocols, protocol)
continue
}
if sawTokenProtocol {
return nil, false, errors.New("multiple base64.bearer.authorization tokens specified")
}
sawTokenProtocol = true
encodedToken := strings.TrimPrefix(protocol, bearerProtocolPrefix)
decodedToken, err := base64.RawURLEncoding.DecodeString(encodedToken)
if err != nil {
return nil, false, errors.New("invalid base64.bearer.authorization token encoding")
}
if !utf8.Valid(decodedToken) {
return nil, false, errors.New("invalid base64.bearer.authorization token")
}
token = string(decodedToken)
}
}
// Must pass at least one other subprotocol so that we can remove the one containing the bearer token,
// and there is at least one to echo back to the client
if len(token) > 0 && len(filteredProtocols) == 0 {
return nil, false, errors.New("missing additional subprotocol")
}
if len(token) == 0 {
return nil, false, nil
}
resp, ok, err := a.auth.AuthenticateToken(req.Context(), token)
// on success, remove the protocol with the token
if ok {
// https://tools.ietf.org/html/rfc6455#section-11.3.4 indicates the Sec-WebSocket-Protocol header may appear multiple times
// in a request, and is logically the same as a single Sec-WebSocket-Protocol header field that contains all values
req.Header.Set(protocolHeader, strings.Join(filteredProtocols, ","))
}
// If the token authenticator didn't error, provide a default error
if !ok && err == nil {
err = errInvalidToken
}
return resp, ok, err
}
-
启用BootstrapToken认证
kube-apiserver通过制定--enable-bootstrap-token-auth
参数启用BootrapToken认证
BootstrapToken认证实现原理
- 根据parseToken解析出TokenID和TokenSecret
- 根据固定规则(bootstrap-token-
)从K8s中获取Secret,验证Secret有效性
- 根据固定规则(bootstrap-token-
-
// AuthenticateToken tries to match the provided token to a bootstrap token secret
// in a given namespace. If found, it authenticates the token in the
// "system:bootstrappers" group and with the "system:bootstrap:(token-id)" username.
//
// All secrets must be of type "bootstrap.kubernetes.io/token". An example secret:
//
// apiVersion: v1
// kind: Secret
// metadata:
// # Name MUST be of form "bootstrap-token-( token id )".
// name: bootstrap-token-( token id )
// namespace: kube-system
// # Only secrets of this type will be evaluated.
// type: bootstrap.kubernetes.io/token
// data:
// token-secret: ( private part of token )
// token-id: ( token id )
// # Required key usage.
// usage-bootstrap-authentication: true
// auth-extra-groups: "system:bootstrappers:custom-group1,system:bootstrappers:custom-group2"
// # May also contain an expiry.
//
// Tokens are expected to be of the form:
//
// ( token-id ).( token-secret )
func (t *TokenAuthenticator) AuthenticateToken(ctx context.Context, token string) (*authenticator.Response, bool, error) {
tokenID, tokenSecret, err := bootstraptokenutil.ParseToken(token)
if err != nil {
// Token isn't of the correct form, ignore it.
return nil, false, nil
}
secretName := bootstrapapi.BootstrapTokenSecretPrefix + tokenID
secret, err := t.lister.Get(secretName)
if err != nil {
if errors.IsNotFound(err) {
klog.V(3).Infof("No secret of name %s to match bootstrap bearer token", secretName)
return nil, false, nil
}
return nil, false, err
}
if secret.DeletionTimestamp != nil {
tokenErrorf(secret, "is deleted and awaiting removal")
return nil, false, nil
}
if string(secret.Type) != string(bootstrapapi.SecretTypeBootstrapToken) || secret.Data == nil {
tokenErrorf(secret, "has invalid type, expected %s.", bootstrapapi.SecretTypeBootstrapToken)
return nil, false, nil
}
ts := bootstrapsecretutil.GetData(secret, bootstrapapi.BootstrapTokenSecretKey)
if subtle.ConstantTimeCompare([]byte(ts), []byte(tokenSecret)) != 1 {
tokenErrorf(secret, "has invalid value for key %s.", bootstrapapi.BootstrapTokenSecretKey)
return nil, false, nil
}
id := bootstrapsecretutil.GetData(secret, bootstrapapi.BootstrapTokenIDKey)
if id != tokenID {
tokenErrorf(secret, "has invalid value for key %s.", bootstrapapi.BootstrapTokenIDKey)
return nil, false, nil
}
if bootstrapsecretutil.HasExpired(secret, time.Now()) {
// logging done in isSecretExpired method.
return nil, false, nil
}
if bootstrapsecretutil.GetData(secret, bootstrapapi.BootstrapTokenUsageAuthentication) != "true" {
tokenErrorf(secret, "not marked %s=true.", bootstrapapi.BootstrapTokenUsageAuthentication)
return nil, false, nil
}
groups, err := bootstrapsecretutil.GetGroups(secret)
if err != nil {
tokenErrorf(secret, "has invalid value for key %s: %v.", bootstrapapi.BootstrapTokenExtraGroupsKey, err)
return nil, false, nil
}
return &authenticator.Response{
User: &user.DefaultInfo{
Name: bootstrapapi.BootstrapUserPrefix + string(id),
Groups: groups,
},
}, true, nil
}
OIDC认证
OIDC认证是一套基于OAtuh2.0协议的亲量级别认证规范,提供通过API进行身份交互的框架
认证流程:
- Client->AuthServer,得到access_token,id_token和refresh_token
- 客户端把id_token配置到Application中(如kubectl –token参数或者kubeconfig文件)
- 客户端使用Token对应的User身份访问kube-apiserver
kube-apiserver和Auth Server没有直接交互,而是通过证书验证签名的方法实现了坚定Token是否合法,下面是OIDC认证完整流程:
- 用户登陆提供商Identity Provider(Auth Server)
- Auth Server认证用户身份,返回access_token、id_token和refresh_token
- 用户使用kubectl,通过–token指定id_token,或者把id_token写入到kubeconfig
- kubectl和id_token设置为Authorization的请求头,发送到kube-apiserver
- kube-apiserver通过配置的证书验证JWT签名是否有效
- 检查确保id_token未过期
- 检查确保用户获取授权
- 获得授权后,kube-apiserver指定对应的操作,返回响应给客户端kubectl
- kubectl向用户提供结果反馈
kube-apiserver为了不和Auth Server交互认证Token,在第五步:
- JWT都被其颁发AuthServer进行了数字签名,只需要kube-apiserver中配置信任的Auth,只需要在kube-apiserver中配置所认证的AuthServer证书,使用它验证收到的id_token中签名是否合法
- 基于使用这种PKI的验证机制,在配置完成后,在认证过程kube-apiserver无需和Auth Server有任何交互
启用OIDC认证
kube-apiserver通过指定参数启用OIDC认证:
--oidc-issuer-url
:Auth Server的URL,kube-apiserver可以通过该地址发现和读取签名验证需要的信息,必选--oidc-client-id
:所有的Token颁发使用的CLientID,如k8s,必选--oidc-username-claim
:JWT声明的用户名称(默认为sub),可选--oidc-username-prefix
:用户名称前缀,所有用户名称添加前缀,禁用前缀需要使用-
,可选--oidc-groups-claim
:JWT声明组名,可选--oidc-groups-prefix
:组名前缀,可选--oidc-ca-file
:使用改CA验证OpenID Server提供的Web证书,没有设置使用操作系统的rootCA验证,可选--oidc-signing-algs
:可以被接受的签名算法,当前支持RS256,RS384,RS51,ES256,ES384,ES512,PS256,PS382,PS512--oidc-required-claim
:键值对,用于藐视IDToken中必要声明,如果设置,验证声明中是否有匹配值存在IDToken中,重复指定此参数多个声明,可选
OIDC认证实现原理
-
func (a *Authenticator) AuthenticateToken(ctx context.Context, token string) (*authenticator.Response, bool, error) {
if !hasCorrectIssuer(a.issuerURL, token) {
return nil, false, nil
}
verifier, ok := a.idTokenVerifier()
if !ok {
return nil, false, fmt.Errorf("oidc: authenticator not initialized")
}
idToken, err := verifier.Verify(ctx, token)
if err != nil {
return nil, false, fmt.Errorf("oidc: verify token: %v", err)
}
var c claims
if err := idToken.Claims(&c); err != nil {
return nil, false, fmt.Errorf("oidc: parse claims: %v", err)
}
if a.resolver != nil {
if err := a.resolver.expand(c); err != nil {
return nil, false, fmt.Errorf("oidc: could not expand distributed claims: %v", err)
}
}
var username string
if err := c.unmarshalClaim(a.usernameClaim, &username); err != nil {
return nil, false, fmt.Errorf("oidc: parse username claims %q: %v", a.usernameClaim, err)
}
if a.usernameClaim == "email" {
// If the email_verified claim is present, ensure the email is valid.
// https://openid.net/specs/openid-connect-core-1_0.html#StandardClaims
if hasEmailVerified := c.hasClaim("email_verified"); hasEmailVerified {
var emailVerified bool
if err := c.unmarshalClaim("email_verified", &emailVerified); err != nil {
return nil, false, fmt.Errorf("oidc: parse 'email_verified' claim: %v", err)
}
// If the email_verified claim is present we have to verify it is set to `true`.
if !emailVerified {
return nil, false, fmt.Errorf("oidc: email not verified")
}
}
}
if a.usernamePrefix != "" {
username = a.usernamePrefix + username
}
info := &user.DefaultInfo{Name: username}
if a.groupsClaim != "" {
if _, ok := c[a.groupsClaim]; ok {
// Some admins want to use string claims like "role" as the group value.
// Allow the group claim to be a single string instead of an array.
//
// See: https://github.com/kubernetes/kubernetes/issues/33290
var groups stringOrArray
if err := c.unmarshalClaim(a.groupsClaim, &groups); err != nil {
return nil, false, fmt.Errorf("oidc: parse groups claim %q: %v", a.groupsClaim, err)
}
info.Groups = []string(groups)
}
}
if a.groupsPrefix != "" {
for i, group := range info.Groups {
info.Groups[i] = a.groupsPrefix + group
}
}
// check to ensure all required claims are present in the ID token and have matching values.
for claim, value := range a.requiredClaims {
if !c.hasClaim(claim) {
return nil, false, fmt.Errorf("oidc: required claim %s not present in ID token", claim)
}
// NOTE: Only string values are supported as valid required claim values.
var claimValue string
if err := c.unmarshalClaim(claim, &claimValue); err != nil {
return nil, false, fmt.Errorf("oidc: parse claim %s: %v", claim, err)
}
if claimValue != value {
return nil, false, fmt.Errorf("oidc: required claim %s value does not match. Got = %s, want = %s", claim, claimValue, value)
}
}
return &authenticator.Response{User: info}, true, nil
}
WebhookTokenAuth认证
Webhook称为钩子,基于HTTP回调的机制
启用WebhookTokenAuth认证
kube-apiserver通过制定参数启用WebhookTokenAuth认证
--authentication-token-webhook-config-file
:kubeconfig格式的Webhook配置文件,描述了如何访问远程Webhook服务器--authentication-token-webhook-cache-ttl
:认证结果返回,默认2min--authentication-token-webhook-cache-version
:指定Webhook服务器交互使用TokenReview API版本
WebhookTokenAuth认证实现原理
-
// AuthenticateToken implements authenticator.Token
func (a *cachedTokenAuthenticator) AuthenticateToken(ctx context.Context, token string) (*authenticator.Response, bool, error) {
record := a.doAuthenticateToken(ctx, token)
if !record.ok || record.err != nil {
return nil, false, record.err
}
for key, value := range record.annotations {
audit.AddAuditAnnotation(ctx, key, value)
}
return record.resp, true, nil
}
Anonymous认证
Anonymous认证称为匿名认证,未被其他认证器拒绝的请求视为匿名请求
启用Anonymous认证
kube-apiserber通过制定--anonymous-auth
启用Anonymous认证,默认false
Anonymous认证实现原理
识别出来的用户名:system:anonymous,对应的用户组为:system:umauthenticated
-
func NewAuthenticator() authenticator.Request {
return authenticator.RequestFunc(func(req *http.Request) (*authenticator.Response, bool, error) {
auds, _ := authenticator.AudiencesFrom(req.Context())
return &authenticator.Response{
User: &user.DefaultInfo{
Name: anonymousUser,
Groups: []string{unauthenticatedGroup},
},
Audiences: auds,
}, true, nil
})
}