logo

Setup Harbor Registry with Traefik as Ingress Controller and LetsEncrypt Certificate

For this post, I’ll document how to setup Harbor registry using Traefik as an ingress controller with a valid certificate from LetsEncrypt . Documentation around the topic is scattered in different places and people just assumes you’ll figure out the trivial details. So without further ado… let’s start with a quick pre-requisites.

Pre-requisite

As an image registry, Harbor needs to have a valid certificate for it to be trusted endpoint when pushing images. Otherwise, you need to manually trust the insecure certificate to be able to interact with it. In a kubernetes cluster this is especially painful given the capabilities to scale-in/out the nodes like what Tanzu Kubernetes offers. Luckily, there are options to get around this which does not cost an arm and a leg.

DISCLAIMER: The following are my personal way of doing it. Vendors and configurations are of my personal preference. There are multiple ways to skin a cat… this is just one of them 🙂

What we need to get started

  • Public Domain Name
    • We will be using this as the domain for our harbor registry.
    • This will set you up around 3-5$ per year depending on how complex the domain is.
  • Cloudflare managed Domain Name
    • This is a free service.
    • CloudFlare is chosen as it will be used to validate LetsEncrypt handshake for issuance of the trusted certificate.

Setting up Cert-Manager with Cloudflare

The following assumes you already have cert-manager running in your kubernetes cluster. In case you need you to install, cert-manager has an awesome documentation you can follow.

Installation steps: https://cert-manager.io/docs/installation/

After installing cert-manager, we need the following high-level steps:

  • Manage your domain name with Cloudflare
  • Create an API key from Cloudflare that allows us to call/create DNS resource.
    • DNS handshake from Cloudflare to LE removes dependency on the connectivity of our on-premise setup. The only requirement for this setup to work is to have cert-manager out-bound call to cloudflare API to create the TXT resource and LE to request cert.
  • Create a secret based on the API key from cloud-flare on the cert-manager namespace.
  • Create a CluterIssuer resource with the secret referenced. This kind of resource allows us to use it throughout the cluster regardless of the namespace.
  • For testing purpose, we can create a the LetsEncrypt Staging endpoint to make sure its working before switching to the production. This is recommended by LE as they *may* block your domain if you set up your environment incorrectly against the production LE endpoint.

REFERENCE: https://cert-manager.io/docs/configuration/acme/dns01/cloudflare/

ClusterIssuer manifest

apiVersion: cert-manager.io/v1alpha2
kind: ClusterIssuer
metadata:
  name: letsencrypt-stg
  namespace: cert-manager
spec:
  acme:
    # staging
    server: https://acme-staging-v02.api.letsencrypt.org/directory
    privateKeySecretRef:
      name: letsencrypt
    solvers:
    - dns01:
        cloudflare:
          email: <YOUR EMAIL>
          apiTokenSecretRef:
            name: cloudflare-api-token
            key: api-token
---
apiVersion: cert-manager.io/v1alpha2
kind: ClusterIssuer
metadata:
  name: letsencrypt-prod
  namespace: cert-manager
spec:
  acme:
    # production
    server: https://acme-v02.api.letsencrypt.org/directory 
    privateKeySecretRef:
      name: letsencrypt
    solvers:
    - dns01:
        cloudflare:
          email: <YOUR EMAIL>
          apiTokenSecretRef:
            name: cloudflare-api-token
            key: api-token

Creating the Harbor Certificate

Once we have ClusterIssuer setup, we need to request the certificate for harbor to use.

  • Create a namespace where we will install harbor
  • Create a certificate resource on the namespace where harbor will be installed. This is because certificate/secret resource are namespaced.

Certificate Request Manifest:

apiVersion: cert-manager.io/v1alpha2
kind: Certificate
metadata:
  name: harbor-cert
  namespace: harbor
spec:
  secretName: harbor-cert
  issuerRef:
    name: letsencrypt-prod
    kind: ClusterIssuer
  commonName: <FQDN of the harbor resource>
  dnsNames:
    - <FQDN of the harbor resource, any additional SAN>
  • After creation of the certificate object, wait around 1-2 minutes for the certificate to be request.
  • You can get the status of the certificate request by issuing the following
That’s two secret because the fist one is from staging

Setting up Traefik

Once we have the issuer ready, now it’s time to setup Traefik as an ingress controller. Documentation to install is fully documented in their website. Personally, I prefer installing the CRD’s and deployments manually. Refernece for the CRD’s to use is documented here: https://doc.traefik.io/traefik/reference/dynamic-configuration/kubernetes-crd/

Set-up entry-points for Notary

In the static configuration of Traefik (I prefer to use configmap), make sure to open up 4443 which is the port Notary needs to expose it’s service. In addition, its also a good time to make sure the proper providers are setup in the static config.

NOTE: We won’t be using the ingressClass as the Certificates will be installed directly to Harbor. Traefik will be configured to passthrough.

Static Config:

    providers:
      kubernetesCRD: {}
      kubernetesIngress: 
        ingressClass: "traefik-certmanager"

    entryPoints:
      web:
        address: ":80"

      web-secure:
        address: ":443" 
      
      web-4443:
        address: ":4443" 
      
      admin:
        address: ":8080"

Setting up Harbor

Harbor is installed using helm (shame) which is customized by passing in a values.yaml. The complete values definition is included in the github page here: https://github.com/goharbor/harbor-helm/blob/master/values.yaml

For this setup, we would be setting up the values.yaml as follows:

expose:
# we will use clusterIP as Traefik will be fronting our application
  type: clusterIP
  tls:
    enabled: true
    certSource: secret
    auto:
      commonName: ""
    secret:
      # specify secret resource created by cert-manager in the previous step.
      secretName: "harbor-cert"

You can consider modifying the PV as well if you prefer to increase the disk used by harbor. By default, it only creates 5GB of disk.

Once you have the values.yaml customized, install harbor by doing the following:

helm install harbor harbor/harbor -f values.yaml -n harbor

Setting IngressRoutes for Harbor

Once we have Harbor up and running, it’s time to create Traefik’s IngressRoute to expose Harbor to the outside world. We will be exposing three ports: 80, 443, and 4443. Make sure this ports are part of Traefik’s static configuration.

Sample IngressRoutes:

apiVersion: traefik.containo.us/v1alpha1
kind: IngressRouteTCP
metadata:
  name: harbor-https
  namespace: harbor
spec:
  entryPoints:                 
  - web-secure
  routes:                      
  - match: HostSNI(`FQDN HERE`)
    kind: Rule
    services:                  
    - name: harbor             
      port: 443   
  tls:                          
    passthrough: true
---
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRouteTCP
metadata:
  name: harbor-4443
  namespace: harbor
spec:
  entryPoints:                 
  - web-4443
  routes:                      
  - match: HostSNI(`FQDN HERE`)
    kind: Rule
    services:                  
    - name: harbor             
      port: 4443         
  tls:                          
    passthrough: true
---
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
  name: harbor-route-http
  namespace: harbor
spec:
  entryPoints:
  - web
  routes:
  - match: Host(`FQDN HERE`)
    kind: Rule
    services:
    - name: harbor
      port: 80
    middleware:
    - name: redirect
      namespace: traefik

NOTE: For the HTTP resource, I used a middleware to redirect HTTP to HTTPS. This resource is created in the traefik namespace hence the reference. Middleware examples are in the traefik documentations.

Once this is done, your all set and hopefully have harbor up and accessible using the FQDN you specified.

Hope that helps. Enjoy!

  1. Walid Shaari Reply

    Great post, thank you for sharing

  2. cloudflare terraform provider Reply

    Hi,

    Really liking the site!

    If you require any devops services please get in touch, we’d be honoured to
    help you out.

Leave a Reply

*

This site uses Akismet to reduce spam. Learn how your comment data is processed.