argoproj / argo-cd

Declarative Continuous Deployment for Kubernetes
https://argo-cd.readthedocs.io
Apache License 2.0
18.01k stars 5.49k forks source link

Provide a easy way to add a custom CA #7572

Open klausenbusk opened 3 years ago

klausenbusk commented 3 years ago

Summary

Argo supports adding a custom TLS certificate per repository, but it isn't very scalable if you ex: want to MITM all traffic.

Motivation

In our case we want to MITM all traffic with a proxy and we don't necessarily know all server names up front. Adding a TLS certificate per server name is cumbersome and the only alternative seems to BYOI (add the certificate and run update-ca-certificates).

Proposal

We could perhaps use the existing infrastructure and support a "default certificate" in: https://github.com/argoproj/argo-cd/blob/b37eee1054e42c873699460dd5e2447c2f9fe5a6/util/cert/cert.go#L321-L334

jgallucci32 commented 2 years ago

This can be achieved by creating a Kubernetes secret and adding it as volume mounts to the argo-cd pods. Since ArgoCD is written in Go it will automatically look for the custom CA certs placed in /etc/ssl/certs/. Here are the steps:

  1. Create a Kubernetes secret called custom-ca-certificates using your custom CA bundle:
    kubectl create secret generic custom-ca-certificates --from-file=custom-ca-certificates.crt=/path/to/custom/bundle.crt
  2. Add volume and volumeMount parameters to the Helm values.yaml answer file:
    controller:
    volumeMounts:
      - name: custom-ca-certificates
        mountPath: /etc/ssl/certs/custom-ca-certificates.crt
        subPath: custom-ca-certificates.crt
    volumes:
      - name: custom-ca-certificates
        secret:
          defaultMode: 420
          secretName: custom-ca-certificates
    repoServer:
    volumeMounts:
      - name: custom-ca-certificates
        mountPath: /etc/ssl/certs/custom-ca-certificates.crt
        subPath: custom-ca-certificates.crt
    volumes:
      - name: custom-ca-certificates
        secret:
          defaultMode: 420
          secretName: custom-ca-certificates
    server:
    volumeMounts:
      - name: custom-ca-certificates
        mountPath: /etc/ssl/certs/custom-ca-certificates.crt
        subPath: custom-ca-certificates.crt
    volumes:
      - name: custom-ca-certificates
        secret:
          defaultMode: 420
          secretName: custom-ca-certificates
  3. Upgrade Helm chart and it now can use custom certificates from your SSL proxy or other sites with custom signed certificates.
Morriz commented 2 years ago

The operator has no such fields. Any insights?

WesselAtWork commented 1 year ago

I had multiple certs (root and intermediaries) so I mounted the certs on /etc/ssl/certs. Sadly that overrides EVERYTHING on that. Everything was fine till I needed to do helm charts. Our helm charts uses some upstream charts and while connecting to our main repo worked fine, connecting to the upstream failed. Fixed it with:

initContainers:
  name: generate-certs
  image: docker.io/alpine/curl:latest
  command:
    - "/usr/sbin/update-ca-certificates"
  securityContext:
    runAsUser: 0
    runAsGroup: 0
    runAsNonRoot: false
    allowPrivilegeEscalation: true
    readOnlyRootFilesystem: false
    capabilities:
      add:
      - ALL
  volumeMounts:
   - name: ssl-certs
      mountPath: /etc/ssl/certs
   - name: ssl-local
      mountPath: /usr/local/share/ca-certificates
  volumes:
   - name: ssl-certs
     emptyDir: {}
   - name: ssl-local
     emptyDir: {}

I put this init job on every application that required the certs:

I don't think you need the insanely privileged securityContext. I was struggling getting it to work because I accidentally mounted /usr/share/ca-certificates which overode the /mozilla which was were all the default certs were and update-ca-certificates --fresh wasn't working the way I thought it was. If you can do it with less privileges (or specific privileges) I'd love to see them.

lknite commented 1 year ago

I've been using the work around to use a mount to overwrite /etc/ssl/certs but this is not a full solution. Overwriting /etc/ssl/certs with my onprem certs means that some certs normally verified with the default /etc/ssl/certs were are no longer verified, such as dev.azure.com in my situation. I can add them manually but eventually they expire.

In the past I have copied the ca-certificates bundle which comes with linux by default, appended my onprem certs, then added this to a configmap or secret, but it turned out the file was too big so this work-around does not always work.

We need a way to specify onprem certs that will then be added using whatever method the source argocd image uses such as 'update-ca-trust' or 'update-ca-certificates' after adding to an import directory.

lknite commented 1 year ago

I wrote up a way to implement this at another site, maybe we could use the same technique: https://github.com/bitnami/charts/issues/19744

global:
  # specify by providing the name of a secret and use all data as .crt files
  ca-bundle-secret: ca-bundle
  # or specify directly
  ca-bundle: |
    -----BEGIN CERTIFICATE-----
    MIIFrDCCBJSgAwIBAgIQCfluwpVVXyR0nq8eXc7UnTANBgkqhkiG9w0BAQwFADBh
    MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
    -----END CERTIFICATE-----
    -----BEGIN CERTIFICATE-----
    MIIFrDCCBJSgAwIBAgIQCfluwpVVXyR0nq8eXc7UnTANBgkqhkiG9w0BAQwFADBh
    MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
    -----END CERTIFICATE-----
lknite commented 1 year ago

Summary

Here's a pvc-based workaround inspired by @WesselAtWork (needed to get around size limitations of a configmap / secret, otherwise we could just mount one of those):

  1. mount one or more certs from a ca-bundle secret to the /tmp/source folder
  2. loop through certs and split into multiple cert files as needed, save to /usr/local/share/ca-certificates
  3. run update-ca-certificates to import the additional certs
  4. copy the /etc/ssl/certs folder contents to /tmp/target (which is the pvc)
  5. mount the pvc to /etc/ssl/certs in the pod

Requirements

Recommend

Additional

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: ca-bundle-server
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 2Mi
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: ca-bundle-reposerver
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 2Mi

initContainers

  server:
    initContainers:
    - name: get-ca-bundle
      image: docker.io/alpine/curl:latest
      command:
      - "/bin/sh"
      - "-c"
      args:
      - |
        for FILE in /tmp/source/*; do awk 'BEGIN {c=0;} /BEGIN CERT/{c++} { print > "/usr/local/share/ca-certificates/cert." c ".crt"}' < $FILE; done
        /usr/sbin/update-ca-certificates
        rm -rf /tmp/target/*
        cp -r /etc/ssl/certs/* /tmp/target/
      volumeMounts:
      - name: ca-bundle
        mountPath: /tmp/source
      - name: pvc-ca-bundle
        mountPath: /tmp/target
    volumes:
    - name: ca-bundle
      secret:
        secretName: ca-bundle
    - name: pvc-ca-bundle
      persistentVolumeClaim:
        claimName: ca-bundle-server

    volumeMounts:
    - name: pvc-ca-bundle
      mountPath: "/etc/ssl/certs"

  repoServer:
    initContainers:
    - name: get-ca-bundle
      image: docker.io/alpine/curl:latest
      command:
      - "/bin/sh"
      - "-c"
      args:
      - |
        for FILE in /tmp/source/*; do awk 'BEGIN {c=0;} /BEGIN CERT/{c++} { print > "/usr/local/share/ca-certificates/cert." c ".crt"}' < $FILE; done
        /usr/sbin/update-ca-certificates
        rm -rf /tmp/target/*
        cp -r /etc/ssl/certs/* /tmp/target/
      volumeMounts:
      - name: ca-bundle
        mountPath: /tmp/source
      - name: pvc-ca-bundle
        mountPath: /tmp/target
    volumes:
    - name: ca-bundle
      secret:
        secretName: ca-bundle
    - name: pvc-ca-bundle
      persistentVolumeClaim:
        claimName: ca-bundle-reposerver

    volumeMounts:
    - name: pvc-ca-bundle
      mountPath: "/etc/ssl/certs"
lknite commented 1 year ago

The idea to just mount a configmap /secret should not be considered a solution. This works in most cases but requires a person to start googling until they find out the right location to put the file vs just searching for 'bundle' or 'cert' in the values.yaml and being done. Why not just let the user specify a secret to use and put it where it needs to go ... the consumer shouldn't have to know what language or image file is being used to get the onprem certs installed.

hamelg commented 1 year ago

Another workaround with the operator by specifying volumes and volumeMounts in the crd argocds.argoproj.io, ca certificats are in the configmap ca-certs :

  repo:
    volumeMounts:
      - mountPath: /etc/ssl/certs/ca-bundle-private.crt
        name: ca-certs
        subPath: ca-bundle.crt
    volumes:
      - configMap:
          name: ca-certs
        name: ca-certs
jmanuelortizn commented 1 year ago

I am facing the same issue when pulling docker images from a custom private registry with self signed cert.

Failed to pull image "docker.pvt-registry.net/image-services:2.0.0.76-develop": 
rpc error: code = Unknown desc = failed to pull and unpack image "docker.pvt-registry.net/image-services:2.0.0.76-develop": 
failed to resolve reference "docker.pvt-registry.net/image-services:2.0.0.76-develop": 
failed to do request: Head "https://docker.pvt-registry.net/v2/image-service/manifests/2.0.0.76-develop": 
x509: certificate signed by unknown authority

Is there other work around that worked for anyone?

jle-pass commented 10 months ago

+1