证书控制器chiron
证书控制器chiron
Chiron是与Istiod链接的轻量级组件,该组件使用Kubernetes CA API签署证书而无需维护其私钥。使用此功能具有以下优点:
不像istiod,此功能不需要维护私有签名密钥,从而增强了安全性。
简化了向TLS客户端的根证书分发。客户不再需要等待Istiod生成和分发其CA证书。
配置提供证书
cat < ./istio.yaml apiVersion: install.istio.io/v1alpha1 kind: IstioOperator spec: meshConfig: certificates:
secretName: dns.example1-service-account
dnsNames: [example1.istio-system.svc, example1.istio-system]
secretName: dns.example2-service-account
dnsNames: [example2.istio-system.svc, example2.istio-system]
EOF
istioctl install -f ./istio.yaml
代码
初始化证书控制器
if err := s.initCertController(args); err != nil {
return fmt.Errorf("error initializing certificate controller: %v", err)
}
具体逻辑
func (s *Server) initCertController(args *PilotArgs) error {
var err error
var secretNames, dnsNames, namespaces []string
//获取网格配置
meshConfig := s.environment.Mesh()
if meshConfig.GetCertificates() == nil || len(meshConfig.GetCertificates()) == 0 {
log.Info("No certificates specified, skipping K8S DNS certificate controller")
return nil
}
k8sClient := s.kubeClient
// 读取上述配置的certificates
for _, c := range meshConfig.GetCertificates() {
name := strings.Join(c.GetDnsNames(), ",")
if len(name) == 0 { // 必须包含至少一个DNS name
continue
}
if len(c.GetSecretName()) > 0 {
// Chiron 将生成key和证书保存到secret
secretNames = append(secretNames, c.GetSecretName())
dnsNames = append(dnsNames, name)
namespaces = append(namespaces, args.Namespace)
}
}
// 设置和管理非pilot服务的证书.如果服务为空,则证书控制器将不执行任何操作。
s.certController, err = chiron.NewWebhookController(defaultCertGracePeriodRatio, defaultMinCertGracePeriod,
k8sClient.CoreV1(), k8sClient.AdmissionregistrationV1beta1(), k8sClient.CertificatesV1beta1(),
defaultCACertPath, secretNames, dnsNames, namespaces)
if err != nil {
return fmt.Errorf("failed to create certificate controller: %v", err)
}
s.addStartFunc(func(stop <-chan struct{}) error {
go func() {
// 启动Chiron 管理certificates的生命周期
s.certController.Run(stop)
}()
return nil
})
return nil
}
在启动时
func (wc *WebhookController) Run(stopCh <-chan struct{}) {
// 创建证书
for i, secretName := range wc.secretNames {
err := wc.upsertSecret(secretName, wc.dnsNames[i], wc.serviceNamespaces[i])
if err != nil {
log.Errorf("error when upserting secret (%v) in ns (%v): %v", secretName, wc.serviceNamespaces[i], err)
}
}
...
}
创建WebhookController
func NewWebhookController(gracePeriodRatio float32, minGracePeriod time.Duration,
core corev1.CoreV1Interface, admission admissionv1beta1.AdmissionregistrationV1beta1Interface,
certClient certclient.CertificatesV1beta1Interface, k8sCaCertFile string,
secretNames, dnsNames, serviceNamespaces []string) (*WebhookController, error) {
...
c := &WebhookController{
gracePeriodRatio: gracePeriodRatio,
minGracePeriod: minGracePeriod,
k8sCaCertFile: k8sCaCertFile,
core: core,
admission: admission,
certClient: certClient,
secretNames: secretNames,
dnsNames: dnsNames,
serviceNamespaces: serviceNamespaces,
certUtil: certutil.NewCertUtil(int(gracePeriodRatio * 100)),
}
// 读取CA.
_, err := reloadCACert(c)
if err != nil {
return nil, err
}
if len(dnsNames) == 0 {
log.Warn("the input services are empty, no services to manage certificates for")
} else {
// watch istio.io/dns-key-and-cert类型的secret
istioSecretSelector := fields.SelectorFromSet(map[string]string{"type": IstioDNSSecretType}).String()
scrtLW := listwatch.MultiNamespaceListerWatcher(serviceNamespaces, func(namespace string) cache.ListerWatcher {
return &cache.ListWatch{
ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
options.FieldSelector = istioSecretSelector
return core.Secrets(namespace).List(context.TODO(), options)
},
WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
options.FieldSelector = istioSecretSelector
return core.Secrets(namespace).Watch(context.TODO(), options)
},
}
})
c.scrtStore, c.scrtController =
cache.NewInformer(scrtLW, &v1.Secret{}, secretResyncPeriod, cache.ResourceEventHandlerFuncs{
DeleteFunc: c.scrtDeleted,
UpdateFunc: c.scrtUpdated,
})
}
return c, nil
}
当证书被删除时
scrtDeleted
func (wc *WebhookController) scrtDeleted(obj interface{}) {
log.Debugf("enter WebhookController.scrtDeleted()")
scrt, ok := obj.(*v1.Secret)
if !ok {
log.Warnf("failed to convert to secret object: %v", obj)
return
}
scrtName := scrt.Name
if wc.isWebhookSecret(scrtName, scrt.GetNamespace()) {
log.Infof("re-create deleted Istio secret %s in namespace %s", scrtName, scrt.GetNamespace())
dnsName, found := wc.getDNSName(scrtName)
if !found {
log.Errorf("failed to find the DNS name of the secret: %v", scrtName)
return
}
// 重新生成证书
err := wc.upsertSecret(scrtName, dnsName, scrt.GetNamespace())
if err != nil {
log.Errorf("re-create deleted Istio secret %s in namespace %s failed: %v",
scrtName, scrt.GetNamespace(), err)
}
}
}
当证书被更新时
scrtUpdated
// scrtUpdated()在收到更新事件进行回调. 用于证书轮转
func (wc *WebhookController) scrtUpdated(oldObj, newObj interface{}) {
......
//解析证书,无法解析则更新
certBytes := scrt.Data[ca.CertChainID]
_, err := util.ParsePemEncodedCertificate(certBytes)
if err != nil {
log.Warnf("failed to parse certificates in secret %s/%s (error: %v), refreshing the secret.",
namespace, name, err)
//更新证书
if err = wc.refreshSecret(scrt); err != nil {
log.Errora(err)
}
return
}
_, waitErr := wc.certUtil.GetWaitTime(certBytes, time.Now(), wc.minGracePeriod)
//当 证书过期或者证书CA非K8S CA生成则重新签发
caCert, err := wc.getCACert()
if err != nil {
log.Errorf("failed to get CA certificate: %v", err)
return
}
if waitErr != nil || !bytes.Equal(caCert, scrt.Data[ca.RootCertID]) {
log.Infof("refreshing secret %s/%s, either the leaf certificate is about to expire "+
"or the root certificate is outdated", namespace, name)
//更新证书
if err = wc.refreshSecret(scrt); err != nil {
log.Errorf("failed to update secret %s/%s (error: %s)", namespace, name, err)
}
}
}
upsertSecret/refreshSecret分别将调用GenKeyCertK8sCA生成证书,GenKeyCertK8sCA又将调用SignCSRK8s发出请求
SignCSRK8s 1. 发起CSR请求 2. Approve CSR 3. 读取certificate 4.删除依赖
Last updated