You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
kubeSourceCodeNote/apiServer/Kubernetes源码学习-APIServer-P3...

791 lines
28 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

## 前言
在上一篇[**APIServer-P2-启动流程**](https://github.com/yinwenqin/kubeSourceCodeNote/blob/master/apiServer/Kubernetes%E6%BA%90%E7%A0%81%E5%AD%A6%E4%B9%A0-APIServer-P2-%E5%90%AF%E5%8A%A8%E6%B5%81%E7%A8%8B.md)中把APIServer预启动、加载配置、启动 的流程大致过了一遍其中也有提到APIServer的认证机制分为多种以组合的形式遍历认证任一机制认证成功则判定请求认证成功顺利进入下一个授权判定的环节。那么在本篇就来详细看看这些认证机制。
## Kubernetes用户
所有 Kubernetes 集群都有两类用户:由 Kubernetes 管理的**服务账号**和**普通用户**。
其中服务账号(ServiceAccount)是提供给集群中的程序使用以Secret资源保存凭据挂载到pod中从而允许集群内的服务调用k8s API。
而普通用户尚不支持使用API创建一般由证书创建Kubernetes 使用证书中的 'subject' 的通用名称Common Name字段例如"/CN=bob")来 确定用户名。
> 以上摘自官方文档
## 身份认证策略
Kubernetes 使用身份认证插件利用客户端证书、持有者令牌Bearer Token、身份认证代理Proxy 或者 HTTP 基本认证机制来认证 API 请求的身份。HTTP 请求发给 API 服务器时, 插件会将以下属性关联到请求本身:
- 用户名:用来辩识最终用户的字符串。常见的值可以是 `kube-admin``jane@example.com`
- 用户 ID用来辩识最终用户的字符串旨在比用户名有更好的一致性和唯一性。
- 用户组:取值为一组字符串,其中各个字符串用来标明用户是某个命名的用户逻辑集合的成员。 常见的值可能是 `system:masters` 或者 `devops-team` 等。
- 附加字段:一组额外的键-值映射,键是字符串,值是一组字符串;用来保存一些鉴权组件可能 觉得有用的额外信息。
与其它身份认证协议LDAP、SAML、Kerberos、X509 的替代模式等等)都可以通过 使用一个[身份认证代理](https://kubernetes.io/zh/docs/reference/access-authn-authz/authentication/#authenticating-proxy)或 [身份认证 Webhoook](https://kubernetes.io/zh/docs/reference/access-authn-authz/authentication/#webhook-token-authentication)来实现。
> 以上摘自官方文档
## 认证机制
### 流程图
首先为了加深印象,再贴一下上一章列出的认证流程图:
![](https://mycloudn.wqyin.cn/20210203112246.png)
> 注:认证器的执行顺序为随机的,图中流向不代表固定顺序
当客户端发送请求到达APIServer端请求首先进入认证环节对应处理认证的是Authentication Handler方法
Authentication Handler方法中遍历每一个已启用的认证器仅需某一个认证器返回true则认证成功结束遍历若所有认证器全部返回false则认证失败。
代码路径:
`staging/src/k8s.io/apiserver/pkg/server/config.go:550`
--> `vendor/k8s.io/apiserver/pkg/endpoints/filters/authentication.go:53`
```go
func WithAuthentication(handler http.Handler, auth authenticator.Request, failed http.Handler, apiAuds authenticator.Audiences) http.Handler {
// 关闭认证则直接进入下一个环节
if auth == nil {
klog.Warningf("Authentication is disabled")
return handler
}
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
if len(apiAuds) > 0 {
req = req.WithContext(authenticator.WithAudiences(req.Context(), apiAuds))
}
// 这里进行认证
resp, ok, err := auth.AuthenticateRequest(req)
if err != nil || !ok {
if err != nil {
klog.Errorf("Unable to authenticate the request due to an error: %v", err)
}
failed.ServeHTTP(w, req)
return
}
// TODO(mikedanese): verify the response audience matches one of apiAuds if
// non-empty
// authorization header is not required anymore in case of a successful authentication.
req.Header.Del("Authorization")
// 请求上下文携带上认证后的用户的信息
req = req.WithContext(genericapirequest.WithUser(req.Context(), resp.User))
// 计数器
authenticatedUserCounter.WithLabelValues(compressUsername(resp.User.GetName())).Inc()
// 处理请求
handler.ServeHTTP(w, req)
})
}
```
`auth.AuthenticateRequest`是一个接口方法,看看它的引用:
`vendor/k8s.io/apiserver/pkg/authentication/authenticator/interfaces.go:35`
![](https://mycloudn.wqyin.cn/20210202115500.png)
引用中的union.go中聚合了所有的Authenticator链进去看看
`staging/src/k8s.io/apiserver/pkg/authentication/request/union/union.go:53`
```go
// AuthenticateRequest authenticates the request using a chain of authenticator.Request objects.
func (authHandler *unionAuthRequestHandler) AuthenticateRequest(req *http.Request) (*authenticator.Response, bool, error) {
var errlist []error
for _, currAuthRequestHandler := range authHandler.Handlers {
resp, ok, err := currAuthRequestHandler.AuthenticateRequest(req)
if err != nil {
if authHandler.FailOnError {
return resp, ok, err
}
errlist = append(errlist, err)
continue
}
if ok {
return resp, ok, err
}
}
return nil, false, utilerrors.NewAggregate(errlist)
}
```
apiserver的各种request相关定义代码大多集中在这个目录里
<img src="https://mycloudn.wqyin.cn/20210202145632.png" style="zoom:70%;" />
那么依次逐个来看看每一种Authenticator对应的AuthenticateRequest方法。
### RequestHeader认证
是一种代理认证方式需要再apiserver启动时以参数形式配置来看看官方的介绍
API 服务器可以配置成从请求的头部字段值(如 `X-Remote-User`)中辩识用户。 这一设计是用来与某身份认证代理一起使用 API 服务器,代理负责设置请求的头部字段值。
- `--requestheader-username-headers` 必需字段,大小写不敏感。用来设置要获得用户身份所要检查的头部字段名称列表(有序)。第一个包含数值的字段会被用来提取用户名。
- `--requestheader-group-headers` 可选字段,在 Kubernetes 1.6 版本以后支持,大小写不敏感。 建议设置为 "X-Remote-Group"。用来指定一组头部字段名称列表,以供检查用户所属的组名称。 所找到的全部头部字段的取值都会被用作用户组名。
- `--requestheader-extra-headers-prefix` 可选字段,在 Kubernetes 1.6 版本以后支持,大小写不敏感。 建议设置为 "X-Remote-Extra-"。用来设置一个头部字段的前缀字符串API 服务器会基于所给 前缀来查找与用户有关的一些额外信息。这些额外信息通常用于所配置的鉴权插件。 API 服务器会将与所给前缀匹配的头部字段过滤出来,去掉其前缀部分,将剩余部分 转换为小写字符串并在必要时执行[百分号解码](https://tools.ietf.org/html/rfc3986#section-2.1) 后,构造新的附加信息字段键名。原来的头部字段值直接作为附加信息字段的值。
例如,使用下面的配置:
```
--requestheader-username-headers=X-Remote-User
--requestheader-group-headers=X-Remote-Group
--requestheader-extra-headers-prefix=X-Remote-Extra-
```
针对所收到的如下请求:
```http
GET / HTTP/1.1
X-Remote-User: fido
X-Remote-Group: dogs
X-Remote-Group: dachshunds
X-Remote-Extra-Acme.com%2Fproject: some-project
X-Remote-Extra-Scopes: openid
X-Remote-Extra-Scopes: profile
```
会生成下面的用户信息:
```yaml
name: fido
groups:
- dogs
- dachshunds
extra:
acme.com/project:
- some-project
scopes:
- openid
- profile
```
为了防范头部信息侦听,在请求中的头部字段被检视之前, 身份认证代理需要向 API 服务器提供一份合法的客户端证书, 供后者使用所给的 CA 来执行验证。 警告:*不要* 在不同的上下文中复用 CA 证书,除非你清楚这样做的风险是什么以及 应如何保护 CA 用法的机制。
- `--requestheader-client-ca-file` 必需字段,给出 PEM 编码的证书包。 在检查请求的头部字段以提取用户名信息之前,必须提供一个合法的客户端证书, 且该证书要能够被所给文件中的机构所验证。
- `--requestheader-allowed-names` 可选字段用来给出一组公共名称CN。 如果此标志被设置,则在检视请求中的头部以提取用户信息之前,必须提供 包含此列表中所给的 CN 名的、合法的客户端证书。
> 以上摘自官方文档[身份认证代理](https://kubernetes.io/zh/docs/reference/access-authn-authz/authentication/#authenticating-proxy)
简而言之就是在apiserver之前有一个代理服务器通过代理服务器的名义可以将相关的认证信息在请求头透传给apiserver以通过apiserver的认证。代理服务的相关信息需要预先配置。
使用kubeadm默认部署的集群就启用了requestheader的配置
![](https://mycloudn.wqyin.cn/20210202163330.png)
来看看AuthenticateRequest方法的代码
`vendor/k8s.io/apiserver/pkg/authentication/request/headerrequest/requestheader.go:108`
```go
func (a *requestHeaderAuthRequestHandler) AuthenticateRequest(req *http.Request) (*authenticator.Response, bool, error) {
// 取到用户名
name := headerValue(req.Header, a.nameHeaders)
if len(name) == 0 {
return nil, false, nil
}
// 取到用户group
groups := allHeaderValues(req.Header, a.groupHeaders)
// 取到其他信息
extra := newExtra(req.Header, a.extraHeaderPrefixes)
// clear headers used for authentication
// 请求的header信息去除
for _, headerName := range a.nameHeaders {
req.Header.Del(headerName)
}
for _, headerName := range a.groupHeaders {
req.Header.Del(headerName)
}
for k := range extra {
for _, prefix := range a.extraHeaderPrefixes {
req.Header.Del(prefix + k)
}
}
// 返回认证成功信息
return &authenticator.Response{
User: &user.DefaultInfo{
Name: name,
Groups: groups,
Extra: extra,
},
}, true, nil
}
```
参考代码片中注释
### BasicAuth认证
BasicAuth是一种简单的基础http认证用户名、密码写入http请求头中用base64编码防君子不防小人安全性较低因此很少使用。快速略过
启动apiserver时使用--basic-auth-file参数指定csv文件csv里面以逗号切割存放用户名、密码、uid。
看看AuthenticateRequest方法
`vendor/k8s.io/apiserver/plugin/pkg/authenticator/request/basicauth/basicauth.go:38`
```go
// AuthenticateRequest authenticates the request using the "Authorization: Basic" header in the request
func (a *Authenticator) AuthenticateRequest(req *http.Request) (*authenticator.Response, bool, error) {
username, password, found := req.BasicAuth()
if !found {
return nil, false, nil
}
// 简单的账号密码比对,没有什么值得深入的
resp, ok, err := a.auth.AuthenticatePassword(req.Context(), username, password)
// If the password authenticator didn't error, provide a default error
if !ok && err == nil {
err = errInvalidAuth
}
return resp, ok, err
}
```
### x509 CA认证
又称TLS双向认证APIServer启动时使用--client-ca-file指定客户端的证书文件用作请求的认证。
`vendor/k8s.io/apiserver/pkg/authentication/request/x509/x509.go:88`
```go
// 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 := a.opts
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.Observe(remaining.Seconds())
// 校验客户端request携带的证书如果是ca签名的证书且在有效期内则视为有效返回对应的证书chain
chains, err := req.TLS.PeerCertificates[0].Verify(optsCopy)
if err != nil {
return nil, false, 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)
}
```
--> `vendor/k8s.io/apiserver/pkg/authentication/request/x509/x509.go:72`
--> `vendor/k8s.io/apiserver/pkg/authentication/request/x509/x509.go:185`
```go
// 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
}
// 取得证书chain中的CommonName/Organization作为用户和组
return &authenticator.Response{
User: &user.DefaultInfo{
Name: chain[0].Subject.CommonName,
Groups: chain[0].Subject.Organization,
},
}, true, nil
})
```
### BearerToken认证
这种认证方式是专为k8s节点准备的避免每个节点都要手动配置TLS证书在apiserver启动时指定`--enable-bootstrap-token-auth`参数来启用这种认证方式,详细说明见官方文档:
> [启动引导令牌](https://kubernetes.io/zh/docs/reference/access-authn-authz/authentication/#bootstrap-tokens)
bearertoken的认证方式是在请求头里放入bearer令牌令牌的格式为 `[a-z0-9]{6}.[a-z0-9]{16}`。第一个部分是令牌的 ID第二个部分 是令牌的 Secret。对应http请求头的格式是:
`Authorization: Bearer xxxxxx.xxxxxxxxxxxxxxxx`
代码路径:
`vendor/k8s.io/apiserver/pkg/authentication/request/bearertoken/bearertoken.go:37`
```go
func (a *Authenticator) AuthenticateRequest(req *http.Request) (*authenticator.Response, bool, error) {
// Authorization header
auth := strings.TrimSpace(req.Header.Get("Authorization"))
if auth == "" {
return nil, false, nil
}
// 切割分开第二部分就是token
parts := strings.Split(auth, " ")
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 {
return nil, false, nil
}
// 校验token
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
}
```
`AuthenticateToken`是一个接口方法:
`vendor/k8s.io/apiserver/pkg/authentication/authenticator/interfaces.go:28`
```go
type Token interface {
AuthenticateToken(ctx context.Context, token string) (*Response, bool, error)
}
// TokenFunc is a function that implements the Token interface.
type TokenFunc func(ctx context.Context, token string) (*Response, bool, error)
// AuthenticateToken implements authenticator.Token.
func (f TokenFunc) AuthenticateToken(ctx context.Context, token string) (*Response, bool, error) {
return f(ctx, token)
}
```
接口的实现方法在:
`plugin/pkg/auth/authenticator/token/bootstrap/bootstrap.go:94`
```go
func (t *TokenAuthenticator) AuthenticateToken(ctx context.Context, token string) (*authenticator.Response, bool, error) {
// 把token用'.'切割分为id和密文两段
tokenID, tokenSecret, err := parseToken(token)
if err != nil {
// Token isn't of the correct form, ignore it.
return nil, false, nil
}
// 从APIServer取到对应的secret资源对象跟这个token进行信息比对
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 := getSecretString(secret, bootstrapapi.BootstrapTokenSecretKey)
if subtle.ConstantTimeCompare([]byte(ts), []byte(tokenSecret)) != 1 {
tokenErrorf(secret, "has invalid value for key %s, expected %s.", bootstrapapi.BootstrapTokenSecretKey, tokenSecret)
return nil, false, nil
}
id := getSecretString(secret, bootstrapapi.BootstrapTokenIDKey)
if id != tokenID {
tokenErrorf(secret, "has invalid value for key %s, expected %s.", bootstrapapi.BootstrapTokenIDKey, tokenID)
return nil, false, nil
}
if isSecretExpired(secret) {
// logging done in isSecretExpired method.
return nil, false, nil
}
if getSecretString(secret, bootstrapapi.BootstrapTokenUsageAuthentication) != "true" {
tokenErrorf(secret, "not marked %s=true.", bootstrapapi.BootstrapTokenUsageAuthentication)
return nil, false, nil
}
groups, err := 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
}
```
### ServiceAccount(SA)认证
启用方式为APIServer命令使用`--service-account-key-file`参数指定一个为token签名的PEM秘钥文件。
SA认证是jwt形式的认证使用方式与bearer token类似也是放在请求头里内容为Base64编码header格式为:
`Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjo0NTk0LCJ1c2VybmFtZSI6InlpbndlbnFpbiIsImV4cCI6MTU3MDY3NDEyNywiZW1haWwiOiIifQ.djC2w5l3IiXYv7slZtGzlMzLc3_oPuR1M0dM9FwoaUU`
token哪里来呢答案就是ServiceAccount。
SA是一种面向集群内部应用需要调用APIServer的场景所设计的认证方式。在创建ServiceAccount资源时可以显示地设置标签将ServiceAccount绑定给某Deploy/sts/pod也可以在Deploy/sts/pod的声明文件里显示指定ServiceAccount。ServiceAccount会自动创建Secret资源token秘钥存放其中。
在相应的容器层面token信息会被挂载进容器中包含3个文件
- namespace文件指明命名空间
- ca.crt文件APIServer的公钥证书容器用来校验APIServer
- token文件: 存放在Secret里的JWT token
在集群上找一个例子以kube-proxy举例
![](https://mycloudn.wqyin.cn/20210203103806.png)
看看代码层面:
`pkg/serviceaccount/jwt.go:145`
```go
func (j *jwtTokenAuthenticator) AuthenticateToken(ctx context.Context, tokenData string) (*authenticator.Response, bool, error) {
if !j.hasCorrectIssuer(tokenData) {
return nil, false, nil
}
// 拿到token
tok, err := jwt.ParseSigned(tokenData)
if err != nil {
return nil, false, nil
}
public := &jwt.Claims{}
private := j.validator.NewPrivateClaims()
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
}
if !found {
return nil, false, utilerrors.NewAggregate(errlist)
}
tokenAudiences := authenticator.Audiences(public.Audience)
if len(tokenAudiences) == 0 {
// only apiserver audiences are allowed for legacy tokens
tokenAudiences = j.implicitAuds
}
requestedAudiences, ok := authenticator.AudiencesFrom(ctx)
if !ok {
// default to apiserver audiences
requestedAudiences = j.implicitAuds
}
auds := authenticator.Audiences(tokenAudiences).Intersect(requestedAudiences)
if len(auds) == 0 && len(j.implicitAuds) != 0 {
return nil, false, fmt.Errorf("token audiences %q is invalid for the target audiences %q", tokenAudiences, requestedAudiences)
}
// If we get here, we have a token with a recognized signature and
// issuer string.
// 校验token并拿到SA信息
sa, err := j.validator.Validate(tokenData, public, private)
if err != nil {
return nil, false, err
}
return &authenticator.Response{
User: sa.UserInfo(),
Audiences: auds,
}, true, nil
}
```
### WebhookToken认证
Webhook 身份认证是一种用来验证持有者令牌的回调机制。
- `--authentication-token-webhook-config-file` 指向一个配置文件,其中描述 如何访问远程的 Webhook 服务。
- `--authentication-token-webhook-cache-ttl` 用来设定身份认证决定的缓存时间。 默认时长为 2 分钟。
配置文件使用 [kubeconfig](https://kubernetes.io/zh/docs/concepts/configuration/organize-cluster-access-kubeconfig/) 文件的格式。文件中,`clusters` 指代远程服务,`users` 指代远程 API 服务 Webhook。下面是一个例子
```yaml
# Kubernetes API 版本
apiVersion: v1
# API 对象类别
kind: Config
# clusters 指代远程服务
clusters:
- name: name-of-remote-authn-service
cluster:
certificate-authority: /path/to/ca.pem # 用来验证远程服务的 CA
server: https://authn.example.com/authenticate # 要查询的远程服务 URL。必须使用 'https'。
# users 指代 API 服务的 Webhook 配置
users:
- name: name-of-api-server
user:
client-certificate: /path/to/cert.pem # Webhook 插件要使用的证书
client-key: /path/to/key.pem # 与证书匹配的密钥
# kubeconfig 文件需要一个上下文Context此上下文用于本 API 服务器
current-context: webhook
contexts:
- context:
cluster: name-of-remote-authn-service
user: name-of-api-sever
name: webhook
```
当客户端尝试在 API 服务器上使用持有者令牌完成身份认证( 如[前](https://kubernetes.io/zh/docs/reference/access-authn-authz/authentication/#putting-a-bearer-token-in-a-request)所述)时, 身份认证 Webhook 会用 POST 请求发送一个 JSON 序列化的对象到远程服务。 该对象是 `authentication.k8s.io/v1beta1` 组的 `TokenReview` 对象, 其中包含持有者令牌。 Kubernetes 不会强制请求提供此 HTTP 头部。
要注意的是Webhook API 对象和其他 Kubernetes API 对象一样,也要受到同一 [版本兼容规则](https://kubernetes.io/zh/docs/concepts/overview/kubernetes-api/)约束。 实现者要了解对 Beta 阶段对象的兼容性承诺,并检查请求的 `apiVersion` 字段, 以确保数据结构能够正常反序列化解析。此外API 服务器必须启用 `authentication.k8s.io/v1beta1` API 扩展组 `--runtime-config=authentication.k8s.io/v1beta1=true`)。
POST 请求的 Body 部分将是如下格式:
```json
{
"apiVersion": "authentication.k8s.io/v1beta1",
"kind": "TokenReview",
"spec": {
"token": "<持有者令牌>"
}
}
```
远程服务应该会填充请求的 `status` 字段,以标明登录操作是否成功。 响应的 Body 中的 `spec` 字段会被忽略,因此可以省略。 如果持有者令牌验证成功,应该返回如下所示的响应:
```json
{
"apiVersion": "authentication.k8s.io/v1beta1",
"kind": "TokenReview",
"status": {
"authenticated": true,
"user": {
"username": "janedoe@example.com",
"uid": "42",
"groups": [
"developers",
"qa"
],
"extra": {
"extrafield1": [
"extravalue1",
"extravalue2"
]
}
}
}
}
```
而不成功的请求会返回:
```json
{
"apiVersion": "authentication.k8s.io/v1beta1",
"kind": "TokenReview",
"status": {
"authenticated": false
}
}
```
HTTP 状态码可用来提供进一步的错误语境信息。
> 以上摘自官方文档[Webhook 令牌身份认证](https://kubernetes.io/zh/docs/reference/access-authn-authz/authentication/#webhook-token-authentication)
代码:
`vendor/k8s.io/apiserver/plugin/pkg/authenticator/token/webhook/webhook.go:77`
```go
// AuthenticateToken implements the authenticator.Token interface.
func (w *WebhookTokenAuthenticator) AuthenticateToken(ctx context.Context, token string) (*authenticator.Response, bool, error) {
// We take implicit audiences of the API server at WebhookTokenAuthenticator
// construction time. The outline of how we validate audience here is:
//
// * if the ctx is not audience limited, don't do any audience validation.
// * if ctx is audience-limited, add the audiences to the tokenreview spec
// * if the tokenreview returns with audiences in the status that intersect
// with the audiences in the ctx, copy into the response and return success
// * if the tokenreview returns without an audience in the status, ensure
// the ctx audiences intersect with the implicit audiences, and set the
// intersection in the response.
// * otherwise return unauthenticated.
wantAuds, checkAuds := authenticator.AudiencesFrom(ctx)
r := &authentication.TokenReview{
Spec: authentication.TokenReviewSpec{
Token: token,
Audiences: wantAuds,
},
}
var (
result *authentication.TokenReview
err error
auds authenticator.Audiences
)
webhook.WithExponentialBackoff(w.initialBackoff, func() error {
// webhook插件向远端认证服务器发起post请求
result, err = w.tokenReview.Create(r)
return err
})
if err != nil {
// An error here indicates bad configuration or an outage. Log for debugging.
klog.Errorf("Failed to make webhook authenticator request: %v", err)
return nil, false, err
}
if checkAuds {
gotAuds := w.implicitAuds
if len(result.Status.Audiences) > 0 {
gotAuds = result.Status.Audiences
}
auds = wantAuds.Intersect(gotAuds)
if len(auds) == 0 {
return nil, false, nil
}
}
r.Status = result.Status
if !r.Status.Authenticated {
var err error
if len(r.Status.Error) != 0 {
err = errors.New(r.Status.Error)
}
return nil, false, err
}
var extra map[string][]string
if r.Status.User.Extra != nil {
extra = map[string][]string{}
for k, v := range r.Status.User.Extra {
extra[k] = v
}
}
// 根据远端认证服务器返回的status字段填充请求的相关用户信息(name/uid/group等)
return &authenticator.Response{
User: &user.DefaultInfo{
Name: r.Status.User.Username,
UID: r.Status.User.UID,
Groups: r.Status.User.Groups,
Extra: extra,
},
Audiences: auds,
}, true, nil
}
```
### Anonymous认证
启用匿名请求支持之后,如果请求没有被已配置的其他身份认证方法拒绝,则被视作 匿名请求Anonymous Requests。这类请求获得用户名 `system:anonymous` 和 对应的用户组 `system:unauthenticated`
例如,在一个配置了令牌身份认证且启用了匿名访问的服务器上,如果请求提供了非法的 持有者令牌,则会返回 `401 Unauthorized` 错误。 如果请求没有提供持有者令牌,则被视为匿名请求。
在 1.5.1-1.5.x 版本中,匿名访问默认情况下是被禁用的,可以通过为 API 服务器设定 `--anonymous-auth=true` 来启用。
在 1.6 及之后版本中,如果所使用的鉴权模式不是 `AlwaysAllow`,则匿名访问默认是被启用的。 从 1.6 版本开始ABAC 和 RBAC 鉴权模块要求对 `system:anonymous` 用户或者 `system:unauthenticated` 用户组执行显式的权限判定,所以之前的为 `*` 用户或 `*` 用户组赋予访问权限的策略规则都不再包含匿名用户。
> 以上摘自官方文档[匿名请求](https://kubernetes.io/zh/docs/reference/access-authn-authz/authentication/#anonymous-requests)
代码:
`vendor/k8s.io/apiserver/pkg/authentication/request/anonymous/anonymous.go:32`
```go
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
})
}
```
## 总结
认证机制有许多种,每种都有不同的适用场景,一般情况下只需要了解认证流程和其中常见的几种认证器即可,不必过分深挖。认证流程通过之后,下一篇进入授权环节