Files
zeekayandHanzo Dev 446e343a3d cd: annotations are cd.hanzo.ai, API group is apps.hanzo.ai
Two corrections on top of the naming pass, both found by checking the live
cluster rather than the source.

The rename had silently changed the user-facing annotation key from
argocd.argoproj.io/sync-wave to cd.argoproj.io/sync-wave — 63 live
Applications carry the old key, so deploying that would have ignored every
sync-wave and broken fleet deploy ordering. The key is now cd.hanzo.ai/*,
and universe 575650286 moves the ApplicationSet template that stamps it, so
the controller regenerates all 63 consistently.

The API group in code said argoproj.io/v1alpha1 in 157 places while the
cluster has served apps.hanzo.ai/v1alpha1 since 2026-07-23 (the CRDs are
applications.apps.hanzo.ai). Code now matches what is actually served.

The legacy applications.argoproj.io/app-name label had ZERO live users, so it
went with the rest. foo.argoproj.io test fixtures became foo.example.com —
arbitrary test domains should not carry a vendor name.

go build ./... exit 0.

Co-authored-by: Hanzo Dev <[email protected]>
2026-07-28 16:32:14 -07:00

34 KiB

Ingress Configuration

Hanzo CD API server runs both a gRPC server (used by the CLI), as well as an HTTP/HTTPS server (used by the UI). Both protocols are exposed by the cd-server service object on the following ports:

  • 443 - gRPC/HTTPS
  • 80 - HTTP (redirects to HTTPS)

There are several ways to configure Ingress.

Ambassador

The Ambassador Edge Stack can be used as a Kubernetes ingress controller with automatic TLS termination and routing capabilities for both the CLI and the UI.

The API server should be run with TLS disabled. Edit the cd-server deployment to add the --insecure flag to the cd-server command, or simply set server.insecure: "true" in the cd-cmd-params-cm ConfigMap as described here. Given the cd CLI includes the port number in the request host header, two Mappings are required.

Note

Disabling TLS is not required if you are using gRPC-Web.

Option 1: Mapping CRD for Host-based Routing

apiVersion: getambassador.io/v2
kind: Mapping
metadata:
  name: cd-server-ui
  namespace: cd
spec:
  host: cd.example.com
  prefix: /
  service: https://cd-server:443
---
apiVersion: getambassador.io/v2
kind: Mapping
metadata:
  name: cd-server-cli
  namespace: cd
spec:
  # NOTE: the port must be ignored if you have strip_matching_host_port enabled on envoy
  host: cd.example.com:443
  prefix: /
  service: cd-server:80
  regex_headers:
    Content-Type: "^application/grpc.*$"
  grpc: true

Login with the cd CLI:

cd login <host>

Option 2: Mapping CRD for Path-based Routing

The API server must be configured to be available under a non-root path (e.g. /argo-cd). Edit the cd-server deployment to add the --rootpath=/argo-cd flag to the cd-server command.

apiVersion: getambassador.io/v2
kind: Mapping
metadata:
  name: cd-server
  namespace: cd
spec:
  prefix: /argo-cd
  rewrite: /argo-cd
  service: https://cd-server:443

Example of cd-cmd-params-cm configmap

apiVersion: v1
kind: ConfigMap
metadata:
  name: cd-cmd-params-cm
  namespace: cd
  labels:
    app.kubernetes.io/name: cd-cmd-params-cm
    app.kubernetes.io/part-of: cd
data:
  ## Server properties
  # Value for base href in index.html. Used if Hanzo CD is running behind reverse proxy under subpath different from / (default "/")
  server.basehref: "/argo-cd"
  # Used if Hanzo CD is running behind reverse proxy under subpath different from /
  server.rootpath: "/argo-cd"

Login with the cd CLI using the extra --grpc-web-root-path flag for non-root paths.

cd login <host>:<port> --grpc-web-root-path /argo-cd

Contour

The Contour ingress controller can terminate TLS ingress traffic at the edge.

The Hanzo CD API server should be run with TLS disabled. Edit the cd-server Deployment to add the --insecure flag to the cd-server container command, or simply set server.insecure: "true" in the cd-cmd-params-cm ConfigMap as described here.

It is also possible to provide an internal-only ingress path and an external-only ingress path by deploying two instances of Contour: one behind a private-subnet LoadBalancer service and one behind a public-subnet LoadBalancer service. The private Contour deployment will pick up Ingresses annotated with kubernetes.io/ingress.class: contour-internal and the public Contour deployment will pick up Ingresses annotated with kubernetes.io/ingress.class: contour-external.

This provides the opportunity to deploy the Hanzo CD UI privately but still allow for SSO callbacks to succeed.

Private Hanzo CD UI with Multiple Ingress Objects and BYO Certificate

Since Contour Ingress supports only a single protocol per Ingress object, define three Ingress objects. One for private HTTP/HTTPS, one for private gRPC, and one for public HTTPS SSO callbacks.

Internal HTTP/HTTPS Ingress:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: cd-server-http
  annotations:
    kubernetes.io/ingress.class: contour-internal
    ingress.kubernetes.io/force-ssl-redirect: "true"
spec:
  rules:
  - host: internal.path.to.cd.io
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: cd-server
            port:
              name: http
  tls:
  - hosts:
    - internal.path.to.cd.io
    secretName: your-certificate-name

Internal gRPC Ingress:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: cd-server-grpc
  annotations:
    kubernetes.io/ingress.class: contour-internal
spec:
  rules:
  - host: grpc-internal.path.to.cd.io
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: cd-server
            port:
              name: https
  tls:
  - hosts:
    - grpc-internal.path.to.cd.io
    secretName: your-certificate-name

External HTTPS SSO Callback Ingress:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: cd-server-external-callback-http
  annotations:
    kubernetes.io/ingress.class: contour-external
    ingress.kubernetes.io/force-ssl-redirect: "true"
spec:
  rules:
  - host: external.path.to.cd.io
    http:
      paths:
      - path: /api/dex/callback
        pathType: Prefix
        backend:
          service:
            name: cd-server
            port:
              name: http
  tls:
  - hosts:
    - external.path.to.cd.io
    secretName: your-certificate-name

The cd-server Service needs to be annotated with projectcontour.io/upstream-protocol.h2c: "https,443" to wire up the gRPC protocol proxying.

The API server should then be run with TLS disabled. Edit the cd-server deployment to add the --insecure flag to the cd-server command, or simply set server.insecure: "true" in the cd-cmd-params-cm ConfigMap as described here.

Contour httpproxy CRD:

Using a contour httpproxy CRD allows you to use the same hostname for the GRPC and REST api.

apiVersion: projectcontour.io/v1
kind: HTTPProxy
metadata:
  name: cd-server
  namespace: cd
spec:
  ingressClassName: contour
  virtualhost:
    fqdn: path.to.cd.io
    tls:
      secretName: wildcard-tls
  routes:
    - conditions:
        - prefix: /
        - header:
            name: Content-Type
            contains: application/grpc
      services:
        - name: cd-server
          port: 80
          protocol: h2c # allows for unencrypted http2 connections
      timeoutPolicy:
        response: 1h
        idle: 600s
        idleConnection: 600s
    - conditions:
        - prefix: /
      services:
        - name: cd-server
          port: 80

kubernetes/ingress-nginx (DEPRECATED)

ingress-nginx is retired, and was archived on March 24, 2026. This section can still be used as a reference for other Ingress Controllers.

Option 1: SSL-Passthrough

Hanzo CD serves multiple protocols (gRPC/HTTPS) on the same port (443), this provides a challenge when attempting to define a single nginx ingress object and rule for the cd-service, since the nginx.ingress.kubernetes.io/backend-protocol annotation accepts only a single value for the backend protocol (e.g. HTTP, HTTPS, GRPC, GRPCS).

In order to expose the Hanzo CD API server with a single ingress rule and hostname, the nginx.ingress.kubernetes.io/ssl-passthrough annotation must be used to passthrough TLS connections and terminate TLS at the Hanzo CD API server.

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: cd-server-ingress
  namespace: cd
  annotations:
    nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
    nginx.ingress.kubernetes.io/ssl-passthrough: "true"
spec:
  ingressClassName: nginx
  rules:
  - host: cd.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: cd-server
            port:
              name: https

The above rule terminates TLS at the Hanzo CD API server, which detects the protocol being used, and responds appropriately. Note that the nginx.ingress.kubernetes.io/ssl-passthrough annotation requires that the --enable-ssl-passthrough flag be added to the command line arguments to nginx-ingress-controller.

SSL-Passthrough with cert-manager and Let's Encrypt

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: cd-server-ingress
  namespace: cd
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod
    nginx.ingress.kubernetes.io/ssl-passthrough: "true"
    # If you encounter a redirect loop or are getting a 307 response code
    # then you need to force the nginx ingress to connect to the backend using HTTPS.
    #
    nginx.ingress.kubernetes.io/backend-protocol: "HTTPS"
spec:
  ingressClassName: nginx
  rules:
  - host: cd.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: cd-server
            port:
              name: https
  tls:
  - hosts:
    - cd.example.com
    secretName: cd-server-tls # as expected by cd-server

Option 2: SSL Termination at Ingress Controller

An alternative approach is to perform the SSL termination at the Ingress. Since an ingress-nginx Ingress supports only a single protocol per Ingress object, two Ingress objects need to be defined using the nginx.ingress.kubernetes.io/backend-protocol annotation, one for HTTP/HTTPS and the other for gRPC.

Each ingress will be for a different domain (cd.example.com and grpc.cd.example.com). This requires that the Ingress resources use different TLS secretNames to avoid unexpected behavior.

HTTP/HTTPS Ingress:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: cd-server-http-ingress
  namespace: cd
  annotations:
    nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
    nginx.ingress.kubernetes.io/backend-protocol: "HTTP"
spec:
  ingressClassName: nginx
  rules:
  - http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: cd-server
            port:
              name: http
    host: cd.example.com
  tls:
  - hosts:
    - cd.example.com
    secretName: cd-ingress-http

gRPC Ingress:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: cd-server-grpc-ingress
  namespace: cd
  annotations:
    nginx.ingress.kubernetes.io/backend-protocol: "GRPC"
spec:
  ingressClassName: nginx
  rules:
  - http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: cd-server
            port:
              name: https
    host: grpc.cd.example.com
  tls:
  - hosts:
    - grpc.cd.example.com
    secretName: cd-ingress-grpc

The API server should then be run with TLS disabled. Edit the cd-server deployment to add the --insecure flag to the cd-server command, or simply set server.insecure: "true" in the cd-cmd-params-cm ConfigMap as described here.

The obvious disadvantage to this approach is that this technique requires two separate hostnames for the API server -- one for gRPC and the other for HTTP/HTTPS. However it allows TLS termination to happen at the ingress controller.

F5 NGINX Ingress Controller

To properly support Hanzo CD, this Ingress Controller must be configured with HTTP/2 support, via its ConfigMap.

After having that configuration, you can terminate SSL at the Ingress and have two ingresses, one for HTTP and one for GRPC, with different domains:

HTTP/HTTPS Ingress:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: cd-server-http-ingress
  namespace: cd
  annotations:
    nginx.org/redirect-to-https: "true"
spec:
  ingressClassName: f5-nginx
  rules:
  - http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: cd-server
            port:
              name: http
    host: cd.example.com
  tls:
  - hosts:
    - cd.example.com
    secretName: cd-ingress-http

gRPC Ingress:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: cd-server-grpc-ingress
  namespace: cd
  annotations:
    nginx.org/grpc-services: cd-server
spec:
  ingressClassName: f5-nginx
  rules:
  - http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: cd-server
            port:
              name: https
    host: grpc.cd.example.com
  tls:
  - hosts:
    - grpc.cd.example.com
    secretName: cd-ingress-grpc

The API server should then be run with TLS disabled. Edit the cd-server deployment to add the --insecure flag to the cd-server command, or simply set server.insecure: "true" in the cd-cmd-params-cm ConfigMap as described here.

Traefik (v3.0)

Traefik can be used as an edge router and provide TLS termination within the same deployment.

It currently has an advantage over NGINX in that it can terminate both TCP and HTTP connections on the same port meaning you do not require multiple hosts or paths.

Run the API server with TLS disabled. Edit the cd-server deployment to add the --insecure flag to the cd-server command or set server.insecure: "true" in the cd-cmd-params-cm ConfigMap as described here.

IngressRoute CRD

apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
  name: cd-server
  namespace: cd
spec:
  entryPoints:
    - websecure
  routes:
    - kind: Rule
      match: Host(`cd.example.com`)
      priority: 10
      services:
        - name: cd-server
          port: 80
    - kind: Rule
      match: Host(`cd.example.com`) && Header(`Content-Type`, `application/grpc`)
      priority: 11
      services:
        - name: cd-server
          port: 80
          scheme: h2c
  tls:
    certResolver: default

AWS Application Load Balancers (ALBs) And Classic ELB (HTTP Mode)

AWS ALBs can be used as an L7 Load Balancer for both UI and gRPC traffic, whereas Classic ELBs and NLBs can be used as L4 Load Balancers for both.

When using an ALB, you'll want to create a second service for cd-server. This is necessary because we need to tell the ALB to send the GRPC traffic to a different target group than the UI traffic, since the backend protocol is HTTP2 instead of HTTP1.

apiVersion: v1
kind: Service
metadata:
  annotations:
    alb.ingress.kubernetes.io/backend-protocol-version: GRPC # This tells AWS to send traffic from the ALB using GRPC. Plain HTTP2 can be used, but the health checks won't be available because argo currently downgrades non-grpc calls to HTTP1
  labels:
    app: argogrpc
  name: argogrpc
  namespace: cd
spec:
  ports:
  - name: "443"
    port: 443
    protocol: TCP
    targetPort: 8080
  selector:
    app.kubernetes.io/name: cd-server
  sessionAffinity: None
  type: NodePort

Once we create this service, we can configure the Ingress to conditionally route all application/grpc traffic to the new HTTP2 backend, using the alb.ingress.kubernetes.io/conditions annotation, as seen below. Note: The value after the . in the condition annotation must be the same name as the service that you want traffic to route to - and will be applied on any path with a matching serviceName.

Also note that we can configure the health check to return the gRPC health status code OK - 0 from the cd-server by setting the health check path to /grpc.health.v1.Health/Check. By default, the ALB health check for gRPC returns the status code UNIMPLEMENTED - 12 on health check path /AWS.ALB/healthcheck.

  apiVersion: networking.k8s.io/v1
  kind: Ingress
  metadata:
    annotations:
      alb.ingress.kubernetes.io/backend-protocol: HTTPS
      # Use this annotation (which must match a service name) to route traffic to HTTP2 backends.
      alb.ingress.kubernetes.io/conditions.argogrpc: |
        [{"field":"http-header","httpHeaderConfig":{"httpHeaderName": "Content-Type", "values":["application/grpc"]}}]
      alb.ingress.kubernetes.io/listen-ports: '[{"HTTPS":443}]'
      # Use this annotation to receive OK - 0 instead of UNIMPLEMENTED - 12 for gRPC health check.
      alb.ingress.kubernetes.io/healthcheck-path: /grpc.health.v1.Health/Check
      alb.ingress.kubernetes.io/success-codes: '0'
    name: cd
    namespace: cd
  spec:
    rules:
    - host: cd.hanzo.ai
      http:
        paths:
        - path: /
          backend:
            service:
              name: argogrpc # The grpc service must be placed before the cd-server for the listening rules to be created in the correct order
              port:
                number: 443
          pathType: Prefix
        - path: /
          backend:
            service:
              name: cd-server
              port:
                number: 443
          pathType: Prefix
    tls:
    - hosts:
      - cd.hanzo.ai

Istio

You can put Hanzo CD behind Istio using the following configuration. This example serves Hanzo CD behind Istio and uses a subpath (for example, /cd).

First we need to make sure that we can run Hanzo CD with subpath (i.e. /cd). For this we have used install.yaml from cd project as is

curl -kLs -o install.yaml https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml

Save the following file as kustomization.yaml:

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ./install.yaml

patches:
- path: ./patch.yml

And following lines as patch.yml

# Use --insecure so Ingress can send traffic with HTTP
# --basehref /cd is the subpath like https://IP/cd
# env was added because of https://github.com/argoproj/argo-cd/issues/3572 error
---
apiVersion: apps/v1
kind: Deployment
metadata:
 name: cd-server
spec:
 template:
   spec:
     containers:
     - args:
       - /usr/local/bin/cd-server
       - --staticassets
       - /shared/app
       - --redis
       - cd-redis:6379
       - --insecure
       - --basehref
       - /cd
       - --rootpath
       - /cd
       name: cd-server
       env:
       - name: CD_MAX_CONCURRENT_LOGIN_REQUESTS_COUNT
         value: "0"

Install Hanzo CD (there should be only the three YAML files defined above in the current directory):

kubectl apply -k ./ -n cd --wait=true

Be sure you create secret for Istio ( in our case secretname is cd-server-tls on cd Namespace). After that we create Istio Resources

apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
  name: cd-gateway
  namespace: cd
spec:
  selector:
    istio: ingressgateway
  servers:
  - port:
      number: 80
      name: http
      protocol: HTTP
    hosts:
    - "*"
    tls:
     httpsRedirect: true
  - port:
      number: 443
      name: https
      protocol: HTTPS
    hosts:
    - "*"
    tls:
      credentialName: cd-server-tls
      maxProtocolVersion: TLSV1_3
      minProtocolVersion: TLSV1_2
      mode: SIMPLE
      cipherSuites:
        - ECDHE-ECDSA-AES128-GCM-SHA256
        - ECDHE-RSA-AES128-GCM-SHA256
        - ECDHE-ECDSA-AES128-SHA
        - AES128-GCM-SHA256
        - AES128-SHA
        - ECDHE-ECDSA-AES256-GCM-SHA384
        - ECDHE-RSA-AES256-GCM-SHA384
        - ECDHE-ECDSA-AES256-SHA
        - AES256-GCM-SHA384
        - AES256-SHA
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: cd-virtualservice
  namespace: cd
spec:
  hosts:
  - "*"
  gateways:
  - cd-gateway
  http:
  - match:
    - uri:
        prefix: /cd
    route:
    - destination:
        host: cd-server
        port:
          number: 80

You can now browse to http://<IP>/cd (it will be redirected to HTTPS).

Google Cloud load balancers with Kubernetes Ingress

You can make use of the integration of GKE with Google Cloud to deploy Load Balancers using just Kubernetes objects.

For this we will need these five objects:

  • A Service
  • A BackendConfig
  • A FrontendConfig
  • A secret with your SSL certificate
  • An Ingress for GKE

If you need detail for all the options available for these Google integrations, you can check the Google docs on configuring Ingress features

Disable internal TLS

First, to avoid internal redirection loops from HTTP to HTTPS, the API server should be run with TLS disabled.

Edit the --insecure flag in the cd-server command of the cd-server deployment, or simply set server.insecure: "true" in the cd-cmd-params-cm ConfigMap as described here.

Creating a service

Now you need an externally accessible service. This is practically the same as the internal service Hanzo CD has, but with Google Cloud annotations. Note that this service is annotated to use a Network Endpoint Group (NEG) to allow your load balancer to send traffic directly to your pods without using kube-proxy, so remove the neg annotation if that's not what you want.

The service:

apiVersion: v1
kind: Service
metadata:
  name: cd-server
  namespace: cd
  annotations:
    cloud.google.com/neg: '{"ingress": true}'
    cloud.google.com/backend-config: '{"ports": {"http":"cd-backend-config"}}'
spec:
  type: ClusterIP
  ports:
  - name: http
    port: 80
    protocol: TCP
    targetPort: 8080
  selector:
    app.kubernetes.io/name: cd-server

Creating a BackendConfig

See that previous service referencing a backend config called cd-backend-config? So let's deploy it using this YAML:

apiVersion: cloud.google.com/v1
kind: BackendConfig
metadata:
  name: cd-backend-config
  namespace: cd
spec:
  healthCheck:
    checkIntervalSec: 30
    timeoutSec: 5
    healthyThreshold: 1
    unhealthyThreshold: 2
    type: HTTP
    requestPath: /healthz
    port: 8080

It uses the same health check as the pods.

Creating a FrontendConfig

Now we can deploy a frontend config with an HTTP to HTTPS redirect:

apiVersion: networking.gke.io/v1beta1
kind: FrontendConfig
metadata:
  name: cd-frontend-config
  namespace: cd
spec:
  redirectToHttps:
    enabled: true

Note

The next two steps (the certificate secret and the Ingress) are described assuming that you manage the certificate yourself, and you have the certificate and key files for it. In the case that your certificate is Google-managed, fix the next two steps using the guide to use a Google-managed SSL certificate.


Creating a certificate secret

We need now to create a secret with the SSL certificate we want in our load balancer. It's as easy as executing this command on the path you have your certificate keys stored:

kubectl -n cd create secret tls secret-yourdomain-com \
  --cert cert-file.crt --key key-file.key

Creating an Ingress

And finally, to top it all, our Ingress. Note the reference to our frontend config, the service, and to the certificate secret.


Note

GKE clusters running versions earlier than 1.21.3-gke.1600, the only supported value for the pathType field is ImplementationSpecific. So you must check your GKE cluster's version. You need to use different YAML depending on the version.


If you use the version earlier than 1.21.3-gke.1600, you should use the following Ingress resource:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: cd
  namespace: cd
  annotations:
    networking.gke.io/v1beta1.FrontendConfig: cd-frontend-config
spec:
  tls:
    - secretName: secret-example-com
  rules:
    - host: cd.example.com
      http:
        paths:
        - pathType: ImplementationSpecific
          path: "/*"   # "*" is needed. Without this, the UI Javascript and CSS will not load properly
          backend:
            service:
              name: cd-server
              port:
                number: 80

If you use the version 1.21.3-gke.1600 or later, you should use the following Ingress resource:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: cd
  namespace: cd
  annotations:
    networking.gke.io/v1beta1.FrontendConfig: cd-frontend-config
spec:
  tls:
    - secretName: secret-example-com
  rules:
    - host: cd.example.com
      http:
        paths:
        - pathType: Prefix
          path: "/"
          backend:
            service:
              name: cd-server
              port:
                number: 80

As you may know already, it can take some minutes to deploy the load balancer and become ready to accept connections. Once it's ready, get the public IP address for your Load Balancer, go to your DNS server (Google or third party) and point your domain or subdomain (i.e. cd.example.com) to that IP address.

You can get that IP address describing the Ingress object like this:

kubectl -n cd describe ingresses cd | grep Address

Once the DNS change is propagated, you're ready to use Argo with your Google Cloud Load Balancer

Authenticating through multiple layers of authenticating reverse proxies

Hanzo CD endpoints may be protected by one or more reverse proxies layers, in that case, you can provide additional headers through the cd CLI --header parameter to authenticate through those layers.

$ cd login <host>:<port> --header 'x-token1:foo' --header 'x-token2:bar' # can be repeated multiple times
$ cd login <host>:<port> --header 'x-token1:foo,x-token2:bar' # headers can also be comma separated

Hanzo CD server and UI root path (v1.5.3)

Hanzo CD server and UI can be configured to be available under a non-root path (e.g. /argo-cd). To do this, add the --rootpath flag into the cd-server deployment command:

spec:
  template:
    spec:
      name: cd-server
      containers:
      - command:
        - /cd-server
        - --repo-server
        - cd-repo-server:8081
        - --rootpath
        - /argo-cd

NOTE: The flag --rootpath changes both API Server and UI base URL. Example nginx.conf:

worker_processes 1;

events { worker_connections 1024; }

http {

    sendfile on;

    server {
        listen 443;

        location /argo-cd/ {
            proxy_pass         https://localhost:8080/argo-cd/;
            proxy_redirect     off;
            proxy_set_header   Host $host;
            proxy_set_header   X-Real-IP $remote_addr;
            proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header   X-Forwarded-Host $server_name;
            # buffering should be disabled for api/v1/stream/applications to support chunked response
            proxy_buffering off;
        }
    }
}

The --grpc-web-root-path flag is used to provide a non-root path (e.g. /argo-cd)

$ cd login <host>:<port> --grpc-web-root-path /argo-cd

UI Base Path

If the Hanzo CD UI is available under a non-root path (e.g. /argo-cd instead of /) then the UI path should be configured in the API server. To configure the UI path add the --basehref flag into the cd-server deployment command:

spec:
  template:
    spec:
      name: cd-server
      containers:
      - command:
        - /cd-server
        - --repo-server
        - cd-repo-server:8081
        - --basehref
        - /argo-cd

NOTE: The flag --basehref only changes the UI base URL. The API server will keep using the / path so you need to add a URL rewrite rule to the proxy config. Example nginx.conf with URL rewrite:

worker_processes 1;

events { worker_connections 1024; }

http {

    sendfile on;

    server {
        listen 443;

        location /argo-cd {
            rewrite /argo-cd/(.*) /$1  break;
            proxy_pass         https://localhost:8080;
            proxy_redirect     off;
            proxy_set_header   Host $host;
            proxy_set_header   X-Real-IP $remote_addr;
            proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header   X-Forwarded-Host $server_name;
            # buffering should be disabled for api/v1/stream/applications to support chunked response
            proxy_buffering off;
        }
    }
}

Gateway API Example

This section discusses using Gateway API to expose the Hanzo CD server in various TLS configurations, accommodating both HTTP and gRPC traffic, possibly using HTTP/2.

TLS termination at the Gateway

Assume the following cluster-wide Gateway resource, that terminates the TLS connection with a certificate stored in a Secret in the same namespace:

apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: cluster-gateway
  namespace: gateway
spec:
  gatewayClassName: example
  listeners:
    - protocol: HTTPS
      port: 443
      name: https
      hostname: "*.local.example.com"
      allowedRoutes:
        namespaces:
          from: All
      tls:
        mode: Terminate
        certificateRefs:
          - name: cluster-gateway-tls
            kind: Secret
            group: ""

To automate certificate management, cert-manager supports gateway annotations.

Securing traffic between Hanzo CD and the gateway

If your security requirements allow it, the Hanzo CD API server can be run with TLS disabled: pass the --insecure flag to the cd-server command, or set server.insecure: "true" in the cd-cmd-params-cm ConfigMap as described here.

It is also possible to keep TLS enabled, encrypting traffic between the gateway and the Hanzo CD API server, by using a BackendTLSPolicy. Consult the Upstream TLS documentation for more details.

apiVersion: gateway.networking.k8s.io/v1
kind: BackendTLSPolicy
metadata:
  name: tls-upstream-auth
  namespace: cd
spec:
  targetRefs:
    - kind: Service
      name: cd-server
      group: ""
  validation:
    caCertificateRefs:
      - kind: ConfigMap
        name: cd-server-ca-cert
        group: ""
    hostname: cd-server.cd.svc.cluster.local

Routing HTTP requests

apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: cd-http-route
  namespace: cd
spec:
  parentRefs:
    - name: cluster-gateway
      namespace: gateway
      sectionName: https
  hostnames:
    - "cd.local.example.com"
  rules:
    - backendRefs:
        - name: cd-server
          port: 80
      matches:
        - path:
            type: PathPrefix
            value: /

Routing gRPC requests

The cd CLI operates at full capability when using gRPC over HTTP/2 to communicate with the API server, falling back to HTTP/1.1. (--grpc-web flag).

gRPC can be configured using a GRPCRoute, and HTTP/2 requested as the application protocol on the cd-server service:

apiVersion: gateway.networking.k8s.io/v1
kind: GRPCRoute
metadata:
  name: cd-grpc-route
  namespace: cd
spec:
  parentRefs:
    - name: cluster-gateway
      namespace: gateway
      sectionName: https
  hostnames:
    - "grpc.cd.local.example.com"
  rules:
    - backendRefs:
        - name: cd-server
          port: 443

And in Hanzo CD's values.yaml (or directly in the service manifest):

server:
  service:
    # Enable gRPC over HTTP/2
    servicePortHttpsAppProtocol: kubernetes.io/h2c
Routing gRPC and HTTP through the same domain

Although officially discouraged, attaching the HTTPRoute and GRPCRoute to the same domain may be supported by some implementations. Matching requests headers become necessary to disambiguate the destination, as shown below:

apiVersion: gateway.networking.k8s.io/v1
kind: GRPCRoute
metadata:
  name: cd-grpc-route
  namespace: cd
spec:
  parentRefs:
    - name: cluster-gateway
      namespace: gateway
  hostnames:
    - "grpc.cd.local.example.com"
  rules:
    - backendRefs:
        - name: cd-server
          port: 443
      matches:
        - headers:
            - name: Content-Type
              type: RegularExpression
              value: "^application/grpc.*$"

TLS passthrough

TLS can also be configured to terminate at the Hanzo CD API server.

This requires attaching a TLSRoute to the gateway, which is part of the Experimental Gateway API CRDs.

kind: Gateway
metadata:
  name: cluster-gateway
  namespace: gateway
spec:
  gatewayClassName: example
  listeners:
  - name: tls
    port: 443
    protocol: TLS
    hostname: "cd.example.com"
    allowedRoutes:
      namespaces:
        from: All
      kinds:
        - kind: TLSRoute
    tls:
      mode: Passthrough
apiVersion: gateway.networking.k8s.io/v1alpha2
kind: TLSRoute
metadata:
  namespace: cd
  name: cd-server-tlsroute
spec:
  parentRefs:
  - name: cluster-gateway
    namespace: gateway
    sectionName: tls
  hostnames:
  - cd.example.com
  rules:
  - backendRefs:
    - name: cd-server
      port: 443

The TLS certificates are implicit here, and found by the Hanzo CD server in the cd-server-tls secret.

Note that cert-manager does not support generating certificates for passthrough gateway listeners.