In this article, I will explain how to configure your ArgoCD server with a TLS/SSL Certificate and propagate your traffic to a custom domain name with automatically renewable certificates managed by cert-manager. I'll guide you through a detailed step-by-step process of hosting your ArgoCD server on a secure custom domain using the HTTP-01 Let's Encrypt challenge.

Steps we're going to cover:

1. Install ArgoCD on the cluster.

2. Install cert-manager.

3. Install nginx-ingress.

4. Create a cluster issuer or an issuer

5. Create an nginx ingress controller.

I am assuming you already have a running Kubernetes cluster. If not, refer to your cloud provider's documentation (if you're using a managed k8s cluster) to set one up quickly, or spin up a local cluster using k3d or Minikube.

There are a few ways to install kubectl depending on your OS. Kubernetes' official documentation covers all the installation steps for MacOs(Intel & Apple Silicon), Linux, and Windows.

Step 1: Install ArgoCD on your cluster:

It's important to consider the type of workload we're running and the access privileges we need to grant. ArgoCD's installation guide details the differences between multi-tenant, core, high availability (HA), and non-high availability setups. Review their official guide to choose the appropriate installation type for your use case.

Personally, I've installed ArgoCD in a multi-tenant, high availability (HA) configuration using the provided install.yml file, as it includes all the necessary CRDs and components. Follow this guide to set up your ArgoCD server with similar settings.

  1. Create a namespace for your ArgoCD resources.
$ kubectl create namespace argocd

2. Install ArgoCD:

$ kubectl apply -f https://github.com/argoproj/argo-cd/blob/master/manifests/ha/install.yaml

3. Set the service type to Loadbalacer:

$ kubectl patch svc argocd-server -n argocd -p '{"spec": {"type": "LoadBalancer"}}'

4. Run your ArgoCD instance locally:

$ kubectl port-forward svc/argocd-server -n argocd 8080:443

Step 2: Install cert-manager

"cert-manager creates TLS certificates for workloads in your Kubernetes or OpenShift cluster and renews the certificates before they expire."

The installation steps for cert-manager are straightforward and well-documented in the official documentation.

Versioning is key and the installation depends on the:

  • Kubernetes cluster's version,
  • kubectl version,
  • Helm version, and
  • cert-manager's version we wish to install.

Ideally, we want to be on a supported Kubernetes version . Kubernetes generally supports the three latest minor releases (for example, as of June 2024 versions 1.30.x, 1.29.x, 1.28.x are supported). kubectl is supported within one minor version (older or newer) of kube-apiserver.

Before the Installation:

Check your k8s and kubectlversion:

$ kubectl version -o yaml

This command should yield a client version which is kubectl's version and a server version which is the kube-apiserver's version, aka the cluster's version.

None
kubectl version

cert-manager's version:

Providing our cluster is on version 1.15.x or greater, we now need only to verify our Helm version is greater than v3.x:

$ helm version

The output should be something like this :

None
Helm version

Now that we've ensured all versions are supported and up-to-date, we can install cert-manager. We're going to install the release charts and the CRDs separately (always install CRDs first).

Add Helm repository:

$ helm repo add jetstack https://charts.jetstack.io --force-update

Install cert-manager CRDs:

$ kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.15.0/cert-manager.crds.yaml

Check that the CRDs are installed and in a running state:

$ kubectl get crds | grep cert-manager.io

Install cert-manager :

$ helm install \
  cert-manager jetstack/cert-manager \
  --namespace cert-manager \
  --create-namespace \
  --version v1.15.0 \

Regardless of whether you installed cert-manager using Helm or kubectl, you should verify that your deployment components are ready and up to date by checking their status.

$ kubectl get all -n cert-manager
None
cert-manager components

Step 3: Install Kubernetes nginx-ingress controller using Helm

Add chart repository:

$ helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx

Update the repository:

$ helm repo update

Install chart (this will install all the necessary components in an ingress-nginx namespace):

$ helm install ingress-nginx ingress-nginx/ingress-nginx

Verify that your installation is successful:

$ kubectl get all -n ingress-nginx
None
ingress-nginx components

Step 4: Create an Issuer or ClusterIssuer object:

cert-manager defines Issuer and ClusterIssuer resources which represent the configuration for a certificate authority(CA) like Let's Encrypt.

The primary difference between the two is scope: a ClusterIssuer can create certificates accessible across the entire cluster, while an Issuer's certificates are restricted to a specific namespace. Choose the appropriate one based on your use case. In this example, I will create a ClusterIssuer as described in the following YAML manifest file named clusterissuer.yaml :

None
clusterissuer.yaml

Where cert-manager's version is above release v0.10 the apiVersion field moves from certmanager.k8s.io to cert-manager.io. Make sure that any cert-manager custom resource manifests that refer to a deprecated API are updated to use the cert-manager.io/v1.

The kind above is a ClusterIssuer, its nameis letsencrypt-dev.

In the specsection we've defined our ACME (Automated Certificate Management Environment) client implementation, which is a protocol cert-manager uses to validate the domain ownership (usually via DNS or HTTP challenges) and request the certificate from Let's Encrypt. Once validated, the certificate is issued and stored in Kubernetes secrets. We will specify the domain in our ingress-nginx controller manifest configuration file.

Terminology:

Let's Encrypt is a certificate authority (CA) that provides free SSL/TLS certificates. These certificates are used to secure communication between a user's browser and a website, ensuring that data transferred is encrypted.

ACME is a protocol developed by Let's Encrypt to automate the process of obtaining and renewing SSL/TLS certificates. It simplifies the management of certificates by allowing servers to interact directly with the CA (Let's Encrypt) to request, renew, and revoke certificates automatically, without human intervention.

Note that ACME provides two environments:

Staging: https://acme-staging-v02.api.letsencrypt.org/directory

production: https://acme-v02.api.letsencrypt.org/directory

In the above config file I'm using the staging environment.

The fields: email: used to send you certificate expiration notice emails in case your certs don't get automatically renewed.

privateKeySecretRef : This private key will be generated and stored in your Kubernetes secrets.

http01 : The HTTP-01 challenge is a method of proving domain ownership to Let's Encrypt. This challenge involves placing a specific file on your web server at a particular URL. Let's Encrypt then tries to access this file to verify that you control the domain.

How the HTTP-01 Challenge Works(the numbers on the diagram do not match the description below):

None
SSL/TLS cert creation
  1. Create a CertificateRequest: The cert-manager initiates a request for a certificate using ACME.
  2. Create a Challenge: Let's Encrypt generates a unique token. cert-manager creates a challenge and places it on the provided domain name server in a directory named /.well-known/acme-challenge/
  3. Verify domain control: Let's Encrypt's validation server makes an HTTP request to the server to fetch the token http://yourdomain.com/.well-known/acme-challenge/<token>If Let's Encrypt can successfully retrieve the token from your server, it confirms that you control the domain.
  4. Issue Certificate: Once the token is verified, Let's Encrypt issues the SSL/TLS certificate for your domain, and stores the secret in k8s secrets.

=> You can read more about HTTP-01 here.

Create the ClusterIssuer by running this command:

$ kubectl apply -f clusterissuer.yaml

check the status of your ClusterIssuer:

$ kubectl get clusterissuer
None
clusterissuer status

Step 5: Create Ingress-nginx configuration file:

None
ingress-nginx-controller.yaml

This manifest file is a Kubernetes Ingress configuration file, its name is argocd-server-ingress, let's break it down to understand it better:

Annotations:

  • cert-manager.io/cluster-issuer: Takes the name of the ClusterIssuer you've created to acquire the certificate required for this Ingress. — Required!
  • nginx.ingress.kubernetes.io/app-root: Redirects requests coming from /to /login instead — optional but useful!
  • cert-manager.io/acme-challenge-type: Defines the challenge mechanism used which is HTTP-01 — optional.
  • nginx.ingress.kubernetes.io/ssl-passthrough: Instructs the controller to send TLS connections directly to the backend instead of letting NGINX decrypt the communication — optional.
  • nginx.ingress.kubernetes.io/backend-protocol: Specifies the protocol that the NGINX Ingress Controller should use to communicate with the backend service, which is HTTPS in this instance. You can also use the nginx.ingress.kubernetes.io/force-ssl-redirect annotation to enforce HTTP or HTTPS redirects — optional.
  • acme.cert-manager.io/http01-edit-in-place: Controls whether the ingress is modified 'in place', or a new one is created specifically for the HTTP01 challenge. the default is 'false', in that case, another ACME ingress controller will be created without an external IP, and you might face issues accessing your backend server — required!

Specification:

  • The spec configuration should be straightforward.

TLS:

  • Under tls.hosts specify your custom domain name should be the same as spec.rules[].host
  • The secretName in the tlsblock is a Kubernetes Secret that tells the Ingress Controller where to find the TLS certificate and private key used for encrypting traffic between clients and the Ingress Controller.

Apply the configurations to your cluster:

$ kubectl apply -f ingress-nginx-controller.yaml

Check that your ingress deployment is ready:

$ kubectl get ingress -n argocd
None
ingress-controller

The final step is to point your domain name to the ingress static IP address using an A record. Once done, your ArgoCD server should be securely accessible through domain.example.com!

Check your Kubernetes secrets and you shall find:

  • the privateKeySecretRef : letsencrypt-dev in a cert-manager namespace.
$ k get secret letsencrypt-dev -n argocd -o yaml
None
  • The secretName : argocd-tls certificate secret in argocd namespace.
$ k get secret argocd-tls -n argocd -o yaml
None

Conclusion:

Setting up ArgoCD with automatic renewal of TLS/SSL certificates is a straightforward process once you understand the roles and relationships between the certificate authority, the automation tools, and ArgoCD itself. This involves configuring certificate management solutions, Let's Encrypt alongside cert-manager to automate the renewal and application of TLS/SSL certificates. I hope this article has elucidated this process.