Members' etcd speaks TLS to peers and clients with a certificate from the cluster's authority and asks clients for one; Patroni reaches it with the member's certificate and serves its API over TLS. A running plain-HTTP cluster is moved with Patroni paused, so PostgreSQL serves throughout: Patroni's lease-free keys are carried over to the new etcd. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
66 lines
1.9 KiB
Go
66 lines
1.9 KiB
Go
package pgha
|
|
|
|
import (
|
|
"crypto/tls"
|
|
"encoding/pem"
|
|
"net"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strconv"
|
|
"testing"
|
|
"time"
|
|
|
|
"gitea.mynodes.xyz/ilker/llmgw/internal/tlsconf"
|
|
)
|
|
|
|
func TestTheAPIIsAskedOverTLSAndOverHTTPOnlyWhenItSpeaksHTTP(t *testing.T) {
|
|
answer := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
_, _ = w.Write([]byte(`{"scheme":"` + map[bool]string{true: "https", false: "http"}[r.TLS != nil] + `"}`))
|
|
})
|
|
secure := httptest.NewTLSServer(answer)
|
|
defer secure.Close()
|
|
plain := httptest.NewServer(answer)
|
|
defer plain.Close()
|
|
t.Cleanup(func() { restMu.Lock(); restCA = nil; restMu.Unlock(); restPort = PatroniPort })
|
|
|
|
if err := setRESTCAPEM(pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: secure.Certificate().Raw})); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
ask := func(url string) (string, error) {
|
|
_, port, _ := net.SplitHostPort(url[len("https://"):])
|
|
if url[:5] == "http:" {
|
|
_, port, _ = net.SplitHostPort(url[len("http://"):])
|
|
}
|
|
restPort, _ = strconv.Atoi(port)
|
|
b, err := restJSON(t.Context(), "127.0.0.1", http.MethodGet, "/x", nil, "", 3*time.Second)
|
|
return string(b), err
|
|
}
|
|
if got, err := ask(secure.URL); err != nil || got != `{"scheme":"https"}` {
|
|
t.Errorf("a member on TLS: %s, %v", got, err)
|
|
}
|
|
if got, err := ask(plain.URL); err != nil || got != `{"scheme":"http"}` {
|
|
t.Errorf("a member not yet on TLS: %s, %v", got, err)
|
|
}
|
|
|
|
// A certificate from elsewhere is refused, not fallen back from.
|
|
ca, err := tlsconf.NewCA("elsewhere")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
leaf, err := ca.IssueServer("other", []string{"127.0.0.1"})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
cert, err := tls.X509KeyPair(leaf.CertPEM, leaf.KeyPEM)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
other := httptest.NewUnstartedServer(answer)
|
|
other.TLS = &tls.Config{Certificates: []tls.Certificate{cert}}
|
|
other.StartTLS()
|
|
defer other.Close()
|
|
if _, err := ask(other.URL); err == nil {
|
|
t.Error("a certificate the authority did not sign was accepted")
|
|
}
|
|
}
|