带你读《云原生应用开发:Operator原理与实践》——2.3.2 认证

本文涉及的产品
容器服务 Serverless 版 ACK Serverless,952元额度 多规格
容器服务 Serverless 版 ACK Serverless,317元额度 多规格
简介: 带你读《云原生应用开发:Operator原理与实践》——2.3.2 认证

2.3.2 认证


1. 认证中使用的用户

Kubernetes 集群中有两种用户:一种是由 Kubernetes 管理的 ServiceAccount ;另一种是普通用户。普通用户是由集群之外的服务管理的,比如存储在 Keystone 中的用户、存储在文件中的用户和存储在 ldap 中的用户等。 所有的 API 请求中都绑定了一个ServiceAccount 或普通用户,如果都未绑定,就是一个使用匿名用户的请求。ServiceAccount 与 它 关 联 的 请 求 中 的 用 户 是 什 么 样 的 呢? ServiceAccoount 在Kuberentes 中关联着一个 Secret,这个 Secret 中包括一个 JWT 格式的 Token,它会使用 Token 中的 Payload 中的 Sub 字段作为请求用户的名称。下面是对 Secret 中的 Token解析的一个示例,请求所使用的用户名是 system:serviceaccount:default:default,见代码清单 2-60。

代码清单 2-60

{
 "iss": "kubernetes/serviceaccount",
 "kubernetes.io/serviceaccount/namespace": "default",
 "kubernetes.io/serviceaccount/secret.name": "default-token-bxzg6",
 "kubernetes.io/serviceaccount/service-account.name": "default",
 "kubernetes.io/serviceaccount/service-account.uid": "a96a30e3-3ee0-44cf-
99b2-1ad4bdbc7632",
 "sub": "system:serviceaccount:default:default"
}

普通用户有多种方式携带认证信息去访问 APIServer。以客户端证书的方式为例,请求中如果携带了客户端证书,并且证书是由集群的 CA 证书(Certification Authority)签发的,那么这个请求的认证会通过,它会使用证书中的 Subject 字段作为用户的名称。代码清单 2-61 是一个证书的示例,示例中携带的用户名是 kcp (Subject: O=system:kcp,CN=kcp)。

代码清单 2-61

Certificate:
 Data:
 Version: 3 (0x2)
 Serial Number:
 08:e2:5b:3a:2c:8c:6c:06:80:f8:aa:1b:81:76:93:4f
 Signature Algorithm: sha256WithRSAEncryption
 Issuer: CN=kubernetes
 Validity
 Not Before: Mar 3 05:59:42 2021 GMT
 Not After : Jul 11 09:37:38 2030 GMT
 Subject: O=system:kcp, CN=kcp
......

2. 认证策略


(1) X509 客户端证书认证

客户端证书认证是双向认证,服务器端需要验证客户端证书的正确性,客户端需要验证服务器端证书的正确性。客户端证书认证方式可以通过 Kube-Apiserver 的 --clientca-file 参数启用,它指向的文件用来验证提供给 API 服务器的客户端证书。当请求中携带的客户端证书通过了认证时,请求关联的用户名就是证书中的 Subject 字段的内容。我们可以使用代码清单 2-62 查看证书内容。

代码清单 2-62

openssl x509 -in < 证书 > -text -noout

(2)静态 Token 文件

静态 Token 文件认证方式可以通过 --token-auth-file 参数启用,在这个文件中存放着没有时间限制的 Bearer Token,如果想变更 Token,则必须要重启 APIServer。代码清单 2-63 是一个 Token 文件的示例,每一行包括 Token、用户名和用户 ID。

代码清单 2-63

JGaOWpJuyBL8NXmeA9V341JOCkHJbOTf,system:kubectl-kcpm1,system:kubectl-kcpm1

访问 APIServer 时在 HTTP 请求头上加入 Authorization 的请求头,值的格式是Bearer ,上面的 Token 见代码清单 2-64。

代码清单 2-64

curl -H "Authorization: Bearer JGaOWpJuyBL8NXmeA9V341JOCkHJbOTf" -k 
https://127.0.0.1:6443/

(3)引导 Token

我们在创建集群或是将新节点加入集群时,新节点上的组件要与 APIServer 进行通信(如Kubelet),但通信需要证书,手动签发证书比较麻烦。为了简化这个过程,Kubernetes 1.4 之后的版本会通过新节点发送请求的方式为新节点签发证书。而发送请求获取证书的请求时使用的 Bearer Token 叫作 Bootstrap Token,这种 Token 在 Kube-System Namespace 下有一个对应的 Secret。Controller-Manager 中有一个 TokenCleaner 的 Controller,它会将那些已经过期的 Bootstrap Token 掉。

Token 的 格 式 是 [a-z0-9]{6}.[a-z0-9]{16},Token 的 前 半 部 分 是 Token ID, 通过前半部分能找到 UI 应的 Secret ;第二部分是 Token Secret,保存在 Secret 中。以Kubeadm 创建的 tokencwb0ly.cqdj5l0k2qa19evv 为例,在 Kube-System 的 Namespace下会有一个名字为 bootstrap-token-cwb0ly 的 Secret,内容见代码清单 2-65。

代码清单 2-65

apiVersion: v1
data:
 auth-extra-groups: system:bootstrappers:kubeadm:default-node-token 
 expiration: 2021-03-13T13:44:23+08:00
 token-id: cwb0ly
 token-secret: cqdj5l0k2qa19evv
 usage-bootstrap-authentication: true
 usage-bootstrap-signing: true
kind: Secret
metadata:
 manager: kubeadm
 operation: Update
 name: bootstrap-token-cwb0ly
 namespace: kube-system

使用该 Token 作为 Authorization 请求头访问 APIServer,请求认证通过后关联到的用户名的格式是 bootstrap-token-,即 system:bootstrap:cwb0ly,请求示例见代码清单 2-66。

代码清单 2-66

curl -H "Authorization: Bearer cwb0ly.cqdj5l0k2qa19evv" -k https://127.0.0.1:6443/

要想使用引导 Token 认证,需要在 APIServer 启动的时候添加 --enable-bootstraptoken-auth 启动参数,如果想使用 Token Clear Controller,需要在 Controller-Manager的启动参数中添加 --controllers=*,tokencleaner。

(4) ServiceAccount Token

ServiceAccount 认证是自动开启的认证方式,它使用签过名的 Bearer Token 去验证请求,这个认证模块包括两个可配置项。

① --service-account-key-file:包含签名 Token 的 PEM 格式的密钥文件,如果不指定这个参数,将使用 APIServer 的 TLS 私钥。

② --service-account-lookup:如果被设置为 True,从 API 请求中删除的 Token 将被收回。ServiceAccount 由 APIServer 自动创建,Pod 在运行时通过 Admission Controller关联 ServiceAccount。Bearer Toen 挂载到 Pod 的特定目录上,并允许集群内的进程与APIServer 通信。 可以使用 PodSpec 的 ServiceAccountName 字段将账户与 Pod 进行关联。ServiceAccount 中包含一个 Secret,示例见代码清单 2-67。

代码清单 2-67

apiVersion: v1
kind: ServiceAccount
metadata:
 name: default
 namespace: default
 resourceVersion: "361"
secrets:
- name: default-token-h29t7
# Secret 中包含了 APIServer 公开的 CA 证书和一个 JWT 格式的 Token
 apiVersion: v1
data:
 ca.crt: < 证书内容 >
 namespace: ZGVmYXVsdA==
 token: <JWT Token>
kind: Secret
metadata:
 name: default-token-h29t7
 namespace: default
type:

(5) OpenID Connect Tokens

OpenID Connect 是一套基于 OAuth2 协议的认证规范,由提供商实现,比如 Azure Active Directory、Salesforce 和 Google。这个认证模块的使用流程:用户先从认证服务器上获取一个 ID Token,这个 Token 是一个 JWT 格式的 Token,用户收到这个 Token 后访问 APIServer。

这个认证模块使用从 OAuth2 中获取的 id_token 进行认证,认证的过程如图 2-10 所示。

image.png

图 2-10 OIDC 认证流程

① 登录到用户身份认证服务提供商。

② 用户身份认证服务提供商返回 access_token、id_token 和 refresh_token。

③ 用户使用 Kubectl 工具时通过 --token 参数指定 id_token 或将它写入 kubeconfig 中。

④ Kubectl 将 id_token 作为认证信息放在请求头中调用 APIServer。

⑤ APIServer 将通过指定的证书检查 JWT 中的签名的正确性。

⑥ 检查 id_token 是不是已经过期了。

⑦ 确保用户请求的资源有操作权限。

⑧ 一旦鉴权通过,APIServer 将返回一个响应给 Kubectl。

⑨ Kubectl 工具箱用户提供反馈。

用来对用户身份进行认证的所有数据都在 id_token 中,在上述整个流程中 Kubernetes 不需要与身份认证服务交互。在一个都是无状态请求的模型中,这种工作方式为身份认证提供了一种更容易处理大规模请求的解决方案。

要使用 OIDC(OpenID Connect)认证模块,需要在 APIServer 中配置如下参数。

① --oidc-issuer-url: 认证服务提供商的地址,允许 APIServer 发现公开的签名密钥服务的 URL。只接受 https:// 的 URL。此值通常设置为服务的发现 URL,不含路径。

② --oidc-client-id:发放 Token 的 Client ID。

③ --oidc-username-claim:使用 JWT 中的哪个字段作为用户名,默认的是 sub。

④ --oidc-username-prefix:为了防止不同认证系统的用户冲突,给用户名添加一个前缀,如果使用的用户名不是 email,那么用户名将是 #。如果设置为“-”,将不会使用前缀。

⑤ --oidc-groups-claim: 使用 JWT 中的哪个字段作为用户的组名。

⑥ --oidc-groups-prefix:组名的前缀,所有的组都将以此值为前缀,以避免与其他身份认证策略发生冲突。

⑦ --oidc-required-claim: 键值对描述 ID Token 中的必要声明,如果设置了这个值,则验证声明是否存在于 ID Token 中且具有匹配值,重复设置可以指定多个声明。

⑧ --oidc-ca-file:签署身份认证提供商的 CA 证书的路径,默认的是主机的根 CA 证书的路径。

OIDC 认证实现见代码清单 2-68。

代码清单 2-68

func (v *IDTokenVerifier) Verify(ctx context.Context, rawIDToken string) 
(*IDToken, error) {
 jws, err := jose.ParseSigned(rawIDToken)
 if err != nil {
 return nil, fmt.Errorf("oidc: malformed jwt: %v", err)
 }
 // 解析 JWT 的 Payload 部分,JWT 分为三段,以逗号作为分隔符,第二段是 Payload 部分,
是 JSON 格式,使用 base64 进行编码
 payload, err := parseJWT(rawIDToken)
 if err != nil {
 return nil, fmt.Errorf("oidc: malformed jwt: %v", err)
 }
 var token idToken
 if err := json.Unmarshal(payload, &token); err != nil {
 return nil, fmt.Errorf("oidc: failed to unmarshal claims: %v", 
err)
 }
 distributedClaims := make(map[string]claimSource)
 for cn, src := range token.ClaimNames {
 if src == "" {
 return nil, fmt.Errorf("oidc: failed to obtain source from 
claim name")
 }
 s, ok := token.ClaimSources[src]
 if !ok {
 return nil, fmt.Errorf("oidc: source does not exist")
 }
 distributedClaims[cn] = s
 }
 t := &IDToken{
 Issuer: token.Issuer,
 Subject: token.Subject,
 Audience: []string(token.Audience),
 Expiry: time.Time(token.Expiry),
 IssuedAt: time.Time(token.IssuedAt),
 Nonce: token.Nonce,
 AccessTokenHash: token.AtHash,
 claims: payload,
 distributedClaims: distributedClaims,
 }
 ......
 // 检查 Token 是否过期
 if !v.config.SkipExpiryCheck {
 now := time.Now
 if v.config.Now != nil {
 now = v.config.Now
 }
 nowTime := now()
 if t.Expiry.Before(nowTime) {
 return nil, fmt.Errorf("oidc: token is expired (Token 
Expiry: %v)", t.Expiry)
 }
 if token.NotBefore != nil {
 nbfTime := time.Time(*token.NotBefore)
 leeway := 1 * time.Minute
 if nowTime.Add(leeway).Before(nbfTime) {
 return nil, fmt.Errorf("oidc: current time %v before 
the nbf (not before) time: %v", nowTime, nbfTime)
 }
 }
 }
 switch len(jws.Signatures) {
 case 0:
 return nil, fmt.Errorf("oidc: id token not signed")
 case 1:
 default:
 return nil, fmt.Errorf("oidc: multiple signatures on id token not 
supported")
 }
 sig := jws.Signatures[0]
 supportedSigAlgs := v.config.SupportedSigningAlgs
 if len(supportedSigAlgs) == 0 {
 supportedSigAlgs = []string{RS256}
 }
 .......
 t.sigAlgorithm = sig.Header.Algorithm
 // 校验签名是否正确
 gotPayload, err := v.keySet.VerifySignature(ctx, rawIDToken)
 if err != nil {
 return nil, fmt.Errorf("failed to verify signature: %v", err)
 }
 if !bytes.Equal(gotPayload, payload) {
 return nil, errors.New("oidc: internal error, payload parsed did 
not match previous payload")
 }
 return t, nil
}

(6) WebHook Token 认证

WebHook 认证就是一种回调机制,用来验证 Bearer Token 的正确性,要使用这种认证方式需要配置如下参数。

① --authentication-token-webhook-config-file:这是一个配置文件,用于描述如何访问远程的 WebHook 服务。

② --authentication-token-webhook-cache-ttl:缓存认证时间,默认是 2 分钟。

③ --authentication-token-webhook-version:使用哪个版本发送和接收 WebHook的消息,TokenReview 可以使用 authentication.k8s.io/v1beta1 或 authentication.k8s.io/v1,默认的是 authentication.k8s.io/v1beta1。

当客户端使用一个 Bearer Token 去访问 APIServer 时,WebHook 认证模块会使用Token Review 对象的 JSON 格式向远端服务器发送请求,这个对象中包含了 Token。远端服务器为返回给 TokenReview 对象的 Status 字段填充内容,内容包含此次请求认证是否通过。

(7)认证代理

可以为 Kubernetes 设置一个认证代理,这个认证代理将信息放在请求头中发送给APIServer,APIServer 从请求头中识别用户。启用认证代理需要设置以下几个参数。

① --requestheader-username-headers:用于指定用户名列表,不区分大小写,按照顺序检查用户身份。

② --requestheader-group-headers:用于指定组列表,不区分大小写,按照顺序检查用户组的名称。

③ --requestheader-extra-headers-prefix:指定额外的列表,不区分大小写。

④ --requestheader-client-ca-file:指定有效的客户端的证书,在检查请求头中的用户名之前,必须在指定的文件中提供有效的客户端证书并针对证书颁发机构进行验证。为防止请求头攻击,在检查请求头之前,代理客户端要为 APIServer 提供有效的客户端证书进行校验。

(8)匿名认证

启用后,未被其他配置身份验证方模块拒绝的请求将被视为匿名请求,并被赋予用户名 system:anonymous 和组 system:unauthenticated。在配置了 Token 身份验证且启用了匿名访问的服务器上,提供无效 Bearer Token 的请求将收到 401 未经授权错误。而不提供 Bearer Token 的请求将被视为匿名请求。

在 Kubernetes 1.6 之后的版本,如果鉴权模式不是 AlwaysAllow,则匿名访问默认是启用的。

相关实践学习
通过Ingress进行灰度发布
本场景您将运行一个简单的应用,部署一个新的应用用于新的发布,并通过Ingress能力实现灰度发布。
容器应用与集群管理
欢迎来到《容器应用与集群管理》课程,本课程是“云原生容器Clouder认证“系列中的第二阶段。课程将向您介绍与容器集群相关的概念和技术,这些概念和技术可以帮助您了解阿里云容器服务ACK/ACK Serverless的使用。同时,本课程也会向您介绍可以采取的工具、方法和可操作步骤,以帮助您了解如何基于容器服务ACK Serverless构建和管理企业级应用。 学习完本课程后,您将能够: 掌握容器集群、容器编排的基本概念 掌握Kubernetes的基础概念及核心思想 掌握阿里云容器服务ACK/ACK Serverless概念及使用方法 基于容器服务ACK Serverless搭建和管理企业级网站应用
相关文章
|
27天前
|
Kubernetes 监控 Cloud Native
云原生时代下的应用开发与部署实践
【10月更文挑战第4天】在云原生的浪潮中,开发者和运维人员面临着新的挑战和机遇。本文将通过实际案例,展示如何在云平台上高效地开发、部署和管理应用,同时确保系统的可扩展性和高可用性。我们将深入探讨容器化技术、微服务架构以及持续集成/持续部署(CI/CD)流程的实施策略,旨在为读者提供一套完整的云原生解决方案框架。
|
2月前
|
Kubernetes Cloud Native 持续交付
云原生技术在现代应用开发中的角色与实践
【9月更文挑战第9天】 随着云计算技术的飞速发展,云原生(Cloud Native)已经成为推动企业数字化转型的核心力量。本文将深入探讨云原生的基本概念、关键技术及其在实际开发中的应用案例,旨在为读者提供一条清晰的云原生技术学习路径和应用指南。通过实例分析,我们将揭示云原生如何优化资源管理、提升应用性能及加快部署速度,进而帮助企业构建更加灵活、可靠和高效的软件系统。
|
5天前
|
监控 Cloud Native 持续交付
云原生技术深度解析:重塑现代应用开发与部署范式####
本文深入探讨了云原生技术的核心概念、关键技术组件及其在现代软件开发中的重要性。通过剖析容器化、微服务架构、持续集成/持续部署(CI/CD)等关键技术,本文旨在揭示云原生技术如何促进应用的敏捷性、可扩展性和高可用性,进而推动企业数字化转型进程。不同于传统摘要仅概述内容要点,本部分将融入具体案例分析,直观展示云原生技术在实际应用中的显著成效与挑战应对策略,为读者提供更加丰富、立体的理解视角。 ####
|
16天前
|
Kubernetes Cloud Native 持续交付
云原生技术:重塑现代应用开发与部署模式####
本文深入探讨了云原生技术的核心概念、发展历程及其在现代软件开发和部署中的关键作用。通过分析云原生架构的特点,如容器化、微服务、持续集成与持续部署(CI/CD),以及它如何促进应用的可伸缩性、灵活性和效率,本文旨在为读者提供一个关于云原生技术全面而深入的理解。此外,还将探讨实施云原生策略时面临的挑战及应对策略,帮助组织更好地把握数字化转型的机遇。 ####
|
16天前
|
人工智能 Serverless API
云原生应用开发平台CAP:一站式应用开发及生命周期管理解决方案
阿里云的云应用开发平台CAP(Cloud Application Platform)是一款一站式应用开发及应用生命周期管理平台。它提供丰富的Serverless与AI应用模板、高效的开发者工具链及企业级应用管理功能,帮助开发者快速构建、部署和管理云上应用,大幅提升研发、部署和运维效能。
44 1
|
24天前
|
Kubernetes Cloud Native 持续交付
云原生技术在现代应用开发中的实践与展望
【10月更文挑战第7天】随着技术的不断演进,云计算已从简单的资源租用模式转变为支持复杂、高效、灵活的云原生应用架构。本文将深入探讨云原生技术的核心概念及其在现代应用开发中的应用,通过分析Kubernetes容器编排和微服务架构的实践案例,揭示云原生技术如何推动软件开发的现代化进程。文章旨在为开发者和架构师提供一套实用的云原生应用开发指南,同时展望未来云原生技术的发展方向。
27 8
|
1月前
|
Cloud Native 测试技术 云计算
云原生技术在现代应用开发中的角色与实践
【9月更文挑战第31天】本文深入探讨了云原生技术如何革新现代应用开发流程,通过实际案例分析,揭示了其对提高开发效率、确保系统可扩展性和可靠性的显著影响。文章不仅介绍了云原生的核心概念,还提供了实施策略和最佳实践,旨在为开发者提供一条清晰的云原生转型之路。
|
2月前
|
Kubernetes Cloud Native 持续交付
云原生技术在现代应用开发中的实践与思考
【9月更文挑战第23天】本文将深入探讨云原生技术如何革新现代应用的开发流程。通过分析云原生的核心概念、优势以及实际应用案例,我们旨在揭示这一新兴技术范式如何助力开发者和企业更高效、灵活地构建和部署应用程序。文章还将提供具体代码示例,展示云原生技术在实际项目中的应用,帮助读者更好地理解和掌握该技术。
|
2月前
|
Cloud Native 持续交付 开发者
云原生技术在现代应用开发中的应用与实践
【9月更文挑战第22天】本文将深入探讨云原生技术如何革新现代应用开发,通过实际案例分析其对提高开发效率、促进持续集成与交付的显著影响。我们将从云原生的基本概念出发,逐步展开到容器化、微服务架构、自动化管理的实践操作,以及这些技术如何协同工作以支持复杂应用的快速迭代和扩展。文章旨在为开发者提供一套云原生技术的实践框架,帮助他们构建更加灵活、可维护的应用系统。
|
2月前
|
Cloud Native 持续交付 云计算
云原生技术在现代应用开发中的应用与实践
【9月更文挑战第12天】随着云计算技术的飞速发展,云原生已成为推动企业数字化转型的关键技术之一。本文将深入探讨云原生的基本概念、核心价值及其在现代应用开发中的实际应用案例,旨在为读者提供一套清晰的云原生应用开发指南。通过分析容器化、微服务架构、持续部署等核心技术的实践过程,我们将揭示云原生如何助力开发者高效构建、部署和管理可扩展的应用。你将看到代码示例,这些示例均选自真实世界的开发场景,帮助你理解云原生技术的强大功能和灵活性。

热门文章

最新文章