docker-bastion/examples/osm-taxiways
Fabian @ Blax Software ec1ef50937 feat(examples): OSM taxiway bastion (broker mode)
A whitelisted SSH box for an OSM airport-taxiway export pipeline. A backend may run
only three commands — taxiways-list / taxiways-export <ICAO> / taxiways-cat <ICAO> —
validated whole-line against an allowlist and exec'd shell-free by the vendored
bastion-broker.

Ubuntu 22.04 standalone (not FROM blaxsoftware/bastion): the export needs osmium, which
Alpine doesn't package, and newer GDAL (Debian trixie 3.10) segfaults on the OSM->GeoJSON
step — 22.04's GDAL 3.4.1 + osmium 1.14 is the known-good toolchain. Reuses the base repo's
bastion-broker unchanged; a compact broker-only sshd entrypoint replaces the Alpine
start-container (account unlocked via `usermod -p '*'` so glibc sshd accepts pubkey).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 14:00:33 +02:00
..
bin feat(examples): OSM taxiway bastion (broker mode) 2026-07-01 14:00:33 +02:00
Dockerfile feat(examples): OSM taxiway bastion (broker mode) 2026-07-01 14:00:33 +02:00
README.md feat(examples): OSM taxiway bastion (broker mode) 2026-07-01 14:00:33 +02:00
allowed-commands.list feat(examples): OSM taxiway bastion (broker mode) 2026-07-01 14:00:33 +02:00
bastion-broker feat(examples): OSM taxiway bastion (broker mode) 2026-07-01 14:00:33 +02:00
docker-compose.yml feat(examples): OSM taxiway bastion (broker mode) 2026-07-01 14:00:33 +02:00
entrypoint feat(examples): OSM taxiway bastion (broker mode) 2026-07-01 14:00:33 +02:00

README.md

OSM taxiway bastion (broker mode)

A self-contained, whitelisted SSH box for an OSM taxiway export pipeline. It lets a backend fetch/generate airport ground-layout GeoJSON without handing it a shell or root: the bastion only ever runs three commands, validated whole-line against a regex allowlist and exec'd shell-free (no sh -c, so nothing an ICAO argument contains can become a shell operator or a path).

ssh agent@<osm-host>
  └─ bastion-broker           (key auth; matches request vs allowed-commands.list)
       ├─ taxiways-list            → ls *-taxiways.geojson in /osm-maps
       ├─ taxiways-export <ICAO>   → export-icao-geojson.sh <ICAO>   (osmium/ogr2ogr/jq)
       └─ taxiways-cat <ICAO>      → base64 <ICAO>-taxiways.geojson   (ssh-safe tunnel)

The OSM toolchain (osmium, ogr2ogr, jq) is baked into the image (see Dockerfile), so the export runs inside this container against the mounted osm-maps directory (which holds a region PBF + export-icao-geojson.sh). No docker socket, no second container — the only host data reachable is that one directory.

Files

file purpose
Dockerfile Debian base (Alpine has no osmium-tool) + osmium-tool/gdal-bin/jq + openssh + the wrappers
bastion-broker the allowlist gate, verbatim from docker-bastion/scripts (whole-line ERE match → shell-free exec)
entrypoint broker-only sshd: key-only, single non-root user, every session forced through the broker
bin/taxiways-{list,export,cat} the only executables the allowlist lets a client run
allowed-commands.list the regex allowlist (taxiways-export [A-Z]{4}, …) — the security boundary
docker-compose.yml broker-mode service, osm-maps + allowlist + keys mounts, port 6770

This flavor is Debian standalone (not FROM blaxsoftware/bastion) because the export needs osmium, which Alpine doesn't package. It reuses the base repo's bastion-broker unchanged; only the sshd bootstrap is a compact broker-only entrypoint instead of the Alpine start-container.

Deploy (on the host holding your osm-maps dir)

# 1) Authorize the backend's agent key
mkdir -p docker-data/bastion/users.d
cp /path/to/agent.pub docker-data/bastion/users.d/backend.pub

# 2) Let the bastion's `agent` user write into osm-maps (export writes files + work/)
AGENT_UID=$(docker run --rm blaxsoftware/bastion:latest id -u agent)
chown -R "$AGENT_UID" /srv/osm-maps          # point at your actual osm-maps dir

# 3) Confirm the osm-maps dir has the script + PBF
ls /srv/osm-maps/export-icao-geojson.sh /srv/osm-maps/*.osm.pbf

# 4) Build + run
docker compose up -d --build

To publish the image for reuse:

docker build --platform linux/amd64 -t blaxsoftware/bastion-osm-taxiways:latest .
docker push blaxsoftware/bastion-osm-taxiways:latest

Test

ssh -p 6770 agent@<osm-host> taxiways-list                 # → list (maybe empty)
ssh -p 6770 agent@<osm-host> taxiways-export EDDF          # → runs the export (minutes)
ssh -p 6770 agent@<osm-host> taxiways-cat EDDF | base64 -d | head   # → GeoJSON
ssh -p 6770 agent@<osm-host> 'rm -rf /'                    # → "command not permitted", exit 126
ssh -p 6770 agent@<osm-host> taxiways-export eddf          # → refused (lowercase ≠ [A-Z]{4})

Backend wiring

The backend stores the private key whose *.pub you dropped in users.d/, and connects as agent@<osm-host>:6770, sending one of the three commands per connection. taxiways-cat returns base64 that the backend decodes into its own storage; taxiways-export runs the (minutes-long) OSM export. A pure-PHP SSH client (e.g. phpseclib) works fine — no ssh binary is required on the backend side.