使用Kubernetes CSR的自定义CA集成
使用Kubernetes CA部署Istio
使用Kubernetes CA部署Istio
使用istioctl以下配置在集群上部署Istio
Copy cat <<EOF > ./istio.yaml
apiVersion: install.istio.io/v1alpha1
kind: IstioOperator
spec:
pilot:
k8s:
env:
# Indicate to Istiod that we use an Custom Certificate Authority
- name: EXTERNAL_CA
value: ISTIOD_RA_KUBERNETES_API
# Tells Istiod to use the Kubernetes legacy CA Signer
- name: K8S_SIGNER
value: kubernetes.io/legacy-unknown
EOF
istioctl install --set profile=demo -f ./istio.yaml
bookinfo在bookinfo名称空间中部署示例应用程序。确保在Istio根目录中执行以下命令
Copy kubectl create ns bookinfo
kubectl apply -f <(istioctl kube-inject -f samples/bookinfo/platform/kube/bookinfo.yaml) -n bookinfo
验证安装的证书是否正确
部署工作负载后,以上这些工作负载会将CSR请求发送给Istiod,然后将其转发给Kubernetes CA进行签名。如果一切顺利,则将已签名的证书发送回安装它们的工作负载。要验证它们是否已被Kubernetes CA签名,您需要首先提取签名的证书。
转储在名称空间中运行的所有Pod。
Copy $ kubectl get pods -n bookinfo
选择任何一个正在运行的吊舱进行下一步。
获取Istio代理用于mTLS的证书链和CA根证书。
Copy $ istioctl pc secret <pod-name> -o json > proxy_secret
proxy_secret json文件在trustedCA字段中包含mTLS的CA根证书。请注意,此证书是base64编码的。
Kubernetes CA使用的证书(特别是kubernetes.io/legacy-unknown签名者)被加载到与bookinfo命名空间中的每个服务帐户关联的机密上。
Copy $ kubectl get secrets -n bookinfo
选择一个与任何服务帐户关联的秘密名称。它们的名称中有一个"令牌"。
Copy $ kubectl get secrets -n bookinfo <secret-name> -o json
在ca.crt输出字段包含base64编码Kubernetes CA证书。
将ca.cert上一步中获得的结果与前一步中的TrustedCA字段内容进行比较。这两个应该是相同的。
(可选)按照bookinfo示例中的其余步骤进行操作,以确保服务之间的通信按预期进行。
使用自定义CA
假设定制CA实现的控制器具有读取和签名Kubernetes CSR请求的必要权限。有关更多详细信息,请参考Kubernetes CSR文档。请注意,以下步骤取决于外部来源,并且可能会更改。
在Kubernetes集群中部署自定义CA控制器
在此示例中,我们使用开源证书颁发机构实现。该代码构建了一个控制器,该控制器读取Kubernetes集群上的CSR资源,并使用本地密钥创建证书。请按照页面上的说明进行操作:
生成Kubernetes manifest以部署它
将上一步中生成的Kubernetes清单部署在signer-ca-system名称空间中的本地集群上。
Copy $ kubectl apply -f local-ca.yaml
确保所有服务都在运行。
Copy $ kubectl get services -n signer-ca-system
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
signer-ca-controller-manager-metrics-service ClusterIP 10.8.9.25 none 8443/TCP 72s
获取CA的公钥。这被编码在signer-ca-system名称空间中的秘密" signer-ca- *"中。
Copy $ kubectl get secrets signer-ca-5hff5h74hm -o json
该tls.crt字段包含base64编码的公共密钥文件。记录下来以备将来使用。
将CA根证书加载到istiod可以访问的secret中
Copy $ cat <<EOF > ./external-ca-secret.yaml
apiVersion: v1
kind: Secret
metadata:
name: external-ca-cert
namespace: istio-system
data:
root-cert.pem: <tls.cert from the step above>
EOF
$ kubectl apply -f external-ca-secret.yaml
Istio必须执行此步骤,以验证工作负载证书已由正确的证书颁发机构签名,并将根证书添加到信任捆绑中以使mTLS正常工作。
部署Istio
使用istioctl以下配置在集群上部署Istio 。
Copy $ cat <<EOF > ./istio.yaml
apiVersion: install.istio.io/v1alpha1
kind: IstioOperator
spec:
components:
base:
k8s:
overlays:
# Amend ClusterRole to add permission for istiod to approve certificate signing by custom signer
- kind: ClusterRole
name: istiod-istio-system
patches:
- path: rules[-1]
value: |
apiGroups:
- certificates.k8s.io
resourceNames:
# Name of k8s external Signer in this example
- example.com/foo
resources:
- signers
verbs:
- approve
pilot:
k8s:
env:
# Indicate to Istiod that we use an external signer
- name: EXTERNAL_CA
value: ISTIOD_RA_KUBERNETES_API
# Indicate to Istiod the external k8s Signer Name
- name: K8S_SIGNER
value: example.com/foo
overlays:
- kind: Deployment
name: istiod
patches:
- path: spec.template.spec.containers[0].volumeMounts[-1]
value: |
# Mount external CA certificate into Istiod
name: external-ca-cert
mountPath: /etc/external-ca-cert
readOnly: true
- path: spec.template.spec.volumes[-1]
value: |
name: external-ca-cert
secret:
secretName: external-ca-cert
optional: true
EOF
$ istioctl install --set profile=demo -f ./istio.yaml
bookinfo在bookinfo名称空间中部署示例应用程序。
Copy $ kubectl create ns bookinfo
$ kubectl apply -f <(istioctl kube-inject -f samples/bookinfo/platform/kube/bookinfo.yaml) -n bookinfo
验证安装的自定义CA证书是否正确
部署工作负载后,以上这些工作负载会将CSR请求发送给Istiod,然后将其转发给Kubernetes CA进行签名。如果一切顺利,则将已签名的证书发送回安装它们的工作负载。为了验证Kubernetes CA确实已对它们进行了签名,您需要首先提取已签名的证书。
转储在名称空间中运行的所有Pod。
Copy $ kubectl get pods -n bookinfo
选择任何正在运行的吊舱进行下一步。
获取Istio代理用于mTLS的证书链和CA根证书。
Copy $ istioctl pc secret <pod-name> -o json > proxy_secret
该proxy_secretJSON文件包含在MTLS CA根证书trustedCA领域。请注意,此证书是base64编码的。
将以上步骤中获得的CA根证书与external-ca-cert中的" root-cert.pem"值进行比较。这两个应该是相同的。
(可选)按照bookinfo示例中的其余步骤进行操作,以确保服务之间的通信按预期进行。
证书签发原理
Copy type CertificateAuthority interface {
// Sign根据给定的CSR和TTL为工作负载或CA生成证书。
Sign (csrPEM [] byte , subjectIDs [] string , ttl time . Duration , forCA bool ) ([] byte , error )
// SignWithCertChain与Sign类似,但返回叶子证书和整个证书链。
SignWithCertChain (csrPEM [] byte , subjectIDs [] string , ttl time . Duration , forCA bool ) ([] byte , error )
// GetCAKeyCertBundle返回CA使用的KeyCertBundle。
GetCAKeyCertBundle () util . KeyCertBundle
}
判断使用哪种类型的CertificateAuthority
Copy if s.CA, err = s.createIstioCA(corev1, caOpts); err != nil {
return fmt.Errorf("failed to create CA: %v", err)
}
// 判断是否启用通过k8s csr 自定义CA
if caOpts.ExternalCAType != "" {
// 创建RA 中间机构
if s.RA, err = s.createIstioRA(s.kubeClient, caOpts); err != nil {
return fmt.Errorf("failed to create RA: %v", err)
}
}
if err = s.initPublicKey(); err != nil {
return fmt.Errorf("error initializing public key: %v", err)
}
CreateCertificate处理传入的证书签名请求(CSR).它进行身份验证和授权.验证后,签署证书以证明:SAN是身份验证结果中呼叫者的身份.主题公钥是CSR中的公钥.有效期限是请求中的ValidityDuration,如果给定的期限无效,则为默认值.它由CA签名密钥签名。
Copy func (s * Server ) CreateCertificate (ctx context . Context , request * pb . IstioCertificateRequest ) (
* pb . IstioCertificateResponse , error ) {
s.monitoring.CSR. Increment ()
caller := s. authenticate (ctx)
if caller == nil {
s.monitoring.AuthnError. Increment ()
return nil , status. Error (codes.Unauthenticated, "request authenticate failure" )
}
// TODO: Call authorizer.
_, _, certChainBytes, rootCertBytes := s.ca. GetCAKeyCertBundle (). GetAll ()
cert, signErr := s.ca. Sign (
[] byte (request.Csr), caller.Identities, time. Duration (request.ValidityDuration) * time.Second, false )
if signErr != nil {
serverCaLog. Errorf ( "CSR signing error ( %v )" , signErr. Error ())
s.monitoring. GetCertSignError (signErr.( * caerror . Error ). ErrorType ()). Increment ()
return nil , status. Errorf (signErr.( * caerror . Error ). HTTPErrorCode (), "CSR signing error ( %v )" , signErr.( * caerror . Error ))
}
respCertChain := [] string { string (cert)}
if len (certChainBytes) != 0 {
respCertChain = append (respCertChain, string (certChainBytes))
}
respCertChain = append (respCertChain, string (rootCertBytes))
response := & pb . IstioCertificateResponse {
CertChain: respCertChain,
}
s.monitoring.Success. Increment ()
serverCaLog. Debug ( "CSR successfully signed." )
return response, nil
}
k8s ra
k8s ra 的sign法法最终调用k8sSign
Copy ra. k8sSign (ra.csrInterface. CertificateSigningRequests (), csrPEM, csrName, ra.raOpts.caCertFile)
k8sSign 使用chiron签发证书,发出csrrequest
Copy func (ra * IstioRA ) k8sSign (k8sCsrInterface certclient . CertificateSigningRequestInterface ,
csrPEM [] byte , csrName string , caCertFile string ) ([] byte , error ) {
csrSpec := & cert . CertificateSigningRequestSpec {
SignerName: & ra.raOpts.caSigner,
Request: csrPEM,
Groups: [] string { "system:authenticated" },
Usages: [] cert . KeyUsage {
cert.UsageDigitalSignature,
cert.UsageKeyEncipherment,
cert.UsageServerAuth,
cert.UsageClientAuth,
},
}
certChain, _, err := chiron. SignCSRK8s (k8sCsrInterface, csrName, csrSpec, "" , caCertFile, false )
if err != nil {
return nil , caerror. NewError (caerror.CertGenError, err)
}
return certChain, err
}
ca
Sign接受PEM编码的CSR,主题ID和生存期,并返回已签名的证书.如果forCA为true,则签名证书为CA证书,否则为工作负载证书。
Copy func (ca * IstioCA ) Sign (csrPEM [] byte , subjectIDs [] string , requestedLifetime time . Duration , forCA bool ) ([] byte , error ) {
signingCert, signingKey, _, _ := ca.keyCertBundle. GetAll ()
if signingCert == nil {
return nil , caerror. NewError (caerror.CANotReady, fmt. Errorf ( "Istio CA is not ready" )) // nolint
}
csr, err := util. ParsePemEncodedCSR (csrPEM)
if err != nil {
return nil , caerror. NewError (caerror.CSRError, err)
}
lifetime := requestedLifetime
// If the requested requestedLifetime is non-positive, apply the default TTL.
if requestedLifetime. Seconds () <= 0 {
lifetime = ca.defaultCertTTL
}
// If the requested TTL is greater than maxCertTTL, return an error
if requestedLifetime. Seconds () > ca.maxCertTTL. Seconds () {
return nil , caerror. NewError (caerror.TTLError, fmt. Errorf (
"requested TTL %s is greater than the max allowed TTL %s " , requestedLifetime, ca.maxCertTTL))
}
certBytes, err := util. GenCertFromCSR (csr, signingCert, csr.PublicKey, * signingKey, subjectIDs, lifetime, forCA)
if err != nil {
return nil , caerror. NewError (caerror.CertGenError, err)
}
block := & pem . Block {
Type: "CERTIFICATE" ,
Bytes: certBytes,
}
cert := pem. EncodeToMemory (block)
return cert, nil
}