diff --git a/.DS_Store b/.DS_Store
index 883c1a8..04a0783 100644
Binary files a/.DS_Store and b/.DS_Store differ
diff --git a/apiServer/Kubernetes源码学习-APIServer-P2-启动流程.md b/apiServer/Kubernetes源码学习-APIServer-P2-启动流程.md
index deb1fab..453f95a 100644
--- a/apiServer/Kubernetes源码学习-APIServer-P2-启动流程.md
+++ b/apiServer/Kubernetes源码学习-APIServer-P2-启动流程.md
@@ -484,7 +484,7 @@ func (config Config) New() (authenticator.Request, *spec.SecurityDefinitions, er
这个New()方法依次生成每一种认证方式的Authenticator,New()方法的流程图如下:
-
+
`union.New(authenticators...)`方法集合所有的认证器
diff --git a/apiServer/Kubernetes源码学习-APIServer-P3-APIServer的认证机制.md b/apiServer/Kubernetes源码学习-APIServer-P3-APIServer的认证机制.md
new file mode 100644
index 0000000..265dc86
--- /dev/null
+++ b/apiServer/Kubernetes源码学习-APIServer-P3-APIServer的认证机制.md
@@ -0,0 +1,791 @@
+## 前言
+
+在上一篇[**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)来实现。
+
+> 以上摘自官方文档
+
+
+
+
+
+## 认证机制
+
+### 流程图
+
+首先为了加深印象,再贴一下上一章列出的认证流程图:
+
+
+
+> 注:认证器的执行顺序为随机的,图中流向不代表固定顺序
+
+当客户端发送请求到达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`
+
+
+
+引用中的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相关定义代码大多集中在这个目录里:
+
+
+
+那么依次逐个来看看每一种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的配置:
+
+
+
+来看看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举例:
+
+
+
+看看代码层面:
+
+`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
+ })
+}
+```
+
+
+
+
+
+## 总结
+
+认证机制有许多种,每种都有不同的适用场景,一般情况下只需要了解认证流程和其中常见的几种认证器即可,不必过分深挖。认证流程通过之后,下一篇进入授权环节
\ No newline at end of file