* feat(network): enable Docker transparent TCP egress Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com> * test(e2e): cover Docker transparent TCP egress Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com> * feat(network): correlate transparent TCP audit events Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com> * docs(examples): add transparent TCP Redis demo Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com> * docs(examples): demonstrate blocked TCP connections Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com> * docs(examples): focus Redis demo audit output Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com> * fix(network): close transparent TCP policy bypasses Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com> * fix(sandbox): reject unsupported TCP policy reloads Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com> * fix(ci): satisfy Linux transparent TCP lints Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com> * feat(podman): enable transparent TCP egress Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com> * fix(podman): permit policy DNS port binding Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com> * test(e2e): use qualified transparent TCP hostname Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com> * fix(podman): preserve exact policy DNS names Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com> * fix(podman): route policy DNS over TCP Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com> * fix(dns): serve multiple TCP queries per connection Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com> * docs(network): explain native DNS and TCP egress Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com> * fix(sandbox): reconcile runtime reload with upstream Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com> * fix(network): harden transparent DNS capture Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com> * fix(podman): preserve resolver behavior for native tcp Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com> * docs(network): clarify native tcp runtime constraints Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com> * fix(network): remove unused transparent tcp pin Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com> * fix(network): admit redirected transparent tcp Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com> * fix(network): restore podman transparent networking Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com> * test(podman): permit alpine busybox binaries Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com> * test(podman): use portable alpine keepalive Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com> * test(podman): build musl networking fixture Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com> * test(podman): isolate musl DNS probe Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com> * fix(podman): keep privileged port capability dropped Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com> * fix(network): preserve transparent TCP port 53 Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com> * fix(network): report synthetic pool pressure by family Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com> * test(podman): bind tcp fixtures before readiness Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com> * test(podman): grant fixture low-port bind Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com> --------- Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com>
3.5 KiB
Transparent TCP Redis
This example connects a Docker-backed OpenShell sandbox to Redis with a native TCP client. It does not use the HTTP forward proxy.
The demo:
- Starts Redis on the OpenShell-managed Docker network.
- Creates a sandbox with an endpoint that explicitly uses
protocol: tcp. - Resolves the policy hostname to an ephemeral synthetic address.
- Opens a native TCP socket and runs Redis
PING,SET,GET, andDELcommands. - Confirms that policy blocks an unapproved hostname, the approved hostname on the wrong port, and a direct connection to Redis's real IP.
- Prints the sandbox log stream, including OCSF DNS and TCP decisions.
- Deletes the sandbox and Redis container, including after a failure.
OpenShell authorizes the hostname and port before policy DNS publishes the synthetic address. When the client connects, OpenShell maps that address back to the approved endpoint, rechecks the process and policy, and dials a pinned real Redis address. A direct connection to the Redis container IP remains blocked.
Prerequisites
- A Docker-backed OpenShell gateway built from the transparent TCP branch
- The
openshellanddockercommands - Access to pull
redis:7-alpine
The Docker compute driver creates and uses the openshell-docker bridge by
default. The gateway process itself runs on the host; only sandbox supervisors
and the Redis service join this bridge. If the driver uses another network, set
OPENSHELL_DOCKER_NETWORK to that network's name.
Run the example
From the repository root:
examples/transparent-tcp-redis/demo.sh
Expected client output includes:
policy DNS: redis.openshell.demo -> 198.18.x.x
PING -> 'PONG'
SET -> 'OK'
GET -> 'hello-from-openshell'
DEL -> 1
BLOCKED (unapproved hostname): openshell-transparent-tcp-redis-demo:6379 -> gaierror
BLOCKED (wrong port): redis.openshell.demo:6380 -> RuntimeError
BLOCKED (direct real-IP dial): 172.x.x.x:6379 -> ConnectionRefusedError
transparent TCP Redis demo passed
The exact exception names vary by operating system and network timing. The demo fails if any negative check receives a Redis response.
Before cleanup, the demo fetches recent sandbox logs and prints only the OCSF events relevant to this flow: policy DNS mappings and denials, transparent TCP allows and wrong-port denials, and direct-bypass findings when the runtime can observe them.
The synthetic address changes across supervisor allocation epochs. It is not the Redis container's address and applications must not persist it.
The demo currently requires the Docker compute driver. Other compute drivers
fail closed when a policy requests protocol: tcp until they implement the
required namespace-local DNS and TCP capture contract.
Create the sandbox with at least one explicit TCP endpoint. Adding the first
protocol: tcp endpoint to a running sandbox that started without one is
rejected atomically, with the previous policy left active; recreate the sandbox
to install the DNS and transparent TCP substrate before the workload starts.
The example policy allows any sandbox binary to use this one Redis endpoint so
the demo works across base images with different Python installation paths. In
a production policy, replace /** with the exact path of the client binary.
Configuration
Override resource names or the image without editing the files:
SANDBOX_NAME=tcp-redis-demo \
REDIS_CONTAINER=my-openshell-redis \
REDIS_IMAGE=redis:7-alpine \
OPENSHELL_DOCKER_NETWORK=openshell-docker \
examples/transparent-tcp-redis/demo.sh