Consul auto_config geht doch

Eigentlich hatte ich das Thema ja schon ad acta gelegt aber dann hat es mich doch nicht losgelassen.

Also hier nochmal die Problemstellung: Wenn man das Consul Service Mesh benutzen möchte und dazu auch noch Consul Datacenter über WAN föderieren will, muß man ACLs aktivieren und das führt dazu, dass jeder Consul-Client ein "Node-Identity" Token benötigt, um überhaupt dem Cluster beitreten zu können. In statischen Umgebungen ist das natürlich kein großes Problem. Anders sieht es aus, wenn man mit dem Nomad-Autoscaler dem Cluster dynamisch Nodes hinzufügen (und auch wieder entfernen) will. Dann muß das Token dynamisch erzeugt werden.

Dafür gibt es in der Consul Konfiguration den Abschnitt auto_config. Netterweise gibt es zu dem Thema auch gleich ein Tutorial.

Im Wesentlichen hilft auto_config dabei, einen (neuen) Consul Client mit wichtigen Teilen der Konfiguration zu versorgen. Insbesondere den relevanten Zertifikaten, dem Encryption-Key und eben dem Node-Identity Token. Das geht, in dem der Client einem Server ein JWT "vorzeigt" und sich dann die genannten Daten herunterlädt.

Aus unerfindlichen Gründen hatte ich in dem Tutorial zwar den Hinweis auf secint gelesen, hatte dann aber den dazugehörigen "Learning Path" übersehen und war damit nicht weitergekommen. Ich hatte zum Beispiel erst nach dem Durchsehen von

Getting into Consul, Part6: Auto Configuration with Vault

verstanden, dass für jeden (neuen) Node ein eigenes JWT erzeugt werden muß (und nicht eins für alle).

Secint ist für die Leute, die auto_config machen aber keinen Vault-Cluster dafür aufsetzen wollen. Also installiert man sich auf einem Server im Cluster secint (z. B. mit go install github.com/banks/secint@latest). Ich habe dazu gleich auch noch einen entsprechenden User angelegt und diesem ein SSH-Schlüsselpaar erzeugt. Auch für den Root-User auf meinen Consul-Clients habe ich ein Schlüsselpaar erzeugt und den öffentlichen Teil im Home-Verzeichnis vom User secint in .ssh/authorized_keys abgespeichert.

Jetzt kann sich ein Skript beim Start des neuen Consul Clients mit

hostn=`cat /etc/hostname` ; ssh secint@server "secint mint -issuer secint -ttl 1h -node $hostn -priv-key secint-priv-key.pem -audience prod1" > /etc/consul/tokens/jwt

beim Secint-Server per ssh einloggen, ein JWT erzeugen und direkt in die Datei schreiben, die im Consul-Client konfiguriert ist. Die Konfigurationsdatei schmilzt dann etwas zusammen:

datacenter = "prod1"
primary_datacenter = "prod2"

data_dir   =  "/opt/consul"
log_level  =  "INFO"
node_name  =  "nomad-client-0"
server     =  false
leave_on_terminate = true

ca_file    = "/etc/consul/certificates/ca.pem"
bind_addr  = "0.0.0.0"
client_addr = "0.0.0.0"
advertise_addr = "{{ GetInterfaceIP \"ens3\" }}"
advertise_addr_wan = "1.2.3.4"
translate_wan_addrs = true

ports {
  https    = 8501
  grpc     = 8502
  grpc_tls = 8503
}
verify_incoming = false
verify_outgoing = true
verify_server_hostname = true

auto_config {
  enabled = true
  intro_token_file = "/etc/consul/tokens/jwt" 
  server_addresses = ["provider=os tag_key=consul-role tag_value=server auth_url=https://prod1.api.pco.get-cloud.io:5000 user_name=auseradm domain_name=adomain password=\"apassword\" region=prod1"]
}

Die zugehörige Serverkonfiguration ist selbstverständlich um den auto_config Absatz gewachsen:

auto_config {
      authorization {
        enabled = true
        static {
          jwt_validation_pub_keys = ["-----BEGIN PUBLIC KEY-----\npublicsecintkey\n-----END PUBLIC KEY-----\n"] 
          bound_issuer = "secint"
          bound_audiences = ["prod1"]
          claim_mappings {
            sub = "node_name"
          }     
          claim_assertions = [ 
            "value.node_name == \"${node}\"" 
          ]
        } 
      }
    }

Natürlich muß man auch an dieser Stelle wieder mit systemd kämpfen. Einerseits sollte der Consul-Client erst starten, wenn das JWT an der richtigen Stelle liegt und andererseits funktioniert in meiner aktuellen Konfiguration (mit systemd-resolved) kein DNS, wenn Consul nicht läuft.

DNS muß aber laufen, damit das Cloud auto_join des Clients funktioniert. Also muß vermutlich von systemd-resolvedauf dnsmasq umgestellt werden, da systemd-resolved ein wenig störrisch ist, wenn es darum geht, für Anfragen für die TLD consul den lokalen Consul-Client zu fragen und für alle anderen Domains doch bitte die "normalen" DNS-Server.

Aber wenn diese Hürde überwunden ist, wird "wie durch ein Wunder" die gewünschte Konfiguration übertragen und ein Token erzeugt so dass der neue Client dem Cluster beitreten kann.

Im Log steht dann etwas lapidar:

Mar 24 10:39:36 nomad-client-0 consul[9919]: 2024-03-24T10:39:36.771Z [INFO]  agent.auto_config: retrieving initial agent auto configuration remotely
Mar 24 10:39:37 nomad-client-0 consul[9919]: 2024-03-24T10:39:37.856Z [INFO]  agent.auto_config: auto-config started

Die Token kann man dann auf den Servern mit consul acl token list sehen:

root@consul-0:~# consul acl token list |grep "Auto Config"
Description:      Auto Config Token for Node "nomad-client-0"
Description:      Auto Config Token for Node "nomad-client-0"
Description:      Auto Config Token for Node "nomad-client-0"

Und die übertragene Konfiguration findet sich als auto-config.json auf dem Client im data_dir:

root@nomad-client-0:/opt/consul# ls -la
total 28
drwxr-xr-x 3 consul consul 4096 Mar 24 10:40 .
drwxr-xr-x 6 root   root   4096 Mar 24 09:49 ..
-rw-r----- 1 consul consul 7788 Mar 24 10:39 auto-config.json
-rw-r--r-- 1 consul consul  394 Mar 24 10:40 checkpoint-signature
-rw------- 1 consul consul   36 Mar 24 10:39 node-id
drwx------ 2 consul consul 4096 Mar 24 10:39 serf