• 【K8s爬坑系列】之解读kubernetes的认证原理&实践


           对于访问kube-apiserver模块的请求来说,如果是使用http协议,则会顺利进入模块内部得到自己想要的;但是如果是用的是https,则能否进入模块内部获得想要的资源,他会首先要进行https自有的tls握手,进而进入kube-apiserver的三大控制,接下来,就让我一起研究下.....

    一,对Kubernetes API访问的三大控制

    • Authentication:认证,确认“你是不是你",包括多种方式,如 Client Certificates, Password, and Plain Tokens, Bootstrap Tokens, and JWT Tokens等
    • Authorization:鉴权,确认“你是不是有权利做这件事”。怎样判定是否有权利,通过配置策略
    • Admission Control:AC是以一个一个软件模块形式存在,经过配置来将这些控制模块加载到自己的系统中。这些模块可以修改和拒绝请求,具体来说就是他不仅可以针对鉴权(Authorization)部门中的属性进行控制,它还可以访问正在创建或更新的对象的内容。                                  也就是说,该部门在在对资源的created, deleted, updated or connected (proxy)起作用,但是对reads不加控制。
    •                                 水鬼子:是对已经"放行"的请求进入apiserver后开始真正操作资源的时候,对这份资源的控制。

           参考:https://kubernetes.io/docs/reference/access-authn-authz/controlling-access/

    API服务器在2个端口上都对外提供服务:

    1,Localhost Port:默认8080端口,通过这个端口的请求会绕过认证和鉴权两个控制模块,一般用于集群内部的组件之间的交互。

    2,Secure Port:默认6443端口,通过这个端口的请求会经过三大控制模块,用于对外提供服务。

    关于认证,ApiServer主要的有三种方式如下:

      1)Https双向认证,是双向认证啊,不是单向认证(最安全)。

      2)Http Token认证

      3)Http Base认证,用户名和密码

       以上的认证方式是以模块的形式存在于apiserver中,可以通过配置支持多种认证,请求只要通过一个认证就算成功。

        kubectl命令行工具既同时支持CA双向认证也支持简单认证(http base或者token)两种模式与apiserver进行通信,但其他组件只能配置成一种模式。

    这里参考了:https://blog.csdn.net/xxb249/article/details/79449434

    wxy: 这里说一点我自己的理解,https协议本身(tls)可以是单向认证也可以是双向认证,需要明确几点

           1)不要想当然认为https是天生的,那也是需要api server去实现的,只不过tls是一套共有的规范,api server组件也需要按照规范来

           2)一般单向认证是指服务端(即api server)一方准备好证书就行了,另一方客户端不需要,这种当然就很灵活,对客户端是curl还是浏览器就没什么要求了

          3)但是如果你使用的是双向认证,那就麻烦了,即客户端也需要准备证书,且这个证书还需要是api server认识的,所以这个时候就不能是任何客户端就能访问了,而是需要先了解api server的内部情况(配置的客户端根证书),再生成证书才行

           4)综述:https的单向认证对客户端没有要求,也就可以认为默认支持,或者说具有普遍意义

                         https的双向认证,以及k8s的自有认证方式(base,token方式)这三种酒称为具有k8s特色的认证方式

    二,https协议的tls握手

    只要你是通过Secure Port来方位api,那么你必然要遵守https协议要求的tls链接,这是一个怎样的交互呢?

    首先需要了解一个概念:数字证书,详细的认证原理参见:https://www.cnblogs.com/shuiguizi/p/11289354.html

    三,api server上有关证书的配置

    0, tls协议使用的证书

        因为认证部门需要证书,所以这里先单独说一下tls证书

         --tls-cert-file=/run/ssl/server.pem  
         --tls-private-key-file=/run/ssl/server-key.pem

        前面tls握手的时候有说,作为服务器端,必须要要准备好自己的证书(对),所以这两个参数就是指定了证书的路径。但是也可以不指定,缺省情况下会自动生成一对证书,因为这是https协议最基本的要求,你配不配置我都要有的。

         自动生成的证书(对)缺省位于: 容器方式安装:位于容器的/run/kubernetes目录下,
                                                          主机直接安装,位于主机的/var/run/kubernetes目录下

         当然可以用--cert-dir参数变更,以下我的环境,为容器方式安装:

    # pwd
    /run/kubernetes
    # ls
    apiserver.crt apiserver.key

    1,认证方式一:双向认证

     其实说到这里,我们一直没有提是怎么实现api server中三大控制的,接下来我们说的就是认证中的 双向认证:Client Certificates 的使能。

    --client-ca-file
    这个参数的含义是指定客户端使用的根证书的路径。一旦设置了,那么你在访问api的时候一定得带上使用该根证书签发的公钥/私钥对,例如:

    //启动时,指定客户端的根证书位于容器的/run/ssl/client目录下,对应host的/etc/kubernetes/apiserver/client目录下
    docker run -d --name=apiserver --net=host -v /etc/kubernetes/apiserver:/run/ssl k8s.gcr.io/kube-apiserver:v1.13.6-beta.0.39_ddd2add0dd3dbc kube-apiserver --insecure-bind-address=0.0.0.0 --service-cluster-ip-range=11.0.0.0/16 --client-ca-file=/run/ssl/client/ca.pem --etcd-servers=http://localhost:2379
    //那么这里配置的证书和私钥,就要和启动时配置的根证书有关了,他们正是我用cfssl工具生成的一组证书,成套的....另外 -k 表示我跳过对服务端的证书检查,详见"tls的握手"章节
    [root@master apiserver]# curl -k https://188.x.x.113:6443 --cert /etc/kubernetes/client/client.pem --key /etc/kubernetes/client/client-key.pem { "paths": [ "/api", "/api/v1" ...

    2,认证方式二:Base认证

      所谓base认证,很好理解:我先在服务器系统中写个名单,上面列一堆:用户名,密码,客户端访问的时候要带着用户名和密码,服务器一检查,是我名单里的,即通过认证,具体配置方式如下:

    --basic-auth-file
    用这个参数指定名单的路径
    #vi basic_auth_file.csv
    admin,admin,1
    wxy,wxy,2
    #docker run -d --name=apiserver --net=host 
     -v /etc/kubernetes/apiserver:/run/ssl 
     k8s.gcr.io/kube-apiserver:v1.13.6-beta.0.39_ddd2add0dd3dbc 
     kube-apiserver 
     --insecure-bind-address=0.0.0.0 
     --service-cluster-ip-range=11.0.0.0/16 
     --basic-auth-file=/run/ssl/basic_auth_file.csv 
     --etcd-servers=http://localhost:2379

    #kubectl --server="https://localhost:6443" --insecure-skip-tls-verify=true --username="admin" --password="admin" get pods

    3,认证方式三:token认证

    在下一节中有用到

    附【坑】k8s的官网上说:

    Authentication modules include Client Certificates, Password, and Plain Tokens, Bootstrap Tokens, and JWT Tokens (used for service accounts).
    Multiple authentication modules can be specified, in which case each one is tried in sequence, until one of them succeeds.

    --basic-auth-file string
    If set, the file that will be used to admit requests to the secure port of the API server via http basic authentication.

    --client-ca-file string
    If set, any request presenting a client certificate signed by one of the authorities in the client-ca-file is authenticated with an identity corresponding to the CommonName of the client certificate.


    所以我以为各种认证模块是通过配置开启的,当我没有配置client-ca-file和basic-auth-file时,我就是没有开启认证,所以我用如下命令访问不应该报错的:

     [root@master ~]# curl -k https://188.x.x.113:6443
    {
      "kind": "Status",
      "apiVersion": "v1",
      "metadata": {
        
      },
      "status": "Failure",
      "message": "Unauthorized",
      "reason": "Unauthorized",
      "code": 401
    }

    但实际的验证的结果是:你在部署你的集群是,至少满足一种认证方式。我使用的kube-apiserver的版本是kubernetes v1.13.5的自己编译。如果这里我理解有误,一定告诉我,谢谢!

    三,kube-controller-manager上有关证书的配置&service account

    前面说了,kube-apiserver对于不是集群组件,一般暴露的是Secure Port,即使用带有认证的https。那么除了自然人通过curl或者kubectl这种要带着证书或者账号密码或者token外,pod也想访问kube-apiserver怎么办?它怎么携带证书呢?

    答:集群为每个namespace指定一个 service account ,叫做服务账号。你可以把它想象成一个名叫"default"的人的账号,pod就是以这个身份去访问api的。具体实现如下:

    1,在启动kube-controller-manager时指定如下参数

    --service-account-private-key-file
    该参数表示的含义是私钥的路径,它的作用是给服务账号产生token,之后pod就可以拿着这个token去访问api server了。

    --root-ca-file
    该参数会给服务账号一个根证书ca.crt,可选配置,如果配置成给api server签发证书的那个根证书,那就可以拿来用于认证api server。

     docker run -d --name=cm --net=host 
     -v /etc/kubernetes/apiserver:/run/ssl 
     k8s.gcr.io/kube-controller-manager:v1.13.6-beta.0.39_ddd2add0dd3dbc 
     kube-controller-manager 
     --master=0.0.0.0:8080 
     --service-account-private-key-file=/run/ssl/server-key.pem 
    --root-ca-file=/run/ssl/ca.pem

    2,在启动kube-apiserver时配置

    --service-account-key-file
    该参数表示的含义是公钥的路径,它与上面的--service-account-private-key-file是对应关系,因为pod带着token去访问api server,则api server要能解密才行啊,所以同时还需要在api那里配置当然你如果不配置,不影响pod创建,只不过你在pod里访问api的时候就不行了。

     docker run -d --name=apiserver --net=host 
     -v /etc/kubernetes/apiserver:/run/ssl 
     k8s.gcr.io/kube-apiserver:v1.13.6-beta.0.39_ddd2add0dd3dbc 
     kube-apiserver 
     --insecure-bind-address=0.0.0.0 
     --service-cluster-ip-range=11.0.0.0/16 
     --service-account-key-file=/run/ssl/server.pem  
     --etcd-servers=http://localhost:2379

    3,启动后,会在各个namespace下生成一个secret

    [root@master ~]# kubectl get secret --all-namespaces
    NAMESPACE     NAME                  TYPE                                  DATA   AGE
    default       default-token-x221b   kubernetes.io/service-account-token   2      59m
    kube-public   default-token-zx79k   kubernetes.io/service-account-token   3      3d3h
    kube-system   default-token-9fbtm   kubernetes.io/service-account-token   3      3d3h 

    4,此时创建pod,可以看到pod把secret的内容挂载到了自己的/var/run/secrets/kubernetes.io/serviceaccount目录下

    [root@master ~]# kubectl create -f centos.yaml 
    pod/myapp-centos created
    [root@master ~]# kubectl get pods
    NAME           READY   STATUS    RESTARTS   AGE
    myapp-centos   1/1     Running   0          6s
    [root@master ~]# kubectl get pods myapp-centos -oyaml
        ...
        volumeMounts:
        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount
          name: default-token-x22lb
          readOnly: true
      ...
      volumes:
      - name: default-token-x22lb
        secret:
          defaultMode: 420
          secretName: default-token-x22lb
    
    [root@master ~]# kubectl exec -ti  myapp-centos /bin/sh
    sh-4.2# ls /var/run/secrets/kubernetes.io/serviceaccount

    sh-4.2# ls
    ca.crt namespace token

    5,实操验证,带着生成的token可以访问api了

    [root@master ~]# kubectl exec -ti myapp-centos /bin/sh
    sh-4.2# cd /var/run/secrets/kubernetes.io/serviceaccount/
    sh-4.2# token=$(cat ./token)
    sh-4.2# curl  https://188.131.210.113:6443 --header "Authorization: Bearer $token" --cacert /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
    {
      "paths": [
        "/api",
        "/api/v1"...

    结束:

    认证这块之前一直不敢碰,感觉好难,但是一路走下来突然发现好像也没什么,总结起来就是:

    1,https使用的tls协议,其实就是常说的非对称加密算法的应用,说到加密就得用加密物料,于是要明白数字证书的概念

    2,数字证书,根据字面意思他就是一个证书,由第三方大家都认可的机构:CA盖章颁发,证书上面有公钥和CA签名。其存在的价值是保证上面的公钥是官方有效的。

           同时CA机构还有一个根证书,也叫CA证书,用来验证数字证书的真伪。每一个人都要去CA那里把根证书提前下载到自己的系统里,以备使用。

    3,说到公钥,就不得不提非对称加密算法的原理:

          每个人都有一对钥匙:公钥和私钥,公钥加密的数据只能被私钥解密,私钥加密的数据只能被公钥解密;

          私钥自己留着,公钥用数字证书的方式可以发给任何人;

          于是,这个巧妙的原理就实现了非对称加密算法;

           水鬼子:不知道为什么,我就是特别喜欢这个原理,感觉很简单又很有巧思,像按压笔一样,我觉得真是人类伟大的发明。

    4,说了这么多都是为访问api server服务,就是说任何请求都不能随随便便请求我,总得带上你的身份证明,是我认可的才行;

         所以,api还是manage组件的各项和证书相关的配置就出现了

     -------------------------------番外篇------------------------------------------------------------------------------------------------------------------------------------------

    1,借一位网友的疑问所作的一个小结

    问题:

    1. pods的证书和controller-manager有啥关系,为啥要配置到controller-manager上
    
    2. kubernetes又是如何使用使用controller-manager上的service-account-private-key-file去生成token的
    
    3. 这个用service-account-private-key-file生成的token,apiserver又是如何验证的?

    回答:

    以上问题从根本上可以归结为一个问题:pod拿什么和apiserver通信?

    答:拿证书去通信; 证书哪里来?
      从cm那里来,cm会给创建的pod里塞进一个token文件,具体这块我没看cm和kubelet的代码,不知道是cm生成好了token交给kubelet然后kubelet塞给pod,还是kubelet自己拿着秘钥生成的,我觉得应该是前者,反正以上结论是我实验得来的,没有理论支撑,待后续...
            然后如果pod想访问apiserver,就拿着这个token去; apiserver怎么就认了?
      因为apiserver配置了service-account-key-file参数,即指定了公钥的位置,收到pod的token后就拿这个公钥去认证!

    水鬼子: 其实我觉得这个问题特别好,因为我就常常有类似的疑惑,当我们对一个知识还是似懂非懂时,看似简单的若干问题其实都能归结到一个本质!

    ----------------------------------------------------------------------------------------------------------------------------------------------------------------------

     》》》》》》》》》》》》》》》》》》》》END《《《《《《《《《《《《《《《《《《《《《《《《

  • 相关阅读:
    python字符串格式化笔记
    看球时的随笔——“如何掌握新的知识”
    str()和repre()的区别
    关于抛出异常和捕获异常
    python关键字详解
    博客的第一天
    PEP8 Python 编码规范
    SQL Server 自动循环归档分区数据脚本
    反射应用--取得类的结构
    回调函数callback使用例子
  • 原文地址:https://www.cnblogs.com/shuiguizi/p/10947495.html
Copyright © 2020-2023  润新知