100 lines
3.3 KiB
Go
100 lines
3.3 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net"
|
|
"net/http"
|
|
"net/netip"
|
|
"time"
|
|
|
|
"golang.zx2c4.com/wireguard/tun/netstack"
|
|
)
|
|
|
|
// piiForwardPort is the tunnel-side port the host dials to reach the PII
|
|
// sterilization service on this box's LAN (or this box). Reported in the
|
|
// heartbeat (agents.go "pii_port") so the host never has to assume it —
|
|
// ONE definition, here. Next in the forwarder series after llm's 18080.
|
|
const piiForwardPort = 18081
|
|
|
|
// piiProbe reports (advertised, healthy) for the PII sterilization lane.
|
|
// PRIVATAE_PII_ADDR unset means the service is absent — no probe, no
|
|
// capability, no heartbeat fields; a box without it is unaffected.
|
|
// When set, `advertised` is true even if the upstream is down: the
|
|
// forwarder is running, and pii_ok=false is the distinct, useful signal
|
|
// that the lane exists but its upstream is dead (the host gates writes
|
|
// on it instead of discovering the failure one request at a time).
|
|
//
|
|
// /health is the service's only unauthenticated route; 200 is alive.
|
|
// The bearer-authed surface is proxied opaquely — this client never
|
|
// holds the PII_API_KEY.
|
|
func piiProbe() (bool, bool) {
|
|
return piiProbeAt(piiTarget())
|
|
}
|
|
|
|
// piiProbeAt is piiProbe's probe of one address.
|
|
func piiProbeAt(a string) (bool, bool) {
|
|
if a == "" {
|
|
return false, false
|
|
}
|
|
cl := &http.Client{Timeout: 3 * time.Second}
|
|
resp, err := cl.Get("http://" + a + "/health")
|
|
if err != nil {
|
|
return true, false
|
|
}
|
|
defer resp.Body.Close()
|
|
return true, resp.StatusCode == http.StatusOK
|
|
}
|
|
|
|
// servePIIForward is serveLLMForward's twin for the PII sterilization
|
|
// service: host dials <bastion tunnel IP>:18081, we proxy raw TCP to
|
|
// PRIVATAE_PII_ADDR. Only runs when the env is set (the operator's opt-in;
|
|
// unset = absent, nothing advertised). Raw TCP splice: no buffering, no
|
|
// body caps, no header rewrites — the service's own limits (2 MiB cap,
|
|
// bearer auth, fail-closed 502) pass through untouched, so moving the
|
|
// service to another machine is exactly one env change on this bastion.
|
|
func servePIIForward(ctx context.Context, self string, tnet *netstack.Net, gwIP netip.Addr) {
|
|
target := piiTarget()
|
|
if target == "" {
|
|
return
|
|
}
|
|
ln, err := tnet.ListenTCP(&net.TCPAddr{IP: gwIP.AsSlice(), Port: piiForwardPort})
|
|
if err != nil {
|
|
return
|
|
}
|
|
defer context.AfterFunc(ctx, func() { _ = ln.Close() })()
|
|
refused := inferenceRefuser(self, "pii")
|
|
scopeRefused := piiScopeRefuser(self)
|
|
for {
|
|
in, err := ln.Accept()
|
|
if err != nil {
|
|
return
|
|
}
|
|
if refused(in) || scopeRefused(in) {
|
|
continue
|
|
}
|
|
go spliceConn(in, target)
|
|
}
|
|
}
|
|
|
|
// piiScopeRefuser is the model-scope half of the accept gate, and the ONE place in
|
|
// this feature where a model scope really can be decided at accept time: the
|
|
// sterilizer is not a model, so a grant that names models says nothing about it
|
|
// and it is refused unless the reserved entry `pii` was named (modelscope.go).
|
|
// Same per-connection re-read and once-per-reason logging as inferenceRefuser.
|
|
func piiScopeRefuser(self string) func(in net.Conn) bool {
|
|
logged := false
|
|
return func(in net.Conn) bool {
|
|
if pairingModelScope(self).allowsPII() {
|
|
return false
|
|
}
|
|
_ = in.Close()
|
|
if !logged {
|
|
fmt.Printf("[%s] pii forward: refused — this pairing is scoped to named models and "+
|
|
"the PII sterilizer was not one of them (approve with 'pii' in the model list to share it)\n", self)
|
|
logged = true
|
|
}
|
|
return true
|
|
}
|
|
}
|