Skip to content

Rotate Akka.Remote TLS certificates without a restart (Akka.NET 1.5.75 beta trial) - #305

Draft
Aaronontheweb wants to merge 2 commits into
devfrom
test/tls-rotation-beta
Draft

Aaronontheweb wants to merge 2 commits into
devfrom
test/tls-rotation-beta

Conversation

@Aaronontheweb

Copy link
Copy Markdown
Member

Draft, for review only. CI cannot pass yet. This branch pins Akka.NET to 1.5.75-beta-1794596265, which exists only on the private test lab feed. The feed is left out of this pull request on purpose, so the diff reads as what an application developer would change. The Akka.NET side is akkadotnet/akka.net#8959, akkadotnet/akka.net#8960 and akkadotnet/akka.net#8961.

What changed

  • AkkaSettings:TlsSettings:CertificatePath may now name a folder. Each node then uses the newest usable .pfx in it (latest start date) for new connections. Copy a renewed certificate in and the nodes pick it up within about a minute, with no restart. A single file still works as before.
  • The Aspire path now applies the TLS settings. Before, the Akka.Aspire plugin set up remoting itself and the app's TlsSettings were ignored there, so the cluster ran without TLS under Aspire.
  • The Aspire host takes DrawTogether:TlsCertificatePath and DrawTogether:TlsCertificatePassword. Both are off by default.
  • generate-certs.ps1 now adds client authentication to the certificate. With mutual TLS, a folder rejects a certificate that only allows server authentication, such as the committed certs/akka-node.pfx.
  • certs/README.md has a new section on rotating between two certificates.

The app change is BuildSslOptions in AkkaConfiguration.cs: when the path is a folder it sets Ssl.CertificateSource = TlsCertificateSource.FromFolder(path, password); otherwise it loads one file as before.

Try it locally

  1. Make two certificates with openssl as shown in certs/README.md (serverAuth + clientAuth, one shared password). Put only cert-a.pfx in a folder.
  2. dotnet run --project src/DrawTogether.AppHost -- --DrawTogether:TlsCertificatePath=<folder> --DrawTogether:TlsCertificatePassword='Test123!' --DrawTogether:Replicas=3
  3. Once the cluster is up, cp cert-b.pfx <folder>/.

What you will see

  • At startup each node logs TLS client and server certificate changed from none to <A thumbprint> (subject 'CN=DrawTogether.Akka.Remote', expires …), and node addresses use akka.ssl.tcp://.
  • Within about a minute of the copy each node logs … changed from <A> (…) to <B> (…).
  • openssl s_client -connect localhost:<remoting port> </dev/null 2>/dev/null | openssl x509 -noout -fingerprint -sha1 shows B on new connections. Each probe like this logs a handshake error on the node, because the probe sends no client certificate.
  • The cluster stays up the whole time.
  • A bad file in the folder (wrong password, not a certificate) logs a TLS certificate skipped: … warning, and the node keeps its current certificate.

What the trial run showed

Run locally under Aspire with 3 replicas, Redis discovery and SQL Server.

Step Result
Start on certificate A Cluster formed over TLS, 3 members up
Copy in B (direct cp) All nodes logged the change about 7 seconds later, when the next connection arrived; B presented on new connections
Move in C (ECDSA), no new connections One node changed after 6 seconds, the others at their once-a-minute check, 53 seconds later
Replace a node after rotation The new node started on B and joined
A node that only has C joins nodes still on B Joined; all members up
Remove the old certificate No change
Junk file, and a .pfx with the wrong password Warning each, nodes kept their certificate
Certificate with a start date in the future Ignored until then, picked up 12 to 60 seconds after
Empty the folder Nodes kept their last certificate and warned

Through all of it: no node marked unreachable, no failed node-to-node handshake, no shutdown.

Found along the way

  • The committed certs/akka-node.pfx does not work in a folder. It allows server authentication only. As a single file it is still accepted; in a folder every node fails to start with its enhanced key usage does not permit client authentication. Regenerate it with the updated script before pointing the Kubernetes manifests at a folder.
  • Kubernetes (not changed here): statefulset.yaml already mounts the whole akka-tls-cert secret at /certs, so the change is to set AkkaSettings__TlsSettings__CertificatePath to /certs and give the secret one key per certificate. deploy.ps1 deletes and recreates the secret today; a renewal should update it in place.

Not tested

PEM files, the store and callback sources, peer validation on (ValidateCertificates = true), and the end-to-end test suite.

Points AkkaVersion and AkkaHostingVersion at 1.5.75-beta-1794596265,
which adds TlsCertificateSource for Akka.Remote's DotNetty transport.
When AkkaSettings:TlsSettings:CertificatePath names a folder, use
TlsCertificateSource.FromFolder so each node picks the newest usable
certificate in it and new connections use it with no restart. A single
file still loads once at startup, as before.

The Aspire path now applies the same TLS settings on top of the
Akka.Aspire plugin's remoting set-up, which otherwise ignores them.

The Aspire host gains DrawTogether:TlsCertificatePath and
DrawTogether:TlsCertificatePassword (off by default) to try this locally.

generate-certs.ps1 now adds client authentication to the extended key
usage: with mutual TLS a folder rejects a server-only certificate.
certs/README.md shows how to try a rotation with two certificates.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant