Metadata-Version: 2.5
Name: glovebox-egress-gateway
Version: 0.4.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.5.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.4.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 JSON object a table is rendered from, or a table a launcher rendered, one `host<TAB>tier<TAB>capabilities` row per host, whose `# policy-identity` header still names its rows. A hand-written or edited table is refused: write the JSON object instead. 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`. A host entry covers that host alone, and `*.example.com` covers every name below `example.com`.

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. A leaf that leaves one out fails TLS on exactly the host 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)
```

The gateway's own leaf names each entry once: `DNS:h` for a host, the `*.S` entry itself for a wildcard. A certificate wildcard covers one label, so a bumped HTTPS request two labels below a `*.S` entry fails its handshake; give that host its own entry. A policy naming no host gets a leaf naming `127.0.0.1` alone. That policy admits no request, so no client is shown the leaf, and the gateway still starts on it.
