Skip to content

Commit 62cb46a

Browse files
fix(restheart-mongo): pin JWT key + correct globalNoise format for deterministic replay
Two record/replay determinism fixes the full keploy/enterprise compat matrix needs to go green: 1) RHO `/jwtConfigProvider/key` pinned to a fixed string. Default is `key: null` which makes RESTHeart auto-generate a random HS256 secret per container start. Recorded JWT bearers carry an HS256 signature over the payload using that secret, so a fresh-container replay phase rejects the recorded bearer with 401 even though --freezeTime keeps `exp` valid. Pinning the secret keeps the bearer signature verifiable across record→replay container restarts. 2) keploy.yml.template rewritten to use NESTED globalNoise format (`body: { field: [] }`) instead of flat dotted keys (`body.field: []`). Keploy's matcher reads `globalNoise.global` as map[section][field]regex and treats dotted keys as literal outer keys, never matching the body section. Verified by walking pkg/matcher/http/match.go and the employee-manager sample's commented example. Fields added: header.{Date, Content-Length, Auth-Token}, body.{_etag, _oid, _id, lastModified, client_ip, latencyMs, access_token}. Validated locally with all three matrix cells (record-stable-replay-pr, record-pr-replay-pr, record-pr-replay-stable) — each reaches 296/296 PASSED with these two changes plus the lane-side --port and --freezeTime flags already in keploy/enterprise#1889.
1 parent 2541e2b commit 62cb46a

2 files changed

Lines changed: 58 additions & 17 deletions

File tree

restheart-mongo/docker-compose.yml

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,19 @@ services:
1515
# RHO is RESTHeart's runtime config-override syntax:
1616
# key->value pairs separated by ';'
1717
# We override the default mongo URL (which the upstream image
18-
# points at host.docker.internal — irrelevant in compose) AND
19-
# explicitly bind /http-listener/host to 0.0.0.0; without that
20-
# second override the upstream image binds to localhost and is
21-
# unreachable from the host port mapping.
22-
RHO: '/mclient/connection-string->"mongodb://${RESTHEART_MONGO_IP:-172.36.0.10}:27017";/http-listener/host->"0.0.0.0";/core/log-level->"INFO"'
18+
# points at host.docker.internal — irrelevant in compose),
19+
# explicitly bind /http-listener/host to 0.0.0.0 (without it
20+
# the upstream image binds localhost and is unreachable from
21+
# the host port mapping), AND pin /jwtConfigProvider/key to
22+
# a fixed secret. The default `key: null` makes RESTHeart
23+
# generate a fresh random HS256 secret on every container
24+
# start. Recorded JWT bearers carry an HS256 signature over
25+
# the payload using that secret, so a fresh-container replay
26+
# phase rejects the recorded bearer with 401 even though
27+
# --freezeTime keeps `exp` valid. Pinning the secret keeps
28+
# the bearer signature verifiable across record→replay
29+
# container restarts (deterministic key, deterministic JWT).
30+
RHO: '/mclient/connection-string->"mongodb://${RESTHEART_MONGO_IP:-172.36.0.10}:27017";/http-listener/host->"0.0.0.0";/core/log-level->"INFO";/jwtConfigProvider/key->"keploy-fixed-jwt-secret-for-deterministic-recordings"'
2331
depends_on:
2432
mongo:
2533
condition: service_healthy
Lines changed: 45 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,54 @@
11
# keploy.yml template for the restheart-mongo sample.
22
#
3-
# globalNoise covers fields whose value is non-deterministic
4-
# across record/replay:
3+
# globalNoise.global is consumed at replay-time as a `body` /
4+
# `header` map of field-name → regex array. Keploy's matcher
5+
# expects a NESTED structure (top-level keys are the response
6+
# section "body" / "header"; inner keys are the field names).
7+
# Flat dotted-keys like `body.field: []` are not unflattened by
8+
# the parser — they end up as outer keys "body.field" and never
9+
# match the body section, so the noise is silently ignored.
510
#
6-
# header.Date runtime-stamped
7-
# body._etag RESTHeart auto-stamped on each
8-
# document; changes per write
9-
# body._oid / body._id server-generated ObjectIds
10-
# (when not set by client)
11-
# body.lastModified auto-now timestamp
11+
# Fields covered here:
12+
#
13+
# header.Date / header.Content-Length
14+
# Runtime-stamped; Content-Length is downstream-of body.client_ip
15+
# on /ping (length changes when the reflected client_ip differs
16+
# in byte width).
17+
# body._etag / body._oid / body._id
18+
# RESTHeart auto-stamped on each document; ObjectIds and ETag
19+
# change per write.
20+
# body.lastModified
21+
# Auto-now timestamp.
22+
# body.client_ip
23+
# RESTHeart's /ping echoes the requesting client IP. Differs
24+
# between host-driven record (loopback) and docker-bridge
25+
# replay (gateway). Time-freeze can't help; this is
26+
# network-derived.
27+
# body.latencyMs
28+
# /health/db reports a freshly measured DB ping duration; varies
29+
# per request even with time freeze (the duration is computed
30+
# end-start across two clock reads).
31+
# header.Auth-Token / body.access_token
32+
# RESTHeart's /token endpoint mints a JWT with `exp` and `jti`
33+
# (random UUID) on every call. Time-freeze pins exp, but jti is
34+
# a SecureRandom UUID and the HS256 signature is a function of
35+
# the full payload, so tokens never match byte-for-byte across
36+
# record/replay.
1237
#
1338
# Centralised here so a future RESTHeart version that adds another
1439
# auto-stamped field is one edit, not a fan-out across lane scripts.
1540
test:
1641
globalNoise:
1742
global:
18-
header.Date: []
19-
body._etag: []
20-
body._oid: []
21-
body.lastModified: []
43+
header:
44+
Date: []
45+
Content-Length: []
46+
Auth-Token: []
47+
body:
48+
_etag: []
49+
_oid: []
50+
_id: []
51+
lastModified: []
52+
client_ip: []
53+
latencyMs: []
54+
access_token: []

0 commit comments

Comments
 (0)