安全永远是一个重大的话题,特别是云计算平台,更需要设计出一套完善的安全方案,以应对复杂的场景。 Kubernetes主要使用Docker作为应用承载环境,Kubernetes首先设计出一套API和敏感信息处理方案,当然也基于Docker提供容器安全控制。以下是Kubernetes的安全设计原则:
1. 保证容器与其运行的宿主机之间有明确的隔离 2. 限制容器对基础设施或者其它容器造成不良影响的能力 3. 最小特权原则——限定每个组件只被赋予了执行操作所必需的最小特权,由此确保可能产生的损失达到最小 4. 允许系统用户明确区别于管理员 5. 允许赋予管理权限给用户 6. 允许应用能够从公开数据中提取敏感信息(keys, certs, passwords)
http://docs.kubernetes.org.cn/27.html
Kubernetes API Server通过一个名为kube-apiserver的进程提供服务,该进程运行在Master节点上。在默认情况下,kube-apiserver进程在本机的8080端口(--insecure-port)提供REST服务。我们可以同时启动HTTPS安全端口(--secure=6443)来启动安全机制,加强REST API访问的安全性。
1、REST调用
通常我们使用kubectl来与Kubernetes API Server交互,它们之间的接口是REST调用。也可以使用curl命令行工具进行快速验证。
2、编程的方式调用
另外是通过编程的方式调用Kubernetes API Server 具体又细分为以下两种场景:
1. 运行在POD里的进程调用Server API. Pod中的进程如何指定API Server的访问地址呢?答案很简单,因为Kubernetes API Server本身也是一个Service,它的名字是“kubernetes”,IP地址是ClusterIP地址池里面的第一个地址。服务端口是HTTPS端口443.
2. 开发基于Kubernetes的管理平台,比如调用Kubernetes API来完成Pod,Service,RC等资源对象的图形化创建和管理界面。可采用社区中相关的Client Library.
正常情况下,为了确保Kubernetes集群的安全,API Server都会对客户端进行身份认证,认证失败则无法调用API。此外,Pod中访问Kubernetes API Server服务的时候,是以Service方式访问服务名为kubernetes的这个服务,而kubernetes服务又只在HTTPS 443上提供服务,那么如何进行身份认证呢? 答案是 Service Account Token.
API的请求会经过多个阶段的访问控制才会被接受处理,其中包含认证、授权以及准入控制(Admission Control)等。如下图所示:
需要注意:认证授权过程只存在HTTPS形式的API中。也就是说,如果客户端使用HTTP连接到kube-apiserver,是不会进行认证授权的。所以说,可以这么设置,在集群内部组件间通信使用HTTP,集群外部就使用HTTPS,这样既增加了安全性,也不至于太复杂。
Kubernetes集群中所有资源的访问和变更都是通过Kubernetes API Server的REST API来实现的,所以集群安全的关键点在于识别认证客户端身份(Authentication)以及访问权限的授权(Authorization)。
当TLS建立时,HTTP请求会进行身份认证步骤,如图中步骤1,集群管理器将apiserver配置为运行一个或多个认证器模块。
认证模块包含客户端证书,密码、Plain Tokens、Bootstrap Tokens、JWT Tokens(used for service accounts)。
我们可以指定多个认证模块,每个认证模块都会按顺序进行,直到其中一个成功。(在GCE上,客户端证书、密码、Plain Tokens和JWT Tokens都会启用。)
API Server认证 Authentication提供管理三种级别的客户端身份认证方式:
CA认证流程图:
上述是双向SSL协议的具体通信过程,这种情况要求服务器和用户双方都有证书。单向认证SSL协议不需要客户拥有CA证书,对应上面的步骤,只需将服务器端验证客户端证书的过程去掉,以及在协商对称密码方案和对称通话秘钥时,服务器端发送给客户端的是没有加过密的(这并不影响SSL过程的安全性)密码方案。
HTTP Token的认证是用一个很长的特殊编码方式的并且难以被模仿的字符串——Token来表明客户身份的一种方式。在通常情况下,Token是一个复杂的字符串,比如我们用私钥签名一个字符串的数据就可以作为一个Token,此外每个Token对应一个用户名,存储在API Server能访问的一个文件中。当客户端发起API调用请求时,需要在HTTP Header里放入Token,这样一来API Server就能够识别合法用户和非法用户了。
常见的客户端账号登录程序,这种认证方式是把“用户名+冒号+密码”用BASE64算法进行编码后的字符串放在HTTP REQUEST中的Header Authorization域里发送给服务端,服务端收到后进行解码,获取用户名及密码,然后进行用户身份的鉴权过程。
现在使用Basic authentication + ABAC model设置API server, 首先配置Basic authentication,设置用户密码,格式为每行password, user name, user id, basic_auth.csv:
admin_passwd,admin,admin
test_passwd,test,test
然后配置ABAC访问策略, 设置admin具有任何权限,test用户只能访问pods, policy_file.jsonl:
{"user":"admin"}
{"user":"test", "resource": "pods", "readonly": true}
访问策略配置详情参考: https://github.com/kubernetes/ ... rithm
然后启动API Server:
$kube-apiserver
...
--basic-auth-file=basic_auth.csv \
--authorization-mode=ABAC --authorization-policy-file=policy_file.jsonl
访问API,可以看到test用户无法访问Pod之外的资源:
$ curl --basic -u admin:admin_passwd https://192.168.3.146:6443/api/v1/pods -k
[
...
]
$ curl --basic -u test:test_passwd https://192.168.3.146:6443/api/v1/pods -k
[
...
]
$ curl --basic -u test:test_passwd https://192.168.3.146:6443/api/v1/nodes -k
Forbidden: "/api/v1/nodes"
认证之后的请求是授权模块:如图中步骤2
对合法用户进行授权(Authorization)并且随后在用户访问时进行鉴权,是权限与安全系统的重要一环。授权就是授予不同用户不同访问权限:
API Server 目前支持以下几种授权策略 (通过 API Server 的启动参数 --authorization-mode
设置)
为了简化授权的复杂度,对于ABAC模式的授权策略,Kubernetes仅有下面四个基本属性:
当API Server启用ABAC模式时,需要指定授权文件的路径和名字(--authorization_policy_file=SOME_FILENAME),授权策略文件里的每一行都是一个Map类型的JOSN对象,被称为访问策略对象,我们可以通过设置“访问策略对象”中的如下属性来确定具体的授权行为:
eg:
RBAC 基于角色的访问控制,在 kubernetes1.5 中引入,现行版本成为默认标准。相对其他访问控制方式,拥有以下优势:
RBAC 引入了 4个新的顶级资源对象:Role、ClusterRole、RoleBinding、ClusterRoleBinding、4种对象类型均可以通过 kubectl 与 API 操作。
Role:普通角色 | ClusterRole:集群角色
Rolebinding:普通角色绑定 ClusterRoleBinding:集群角色绑定
在认证和授权之外,Admission Controller也可以对Kubernetes API Server的访问控制,任何请求在访问API Server时需要经过一系列的验证,任何一环拒绝了请求,则会返回错误。 实际上Admission Controller是作为Kubernetes API Serve的一部分,并以插件代码的形式存在,在API Server启动的时候,可以配置需要哪些Admission Controller,以及它们的顺序,如:
--admission_control=NamespaceLifecycle,NamespaceExists,LimitRanger,SecurityContextDeny,ServiceAccount,ResourceQuota
Admission Controller支持的插件如下:
在API Server上设置--admission-control参数,即可定制我们需要的准入控制链,如果启用多种准入控制选项,则建议的设置如下:
下面着重介绍三个准入控制器:
Security Context时运用于容器的操作系统安全设置(uid、gid、capabilities、SELinux role等),Admission Control的SecurityContextDeny插件的作用是,禁止创建设置了Security Context的Pod,例如包含以下配置项的Pod:
ResourceQuota不仅能够限制某个Namespace中创建资源的数量,而且能够限制某个namespace中被Pod所请求的资源总量。该准入控制器和资源对象ResourceQuota一起实现了资源的配额管理;
准入控制器LimitRanger的作用类似于上面的ResourceQuota控制器,这对Namespace资源的每个个体的资源配额。该插件和资源对象LimitRange一起实现资源限制管理。
什么是service account? 顾名思义,相对于user account(比如:kubectl访问APIServer时用的就是user account),service account就是Pod中的Process用于访问Kubernetes API的account,它为Pod中的Process提供了一种身份标识。相比于user account的全局性权限,service account更适合一些轻量级的task,更聚焦于授权给某些特定Pod中的Process所使用。
Service Account概念的引入是基于这样的使用场景:运行在pod里的进程需要调用Kubernetes API以及非Kubernetes API的其它服务(如image repository/被mount到pod上的NFS volumes中的file等)。我们使用Service Account来为pod提供id。 Service Account和User account可能会带来一定程度上的混淆,User account可以认为是与Kubernetes交互的个体,通常可以认为是human, 目前并不作为一个代码中的类型单独出现,比如第一节中配置的用户,它们的区别如下。
Kubernetes有User Account和Service Account两套独立的账号系统: 1.User Account是给人用的,Service Account 是给Pod 里的进程使用的,面向的对象不同。 2.User Account是全局性的,即跨namespace使用。 Service Account 是属于某个具体的Namespace,即仅在所属的namespace下使用。 3.User Account是与后端的用户数据库同步的。创建一个新的user account通常需要较高的特权并且需要经过比较复杂的business process(即对于集群的访问权限的创建),而service account则不然。
如果kubernetes开启了ServiceAccount(–admission_control=…,ServiceAccount,… )那么会在每个namespace下面都会创建一个默认的default的ServiceAccount。即service account作为一种resource存在于Kubernetes cluster中,我们可以通过kubectl获取
1、当前cluster中的service acount列表:
kubectl get serviceaccount --all-namespaces
2、service account的详细信息
我们查看一下kube-system namespace下名为”default”的service account的详细信息:
kubectl describe serviceaccount/default -n kube-system
我们看到service account并不复杂,只是关联了一个secret资源作为token,该token也叫service-account-token,该token才是真正在API Server验证(authentication)环节起作用的。
3、查看系统的secret 的token列表:
[root@k8s-master k8s-kube-scheduler]# kubectl get secret -n kube-system NAME TYPE DATA AGE coredns-token-cdn9x kubernetes.io/service-account-token 2 1h default-token-lht2v kubernetes.io/service-account-token 2 1h
[root@k8s-master k8s-kube-scheduler]# kubectl get secret NAME TYPE DATA AGE db-user-pass Opaque 0 18d default-token-k7jfg kubernetes.io/service-account-token 2 1h registry-key-secret kubernetes.io/dockerconfigjson 1 18d
4、查看系统的secret 详细:
我们看到这个类型为service-account-token的secret资源包含的数据有三部分:namespace和token。
5、获取原始token:
获取对应sa的secret从中获取token。并进行base64解码。
kubectl get secret default-token-lht2v -n kube-system -o jsonpath={".data.token"} | base64 -d
6、使用token 请求apiserver:
获取对应sa的secret从中获取token。并进行base64解码。
curl -k -H 'Authorization: Bearer token' https://111.111.111.111:6443
curl -k -H 'Authorization: Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6IiJ9.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJrdWJlLXN5c3RlbSIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VjcmV0Lm5hbWUiOiJkZWZhdWx0LXRva2VuLWxodDJ2Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZXJ2aWNlLWFjY291bnQubmFtZSI6ImRlZmF1bHQiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC51aWQiOiI2MDg2MmMxMS04ZTgzLTExZTktYjY2YS0wMDUwNTZiMTIzYjEiLCJzdWIiOiJzeXN0ZW06c2VydmljZWFjY291bnQ6a3ViZS1zeXN0ZW06ZGVmYXVsdCJ9.SCn9sq6i4t4fLPW7kuTe3VfoQUO_iXY6M6vKjR40wqPDFJll5-F9n92iY0DqpBAs-vTWeZDmUuevjSJYeRzJlQmMzw5tZj4CIXZ_LHdiiW4kwmAWiproDE-nAXjF4vKHKkWsGLwpeHOQ2F2pCMIibQkk60ZudlJb_qszoTQvt2NgkUmh-DxwEy6uj6pc-j14s--qNSrValkPIoZV7u2zmPEXzC9sFjADKnqUJ4GCtSS3VIVfqUZvSecWB95f-VIBnQwKJAcOvN7Nspk8Vjp0zW3CAWTmiPm_1AHqIuM0daxl6JLpUBZvfQnCAm_s928DbSyyNrTR9V3ra0M3pHcPdw' https://192.168.10.50:6443/api
{
"kind": "APIVersions",
"versions": [
"v1"
],
"serverAddressByClientCIDRs": [
{
"clientCIDR": "0.0.0.0/0",
"serverAddress": "192.168.10.50:6443"
},
{
"clientCIDR": "192.168.0.0/16",
"serverAddress": "192.168.0.1:443"
}
]
}
1、开启ServiceAccount
如果K8S apiserver的启动参数中添加--admission_control=ServiceAccount
apiserver在启动的时候会自己创建一个key和crt(见/var/run/kubernetes/apiserver.crt和apiserver.key)
然后在启动./kube-controller-manager 时添加flag:
--service_account_private_key_file=/var/run/kubernetes/apiserver.key
那么k8s会在每个namespace下面都会创建一个默认的service account资源,并命名为”default”:
kubectl get serviceaccount --all-namespaces
如果Pod中没有显式指定spec.serviceAccount字段值,那么Kubernetes会将该namespace下的”default” service account自动mount到在这个namespace中创建的Pod里。
2、查看pod的service account资源,
当用户在namespace下创建pod时会默认使用默认的service account资源,。
我们以namespace “default”为例,我们查看一下其中的一个Pod的信息:
kubectl describe pod/webapp-nl754
可以看到,secret:default-token-k7jfg就是我们default这个serviceaccount下的secret。它被装载到容器内部mountPath: /var/run/secrets/kubernetes.io/serviceaccount
目录中
Pod创建成功后,可以查询Pod的容器挂载/var/run/secrets/kubernetes.io/serviceaccount,实际上这个目录是ServiceAccount的Secret,里面包含了一个token,应用通过使用这个token便可以去访问Kubernetes API:
# kubectl exec webapp-62xws ls /var/run/secrets/kubernetes.io/serviceaccount namespace token
3、查看pod的token的内容:
# kubectl exec webapp-62xws cat /var/run/secrets/kubernetes.io/serviceaccount/token
也可以深入容器内部,查看mount的serviceaccount路径下的结构:
# docker exec 3d11ee06e0f8 ls /var/run/secrets/kubernetes.io/serviceaccount
namespace
token
这token文件与上面提到的service account的token中的数据是一一对应的。
1、开启ServiceAccount
kubernetes apiserver开启了ServiceAccount(–admission_control=…,ServiceAccount,… )那么会在每个namespace下面都会创建一个默认的default的ServiceAccount。即service account作为一种resource存在于Kubernetes cluster中。
每个ServiceAccount下面都会拥有一个加密过的secret作为Token. 该token也叫service-account-token
2、apiserver启动的时候,每个ServiceAccount自动生成token。
该token是APIServer在创建service account时用API server启动参数:–service-account-key-file的值签署(sign)生成的。如果–service-account-key-file未传入任何值,那么将默认使用–tls-private-key-file的值,即API Server的私钥(server.key)。
3、pod发起请求http请求携带signed bearer token
在HTTP Header中传递了一个Token字符串,这类似于之前提到的HTTP Token认证方式
类似这样:curl -k -H 'Authorization: Bearer token' https://192.168.10.50:6443/api
即pod的Token是在指定路径下的一个文件(/run/secrets/kubernetes.io/serviceaccount/token),这token是动态生成的,确切的说,是由KubernetesController进程用API Server的私钥(--service-account-private-key-file指定的私钥)签名生成的一个JWT Secret。
4、apiserver验证token
apiserver的authenticating环节支持多种身份校验方式:client cert、bearer token、static password auth等。
apiserver发现client发起的request使用的是service account token的方式,apiserver就会自动采用signed bearer token方式进行身份校验。而request就会使用携带的service account token参与验证。
apiserver收到这个Token以后,采用自己的私钥(实际是使用参数service-account-key-file)指定的私钥,如果此参数没有设置,则默认采用tls-private-key-file指定的参数,即自己的私钥,对token进行合法性验证。
5、通过authenticating后
API Server将根据Pod username所在的group:system:serviceaccounts和system:serviceaccounts:(NAMESPACE)的权限对其进行authority 和admission control两个环节的处理。在这两个环节中,cluster管理员可以对service account的权限进行细化设置。
Kubernetes提供了Secret来处理敏感信息,目前Secret的类型有3种:
开发者可以任意定义Secret的格式和内容,现在创建一个假设Opaque Secret,比如应用要使用账号密码:
username: value-1
password: value-2
首先创建Secret, secret.yaml:
apiVersion: v1
kind: Secret
metadata:
name: mysecret
type: Opaque
data:
password: dmFsdWUtMg0K
username: dmFsdWUtMQ0K
注意:其中password和username的值是通过base64 加密。
$ kubectl create -f secret.yaml
$ kubectl describe secrets mysecret
Name: mysecret
Namespace: default
Labels: <none>
Annotations: <none>
Type: Opaque
Data
password: 9 bytes username: 9 bytes 现在创建一个Pod使用该Secret red-pod.json:
{
"apiVersion": "v1",
"kind": "Pod",
"metadata": {
"name": "mypod"
},
"spec": {
"containers": [{
"name": "mypod",
"image": "redis",
"volumeMounts": [{
"name": "foo",
"mountPath": "/etc/foo",
"readOnly": true
}]
}],
"volumes": [{
"name": "foo",
"secret": {
"secretName": "mysecret"
}
}]
}
}
这里将Secret作为一个Volume挂载到Po中容器的/etc/foo下,实际上Secret中的值都会以文件生成到/etc/foo下(文件名是key,文件内容是value),待Pod运行后查看:
$ kubectl exec mypod ls /etc/foo
password
username
$ kubectl exec mypod cat /etc/foo/password
value-2
$ kubectl exec mypod cat /etc/foo/username
value-1
Secret用于 Docker Registry的安全认证,参考: https://github.com/GoogleCloud ... a-pod
Security context是用以对容器进行限制,使得不同的运行容器之前能够实现较为明晰的隔离,以及降低其影响宿主机和其它容器的可能性。通俗而言,容器中的security context用于表征在创建及运行容器时,它能够使用及访问的资源参数。 securityContext目前只实现了capabilities和privileged , etcd-discovery-controller.yaml:
kind: ReplicationController
apiVersion: v1
metadata:
name: etcd-discovery
creationTimestamp:
spec:
strategy:
type: Recreate
resources: {}
triggers:
- type: ConfigChange
replicas: 1
selector:
name: etcd-discovery
template:
metadata:
creationTimestamp:
labels:
name: etcd-discovery
spec:
containers:
- name: discovery
image: openshift/etcd-20-centos7
args:
- etcd-discovery.sh
ports:
- containerPort: 2379
protocol: TCP
resources: {}
terminationMessagePath: "/dev/termination-log"
imagePullPolicy: IfNotPresent
capabilities: {}
securityContext:
capabilities: {}
privileged: false
restartPolicy: Always
dnsPolicy: ClusterFirst
serviceAccount: ''
status: {}
kube-controller-manager日志出现:
E0711 18:59:01.092033 18548 leaderelection.go:234] error retrieving resource lock kube-system/kube-controller-manager: Get https://k8s-master:6443/api/v1/namespaces/kube-system/endpoints/kube-controller-manager: x509: certificate is valid for kubernetes, kubernetes.default, kubernetes.default.svc, kubernetes.default.svc.cluster.local, not k8s-master
说明masterssl.cnf没有指定域名k8s-master
Kubernetes 解决/var/run/secret/kubernetes.io/serviceaccount/token no such file or directory问题
kubectl get serviceaccount
NAME SECRETS
default 0
如果没有则需要添加
在apiserver的启动参数中添加:
--admission_control=ServiceAccount
apiserver在启动的时候会自己创建一个key和crt(见/var/run/kubernetes/apiserver.crt和apiserver.key)
然后在启动./kube-controller-manager 时添加flag:
--service_account_private_key_file=/var/run/kubernetes/apiserver.key
kubectl get serviceaccount
NAME SECRETS
default 1