【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鏈接,這是一個怎樣的交互呢?首先需要了解一個概念:數字證書,讓我們細細道來

0,數字證書以及網絡傳輸加密

     假設,有兩個人,一個叫 小起,一個叫 小終,小起想給小終發送數據,可是怕別人截取,篡改等等一些不安全的行為,於是經歷了如下的演變過程,直到最后得到了最完美的加密過程。

 stage1: 對稱加密

          小起     ----------將數據加密,並帶着能解密的鑰匙,一起交給小終   -----------> 小終

          問題:傳輸的過程中,鑰匙可能被盜

 stage2:非對稱加密

          小終:自己有一個秘鑰對,公鑰:對外公開;    私鑰:自己保留;    由公鑰加密的數據只能由私鑰解密,由私鑰加密的內容只能由公鑰解密

          小起     ----------將數據用小終的公鑰加密,交給小終   -----------> 小終(用小終自己的私鑰解密)

          問題:傳輸的過程中,有人將數據截獲,搞一堆爛七八糟的內容重新用小終的公鑰加密,再發給小終。

 stage2進階:簽名技術的應用

      利用hash算法不可逆的特性,將正文數據進行hash計算得到摘要,當小終收到正文后同樣hash計算,如果二者相同說明數據是完整的。
   同時,小起還要告訴小終,這個摘要是我計算的,可不是別人計算的的。

           小起 -----------------1)就數據用小終的公鑰加密; -------------------------------------------------------------------> 小終(用自己的公鑰解密數據,用小起的公鑰解密簽名)
                                        2)將數據用hash算法計算出摘要,並用小起的私鑰對摘要進行加密(簽名)

          問題:有人冒充是小起給小終發數據

 stage3:數字證書                  

          前面說了公鑰/私鑰都是自己家搞出來的東西,就算是被仿造了也不知道,於是將公鑰"貼在第三方出具的正式文件上"(我們所說的簽發數字證書)。這樣就顯得有保障,因為這個正式文件上可是有第三方蓋章的。但同樣還要能夠識別這個"章"是真正權威機構的。具體原理是:

          小起,小終    <-----------從ca那里獲取兩樣:根證書(“印章”圖樣),公鑰(數字證書)私鑰對---------CA中心(證書頒發機構)

          小起 (用根證書校驗小終的數字證書)   <---------交換數字證書 -----------> 小終(用根證書校驗小起的數字證書)

 整合:對稱加密結合非對稱加密

     考慮到效率問題,真正的業務數據是用對稱加密算法加密的。所以綜合以后,過程是這樣的:
     通過stage3,我們現在互相已經有了對方准確的公鑰,然后通過stage2的數據交換協商出對稱加密秘鑰自己保存好,再用stage1的對稱加密算法(此時不需要攜帶解密秘鑰)加密數據

          小起   --------- 用對稱加密算法加密的數據-----------> 小終

 

 1,tls的握手前的准備 

       所謂的握手,其實就是我們在上一節講述的“對稱加密秘鑰”的協商過程,所以再協商之前雙方要先去向CA機構發請求,獲得數字證書和根證書,而這個CA機構可以是自己,只要在集群范圍內大家認可就可以了,所以我們通過cfssl工具生成證書,當然也可以用openssl工具。

      以下列出了步驟,有些細節可以參考這里:https://blog.csdn.net/xxb249/article/details/79449434

1)安裝cfssl

curl -s -L -o /bin/cfssl https://pkg.cfssl.org/R1.2/cfssl_linux-amd64 
curl -s -L -o /bin/cfssljson https://pkg.cfssl.org/R1.2/cfssljson_linux-amd64 
curl -s -L -o /bin/cfssl-certinfo https://pkg.cfssl.org/R1.2/cfssl-certinfo_linux-amd64 
chmod +x /bin/cfssl*

 

2)寫根證書的配置文件

#mkdir -p /etc/kubernetes/apiserver/
#cd /etc/kubernetes/apiserver/

//利用命令先生成一個缺省的樣板的請求文件
#cfssl print-defaults config > ca-config.json ----
#cfssl print-defaults csr > ca-csr.json ----修改配置,配置請求文件.csr應該是什么樣子的

  //根據自己的情況修改配置,ca-config.json中可以同時定義多個配置簇,一個配置簇對應一種類型的數字證書的各項參數
  #vi ca-config.json
  #vi ca-csr.json

我的配置文件供參考

[root@master test]# cat ca-config.json 
{
    "signing": {
        "default": {
            "expiry": "43800h"
        },
        "profiles": {
            "client": {
                "expiry": "43800h",
                "usages": [
                    "signing",
                    "key encipherment",
                    "client auth"
                ]
            },
            "server": {
                "expiry": "43800h",
                "usages": [
                    "signing",
                    "key encipherment",
                    "server auth",
                    "client auth"
                ]
            }
        }
    }
}
View Code

 

3)生成根正書

# cfssl gencert -initca ca-csr.json | cfssljson -bare ca -
2019/05/22 10:46:34 [INFO] generating a new CA key and certificate from CSR
2019/05/22 10:46:34 [INFO] generate received request
2019/05/22 10:46:34 [INFO] received CSR
2019/05/22 10:46:34 [INFO] generating key: rsa-2048
2019/05/22 10:46:35 [INFO] encoded CSR
2019/05/22 10:46:35 [INFO] signed certificate with serial number 357142409311435315855410100215094728480952302820

[root@master apiserver]# ll
total 24
-rw-r--r-- 1 root root 833 May 22 10:39 ca-config.json
-rw-r--r-- 1 root root 997 May 22 10:46 ca.csr        ---根證書請求文件
-rw-r--r-- 1 root root 262 May 22 10:42 ca-csr.json
-rw------- 1 root root 1679 May 22 10:46 ca-key.pem   ---根證書對應的私鑰
-rw-r--r-- 1 root root 1350 May 22 10:46 ca.pem       ----根證書

 

4)簽發數字證書,首先寫好配置,然后簽發

#cfssl print-defaults csr > server-csr.json
# vi server-csr.json ---按情況修改

//-profile參數對應ca-config.json中的一個配置簇
# cfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=ca-config.json -profile=server server-csr.json | cfssljson -bare server
2019/05/30 19:37:53 [INFO] generate received request
2019/05/30 19:37:53 [INFO] received CSR
2019/05/30 19:37:53 [INFO] generating key: rsa-2048
2019/05/30 19:37:53 [INFO] encoded CSR
2019/05/30 19:37:53 [INFO] signed certificate with serial number 605959301321047408407639792005193883834034252126
2019/05/30 19:37:53 [WARNING] This certificate lacks a "hosts" field. This makes it unsuitable for
websites. For more information see the Baseline Requirements for the Issuance and Management
of Publicly-Trusted Certificates, v.1.1.6, from the CA/Browser Forum (https://cabforum.org);
specifically, section 10.2.3 ("Information Requirements").

 

說明:在部署集群的時候會用到各種證書,有的文檔會告訴你有server類型,peer類型,client類型......這對我來說反倒讓我一臉懵逼,實際上CA機構才不管你什么類型的證書呢,我只管生成,至於你是用作服務端還是客戶端還是什么的,那是你自己去配置,只不過一般情況下我們會根據使用場景生成的證書,會命名成服務端證書和客戶端證書之類的。

接下來我們來看看這些證書在tls握手中怎么被使用的。

 注:CA是個機構,該機構才能簽發證書,根證書可以看做是CA的身份證明,但是我們在接下來的敘述中為了方便,就說成是根證書簽發數字證書。

 2,tls的握手

關於tlls握手的原理強烈推薦這個博客:https://baijiahao.baidu.com/s?id=1616211978225668389&wfr=spider&for=pc

簡述之:

1,客-->服:客戶端打招呼,我要訪問https:服務器   

2,服-->客:服務器把自己的數字證書(公鑰)發給客戶端

                     [如果使能了客戶端認證]服務器端還會向客戶端發請求,告知想要客戶的證書

      《《《此時客戶端拿到了服務端的公鑰,如果使能了"跳過證書認證"則就這么着了,否則還要用根證書對該證書進行校驗》》》

3,客-->服[如果服務端有請求證書]:客戶端同樣把自己的數字證書(公鑰)發給服務器端

      《《《此時服務端拿到了客戶端的公鑰,然后用根證書對該證書進行校驗》》》

 

小結:在這里面涉及到的證書及關系為:服務器端的數字證書(對)以及簽發該證書的根證書;客戶端的數字證書(對)以及簽發該證書的根證書;

          所以當一個app作為服務器的時候,它必須要准備好自己的證書(對)和 客戶端使用的根證書

                 當客戶端訪問的時候,客戶端也必須要准備自己的證書(對) 和 服務器端使用的根證書

          好了,講解到這里,我們去研究下kube-apiserver中是怎么配置這些的,就變得很容易了。

 

三,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《《《《《《《《《《《《《《《《《《《《《《《《


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM