HTTPS單向認證流程
1. 客戶端發起建立HTTPS連接請求,將SSL協議版本的信息發送給服務端。
2. 服務端將自己的公鑰證書(server.crt)發送給客戶端。
3. 客戶端通過自己的根證書(root.crt)驗證服務端的公鑰證書(server.crt)的合法性,取出服務端公鑰。
4. 客戶端生成密鑰R,用服務端公鑰去加密它形成密文,發送給服務端。
5. 服務端用自己的私鑰(server.key)去解密這個密文,得到客戶端的密鑰R。
6. 服務端和客戶端使用密鑰R進行通信。
HTTPS雙向認證流程
1. 客戶端發起建立HTTPS連接請求,將SSL協議版本的信息發送給服務端。
2. 服務端將自己的公鑰證書(server.crt)發送給客戶端。
3. 客戶端通過自己的根證書(root.crt)驗證服務端的公鑰證書(server.crt)的合法性,取出服務端公鑰。
4. 客戶端將自己的公鑰證書(client.crt)發送給服務端。
5. 服務端使用根證書(root.crt)驗證客戶端公鑰證書的合法性,取出客戶端公鑰。
6. 客戶端發送自己支持的加密方案給服務端。
7. 服務端選擇一個雙方都能接受的加密方案,使用客戶端的公鑰加密后發送給客戶端。
8. 客戶端使用自己的私鑰解密加密方案,生成密鑰R,使用服務端公鑰加密后傳給服務端。
9. 服務端用自己的私鑰去解密這個密文,得到了密鑰R。
10. 服務端和客戶端使用密鑰R進行通信。
雙向認證使用的證書
服務端公鑰證書:server.crt
服務端私鑰:server.key
根證書(服務端和客戶端都有,內容相同):root.crt
客戶端公鑰證書:client.crt
客戶端私鑰:client.key
curl -k背后邏輯
作用是,跳過客戶端通過自己的根證書(root.crt)驗證服務端的公鑰證書(server.crt)的合法性這一步。
通過curl來模擬請求
# 對HTTPS服務發起HTTP請求(請求方式錯誤)
$ curl http://192.168.0.100:6443/apis/apps/v1/namespaces/kube-system/deployments/coredns
Client sent an HTTP request to an HTTPS server.
# 不帶證書發起HTTPS請求(認證失敗)
$ curl https://192.168.0.100:6443/apis/apps/v1/namespaces/kube-system/deployments/coredns
...
If this HTTPS server uses a certificate signed by a CA represented in
the bundle, the certificate verification probably failed due to a
problem with the certificate (it might be expired, or the name might
not match the domain name in the URL).
If you'd like to turn off curl's verification of the certificate, use
the -k (or --insecure) option.
# 以跳過SSL證書認證的方式來發起HTTPS請求(認證失敗,無法通過TLS雙向認證)
$ curl -k https://192.168.0.100:6443/apis/apps/v1/namespaces/kube-system/deployments/coredns
{
"kind": "Status",
"apiVersion": "v1",
"metadata": {
},
"status": "Failure",
"message": "Unauthorized",
"reason": "Unauthorized",
"code": 401
}
# 攜帶客戶端根證書的方式來發起HTTPS請求(因缺少客戶端公鑰證書和私鑰而認證失敗)
$ curl https://192.168.0.100:6443/apis/apps/v1/namespaces/kube-system/deployments/coredns --cacert /etc/kubernetes/pki/ca.crt
{
...
"status": "Failure",
"message": "Unauthorized",
"reason": "Unauthorized",
"code": 401
}
# 攜帶錯誤的客戶端根證書的方式來發起HTTPS請求(認證失敗)
$ curl https://192.168.0.100:6443/apis/apps/v1/namespaces/kube-system/deployments/coredns --cacert /root/cert/root.crt
curl: (60) Peer's Certificate issuer is not recognized.
# 拒絕匿名請求
$ curl https://192.168.0.100:6443/apis/apps/v1/namespaces/kube-system/deployments/coredns --key /etc/kubernetes/pki/ca.crt/apiserver-kubelet-clien.key --cert /etc/kubernetes/pki/ca.crt/apiserver-kubelet-client.crt --cacert /etc/kubernetes/pki/ca.crt
{
"kind": "Status",
"apiVersion": "v1",
"metadata": {
},
"status": "Failure",
"message": "deployments.apps \"coredns\" is forbidden: User \"system:anonymous\" cannot get resource \"deployments\" in API group \"apps\" in the namespace \"kube-system\"",
"reason": "Forbidden",
"details": {
"name": "coredns",
"group": "apps",
"kind": "deployments"
},
"code": 403
}
# kubeconfig管理訪問kube-apiserver的配置信息。k8s組件都使用kubeconfig配置信息來連接kube-apiserver,包括kubectl。
# 根據kubeconfig(~/.kube/config)來保存證書內容(base64解碼:echo “xxx” | base64 -d),client-certificate-data對應client.crt,client-key-data對應client.key,certificate-authority-data對應root.crt
$ curl https://192.168.0.100:6443/apis/apps/v1/namespaces/kube-system/deployments/coredns --key /root/cert/client.key --cert /root/cert/client.crt --cacert /root/cert/root.crt
{
"kind": "Deployment",
"apiVersion": "apps/v1",
"metadata": {
"name": "coredns",
"namespace": "kube-system",
"selfLink": "/apis/apps/v1/namespaces/kube-system/deployments/coredns",
"uid": "6c1c53e8-4e6e-4f76-8509-cd018a016f02",
"resourceVersion": "1494",
"generation": 1,
...
}
基於beego框架向kube-apiserver發起PATCH請求(API項目)
routers/router.go配置固定路由
func init() {
...
beego.Router("/scale", &controllers.ScaleDeployController{})
}
models/scale.go配置json接收的數據模型
package models
type ScaleDeploy struct {
M map[string]interface{}
}
controllers/scale.go配置ScaleDeployController
package controllers
import (
...
)
type ScaleDeployController struct {
beego.Controller
}
func (o *ScaleDeployController) Patch() {
fmt.Println("enter")
var sd models.ScaleDeploy
err := json.Unmarshal(o.Ctx.Input.RequestBody, &sd.M)
if err != nil {
panic(err)
}
...
}
curl命令觸發PATCH請求
curl -X PATCH -H 'Content-Type: application/strategic-merge-patch+json' --data '{"Namespace":"default", "DeployName":"nginx-deploy", "Replicas":5}' 'http://127.0.0.1:8080/scale'
向kube-apiserver發送請求失敗:x509: certificate signed by unknown authority
// 添加證書(客戶端CA證書、客戶端包含公鑰的證書、客戶端私鑰)
// 加載客戶端ca證書
caCert, err := ioutil.ReadFile("/root/k8s/client-ca.crt")
if err != nil {
panic(err)
}
caCertPool := x509.NewCertPool()
caCertPool.AppendCertsFromPEM(caCert)
// 加載客戶端包含公鑰的證書和私鑰
cert, err := tls.LoadX509KeyPair("/root/k8s/client.crt", "/root/k8s/client.key")
if err != nil {
panic(err)
}
client := &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{
RootCAs: caCertPool,
Certificates: []tls.Certificate{cert},
},
},
Timeout: 5 * time.Second,
}
完整代碼
package controllers
import (
"beego-test/models"
"bytes"
"crypto/tls"
"crypto/x509"
"encoding/json"
"fmt"
"github.com/astaxie/beego"
"io/ioutil"
"net/http"
"strconv"
"time"
)
type ScaleDeployController struct {
beego.Controller
}
func (o *ScaleDeployController) Patch() {
fmt.Println("enter")
var sd models.ScaleDeploy
err := json.Unmarshal(o.Ctx.Input.RequestBody, &sd.M)
if err != nil {
panic(err)
}
deployName := ""
namespace := ""
replicas := -1
for key, value := range sd.M {
switch cur := value.(type) {
case float64:
if key == "Replicas" {
replicas = int(int64(cur))
} else {
panic("key error")
}
case string:
if key == "DeployName" {
deployName = cur
} else if key == "Namespace" {
namespace = cur
} else {
panic("key error")
}
default:
panic("type error")
}
}
fmt.Println(replicas)
replicasStr := strconv.Itoa(replicas)
// 請求體字節流,轉義雙引號
jsonStr := []byte("{\"spec\":{\"replicas\":" + replicasStr + "}}")
// 加載客戶端ca證書
caCert, err := ioutil.ReadFile("/root/k8s/client-ca.crt")
if err != nil {
panic(err)
}
caCertPool := x509.NewCertPool()
caCertPool.AppendCertsFromPEM(caCert)
// 加載客戶端包含公鑰的證書和私鑰
cert, err := tls.LoadX509KeyPair("/root/k8s/client.crt", "/root/k8s/client.key")
if err != nil {
panic(err)
}
client := &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{
RootCAs: caCertPool,
Certificates: []tls.Certificate{cert},
},
},
Timeout: 5 * time.Second,
}
req, err := http.NewRequest("PATCH",
"https://192.168.0.100:6443/apis/apps/v1/namespaces/" + namespace + "/deployments/" + deployName,
bytes.NewBuffer(jsonStr))
req.Header.Add("content-type", "application/strategic-merge-patch+json")
if err != nil {
panic(err)
}
defer req.Body.Close()
resp, error := client.Do(req)
if error != nil {
panic(error)
}
defer resp.Body.Close()
//result, _ := ioutil.ReadAll(resp.Body)
//content := string(result)
//o.Data["json"] = content
o.Data["json"] = resp.Status
o.ServeJSON()
}
運行結果
"200 OK"