1. 前言
转载请说明原文出处, 尊重他人劳动成果!
源码位置: https://github.com/nicktming/istio
分支: tming-v1.3.6 (基于1.3.6版本)
本文将分析
istio
中的负责证书方面事情的组件citadel
, 源码位置在istio/security
, 具体将分析citadel
中istio_ca
的作用.
对证书
https
不了解的可以参考 https://www.cnblogs.com/pzblog/p/9088286.html.
2. 初识istio_ca
[root@master istio_ca]# pwd
/root/go/src/istio.io/istio/security/cmd/istio_ca
[root@master istio_ca]# ls
istio_ca main.go run.sh
[root@master istio_ca]# kubectl get sa --all-namespaces
NAMESPACE NAME SECRETS AGE
default bookinfo-details 1 14d
default bookinfo-productpage 1 14d
default bookinfo-ratings 1 14d
default bookinfo-reviews 1 14d
default default 1 39d
istio-system default 1 15d
kube-node-lease default 1 23d
kube-public default 1 39d
kube-system coredns 1 39d
kube-system default 1 39d
tming default 1 15d
[root@master istio_ca]# kubectl get cm -n istio-system
NAME DATA AGE
istio 2 15d
istio-sidecar-injector 2 15d
[root@master istio_ca]# cat run.sh
#!/usr/bin/env bash
export CITADEL_ENABLE_NAMESPACES_BY_DEFAULT=true
config=""
config="$config --append-dns-names=true"
config="$config --grpc-port=8060"
config="$config --citadel-storage-namespace=istio-system"
config="$config --custom-dns-names=istio-pilot-service-account.istio-system:istio-pilot.istio-system"
config="$config --monitoring-port=15014"
config="$config --self-signed-ca=true"
config="$config --workload-cert-ttl=2160h"
./istio_ca $config
unset CITADEL_ENABLE_NAMESPACES_BY_DEFAULT
[root@master istio_ca]# ./run.sh
2020-02-04T08:02:23.012518Z info The custom-defined DNS name list is [istio-pilot-service-account.istio-system:istio-pilot.istio-system]
2020-02-04T08:02:23.018847Z info Use self-signed certificate as the CA certificate
2020-02-04T08:02:23.019753Z info ControlZ available at 127.0.0.1:9876
2020-02-04T08:02:23.027905Z info Failed to get secret (error: secrets "istio-ca-secret" not found), will create one
2020-02-04T08:02:23.495396Z info Using self-generated public key: -----BEGIN CERTIFICATE-----
...
-----END CERTIFICATE-----
2020-02-04T08:02:23.502515Z info The Citadel's public key is successfully written into configmap istio-security in namespace istio-system.
2020-02-04T08:02:23.502545Z info rootCertRotator Set up back off time 2m21s to start rotator.
2020-02-04T08:02:23.502558Z info Creating Kubernetes controller to write issued keys and certs into secret ...
2020-02-04T08:02:23.502619Z info rootCertRotator Jitter is enabled, wait 2m21s before starting root cert rotator.
2020-02-04T08:02:23.602779Z info adding registry entry "k8s.cluster.local" -> "k8s.cluster.local"
2020-02-04T08:02:23.602831Z info added client certificate authenticator
2020-02-04T08:02:23.603288Z info Citadel monitor has started.
2020-02-04T08:02:23.603298Z info Citadel has started
2020-02-04T08:02:23.603332Z info monitor Monitor server started.
2020-02-04T08:02:23.604169Z info Starting GRPC server on port 8060
2020-02-04T08:02:23.610077Z info adding registry entry "spiffe://cluster.local/ns/default/sa/bookinfo-details" -> "spiffe://cluster.local/ns/default/sa/bookinfo-details"
...
2020-02-04T08:02:23.786537Z info k8sController Secret tming/istio.default is created successfully
...
查看所有
secret
, 可以每个serviceaccount
都额外增加了一个类型为istio.io/key-and-cert
的secret
, 名字就是istio.
加上serviceaccount
的名字.
[root@master ~]# kubectl get secret --all-namespaces
NAMESPACE NAME TYPE DATA AGE
...
default istio.bookinfo-details istio.io/key-and-cert 3 98s
default istio.bookinfo-productpage istio.io/key-and-cert 3 96s
default istio.bookinfo-ratings istio.io/key-and-cert 3 96s
default istio.bookinfo-reviews istio.io/key-and-cert 3 97s
default istio.default istio.io/key-and-cert 3 39d
istio-system istio.default istio.io/key-and-cert 3 95s
kube-node-lease istio.default istio.io/key-and-cert 3 23d
kube-public istio.default istio.io/key-and-cert 3 39d
kube-system istio.coredns istio.io/key-and-cert 3 39d
kube-system istio.default istio.io/key-and-cert 3 39d
tming istio.default istio.io/key-and-cert 3 98s
...
[root@master ~]#
3. pki
pki
的地址在security/pki
.
3.1 NewIstioCA
func NewSelfSignedIstioCAOptions(ctx context.Context,
rootCertGracePeriodPercentile int, caCertTTL, rootCertCheckInverval, certTTL,
maxCertTTL time.Duration, org string, dualUse bool, namespace string,
readCertRetryInterval time.Duration, client corev1.CoreV1Interface, rootCertFile string,
enableJitter bool) (caOpts *IstioCAOptions, err error) {
// 查看istio-system这个namespaces中是否存在istio-ca-secret这个secret
caSecret, scrtErr := client.Secrets(namespace).Get(CASecret, metav1.GetOptions{})
if scrtErr != nil && readCertRetryInterval > time.Duration(0) {
// 重试一次
}
caOpts = &IstioCAOptions{
...
}
if scrtErr != nil {
// 如果k8s中不存在 就自己生成key和cert
options := util.CertOptions{
...
}
// 用x509生成key和证书cert
pemCert, pemKey, ckErr := util.GenCertKeyFromOptions(options)
if ckErr != nil {
return nil, fmt.Errorf("unable to generate CA cert and key for self-signed CA (%v)", ckErr)
}
// 把证书cert添加到rootCerts
rootCerts, err := util.AppendRootCerts(pemCert, rootCertFile)
if err != nil {
return nil, fmt.Errorf("failed to append root certificates (%v)", err)
}
// 利用pemCert, pemKey, rootCerts 填充caOpts.KeyCertBundle
if caOpts.KeyCertBundle, err = util.NewVerifiedKeyCertBundleFromPem(pemCert, pemKey, nil, rootCerts); err != nil {
return nil, fmt.Errorf("failed to create CA KeyCertBundle (%v)", err)
}
// 在istio-system namespace中生成一个类型为istio.io/ca-root的secret
// secret里面的数据是 根证书和根key
secret := k8ssecret.BuildSecret("", CASecret, namespace, nil, nil, nil, pemCert, pemKey, istioCASecretType)
if _, err = client.Secrets(namespace).Create(secret); err != nil {
log.Errorf("Failed to write secret to CA (error: %s). Abort.", err)
return nil, fmt.Errorf("failed to create CA due to secret write error")
}
log.Infof("Using self-generated public key: %v", string(rootCerts))
} else {
// 从已有的secret中读取
log.Infof("Load signing key and cert from existing secret %s:%s", caSecret.Namespace, caSecret.Name)
rootCerts, err := util.AppendRootCerts(caSecret.Data[caCertID], rootCertFile)
if err != nil {
return nil, fmt.Errorf("failed to append root certificates (%v)", err)
}
if caOpts.KeyCertBundle, err = util.NewVerifiedKeyCertBundleFromPem(caSecret.Data[caCertID],
caSecret.Data[caPrivateKeyID], nil, rootCerts); err != nil {
return nil, fmt.Errorf("failed to create CA KeyCertBundle (%v)", err)
}
log.Infof("Using existing public key: %v", string(rootCerts))
}
// 将根证书保存到configmap中 名字为istio-system/istio-security
if err = updateCertInConfigmap(namespace, client, caOpts.KeyCertBundle.GetRootCertPem()); err != nil {
log.Errorf("Failed to write Citadel cert to configmap (%v). Node agents will not be able to connect.", err)
} else {
log.Infof("The Citadel's public key is successfully written into configmap istio-security in namespace %s.", namespace)
}
return caOpts, nil
}
1. 查看
istio-system namespace
中是否存在istio-ca-secret
这个secret
.
2. 如果不存在, 则会自己生成cert
和key
, 该cert
和key
会用于签名那些所有的secret
的. 并且存到istio-ca-secret
中.
3. 如果存在, 则直接从已有的secret
中读取.
4. 无论存在不存在, 都需要根据pemCert
,pemKey
,rootCerts
填充caOpts.KeyCertBundle
.
5. 将根证书保存到configmap
中, 名字为istio-system/istio-security
.
// security/pkg/pki/ca/ca.go
func updateCertInConfigmap(namespace string, client corev1.CoreV1Interface, cert []byte) error {
certEncoded := base64.StdEncoding.EncodeToString(cert)
cmc := configmap.NewController(namespace, client)
return cmc.InsertCATLSRootCert(certEncoded)
}
// security/pkg/k8s/configmap/configmap.go
func (c *Controller) InsertCATLSRootCert(value string) error {
configmap, err := c.core.ConfigMaps(c.namespace).Get(istioSecurityConfigMapName, metav1.GetOptions{})
// 更新或生成该configmap
return nil
}
查看
istio-ca-secret
和istio-security
.
[root@master ~]# kubectl get secret istio-ca-secret -o yaml -n istio-system
apiVersion: v1
data:
ca-cert.pem: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUMzVENDQWNXZ0F3SUJBZ0lRY005Q2w4OCttYThzTERnWFNFaGF3akFOQmdrcWhraUc5dzBCQVFzRkFEQVkKTVJZd0ZBWURWUVFLRXcxamJIVnpkR1Z5TG14dlkyRnNNQjRYRFRJd01ESXdOREE0TURJeU0xb1hEVE13TURJdwpNVEE0TURJeU0xb3dHREVXTUJRR0ExVUVDaE1OWTJ4MWMzUmxjaTVzYjJOaGJEQ0NBU0l3RFFZSktvWklodmNOCkFRRUJCUUFEZ2dFUEFEQ0NBUW9DZ2dFQkFKOVZrLythRXV1bHhvb1F1bzNSUnlrWUpuME9udlViZ1JyRHJXWEYKekxKYVZDVUthY1lxTE5vSjBXKzFES1dyZ05weFBKcE8vUUtQeGxOdnJtK004cllVOHc0NWI5VGFLdTNxSGwvdgp1UmkrSnJZT3ZWcGlZY3A4SEc2cnI5UWtIcGJjcm1OT2VOQ1lVZGdXQ2VkMW5tWjZjZGcrT0RIYkFzVkloTmRiCjBHNDc4RU1RM3FOaWFTdGVIRXJMSDRlU045Z2VVbkhmY2hCTVFnQXN4cjJza01wUEw2VWJSMHl2dzVXM3dpWngKbWpaN2ZUYURBcy9vZ3hTV1N6LzE2eG1lVzYvbEx4N1JQNVd6ZTlkWnBPOE9xUW5BV3dlL2puTTNrQ0lNbksrUgpPTVlBRjBiU1VLKzZiT004bmZVanM4RUVJdEJvdUluYmNwNFplZkhvSFlaNk10RUNBd0VBQWFNak1DRXdEZ1lEClZSMFBBUUgvQkFRREFnSUVNQThHQTFVZEV3RUIvd1FGTUFNQkFmOHdEUVlKS29aSWh2Y05BUUVMQlFBRGdnRUIKQUcxdEU3UnFCTHpzd3ovTStuYndoeFR2VE14eHJ2RDYxVDc1eTVPRUMxUnk4RmQxbnYyT09vR0UybjUxV0N4VAp0YzFERzAvWUNrbko1a3grWEo5TElDbkdkcHFxR2VnQkNrR1RlWmpPMlk0Zko5dEZ3SWdhbVQwYmNUVjJ3NmUwClVRdUxuR1hxaytVdUFiOHFHWUZObGVlMk1LOFV5aEM0SGlEN1Z2c2xXcUdmazdTOHNtWmR0cGUyMkY2RThrbDQKSFlYNHh4RWQyRTZxVlYxbFZsMTV1TGo2V3R3Z1V0dmZMR3RvMnlyOHN3cExETHZ4SVprdWd6azc1OE1LbUljNwowOEV3bUdNVnVvUUhEcnA4NDU1NkNwZGl2NDY0dUdWeW9PaDU1WWlPcGxidWtWQzZTRE9QbTlueTRRTThRV1ZKCmYyYkRXQkpoSmRXb1FXVElPWm1wek5vPQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==
ca-key.pem: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFb3dJQkFBS0NBUUVBbjFXVC81b1M2NlhHaWhDNmpkRkhLUmdtZlE2ZTlSdUJHc090WmNYTXNscFVKUXBwCnhpb3MyZ25SYjdVTXBhdUEybkU4bWs3OUFvL0dVMit1YjR6eXRoVHpEamx2MU5vcTdlb2VYKys1R0w0bXRnNjkKV21KaHlud2NicXV2MUNRZWx0eXVZMDU0MEpoUjJCWUo1M1dlWm5weDJENDRNZHNDeFVpRTExdlFianZ3UXhEZQpvMkpwSzE0Y1Nzc2ZoNUkzMkI1U2NkOXlFRXhDQUN6R3ZheVF5azh2cFJ0SFRLL0RsYmZDSm5HYU5udDlOb01DCnoraURGSlpMUC9YckdaNWJyK1V2SHRFL2xiTjcxMW1rN3c2cENjQmJCNytPY3plUUlneWNyNUU0eGdBWFJ0SlEKcjdwczR6eWQ5U096d1FRaTBHaTRpZHR5bmhsNThlZ2Robm95MFFJREFRQUJBb0lCQUdHYzloeURjYy80TVpmbwpBOEphVWZRMUhXOUVBOUk1MVhCbUxOYkt4VXNHMThJUmpSZWdRdllaU2J2YitUR056bFVGUnBGcWpzcUE5b21yClEveUhKekt4eHU0UjloYzZ5VTRVUGlPY0k1T3ErdUJTUzJNU0hzTUVJZzhURTVjdHdhZSs3djliMWR4RlZPN0QKSWJJeGRxZGxvRlZRV1BFQ01jSlhXVHJ1dnRTbzRFeWQ5NlFzV0w5RFBvRkRiU0xULzJKM3lQTFJlUEhQK25mbQpiTEJZNStGcFI0TElGSHpjZ1VtdWhHdFA2QkQyTnM5VERLSWJINjd1OUZ5S05Ec0laRmZEZkY5NDJIbzNYUnhkCkpoaHpKdy9Kbkl2M0pGbzM2ekwxWHVjd0hkNnVIYlJoMGw5eER3Y3Z6MlFmYm9BN2hlSlAvUllRb3VLVGl1UHAKMVVWbXVaa0NnWUVBeElQYUNTRjQzMUNjQjlYNHpJRDhCaFBQY3E3K2cvbjZEMThiY1NWK2M1eHhGYytKU1FvbwpIRC9MZnJUWlNEa3lCbmV6eU5zbXRzY1pXTGpGblRZWFFaZDZVUkl4dUw3anU1emI3N2YwMWVMS1RWT3IwUk81CjlZMEF1UkEyaEp4Q0RObVdmYm5JcUxzTUJBOUhUNGE5T3BGME1KVi9DaGpPcElBSEdKMHV3L2NDZ1lFQXo1Q04KWWFQbzNwaGlFZnFyUElEMlRtUjdFNnZveGkxc0VmVjMvRk1EMEVUTkpidE5xL09KWElyOGFEV1lDeThMVDZteApXU3FDVG1tdDEvNTREa3hzVnM2NVJGUVVIc1pKSktkVVNTNXI0MkUxZElvcHBWMWZQcDdqTXFUb1l2QjQ3eWpMCnR2LzRvdnlzajdqYWJIZTZ4ZFF1bUhmZ1B6dndaaEllUmVuKy9YY0NnWUVBd2xqYjAxZmxJSVdxS2gyMU54c0IKSkVtSFNoWkM0K2JmSlVDYjlTUnRrSXpSVWc2ejZTWkFVTi9Pc3ZyTVFKOUFHQ0ZlRG5DZU12bG8yZE95ckM0SQpoZmYzSWlKcVJobVRROEozeVBZWEQwaUJaa1F2a2xCK0FwaDJkSS9TT3dnR1Vvc0dTRVRxYStUQWwyczh6U3VtCkRUdzR1cXUrdmcrV25oMTM1eUhjVkRVQ2dZQWVtM0k5czYzakpSVlJFV1d1eGxXTHVjVnNZUzB5REFDanBVN00KSm5HcGhIdTcxS0xmZUVvSkczV2M2RXBEVyt5UEVyYlVMekNIMzQyRERFTW4ySjBoTDlxaCtNUCt4YjZEYVNsRQpvVlBIYzg0dUlUR0M4UUhhZWhPR3BFcWdURldJN3JzYmdTTm81Vm1NMS9WZGlVcEJZY2g5TXpBZUl2aVFGSnZpCk0xOG1nUUtCZ0N0QVFNcytIK1RGcnFHaDZZN3RFNXBoM3pwZ2tsclp4ZEN2MDR4M2JDcy9EZDEyZDZKUElRSk0KQ0JGYXZibmgvNDhwZUJxR0NDRkxIZ1FVL0VUOTdRdFRPU3h3SWxHck9rR1BhUkR3eVc2M0hXWGV1RHBiRXhrbQpXUW9CcHBZYlpWSGlTcVVWaVR1NG5tNjRPQ0loNWVoa3dVS05YY0cvakFxSkdnUDhPYzEzCi0tLS0tRU5EIFJTQSBQUklWQVRFIEtFWS0tLS0tCg==
cert-chain.pem: ""
key.pem: ""
root-cert.pem: ""
kind: Secret
metadata:
name: istio-ca-secret
...
type: istio.io/ca-root
[root@master ~]# kubectl get cm istio-security -o yaml -n istio-system
apiVersion: v1
data:
caTLSRootCert: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUMzVENDQWNXZ0F3SUJBZ0lRY005Q2w4OCttYThzTERnWFNFaGF3akFOQmdrcWhraUc5dzBCQVFzRkFEQVkKTVJZd0ZBWURWUVFLRXcxamJIVnpkR1Z5TG14dlkyRnNNQjRYRFRJd01ESXdOREE0TURJeU0xb1hEVE13TURJdwpNVEE0TURJeU0xb3dHREVXTUJRR0ExVUVDaE1OWTJ4MWMzUmxjaTVzYjJOaGJEQ0NBU0l3RFFZSktvWklodmNOCkFRRUJCUUFEZ2dFUEFEQ0NBUW9DZ2dFQkFKOVZrLythRXV1bHhvb1F1bzNSUnlrWUpuME9udlViZ1JyRHJXWEYKekxKYVZDVUthY1lxTE5vSjBXKzFES1dyZ05weFBKcE8vUUtQeGxOdnJtK004cllVOHc0NWI5VGFLdTNxSGwvdgp1UmkrSnJZT3ZWcGlZY3A4SEc2cnI5UWtIcGJjcm1OT2VOQ1lVZGdXQ2VkMW5tWjZjZGcrT0RIYkFzVkloTmRiCjBHNDc4RU1RM3FOaWFTdGVIRXJMSDRlU045Z2VVbkhmY2hCTVFnQXN4cjJza01wUEw2VWJSMHl2dzVXM3dpWngKbWpaN2ZUYURBcy9vZ3hTV1N6LzE2eG1lVzYvbEx4N1JQNVd6ZTlkWnBPOE9xUW5BV3dlL2puTTNrQ0lNbksrUgpPTVlBRjBiU1VLKzZiT004bmZVanM4RUVJdEJvdUluYmNwNFplZkhvSFlaNk10RUNBd0VBQWFNak1DRXdEZ1lEClZSMFBBUUgvQkFRREFnSUVNQThHQTFVZEV3RUIvd1FGTUFNQkFmOHdEUVlKS29aSWh2Y05BUUVMQlFBRGdnRUIKQUcxdEU3UnFCTHpzd3ovTStuYndoeFR2VE14eHJ2RDYxVDc1eTVPRUMxUnk4RmQxbnYyT09vR0UybjUxV0N4VAp0YzFERzAvWUNrbko1a3grWEo5TElDbkdkcHFxR2VnQkNrR1RlWmpPMlk0Zko5dEZ3SWdhbVQwYmNUVjJ3NmUwClVRdUxuR1hxaytVdUFiOHFHWUZObGVlMk1LOFV5aEM0SGlEN1Z2c2xXcUdmazdTOHNtWmR0cGUyMkY2RThrbDQKSFlYNHh4RWQyRTZxVlYxbFZsMTV1TGo2V3R3Z1V0dmZMR3RvMnlyOHN3cExETHZ4SVprdWd6azc1OE1LbUljNwowOEV3bUdNVnVvUUhEcnA4NDU1NkNwZGl2NDY0dUdWeW9PaDU1WWlPcGxidWtWQzZTRE9QbTlueTRRTThRV1ZKCmYyYkRXQkpoSmRXb1FXVElPWm1wek5vPQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==
kind: ConfigMap
metadata:
name: istio-security
...
[root@master ~]#
总结一下, 就是用
x509
生成了根cert
和 根key
, 保存到了istio-ca-secret
中, 一个可以叠加的root cert
保存到了istio-security
.
3.2 Sign
func (ca *IstioCA) Sign(csrPEM []byte, subjectIDs []string, requestedLifetime time.Duration, forCA bool) ([]byte, error) {
// 用于签名的证书和key 就是在NewSelfSignedIstioCAOptions中生成的key和cert
signingCert, signingKey, _, _ := ca.keyCertBundle.GetAll()
if signingCert == nil {
return nil, caerror.NewError(caerror.CANotReady, fmt.Errorf("Istio CA is not ready")) // nolint
}
// 生成csr
csr, err := util.ParsePemEncodedCSR(csrPEM)
if err != nil {
return nil, caerror.NewError(caerror.CSRError, err)
}
...
// 生成签名证书
certBytes, err := util.GenCertFromCSR(csr, signingCert, csr.PublicKey, *signingKey, subjectIDs, lifetime, forCA)
...
block := &pem.Block{
Type: "CERTIFICATE",
Bytes: certBytes,
}
cert := pem.EncodeToMemory(block)
return cert, nil
}
可以看到签名是用之前生成保存在
ca.keyCertBundle
的根cert
的.
4. workloadsecret
该组件是专门为所有的
serviceaccount
生成和维护一个istio.io/key-and-cert
类型的secret
. 所有生成的这种类型的secret
中保存着自己的key and cert
, 并且它的证书都是通过保存在istio-ca-secret
的根证书来进行签名的.
func NewSecretController(ca certificateAuthority, enableNamespacesByDefault bool,
certTTL time.Duration, gracePeriodRatio float32, minGracePeriod time.Duration,
dualUse bool, core corev1.CoreV1Interface, forCA bool, pkcs8Key bool, namespaces []string,
dnsNames map[string]*DNSNameEntry, istioCaStorageNamespace, rootCertFile string,
selfSignedCa bool) (*SecretController, error) {
...
c.saStore, c.saController =
cache.NewInformer(saLW, &v1.ServiceAccount{}, time.Minute, cache.ResourceEventHandlerFuncs{
AddFunc: c.saAdded,
DeleteFunc: c.saDeleted,
})
istioSecretSelector := fields.SelectorFromSet(map[string]string{"type": IstioSecretType}).String()
scrtLW := listwatch.MultiNamespaceListerWatcher(namespaces, func(namespace string) cache.ListerWatcher {
return &cache.ListWatch{
ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
options.FieldSelector = istioSecretSelector
return core.Secrets(namespace).List(options)
},
WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
options.FieldSelector = istioSecretSelector
return core.Secrets(namespace).Watch(options)
},
}
})
c.scrtStore, c.scrtController =
cache.NewInformer(scrtLW, &v1.Secret{}, secretResyncPeriod, cache.ResourceEventHandlerFuncs{
DeleteFunc: c.scrtDeleted,
UpdateFunc: c.scrtUpdated,
})
...
c.namespaceStore, c.namespaceController =
cache.NewInformer(namespaceLW, &v1.Namespace{}, namespaceResyncPeriod, cache.ResourceEventHandlerFuncs{
UpdateFunc: c.namespaceUpdated,
})
return c, nil
}
func (sc *SecretController) Run(stopCh chan struct{}) {
go sc.scrtController.Run(stopCh)
cache.WaitForCacheSync(stopCh, sc.scrtController.HasSynced)
go sc.saController.Run(stopCh)
go sc.namespaceController.Run(stopCh)
}
1. 可以看到有
serviceaccount informer
,secret informer
和namespace informer
. 另外需要注意的是istioSecretSelector
过滤的是类型为istio.io/key-and-cert
的secret
.
2.ca certificateAuthority
需要实现以下三个方法,IstioCA
就是其中一个实现体.
type certificateAuthority interface {
Sign(csrPEM []byte, subjectIDs []string, ttl time.Duration, forCA bool) ([]byte, error)
SignWithCertChain(csrPEM []byte, subjectIDs []string, ttl time.Duration, forCA bool) ([]byte, error)
GetCAKeyCertBundle() util.KeyCertBundle
}
3. 另外需要注意的是
dnsNames
, 这个在生成证书的时候会用到, 支持自定义域名, 可以看到运行中的参数有--custom-dns-names=istio-pilot-service-account.istio-system:istio-pilot.istio-system
.
// security/pkg/k8s/controller/customdnsname.go
func ConstructCustomDNSNames(serviceAccounts []string, serviceNames []string,
namespace string, customDNSNames string) map[string]*DNSNameEntry {
result := make(map[string]*DNSNameEntry)
for i, svcAccount := range serviceAccounts {
result[svcAccount] = &DNSNameEntry{
ServiceName: serviceNames[i],
Namespace: namespace,
}
}
if len(customDNSNames) > 0 {
customNames := strings.Split(customDNSNames, ",")
log.Infof("The custom-defined DNS name list is %v", customNames)
for _, customName := range customNames {
nameDomain := strings.Split(customName, ":")
if len(nameDomain) == 2 {
override, ok := result[nameDomain[0]]
if ok {
override.CustomDomains = append(override.CustomDomains, nameDomain[1])
} else {
result[nameDomain[0]] = &DNSNameEntry{
ServiceName: nameDomain[0],
CustomDomains: []string{nameDomain[1]},
}
}
} else {
...
}
}
}
return result
}
所以
SecretController
中的dnsName
就是这么来的.
4.1 upsertSecret
作用: 为对应的
serviceaccount
创建istio.io/key-and-cert
的secret
.
func (sc *SecretController) upsertSecret(saName, saNamespace string) {
// 根据serviceaccount name和namespace 构造一个istio.io/key-and-cert类型的secret
secret := k8ssecret.BuildSecret(saName, GetSecretName(saName), saNamespace, nil,
nil, nil, nil, nil, IstioSecretType)
// 查看该secret是否已经存在于k8s中
_, exists, err := sc.scrtStore.Get(secret)
...
// 如果已经存在 直接返回
if exists {
return
}
// k8s中不存在 则生成自己的key和证书 签名是用根证书签的
chain, key, err := sc.generateKeyAndCert(saName, saNamespace)
...
// 根证书
rootCert := sc.ca.GetCAKeyCertBundle().GetRootCertPem()
secret.Data = map[string][]byte{
CertChainID: chain,
PrivateKeyID: key,
RootCertID: rootCert,
}
for i := 0; i < secretCreationRetry; i++ {
// 保存到k8s中
_, err = sc.core.Secrets(saNamespace).Create(secret)
...
}
}
1. 根据
serviceaccount name
和namespace
构造一个istio.io/key-and-cert
类型的secret
.
2. 查看该secret
是否已经存在于k8s
中.
3. 如果已经存在, 直接返回.
4. 如果k8s
中不存在, 则生成自己的key
和证书, 签名是用根证书签的.
func (sc *SecretController) generateKeyAndCert(saName string, saNamespace string) ([]byte, []byte, error) {
// 生成可以识别的名字
id := spiffe.MustGenSpiffeURI(saNamespace, saName)
// 如果有自定义域名 则加入到id中
if sc.dnsNames != nil {
// Control plane components in same namespace.
if e, ok := sc.dnsNames[saName]; ok {
if e.Namespace == saNamespace {
// Example: istio-pilot.istio-system.svc, istio-pilot.istio-system
id += "," + fmt.Sprintf("%s.%s.svc", e.ServiceName, e.Namespace)
id += "," + fmt.Sprintf("%s.%s", e.ServiceName, e.Namespace)
}
}
if e, ok := sc.dnsNames[saName+"."+saNamespace]; ok {
for _, d := range e.CustomDomains {
id += "," + d
}
}
}
options := util.CertOptions{
Host: id,
RSAKeySize: keySize,
IsDualUse: sc.dualUse,
PKCS8Key: sc.pkcs8Key,
}
// 生成key 和csr
csrPEM, keyPEM, err := util.GenCSR(options)
...
certChainPEM := sc.ca.GetCAKeyCertBundle().GetCertChainPem()
// 获得签名后的证书
certPEM, signErr := sc.ca.Sign(csrPEM, strings.Split(id, ","), sc.certTTL, sc.forCA)
...
certPEM = append(certPEM, certChainPEM...)
return certPEM, keyPEM, nil
}
5. 获得根证书, 组装信息把该
secret
保存到k8s
中.
func (sc *SecretController) refreshSecret(scrt *v1.Secret) error {
namespace := scrt.GetNamespace()
saName := scrt.Annotations[ServiceAccountNameAnnotationKey]
chain, key, err := sc.generateKeyAndCert(saName, namespace)
if err != nil {
return err
}
scrt.Data[CertChainID] = chain
scrt.Data[PrivateKeyID] = key
scrt.Data[RootCertID] = sc.ca.GetCAKeyCertBundle().GetRootCertPem()
_, err = sc.core.Secrets(namespace).Update(scrt)
return err
}
更新
secret
.
4.2 各informer的行为
总体是上图所示, 具体细节参考详细代码即可. 所以查看一个
istio.io/key-and-cert
类型的secret
,data
属性有cert-chain.pem
,key.pem
和root-cert.pem
.
[root@master istio_ca]# kubectl get secret istio.bookinfo-details -o yaml
apiVersion: v1
data:
cert-chain.pem: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURLVENDQWhHZ0F3SUJBZ0lSQUlzNzlyd0IvaUdISXppUFV4MUFPTEF3RFFZSktvWklodmNOQVFFTEJRQXcKR0RFV01CUUdBMVVFQ2hNTlkyeDFjM1JsY2k1c2IyTmhiREFlRncweU1EQXlNRFF3T0RBeU1qTmFGdzB5TURBMQpNRFF3T0RBeU1qTmFNQUF3Z2dFaU1BMEdDU3FHU0liM0RRRUJBUVVBQTRJQkR3QXdnZ0VLQW9JQkFRRGhET3hXCndXUVNMeDNjR0hsOHI1M3NRd1dGQTZBcC9PZVJ6YlpvSU5ySUVucithVVVmWHU3UGNtQ0xhNzVScy9UcENsTXgKeGpDTFU0aGZIWkk2cHBCS09xS2piZ3pIaldRbVpxTHdseWtEMWltMzNkTW9IekFaMHV3OFV0UDhpQS9GWUUzZAoxYnYxM3Q1eHlhaksxd2ZGUTF6bmt1YUhzNkdsMjZxcllzUHpSQUd4RHBVRk54T2l2cUJzUzZqQXJKWFplSWNBClFqMkl6cHArK1MvK2Z6alRNS2k4R3FiQ0M1VzZTUkpRMUxWbkIwT0RKd0JiSFYxUW1ML2FrbTNKaE9vQ1lFMzYKZG8wVzA0aWpERlZhM2E1c1NPZjdLcXNqcGZESHFZRDhXZFhhZThObmVxTVJ1aGpIdGF2cGRuQXR3YStMQXpKTApYU2drcTJ6UldBME5uRjB4QWdNQkFBR2pnWVV3Z1lJd0RnWURWUjBQQVFIL0JBUURBZ1dnTUIwR0ExVWRKUVFXCk1CUUdDQ3NHQVFVRkJ3TUJCZ2dyQmdFRkJRY0RBakFNQmdOVkhSTUJBZjhFQWpBQU1FTUdBMVVkRVFFQi93UTUKTURlR05YTndhV1ptWlRvdkwyTnNkWE4wWlhJdWJHOWpZV3d2Ym5NdlpHVm1ZWFZzZEM5ellTOWliMjlyYVc1bQpieTFrWlhSaGFXeHpNQTBHQ1NxR1NJYjNEUUVCQ3dVQUE0SUJBUUJmaDRxY1F0NUFJRzlGcWF5dXB2SExsUTVYCnJDYmNFSlRGQWFjeUZVQVJuelk4aDBWV21hYTB4WnNJVXpGaklZd2ZLVU5DRWszUHhOQ25laTlEdVV2U2hOdlYKQlZnSnBaWUR0NS91ZnVlc3M5Q2FSMTdYMTNDTFJuRFJBTExWWXNaOVpFKzB6bWRLb1NkdmdSTUZVQ0N0QXgvYwpEaFVsU3F5eFpaSk8wU1hUT1JCNGFWK3dUZDdub0UrcEl6eEF3Q1VHc2U0TjNJK1B6WWVaQkd2Zmd6cXFLOGJ2CkdRWFlPcHdKTTdjc1pwWHlDRGtxSEt0UG81dlM2cXVXMnBPUWdrYkdnV2FaYWZ4aWtvWVBJZklQMFF1NVNjWjgKNXVDRXROQnY3c2xOcmV4amJhZnJBUzFpaFZ5c3R3bGdDc0NBM0FRNExqQnlzNjQyUUo4Mko0SDFEVkUzCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K
key.pem: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcEFJQkFBS0NBUUVBNFF6c1ZzRmtFaThkM0JoNWZLK2Q3RU1GaFFPZ0tmem5rYzIyYUNEYXlCSjYvbWxGCkgxN3V6M0pnaTJ1K1ViUDA2UXBUTWNZd2kxT0lYeDJTT3FhUVNqcWlvMjRNeDQxa0ptYWk4SmNwQTlZcHQ5M1QKS0I4d0dkTHNQRkxUL0lnUHhXQk4zZFc3OWQ3ZWNjbW95dGNIeFVOYzU1TG1oN09ocGR1cXEyTEQ4MFFCc1E2VgpCVGNUb3I2Z2JFdW93S3lWMlhpSEFFSTlpTTZhZnZrdi9uODQwekNvdkJxbXdndVZ1a2tTVU5TMVp3ZERneWNBCld4MWRVSmkvMnBKdHlZVHFBbUJOK25hTkZ0T0lvd3hWV3QydWJFam4reXFySTZYd3g2bUEvRm5WMm52RFozcWoKRWJvWXg3V3I2WFp3TGNHdml3TXlTMTBvSkt0czBWZ05EWnhkTVFJREFRQUJBb0lCQVFEYS9qVlk4cFZMY0pmdwozY3dTUGQ1QjBySWpUblRqaHR0Y01UNkhzempTS2RHUGthYVdzVTFYaG1oV0kwRXV4aHZUVFozSk9KOXlaSlcyCllOTXp5WE50R0FPOVh0Q0d1cHF6RjNzRS9VTUhIYmE0MmQycEZEZzlXTmRTbUJMNmtQZ3Z5OWZadnl0SlJWZFcKaUxKOHQ2UXpHNnJqR1RyRTRGS3pUNytUU09kKzVtSDNGSGtiR05QUUpoZkJZNnRCR2k4bmFUNnhtUEpadjlhaQpXbVJpNUNjVkkrc2R4ajNOYXorcXNFRHJ3TTlLckcyTnMyQWxQejVrRndXQUEyY3U2N3BDUy9ZZFRtQkEzVGhICjIrQmp1dlBrL2pNRHhsU2IwNzZSa2JtZkZxcmY3OW9CMmxDWW42Z0Rybk5mNDFLSSs1ekJKN2w2OGo1aThqUnAKdmZBclExd3hBb0dCQU9iOFh4TnhMVFpGRGRablRUcGUwbmw5Vy9wZFpnQ2NQaE5FSzBNTHJGaWlaSzl2bS9TSAoydGEzTU1KbVlnSGpEa0NGWnh2cGRiY3BsY3BnNEIyTGh5UmtqcjA1NDZZSUplTkJhQTgzdElkdllRMm90OTMyCmQyOXlYUFdKRkowNnFBT0JZNEVOcUlzQlZuNFpJc1RpVzAwTGdNeHNHcyt5RE9zaGRueWhPdEhkQW9HQkFQbHMKQVl0ZDRQS3NkRTZYZi9PMlNTVEJWVHZuQnd2SWNwelRWbG02cnJVakFQd2RNSStOdmhoYmMrTDBEQk82RWRmRQo5dVpycE4xV3V5Mk0xVEpHU2dGdndpUWVLLzdOK3BzOXVzMEdmVnhIK2I2SEQ4bnJtWUhVWjVlR25pdysrZ3FwClFOZlFIWU9BU0FWRnl1MHB0anl1VGpDeVlwWW1Xck5lRWN5eVEwVmxBb0dBZWVSRDUrYVpqVUh6ZzJrUC8vVjUKN0ZLUzd4UEtlQmY3U2Y5M05QSThDS25wcUFxbHFlem5OdGVGQzVFcnR3TXl5aW1idDhjREw1enFSdG5JYXM3dQppZTNteFVSL05XYW5WNzEwUkZPSkdNOVZ6L2R3ejlqNFBmK0R2cTZRQ2tpaDBQZStvcU9xaFhBM1RHUEhUVTZHCkQ2bzZWYVhXb0RPOVRYZGpKM0dOc0tFQ2dZQUhBcVFnZUtrRDZSanp4SXBTSFVOOUJ2b1FUdlFCdnNhSjVkNjkKY3VQS0w0dXRpbHg5REd6VlhteXBhbGFVajF4RkJrSXlROEJFZ0ZXT2VERGQwdC90bm1pRWYxeVpNc3ppWkIvRgo5M0s2ajBOVHVaUVdCc3N2dlBxVVpiSTRhQ1M2Ky9yWWxmN2VYVktvNDBkSzF5dEtGVlFaUmtwREVoem1nYlJhClhvQkppUUtCZ1FDdDlSZll6cURRKzJ2eVFRUkhoRVhIaXpQbVRXZEN6M00xVERPSUpZcEI5VENUVE81cGwvYWcKbmVMamNjTng0czFPWm9qVDM4MXRFMndPNFRPQlRIMlJBTnBHcDBUbDRyL2RqSmdKVmpUa0Z4S0o0WG1OWEdiUApnWHBTdERWTTVrbDI1dUlKbXpVdVJSSTdIbEZmcnV4VnlYR3BUMTNUY2RHL01ib05vOE1Ianc9PQotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQo=
root-cert.pem: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUMzVENDQWNXZ0F3SUJBZ0lRY005Q2w4OCttYThzTERnWFNFaGF3akFOQmdrcWhraUc5dzBCQVFzRkFEQVkKTVJZd0ZBWURWUVFLRXcxamJIVnpkR1Z5TG14dlkyRnNNQjRYRFRJd01ESXdOREE0TURJeU0xb1hEVE13TURJdwpNVEE0TURJeU0xb3dHREVXTUJRR0ExVUVDaE1OWTJ4MWMzUmxjaTVzYjJOaGJEQ0NBU0l3RFFZSktvWklodmNOCkFRRUJCUUFEZ2dFUEFEQ0NBUW9DZ2dFQkFKOVZrLythRXV1bHhvb1F1bzNSUnlrWUpuME9udlViZ1JyRHJXWEYKekxKYVZDVUthY1lxTE5vSjBXKzFES1dyZ05weFBKcE8vUUtQeGxOdnJtK004cllVOHc0NWI5VGFLdTNxSGwvdgp1UmkrSnJZT3ZWcGlZY3A4SEc2cnI5UWtIcGJjcm1OT2VOQ1lVZGdXQ2VkMW5tWjZjZGcrT0RIYkFzVkloTmRiCjBHNDc4RU1RM3FOaWFTdGVIRXJMSDRlU045Z2VVbkhmY2hCTVFnQXN4cjJza01wUEw2VWJSMHl2dzVXM3dpWngKbWpaN2ZUYURBcy9vZ3hTV1N6LzE2eG1lVzYvbEx4N1JQNVd6ZTlkWnBPOE9xUW5BV3dlL2puTTNrQ0lNbksrUgpPTVlBRjBiU1VLKzZiT004bmZVanM4RUVJdEJvdUluYmNwNFplZkhvSFlaNk10RUNBd0VBQWFNak1DRXdEZ1lEClZSMFBBUUgvQkFRREFnSUVNQThHQTFVZEV3RUIvd1FGTUFNQkFmOHdEUVlKS29aSWh2Y05BUUVMQlFBRGdnRUIKQUcxdEU3UnFCTHpzd3ovTStuYndoeFR2VE14eHJ2RDYxVDc1eTVPRUMxUnk4RmQxbnYyT09vR0UybjUxV0N4VAp0YzFERzAvWUNrbko1a3grWEo5TElDbkdkcHFxR2VnQkNrR1RlWmpPMlk0Zko5dEZ3SWdhbVQwYmNUVjJ3NmUwClVRdUxuR1hxaytVdUFiOHFHWUZObGVlMk1LOFV5aEM0SGlEN1Z2c2xXcUdmazdTOHNtWmR0cGUyMkY2RThrbDQKSFlYNHh4RWQyRTZxVlYxbFZsMTV1TGo2V3R3Z1V0dmZMR3RvMnlyOHN3cExETHZ4SVprdWd6azc1OE1LbUljNwowOEV3bUdNVnVvUUhEcnA4NDU1NkNwZGl2NDY0dUdWeW9PaDU1WWlPcGxidWtWQzZTRE9QbTlueTRRTThRV1ZKCmYyYkRXQkpoSmRXb1FXVElPWm1wek5vPQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==
kind: Secret
metadata:
annotations:
istio.io/service-account.name: bookinfo-details
name: istio.bookinfo-details
namespace: default
...
type: istio.io/key-and-cert
[root@master istio_ca]#
5. server
rootCmd = &cobra.Command{
Use: "istio_ca",
Short: "Istio Certificate Authority (CA).",
Args: cobra.ExactArgs(0),
Run: func(cmd *cobra.Command, args []string) {
runCA()
},
}
func main() {
if err := rootCmd.Execute(); err != nil {
log.Errora(err)
os.Exit(-1)
}
}
func runCA() {
...
var webhooks map[string]*controller.DNSNameEntry
if opts.appendDNSNames {
// 生成自定义的dnsname
webhooks = controller.ConstructCustomDNSNames(webhookServiceAccounts,
webhookServiceNames, opts.istioCaStorageNamespace, opts.customDNSNames)
}
// 创建k8s client
cs, err := kubelib.CreateClientset(opts.kubeConfigFile, "")
...
// 创建IstioCA
ca := createCA(cs.CoreV1())
stopCh := make(chan struct{})
if !opts.serverOnly {
// 创建SecretController
sc, err := controller.NewSecretController(ca, opts.enableNamespacesByDefault,
opts.workloadCertTTL, opts.workloadCertGracePeriodRatio, opts.workloadCertMinGracePeriod,
opts.dualUse, cs.CoreV1(), opts.signCACerts, opts.pkcs8Keys, listenedNamespaces, webhooks,
opts.istioCaStorageNamespace, opts.rootCertFile, opts.selfSignedCA)
...
// 运行SecretController
sc.Run(stopCh)
} else {
...
}
if opts.grpcPort > 0 {
...
}
...
}
1. 生成自定义的
dnsname
.
2. 创建IstioCA
对象ca
.
3. 创建SecretController
对象sc
并运行.
6. 参考
- istio 1.3.6源码