This project contains a gateway implementation for an Oblivious HTTP (OHTTP) gateway in Go.
This gateway implements a simple version of the Oblivious Gateway Resource as described in the specification. In particular, it accepts encapsulated Binary HTTP requests and then uses the corresponding HTTP requests to fetch a Target resource. The response from this Target is encapsulated back to the original client of the encapsulated request.
By default, the gateway exposes the following API endpoints:
- "/gateway": An endpoint that will accept OHTTP requests, fetch the corresponding target resource, and return an OHTTP response.
- "/gateway-echo": An endpoint that will echo the contents of the encapsulated OHTTP request back in an OHTTP response.
- "/ohttp-configs": An endpoint that will provide an encoded KeyConfig.
- "/health": An endpoint for inspecting the health of the gateway (returns 200 in normal conditions).
The gateway only supports the HPKE ciphersuite based on DHKEM(X25519, HKDF-SHA256), HKDF-SHA256, and AES-128-GCM.
The gateway does not currently support key rotation. This issue tracks adding this feature.
This section describes deployment instructions for the gateway.
The behavior of the gateway is configurable via a number of environment variables. These are explained below.
- SEED_SECRET_KEY: This environment variable is a hex-encoded byte array representing a secret seed used to derive the gateway private and public key pair. It MUST be 32 randomly generated bytes produced from a cryptographically secure random number generator, such as /dev/urandom. See this guidance for additional information.
- ALLOWED_TARGET_ORIGINS: This environment variable contains a comma-separated list of target origin names that the gateway is allowed to access. When configured, the gateway will only attempt to resolve requests to target origins in this list. Any other request will yield a HTTP 403 Forbidden return code.
- CERT: This environment variable is the name of a file containing the certificate (chain) used to serve TLS connections.
- KEY: This environment variable is the name of a file containing the private key used to serve TLS connections.
- LOG_FORMAT: This environment variable controls the format in which events are logged. Supported values are:
default
: events are run throughslog.TextHandler
.json
: events are run throughslog.JSONHandler
.
- LOG_LEVEL: This environment variable controls how noisy logs are. The supported values correspond to the
slog.Level
values. - TARGET_REWRITES: This environment variable contains a JSON document instructing the gateway to rewrite the target URL found in an encapsulated request to some specified scheme and host.
The TARGET_REWRITES
configuration option is useful to set up forwarding between the gateway and target when both are on a private network or if they share a loopback interface. For example, suppose that the target is exposed to the internet at https://example.org
, but also reachable by the gateway at http://localhost:8080
(note http
and not https
). It's more efficient to redirect traffic over localhost
than back out over the internet, so you could set TARGET_REWRITES
to:
{
"example.org": { "Scheme": "http", "Host": "localhost:8080" }
}
Then the encapsulated HTTP requests
POST /some-cool-api HTTP/1.1
Host: example.org
some content
or
POST https://example.org/some-cool-api HTTP/1.1
some content
...would both be rewritten to:
POST http://localhost:8080/some-cool-api HTTP/1.1
some content
The gateway can be configured to service Binary HTTP (BHTTP) messages or custom application payloads. To use custom applciation payloads, you must specify the type of application request and response encodings using the CUSTOM_REQUEST_TYPE and CUSTOM_RESPONSE_TYPE environment variables. For example, if you were using protobuf as the application data encoding, you might set CUSTOM_REQUEST_TYPE="message/protohttp request" and CUSTOM_RESPONSE_TYPE="message/protohttp response". See the OHTTP library and OHTTP standard for additional information about choosing custom content types. This example protobuf file contains an example protobuf encoding of HTTP messages as an alternate to BHTTP.
When specifying a custom application format, it is also required to implement a new handler for the format. This can be done by adding a new ContentType
handler that implements the logic for producing an application response for your application request. As an example, if the custom content type corresponded to DNS messages, the handler might resolve the DNS query and produce an encoded DNS response. Alternatively, if using the example protobuf-based HTTP encoding, the ContentType
handler might be implemented as follows:
func protobufHandler(binaryRequest []byte) ([]byte, error) {
request := &Request{}
if err := proto.Unmarshal(binaryRequest, request); err != nil {
return nil, err
}
// Convert the protohttp Request to a http.Request equivalent value
targetRequest, err := protoHTTPToRequest(request)
if err != nil {
return nil, err
}
client := &http.Client{}
targetResponse, err := client.Do(targetRequest)
if err != nil {
return nil, err
}
response, err := responseToProtoHTTP(targetResponse)
if err != nil {
return nil, err
}
return proto.Marshal(response)
}
That's it!
To deploy the server locally, first acquire a TLS certificate using mkcert as follows:
$ mkcert -key-file key.pem -cert-file cert.pem 127.0.0.1 localhost
Then build and run the server as follows:
$ make all
$ CERT=cert.pem KEY=key.pem PORT=4567 ./gateway
This server can also be manually deployed on any bare metal machine, or in cloud providers such as GCP. Instructions for both follow.
Deployment on bare metal servers, such as Equinix, can be done following
the instructions below. These steps assume that git
and go
are both installed on the metal.
- Configure a certificate on the metal using certbot. Once complete, the output should be something like the following, assuming the server domain name is "example.com":
Successfully received certificate.
Certificate is saved at: /etc/letsencrypt/live/example.com/fullchain.pem
Key is saved at: /etc/letsencrypt/live/example.com/privkey.pem
You must configure certbot to renew this certificate periodically. The simplest way to do this is via a cron job:
$ 00 00 1 * 1 certbot renew
- Configure two environment variables to reference these files:
$ export CERT=/etc/letsencrypt/live/example.com/fullchain.pem
$ export KEY=/etc/letsencrypt/live/example.com/privkey.pem
- Clone and build the server:
$ git clone git@github.com:cloudflare/app-relay-gateway-go.git
$ cd app-relay-gateway-go
$ go build ./...
- Run the server:
$ PORT=443 ./gateway &
This will run the server until completion. You must configure the server to restart should it terminate prematurely.
To deploy, run:
$ gcloud app deploy
To check on its status, run:
$ gcloud app browse
To stream logs when deployed, run
$ gcloud app logs tail -s default