mirror of
https://github.com/hanzoai/egress.git
synced 2026-10-30 15:08:38 +00:00
A call and a fetch are each one request and one answer. A database is neither: a caller opens a connection and keeps it, so what egress brokers there is the whole connection. It listens for postgres clients, reads the caller's IAM access token out of the password field — the one field a postgres client has for a secret — and verifies it with the same Verifier that guards every other route. Two origins, and the difference is only where the connection then goes. A base of ours carries NO CREDENTIAL: hanzo-sql and the forks beside it are co-located and reached over the trust between them, with the caller's tenant presented as the database role, so the base's own grants are a second boundary under egress's first. A database that is not ours comes out of the caller's existing custody as a whole connection URL — the same custody.resolve the model and cloud paths read, with a URL where a bearer would be. Custody mode is the one place a caller decides an address, so a sealed origin resolves through a lookup that returns public addresses only and refuses a socket path outright; otherwise the broker is a tunnel to this host's own network. Nothing about a session comes from the environment: pgconn's parser fills an absent setting from PG* and ~/.pgpass, so every field that decides where the connection goes is written over its defaults. A session ends when its token does. pgproto3 is a codec here and never a reader — its own reader may fill past the message it was asked for, and after the handshake every byte read is a byte the database never sees. spend gains Session, which builds the connection URL a driver dials. It adds no dependency to the client module. Co-authored-by: Hanzo Dev <[email protected]>
74 lines
2.8 KiB
Go
74 lines
2.8 KiB
Go
package spend
|
|
|
|
import (
|
|
"net/url"
|
|
"path"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// Session returns what a database driver dials to reach egress.
|
|
//
|
|
// It is the same swap Client is, one layer down. Client hands an SDK an
|
|
// *http.Client and the SDK is unchanged; this hands a driver a connection URL
|
|
// and the driver is unchanged — pgx.Connect, sql.Open, DATABASE_URL. What
|
|
// changes underneath is that the password is this caller's IAM access token
|
|
// rather than a database password, and the far end is egress, which holds
|
|
// whatever the real database wants and attaches it there.
|
|
//
|
|
// Provider names the base, and it travels as the postgres user because that is
|
|
// the field a connection URL has for it. Database names the database within
|
|
// that base and travels as its own.
|
|
//
|
|
// The token expires, which is the point of it and is worth saying out loud: the
|
|
// URL is good for as long as the token in it is, so a pool that opens a
|
|
// connection later needs one built later. Call this again — it is a string and
|
|
// costs nothing — from wherever the driver lets you set a password per
|
|
// connection, and mint the token the way visor does, replacing it before it
|
|
// dies rather than after.
|
|
func Session(cfg Config) string {
|
|
u := &url.URL{
|
|
Scheme: "postgres",
|
|
User: url.UserPassword(cfg.Provider, cfg.Token),
|
|
Path: "/" + cfg.Database,
|
|
}
|
|
// sslmode is stated rather than left to default, so that this leg is what
|
|
// the URL says it is and not what the environment last decided. Egress
|
|
// answers no to TLS here: what the caller reaches is a unix socket or a
|
|
// link the operator trusts, and the encryption that matters is on the leg
|
|
// egress makes to the database.
|
|
q := url.Values{"sslmode": {"disable"}}
|
|
if deadline := cfg.Deadline; deadline > 0 {
|
|
q.Set("connect_timeout", strconv.Itoa(int((deadline+time.Second-1)/time.Second)))
|
|
}
|
|
if cfg.Network == "unix" || strings.HasPrefix(cfg.Address, "/") {
|
|
// libpq takes a socket as the DIRECTORY holding it and derives the file
|
|
// from the port, so the address egress binds is split back into the two
|
|
// halves a client states. One value configures both ends.
|
|
dir, port := socket(cfg.Address)
|
|
q.Set("host", dir)
|
|
if port != "" {
|
|
q.Set("port", port)
|
|
}
|
|
} else {
|
|
u.Host = cfg.Address
|
|
}
|
|
u.RawQuery = q.Encode()
|
|
return u.String()
|
|
}
|
|
|
|
// prefix is how libpq names a socket file inside its directory.
|
|
const prefix = ".s.PGSQL."
|
|
|
|
// socket splits the path egress binds into the directory and port a client
|
|
// states. A path that is not named the way libpq names one is taken whole as
|
|
// the directory, which is what a client would do with it too.
|
|
func socket(address string) (dir, port string) {
|
|
base := path.Base(address)
|
|
if !strings.HasPrefix(base, prefix) {
|
|
return address, ""
|
|
}
|
|
return path.Dir(address), strings.TrimPrefix(base, prefix)
|
|
}
|