Documentation
¶
Overview ¶
Package serve answers read-only questions about a warehouse, over HTTP, so the console never holds a warehouse credential.
Everything here runs on the far side of that line. The console forwards a request; this decides what may run, bounds it, runs it and says what it did. A limit the console could change is not a limit, which is why none of them live there.
Index ¶
Constants ¶
const CodeNoConnection = "no-connection"
CodeNoConnection is what the console matches on. See ErrNoConnection.
const EnvLocal = "local"
EnvLocal is the one environment where an open endpoint is allowed.
Variables ¶
var ErrNoConnection = errors.New("no connection is declared for that destination")
ErrNoConnection is what an opener returns when nothing is DECLARED for a destination, as opposed to declared and unreachable.
THE DIFFERENCE IS THE WHOLE POINT. "Unreachable" is an incident and says nothing a reader can act on; "nobody has declared where that database is" is a sentence with an action in it, and V4 puts it on the screen. A 502 would bury the second inside the first.
Functions ¶
func BytesText ¶
BytesText is a byte count somebody can judge at a glance.
A refusal saying "10737418240 bytes" is one somebody has to count the digits of, and the number IS the content of that message: it is the difference between a typo and a query worth waiting for.
func ReadOnly ¶
ReadOnly refuses any statement that is not a single query.
THIS IS THE FUNCTION BETWEEN A BROWSER AND A CUSTOMER'S WAREHOUSE, and it is written to be read by somebody deciding whether to trust it.
OVER A TOKENISER AND NOT A REGEX, which is the whole reason `sqltok` exists as its own package. A pattern matching `DELETE` cannot tell code from a string literal or a comment, so it refuses `WHERE note = 'please DELETE this row'` -- a legitimate query -- and it accepts `/* SELECT */ DROP TABLE t`. Both of those are cases below.
WHAT IT CANNOT DO, said plainly so nobody discovers it as a bug: a function call writes whatever the function writes. `SELECT nextval('s')` advances a sequence and `SELECT dblink_exec(...)` runs anything at all, and no classifier that is not a planner can see inside them. THE READ-ONLY ROLE IS THE DEFENCE THERE, not this -- which is exactly the question CHECKPOINT B exists to answer before this is deployed anywhere.
WHEN IT CANNOT FOLLOW THE STATEMENT, IT REFUSES. A parser that gives up and allows is a parser that can be made to give up on purpose.
Types ¶
type Caller ¶
type Caller struct {
// Name is what the audit line carries. It reaches a JSON document and
// somebody's grep, so it is checked at boot rather than escaped later.
Name string
// Token is the bearer this caller presents.
Token string
}
Caller is one named bearer.
CHECKPOINT B's F7: the audit line carried connection, hash, rows, bytes, duration and outcome, and nothing about WHO asked. With one operator that was a note; with two it means the log cannot answer the only question it is read for.
IDENTITY IS THE CREDENTIAL, not a header. A caller that announced its own name would be making a CLAIM, and an audit of claims is an audit of whatever an attacker typed. A token the operator issued is a FACT: the line cannot say anything the holder of that token did not actually hold.
The Name is not a secret -- it is printed at boot and written to every line. The Token is, and appears in neither.
type Options ¶
type Options struct {
// Env decides whether an unauthenticated service may exist. It comes from
// BREVIS_ENV and NEVER from a configuration file -- the gateway's rule,
// and its reason: "a config file that could declare itself local would be
// a file that turns off authentication."
Env string
// Token is the bearer every request must carry, with NO NAME attached.
// Required outside local unless Callers names one.
//
// It is what every deployment has today and it keeps working. Its audit
// lines carry no caller, which is itself the finding: a shared secret
// with nobody's name on it.
Token string
// Callers are named bearers, and the audit line carries whichever one
// asked. See caller.go for why identity is the credential rather than a
// header. Folded together with Token into one list at boot.
Callers []Caller
// Addr is where this will listen, and it is EVIDENCE rather than
// configuration: see New. Empty means nobody is deploying anything --
// a test, or something embedding this -- and no rule is drawn from it.
Addr string
// Rows is the ceiling. A caller may ask for fewer and cannot ask for more.
Rows int
// Bytes is the most a single query may SCAN. A caller cannot raise it
// and cannot name one at all: it is money, and the browser is not the
// place that decides how much of it to spend.
//
// IT BOUNDS THE PREVIEW TOO, and that was a hole rather than a choice.
// A preview is `SELECT * FROM t LIMIT 20`, and a LIMIT does not reduce
// what BigQuery scans -- twenty rows off a petabyte table reads the
// petabyte and bills for it. The row ceiling bounds the screen and
// bounds nothing else.
Bytes int64
// Budget is the most one CONNECTION may scan in an hour, across every
// query. Zero is no budget, which is the default.
//
// A CONCURRENCY LIMIT IS NOT A BUDGET -- CHECKPOINT B's F6. Four queries
// at a time, each under the per-query ceiling, repeated forever, is
// unbounded: the ceiling bounds one query and nothing bounds the sum.
// For a single authenticated operator no budget may well be the right
// answer, but it has to be a CHOICE, and an absence is not one. The boot
// banner states whichever it is.
//
// PER CONNECTION, because a connection is what pays the bill: two
// warehouses are two accounts, and exhausting one must not close the
// other.
//
// IT BOUNDS WHAT IS METERED, which is not everything. A warehouse that
// charges for a machine by the hour reports no bytes, so a budget bounds
// BigQuery and bounds nothing on Postgres. That is fine -- Postgres is
// not billed by the byte -- and dangerous only if somebody believes
// otherwise, which is why the banner says which connections it can
// actually bound.
Budget int64
// Writable NAMES THE CONNECTIONS on which a statement may write. Empty
// is the default and is every deployment that has not asked otherwise.
//
// IT COMES FROM BREVIS_SQL_SERVE_WRITES AND NEVER FROM A FILE, which is
// the rule Token already follows one field up and for the same reason: a
// config file that could declare itself local would be a file that turns
// off authentication, and one that could declare itself writable would
// be a file that turns off every limit on this surface at once.
//
// NAMES AND NOT A BOOLEAN. One service reaches several warehouses, and
// unlocking the one somebody develops against must not unlock the one
// beside it. The accident this exists to prevent is a development
// service that is also pointed at a production DSN, and a switch cannot
// tell those apart.
//
// A NAME HERE STANDS DOWN EVERY LAYER AT ONCE -- the tokeniser, the
// warehouse's verdict and the warehouse's enforcement -- so on a named
// connection this service is exactly as powerful as its credential. The
// row ceiling bounds a screen and the byte ceiling bounds MONEY; neither
// bounds damage, because an UPDATE scans almost nothing and changes
// everything.
Writable []string
// Audit is where one line per query goes. Nil writes none, which is what
// a test wants and never what a deployment does.
Audit io.Writer
// Concurrent is how many queries may run at once. Over that, a caller is
// REFUSED rather than queued: a queue behind a browser is a browser that
// waits with no way to know why, and every waiting request still holds a
// connection to the warehouse.
Concurrent int
// Timeout bounds one query, the dry run and the read together.
Timeout time.Duration
// Open connects to one warehouse. A field so a test can hand over a fake
// without a warehouse, and so this package holds no driver of its own.
//
// IT TAKES THE WHOLE TARGET and not just a name, because a name is not
// unique on its own: `app` can be a BigQuery project and a Postgres
// database at once, and the registry matches on both halves.
Open func(ctx context.Context, t Table) (dialect.Conn, error)
}
Options is what a Service needs.
type Service ¶
type Service struct {
// contains filtered or unexported fields
}
Service answers read-only questions about a warehouse.
func New ¶
New builds the service, or refuses to.
REFUSING TO EXIST IS THE POINT. A service that started without a token and logged a warning would be an open endpoint onto a customer's warehouse, with a line in a log nobody reads. Failing at boot is the only refusal that cannot be ignored.
func (*Service) Metrics ¶
Metrics is the exposition, and it is a SEPARATE HANDLER on purpose.
The rule this repository states twice, in the engine and in the gateway: a scrape endpoint on the port that answers requests would either need a session no scraper has, or publish every connection name to whoever can reach that port. `Handler` does not route /metrics at all, and a test says so.
It carries no authentication of its own, for the same reason the gateway's does not: it is bound to an address an operator chooses, and a scraper cannot hold a token.
type Table ¶
type Table struct {
// Connection is which warehouse: the BigQuery project.
Connection string
// Dialect is whose SQL this is, which the classifier needs: `#` opens a
// comment in one of them and is an operator in the other, and reading
// the wrong one wrong is how `# SELECT\nDROP TABLE t` gets through.
Dialect string
// Relation is `dataset.table`, ready to be written into SQL -- which is
// safe only because both halves matched `identifier`.
Relation string
}
Table is a target this service can read.
func ParseTarget ¶
ParseTarget turns a catalog target into something readable, or refuses it.
THE MOST DANGEROUS FUNCTION IN THIS PACKAGE. What it returns is written into a statement, so nothing here escapes an input and then uses it: every segment must ALREADY be a name BigQuery could hold, and anything else is refused whole. A semicolon, a comment marker, a backtick or a space each turn one statement into two.
BIGQUERY ONLY, FOR NOW, and that is a shape rather than an omission: a `bigquery://` target carries its project, and the project IS the connection. `postgres://database/schema/table` carries a database name and no host, port or user -- a target is an identity and never an address -- so it cannot be reached without a connection somebody declared, which is a later slice.
`s3`, `gs`, `file` and `pubsub` are not tables. They are in the catalog because Brevis lands them, and they have no rows to preview.