Kubernetes将CA证书添加到Pod的信任根目录

29
在我的10台裸机Kubernetes集群中,一个服务需要调用另一个使用自签名证书的https服务。然而,由于这个自签名证书没有添加到pod的受信任根CA中,调用失败并提示无法验证x.509证书。
所有的pod都是基于ubuntu docker镜像构建的。然而,在ubuntu上将ca证书添加到信任列表的方法(使用dpkg-reconfigure ca-certificates)在这个pod上不再起作用。当然,即使我成功地将ca证书添加到一个pod的信任根上,当另一个pod启动时它就会消失。
我搜索了Kubernetes文档,惊讶地发现除了配置证书与API服务通信之外,没有找到任何其他内容,而这不是我正在寻找的。如果需要在pod之间建立安全通道,应该是非常普遍的情况。有什么想法吗?
4个回答

21

更新编辑,读取选项3:

如果我处于您的情境中,我可以想到3种解决问题的方法:

选项1)

(我能提供的唯一完整解决方案,不幸的是我的其他解决方案只是半个解决方案,感谢Paras Patidar/以下网站 :)

  1. 将证书添加到配置映射中: 假设你的PEM文件为my-cert.pem
    kubectl -n <namespace-for-config-map-optional> create configmap ca-pemstore — from-file=my-cert.pem

  2. 将配置映射作为容器现有CA根位置的卷挂载: 在卷挂载中,将该配置映射的文件作为一个对应于/etc/ssl/certs/目录下的文件的一对一关系进行挂载

apiVersion: v1 
kind: Pod
metadata:
  name: cacheconnectsample
spec:
      containers:
      - name: cacheconnectsample
        image: cacheconnectsample:v1
        volumeMounts:
        - name: ca-pemstore
          mountPath: /etc/ssl/certs/my-cert.pem
          subPath: my-cert.pem
          readOnly: false
        ports:
        - containerPort: 80
        command: [ "dotnet" ]
        args: [ "cacheconnectsample.dll" ]
      volumes:
      - name: ca-pemstore
        configMap:
          name: ca-pemstore

我认为这里的想法是/etc/ssl/certs/是pod信任的tls证书的位置,而subPath方法允许您添加一个文件,而不会清除含有k8s secrets的文件夹的内容。

如果所有的pod都共享这个mountPath,那么你可能能够为每个命名空间添加一个存在的pod和configmap,但这只对静态命名空间有用。但是如果这是真的,那么你的所有pod都会信任那个证书。

Option 2):

理论上,您应该能够利用cert-manager + external-dns + Lets Encrypt Free +公共域名来替换自签名证书。在您的集群中,cert-manager的最终结果是自动生成由Lets Encrypt Free签名的k8s tls secret,他们有一个dns01挑战可以用来证明您拥有该证书,这意味着即使没有入口/即使集群仅用于私有网络,您也应该能够利用该解决方案。

Option 3):

我认为switchboard.op的答案可能是最好的/应该成为被接受的答案。这个东西可以在运行时完成,但我认为它永远不应该在运行时完成,因为在运行时完成这个操作非常hacky且充满了边缘情况/没有一个通用的解决方案。

同时,我的Option 1只是部分正确的。仅仅将ca.crt挂载到pod上是不够的。在该文件挂载到pod之后,您需要运行一个命令来信任它。这意味着您可能需要覆盖pod的启动命令。例如,您不能做像连接数据库(默认启动命令)然后更新受信任的CA证书的命令。您必须覆盖启动文件以手工编写、覆盖默认启动脚本、更新受信任的CA证书、连接数据库。问题是Ubuntu、RHEL、Alpine和其他操作系统有不同的位置,您必须在其中安装CA证书,并且有时需要不同的命令来信任CA证书,因此在集群中应用于所有pod以更新其ca.cert的运行时通用解决方案并非不可能,但会需要大量的if语句和变异的webhooks/复杂性。如果您只需要能够为单个pod动态更新它,则可以使用手工制作的每个pod解决方案。

switchboard.op的答案是我要回答的方式。通过将自定义的ca.cert信任嵌入到新的自定义docker镜像中,构建一个新的自定义docker镜像。这是一种通用的解决方案,并大大简化了YAML的一面。在docker镜像方面,这相对容易做到。


方法一对我有帮助。谢谢。我使用了Secret而不是ConfigMap来部署根CA证书。 - Umar Farooq Khawaja

17
如果你想在构建时嵌入证书,请编辑你的Dockerfile,添加从构建上下文中复制证书并更新信任的命令。你甚至可以将这个作为一个层添加到来自docker hub等地方的内容中。
COPY my-cert.crt /usr/local/share/ca-certificates/
RUN update-ca-certificates

如果你试图在运行时更新信任,事情会变得更加复杂。我自己没有做过这个操作,但你可以尝试创建一个包含证书的configMap,将其挂载到上面提到的路径中的容器中,然后使用入口点脚本在主进程之前运行update-ca-certificates


2
主要缺点是在启动时使用 update-ca-certificates 的方式是,如果容器未以 root 用户身份运行,则很可能会失败... initContainers 是另一个更复杂的选项。 - Gert van den Berg
1
@GertvandenBerg,我认为你误解了一些东西。如果在运行的 pod 上运行 update-ca-certificates,则会引入作为 root 运行的限制,但这不是他所说的。他说的是在 Docker 镜像构建期间运行该命令(这是完全可以的,因为它们允许您在自定义容器的构建的 1 个阶段中暂时成为 root 以获得提升的权限),然后在发布的容器镜像中变为非 root 用户。 - neokyle
@neokyle 没错,你说得对... 通过一些策略性的 emptyDir 卷和一个 init 容器,可能可以让 update-ca-certificates 在没有 root 权限的情况下运行...(我想我错过了它在 Dockerfile 中的位置)(如果证书在构建时可用,则使用 Dockerfile 更好)。 - Gert van den Berg
在添加了您的建议后,构建dockerfile时我遇到了以下错误:“无法通过模板‘/tmp/ca-certificates.crt.tmp.XXXXXX’创建文件:没有这样的文件或目录”,您能帮忙吗? - HC LW
这个选项似乎相当有限,意味着可部署的工件必须在私有CA和Docker镜像之间保持1:1的关系,而它应该是不可知的,以便可以部署到多个不同的网络配置和不同的私有证书颁发机构。 - Joel Almeida
2
@JoelAlmeida 的说法。容器镜像应该是通用的。我认为唯一真正的解决办法是使用 initContainer 将添加的 CA 证书挂载到适当的位置,并运行 update-ca-certificates,然后将整个 /etc/pki/ca-trust 目录复制到一个 emptyDir 中,以便在后续的容器中进行挂载。我曾考虑过将整个目录转换为 Secrets 并进行挂载,但其大小超过了1MB。使用带有 update-ca-certificates 的 initContainer 的好处是它可以适用于以后出现的任何 - Java、OpenSSL、邮件服务器等情况。 - Marvin

6

仅供好奇,这里是一个使用初始化容器方法的清单示例。

---
apiVersion: v1
kind: ConfigMap
metadata:
  name: demo
data:
  # in my case it is CloudFlare CA used to sign certificates for origin servers
  origin_ca_rsa_root.pem: |
    -----BEGIN CERTIFICATE-----
    ...
    -----END CERTIFICATE-----
---
apiVersion: v1
kind: Pod
metadata:
  name: demo
  labels:
    name: demo
spec:
  nodeSelector:
    kubernetes.io/os: linux
  initContainers:
  - name: init
    # image: ubuntu
    # command: ["/bin/sh", "-c"]
    # args: ["apt -qq update && apt -qq install -y ca-certificates && update-ca-certificates && cp -r /etc/ssl/certs/* /artifact/"]
    # # alternative image with preinstalled ca-certificates utilities
    image: grafana/alpine:3.15.4
    command: ["/bin/sh", "-c"]
    args: ["update-ca-certificates && cp -r /etc/ssl/certs/* /artifact/"]
    volumeMounts:
    - name: demo
      # note - we need change extension to crt here
      mountPath: /usr/local/share/ca-certificates/origin_ca_rsa_root.crt
      subPath: origin_ca_rsa_root.pem
      readOnly: false
    - name: tmp
      mountPath: /artifact
      readOnly: false
  containers:
  - name: demo
    # note - even so init container is alpine base, and this one is ubuntu based everything still works
    image: nginx
    ports:
      - containerPort: 80
    volumeMounts:
      - name: tmp
        mountPath: /etc/ssl/certs
        readOnly: false
  volumes:
    - name: demo
      configMap:
        name: demo
    # will be used to pass files between init container and actual container
    - name: tmp
      emptyDir: {}

以及它的使用:

kubectl apply -f demo.yml
kubectl exec demo -c demo -- curl --resolve foo.bar.com:443:10.0.14.14 https://foo.bar.com/swagger/v1/swagger.json
kubectl delete -f demp.yml

注意事项:

  • 将foo.bar.com替换为您的域名
  • 将10.0.14.14替换为入口控制器集群IP
  • 您可能需要添加-vv标志以查看更多细节

实际上这有点丑陋和庞大,但至少它能够工作并证明了概念。使用简单ConfigMap的解决方法不起作用,因为curl会读取ca-certificates.crt文件,而在该方法中没有对其进行修改。


不是所有的英雄都戴着帽子!谢谢 - r4cc00n

2
我遇到了同样的问题,这里提供的选项都是很好的起点。initContainers方法很有前途,但在那里遇到了一些特定于应用程序的问题,对我不利。最后,使用生命周期钩子的方法对我有效。
  1. 使用一个证书文件创建一个configMap,其中只包含一个我需要信任的根CA证书:
kubectl -n my-namespace create configmap my-cert --from-file=root_ca_only.crt

将configMap作为一个卷添加到我的部署中:
...
volumes:
  - name: my-cert
    configMap:
      defaultMode: 420
      name: my-cert
...

将证书安装到我的容器中的 /usr/local/share/ca-certificates/ 目录下。
...
volumeMounts:
  - mountPath: /usr/local/share/ca-certificates/root_ca_only.crt
          name: my-cert
          subPath: root_ca_only.crt
...

使用postStart生命周期钩子来运行update-ca-certificates,我需要延迟一些特定于应用程序的事情,但我会将它保留在这里,以防其他人遇到相同的问题。
lifecycle:
  postStart:
    exec:
      command:
      - sh
      - -c
      - sleep 10; update-ca-certificates

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接