API的访问安全性
API Server的端口和地址node
在默认状况下,API Server经过本地端口和安全端口两个不一样的HTTP端口,对外提供API服务,其中本地端口是基于HTTP协议的,用于在本机(API Server所在主机)无限制的访问API Server,而安全端口则是基于HTTPS协议的,用于远程有限制的访问API Server,下面就这两种端口作详细的介绍。mysql
本地端口(Localhost Port)nginx
在API Server的默认配置中,本地端口默认绑定到地址127.0.0.1上,因此,在默认状况下,本地端口只能在本机(API Server所在主机)访问,因为API Server不对任何经过本地端口的访问作任何权限控制(经过本地端口的访问绕过了认证和受权过程),换句话说,只要可以访问本地端口,任何人均可以经过本地端口无限制的访问API Server,基于安全性方面的考虑,在配置API Server时,尽可能不要将本地端口绑定到地址127.0.0.1之外的地址上,以免将本地端口暴露到本机之外。web
本地端口默认绑定到端口8080上,本地端口的绑定端口号能够经过API Server的启动参数--insecure-port来进行指定,若是将绑定的端口号指定为0,则表示关闭本地端口。另外,能够经过API Server的启动参数--insecure-bind-address来指定本地端口的绑定地址。sql
注意:在生产环境中尽可能避免将本地端口绑定到127.0.0.1之外的地址,以免带来没必要要的安全问题。docker
安全端口(Secure Port)数据库
顾名思义,安全端口是API Server对外提供的,用于外部访问的、安全的、可控的API调用接口,API Server只容许经过认证(Authentication)的用户才可以经过安全端口访问API Server,不管是认证为User Account或者Service Account均可以正常的经过安全端口访问API Server,可是对于还没有认证的匿名用户,当经过安全端口访问API Server时,服务端老是返回401(Unauthorized),拒绝其后续访问。json
安全端口默认绑定到端口6443上,而且老是经过HTTPS协议对外提供服务。安全端口绑定的端口号能够经过API Server的启动参数--secure-port来进行指定,和本地端口的配置相似,若是将安全端口绑定的端口号指定为0,则表示关闭安全端口。安全端口默认绑定到地址0.0.0.0上(理论上应该是0.0.0.0/32,表示本机的全部源地址),固然也能够经过API Server的启动参数--bind-address来进行显示指定。api
因为安全端口是基于HTTPS协议对外提供服务的,当未显示指定HTTPS证书和私钥的状况下,API Server会自动在主机路径/var/run/kubernetes下生成用于HTTPS的自签名证书和私钥(版本1.2的Kubernetes生成的自签名证书和私钥文件分别为:apiserver.crt和apiserver.key),固然,若是但愿使用指定的证书和私钥,则能够经过API Server的启动参数--tls-cert-file和--tls-private-key-file来分别指定。安全
代理和防火墙
在实际的生产环境中,可能存在现有的认证体系没法与Kubernetes集群集成或者须要执行特殊认证和受权逻辑的状况,在这种状况下,能够考虑引入代理(Proxy)来解决认证和受权的问题,在认证经过以后,代理将请求转发到API Server。根据,代理可否与API Server部署在同一台主机的不一样,须要分为如下两种状况进行分别讨论。
代理与API Server可以部署在同一台主机
当代理可以与API Server部署在同一台主机时,建议按照下面的方式进行集成:
关闭安全端口(将安全端口绑定到端口号0),确保全部的API请求只能经过代理接入
将本地端口绑定到地址127.0.0.1上,确保只能在本机访问
设置防火墙规则,仅开放本机的443端口
配置nginx监听443端口,而且在此端口上配置认证和HTTPS
配置nginx将请求转发到本地端口,默认状况下为127.0.0.1:8080
代理与API Server没法部署在同一台主机
当代理没法与API Server部署在同一台主机时,从安全性的角度来看,再试图经过本地端口与API Server来集成将不会是一个好的选择,在这种状况下,使用安全端口与API Server集成成立惟一的选择。因为安全接口的认证和受权体系比较复杂,具体的集成方式在后续的内容中进行深刻的讨论。
帐号类型帐号
Kubernetes根据使用帐号的进程是否在Pod内部运行这个标准将帐号划分为用于提供给外部进程使用的用户帐号(User Accounts)和用于提供给内部进程使用的服务帐号(Service Accounts),这两种不一样的帐号类型。
当进程在Pod内部运行时,通常建议该进程使用服务帐号来访问API Server,而当进程运行在Pod以外甚至在Kubernetes集群以外时,则建议该进程使用用户帐号来访问API Server;固然,这个标准并非绝对的,例如,当Pod内部运行的进程使用用户帐号来访问API Server时,并不会被API Server视为不合法而拒绝访问。
用户帐号(User Accounts)
用户帐号是一个很是传统的概念,能够简单的理解为用户名和密码,当调用方经过API Server提供的认证接口传入用户名、密码经过认证以后,调用方就扮演了这个用户与API Server进行交互。与通常的用户名的概念相同,在Kubernetes中,用户名在一个集群中是全局惟一的,也就是说在同一个集群中,只容许有一个指定名称的用户帐号,而与集群中建立了多少个命名空间(Namespace)或者启动了多少个API Server无关。
此外,用户帐号能够从数据库等第三方系统同步到进来,以实现与其它系统共享用户帐号信息。
服务帐号(Service Accounts)
从外在的表现来看,服务帐号与用户帐号的最大的不一样点,表如今服务帐号是命名空间惟一,而用户帐号是整个集群惟一。在Kubernetes中,每个命名空间均可以建立具备相同名称的服务帐号,在默认状况下,每个namespace在建立时,都会自动建立一个名为default的默认服务帐号,若是在API Server开启了ServiceAccount插件的状况下(经过API Server的–admission-control启动参数指定),该默认服务帐号会在Pod建立或者更新时,被自动的关联到该Pod上,而且自动的将默认服务帐号的凭证(Token)部署到Pod中全部容器文件系统的目录/var/run/secrets/kubernetes.io/serviceaccount/下。
从实质上来看,服务帐号与用户帐号并无本质上的不一样,能够认为每个服务帐号的背后都自动关联了一个隐藏的用户帐号,就以以默认服务帐号为例,假设在默认命名空间下有一个默认服务帐号(default),那么当某一个进程使用这个服务帐号访问API Server时,能够简单理解为使用名为system:serviceaccount:default:default的用户帐号来访问API Server,因此但愿控制某一个服务帐号的权限时,就能够简单的经过对名为system:serviceaccount: <命名空间> : <服务帐号名称> 的隐藏用户帐号进行权限控制就能够达到目的。
TODO 实验服务帐号是否能够采用与用户帐号相同的认证方式
认证(Authentication)
1.2版本的Kubernetes,提供了客户端证书认证、Token认证、OpenID认证、HTTP基本认证以及Keystone认证等五种不一样的认证方式,下面将会就这些认证方式进行详细的介绍。
须要注意的是,这五种认证方式之间不是互斥的,同一个API Server容许同时开启一种或者多种不一样的认证方式,并不会存在开启客户端证书认证而Token认证自动失效的状况,在开启多种认证的状况下,客户端能够自由的选择合适的认证方式来进行认证。
例如,假设服务器同时开启了客户端证书认证和Token认证,客户端能够仅仅传入合法的Token访问,也能够传入合法的证书私钥对访问,也能够同时传入Token和证书私钥对进行访问,当客户端同时使用多种认证方式同时认证时,只要一种认证方式经过认证,就能够继续访问,若是全部的认证方式都没法经过认证,则服务端会拒绝客户端继续访问。
TODO确认认证的优先级别
客户端证书认证(Client certificate authentication)
客户端认证的开启很是的简单,只须要经过API Server的启动参数--client-ca-file指定用于客户端认证的证书文件便可(注意:证书文件中能够包含一个或者多个证书)。
当客户端经过客户端证书认证后,用于认证证书的公用名(Common name of the subject)将做为用于后续访问的用户名。因此,当但愿对于客户端证书认证用户进行权限控制时,对名为证书公用名的用户进行受权就是对客户端证书认证用户进行受权。
如下就以自签名证书为例,演示如何配置API Server的客户端证书认证:
建立自签名证书
可使用以下的命令建立一个用于客户端认证的证书
openssl req -new -nodes
-x509 -subj "/C=CN/ST=GuangDong/L=ShenZhen/O=HuaWei/OU=PaaS/CN=batman" -days 3650 -keyout 私钥.key -out 证书.crt
说明:
/C 表示国家只能为两个字母的国家缩写,例如CN,US等
/ST 表示州或者省份
/L 表示城市或者地区
/O 表示组织机构名称
/OU 表示组织机构内的部门或者项目名称
/CN 表示公用名,若是用来做为SSL证书则应该填入域名或者子域名,
若是做为客户端认证证书则能够填入指望的用户名
为API Server指定要应用的客户端认证证书 将上一步建立的证书文件拷贝到API Server所在的主机,而后经过启动参数--client-ca-file将证书文件的路径传递给API Server。
验证客户端认证证书
可使用以下命令来验证客户端认证是否起效:
kubectl --server=https://192.168.0.1:6443 --insecure-skip-tls-verify=true --client-certificate=证书.crt --client-key=私钥.key get nodes
说明:
--server 用来指定API Server的地址,注意必定要使用安全端口
--insecure-skip-tls-verify 表示不验证证书,当服务端证书为自签名证书时指定
--client-certificate 指定客户端认证证书
--client-key 指定客户端认证证书的私钥
Token认证(Token File)
Token认证的开启也一样很是简单,只须要经过API Server的启动参数--token-auth-file指定包含Token信息的Token文件便可。Token文件是一个3到4列的csv文件,这个csv文件中,从左到右分别为Token、用户名(User Name)、用户UID(User UID)以及用户所属的组,其中前3列为必须列,用户组列为可选列,若是用户隶属于多个组,则须要将全部的组名经过双引号括起来:
token,user name,uid,"group1,group2,grooup3"
须要注意的是,Token认证没有过时的概念,全部的Token理论上能够认为永不过时,另外,除非重启API Server,不然没法更新或者删除Token。
如下演示如何配置Token认证:
建立Token文件 咱们经过手工建立内容以下的Token文件:
demo,demo,demo,demo
为API Server指定要应用的Token文件 将上一步建立的Token文件拷贝到API Server所在的主机,而后经过启动参数--token-auth-file将Token文件的路径传递给API Server。
验证Token认证
可使用以下命令来验证Token认证是否起效:
kubectl --server=https://192.168.0.1:6443 --insecure-skip-tls-verify=true --token=demo
get nodes
或者
curl -k -H "Authorization: Bearer demo" https://192.168.0.1:6443/api/v1/nodes
OpenID认证(OpenID Connect ID Token)
OpenID认证的开启相对比较复杂,开启OpenID认证须要设置以下几项启动参数:
–oidc-issuer-url(必须指定)
用于指定用于提供OpenID认证服务的服务地址。注意:服务地址必须为HTTPS的URL。
–oidc-client-id (必须指定) 用于指定 TODO
HTTP基本认证(HTTP Basic Authentication)
HTTP基本认证的开启也一样很是简单,只须要经过API Server的启动参数--basic-auth-file指定包含用户信息的用户配置文件便可。用户配置文件是一个3列的csv文件,这个csv文件中,从左到右分别为Token、用户名(User Name)、用户ID(User ID):
password,user name,user id
须要注意的是,HTTP基本认证和Toke认证同样没有过时的概念,全部只有重启API Server才能更新或者删除用户信息。此外,HTTP基本认证是做为便利性方面的考虑才加以支持的,在正式生产环境中应该优先考虑上述的几种认证方式。
如下演示如何配置HTTP基本认证:
建立用户配置文件 咱们经过手工建立内容以下的用户配置文件:
password,zhangsan,zhangsan
为API Server指定要应用的HTTP基本认证用户配置文件 将上一步建立的用户配置文件拷贝到API Server所在的主机,而后经过启动参数--basic-auth-file将用户配置文件的路径传递给API Server。
验证HTTP基本认证
可使用以下命令来验证HTTP基本认证是否起效:
kubectl --server=https://192.168.0.1:6443 --insecure-skip-tls-verify=true --username=zhangsan
--password=password
get nodes
或者
curl -k -u zhangsan:password https://192.168.0.1:6443/api/v1/nodes
Keystone认证(Keystone Authentication)
Keystone认证的开启很是简单,只须要经过API Server的启动参数--experimental-keystone-url指定Keystone服务提供的认证地址便可。因为目前版本的(版本1.2)Kubernetes对Keystone认证的支持还处于试验状态,在这里就不进行详细的介绍了,详细的信息能够参考Keystone官方文档。
Kubeconfig文件
在测试环境中,Slave(Kubelet)通常经过本地端口与API Server集成,可是在正式生产环境中,基于安全性方面的考虑,通常都会选择关闭API Server的本地端口或者只容许在API Server所在主机上访问本地端口,在这种状况下Slave只能经过安全端口与API Server集成。
为了可以经过安全端口与API Server集成,Kubelet提供了--client-certificate、-client-key、--username、--password以及--token等启动参数来支持上述认证方式,经过这些启动参数,Kubelet能够选择一种当前API Server提供的认证方式经过安全端口与API Server集成。
虽然上述的方式可以实现Kubelet与API Server的集成,可是配置上稍显复杂,须要在Kubelet的启动参数中指定不少的认证信息。为了简化配置以及方便在多个集群之间进行切换,Kubelet支持一种名为kubeconfig的机制,能够将集群信息、认证信息等配置信息保存到一个或者多个YAML格式的配置文件中(默认配置文件的路径为/var/lib/kubelet/kubeconfig),具体的信息能够参看Kubeconfig。 在配置合理的状况,能够不须要指定Kubelet的任何启动参数,Kubelet就能够顺利的加入到集群中。
如下为一个配置文件的示例:
apiVersion: v1
kind: Config
clusters:
#集群配置信息,能够经过--cluster参数指定使用
name: kubelet
user:
#如下认证任选一种
token: Token认证
username: HTTP基本认证用户名
password: HTTP基本认证密码
client-certificate: 客户端认证证书
client-key: 客户端认证私钥
current-context: service-account-context
受权(Authorization)
在Kubernetes中,受权和认证是两个相互相对独立的过程,当客户端经过安全端口访问API Server时,API Server会对客户端发起的请求进行认证,若是请求没法经过认证,哪怕后续的受权过程不对请求作任何限制(AlwaysAllow),该请求任然会被API Server拒绝,只有当请求经过认证以后,才会轮到受权插件来对请求进行权限校验。
Kubernetes的受权是经过插件的方式来实现的,,目前Kubernetes内置提供了AlwaysDeny、AlwaysAllow、ABAC以及WebHook等四种不一样的受权插件,用户能够经过赋予API Server启动参数--authorization-mode受权插件的名称来指定但愿启用的受权模式,下面,就这些受权模式作进一步的详解介绍。
AlwaysDeny
顾名思义,当API Server的受权模式设置为AlwaysDeny模式时,服务端将会拒绝任何对安全端口的请求,之前面介绍的Token认证的例子为例,当服务端的受权模式设置为AlwaysDeny时,再使用命令curl -k --H "Authorization: Bearer demo" https://192.168.0.1:6443/api/v1/nodes 访问服务端时,服务端老是返回Forbidden: "/api/v1/nodes",表示访问被拒绝。
AlwaysDeny模式主要用于测试,固然也能够用来暂时中止集群的对外服务。
AlwaysAllow
与AlwaysDeny模式相反,当API Server的受权模式设置为AlwaysAllow模式时,只要经过认证,服务端将会接受任何对安全端口的请求,换句话说就是除了认证没有任何权限限制。
当集群不须要受权时,则能够考虑将受权模式设置为AlwaysAllow模式,以下降配置的复杂性。
ABAC(基于属性的访问控制)
ABAC是英文Attribute-based access control的缩写,ABAC的核心是根据请求的相关属性,例如用户属性、资源属性以及环境属性等属性,做为受权的基础来进行访问控制,以解决分布式系统的可信任关系的访问控制问题。
基于身份的访问控制(Identity-based access control)和基于角色的访问控制(Role-based access control)均可以认为是ABAC的一个单属性特例。
目前,Kubernetes主要根据请求的如下几个属性进行受权:
用户名
用户组
是否访问资源
请求的地址
是否访问杂项接口(Miscellaneous Endpoints)
对资源的请求动做类型(Request Verb)
对非资源的HTTP动做类型(HTTP Verb)
访问的资源类型
访问对象所属的命名空间(Namespace)
访问的API的所属API组(API Grooup)
若是须要启用ABAC受权模式,首先须要经过将API Server的启动参数--authorization-mode设置为ABAC将受权模式设置为ABAC,而后经过API Server的启动参数--authorization-policy-file将 ABAC的策略文件路径传递给API Server。
ABAC的策略文件是一个one JSON object per line格式的文本文件,下面就是一个策略文件的例子:
{ "apiVersion": "abac.authorization.kubernetes.io/v1beta1","kind": "Policy","spec": { "user": "","nonResourcePath": "","readonly": true } }
{ "apiVersion": "abac.authorization.kubernetes.io/v1beta1","kind": "Policy","spec": { "user": "admin","namespace": "","resource": "","apiGroup": "" } }
{ "apiVersion": "abac.authorization.kubernetes.io/v1beta1","kind": "Policy","spec": { "user": "scheduler","namespace": "","resource": "pods","readonly": true } }
{ "apiVersion": "abac.authorization.kubernetes.io/v1beta1","kind": "Policy","spec": { "user": "scheduler","namespace": "","resource": "bindings" } }
{ "apiVersion": "abac.authorization.kubernetes.io/v1beta1","kind": "Policy","spec": { "user": "kubelet","namespace": "","resource": "pods","readonly": true } }
{ "apiVersion": "abac.authorization.kubernetes.io/v1beta1","kind": "Policy","spec": { "user": "kubelet","namespace": "","resource": "services","readonly": true } }
{ "apiVersion": "abac.authorization.kubernetes.io/v1beta1","kind": "Policy","spec": { "user": "kubelet","namespace": "","resource": "endpoints","readonly": true } }
{ "apiVersion": "abac.authorization.kubernetes.io/v1beta1","kind": "Policy","spec": { "user": "kubelet","namespace": "","resource": "events" } }
{ "apiVersion": "abac.authorization.kubernetes.io/v1beta1","kind": "Policy","spec": { "user": "alice","namespace": "projectCaribou","resource": "","apiGroup": "" } }
{ "apiVersion": "abac.authorization.kubernetes.io/v1beta1","kind": "Policy","spec": { "user": "bob","namespace": "projectCaribou","resource": "","apiGroup": "*","readonly": true } }
ABAC的受权过程能够简单的理解为将请求属性转换为一个spec对象,而后拿到这个spec对象与策略文件中定义的spec对象进行匹配,若是这个spec对象可以与策略文件中定义的任何一条规则容许的spec对象匹配,那么受权经过;若是这个spec对象没法与任何一条规则匹配,那么受权失败。
下面是一个完整的spec对象的例子:
{
"apiVersion": "abac.authorization.kubernetes.io/v1beta1",
"kind": "Policy",
"spec":
{
"user": "用户名",
"group": "用户组",
"readonly": "是否只读",
"apiGroup": "访问的API所属的API组",
"namespace": "访问对象的所属命名空间",
"resource": “访问的资源类型”,
"nonResourcePath": "访问的非资源路径"
}
}
假设只容许名为bob的用户读取命名空间projectCaribou下的Pod信息,则能够建立以下规则:
{
"apiVersion": "abac.authorization.kubernetes.io/v1beta1",
"kind": "Policy",
"spec":
{
"user": "bob",
"namespace": "projectCaribou",
"resource": "pods",
"apiGroup": "*",
"readonly": true
}
}
如下面的策略配置为例:
{"apiVersion": "abac.authorization.kubernetes.io/v1beta1", "kind": "Policy", "spec": {"user":"zhangsan", "namespace": "","resource": "pods","apiGroup": "","readonly": true }}
{"apiVersion": "abac.authorization.kubernetes.io/v1beta1", "kind": "Policy", "spec": {"user":"admin", "namespace": "","resource": "","apiGroup": "","readonly": true, "nonResourcePath": "" }}
{"apiVersion": "abac.authorization.kubernetes.io/v1beta1", "kind": "Policy", "spec": {"user":"lisi", "namespace": "","resource": "nodes","apiGroup": "","readonly": true }}
至关于定义了以下规则:
用户 能力
zhangsan 容许读取Pod信息
admin 容许读取全部资源信息
lisi 容许读取Node信息
请求在转换为spec对象的过程当中,若是某一个属性请求具有多值,例如group属性,那么能够理解为将请求转换为多个spec对象,每个对象持有多值属性中的一个值,而后这些对象分别于策略文件进行匹配,只要任何一个对象匹配经过,则请求受权经过。对于请求没法提供的属性,例如group属性,那么在转换为spec对象的过程当中该属性被设置为该属性声明类型的默认值,例如,字符串类型的属性设置为空字符串,而整数型的属性设置为0。
规则文件中,可使用来进行通配,例如,在规则中出现以下定义"user": "",则表示该规则匹配任何用户,也就是说任何用户的请求,在用户名这一项上该规则都匹配。须要注意的是,缺省不表明匹配任何项,当某一个属性缺省时,能够理解为该属性设置为默认值。
在上面的介绍中,有人可能会有疑惑:为何要将请求路径划分为资源和非资源路径两种不一样的属性?缘由是:Kubernetes实现了一种名API Group的特性,因为这种特性致使了同一种资源可能有N个入口能够访问,以Pod为例,用户可能经过地址https://192.168.0.1:6443/api/v1/pods访问,也能够经过地址https://192.168.0.1:6443/api/v1/namespaces/default/pods访问,若是依靠请求地址来控制资源的访问的话,会致使规范定义的极速膨胀;此外,一旦增长了新的API组,又会致使同一个资源的访问增长大量的请求地址,因此从易用性以及性能等方面的考虑,对于资源直接使用资源类型进行控制是相对较好的一种方案。
因为非资源的请求地址相对固定,不会存在这个问题,并且又没有关联的可供识别的对象,因此对于非资源请求使用访问地址来作限制就是一种显而易见的选择了。
WebHook
WebHook模式是一种扩展受权模式,在这种模式下,API Server将受权过程委派到外部的一个REST服务,由外部的服务决定是否授予指定请求继续访问的权限。
WebHook模式的开启很是的简单,只须要经过API Server的启动参数--authorization-mode设置为WebHook而且经过启动参数--authorization-webhook-config-file将外部受权服务的配置信息告诉API Server便可。
因为WebHook模式的受权策略彻底由外部受权服务来决定,在这里就不进行详解的介绍,具体的信息能够参看Kubernetes官方文档。
自定义插件
此外,Kubernetes也支持经过开发新的插件的方式支持新的受权模式,插件的开发很是简单,只须要实现以下接口便可,在这里就不作展开讨论:
type Authorizer interface {
Authorize(a Attributes) error
}
如何识别各类认证方式的用户名和用户组
TODO 须要进一步验证
认证方式 用户名 用户组
客户端证书认证 证书的公共名 无
Token认证 Token文件中指定的用户名 Token文件中指定的用户组
HTTP基本认证 配置文件中指定的用户名 无
OpenID认证 经过启动参数--oidc-username-claim指定 经过启动参数--oidc-groups-claim指定
Secret
在实际的生产环境中,在大多数状况下,容器都不是孤立存在的,通常都须要与其它服务或者系统进行通信或者集成,而其它服务或者系统通常都须要调用者提供密码、认证Token以及SSH秘钥等信息来确保信息安全。
在常规的容器化实践中,通常采用环境变量、命令行参数、挂载文件甚至直接Build到镜像中等方式将这些敏感信息传递到容器中,以达到容器可以在运行中得到这些敏感信息的目的。然而,上述的方式存在容易泄露、难以变动以及维护困难等问题,为了解决这些问题,在Kubernetes中引入了秘密(Secret)的概念。
在Kubernetes中,秘密能够简单的理解为一个命名对象,在这个对象中保存了特定的敏感信息,用户能够简单的经过Pod定义文件、Service Account甚至在运行中动态获取等方式,在容器得到秘密中保存的敏感信息。此外,经过Pod定义文件、服务帐号等静态方式挂接在Pod上的秘密,在Pod没有启动以前,任何对秘密的更改都会在Pod启动以后直接反应到Pod中,而在Pod启动以后的更改,则须要从新启动Pod。
目前,Kubernetes提供了如下三种不一样的秘密:
不透明秘密(Opaque Secret)
不透明秘密能够简单的理解为能够随便听任何数据的字典,Kubernetes只是简单的将秘密中包含的数据传递到包含在Pod中的容器,具体的内容只有提供方和使用方可以理解。须要注意的是,单个秘密的大小上限是1MB,若是但愿传递更多的内容,能够考虑将内容拆分到多个小的秘密中。
API Token Secret
API Token通常与服务帐号配对使用,经过准入控制(Admission Control)提供的Service Account插件自动的将API Token挂载到容器中(默认挂载到容器的/var/run/secrets/kubernetes.io/serviceaccount/路径下),以实如今容器中可以有权限访问API Server。固然,在不使用准入控制的状况下,也能够采用与其它秘密相同的方式挂载到容器中。
imagePullSecret
imagePullSecret用来保存镜像仓库的认证信息,以方便Kubelet在启动Pod时,可以得到镜像仓库的认证信息,确保能Kubelet够有权限从镜像仓库中下载Pod所需的镜像。
此外,为了确保镜像的安全以及保证只有受权的用户才能给使用特定的镜像,建议在生产环境中启用准入控制的AlwaysPullImages插件,当启用这个插件时,将无视Pod定义中的镜像下载策略(imagePullPolicy),强制Kubelet老是从镜像仓库中下载镜像,而不使用本地镜像,从效果上看至关于将Pod定义中的镜像下载策略设置为Always。
Opaque Secret
建立
经过命令行建立(Kubernetes 1.2新增长的特性)
假设须要将如下MySQL的链接信息经过秘密传入到容器中:
db-user-name:mysql
db-user-pass:password
db-address:192.168.0.1:3306
db-name:database
能够采用下面的命令建立秘密:
经过文件建立
echo "mysql" > ./username.txt
echo "password" > ./password.txt
echo "192.168.0.1:3306" > ./address.txt
echo "database" > ./name.txt
./kubectl create secret generic mysql-database-secret --from-file=db-user-name=./username.txt --from-file=db-user-pass=./password.txt --from-file=db-address=./address.txt --from-file=db-name=./name.txt
也能够经过字面参数直接建立
./kubectl create secret generic mysql-database-secret --from-literal=db-user-name=mysql --from-literal=db-user-pass=password --from-literal=db-address=192.168.0.1:3306 --from-literal=db-name=database
若是建立成功,则可使用命令./kubectl describe secret mysql-database-secret查看建立的秘密:
Name: mysql-database-secret
Namespace: default
Labels:
Annotations:
Type: Opaque
db-name: 9 bytes
db-user-name: 6 bytes
db-user-pass: 9 bytes
db-address: 17 bytes
经过定义文件建立
建立以下内容的YAML文件,而后使用命名./kubectl create -f 文件路径便可建立秘密,其中的数据内容是各项数据的Base64编码,能够简单的利用以下命令echo 内容 | Base64,生成指定内容的Base64编码。
apiVersion: v1
data:
db-address: MTkyLjE2OC4wLjE6MzMwNg==
db-name: ZGF0YWJhc2U=
db-user-name: bXlzcWw=
db-user-pass: cGFzc3dvcmQ=
kind: Secret
metadata:
name: mysql-database-secret
namespace: default
type: Opaque
更新
相对于建立而言,更新只能经过文件来实现了,简单的方式是首先使用以下的命名导出秘密定义:
./kubectr get secret mysql-database-secret -o yaml > mysql-database-secret.yaml
或者
./kubectr get secret mysql-database-secret -o json > mysql-database-secret.json
而后在更新文件内容以后,再使用以下命令更新秘密:
./kubectl replace -f mysql-database-secret.yaml
或者
./kubectl replace -f mysql-database-secret.json
使用
挂载为文件
针对上一步建立的秘密,能够经过以下的定义直接挂载到容器的文件系统中:
apiVersion: v1
kind: Pod
metadata:
name: demo
spec:
containers:
- name: demo
imagePullPolicy: IfNotPresent
image: image
volumeMounts:
- name: mysql
mountPath: /etc/mysql
readOnly: true
volumes:
- name: mysql
secret:
secretName: mysql-database-secret
挂载成功以后能够,在容器的文件系统中看到秘密的内容:
docker exec -it containerId ls /etc/mysql
db-address db-name db-user-name db-user-pass
docker exec -it containerId cat /etc/mysql/db-user-pass
password
挂载为环境变量
能够采用下面的定义直接将秘密挂载为环境变量:
apiVersion: v1
kind: Pod
metadata:
name: demo
spec:
containers:
- name: demo
imagePullPolicy: IfNotPresent
image: image
env:
- name: SECRET_USERNAME
valueFrom:
secretKeyRef:
name: mysql-database-secret
key: db-user-name
- name: SECRET_PASSWORD
valueFrom:
secretKeyRef:
name: mysql-database-secret
key: db-user-pass
- name: SECRET_NAME
valueFrom:
secretKeyRef:
name: mysql-database-secret
key: db-name
- name: SECRET_ADDRESS
valueFrom:
secretKeyRef:
name: mysql-database-secret
key: db-address
而后使用以下命令,就能够看到秘密的内容已经挂载为环境变量:
docker exec -it containerId env
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
HOSTNAME=nginx
SECRET_USERNAME=mysql
SECRET_PASSWORD=password
SECRET_NAME=database
SECRET_ADDRESS=192.168.0.1:3306
KUBERNETES_SERVICE_PORT_HTTPS=443
KUBERNETES_PORT=tcp://172.0.0.1:443
KUBERNETES_PORT_443_TCP=tcp://172.0.0.1:443
KUBERNETES_PORT_443_TCP_PROTO=tcp
KUBERNETES_PORT_443_TCP_PORT=443
KUBERNETES_PORT_443_TCP_ADDR=172.0.0.1
KUBERNETES_SERVICE_HOST=172.0.0.1
KUBERNETES_SERVICE_PORT=443
...
自动挂载
目前Opaque Secret还没有实现自动挂载,也许在Kubernetes的后续版本中会提供这个功能,具体的信息能够参看Issue 9902。
imagePullSecret
建立
imagePullSecret的建立和更新方式与Opaque Secret的建立和更新方式相似,支持在建立和更新中一些参数稍有区别。
下面是一个完整的YAML格式的imagePullSecret定义文件:
apiVersion: v1 data: .dockercfg: eyJET0NLRVJfUkVHSVNUUllfU0VSVkVSIjp7InVzZXJuYW1lIjoiRE9DS0VSX1VTRVIiLCJwYXNzd29yZCI6IkRPQ0tFUl9QQVNTV09SRCIsImVtYWlsIjoiRE9DS0VSX0VNQUlMIiwiYXV0aCI6IlJFOURTMFZTWDFWVFJWSTZSRTlEUzBWU1gxQkJVMU5YVDFKRSJ9fQ== kind: Secret metadata: name: local-registry-secret namespace: default type: kubernetes.io/dockercfg
上面的定义文件中有两点须要注意:
Secret的类型
imagePullSecret的类型为kubernetes.io/dockercfg
Secret的数据
imagePullSecret中只包含一个名为.dockercfg的数据,注意,这个名称是固定的,而具体的内容是如下内容的Base64编码:
{
"DOCKER_REGISTRY_SERVER":
{
"username":"用户名",
"password":"密码",
"email":"邮件地址",
"auth":"RE9DS0VSX1VTRVI6RE9DS0VSX1BBU1NXT1JE"
}
}
其中auth属性是必须的,明文部分除了邮件地址之外,用户名和密码这两个属性均可以不要,而auth属性的值就是用户名:密码的简单Base64编码,可使用以下命令简单生成:
echo 用户名:密码 | base64
如下是最小内容的示例:
{
"DOCKER_REGISTRY_SERVER":
{
"username":"用户名",
"password":"密码",
"email":"邮件地址",
"auth":"RE9DS0VSX1VTRVI6RE9DS0VSX1BBU1NXT1JE"
}
}
此外,还能够经过Docker提供的login命令生成imagePullSecret的内容,如下为经过Docker生成的示例命令:
docker login -u 用户名 -p 密码 -e 邮件地址 镜像库地址
命令的执行结果会写入到以下路径:
~/.docker/config.json
最后经过以下命令就能够简单生成imagePullSecret的内容了:
cat ~/.docker/config.json | base64
此外,还能够经过kubectl命令来生成imagePullSecret,下面是命令的示例:
./kubectl create secret docker-registry 镜像下载秘密名称 --docker-server=镜像库地址 --docker-username=用户名 --docker-password=密码 --docker-email=邮件地址 -s API Server地址
使用
imagePullSecret的使用方式与Opaque Secret的挂载方式不一样,因为imagePullSecret用于提供给kubectl来下载镜像,而不须要挂载到容器中,因此对于imagePullSecret而言,只须要在Pod定义中声明使用便可。在Pod能够声明多个imagePullSecret,使得Kubelet能够从多个不一样的镜像仓库中下载镜像,当kubectl下载镜像时,会根据镜像仓库的不一样选择合适的imagePullSecret去执行镜像下载操做。
目前,主要有如下两种方式将imagePullSecret绑定到Pod上:
在Pod中直接定义
能够在Pod定义中,直接声明须要绑定的imagePullSecret,如下为Pod中绑定imagePullSecret定义文件的示例:
apiVersion: v1
kind: Pod
metadata:
name: foo
namespace: awesomeapps
spec:
containers:
- name: foo
image: janedoe/awesomeapp:v1
imagePullSecrets:
- name: 秘密名称
- name: 秘密名称
- ...
在服务帐号中定义
能够在服务帐号中,声明须要绑定到服务帐号的imagePullSecret,当服务帐号被隐式或者显式的绑定到Pod上时,服务帐号中声明的秘密,包括imagePullSecret也自动被绑定到Pod。如下为在服务帐号中绑定imagePullSecret定义文件的示例:
apiVersion: v1
kind: ServiceAccount
metadata:
name: default
namespace: default
imagePullSecrets:
- name: 秘密名称
- name: 秘密名称
- ...
在Pod中显式的声明服务帐号
apiVersion: v1
kind: Pod
metadata:
name: foo
namespace: awesomeapps
spec:
containers:
- name: foo
image: janedoe/awesomeapp:v1
serviceAccountName: 服务帐号名称
注意:秘密和服务帐号都是命名空间敏感的,因此不管在Pod中引用秘密、服务帐号或者在服务帐号中引用秘密,都只能本命名空间内的秘密和服务帐号,不可以跨命名空间引用其它命名空间的秘密和服务。
对于已经存在的服务帐号,但愿往服务帐号中添加或者删除imagePullSecret,能够按照以下步骤实现:
导出现有服务帐号的定义文件
kubectl get serviceaccount 服务帐号名称 -o 格式(json或者yaml) > 定义文件路径
更新服务定义
修改上一步导出的定义文件,在定义文件中添加或者删除imagePullSecret。
更新服务帐号
kubectl replace serviceaccount 服务帐号名称 -thef 定义文件路径
API Token秘密
API Token Secret通常用于绑定到服务帐号,用于标识服务帐号,从而实如今Pod中可以以服务帐号的身份访问API Server,具体的内容能够参看在Pod中访问API Server。
虽然,API Token Secret能够手动建立,可是大多数状况下都不须要手动建立,而是伴随服务帐号自动建立,若是确实要手动建立,则可使用下面的模板进行建立:
apiVersion: v1
kind: Secret
metadata:
name: 秘密名称
annotations:
kubernetes.io/service-account.name: 服务帐号名称
type: kubernetes.io/service-account-token
建立成功的API Token秘密能够按照普通秘密相同的方式挂载到服务帐号或者Pod,在这里就不进行详细讨论了。
服务帐号的自动化以及受权
Kubernetes内置提供一种机制,能够实现默认服务帐号的自动建立和自动挂载,对于大多数状况而言,使用这种内置机制基本上能够知足服务帐号的使用要求了。固然若是须要进一步的细化权限,则必须手动建立服务帐号手动绑定服务帐号了。
Kubernetes经过ServiceAccount插件、Token Controller以及Service Account Controller等三个组件实现服务帐号的自动化,下面就这个三个组件的分工作简要概述。
ServiceAccount插件
ServiceAccount插件运行在API Server中,经过API Server的--admission-control参数启用,当启用了ServiceAccunt插件,ServiceAccount插件将在Pod启动或者更新的过程当中执行下面的动做:
确保Pod绑定了服务帐号,若是没有显示绑定,则自动绑定到default服务帐号
确保Pod绑定的服务帐号是存在的,若是不存在,则拒绝Pod启动
若是Pod没有显示声明ImagePullSecret,则自动将服务帐号上声明的ImagePullSecret绑定到Pod
将服务帐号中绑定的API Token经过卷的方式自动加载到容器的文件系统/var/run/secrets/kubernetes.io/serviceaccount
Token Controller
Token Controller是Kubernetes Controller Manager的一个组件,用于同步服务帐号和密码,主要实现了下面的功能:
当服务帐号建立时,自动建立一个API Token秘密
当服务帐号删除时,自动删除服务帐号的全部API Token秘密
当秘密删除时,自动从服务帐号中删除引用关系
建立API Token秘密是确保服务帐号存在,而且自动添加一个用于访问API的Token
Service Account Controller
Service Account Controller用于管理命名空间中的服务帐号,而且确保每个活动的命名空间中都存在default服务帐号。
对于服务帐号的受权,在前面的章节中已近作了一些概要的介绍,从本质上来讲与用户帐号的受权是同样的,只是须要注意服务帐号的帐号名。
因为服务帐号通常用于提供给Pod来访问API Server,因此从安全性的角度来看,尽可能限制服务帐号为只读,且最好不容许跨命名空间访问(在Kubernetes中,通常采用命名空间的方式来实现多租户)。
在Pod中访问API Server
在Pod中访问API Server或者说在容器中访问API Server,存在不少种可能的方式,可是从安全性的角度而言,使用服务帐号而且只经过安全端口访问API Server是一种受控和安全的访问方式。
若是要使用服务帐号访问API Server,建议经过服务帐号自动化机制,自动的将用于访问API Server的Token挂载到容器中,在容器中就能够简单的使用Token认证来访问API Server了。
此外,也可使用kubectl的proxy命令,建立一个到API Server的代理。当启用Kubectl代理时,在代理中已经处理了服务地址以及认证信息,客户端只须要简单的访问代理提供的地址,就能够以指定的身份访问API Server了。
关于Kubectl代理的详细信息能够参考访问集群。