# istio ca控制器

## 使用Kubernetes CSR的自定义CA集成

## 使用Kubernetes CA部署Istio

### 使用Kubernetes CA部署Istio

1.  使用istioctl以下配置在集群上部署Istio

    ```
    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
    ```
2. bookinfo在bookinfo名称空间中部署示例应用程序。确保在Istio根目录中执行以下命令

```
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。

```
$ kubectl get pods -n bookinfo
```

选择任何一个正在运行的吊舱进行下一步。

获取Istio代理用于mTLS的证书链和CA根证书。

```
$ istioctl pc secret <pod-name> -o json > proxy_secret
```

proxy\_secret json文件在trustedCA字段中包含mTLS的CA根证书。请注意,此证书是base64编码的。

Kubernetes CA使用的证书(特别是kubernetes.io/legacy-unknown签名者)被加载到与bookinfo命名空间中的每个服务帐户关联的机密上。

```
$ kubectl get secrets -n bookinfo
```

选择一个与任何服务帐户关联的秘密名称。它们的名称中有一个"令牌"。

```
$ 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控制器

1. 在此示例中,我们使用开源证书颁发机构实现。该代码构建了一个控制器,该控制器读取Kubernetes集群上的CSR资源,并使用本地密钥创建证书。请按照页面上的说明进行操作:
   1. 构建证书控制器docker 镜像
   2. 将镜像上传到Docker registry
   3. 生成Kubernetes manifest以部署它
2. 将上一步中生成的Kubernetes清单部署在signer-ca-system名称空间中的本地集群上。

```
$ kubectl apply -f local-ca.yaml
```

确保所有服务都在运行。

```
$ 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
```

1. 获取CA的公钥。这被编码在signer-ca-system名称空间中的秘密" signer-ca- \*"中。

```
$ kubectl get secrets signer-ca-5hff5h74hm -o json
```

该tls.crt字段包含base64编码的公共密钥文件。记录下来以备将来使用。

### 将CA根证书加载到istiod可以访问的secret中

1. 将机密加载到istiod名称空间中。

```
$ 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 。

```
$ 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
```

1. bookinfo在bookinfo名称空间中部署示例应用程序。

```
$ 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。

```
$ kubectl get pods -n bookinfo
```

选择任何正在运行的吊舱进行下一步。

获取Istio代理用于mTLS的证书链和CA根证书。

```
$ istioctl pc secret <pod-name> -o json > proxy_secret
```

该proxy\_secretJSON文件包含在MTLS CA根证书trustedCA领域。请注意,此证书是base64编码的。

将以上步骤中获得的CA根证书与external-ca-cert中的" root-cert.pem"值进行比较。这两个应该是相同的。

(可选)按照bookinfo示例中的其余步骤进行操作,以确保服务之间的通信按预期进行。

## 证书签发原理

```go
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

```
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签名密钥签名。

```go
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

```go
ra.k8sSign(ra.csrInterface.CertificateSigningRequests(), csrPEM, csrName, ra.raOpts.caCertFile)
```

k8sSign 使用chiron签发证书,发出csrrequest

```go
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证书,否则为工作负载证书。

```go
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
}
```
