• 6-k8s笔记-深入分析集群安全机制


    第6章 深入分析集群安全机制
    6.1 API Server认证管理
    6.2 API Server授权管理
    6.2.1 ABAC授权模式详解
    6.2.2 Webhook授权模式详解
    6.2.3 RBAC授权模式详解
    6.3 Admission Control
    6.4 Service Account
    6.5 Secret私密凭据
    6.6 Pod的安全策略配置
    6.6.1 PodSecurityPolicy的工作机制
    6.6.2 PodSecurityPolicy配置详解
    6.6.3 Pod的安全设置详解


    Kubernetes通过一系列机制来实现集群的安全控制,其中包括API Server的认证授权、准入控制机制及保护敏感信息的Secret机制等。
    集群的安全性必须考虑如下几个目标:
    (1)保证容器与其所在宿主机的隔离。
    (2)限制容器给基础设施或其他容器带来的干扰。
    (3)最小权限原则—合理限制所有组件的权限,确保组件只执行它被授权的行为,通过限制单个组件的能力来限制它的权限范围。
    (4)明确组件间边界的划分。
    (5)划分普通用户和管理员的角色。
    (6)在必要时允许将管理员权限赋给普通用户。
    (7)允许拥有Secret数据(Keys、Certs、Passwords)的应用在集群中运行。
    下面分别从Authentication(认证)、Authorization(授权)、Admission Control(准入控制)、Secret(保密)和Service Account(服务账户)等方面来说明集群的安全机制。

    6.1 API Server认证管理
    我们知道,Kubernetes集群中所有资源的访问和变更都是通过Kubernetes API Server的REST API来实现的,
    所以集群安全的关键点就在于如何识别并认证客户端身份(Authentication),以及随后访问权限的授权(Authorization)这两个关键问题,本节对认证管理进行说明。
    我们知道,Kubernetes集群提供了3种级别的客户端身份认证方式。
    HTTPS证书认证(最严格的):基于CA根证书签名的双向数字证书认证方式。
    HTTP Token认证:通过一个Token来识别合法用户。
    HTTP Base认证:通过用户名+密码的方式认证。

    首先说说HTTPS证书认证的原理。
    这里需要有一个CA证书,我们知道CA是PKI系统中通信双方都信任的实体,被称为可信第三方(Trusted Third Party,TTP)。
    CA作为可信第三方的重要条件之一就是CA的行为具有非否认性。作为第三方而不是简单的上级,就必须能让信任者有追究自己责任的能力。
    CA通过证书证实他人的公钥信息,证书上有CA的签名。用户如果因为信任证书而有了损失,则证书可以作为有效的证据用于追究CA的法律责任。
    正是因为CA承担责任的承诺,所以CA也被称为可信第三方。
    在很多情况下,CA与用户是相互独立的实体,CA作为服务提供方,有可能因为服务质量问题(例如,发布的公钥数据有错误)而给用户带来损失。
    在证书中绑定了公钥数据和相应私钥拥有者的身份信息,并带有CA的数字签名;在证书中也包含了CA的名称,以便于依赖方找到CA的公钥,验证证书上的数字签名。
    CA认证涉及诸多概念,比如根证书、自签名证书、密钥、私钥、加密算法及HTTPS等,本书大致讲述SSL协议的流程,有助于理解CA认证和Kubernetes CA认证的配置过程。
    如图6.1所示,CA认证大概包含下面几个步骤:
    (1)HTTPS通信双方的服务器端向CA机构申请证书,CA机构下发根证书、服务端证书及私钥给申请者。
    CA机构是可信的第三方机构,它可以是一个公认的权威企业,也可以是企业自身。企业内部系统一般都用企业自身的认证系统。
    (2)HTTPS通信双方的客户端向CA机构申请证书,CA机构下发根证书、客户端证书及私钥给申请者。
    (3)客户端向服务器端发起请求,服务端下发服务端证书给客户端。
    客户端接收到证书后,通过私钥解密证书,并利用服务器端证书中的公钥认证证书信息比较证书里的消息,
    例如,比较域名和公钥与服务器刚刚发送的相关消息是否一致,如果一致,则客户端认可这个服务器的合法身份。
    (4)客户端发送客户端证书给服务器端,服务端在接收到证书后,通过私钥解密证书,获得客户端证书公钥,并用该公钥认证证书信息,确认客户端是否合法。
    (5)客户端通过随机密钥加密信息,并发送加密后的信息给服务端。
    在服务器端和客户端协商好加密方案后,客户端会产生一个随机的密钥,客户端通过协商好的加密方案加密该随机密钥,并发送该随机密钥到服务器端。
    服务器端接收这个密钥后,双方通信的所有内容都通过该随机密钥加密。
    上述是双向认证SSL协议的具体通信过程,这种情况要求服务器和用户双方都有证书。
    单向认证SSL协议则不需要客户端拥有CA证书,
    对于上面的步骤,只需将服务器端验证客户证书的过程去掉,之后协商对称密码方案和对称通话密钥时,服务器发送给客户的密码没被加密即可。

    然后说说HTTP Token的认证原理。
    HTTP Token的认证是用一个很长的特殊编码方式的并且难以被模仿的字符串—Token来表明客户身份的一种方式。
    在通常情况下,Token是一个很复杂的字符串,比如我们用私钥签名一个字符串后的数据就可以被当作一个Token。
    此外,每个Token对应一个用户名,存储在API Server能访问的一个文件中。
    当客户端发起API调用请求时,需要在HTTP Header里放入Token,这样一来,API Server就能识别合法用户和非法用户了。

    最后说说HTTP Base认证。
    我们知道,HTTP是无状态的,浏览器和Web服务器之间可以通过Cookie来进行身份识别。
    桌面应用程序(比如新浪桌面客户端、SkyDrive客户端、命令行程序)一般不会使用Cookie,那么它们与Web服务器之间是如何进行身份识别的呢?
    这就用到了HTTP Base认证,这种认证方式是把“用户名+冒号+密码”用BASE64算法进行编码后的字符串放在HTTP Request中的Header Authorization域里发送给服务端,
    服务端在收到后进行解码,获取用户名及密码,然后进行用户身份鉴权。

    6.2 API Server授权管理
    当客户端发起API Server调用时,API Server内部要先进行用户认证,然后执行用户授权流程,即通过授权策略来决定一个API调用是否合法。
    对合法用户进行授权并且随后在用户访问时进行鉴权,是权限与安全系统的重要一环。
    简单地说,授权就是授予不同的用户不同的访问权限。
    API Server目前支持以下几种授权策略(通过API Server的启动参数“--authorization-mode”设置)。
    AlwaysDeny:表示拒绝所有请求,一般用于测试。
    AlwaysAllow:允许接收所有请求,如果集群不需要授权流程,则可以采用该策略,这也是Kubernetes的默认配置。
    ABAC(Attribute-Based Access Control):基于属性的访问控制,表示使用用户配置的授权规则对用户请求进行匹配和控制。
    Webhook:通过调用外部REST服务对用户进行授权。
    RBAC:Role-Based Access Control,基于角色的访问控制。
    Node:是一种专用模式,用于对kubelet发出的请求进行访问控制。
    API Server在接收到请求后,会读取该请求中的数据,生成一个访问策略对象,
    如果在该请求中不带某些属性(如Namespace),则这些属性的值将根据属性类型的不同,设置不同的默认值
    (例如,为字符串类型的属性设置一个空字符串;为布尔类型的属性设置false;为数值类型的属性设置0)。
    然后将这个访问策略对象和授权策略文件中的所有访问策略对象逐条匹配,
    如果至少有一个策略对象被匹配,则该请求被鉴权通过,否则终止API调用流程,并返回客户端的错误调用码。

    6.2.1 ABAC授权模式详解
    在API Server启用ABAC模式时,需要指定授权策略文件的路径和名称(--authorization- policy-file=SOME_FILENAME),
    授权策略文件里的每一行都以一个Map类型的JSON对象进行设置,这被称为“访问策略对象”。
    通过设置访问策略对象中的apiVersion、kind、spec属性来确定具体的授权策略,
    其中,
    apiVersion当前版本为abac.authorization.kubernetes.io/v1beta1;
    kind被设置为Policy;
    spec指详细的策略设置,包括主体属性、资源属性、非资源属性这三个字段,如下所述。
    (1)主体属性
    user(用户名):字符串类型,该字符串类型的用户名来源于Token文件(--token-auth-file参数设置的文件)或基本认证文件中用户名称段的值。
    group(用户组):在被设置为“system:authenticated”时表示匹配所有已认证的请求,在被设置为“system:unauthenticated”时表示匹配所有未认证的请求。
    (2)资源属性
    apiGroup(API组):字符串类型,表明匹配哪些API Group,例如extensions或*(表示匹配所有API Group)。
    namespace(命名空间):字符串类型,表明该策略允许访问某个Namespace的资源,例如kube-system或*(表示匹配所有Namespace)。
    resource(资源):字符串类型,API资源对象,例如pods或*(表示匹配所有资源对象)。
    (3)非资源属性
    nonResourcePath(非资源对象类路径):非资源对象类的URL路径,例如/version或/apis,
    *表示匹配所有非资源对象类的请求路径,也可以设置为子路径,/foo/*表示匹配所有/foo路径下的所有子路径。
    readonly(只读标识):布尔类型,当它的值为true时,表明仅允许GET请求通过。
    下面对ABAC授权算法、使用kubectl时的授权机制、常见ABAC授权示例、以及如何对Service Account进行授权进行说明。

    1.ABAC授权算法
    API Server进行ABAC授权的算法为:
    在API Server收到请求之后,首先识别出请求携带的策略对象的属性,
    然后根据在策略文件中定义的策略对这些属性进行逐条匹配,以判定是否允许授权。
    如果有至少一条匹配成功,那么这个请求就通过了授权(不过还是可能在后续其他授权校验中失败)。
    常见的策略配置如下:
    要允许所有认证用户做某件事,可以写一个策略,将group属性设置为system:authenticated。
    要允许所有未认证用户做某件事,可以把策略的group属性设置为system:unauthenticated。
    要允许一个用户做任何事,将策略的apiGroup、namespace、resource和nonResourcePath属性设置为“*”即可。

    2.使用kubectl时的授权机制
    kubectl使用API Server的/api和/apis端点来获取版本信息。
    要验证kubectl create/update命令发送给服务器的对象,kubectl需要向OpenAPI进行查询,对应的URL路径为/openapi/v2。
    当使用ABAC授权模式时,下列特殊资源必须显式地通过nonResourcePath属性进行设置:
    API版本协商过程中的/api、/api/*、/apis、和/apis/*。
    使用kubectl version命令从服务器获取版本时的/version。
    create/update操作过程中的/swaggerapi/*。
    在使用kubectl操作时,如果需要查看发送到API Server的HTTP请求,则可以将日志级别设置为8,例如:
    # kubectl --v=8 version

    3.常见的ABAC授权示例
    下面通过几个授权策略文件(JSON格式)示例说明ABAC的访问控制用法。
    (1)允许用户alice对所有资源做任何操作:
    {"apiVersion": "abac.authorization.kubernetes.io/vibeta1", "kind": "Policy", "spec": {"user": "alice", "namespace": "*", "resource": "*", "apiGroup": "*"}}
    (2)kubelet可以读取任意Pod:
    {"apiVersion": "abac.authorization.kubernetes.io/vibeta1", "kind": "Policy", "spec": {"user": "`", "namespace": "*", "resource": "pods", "readonly": true}}
    (3)kubelet可以读写Event对象:
    {"apiVersion": "abac.authorization.kubernetes.io/vibeta1", "kind": "Policy", "spec": {"user": "kubelet", "namespace": "*", "resource": "events"}}
    (4)用户bob只能读取projectCaribou中的Pod:
    {"apiVersion": "abac.authorization.kubernetes.io/vibeta1", "kind": "Policy", "spec": {"user": "bob", "namespace": "projectCaribou", "resource": "pods", "readonly": true}}
    (5)任何用户都可以对非资源类路径进行只读请求:
    {"apiVersion": "abac.authorization.kubernetes.io/vibeta1", "kind": "Policy", "spec": {"group": "system:authenticated", "readonly": true, "nonResourcePath": "*"}}
    {"apiVersion": "abac.authorization.kubernetes.io/vibeta1", "kind": "Policy", "spec": {"group": "system:unauthenticated", "readonly": true, "nonResourcePath": "*"}}
    如果添加了新的ABAC策略,则需要重启API Server以使其生效。

    4.对Service Account进行授权
    Service Account会自动生成一个ABAC用户名(username),用户名按照以下命名规则生成:
    system:serviceaccount:<namespace>:<serviceaccountname>
    创建新的命名空间时,会产生一个如下名称的Service Account:
    system:serviceaccount:<namespace>:default
    如果希望kube-system命名空间中的Service Account“default”具有全部权限,就要在策略文件中加入如下内容:
    {"apiVersion": "abac.authorization.kubernetes.io/vibeta1", "kind": "Policy", "spec": {"user": "system:serviceaccount:kube-system:default", "namespace": "*", "resource": "*", "apiGroup": "*"}}

    6.2.2 Webhook授权模式详解
    Webhook定义了一个HTTP回调接口,实现Webhook的应用会在指定事件发生时,向一个URL地址发送(POST)通知信息。
    启用Webhook授权模式后,Kubernetes会调用外部REST服务对用户进行授权。
    Webhook模式用参数--authorization-webhook-config-file=SOME_FILENAME来设置远端授权服务的信息。
    配置文件使用的是kubeconfig文件的格式。文件里user一节的内容指的是API Server。
    相对于远程授权服务来说,API Server是客户端,也就是用户;cluster一节的内容指的是远程授权服务器的配置。
    下面的例子为设置一个使用HTTPS客户端认证的配置:
    ......
    在授权开始时,API Server会生成一个api.authorization.v1beta1.SubjectAccessReview对象,用于描述操作信息,在进行JSON序列化之后POST出来。
    在这个对象中包含用户尝试访问资源的请求动作的描述,以及被访问资源的属性。
    Webhook API对象和其他API对象一样,遵循同样的版本兼容性规则,在实现时要注意apiVersion字段的版本,以实现正确的反序列化操作。
    另外,API Server必须启用authorization.k8s.io/v1beta1 API扩展(--runtime-config=authorization.k8s.io/v1beta1=true)。
    下面是一个希望获取Pod列表的请求报文示例:
    ......
    远端服务需要填充请求中的SubjectAccessReviewStatus字段,并返回允许或不允许访问的结果。
    应答报文中的spec字段是无效的,也可以省略。
    一个返回“运行访问”的应答报文示例如下:
    ......
    一个返回“不允许访问”的应答报文示例如下:
    ......
    非资源的访问请求路径包括/api、/apis、/metrics、/resetMetrics、/logs、/debug、/healthz、/swagger-ui/、/swaggerapi/、/ui和/version。
    通常可以对/api、/api/*、/apis、/apis/*和/version对于客户端发现服务器提供的资源和版本信息给予“允许”授权,
    对于其他非资源的访问一般可以禁止,以限制客户端对API Server进行没有必要的查询。
    查询/debug的请求报文示例如下:
    ......

    6.2.3 RBAC授权模式详解
    RBAC(Role-Based Access Control,基于角色的访问控制)在Kubernetes的1.5版本中引入,在1.6版本时升级为Beta版本,在1.8版本时升级为GA。
    作为kubeadm安装方式的默认选项,足见其重要程度。
    相对于其他访问控制方式,新的RBAC具有如下优势:
    对集群中的资源和非资源权限均有完整的覆盖。
    整个RBAC完全由几个API对象完成,同其他API对象一样,可以用kubectl或API进行操作。
    可以在运行时进行调整,无须重新启动API Server。
    要使用RBAC授权模式,需要在API Server的启动参数中加上--authorization-mode=RBAC。
    下面对RBAC的原理和用法进行说明。

    1.RBAC的API资源对象说明
    RBAC引入了4个新的顶级资源对象:Role、ClusterRole、RoleBinding和ClusterRoleBinding。
    同其他API资源对象一样,用户可以使用kubectl或者API调用等方式操作这些资源对象。
    1)角色(Role)
    一个角色就是一组权限的集合,这里的权限都是许可形式的,不存在拒绝的规则。
    在一个命名空间中,可以用角色来定义一个角色,如果是集群级别的,就需要使用ClusterRole了。
    角色只能对命名空间内的资源进行授权,在下面例子中定义的角色具备读取Pod的权限:
    apiVersion: rbac.authorization.k8s.io/v1
    kind: Role
    metadata:
    name: pod-reader
    namespace: default
    rules:
    - apiGroup: [""] # 支持的API组列表,例如“apiVersion: batch/v1”“apiVersion: extensions:v1beta1”“apiVersion: apps/v1beta1”等,空字符串表示核心API组
    resources: ["pods"] # 支持的资源对象列表,例如pods、deployments、jobs等。
    verbs: ["get","watch","list"] # 对资源对象的操作方法列表,例如get、watch、list、delete、replace、patch等
    rules中的参数说明如下。
    apiGroups:支持的API组列表,例如“apiVersion: batch/v1”“apiVersion: extensions:v1beta1”“apiVersion: apps/v1beta1”等,详细的API组说明参见第9章的说明。
    resources:支持的资源对象列表,例如pods、deployments、jobs等。
    verbs:对资源对象的操作方法列表,例如get、watch、list、delete、replace、patch等,详细的操作方法说明参见第9章的说明。
    2)集群角色(ClusterRole)
    集群角色除了具有和角色一致的命名空间内资源的管理能力,因其集群级别的范围,还可以用于以下特殊元素的授权。
    集群范围的资源,例如Node。
    非资源型的路径,例如“/healthz”。
    包含全部命名空间的资源,例如pods(用于kubectl get pods --all-namespaces这样的操作授权)。
    下面的集群角色可以让用户有权访问任意一个或所有命名空间的secrets(视其绑定方式而定):
    apiVersion: rbac.authorization.k8s.io/v1
    kind: ClusterRole
    metadata:
    # ClusterRole 不受限于命名空间,所以无须设置Namespace的名称
    rules:
    - apiGroup: [""] # 支持的API组列表,例如“apiVersion: batch/v1”“apiVersion: extensions:v1beta1”“apiVersion: apps/v1beta1”等,空字符串表示核心API组
    resources: ["secrets"] # 支持的资源对象列表,例如pods、deployments、jobs等。
    verbs: ["get","watch","list"] # 对资源对象的操作方法列表,例如get、watch、list、delete、replace、patch等

    3)角色绑定(RoleBinding)和集群角色绑定(ClusterRoleBinding)
    角色绑定或集群角色绑定用来把一个角色绑定到一个目标上,绑定目标可以是User(用户)、Group(组)或者Service Account。
    使用RoleBinding为某个命名空间授权,使用ClusterRoleBinding为集群范围内授权。
    RoleBinding可以引用Role进行授权。
    下面的例子中的RoleBinding将在default命名空间中把pod-reader角色授予用户jane,这一操作可以让jane读取default命名空间中的Pod:
    ......
    RoleBinding也可以引用ClusterRole,对属于同一命名空间内ClusterRole定义的资源主体进行授权。
    一种常见的做法是集群管理员为集群范围预先定义好一组ClusterRole,然后在多个命名空间中重复使用这些ClusterRole。
    例如,在下面的例子中,虽然secret-reader是一个集群角色,但是因为使用了RoleBinding,所以dave只能读取development命名空间中的secret:
    ......
    集群角色绑定中的角色只能是集群角色,用于进行集群级别或者对所有命名空间都生效的授权。
    下面的例子允许manager组的用户读取任意Namespace中的secret:
    ......
    图6.2展示了上述对Pod的get/watch/list操作进行授权的Role和RoleBinding的逻辑关系。

    2.对资源的引用方式
    多数资源可以用其名称的字符串来表达,也就是Endpoint中的URL相对路径,例如pods。
    然而,某些Kubernetes API包含下级资源,例如Pod的日志(logs)。
    Pod日志的Endpoint是GET /api/v1/namespaces/{namespace}/pods/{name}/log。
    在这个例子中,Pod是一个命名空间内的资源,log就是一个下级资源。
    要在一个RBAC角色中体现,就需要用斜线“/”来分隔资源和下级资源。
    若想授权让某个主体同时能够读取Pod和Pod log,则可以配置resources为一个数组:
    apiVersion: rbac.authorization.k8s.io/v1
    kind: Role
    metadata:
    namespace: defualt
    name: pod-and-pod-logs-reader
    rules:
    - apiGroups: [""]
    resources: ["pods","pods/log"]
    verbs: ["get","list"]
    资源还可以通过名称(ResourceName)进行引用。在指定ResourceName后,使用get、delete、update和patch动词的请求,就会被限制在这个资源实例范围内。
    例如,下面的声明让一个主体只能对一个ConFigmap进行get和update操作:
    apiVersion: rbac.authorization.k8s.io/v1
    kind: Role
    metadata:
    namespace: defualt
    name: configmap-updater
    rules:
    - apiGroups: [""]
    resources: ["configmap"]
    resourceNames: ["my-configmap"]
    verbs: ["update","get"]
    可想而知,resourceName这种用法对list、watch、create或deletecollection操作是无效的,
    这是因为必须要通过URL进行鉴权,而资源名称在list、watch、create或deletecollection请求中只是请求Body数据的一部分。
    下面对常见的角色和角色绑定给出示例,提供参考用法。

    3.常用的角色示例
    注意,下面的例子只展示了rules部分的内容。
    (1)允许读取核心API组中的Pod资源:
    rules:
    - apiGroups: [""]
    resources: ["pods"]
    verbs: ["get","list","watch"]
    (2)允许读写extensions和apps两个API组中的deployment资源:
    rules:
    - apiGroups: ["extensions","apps"]
    resources: ["deployments"]
    verbs: ["get","list","watch","create","update","patch","delete"]
    (3)允许读取pods及读写jobs:
    rules:
    - apiGroups: [""]
    resources: ["pods"]
    verbs: ["get","list","watch"]
    - apiGroups: ["batch","extensions"]
    resources: ["jobs"]
    verbs: ["get","list","watch","create","update","patch","delete"]
    (4)允许读取一个名为my-config的ConfigMap(必须绑定到一个RoleBinding来限制到一个Namespace下的ConfigMap):
    rules:
    - apiGroups: [""]
    resources: ["configmaps"]
    resourceNames: ["my-config"]
    verbs: ["get"]
    (5)读取核心组的Node资源(Node属于集群级的资源,所以必须存在于ClusterRole中,并使用ClusterRoleBinding进行绑定):
    rules:
    - apiGroups: [""]
    resources: ["nodes"]
    verbs: ["get","list","watch"]
    (6)允许对非资源端点“/healthz”及其所有子路径进行GET和POST操作(必须使用ClusterRole和ClusterRoleBinding):
    rules:
    - nonResourceURLs: ["/healthz","/healthz/*"]
    verbs: ["get","post"]

    4.常见的角色绑定示例
    注意,在下面的例子中只包含subjects部分的内容。
    (1)用户名alice@example.com:
    subjects:
    - kind: User
    name: "alice@example.com"
    apiGroup: rbac.authorization.k8s.io
    (2)组名frontend-admins:
    subjects:
    - kind: Group
    name: "frontend-admins"
    apiGroup: rbac.authorization.k8s.io
    (3)kube-system命名空间中的默认Service Account:
    subjects:
    - kind: ServiceAccount
    name: default
    apiGroup: kube-system
    (4)qa命名空间中的所有Service Account:
    subjects:
    - kind: Group
    name: system:serviceaccounts:qa
    apiGroup: rbac.authorization.k8s.io
    (5)所有Service Account:
    subjects:
    - kind: Group
    name: system:serviceaccounts
    apiGroup: rbac.authorization.k8s.io
    (6)所有认证用户(Kubernetes 1.5以上版本):
    subjects:
    - kind: Group
    name: system:authenticated
    apiGroup: rbac.authorization.k8s.io
    (7)所有未认证用户(Kubernetes 1.5以上版本):
    subjects:
    - kind: Group
    name: system:unauthenticated
    apiGroup: rbac.authorization.k8s.io
    (8)全部用户(Kubernetes 1.5以上版本):
    subjects:
    - kind: Group
    name: system:authenticated
    apiGroup: rbac.authorization.k8s.io
    - kind: Group
    name: system:unauthenticated
    apiGroup: rbac.authorization.k8s.io

    5.默认的角色和角色绑定
    API Server会创建一套默认的ClusterRole和ClusterRoleBinding对象,
    其中很多是以“system:”为前缀的,以表明这些资源属于基础架构,对这些对象的改动可能造成集群故障。
    举例来说,system:node这个ClusterRole为kubelet定义了权限,如果这个集群角色被改动了,kubelet就会停止工作。
    所有默认的ClusterRole和RoleBinding都会用标签kubernetes.io/bootstrapping=rbac-defaults进行标记。
    下面对一些常见的默认ClusterRole和ClusterRoleBinding对象进行说明。
    对系统角色的说明如表6.1所示。
    ClusterRole ClusterRoleBinding 描述
    system:basic-user system:authenticated 让用户能够读取自身的信息(在Kubernetes 1.14版之前还绑定了system:unauthenticated 组)
    system:discovery system:authenticated 对API发现Endpoint的只读访问,用于API级别的发现和协商(在Kubernetes 1.14版之前还绑定了system:unauthenticated 组)
    system:public-info-viewer system:authenticated和system:unauthenticated 允许读取集群的非敏感信息(在Kubernetes 1.14开始引入)
    对用户角色的说明如表6.2所示。
    有些默认角色不是以“system:”为前缀的,这部分角色是针对用户的。
    其中包含超级用户角色(cluster-admin),有的用于集群一级的角色(cluster-status),还有针对Namespace的角色(admin、edit、view)。
    ClusterRole ClusterRoleBinding 描述
    cluster-admin system:masters组 让超级用户可以对任何资源执行任何操作。
    如果在ClusterRoleBinding中使用,则影响的是整个集群所有Namespace中的任何资源;
    如果使用的是RoleBinding,则能控制这一绑定的Namespace中的资源及Namespace本身。
    admin None 允许admin访问,可以限制在一个Namespace中使用RoleBinding。
    如果在RoleBinding中使用,则允许对Namespace中的大多数资源进行读写访问,其中包含创建角色和角色绑定的能力。
    admin角色不允许操作Namespace本身,也不饿能够写入资源限额。
    edit None 允许对命名空间中大多数资源进行读写操作,不允许查看或修改角色以及角色绑定
    view None 允许对命名空间中大多数资源进行只读操作,不允许访问角色、角色绑定及secret。
    对核心Master组件角色的说明如表6.3所示。
    对其他组件角色的说明如表6.4所示。
    Controller角色如表6.5所示。
    Kubernetes Controller Manager负责的是核心控制流。
    如果用--use-service-accountcredentials调用,则每个控制过程都会使用不同的Service Account启动,因此就有了对应各个控制过程的角色,前缀是system:controller。
    如果Controller Manager没有用--use-service-account-credentials启动参数,则将使用自己的凭据运行各个控制流程,这就需要为该凭据授予所有相关角色。

    6.授权注意事项:预防提升权限和授权初始化
    RBAC API拒绝用户通过编辑角色或者角色绑定进行提升权限。这一限制是在API层面做出的,因此即使RBAC没有启用也仍然有效。
    用户要对角色进行创建或更新操作,需要满足下列至少一个条件:
    (1)拥有一个角色的所有权限,且与该角色的生效范围一致(如果是集群角色,则是集群范围;如果是普通角色,则可能是同一个命名空间或者整个集群);
    (2)为用户显式授予针对该角色或集群角色的提权(escalate)操作的权限(要求Kubernetes 1.12及以上版本)。
    例如,用户user-1没有列出集群中所有secret的权限,就不能创建具有这一权限的集群角色。
    要让一个用户能够创建或更新角色,需要:
    (1)为其授予一个允许创建或更新Role或ClusterRole资源对象的角色;
    (2)为其授予绑定某一角色的权限,有隐式或显式两种方法。
    隐式:为用户授予权限,要覆盖该用户所能控制的所有权限范围。用户如果尝试创建超出其自身权限的角色或者集群角色,则该API调用会被禁止。
    显式:为用户显式授予针对该Role或ClusterRole的提权(Escalate)操作的权限(要求Kubernetes 1.12及以上版本)。
    如果一个用户的权限包含一个角色的所有权限,就可以为其创建和更新角色绑定(要求同样的作用范围);
    或者如果被授予了针对某个角色的绑定授权,则也有权完成此操作。
    例如,如果用户user-1没有列出集群内所有secret的权限,就无法为一个具有这样权限的角色创建集群角色绑定。
    要使用户能够创建、更新这一角色绑定,则需要这样做,如下所述。
    (1)为其授予一个允许创建和更新RoleBinding或ClusterRoleBinding的角色。
    (2)为其授予绑定某一角色的权限,有隐式或显式两种方法。
    隐式:让其具有该角色的所有权限。
    显式:为用户授予针对该角色(或集群角色)的绑定操作的权限。
    例如,下面的集群角色和角色绑定能让user-1为其他用户在user-1-namespace命名空间中授予admin、edit及view角色:
    apiVersion: rbac.authorization.k8s.io/v1
    kind: ClusterRole
    metadata:
    name: role-grantor
    rules:
    - apiGroup: ["rbac.authorization.k8s.io"]
    resources: ["rolebindings"]
    verbs: ["create"]
    - apiGroup: ["rbac.authorization.k8s.io"]
    resources: ["clusterroles"]
    verbs: ["bind"]
    resourceNames: ["admin","edit","view"]
    ---
    apiVersion: rbac.authorization.k8s.io/v1
    kind: RoleBinding
    metadata:
    name: role-grantor-binding
    namespace: user-1-namespace命
    ruleRef:
    apiGroup: rbac.authorization.k8s.io
    kind: ClusterRole
    name: role-grantor
    subjects:
    - apiGroup: rbac.authorization.k8s.io
    kind: User
    name: user-1
    在进行第1个角色和角色绑定时,必须让初始用户具备其尚未被授予的权限。
    要进行初始的角色和角色绑定设置,有以下两种办法。
    (1)使用属于system:masters组的身份,这一群组默认具有cluster-admin这一超级角色的绑定。
    (2)如果API Server以--insecure-port参数运行,则客户端通过这个非安全端口进行接口调用,这一端口没有认证鉴权的限制。

    7.对Service Account的授权管理
    默认的RBAC策略为控制平台组件、节点和控制器授予有限范围的权限,但是除kube-system外的Service Account是没有任何权限的(除了所有认证用户都具有的discovery权限)。
    这就要求用户为Service Account赋予所需的权限。细粒度的角色分配能够提高安全性,但也会提高管理成本。
    粗放的授权方式可能会给Service Account多余的权限,但更易于管理。
    下面的实践以安全性递减的方式排序。
    (1)为一个应用专属的Service Account赋权(最佳实践)。
    这个应用需要在Pod的Spec中指定一个serviceAccountName,用API、Application Manifest、kubectl create serviceaccount命令等创建Service Account,
    例如为my-namespace中的“my-sa”Service Account授予只读权限:
    # kubectl create rolebinding my-sa-view --clusterrole=view --serviceaccount=my-namespace:my-sa --namespace=my-namespace
    (2)为一个命名空间中名为default的Service Account授权。
    如果一个应用没有指定serviceAccountName,则会使用名为default的Service Account。
    注意,赋给Service Account“default”的权限会让所有没有指定serviceAccountName的Pod都具有这些权限。
    例如,在my-namespace命名空间中为Service Account“default”授予只读权限:
    # kubectl create rolebinding default-view --clusterrole=view --serviceaccount=my-namespace:default --namespace=my-namespace
    另外,许多系统级Add-Ons都需要在kube-system命名空间中运行。
    要让这些Add-Ons能够使用超级用户权限,则可以把cluster-admin权限赋予kube-system命名空间中名为default的Service Account。
    注意,这一操作意味着kube-system命名空间包含了通向API超级用户的捷径:
    # kubectl create clusterrolebinding add-on-cluster-admin --clusterrole=cluster-admin --serviceaccount=kube-system:default
    (3)为命名空间中的所有Service Account都授予一个角色。
    如果希望在一个命名空间中,任何Service Account的应用都具有一个角色,则可以为这一命名空间的Service Account群组进行授权。
    例如,为my-namespace命名空间中的所有Service Account赋予只读权限:
    # kubectl create rolebinding serviceaccount-view --clusterrole=view --group=system:serviceaccounts:my-namespace --namespace=my-namespace
    (4)为集群范围内的所有Service Account都授予一个低权限角色(不推荐)。
    如果不想为每个命名空间管理授权,则可以把一个集群级别的角色赋给所有Service Account。
    例如,为所有命名空间中的所有Service Account授予只读权限:
    # kubectl create clusterrolebinding serviceaccounts-view --clusterrole=view --group=system:serviceaccounts
    (5)为所有Service Account授予超级用户权限(强烈不建议这样设置)。
    如果完全不在意权限,则可以把超级用户权限分配给每个Service Account。
    注意,这让所有具有读取Secret权限的用户都可以创建Pod来访问超级用户的专属权限:
    # kubectl create clusterrolebinding serviceaccounts-cluster-admin --clusterrole=cluster-admin --group=system:serviceaccounts

    8.使用kubectl命令行工具创建资源对象
    除了使用YAML配置文件来创建这些资源对象,也可以直接使用kubectl命令行工具对它们进行创建。
    下面通过几个例子进行说明。
    (1)在命名空间acme中为用户bob授权admin ClusterRole:
    # kubectl create rolebinding bob-admin-binding --clusterrole=admin --user=bob --namespace=acme
    (2)在命名空间acme中为名为myapp的Service Account授予view ClusterRole:
    # kubectl create rolebinding myapp-view-binding --clusterrole=view --serviceaccount=acme:myapp --namespace=acme
    (3)在全集群范围内为用户root授予cluster-admin ClusterRole:
    # kubectl create clusterrolebinding root-cluster-admin-binding --clusterrole=cluster-admin --user=root
    (4)在全集群范围内为用户kubelet授予system:node ClusterRole:
    # kubectl create clusterrolebinding kubelet-node-binding --clusterrole=system:node --user=kubelet
    (5)在全集群范围内为名为myapp的Service Account授予view ClusterRole:
    # kubectl create clusterrolebinding myapp-view-binding --clusterrole=view --serviceaccount=acme:myapp
    可以在kubectl --help的帮助中查看更详细的说明。

    9.RBAC的Auto-reconciliation(自动恢复)功能
    自动恢复从Kubernetes 1.6版本开始引入。
    每次启动时,API Server都会更新默认的集群角色的缺失权限,也会刷新在默认的角色绑定中缺失的主体,
    这样就防止了一些破坏性的修改,也保证了在集群升级的情况下相关内容能够及时更新。
    如果不希望使用这一功能,则可以将一个默认的集群角色或者角色绑定的Annotation注解“rbac.authorization.kubernetes.io/autoupdate”值设置为false。

    10.从旧版本的授权策略升级到RBAC
    在Kubernetes 1.6之前,很多Deployment都试用了比较宽松的ABAC策略,包含为所有Service Account都开放完全API访问权限。
    默认的RBAC策略为控制台组件、节点和控制器授予了范围受限的权限,但是不会为kube-system以外的Service Account授予任何权限。
    这样一来,可能会对现有的一些工作负载造成影响,这时有两种办法来解决这一问题。
    (1)并行认证。
    RBAC和ABAC同时运行,并包含传统的ABAC策略:
    --authorization-mode=RBAC,ABAC --authorization-policy-file=mypolicy.json1
    首先会由RBAC尝试对请求进行鉴权,如果得到的结果是拒绝,就轮到ABAC生效。这样,所有应用只要满足RBAC或ABAC之一即可工作。
    使用5或者更详细的日志级别(--v=5或--vmodule=rbac*=5),则可以在API Server日志中看到RBAC的拒绝行为(前缀:RBAC DENY)。
    可以利用这一信息来确定需要授予何种权限给用户、组或Service Account。
    等到集群管理员按照RBAC的方式对相关组件进行了授权,并且在日志中不再出现RBAC的拒绝信息,就可以移除ABAC认证方式了。

    (2)粗放管理。
    可以使用RBAC的角色绑定,复制一个粗放的策略。
    警告:下面的策略让所有Service Account都具备了集群管理员权限,所有容器运行的应用都会自动接收Service Account的认证,能够对任何API做任何事情,包括查看secret和修改授权。
    注意,这不是一个值得推荐的策略。
    # kubectl create clusterrolebinding permissive-binding --clusterrole=cluster-admin --user=admin --user=kubelet --group=system:serviceaccounts

    6.3 Admission Control
    突破了之前所说的认证和鉴权两道关卡之后,客户端的调用请求就能够得到API Server的真正响应了吗?答案是:不能!
    这个请求还需要通过Admission Control(准入控制)所控制的一个准入控制链的层层考验,才能获得成功的响应。
    Kubernetes官方标准的“关卡”有30多个,还允许用户自定义扩展。
    Admission Control配备了一个准入控制器的插件列表,发送给API Server的任何请求都需要通过列表中每个准入控制器的检查,检查不通过,则API Server拒绝此调用请求。
    此外,准入控制器插件能够修改请求参数以完成一些自动化任务,比如ServiceAccount这个控制器插件。
    当前可配置的准入控制器插件如下:
    AlwaysAdmit:已弃用,允许所有请求。
    AlwaysPullImages:在启动容器之前总是尝试重新下载镜像。
    这对于多租户共享一个集群的场景非常有用,系统在启动容器之前可以保证总是使用租户的密钥去下载镜像。
    如果不设置这个控制器,则在Node上下载的镜像的安全性将被削弱,只要知道该镜像的名称,任何人便都可以使用它们了。
    AlwaysDeny:已弃用,禁止所有请求,用于测试。
    DefaultStorageClass:会关注PersistentVolumeClaim资源对象的创建,如果其中没有包含任何针对特定Storage class的请求,则为其指派指定的Storage class。
    在这种情况下,用户无须在PVC中设置任何特定的Storage class就能完成PVC的创建了。
    如果没有设置默认的Storage class,该控制器就不会进行任何操作;如果设置了超过一个的默认Storage class,该控制器就会拒绝所有PVC对象的创建申请,并返回错误信息。
    管理员必须检查StorageClass对象的配置,确保只有一个默认值。
    该控制器仅关注PVC的创建过程,对更新过程无效。
    DefaultTolerationSeconds:针对没有设置容忍node.kubernetes.io/not-ready:NoExecute或者node.alpha.kubernetes.io/unreachable:NoExecute的Pod,设置5min的默认容忍时间。
    DenyExecOnPrivileged:已弃用,拦截所有想在Privileged Container上执行命令的请求。
    如果你的集群支持Privileged Container,又希望限制用户在这些Privileged Container上执行命令,那么强烈推荐使用它。
    其功能已被合并到DenyEscalatingExec中。
    DenyEscalatingExec:拦截所有exec和attach到具有特权的Pod上的请求。
    如果你的集群支持运行有escalated privilege权限的容器,又希望限制用户在这些容器内执行命令,那么强烈推荐使用它。
    EventReateLimit:Alpha版本,用于应对事件密集情况下对API Server造成的洪水攻击。
    ExtendedResourceToleration:如果运维人员要创建带有特定资源(例如GPU、FPGA等)的独立节点,则可能会对节点进行Taint处理来进行特别配置。
    该控制器能够自动为申请这些特别资源的Pod加入Toleration定义,无须人工干预。
    ImagePolicyWebhook:这个插件将允许后端的一个Webhook程序来完成admission controller的功能。
    ImagePolicyWebhook需要使用一个配置文件(通过kube-apiserver的启动参数--admission-control-config-file设置)定义后端Webhook的参数。
    目前是Alpha版本的功能。
    Initializers:Alpha。用于为动态准入控制提供支持,通过修改待创建资源的元数据来完成对该资源的修改。
    LimitPodHardAntiAffinityTopology:该插件启用了Pod的反亲和性调度策略设置,
    在设置亲和性策略参数requiredDuringSchedulingRequiredDuringExecution时要求将topologyKey的值设置为“kubernetes.io/hostname”,否则Pod会被拒绝创建。
    LimitRanger:这个插件会监控进入的请求,确保请求的内容符合在Namespace中定义的LimitRange对象里的资源限制。
    如果要在Kubernetes集群中使用LimitRange对象,则必须启用该插件才能实施这一限制。
    LimitRanger还能用于为没有设置资源请求的Pod自动设置默认的资源请求,该插件会为default命名空间中的所有Pod设置0.1CPU的资源请求。
    请参考第10章对LimitRange原理和用法的详细说明。
    MutatingAdmissionWebhook:Beta。这一插件会变更符合要求的请求的内容,Webhook以串行的方式顺序执行。
    NamespaceAutoProvision:这一插件会检测所有进入的具备命名空间的资源请求,如果其中引用的命名空间不存在,就会自动创建命名空间。
    NamespaceExists:这一插件会检测所有进入的具备命名空间的资源请求,如果其中引用的命名空间不存在,就会拒绝这一创建过程。
    NamespaceLifecycle:如果尝试在一个不存在的Namespace中创建资源对象,则该创建请求将被拒绝。
    当删除一个Namespace时,系统将会删除该Namespace中的所有对象,包括Pod、Service等,并阻止删除default、kube-system和kube-public这三个命名空间。
    NodeRestriction:该插件会限制kubelet对Node和Pod的修改行为。
    为了实现这一限制,kubelet必须使用system:nodes组中用户名为system:node:<nodeName>的Token来运行。
    符合条件的kubelet只能修改自己的Node对象,也只能修改分配到各自Node上的Pod对象。
    在Kubernetes 1.11以后的版本中,kubelet无法修改或者更新自身Node的taint属性。
    在Kubernetes 1.13以后,这一插件还会阻止kubelet删除自己的Node资源,并限制对有kubernetes.io/或k8s.io/前缀的标签的修改。
    OnwerReferencesPermissionEnforcement:在该插件启用后,一个用户要想修改对象的metadata.ownerReferences,就必须具备delete权限。
    该插件还会保护对象的metadata.ownerReferences[x].blockOwnerDeletion字段,用户只有在对finalizers子资源拥有update权限的时候才能进行修改。
    PersistentVolumeLabel:弃用。这一插件自动根据云供应商(例如GCE或AWS)的定义,为PersistentVolume对象加入region或zone标签,以此来保障PersistentVolume和Pod同处一区。
    如果插件不为PV自动设置标签,则需要用户手动保证Pod和其加载卷的相对位置。该插件正在被Cloud controller manager替换,从Kubernetes 1.11版本开始默认被禁止。
    PodNodeSelector:该插件会读取命名空间的annotation字段及全局配置,来对一个命名空间中对象的节点选择器设置默认值或限制其取值。
    PersistentVolumeClaimResize:该插件实现了对PersistentVolumeClaim发起的resize请求的额外校验。
    PodPreset:该插件会使用PodSelector选择Pod,为符合条件的Pod进行注入。
    PodSecurityPolicy:在创建或修改Pod时决定是否根据Pod的security context和可用的PodSecurityPolicy对Pod的安全策略进行控制。
    PodTolerationRestriction:该插件首先会在Pod和其命名空间的Toleration中进行冲突检测,如果其中存在冲突,则拒绝该Pod的创建。
    它会把命名空间和Pod的Toleration进行合并,然后将合并的结果与命名空间中的白名单进行比较,如果合并的结果不在白名单内,则拒绝创建。
    如果不存在命名空间级的默认 Toleration和白名单,则会采用集群级别的默认Toleration和白名单。
    Priority:这一插件使用priorityClassName字段来确定优先级,如果没有找到对应的Priority Class,该Pod就会被拒绝。
    ResourceQuota:用于资源配额管理目的,作用于Namespace。该插件拦截所有请求,以确保在Namespace上的资源配额使用不会超标。
    推荐在Admission Control参数列表中将这个插件排最后一个,以免可能被其他插件拒绝的Pod被过早分配资源。
    在10.4节将详细介绍ResourceQuota的原理和用法。
    SecurityContextDeny:这个插件将在Pod中定义的SecurityContext选项全部失效。
    SecurityContext在Container中定义了操作系统级别的安全设定(uid、gid、capabilities、SELinux等)。
    在未设置PodSecurityPolicy的集群中建议启用该插件,以禁用容器设置的非安全访问权限。
    ServiceAccount:这个插件将ServiceAccount实现了自动化,如果想使用ServiceAccount对象,那么强烈推荐使用它,在后面讲述ServiceAccount的章节会详细说明其作用。
    StorageObjectInUseProtection:这一插件会在新创建的PVC或PV中加入kubernetes.io/pvc-protection或kubernetes.io/pv-protection的finalizer。
    如果想要删除PVC或者PV,则直到所有finalizer的工作都完成,删除动作才会执行。
    ValidatingAdmissionWebhook:在Kubernetes 1.8中为Alpha版本,在Kubernetes 1.9中为Beta版本。该插件会针对符合其选择要求的请求调用校验Webhook。
    目标Webhook会以并行方式运行;如果其中任何一个Webhook拒绝了该请求,该请求就会失败。
    在API Server上设置参数即可定制我们需要的准入控制链,如果启用多种准入控制选项,则建议设置:
    在Kubernetes 1.9及之前的版本中使用的参数是--admission-control,并且其中的内容是顺序相关的;
    在Kubernetes 1.10及之后的版本中,该参数为--enable-admission-plugins,并且与顺序无关。
    对Kubernetes 1.10及以上版本设置如下:
    --enable-admission-plugins=NamespaceLifecycle,LimitRanger,ServiceAccount,DefaultStorageClass,DefaultTolerationSeconds,MutatingAdmissionWebhook,ValidatingAdmissionWebhook,ResourceQuota
    对Kubernetes 1.9及以下版本设置如下:
    --admission-control=NamespaceLifecycle,LimitRanger,ServiceAccount,DefaultStorageClass,DefaultTolerationSeconds,MutatingAdmissionWebhook,ValidatingAdmissionWebhook,ResourceQuota

    6.4 Service Account
    Service Account也是一种账号,但它并不是给Kubernetes集群的用户(系统管理员、运维人员、租户用户等)用的,而是给运行在Pod里的进程用的,它为Pod里的进程提供了必要的身份证明。
    在继续学习之前,请回忆6.1节API Server认证的内容。
    在正常情况下,为了确保Kubernetes集群的安全,API Server都会对客户端进行身份认证,认证失败的客户端无法进行API调用。
    此外,在Pod中访问Kubernetes API Server服务时,是以Service方式访问名为Kubernetes的这个服务的,
    而Kubernetes服务又只在HTTPS安全端口443上提供,那么如何进行身份认证呢?
    这的确是个谜,因为Kubernetes的官方文档并没有清楚说明这个问题。
    通过查看官方源码,我们发现这是在用一种类似HTTP Token的新认证方式—Service Account Auth,
    Pod中的客户端调用Kubernetes API时,在HTTP Header中传递了一个Token字符串,
    这类似于之前提到的HTTP Token认证方式,但有以下几个不同之处:
    这个Token的内容来自Pod里指定路径下的一个文件(/run/secrets/kubernetes.io/serviceaccount/token),这种Token是动态生成的,
    确切地说,是由Kubernetes Controller进程用API Server的私钥(--service-account-private-key-file指定的私钥)签名生成的一个JWT Secret。
    在官方提供的客户端REST框架代码里,通过HTTPS方式与API Server建立连接后,
    会用Pod里指定路径下的一个CA证书(/run/secrets/kubernetes.io/serviceaccount/ca.crt)验证API Server发来的证书,验证是否为CA证书签名的合法证书。
    API Server在收到这个Token以后,采用自己的私钥对Token进行合法性验证。
    (实际上是使用service-accountkey-file参数指定的私钥,如果没有设置此参数,则默认采用tls-private-key-file指定的参数,即自己的私钥)
    明白了认证原理,我们接下来继续分析在上面的认证过程中所涉及的Pod中的以下三个文件。
    /run/secrets/kubernetes.io/serviceaccount/token。
    /run/secrets/kubernetes.io/serviceaccount/ca.crt。
    /run/secrets/kubernetes.io/serviceaccount/namespace(客户端采用这里指定的namespace作为参数调用Kubernetes API)。
    这三个文件由于参与到Pod进程与API Server认证的过程中,起到了类似secret(私密凭据)的作用,所以它们被称为Kubernetes Secret对象。
    Secret从属于Service Account资源对象,属于Service Account的一部分,在一个Service Account对象里面可以包括多个不同的Secret对象,分别用于不同目的的认证活动。
    下面通过运行一些命令来加深我们对Service Account与Secret的直观认识。
    首先,查看系统中的Service Account对象,看到有一个名为default的Service Account对象,包含一个名为default-token-77oyg的Secret,
    这个Secret同时是Mountable secrets,表明它是需要被挂载到Pod上的:
    # kubectl describe serviceaccounts
    接下来看看default-token-77oyg都有什么内容:
    # kubectl describe secrets default-token-77oyg
    从上面的输出信息中可以看到,default-token-77oyg包含三个数据项,分别是token、ca.crt、namespace。
    联想到Mountable secrets的标记,以及之前看到的Pod中的三个文件的文件名,我们恍然大悟:
    在每个Namespace下都有一个名为default的默认Service Account对象,
    在这个Service Account里面有一个名为Tokens的可以当作Volume被挂载到Pod里的Secret,
    当Pod启动时,这个Secret会自动被挂载到Pod的指定目录下,用来协助完成Pod中的进程访问API Server时的身份鉴权。
    如图6.3所示,一个Service Account可以包括多个Secret对象。
    其中:
    (1)名为Tokens的Secret用于访问API Server的Secret,也被称为Service Account Secret。
    (2)名为imagePullSecrets的Secret用于下载容器镜像时的认证过程,通常镜像库运行在Insecure模式下,所以这个Secret为空。
    (3)用户自定义的其他Secret,用于用户的进程。
    如果一个Pod在定义时没有指定spec.serviceAccountName属性,则系统会自动为其赋值为default,即大家都使用同一个Namespace下的默认Service Account。
    如果某个Pod需要使用非default的Service Account,则需要在定义时指定:
    apiVersion: v1
    kind: Pod
    metadata:
    name: mypod
    spec:
    containers:
    - name: mycontainer
    image: mginx:v1
    serviceAccountName: myserviceaccount
    Kubernetes之所以要创建两套独立的账号系统,原因如下:
    User账号是给人用的,Service Account是给Pod里的进程使用的,面向的对象不同。
    User账号是全局性的,Service Account则属于某个具体的Namespace。
    通常来说,User账号是与后端的用户数据库同步的,创建一个新用户通常要走一套复杂的业务流程才能实现,
    Service Account的创建则需要极轻量级的实现方式,集群管理员可以很容易地为某些特定任务创建一个Service Account。
    对于这两种不同的账号,其审计要求通常不同。
    对于一个复杂的系统来说,多个组件通常拥有各种账号的配置信息,Service Account是Namespace隔离的,可以针对组件进行一对一的定义,同时具备很好的“便携性”。

    接下来深入分析Service Account与Secret相关的一些运行机制。
    根据5.2节的内容,我们知道Controller manager创建了ServiceAccount Controller与Token Controller这两个安全相关的控制器。
    其中ServiceAccount Controller一直监听Service Account和Namespace的事件,
    如果在一个Namespace中没有default Service Account,那么ServiceAccount Controller会为该Namespace创建一个默认(default)的Service Account,
    这就是我们之前看到在每个Namespace下都有一个名为default的Service Account的原因。
    如果Controller manager进程在启动时指定了API Server私钥(service-accountprivate-key-file参数),那么Controller manager会创建Token Controller。
    Token Controller也监听Service Account的事件,如果发现在新创建的Service Account里没有对应的Service Account Secret,则会用API Server私钥创建一个Token(JWT Token),
    并用该Token、CA证书及Namespace名称等三个信息产生一个新的Secret对象,然后放入刚才的Service Account中;
    如果监听到的事件是删除Service Account事件,则自动删除与该Service Account相关的所有Secret。
    此外,Token Controller对象同时监听Secret的创建、修改和删除事件,并根据事件做不同的处理。

    当我们在API Server的鉴权过程中启用了Service Account类型的准入控制器,即在kube-apiserver启动参数中包括下面的内容时:
    --adminssion_control=ServiceAccount
    则针对Pod新增或修改的请求,Service Account准入控制器会验证Pod里的Service Account是否合法。
    (1)如果spec.serviceAccount域没有被设置,则Kubernetes默认为其指定名称为default的Service accout。
    (2)如果Pod的spec.serviceAccount域指定了default以外的Service Account,而该Service Account没有被事先创建,则该Pod操作失败。
    (3)如果在Pod中没有指定ImagePullSecrets,那么这个spec.serviceAccount域指定的Service Account的ImagePullSecrets会被加入该Pod中。
    (4)给Pod添加一个特殊的Volume,在该Volume中包含Service Account Secret中的Token,并将Volume挂载到Pod中所有容器的指定目录下(/var/run/secrets/kubernetes.io/serviceaccount)。
    综上所述,Service Account的正常工作离不开以下几个控制器。
    (1)Admission Controller。
    (2)Token Controller。
    (3)ServiceAccount Controller。

    6.5 Secret私密凭据
    上一节提到Secret对象,Secret的主要作用是保管私密数据,比如密码、OAuth Tokens、SSH Keys等信息。
    将这些私密信息放在Secret对象中比直接放在Pod或Docker Image中更安全,也更便于使用和分发。
    下面的例子用于创建一个Secret(secret.yaml):
    apiVersion: v1
    kind: Secret
    metadata:
    name: mysecret
    type: Opaque
    data:
    password: xxxxxx
    username: xxxxxx

    # kubectl create -f secret.yaml
    在上面的例子中,data域的各子域的值必须为BASE64编码值,其中password域和username域BASE64编码前的值分别为value-1和value-2。
    一旦Secret被创建,就可以通过下面三种方式使用它:
    (1)在创建Pod时,通过为Pod指定Service Account来自动使用该Secret。
    (2)通过挂载该Secret到Pod来使用它。
    (3)在Docker镜像下载时使用,通过指定Pod的spc.ImagePullSecrets来引用它。
    第1种使用方式主要用在API Server鉴权方面,之前提到过。
    下面的例子展示了第2种使用方式:将一个Secret通过挂载的方式添加到Pod的Volume中:
    apiVersion: v1
    kind: Pod
    metadata:
    name: mypod
    namespace: myns
    spec:
    containers:
    - name: mycontainer
    image: redis
    volumeMounts:
    - name: foo
    mountPath: "/etc/foo"
    readOnly: true
    volumes:
    - name: foo
    secret:
    secretName: mysecret
    其结果如图6.4所示。
    第3种使用方式的使用流程如下。
    (1)执行login命令,登录私有Registry:
    # docker login localhost:5000
    输入用户名和密码,如果是第1次登录系统,则会创建新用户,相关信息被会写入~/.dockercfg文件中。
    (2)用BASE64编码dockercfg的内容:
    # cat ~/.dockercfg | base64
    (3)将上一步命令的输出结果作为Secret的data.dockercfg域的内容,由此来创建一个Secret(image-pull-secret.yaml):
    apiVersion: v1
    kind: Secret
    metadata:
    name: myregistrykey
    type: Opaque
    data:
    dockercfg: xxxxxx上一步命令的输出结果
    type: kubernetes.io/dockercfg

    # kubectl create -f image-pull-secret.yaml
    (4)在创建Pod(pods.yaml)时引用该Secret:
    apiVersion: v1
    kind: Pod
    metadata:
    name: mypod
    spec:
    containers:
    - name: foo
    image: janedoe/awesomeapp:v1
    imagePullSecrets:
    - name: myregistrykey
    # kubectl create -f pods.yaml
    其结果如图6.5所示。

    每个单独的Secret大小不能超过1MB,Kubernetes不鼓励创建大的Secret,因为如果使用大的Secret,则将大量占用API Server和kubelet的内存。
    当然,创建许多小的Secret也能耗尽API Server和kubelet的内存。
    在使用Mount方式挂载Secret时,Container中Secret的data域的各个域的Key值作为目录中的文件,Value值被BASE64编码后存储在相应的文件中。
    在前面的例子中创建的Secret,被挂载到一个叫作mycontainer的Container中,在该Container中可通过相应的查询命令查看所生成的文件和文件中的内容,如下所示:
    # ls /etc/foo/
    # cat /etc/foo/username
    # cat /etc/foo/password
    通过上面的例子可以得出如下结论:
    我们可以通过Secret保管其他系统的敏感信息(比如数据库的用户名和密码),并以Mount的方式将Secret挂载到Container中,然后通过访问目录中文件的方式获取该敏感信息。
    当Pod被API Server创建时,API Server不会校验该Pod引用的Secret是否存在。
    一旦这个Pod被调度,则kubelet将试着获取Secret的值。
    如果Secret不存在或暂时无法连接到API Server,则kubelet按一定的时间间隔定期重试获取该Secret,并发送一个Event来解释Pod没有启动的原因。
    一旦Secret被Pod获取,则kubelet将创建并挂载包含Secret的Volume。
    只有所有Volume都挂载成功,Pod中的Container才会被启动。
    在kubelet启动Pod中的Container后,Container中和Secret相关的Volume将不会被改变,即使Secret本身被修改。
    为了使用更新后的Secret,必须删除旧Pod,并重新创建一个新Pod。

    6.6 Pod的安全策略配置
    为了更精细地控制Pod对资源的使用方式,Kubernetes从1.4版本开始引入了PodSecurityPolicy资源对象对Pod的安全策略进行管理,并在1.10版本中升级为Beta版,到1.14版本时趋于成熟。
    目前PodSecurityPolicy资源对象的API版本为extensions/v1beta1,从1.10版本开始更新为policy/v1beta1,并计划于1.16版本时弃用extensions/v1beta1。

    6.6.1 PodSecurityPolicy的工作机制
    若想启用PodSecurityPolicy机制,则需要在kube-apiserver服务的启动参数--enable-admission-plugins中进行设置:
    --enable-admission-plugins=PodSecurityPolicy
    在开启PodSecurityPolicy准入控制器后,Kubernetes默认不允许创建任何Pod,
    需要创建PodSecurityPolicy策略和相应的RBAC授权策略(Authorizing Policies),Pod才能创建成功。
    例如,尝试创建如下Pod(pod.yaml):
    apiVersion: v1
    kind: Pod
    metadata:
    name: nginx
    spec:
    containers:
    - name: nginx
    image: jnginx
    使用kubectl命令创建时,系统将提示“禁止创建”的报错信息:
    # kubectl create -f pod.yaml
    接下来创建一个PodSecurityPolicy,配置文件psp-non-privileged.yaml的内容如下:
    apiVersion: policy/v1beta1
    kind: PodSecurityPolicy
    metadata:
    name: psp-non-privileged
    spec:
    privileged: false #不允许特权模式的Pod
    seLinux:
    rule: RunAsAny
    supplementalGroups:
    rule: RunAsAny
    runAsUser:
    rule: RunAsAny
    fsGroup:
    rule: RunAsAny
    volumes:
    - '*'
    使用kubectl create命令创建该PodSecurityPolicy:
    # kubectl create -f psp-non-privileged.yaml
    查看PodSecurityPolicy:
    # kubectl get psp psp-non-privileged
    再次创建Pod就能成功:
    # kubecrl create -f pod.yaml
    上面的PodSecurityPolicy“psp-non-privileged”设置了privileged: false,表示不允许创建特权模式的Pod。
    在下面的YAML配置文件pod-privileged.yaml中为Pod设置了特权模式:
    apiVersion: v1
    kind: Pod
    metadata:
    name: nginx
    spec:
    containers:
    - name: nginx
    image: jnginx
    securityContext:
    privileged: true
    创建Pod时,系统将提示“禁止创建特权模式的Pod”的报错信息:
    # kubectl create -f pod-privileged.yaml

    6.6.2 PodSecurityPolicy配置详解
    在PodSecurityPolicy对象中可以设置下列字段来控制Pod运行时的各种安全策略。
    1.特权模式相关配置
    privileged:是否允许Pod以特权模式运行。

    2.宿主机资源相关配置
    (1)hostPID:是否允许Pod共享宿主机的进程空间。
    (2)hostIPC:是否允许Pod共享宿主机的IPC命名空间。
    (3)hostNetwork:是否允许Pod使用宿主机网络的命名空间。
    (4)hostPorts:是否允许Pod使用宿主机的端口号,可以通过hostPortRange字段设置允许使用的端口号范围,以[min, max]设置最小端口号和最大端口号。
    (5)Volumes:允许Pod使用的存储卷Volume类型,设置为“*”表示允许使用任意Volume类型,建议至少允许Pod使用下列Volume类型:
    configMap、downwardAPI、emptyDir、persistentVolumeClaim、secret、projected。
    (6)AllowedHostPaths:允许Pod使用宿主机的hostPath路径名称,可以通过pathPrefix字段设置路径的前缀,并可以设置是否为只读属性
    (7)FSGroup:设置允许访问某些Volume的Group ID范围,可以将规则(rule字段)设置为MustRunAs、MayRunAs或RunAsAny。
    MustRunAs:需要设置Group ID的范围,例如1~65535,要求Pod的securityContext.fsGroup设置的值必须属于该Group ID的范围。
    MayRunAs:需要设置Group ID的范围,例如1~65535,不强制要求Pod设置securityContext.fsGroup。
    RunAsAny:不限制Group ID的范围,任何Group都可以访问Volume。
    (8)ReadOnlyRootFilesystem:要求容器运行的根文件系统(root filesystem)必须是只读的。
    (9)allowedFlexVolumes:对于类型为flexVolume的存储卷,设置允许使用的驱动类型。
    例1:结果为允许Pod访问宿主机上以“/foo”为前缀的路径,包括“/foo”“/foo/”“/foo/bar”等,但不能访问“/fool”“/etc/foo”等路径,也不允许通过“/foo/../”表达式访问/foo的上层目录。
    apiVersion: policy/v1beta1
    kind: PodSecurityPolicy
    metadata:
    name: allow-hostpath-volumes
    spec:
    volumes:
    - hostPath
    allowedHostPaths:
    - pathPrefix: “/foo”
    readOnly: true
    例2:
    apiVersion: policy/v1beta1
    kind: PodSecurityPolicy
    metadata:
    name: allow-flex-volumes
    spec:
    volumes:
    - flexVolume
    allowedFlexVolumes:
    - driver: example/lvm
    - driver: example/cifs

    3.用户和组相关配置
    (1)RunAsUser:设置运行容器的用户ID(User ID)范围,规则字段(rule)的值可以被设置为MustRunAs、MustRunAsNonRoot或RunAsAny。
    MustRunAs:需要设置User ID的范围,要求Pod的securityContext.runAsUser设置的值必须属于该User ID的范围。
    MustRunAsNonRoot:必须以非root用户运行容器,要求Pod的securityContext.runAsUser设置一个非0的用户ID,或者镜像中在USER字段设置了用户ID,
    建议同时设置allowPrivilegeEscalation=false以避免不必要的提升权限操作。
    RunAsAny:不限制User ID的范围,任何User都可以运行。
    (2)RunAsGroup:设置运行容器的Group ID范围,规则字段的值可以被设置为MustRunAs、MustRunAsNonRoot或RunAsAny。
    MustRunAs:需要设置Group ID的范围,要求Pod的securityContext.runAsGroup设置的值必须属于该Group ID的范围。
    MustRunAsNonRoot:必须以非root组运行容器,要求Pod的securityContext.runAsUser设置一个非0的用户ID,或者镜像中在USER字段设置了用户ID,
    建议同时设置allowPrivilegeEscalation=false以避免不必要的提升权限操作。
    RunAsAny:不限制Group ID的范围,任何Group的用户都可以运行。
    (3)SupplementalGroups:设置容器可以额外添加的Group ID范围,可以将规则(rule字段)设置为MustRunAs、MayRunAs或RunAsAny。
    MustRunAs:需要设置Group ID的范围,要求Pod的securityContext.supplementalGroups设置的值必须属于该Group ID范围。
    MayRunAs:需要设置Group ID的范围,不强制要求Pod设置securityContext.supplementalGroups。
    RunAsAny:不限制Group ID的范围,任何supplementalGroups的用户都可以运行。

    4.提升权限相关配置
    (1)AllowPrivilegeEscalation:设置容器内的子进程是否可以提升权限,通常在设置非root用户(MustRunAsNonRoot)时进行设置。
    (2)DefaultAllowPrivilegeEscalation:设置AllowPrivilegeEscalation的默认值,设置为disallow时,管理员还可以显式设置AllowPrivilegeEscalation来指定是否允许提升权限。
    5.Linux能力相关配置
    (1)AllowedCapabilities:设置容器可以使用的Linux能力列表,设置为“*”表示允许使用Linux的所有能力(如NET_ADMIN、SYS_TIME等)。
    (2)RequiredDropCapabilities:设置不允许容器使用的Linux能力列表。
    (3)DefaultAddCapabilities:设置默认为容器添加的Linux能力列表,例如SYS_TIME等,
    Docker建议默认设置的Linux能力请查看https://docs.docker.com/engine/reference/run/#runtime-privilege-and-linux-capabilities。
    6.SELinux相关配置
    seLinux:设置SELinux参数,可以将规则字段(rule)的值设置为MustRunAs或RunAsAny。
    MustRunAs:要求设置seLinuxOptions,系统将对Pod的securityContext.seLinuxOptions设置的值进行校验。
    RunAsAny:不限制seLinuxOptions的设置。
    7.其他Linux相关配置
    (1)AllowedProcMountTypes:设置允许的ProcMountTypes类型列表,可以设置allowedProcMountTypes或DefaultProcMount。
    (2)AppArmor:设置对容器可执行程序的访问控制权限,详情请参考https://kubernetes.io/docs/tutorials/clusters/apparmor/#podsecuritypolicy-annotations。
    (3)Seccomp:设置允许容器使用的系统调用(System Calls)的profile。
    (4)Sysctl:设置允许调整的内核参数,详情请参考https://kubernetes.io/docs/concepts/cluster-administration/sysctl-cluster/#podsecuritypolicy。
    下面列举两种常用的PodSecurityPolicy安全策略配置。
    例1:基本没有限制的安全策略,允许创建任意安全设置的Pod。
    apiVersion: policy/v1beta1
    kind: PodSecurityPolicy
    metadata:
    name: privileged
    annotations:
    seccomp.security.alpha.kubernetes.io/allowedProfileNames: '*'
    spec:
    privileged: true
    allowedPrivilegeEscalation: true
    allowedPrivCapabilities:
    - '*'
    volumes:
    - '*'
    hostNetwork: true
    hostPorts:
    - min: 0
    max: 65535
    hostIPC: true
    hostPID: true
    runAsUser:
    rule: 'RunAsAny'
    seLinux:
    rule: 'RunAsAny'
    supplementalGroups:
    rule: 'RunAsAny'
    fsGroup:
    rule: 'RunAsAny'
    例2:要求Pod运行用户为非特权用户;禁止提升权限;不允许使用宿主机网络、端口号、IPC等资源;限制可以使用的Volume类型,等等。
    apiVersion: policy/v1beta1
    kind: PodSecurityPolicy
    metadata:
    name: restricted
    annotations:
    seccomp.security.alpha.kubernetes.io/allowedProfileNames: 'docker/default'
    seccomp.security.alpha.kubernetes.io/allowedProfileNames: 'runtime/default'
    seccomp.security.alpha.kubernetes.io/defaultProfileNames: 'docker/default'
    seccomp.security.alpha.kubernetes.io/defaultProfileNames: 'runtime/default'
    spec:
    privileged: false
    allowedPrivilegeEscalation: false
    requiredDropCapabilities:
    - ALL
    volumes:
    - 'configMap'
    - 'emptyDir'
    - 'projected'
    - 'secret'
    - 'downwardAPI'
    - 'persistentVolumeClaim'
    hostNetwork: false
    hostIPC: false
    hostPID: false
    runAsUser:
    rule: 'MustRunAsNonRoot'
    seLinux:
    rule: 'RunAsAny'
    supplementalGroups:
    rule: 'MustRunAs'
    ranges:
    - min: 1
    max: 65535
    fsGroup:
    rule: 'MustRunAs'
    ranges:
    - min: 1
    max: 65535
    readOnlyRootFilesystem: false
    此外,Kubernetes建议使用RBAC授权机制来设置针对Pod安全策略的授权,通常应该对Pod的ServiceAccount进行授权。
    例如,可以创建如下ClusterRole(也可以创建Role)并将其设置为允许使用PodSecurityPolicy:
    apiVersion: rbac.authorization.k8s.io/v1
    kind: ClusterRole
    metadata:
    name: <role-name>
    rules:
    - apiGroup: ["policy"]
    resources: ["podsecuritypolicies"]
    verbs: ["use"]
    resourceNames: <list of policies to authorize>
    然后创建一个ClusterRoleBinding与用户和ServiceAccount进行绑定:
    apiVersion: rbac.authorization.k8s.io/v1
    kind: ClusterRoleBinding
    metadata:
    name: <binding-name>
    ruleRef:
    apiGroup: rbac.authorization.k8s.io
    kind: ClusterRole/Role
    name: <role-name>
    subjects:
    - kind: ServiceAccount
    name: <ServiceAccount-name>
    namespace: <namespace-name>
    - apiGroup: rbac.authorization.k8s.io
    kind: User
    name: <user-name>
    也可以创建RoleBinding对与该RoleBinding相同的Namespace中的Pod进行授权,通常可以与某个系统级别的Group关联配置,例如:
    apiVersion: rbac.authorization.k8s.io/v1
    kind: RoleBinding
    metadata:
    name: <binding-name>
    namespace: <binding-namespace>
    ruleRef:
    apiGroup: rbac.authorization.k8s.io
    kind: ClusterRole/Role
    name: <role-name>
    subjects:
    - apiGroup: rbac.authorization.k8s.io
    kind: Group
    name: system:serviceaccounts
    - apiGroup: rbac.authorization.k8s.io
    kind: Group
    name: system:authenticated

    6.6.3 Pod的安全设置详解
    在系统管理员对Kubernetes集群中设置了PodSecurityPolicy策略之后,系统将对Pod和Container级别的安全设置进行校验,对于不满足PodSecurityPolicy安全策略的Pod,系统将拒绝创建。
    Pod和容器的安全策略可以在Pod或Container的securityContext字段中进行设置,如果在Pod和Container级别都设置了相同的安全类型字段,容器将使用Container级别的设置。
    在Pod级别可以设置的安全策略类型如下:
    runAsUser:容器内运行程序的用户ID。
    runAsGroup:容器内运行程序的用户组ID。
    runAsNonRoot:是否必须以非root用户运行程序。
    fsGroup:SELinux相关设置。
    seLinuxOptions:SELinux相关设置。
    supplementalGroups:允许容器使用的其他用户组ID。
    sysctls:设置允许调整的内核参数。
    在Container级别可以设置的安全策略类型如下:
    runAsUser:容器内运行程序的用户ID。
    runAsGroup:容器内运行程序的用户组ID。
    runAsNonRoot:是否必须以非root用户运行程序。
    privileged:是否以特权模式运行。
    allowPrivilegeEscalation:是否允许提升权限。
    readOnlyRootFilesystem:根文件系统是否为只读属性。
    capabilities:Linux能力列表。
    seLinuxOptions:SELinux相关设置。
    下面通过几个例子对Pod的安全设置进行说明。
    例1:Pod级别的安全设置,作用于该Pod内的全部容器。
    apiVersion: v1
    kind: Pod
    metadata:
    name: security-context-demo
    spec:
    securityContext:
    runAsUser: 1000
    runAsGroup: 3000
    fsGroup: 2000
    volumes:
    - name: sec-ctx-vol
    emptyDir: {}
    containers:
    - name: sec-ctx-demo
    image: gcr.io/google-samples/node-hello:1.0
    volumeMounts:
    - name: sec-ctx-vol
    mountPath: /data/demo
    securityContext:
    allowPrivilegeEscalation: false
    在spec.securityContext中设置了如下参数:
    runAsUser=1000:所有容器都将以User ID 1000运行程序,所有新生成文件的User ID也被设置为1000。
    runAsGroup=3000:所有容器都将以Group ID 3000运行程序,所有新生成文件的Group ID也被设置为3000。
    fsGroup=2000:挂载的卷“/data/demo”及其中创建的文件都将属于Group ID 2000。
    创建该Pod之后进入容器环境,查看到运行进程的用户ID为1000:
    # ps aux
    查看从Volume挂载到容器的/data/demo目录,其Group ID为2000:
    # ls -l /data
    在该目录下创建一个新文件,可见其用户ID为1000,组ID为2000:
    # cd demo
    # touch hello
    # ls -l

    例2:Container级别的安全设置,作用于特定的容器。
    apiVersion: v1
    kind: Pod
    metadata:
    name: security-context-demo-2
    spec:
    securityContext:
    runAsUser: 1000
    containers:
    - name: sec-ctx-demo-2
    image: gcr.io/google-samples/node-hello:1.0
    securityContext:
    runAsUser: 2000
    allowPrivilegeEscalation: false
    创建该Pod之后进入容器环境,查看到运行进程的用户ID为2000:
    # ps aux

    例3:为Container设置可用的Linux能力,为容器设置允许使用的Linux能力包括NET_ADMIN和SYS_TIME。
    apiVersion: v1
    kind: Pod
    metadata:
    name: security-context-demo-4
    spec:
    containers:
    - name: sec-ctx-demo-4
    image: gcr.io/google-samples/node-hello:1.0
    securityContext:
    capabilities:
    add: ["NET_ADMIN","SYS_TIME"]
    创建该Pod之后进入容器环境,查看1号进程的Linux能力设置:
    # cd /proc/1
    # cat status
    CapPrm:
    CapEff:
    未设置这两个能力的配置为:
    CapPrm:
    CapEff:
    可以看到系统在第12位和第25位添加了CAP_NET_ADMIN和CAP_SYS_TIME这两个Linux能力。

  • 相关阅读:
    Vista总管注册机
    [更正]谈获取当前系统类型(SP OR PPC)
    [CallerLoc插件]SmsReply For CallerLoc (未接自动回复短信增强版)
    也谈Windows Mobile中打开/关闭WIFI
    C#实现Eval函数功能
    解决DotNetTextBox与window.history冲突!
    Vista单个程序静音带来的麻烦
    快速打开/关闭系统功能开关(CmdSwitch For SP & PPC)
    终于实现打开/关闭飞行模式了
    7.3 Hardware Prefetching 《Speculative Execution in High Performance Computer Architectures》
  • 原文地址:https://www.cnblogs.com/BradMiller/p/12228261.html
Copyright © 2020-2023  润新知