Metadata-Version: 2.5
Name: glovebox-egress-gateway
Version: 0.2.0
Summary: The host-side egress verdict service and TLS certificate helper Envoy asks for glovebox's Kata sandbox
Project-URL: Homepage, https://github.com/AlexanderMattTurner/agent-glovebox
Project-URL: Source, https://github.com/AlexanderMattTurner/agent-glovebox
Author: AlexanderMattTurner
License-Expression: Apache-2.0
Keywords: ai-control,egress,proxy,sandbox,tls-inspection
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Science/Research
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Security
Requires-Python: >=3.11
Requires-Dist: glovebox-hosts>=0.4.0
Requires-Dist: graphql-core==3.2.11
Requires-Dist: grpcio==1.80.0
Requires-Dist: protobuf==6.33.6
Requires-Dist: tldextract==5.3.2
Provides-Extra: verdict-service
Requires-Dist: glovebox-monitor>=0.2.0; extra == 'verdict-service'
Description-Content-Type: text/markdown

# glovebox-egress-gateway

The host-side policy engine behind glovebox's Kata egress boundary: a request's **method and path** decide it, not only the host it goes to.

A packet filter can say "this sandbox may reach `github.com`". It cannot say "this sandbox may `git clone` but may not `git push`", because both are TCP to port 443 of the same address. This package reads the request and rules on it: a host tagged `ro` serves `GET`, `HEAD` and `OPTIONS`, and refuses everything else unless the policy names a capability for it. A refused request gets a `403` whose body says the operator restricted it, so an agent reading the answer does not mistake the refusal for an outage.

The policy file is either the table a launcher renders, one `host<TAB>tier<TAB>capabilities` row per host, or the JSON object that table is rendered from. The JSON object has `capabilities`, `domains` and `policy` keys, and `policy.forbidden_hosts` must be present: the service renders the object into the same table and refuses any entry that reaches a forbidden host. `domains` maps a host to `ro` or `rw`, and a host entry covers the host and one label below it: `example.com` covers `api.example.com` and not `api.eu.example.com`, which needs an entry of its own.

It ships two commands. `glovebox-egress-gateway-authz` is the verdict service Envoy asks before it forwards a request out of a Kata sandbox — the guest holds no network interface at all, so Envoy on the host carries every byte and asks this service one question per decrypted request. `glovebox-egress-gateway-san` names the hosts a TLS certificate needs to cover, for the leaf Envoy presents when it terminates one of those requests.

## Answering a proxy's verdict question

Envoy asks over gRPC ext_authz, sending the client's method, request target and `Host` as it parsed them, plus a `channel` value (`plain`, `bump`, `connect` or `setup`) the route set in the check's context.

```
pip install 'glovebox-egress-gateway[verdict-service]'
glovebox-egress-gateway-authz --socket /run/egress/authz.sock \
  --policy /policy.json --evidence /var/log/egress/egress.jsonl
```

Before it answers, the service appends each verdict to the `--evidence` log through `glovebox-monitor`'s chained audit writer. Each record carries a sequence number and the hash of the record before it. The log never rotates. At `GLOVEBOX_AUDIT_SESSION_LIMIT_MB` the write fails, and the service answers no further question. `python -m egress_gateway.egress_evidence LOG` prints the hosts the log admitted and refused.

The service listens on a Unix socket at mode 0600, never a port, and answers each question with an OK verdict carrying the resolved address as `x-envoy-original-dst-host` dynamic metadata, or with the same `403` document a refused request gets. A host the policy admits but the resolver cannot answer for gets a bodiless `503`, which a client retries. Envoy's `status_on_error` also turns any failure to reach the service into a `503`. The `x-envoy-original-dst-host` metadata names an address the service resolved and screened itself, and Envoy dials it without resolving anything, so no second DNS answer can arrive between the check and the connection.

## Naming a certificate's hosts

Envoy terminates TLS for every host the policy admits, and the leaf it presents must name every one of those hosts, plus a wildcard for each. A leaf naming the entry alone fails TLS on exactly the subdomains the policy just granted, and the client reads that as an outage rather than a refusal.

```
glovebox-egress-gateway-san allowlist.json
openssl x509 -req -in leaf.csr -CA ca.pem -CAkey ca-key.pem -out leaf-cert.pem \
  -extfile <(glovebox-egress-gateway-san allowlist.json)
```

A wildcard covers one label: `*.example.com` names `api.example.com` and not `api.eu.example.com`. A `domains` entry stops at that same label, so `DNS:h,DNS:*.h` names every host the entry grants. A host two labels below an entry needs its own `domains` row; without one the service answers `403` and writes a refusal, so you read what happened instead of guessing.
