Server (harnessd)

TLS and certificate pinning

Serve harnessd over TLS 1.3 with a self-signed certificate, let clients pin it, and rotate it.

harnessd serves TLS 1.3 only. When you add a server in the Mac app, you paste the SHA-256 fingerprint of its certificate, and the app then accepts only that certificate, whoever issued it. A certificate from another CA can't impersonate the server.

Self-signed works only on loopback

macOS App Transport Security rejects a self-signed certificate on any address other than loopback, even when the app pins it. With a self-signed certificate, the app must reach the server through an SSH tunnel to 127.0.0.1, not on a VPN, private or public IP. See Connect the app to a server.

Get a certificate#

A self-signed EC P-256 certificate is all the SSH tunnel setup needs:

Shell
sudo install -d -o harness -g harness -m 0700 /etc/harness/tls
sudo openssl req -x509 -newkey ec -pkeyopt ec_paramgen_curve:P-256 -nodes -days 365 \
  -subj "/CN=harnessd" -keyout /etc/harness/tls/privkey.pem -out /etc/harness/tls/fullchain.pem
sudo chown harness:harness /etc/harness/tls/privkey.pem /etc/harness/tls/fullchain.pem
sudo chmod 600 /etc/harness/tls/privkey.pem

The key must be owned by harness with mode 0600. Point tls.cert and tls.key at the files:

harnessd.yaml
tls:
  cert: /etc/harness/tls/fullchain.pem
  key:  /etc/harness/tls/privkey.pem
Shell
sudo -u harness harnessd fingerprint --config /etc/harness/harnessd.yaml
# 3F:A1:…:9C

It's the SHA-256 of the leaf certificate (DER), as colon-separated hex. It matches:

Shell
openssl x509 -noout -fingerprint -sha256 -in /etc/harness/tls/fullchain.pem

harnessd serve also logs it at startup as certificate_sha256=…. Send it to your users over a trusted channel, along with the URL.

Pin it in the app#

In Add Server, paste it into Certificate SHA-256. The server's row in the sidebar and its Server Settings show Pinned, with the fingerprint. A server added with a public certificate shows Public certificate instead.

Public CA certificates#

A certificate from a public CA for a domain, for example from AWS Certificate Manager on a load balancer, passes App Transport Security. Don't pin it: public CAs replace the leaf at every renewal. In Add Server, choose Public certificate for the domain instead; macOS then checks the chain, the domain name and Certificate Transparency, and renewals need no change in the app. Only the root authorities that ship with macOS count: a root added to the user's Mac, or trusted by its administrator, isn't accepted, so a TLS-inspecting proxy can't stand in for the server. See Behind a load balancer.

Rotate the certificate#

The pin is the certificate, not the CA, so renewing or replacing the certificate changes the fingerprint, and every user needs the new one:

  1. Install the new certificate and restart
    Shell
    sudo systemctl restart harnessd
    sudo -u harness harnessd fingerprint --config /etc/harness/harnessd.yaml
  2. Update each client

    Users remove the server (right-click → Remove Server) and add it again with the new fingerprint.

Behind a local TLS proxy#

If a TLS-terminating proxy on the same host must front harnessd, run it on loopback with plain HTTP:

YAML
listen: 127.0.0.1:8080
insecure_http_behind_proxy: true    # only allowed on 127.0.0.1, ::1 or localhost

The server's General tab then shows TLS: Off (loopback behind a proxy). The proxy must speak TLS to clients, and clients pin the proxy's certificate. The loopback rule applies to the proxy too: with a self-signed certificate, users reach it through an SSH tunnel.

See also#