Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 21 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ corner - and the only trace is a line buried in a log you would have to SSH in t
Start ros2_medkit next to it (no changes to Nav2). The aborted goal becomes a fault:

```bash
curl http://localhost:8080/api/v1/apps/bt_navigator/faults
curl -H "Authorization: Bearer $TOKEN" http://localhost:8080/api/v1/apps/bt_navigator/faults
# → ACTION_NAVIGATE_TO_POSE_ABORTED severity=ERROR source=/bt_navigator status=CONFIRMED
# + a black-box rosbag of the seconds around the failure
```
Expand All @@ -51,7 +51,7 @@ trajectory. Same story: the same action bridge surfaces the aborted move as a fa
`move_group` entity, with the freeze-frame of what the arm was doing, without touching MoveIt.

```bash
curl http://localhost:8080/api/v1/apps/move_group/faults
curl -H "Authorization: Bearer $TOKEN" http://localhost:8080/api/v1/apps/move_group/faults
# → the aborted MoveGroup goal, as a structured fault with its snapshot
```

Expand Down Expand Up @@ -87,6 +87,8 @@ docker run --rm --network host --ipc host \
ghcr.io/selfpatch/ros2_medkit-jazzy:latest \
ros2 launch ros2_medkit_gateway bringup.launch.py
# → REST API live at http://localhost:8080/api/v1/
# The container prints a one-time client_secret on startup; use it to get a
# token. Pass -e MEDKIT_AUTH_DISABLED=1 to run without authentication.
```

Swap `jazzy` for `humble`/`lyrical`; the two `-e` flags forward your shell's `ROS_DOMAIN_ID` and
Expand All @@ -96,7 +98,23 @@ picks them up it is a plain apt install too:

```bash
sudo apt install ros-jazzy-ros2-medkit-gateway # or ros-humble- / ros-lyrical-
ros2 launch ros2_medkit_gateway bringup.launch.py

# The gateway ships closed: it requires a credential and refuses to start
# without a signing secret. Supply one, and either a certificate or
# tls_enabled:=false on a host nothing else can reach.
ros2 launch ros2_medkit_gateway bringup.launch.py \
tls_enabled:=false \
jwt_secret:=change-me-to-at-least-32-characters-long \
auth_clients:=demo:demo-secret:admin
```

Every `curl` below then needs a token:

```bash
TOKEN=$(curl -s http://localhost:8080/api/v1/auth/authorize \
-H 'Content-Type: application/json' \
-d '{"grant_type":"client_credentials","client_id":"demo","client_secret":"demo-secret"}' \
| jq -r .access_token)
```

> [!TIP]
Expand Down
49 changes: 48 additions & 1 deletion docker/entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,60 @@ source "${COLCON_WS}/install/setup.bash"
# Default to FastDDS (can be overridden via RMW_IMPLEMENTATION env var)
export RMW_IMPLEMENTATION="${RMW_IMPLEMENTATION:-rmw_fastrtps_cpp}"

# Bootstrap credentials so the image can ship closed AND still start.
#
# The packaged params file turns authentication on, matching the gateway's own
# default, and the gateway refuses to start without a signing secret. An image
# that therefore needed a secret before `docker run` would do anything is a
# quickstart nobody completes, and the usual reaction is to turn auth off and
# leave it off. So: if the operator supplied nothing, generate a secret and an
# admin client for this container and print the credential once.
#
# Supply MEDKIT_JWT_SECRET and MEDKIT_CLIENTS to pin your own, or
# MEDKIT_AUTH_DISABLED=1 to run open on a host nothing else can reach.
AUTH_ARGS=()
if [ "${MEDKIT_AUTH_DISABLED:-0}" = "1" ]; then
AUTH_ARGS+=(-p auth.enabled:=false)
echo "ros2_medkit: MEDKIT_AUTH_DISABLED=1 - starting WITHOUT authentication." >&2
echo " Every route is readable by anyone who can reach this port." >&2
else
if [ -z "${MEDKIT_JWT_SECRET:-}" ]; then
# Per container, and not persisted: a restart issues a new one, which is
# correct for a credential nobody chose and nobody stored.
MEDKIT_JWT_SECRET="$(head -c 32 /dev/urandom | base64 | tr -d '\n' | tr '+/' '-_' | tr -d '=')"
MEDKIT_CLIENT_SECRET="$(head -c 24 /dev/urandom | base64 | tr -d '\n' | tr '+/' '-_' | tr -d '=')"
MEDKIT_CLIENTS="${MEDKIT_CLIENTS:-medkit:${MEDKIT_CLIENT_SECRET}:admin}"
echo "=============================================================" >&2
echo "ros2_medkit: generated a one-time admin credential for this" >&2
echo " container. It changes on every restart." >&2
echo "" >&2
echo " client_id: medkit" >&2
echo " client_secret: ${MEDKIT_CLIENT_SECRET}" >&2
echo "" >&2
echo " curl -s http://localhost:8080/api/v1/auth/authorize \\" >&2
echo " -H 'Content-Type: application/json' \\" >&2
echo " -d '{\"grant_type\":\"client_credentials\",\"client_id\":\"medkit\",\"client_secret\":\"${MEDKIT_CLIENT_SECRET}\"}'" >&2
echo "" >&2
echo " Set MEDKIT_JWT_SECRET and MEDKIT_CLIENTS to pin your own." >&2
echo "=============================================================" >&2
fi
AUTH_ARGS+=(-p "auth.jwt_secret:=${MEDKIT_JWT_SECRET}")
[ -n "${MEDKIT_CLIENTS:-}" ] && AUTH_ARGS+=(-p "auth.clients:=[${MEDKIT_CLIENTS}]")
fi
# Exported so the other dispatch branch works too: `docker run <img> ros2 launch
# ... bringup.launch.py` execs a command instead of the node, so it never sees
# AUTH_ARGS. gateway.launch.py falls back to these variables.
export MEDKIT_JWT_SECRET MEDKIT_CLIENTS MEDKIT_AUTH_DISABLED
# The image serves plain HTTP; see gateway_docker_params.yaml for why.
export MEDKIT_TLS_DISABLED=1

# Dispatch on the first argument:
# - empty, or starts with "-" (the default CMD "--ros-args --params-file ..."
# or an override like --ros-args -p server.port:=9090): run the gateway node
# directly, so `docker run <img>` and arg-only overrides keep working.
# - a full command (e.g. `ros2 launch ros2_medkit_gateway bringup.launch.py`
# or `bash`): exec it as-is, so the image can launch the whole bringup stack.
if [ -z "$1" ] || [ "${1#-}" != "$1" ]; then
exec ros2 run ros2_medkit_gateway gateway_node "$@"
exec ros2 run ros2_medkit_gateway gateway_node "$@" "${AUTH_ARGS[@]}"
fi
exec "$@"
21 changes: 21 additions & 0 deletions docker/gateway_docker_params.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,13 @@ ros2_medkit_gateway:
server:
host: "0.0.0.0"
port: 8080
# TLS stays OFF in the image, unlike config/gateway_params.yaml. A
# container has no certificate of its own and is almost always fronted by
# something that terminates TLS; requiring one here would mean the image
# could not start at all. Terminate TLS at your ingress, or mount a
# certificate and set server.tls.* yourself.
tls:
enabled: false
refresh_interval_ms: 2000
# The web UI runs as a separate origin (its own host/port), so the
# documented "run the web UI next to the gateway" path needs CORS. Without
Expand All @@ -17,3 +24,17 @@ ros2_medkit_gateway:
allowed_origins:
- "http://localhost:3000"
- "http://localhost:5173"

# Authentication. On, matching config/gateway_params.yaml, so the published
# image has the same posture as the source default rather than a quietly
# weaker one.
#
# jwt_secret and clients are NOT set here on purpose: a secret baked into a
# published image is a secret every user of that image shares. The
# entrypoint generates one per container and prints the credential, or
# takes MEDKIT_JWT_SECRET / MEDKIT_CLIENTS from the environment. Run with
# MEDKIT_AUTH_DISABLED=1 to serve without authentication.
auth:
enabled: true
require_auth_for: "all"
issuer: "ros2_medkit_gateway"
10 changes: 5 additions & 5 deletions docs/api/rest.rst
Original file line number Diff line number Diff line change
Expand Up @@ -896,7 +896,7 @@ Read and publish data from ROS 2 topics. Item ids follow

.. code-block:: bash

curl http://localhost:8080/api/v1/components/temp_sensor/data/powertrain%2Fengine%2Ftemperature
curl -H "Authorization: Bearer $TOKEN" http://localhost:8080/api/v1/components/temp_sensor/data/powertrain%2Fengine%2Ftemperature

``PUT /api/v1/components/{id}/data/{topic_path}``
Publish to a topic.
Expand Down Expand Up @@ -1630,7 +1630,7 @@ List available bulk-data categories for an entity. Returns the union of rosbag c

.. code-block:: bash

curl http://localhost:8080/api/v1/apps/motor_controller/bulk-data
curl -H "Authorization: Bearer $TOKEN" http://localhost:8080/api/v1/apps/motor_controller/bulk-data

**Response (200 OK):**

Expand All @@ -1651,7 +1651,7 @@ List all bulk-data items in a category for the entity.

.. code-block:: bash

curl http://localhost:8080/api/v1/apps/motor_controller/bulk-data/rosbags
curl -H "Authorization: Bearer $TOKEN" http://localhost:8080/api/v1/apps/motor_controller/bulk-data/rosbags

**Response (200 OK):**

Expand Down Expand Up @@ -2910,7 +2910,7 @@ topic (push-based).

.. code-block:: bash

curl http://localhost:8080/api/v1/apps/engine_temp_sensor/x-medkit-topic-beacon
curl -H "Authorization: Bearer $TOKEN" http://localhost:8080/api/v1/apps/engine_temp_sensor/x-medkit-topic-beacon

**Response (200 OK):**

Expand Down Expand Up @@ -2965,7 +2965,7 @@ polling ROS 2 node parameters matching a configured prefix (pull-based).

.. code-block:: bash

curl http://localhost:8080/api/v1/apps/engine_temp_sensor/x-medkit-param-beacon
curl -H "Authorization: Bearer $TOKEN" http://localhost:8080/api/v1/apps/engine_temp_sensor/x-medkit-param-beacon

**Response (200 OK):**

Expand Down
2 changes: 1 addition & 1 deletion docs/config/discovery-options.rst
Original file line number Diff line number Diff line change
Expand Up @@ -472,7 +472,7 @@ The ``x-medkit-topic-beacon`` vendor endpoint exposes current beacon state:

.. code-block:: bash

curl http://localhost:8080/api/v1/apps/engine_temp_sensor/x-medkit-topic-beacon
curl -H "Authorization: Bearer $TOKEN" http://localhost:8080/api/v1/apps/engine_temp_sensor/x-medkit-topic-beacon

**Example Response:**

Expand Down
6 changes: 3 additions & 3 deletions docs/config/server.rst
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ TLS/HTTPS Configuration
- Description
* - ``server.tls.enabled``
- bool
- ``false``
- ``true``
- Enable HTTPS using OpenSSL.
* - ``server.tls.cert_file``
- string
Expand Down Expand Up @@ -857,7 +857,7 @@ default for local development.
- Description
* - ``auth.enabled``
- bool
- ``false``
- ``true``
- Enable/disable JWT authentication.
* - ``auth.jwt_secret``
- string
Expand All @@ -881,7 +881,7 @@ default for local development.
- Refresh token validity period in seconds (24 hours). Must be >= ``token_expiry_seconds``.
* - ``auth.require_auth_for``
- string
- ``"write"``
- ``"all"``
- When to require authentication: ``"none"`` (auth endpoints still available), ``"write"`` (POST/PUT/DELETE only), or ``"all"`` (every request).
* - ``auth.issuer``
- string
Expand Down
64 changes: 51 additions & 13 deletions docs/getting_started.rst
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,10 @@ Open three terminals. In each, source your workspace:

.. code-block:: bash

ros2 launch ros2_medkit_gateway gateway.launch.py
ros2 launch ros2_medkit_gateway gateway.launch.py \
tls_enabled:=false \
jwt_secret:=change-me-to-at-least-32-characters-long \
auth_clients:=demo:demo-secret:admin
Comment on lines +43 to +46

You should see:

Expand Down Expand Up @@ -122,6 +125,41 @@ Required if you want to test the Faults API.
The ``~/.ros2_medkit/`` directory must exist before starting the fault manager.
SQLite will create the database file automatically.

.. important::

The gateway ships **closed**: ``auth.enabled`` is true and
``require_auth_for`` is ``all``, so every route below needs a credential and
the gateway will not start without a signing secret. That is deliberate - a
gateway that booted open is one nobody notices. The two arguments above are
a throwaway development credential; a real deployment injects them from its
own secret store.

Get a token once and reuse it for every command on this page:

.. code-block:: bash

TOKEN=$(curl -s http://localhost:8080/api/v1/auth/authorize \
-H 'Content-Type: application/json' \
-d '{"grant_type":"client_credentials","client_id":"demo","client_secret":"demo-secret"}' \
| jq -r .access_token)

``tls_enabled:=false`` is what keeps the rest of this page on ``http://``.
TLS is on in the shipped config and the gateway will not start without a
certificate, so a first run either turns it off, as here, or supplies one:
run ``scripts/generate_dev_certs.sh ./certs`` and pass
``cert_file:=./certs/cert.pem key_file:=./certs/key.pem``. Turn it off only
on a host nothing else can reach. See :doc:`tutorials/https` for a real
certificate.

``POST /api/v1/auth/authorize`` takes the ``client_credentials`` grant;
``/auth/token`` is the refresh endpoint and takes ``refresh_token``.

``GET /api/v1/health`` is the other route that stays open, so a container
supervisor with no credential can still tell the process is alive.

To run without authentication - only on a host nothing else can reach -
pass ``auth_enabled:=false``.

.. admonition:: ✅ Checkpoint
:class: tip

Expand All @@ -146,7 +184,7 @@ The gateway exposes all endpoints under ``/api/v1``. Let's explore!

.. code-block:: bash

curl http://localhost:8080/api/v1/
curl -H "Authorization: Bearer $TOKEN" http://localhost:8080/api/v1/

Response shows available endpoints and version info.

Expand Down Expand Up @@ -197,7 +235,7 @@ ros2_medkit organizes ROS 2 nodes into a SOVD-aligned entity hierarchy:

.. code-block:: bash

curl http://localhost:8080/api/v1/functions
curl -H "Authorization: Bearer $TOKEN" http://localhost:8080/api/v1/functions

With ``demo_nodes.launch.py``, you'll see Functions like ``powertrain``, ``chassis``, and ``body``
(created from the first namespace segment).
Expand All @@ -206,15 +244,15 @@ With ``demo_nodes.launch.py``, you'll see Functions like ``powertrain``, ``chass

.. code-block:: bash

curl http://localhost:8080/api/v1/components
curl -H "Authorization: Bearer $TOKEN" http://localhost:8080/api/v1/components

In runtime mode, you'll see a single host-level Component.

**List all areas:**

.. code-block:: bash

curl http://localhost:8080/api/v1/areas
curl -H "Authorization: Bearer $TOKEN" http://localhost:8080/api/v1/areas

In runtime mode, this returns an empty list. Areas require a manifest definition
(see :doc:`tutorials/manifest-discovery`).
Expand All @@ -228,7 +266,7 @@ The data endpoints let you read topic data from apps.

.. code-block:: bash

curl http://localhost:8080/api/v1/apps/temp_sensor/data
curl -H "Authorization: Bearer $TOKEN" http://localhost:8080/api/v1/apps/temp_sensor/data

Response structure (showing one topic):

Expand Down Expand Up @@ -282,7 +320,7 @@ Each data item includes:

.. code-block:: bash

curl http://localhost:8080/api/v1/apps/temp_sensor/data/powertrain%2Fengine%2Ftemperature
curl -H "Authorization: Bearer $TOKEN" http://localhost:8080/api/v1/apps/temp_sensor/data/powertrain%2Fengine%2Ftemperature

Response with live data:

Expand Down Expand Up @@ -339,7 +377,7 @@ The operations endpoints let you call ROS 2 services and actions.

.. code-block:: bash

curl http://localhost:8080/api/v1/apps/calibration/operations
curl -H "Authorization: Bearer $TOKEN" http://localhost:8080/api/v1/apps/calibration/operations

**Call a service (synchronous execution):**

Expand Down Expand Up @@ -387,7 +425,7 @@ Response (202 Accepted):

.. code-block:: bash

curl http://localhost:8080/api/v1/apps/long_calibration/operations/long_calibration/executions/a1b2c3d4-e5f6-7890-abcd-ef1234567890
curl -H "Authorization: Bearer $TOKEN" http://localhost:8080/api/v1/apps/long_calibration/operations/long_calibration/executions/a1b2c3d4-e5f6-7890-abcd-ef1234567890

**Cancel a running action:**

Expand All @@ -406,13 +444,13 @@ The configurations endpoints expose ROS 2 parameters.

.. code-block:: bash

curl http://localhost:8080/api/v1/apps/temp_sensor/configurations
curl -H "Authorization: Bearer $TOKEN" http://localhost:8080/api/v1/apps/temp_sensor/configurations

**Get a specific parameter:**

.. code-block:: bash

curl http://localhost:8080/api/v1/apps/temp_sensor/configurations/publish_rate
curl -H "Authorization: Bearer $TOKEN" http://localhost:8080/api/v1/apps/temp_sensor/configurations/publish_rate

**Set a parameter value:**

Expand All @@ -439,13 +477,13 @@ Step 7: Monitor Faults

.. code-block:: bash

curl http://localhost:8080/api/v1/faults
curl -H "Authorization: Bearer $TOKEN" http://localhost:8080/api/v1/faults

**List faults for a specific component:**

.. code-block:: bash

curl http://localhost:8080/api/v1/apps/lidar_sensor/faults
curl -H "Authorization: Bearer $TOKEN" http://localhost:8080/api/v1/apps/lidar_sensor/faults

**Clear a fault:**

Expand Down
Loading
Loading