redis tls
This commit is contained in:
@@ -17,7 +17,9 @@
|
|||||||
"port": 6379,
|
"port": 6379,
|
||||||
"password": null,
|
"password": null,
|
||||||
"database": 0,
|
"database": 0,
|
||||||
"key_prefix": "network:"
|
"key_prefix": "network:",
|
||||||
|
"tls": false,
|
||||||
|
"verify_peer": true
|
||||||
},
|
},
|
||||||
"network": {
|
"network": {
|
||||||
"auth_token": null
|
"auth_token": null
|
||||||
@@ -52,6 +54,8 @@
|
|||||||
| `password` | `null` | Redis AUTH password. Prefer the env var (below). |
|
| `password` | `null` | Redis AUTH password. Prefer the env var (below). |
|
||||||
| `database` | `0` | Redis logical database (0-15 on stock Redis) |
|
| `database` | `0` | Redis logical database (0-15 on stock Redis) |
|
||||||
| `key_prefix` | `"network:"` | All keys NetworkCore writes start with this prefix. Server registry keys end up at `network:servers:<id>`. Change this only if you're sharing a Redis with something unrelated. |
|
| `key_prefix` | `"network:"` | All keys NetworkCore writes start with this prefix. Server registry keys end up at `network:servers:<id>`. Change this only if you're sharing a Redis with something unrelated. |
|
||||||
|
| `tls` | `false` | Enables TLS for the Redis connection (Lettuce `rediss://` equivalent). **Set to true whenever Redis is on a different host than the Hytale server**, since the connection crosses untrusted network. |
|
||||||
|
| `verify_peer` | `true` | When `tls` is on, verifies the Redis server's certificate against the JVM's default trust store. Set to false only in dev / against self-signed certs. Logs a warning when disabled. |
|
||||||
|
|
||||||
### network
|
### network
|
||||||
|
|
||||||
@@ -92,5 +96,6 @@ These take precedence over the config file at boot time. Use them for secrets so
|
|||||||
- `server.id` left null is fine if you're OK with the id changing on every boot. Set it to something stable like `lobby-prod-01` if you want logs and dashboards to keep continuity across restarts.
|
- `server.id` left null is fine if you're OK with the id changing on every boot. Set it to something stable like `lobby-prod-01` if you want logs and dashboards to keep continuity across restarts.
|
||||||
- `server.role` set explicitly per server. This is what dashboards and routing use.
|
- `server.role` set explicitly per server. This is what dashboards and routing use.
|
||||||
- `redis.host` pointed at your shared Redis. `redis.password` not in the file; set `REDIS_PASSWORD` env var.
|
- `redis.host` pointed at your shared Redis. `redis.password` not in the file; set `REDIS_PASSWORD` env var.
|
||||||
|
- `redis.tls` **on** whenever Redis is on a different host than the Hytale server. `verify_peer` stays true unless Redis uses a self-signed cert you haven't loaded into the JVM trust store.
|
||||||
- `network.auth_token` not in the file; set `NETWORK_AUTH_TOKEN` env var to the same value on every server.
|
- `network.auth_token` not in the file; set `NETWORK_AUTH_TOKEN` env var to the same value on every server.
|
||||||
- `metrics.bind` stays `127.0.0.1`. Run Prometheus on the same host (or use an SSH tunnel / reverse proxy with auth to expose it elsewhere).
|
- `metrics.bind` stays `127.0.0.1`. Run Prometheus on the same host (or use an SSH tunnel / reverse proxy with auth to expose it elsewhere).
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
## Threat model in one paragraph
|
## Threat model in one paragraph
|
||||||
|
|
||||||
NetworkCore assumes Redis is on a private network reachable only by trusted servers. The shared auth token guards against accidents (a misconfigured staging server talking to prod) and casual mischief (someone with bus access spoofing a server id). It does *not* protect against an attacker who already has Redis credentials - at that point they can read the token off any envelope and start signing their own. For hard isolation, rely on network controls: VPC peering, firewall rules, Redis bound to a private interface.
|
NetworkCore expects Redis to be reachable only by trusted servers. If Redis sits on a different host than the Hytale servers (the common case with hosted game servers), enable `redis.tls` so the connection is encrypted in transit. The shared auth token guards against accidents (a misconfigured staging server talking to prod) and casual mischief (someone with bus access spoofing a server id). It does *not* protect against an attacker who already has both Redis credentials and the token - at that point they can craft any envelope they want. The defenses in depth: TLS for the wire, the auth token for accidents, and network controls (firewall rules, private Redis interfaces, hosting-provider ACLs) for the actual attacker scenario.
|
||||||
|
|
||||||
## Auth token rollout
|
## Auth token rollout
|
||||||
|
|
||||||
@@ -16,10 +16,10 @@ When rotating: do a rolling restart. Mid-rotation, servers running the old token
|
|||||||
|
|
||||||
If you want zero rejection windows, you'd need a multi-token scheme where receivers accept either the old or new token during transition. That's not in v1.
|
If you want zero rejection windows, you'd need a multi-token scheme where receivers accept either the old or new token during transition. That's not in v1.
|
||||||
|
|
||||||
## What is *not* encrypted or signed
|
## What is and isn't encrypted or signed
|
||||||
|
|
||||||
- **Redis traffic.** Plain TCP. Anyone sniffing the wire between Hytale servers and Redis sees every payload and every token. Mitigations: keep Redis on a private network, run it on the same host as the Hytale server, or use SSH tunnels / a VPC. TLS (rediss://) is a future addition.
|
- **Redis traffic.** Optional TLS (`redis.tls` in config). Turn it on whenever Redis runs on a different host than the Hytale server. Without TLS, anyone sniffing the network between the Hytale server and Redis sees every payload and the auth token. With TLS, the connection is encrypted end to end. Use `verify_peer: true` against any production Redis with a real cert.
|
||||||
- **Messages themselves.** No HMAC, no signing. A token in the envelope is checked for equality, not for authenticity. An attacker on the bus can craft any envelope.
|
- **Messages themselves.** No HMAC, no signing. The auth token in the envelope is checked for equality, not authenticity. An attacker with Redis credentials can craft any envelope and stamp it with the token (if they have it). TLS prevents the token from leaking on the wire, but doesn't prevent forgery by anyone who legitimately has Redis access.
|
||||||
- **The metrics endpoint.** No auth at all. Defaults to `127.0.0.1` so only the local machine can hit it. If you bind to a non-loopback address, put a reverse proxy with auth in front.
|
- **The metrics endpoint.** No auth at all. Defaults to `127.0.0.1` so only the local machine can hit it. If you bind to a non-loopback address, put a reverse proxy with auth in front.
|
||||||
|
|
||||||
## The metrics endpoint
|
## The metrics endpoint
|
||||||
|
|||||||
@@ -61,6 +61,13 @@ public final class RedisMessageBus implements MessageBus {
|
|||||||
if (redis.password != null && !redis.password.isEmpty()) {
|
if (redis.password != null && !redis.password.isEmpty()) {
|
||||||
b.withPassword(redis.password.toCharArray());
|
b.withPassword(redis.password.toCharArray());
|
||||||
}
|
}
|
||||||
|
if (redis.tls) {
|
||||||
|
b.withSsl(true);
|
||||||
|
b.withVerifyPeer(redis.verify_peer);
|
||||||
|
if (!redis.verify_peer) {
|
||||||
|
LOGGER.at(Level.WARNING).log("Redis TLS is on but peer verification is OFF. Only acceptable in dev.");
|
||||||
|
}
|
||||||
|
}
|
||||||
RedisURI uri = b.build();
|
RedisURI uri = b.build();
|
||||||
|
|
||||||
this.client = RedisClient.create(uri);
|
this.client = RedisClient.create(uri);
|
||||||
@@ -91,8 +98,10 @@ public final class RedisMessageBus implements MessageBus {
|
|||||||
this.receivedCounter = (metrics == null) ? null : metrics.counter("messagebus_received_total", Map.of());
|
this.receivedCounter = (metrics == null) ? null : metrics.counter("messagebus_received_total", Map.of());
|
||||||
this.rejectedCounter = (metrics == null) ? null : metrics.counter("messagebus_rejected_total", Map.of());
|
this.rejectedCounter = (metrics == null) ? null : metrics.counter("messagebus_rejected_total", Map.of());
|
||||||
|
|
||||||
LOGGER.at(Level.INFO).log("Connected to Redis at %s:%d (db %d, auth=%s)",
|
LOGGER.at(Level.INFO).log("Connected to Redis at %s:%d (db %d, tls=%s, auth=%s)",
|
||||||
redis.host, redis.port, redis.database, this.authToken != null ? "on" : "off");
|
redis.host, redis.port, redis.database,
|
||||||
|
redis.tls ? "on" : "off",
|
||||||
|
this.authToken != null ? "on" : "off");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nonnull
|
@Nonnull
|
||||||
|
|||||||
@@ -23,6 +23,8 @@ public final class CoreConfig {
|
|||||||
public String password = null;
|
public String password = null;
|
||||||
public int database = 0;
|
public int database = 0;
|
||||||
public String key_prefix = "network:";
|
public String key_prefix = "network:";
|
||||||
|
public boolean tls = false;
|
||||||
|
public boolean verify_peer = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final class NetworkSection {
|
public static final class NetworkSection {
|
||||||
|
|||||||
Reference in New Issue
Block a user