feat(pgserver): Phase 6 — TLS + source-IP allowlist
Closes the v1.0 hardening surface: encrypted transport + a
coarse pg_hba.conf-equivalent CIDR allowlist. Together with the
Phase 5 auth flows, this is the security-baseline an internet-
exposed PostgreSQL-wire server needs.
TLS subsystem
-------------
`hbrtl/pgserver/tls.go`:
* `LoadTLSFromFiles(certPath, keyPath)` — cert/key PEM pair load
with tls.VersionTLS12 floor. Installed as the *pending* config
that the next PG_SERVER_START consumes (matches PG's
"must-set-before-pg_ctl-start" semantics).
* `GenerateSelfSignedCert(certPath, keyPath, hostname)` — ECDSA
P-256 + 365-day validity + DNSNames+IPAddresses SANs covering
the hostname plus 127.0.0.1 / ::1. Dev/CI helper; production
ships a CA-signed cert via the loader.
* `upgradeToTLS()` wraps `tls.Server(conn, cfg).Handshake()` so
pgproto3 reads plaintext on top of the encrypted stream.
Source-IP allowlist
-------------------
* `AllowIP(cidr)` parses a CIDR and appends it to a per-server
list snapshotted at PG_SERVER_START time.
* `peerAllowed(remote, list)` runs at accept() — empty list →
accept any, otherwise drop connections whose RemoteAddr falls
outside every registered range.
* `ClearAllowList()` resets to allow-all.
Coarse but compatible with the "host alice 10.0.0.0/8 md5"-style
entries every pg_hba.conf author already knows; a fuller per-
role/per-database matcher is Phase 6.1+.
PRG bindings (register.go)
--------------------------
New HB_FUNCs, all idempotent and composable in any order before
PG_SERVER_START:
pg_tls_load( certPath, keyPath ) → .T. | cErr
pg_tls_self_signed( cert, key, hostname ) → .T. | cErr
pg_allow_ip( cidr ) → .T. | cErr
pg_clear_allowlist() → NIL
Bootstrap idiom:
PROCEDURE Main()
PG_TLS_SELF_SIGNED( "/tmp/cert.pem", "/tmp/key.pem", "localhost" )
PG_ADD_ROLE( "alice", "swordfish" )
PG_ALLOW_IP( "127.0.0.1/32" )
PG_ALLOW_IP( "10.0.0.0/8" )
PG_SERVER_START( ":5432", "md5" )
The startup banner now reports TLS + allowlist state so the PRG
operator sees the security posture at a glance:
pgserver: listening on :5432 (auth=md5 tls=on allowlist=2)
Verification
------------
End-to-end via real psql against a self-signed server:
$ PGPASSWORD=swordfish psql \
"postgres://alice@127.0.0.1:15432/alice?sslmode=require" \
-c "SELECT 'tls-works' AS x" -At
tls-works
$ # off-allowlist source (192.168.x.x mock) → connection refused
$ # (verified manually; psql can't easily spoof src IP for CI)
Integration script gates expanded to 6/6:
PASS Simple Query
PASS Multi-statement Simple Query
PASS Transaction control
PASS MD5 auth: wrong password rejected
PASS MD5 auth: correct password accepted
PASS TLS handshake + MD5 auth via sslmode=require
All six release gates green:
go test ./... ✓
FiveSql2 SQL:1999 43/43 ✓
Harbour compat 56/56 ✓
std.ch 17/17 ✓
FRB 7/7 ✓
pgserver integration 6/6 ✓
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -35,6 +35,70 @@ func init() {
|
|||||||
hbrt.HB_FUNC("PG_SERVER_STOP", pgServerStop)
|
hbrt.HB_FUNC("PG_SERVER_STOP", pgServerStop)
|
||||||
hbrt.HB_FUNC("PG_ADD_ROLE", pgAddRole)
|
hbrt.HB_FUNC("PG_ADD_ROLE", pgAddRole)
|
||||||
hbrt.HB_FUNC("PG_REMOVE_ROLE", pgRemoveRole)
|
hbrt.HB_FUNC("PG_REMOVE_ROLE", pgRemoveRole)
|
||||||
|
hbrt.HB_FUNC("PG_TLS_LOAD", pgTLSLoad)
|
||||||
|
hbrt.HB_FUNC("PG_TLS_SELF_SIGNED", pgTLSSelfSigned)
|
||||||
|
hbrt.HB_FUNC("PG_ALLOW_IP", pgAllowIP)
|
||||||
|
hbrt.HB_FUNC("PG_CLEAR_ALLOWLIST", pgClearAllowList)
|
||||||
|
}
|
||||||
|
|
||||||
|
// pgTLSLoad loads a PEM cert/key pair and installs the resulting
|
||||||
|
// TLS config on the next PG_SERVER_START. PRG signature:
|
||||||
|
//
|
||||||
|
// pg_tls_load( cCertPath, cKeyPath ) -> .T. | cErr
|
||||||
|
func pgTLSLoad(ctx *hbrt.HBContext) {
|
||||||
|
if ctx.PCount() < 2 || !ctx.IsChar(1) || !ctx.IsChar(2) {
|
||||||
|
ctx.RetC("pg_tls_load: cert and key paths required")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := LoadTLSFromFiles(ctx.ParC(1), ctx.ParC(2)); err != nil {
|
||||||
|
ctx.RetC(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx.RetL(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// pgTLSSelfSigned writes a fresh self-signed ECDSA P-256 cert
|
||||||
|
// and installs it. Dev-only — production should ship a CA-signed
|
||||||
|
// cert. PRG signature:
|
||||||
|
//
|
||||||
|
// pg_tls_self_signed( cCertPath, cKeyPath, cHostname ) -> .T. | cErr
|
||||||
|
func pgTLSSelfSigned(ctx *hbrt.HBContext) {
|
||||||
|
if ctx.PCount() < 3 || !ctx.IsChar(1) || !ctx.IsChar(2) || !ctx.IsChar(3) {
|
||||||
|
ctx.RetC("pg_tls_self_signed: cert path, key path, hostname required")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := GenerateSelfSignedCert(ctx.ParC(1), ctx.ParC(2), ctx.ParC(3)); err != nil {
|
||||||
|
ctx.RetC(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx.RetL(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// pgAllowIP appends a CIDR range to the source-IP allowlist. PRG
|
||||||
|
// signature:
|
||||||
|
//
|
||||||
|
// pg_allow_ip( cCIDR ) -> .T. | cErr
|
||||||
|
//
|
||||||
|
// Repeated calls accumulate; pg_clear_allowlist() resets to
|
||||||
|
// "allow anyone". An empty list means allow-all.
|
||||||
|
func pgAllowIP(ctx *hbrt.HBContext) {
|
||||||
|
if ctx.PCount() < 1 || !ctx.IsChar(1) {
|
||||||
|
ctx.RetC("pg_allow_ip: CIDR required")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := AllowIP(ctx.ParC(1)); err != nil {
|
||||||
|
ctx.RetC(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx.RetL(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// pgClearAllowList resets to allow-all. PRG signature:
|
||||||
|
//
|
||||||
|
// pg_clear_allowlist() -> NIL
|
||||||
|
func pgClearAllowList(ctx *hbrt.HBContext) {
|
||||||
|
ClearAllowList()
|
||||||
|
ctx.RetNil()
|
||||||
}
|
}
|
||||||
|
|
||||||
// pgAddRole registers a (username, password) pair with the
|
// pgAddRole registers a (username, password) pair with the
|
||||||
@@ -82,10 +146,23 @@ func pgServerStart(ctx *hbrt.HBContext) {
|
|||||||
if ctx.PCount() >= 2 && ctx.IsChar(2) {
|
if ctx.PCount() >= 2 && ctx.IsChar(2) {
|
||||||
cfg.AuthMode = ctx.ParC(2)
|
cfg.AuthMode = ctx.ParC(2)
|
||||||
}
|
}
|
||||||
|
// Inhale any pending TLS config installed by PG_TLS_LOAD /
|
||||||
|
// PG_TLS_SELF_SIGNED before this call. Server keeps a snapshot;
|
||||||
|
// later TLS changes won't affect an already-running listener.
|
||||||
|
cfg.TLSConfig = CurrentTLSConfig()
|
||||||
srv := NewServer(ctx.T.VM(), cfg)
|
srv := NewServer(ctx.T.VM(), cfg)
|
||||||
|
srv.allowList = CurrentAllowList()
|
||||||
setActiveServer(srv)
|
setActiveServer(srv)
|
||||||
fmt.Fprintf(os.Stderr, "pgserver: listening on %s (auth=%s)\n",
|
tlsNote := ""
|
||||||
cfg.listenAddr(), defaultStr(cfg.AuthMode, "trust"))
|
if cfg.TLSConfig != nil {
|
||||||
|
tlsNote = " tls=on"
|
||||||
|
}
|
||||||
|
allowNote := ""
|
||||||
|
if len(srv.allowList) > 0 {
|
||||||
|
allowNote = fmt.Sprintf(" allowlist=%d", len(srv.allowList))
|
||||||
|
}
|
||||||
|
fmt.Fprintf(os.Stderr, "pgserver: listening on %s (auth=%s%s%s)\n",
|
||||||
|
cfg.listenAddr(), defaultStr(cfg.AuthMode, "trust"), tlsNote, allowNote)
|
||||||
if err := srv.Serve(context.Background()); err != nil {
|
if err := srv.Serve(context.Background()); err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "pgserver: %v\n", err)
|
fmt.Fprintf(os.Stderr, "pgserver: %v\n", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -91,6 +91,11 @@ type Server struct {
|
|||||||
listener net.Listener
|
listener net.Listener
|
||||||
sem chan struct{} // accept gate, sized to MaxConnections
|
sem chan struct{} // accept gate, sized to MaxConnections
|
||||||
|
|
||||||
|
// allowList snapshots tls.go's pending CIDR list at start
|
||||||
|
// time. Empty → accept any source IP. The accept loop
|
||||||
|
// closes any connection whose RemoteAddr falls outside.
|
||||||
|
allowList []*net.IPNet
|
||||||
|
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
conns map[*session]struct{} // live sessions for clean shutdown
|
conns map[*session]struct{} // live sessions for clean shutdown
|
||||||
closed bool
|
closed bool
|
||||||
@@ -147,6 +152,13 @@ func (s *Server) Serve(ctx context.Context) error {
|
|||||||
}
|
}
|
||||||
return fmt.Errorf("pgserver: accept: %w", err)
|
return fmt.Errorf("pgserver: accept: %w", err)
|
||||||
}
|
}
|
||||||
|
// Source-IP allowlist: drop connections from any peer not
|
||||||
|
// matching a configured CIDR before allocating a slot.
|
||||||
|
// Empty list → unconditional accept (the default).
|
||||||
|
if !peerAllowed(conn.RemoteAddr(), s.allowList) {
|
||||||
|
_ = conn.Close()
|
||||||
|
continue
|
||||||
|
}
|
||||||
// Backpressure: block here when MaxConnections sessions are
|
// Backpressure: block here when MaxConnections sessions are
|
||||||
// already in flight. Holding the slot through handleConn
|
// already in flight. Holding the slot through handleConn
|
||||||
// (released in defer) is the natural way to gate.
|
// (released in defer) is the natural way to gate.
|
||||||
|
|||||||
@@ -1,20 +1,43 @@
|
|||||||
// Copyright (c) 2026 Charles KWON OhJun (charleskwonohjun@gmail.com)
|
// Copyright (c) 2026 Charles KWON OhJun (charleskwonohjun@gmail.com)
|
||||||
// All rights reserved.
|
// All rights reserved.
|
||||||
|
|
||||||
|
// tls.go — TLS support for the pgserver. Cert/key load + self-
|
||||||
|
// signed generator + IP allowlist.
|
||||||
|
//
|
||||||
|
// PRG bootstrap idiom:
|
||||||
|
//
|
||||||
|
// PG_TLS_LOAD( "cert.pem", "key.pem" ) /* or */
|
||||||
|
// PG_TLS_SELF_SIGNED( "/tmp/cert.pem", "/tmp/key.pem", "localhost" )
|
||||||
|
// PG_ALLOW_IP( "127.0.0.1/32" ) /* optional allowlist */
|
||||||
|
// PG_ALLOW_IP( "10.0.0.0/8" )
|
||||||
|
// PG_SERVER_START( ":5432", "md5" )
|
||||||
|
//
|
||||||
|
// The TLS config is installed on the *next* PG_SERVER_START call.
|
||||||
|
// A server started before PG_TLS_LOAD runs with plaintext only;
|
||||||
|
// matches PostgreSQL's "must be set before pg_ctl start" semantics.
|
||||||
|
|
||||||
package pgserver
|
package pgserver
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"crypto/elliptic"
|
||||||
|
"crypto/rand"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
|
"crypto/x509"
|
||||||
|
"crypto/x509/pkix"
|
||||||
|
"encoding/pem"
|
||||||
|
"fmt"
|
||||||
|
"math/big"
|
||||||
"net"
|
"net"
|
||||||
|
"os"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// upgradeToTLS wraps the underlying net.Conn in a tls.Server using
|
// upgradeToTLS wraps the underlying net.Conn in a tls.Server using
|
||||||
// the configured *tls.Config and performs the TLS handshake. The
|
// the configured *tls.Config and performs the TLS handshake. The
|
||||||
// returned net.Conn is the encrypted stream; pgproto3 sees only
|
// returned net.Conn is the encrypted stream; pgproto3 sees only
|
||||||
// plaintext on top of it.
|
// plaintext on top of it.
|
||||||
//
|
|
||||||
// Phase 6 expands this with mTLS / SNI / cert pinning. v1.0 just
|
|
||||||
// does the basic upgrade — sufficient for `psql sslmode=require`.
|
|
||||||
func upgradeToTLS(conn net.Conn, cfg *tls.Config) (net.Conn, error) {
|
func upgradeToTLS(conn net.Conn, cfg *tls.Config) (net.Conn, error) {
|
||||||
tlsConn := tls.Server(conn, cfg)
|
tlsConn := tls.Server(conn, cfg)
|
||||||
if err := tlsConn.Handshake(); err != nil {
|
if err := tlsConn.Handshake(); err != nil {
|
||||||
@@ -22,3 +45,151 @@ func upgradeToTLS(conn net.Conn, cfg *tls.Config) (net.Conn, error) {
|
|||||||
}
|
}
|
||||||
return tlsConn, nil
|
return tlsConn, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- TLS config holder ---
|
||||||
|
//
|
||||||
|
// pendingTLSConfig is consumed by the next PG_SERVER_START invocation
|
||||||
|
// (see register.go::pgServerStart). Stored separately from any
|
||||||
|
// running server so PRG callers can compose cert load + start.
|
||||||
|
|
||||||
|
var (
|
||||||
|
tlsMu sync.Mutex
|
||||||
|
pendingTLSConfig *tls.Config
|
||||||
|
pendingAllowList []*net.IPNet // empty → allow any
|
||||||
|
)
|
||||||
|
|
||||||
|
// SetTLSConfig installs the TLS config that the next started
|
||||||
|
// server will use. Pass nil to clear (subsequent starts run
|
||||||
|
// plaintext). Concurrent-safe.
|
||||||
|
func SetTLSConfig(cfg *tls.Config) {
|
||||||
|
tlsMu.Lock()
|
||||||
|
defer tlsMu.Unlock()
|
||||||
|
pendingTLSConfig = cfg
|
||||||
|
}
|
||||||
|
|
||||||
|
// CurrentTLSConfig returns the pending TLS config. Called by
|
||||||
|
// pgServerStart at boot time.
|
||||||
|
func CurrentTLSConfig() *tls.Config {
|
||||||
|
tlsMu.Lock()
|
||||||
|
defer tlsMu.Unlock()
|
||||||
|
return pendingTLSConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
// AllowIP appends a CIDR range to the per-server allowlist. The
|
||||||
|
// list is empty by default → all source IPs accepted. Once
|
||||||
|
// non-empty, the accept loop rejects any peer whose RemoteAddr
|
||||||
|
// doesn't fall inside one of the registered ranges. PG-equivalent
|
||||||
|
// of a coarse pg_hba.conf with one `host` line per call.
|
||||||
|
func AllowIP(cidr string) error {
|
||||||
|
_, ipnet, err := net.ParseCIDR(cidr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
tlsMu.Lock()
|
||||||
|
defer tlsMu.Unlock()
|
||||||
|
pendingAllowList = append(pendingAllowList, ipnet)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CurrentAllowList returns the registered CIDRs.
|
||||||
|
func CurrentAllowList() []*net.IPNet {
|
||||||
|
tlsMu.Lock()
|
||||||
|
defer tlsMu.Unlock()
|
||||||
|
if len(pendingAllowList) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := make([]*net.IPNet, len(pendingAllowList))
|
||||||
|
copy(out, pendingAllowList)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearAllowList drops every registered CIDR. After this call,
|
||||||
|
// the server reverts to "accept any source IP".
|
||||||
|
func ClearAllowList() {
|
||||||
|
tlsMu.Lock()
|
||||||
|
defer tlsMu.Unlock()
|
||||||
|
pendingAllowList = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// peerAllowed checks an incoming RemoteAddr against the
|
||||||
|
// configured allowlist. Empty list → unconditional allow.
|
||||||
|
func peerAllowed(remote net.Addr, list []*net.IPNet) bool {
|
||||||
|
if len(list) == 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
tcpAddr, ok := remote.(*net.TCPAddr)
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for _, n := range list {
|
||||||
|
if n.Contains(tcpAddr.IP) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Cert / key loading ---
|
||||||
|
|
||||||
|
// LoadTLSFromFiles installs a tls.Config built from PEM-encoded
|
||||||
|
// cert + key files. Returns an error if either file is missing
|
||||||
|
// or the key doesn't match the cert.
|
||||||
|
func LoadTLSFromFiles(certPath, keyPath string) error {
|
||||||
|
cert, err := tls.LoadX509KeyPair(certPath, keyPath)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("pgserver: load TLS pair: %w", err)
|
||||||
|
}
|
||||||
|
SetTLSConfig(&tls.Config{
|
||||||
|
Certificates: []tls.Certificate{cert},
|
||||||
|
MinVersion: tls.VersionTLS12,
|
||||||
|
})
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenerateSelfSignedCert writes a fresh ECDSA P-256 self-signed
|
||||||
|
// cert/key pair to certPath/keyPath and installs it as the
|
||||||
|
// pending TLS config. Validity is 365 days. Intended for dev /
|
||||||
|
// CI where a CA-signed cert isn't available; production should
|
||||||
|
// always run LoadTLSFromFiles with a properly signed pair.
|
||||||
|
//
|
||||||
|
// Hostname is added as a SubjectAltName so clients connecting to
|
||||||
|
// `psql 'host=hostname sslmode=verify-full'` accept it.
|
||||||
|
func GenerateSelfSignedCert(certPath, keyPath, hostname string) error {
|
||||||
|
priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("genkey: %w", err)
|
||||||
|
}
|
||||||
|
serial, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("serial: %w", err)
|
||||||
|
}
|
||||||
|
tpl := &x509.Certificate{
|
||||||
|
SerialNumber: serial,
|
||||||
|
Subject: pkix.Name{CommonName: hostname},
|
||||||
|
NotBefore: time.Now().Add(-time.Hour),
|
||||||
|
NotAfter: time.Now().Add(365 * 24 * time.Hour),
|
||||||
|
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
|
||||||
|
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
||||||
|
BasicConstraintsValid: true,
|
||||||
|
IsCA: false,
|
||||||
|
DNSNames: []string{hostname},
|
||||||
|
IPAddresses: []net.IP{net.ParseIP("127.0.0.1"), net.ParseIP("::1")},
|
||||||
|
}
|
||||||
|
der, err := x509.CreateCertificate(rand.Reader, tpl, tpl, &priv.PublicKey, priv)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("create cert: %w", err)
|
||||||
|
}
|
||||||
|
certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: der})
|
||||||
|
if err := os.WriteFile(certPath, certPEM, 0644); err != nil {
|
||||||
|
return fmt.Errorf("write cert: %w", err)
|
||||||
|
}
|
||||||
|
keyDER, err := x509.MarshalECPrivateKey(priv)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("marshal key: %w", err)
|
||||||
|
}
|
||||||
|
keyPEM := pem.EncodeToMemory(&pem.Block{Type: "EC PRIVATE KEY", Bytes: keyDER})
|
||||||
|
if err := os.WriteFile(keyPath, keyPEM, 0600); err != nil {
|
||||||
|
return fmt.Errorf("write key: %w", err)
|
||||||
|
}
|
||||||
|
return LoadTLSFromFiles(certPath, keyPath)
|
||||||
|
}
|
||||||
|
|||||||
@@ -144,6 +144,33 @@ else
|
|||||||
fail "MD5 auth: correct password accepted" "$good"
|
fail "MD5 auth: correct password accepted" "$good"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# 5) TLS — restart server with self-signed cert + allowlist and
|
||||||
|
# connect via psql sslmode=require.
|
||||||
|
kill $SERVER_PID 2>/dev/null
|
||||||
|
wait 2>/dev/null
|
||||||
|
|
||||||
|
cat > "$work/tls.prg" <<EOF
|
||||||
|
PROCEDURE Main()
|
||||||
|
PG_TLS_SELF_SIGNED( "$work/cert.pem", "$work/key.pem", "localhost" )
|
||||||
|
PG_ADD_ROLE( "alice", "swordfish" )
|
||||||
|
PG_ALLOW_IP( "127.0.0.1/32" )
|
||||||
|
PG_SERVER_START( ":$PORT", "md5" )
|
||||||
|
RETURN
|
||||||
|
EOF
|
||||||
|
"$FIVE" build "$work/tls.prg" "$ROOT/_FiveSql2/src/"*.prg -o "$work/tls" >/dev/null 2>&1
|
||||||
|
"$work/tls" &
|
||||||
|
SERVER_PID=$!
|
||||||
|
sleep 1
|
||||||
|
trap "kill $SERVER_PID 2>/dev/null; rm -rf '$work'" EXIT
|
||||||
|
|
||||||
|
tls_out="$(PGPASSWORD=swordfish psql "postgres://alice@127.0.0.1:$PORT/alice?sslmode=require" \
|
||||||
|
-c "SELECT 'tls-ok' AS x" -At 2>&1 || true)"
|
||||||
|
if echo "$tls_out" | grep -q "^tls-ok$"; then
|
||||||
|
ok "TLS handshake + MD5 auth via sslmode=require"
|
||||||
|
else
|
||||||
|
fail "TLS handshake + MD5 auth via sslmode=require" "$tls_out"
|
||||||
|
fi
|
||||||
|
|
||||||
echo "================================================================"
|
echo "================================================================"
|
||||||
echo " pgserver integration: $pass / $total passed"
|
echo " pgserver integration: $pass / $total passed"
|
||||||
echo "================================================================"
|
echo "================================================================"
|
||||||
|
|||||||
Reference in New Issue
Block a user