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

本文涉及的产品
容器服务 Serverless 版 ACK Serverless,317元额度 多规格
容器服务 Serverless 版 ACK Serverless,952元额度 多规格
简介: 带你读《云原生应用开发: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搭建和管理企业级网站应用
相关文章
|
7天前
|
Kubernetes Cloud Native 持续交付
云原生技术在现代应用开发中的角色与实践
【9月更文挑战第9天】 随着云计算技术的飞速发展,云原生(Cloud Native)已经成为推动企业数字化转型的核心力量。本文将深入探讨云原生的基本概念、关键技术及其在实际开发中的应用案例,旨在为读者提供一条清晰的云原生技术学习路径和应用指南。通过实例分析,我们将揭示云原生如何优化资源管理、提升应用性能及加快部署速度,进而帮助企业构建更加灵活、可靠和高效的软件系统。
|
4天前
|
Cloud Native 持续交付 云计算
云原生技术在现代应用开发中的应用与实践
【9月更文挑战第12天】随着云计算技术的飞速发展,云原生已成为推动企业数字化转型的关键技术之一。本文将深入探讨云原生的基本概念、核心价值及其在现代应用开发中的实际应用案例,旨在为读者提供一套清晰的云原生应用开发指南。通过分析容器化、微服务架构、持续部署等核心技术的实践过程,我们将揭示云原生如何助力开发者高效构建、部署和管理可扩展的应用。你将看到代码示例,这些示例均选自真实世界的开发场景,帮助你理解云原生技术的强大功能和灵活性。
|
3天前
|
运维 Cloud Native 持续交付
云原生技术:探索现代应用开发的新纪元
本文深入探讨了云原生技术的崛起,以及它如何彻底改变现代应用开发和部署的方式。我们将从云原生的基本概念入手,逐步解析其核心技术如容器化、微服务架构及自动化运维,并展示这些技术如何帮助开发者和企业实现更高效、更灵活的应用管理。通过实际案例分析,我们将揭示云原生技术在提升开发效率、优化资源利用和增强系统可扩展性方面的巨大潜力。
|
4天前
|
Cloud Native 持续交付 开发者
云原生技术在现代应用开发中的角色与实践
【9月更文挑战第12天】本文将探索云原生技术的核心概念及其在现代软件开发中的应用。通过分析容器化、微服务架构、持续集成/持续部署(CI/CD)和DevOps文化的融合,我们旨在揭示如何利用这些技术提升软件的可靠性、可扩展性和交付速度。同时,文章还将展示一个简化的代码示例,以直观地说明云原生技术的实际应用。
|
1月前
|
运维 Cloud Native Android开发
云原生之旅:容器化与微服务架构的融合之道安卓应用开发入门指南
本文将深入探讨云原生技术的核心要素——容器化和微服务架构,并揭示它们如何共同推动现代软件的开发与部署。通过实际案例分析,我们将看到这两种技术如何相辅相成,助力企业实现敏捷、可扩展的IT基础设施。文章旨在为读者提供一条清晰的道路,指引如何在云原生时代利用这些技术构建和优化应用。 本文将引导初学者了解安卓应用开发的基本概念和步骤,从安装开发环境到编写一个简单的“Hello World”程序。通过循序渐进的讲解,让读者快速掌握安卓开发的核心技能,为进一步深入学习打下坚实基础。
39 1
|
19天前
|
Kubernetes Cloud Native 持续交付
云原生技术在现代应用开发中的应用
【8月更文挑战第29天】本文将探讨云原生技术在现代应用开发中的重要性,包括其概念、优势和实践。我们将通过代码示例来展示如何在云平台上构建和部署云原生应用。
|
3月前
|
运维 Cloud Native 持续交付
云原生技术:现代应用开发的革命性变革
云原生技术正在重新定义软件开发和部署的方式。通过采用容器、微服务、持续集成与持续交付(CI/CD)等关键技术,企业可以显著提升开发效率、缩短上市时间,并提高应用的可扩展性和可靠性。本文深入探讨了云原生技术的核心组件及其带来的实际业务价值,展示了其在现代应用开发中的革命性影响。
43 1
|
4月前
|
Cloud Native 云计算
云原生技术:重塑现代应用开发
本文将深入探讨云原生技术如何革新传统应用开发模式,提升应用性能与开发效率。我们将通过分析云原生的核心概念、优势以及实践案例,揭示其对现代企业数字化转型的深远影响。
|
4月前
|
运维 Cloud Native 持续交付
构建未来:云原生架构在现代应用开发中的演进
【5月更文挑战第30天】 随着企业数字化转型的不断深入,云原生技术正成为推动应用现代化的关键力量。本文将探讨云原生架构的核心组件、实施策略及其在提高业务敏捷性、可扩展性和运维效率方面的优势。通过分析微服务、容器化、持续集成/持续部署(CI/CD)和自动化管理的实践案例,我们将揭示如何利用云原生原则来构建一个灵活、高可用的应用生态系统,并讨论在采用这些技术时可能面临的挑战及应对策略。
|
3月前
|
Kubernetes Cloud Native Devops
云原生架构:现代应用开发的未来之路
在当今快速发展的技术环境中,云原生架构正成为企业构建和部署现代应用的首选方式。本文探讨了云原生的基本概念、优势以及在实际应用中的关键组件和最佳实践,为企业如何利用云原生实现更高效、更灵活的应用开发提供了深入分析。

热门文章

最新文章