1
0
mirror of https://github.com/apache/httpd.git synced 2026-10-23 22:49:01 +03:00

mod_proxy_beacon: accept a random hex: key for ProxyBeaconSecret

git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@1939364 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Jim Jagielski
2026-10-23 18:57:56 +00:00
parent 6f637a5fca
commit db11e11abe
4 changed files with 50 additions and 14 deletions

View File

@@ -107,7 +107,7 @@ to the reverse proxy over unicast UDP datagrams</description>
<highlight language="config">
# Receive backend announcements on the cluster network interface (UDP).
ProxyBeaconListen 0.0.0.0:5555
ProxyBeaconSecret "a-long-random-shared-cluster-secret"
ProxyBeaconSecret "hex:00112233445566778899aabbccddeeff"
ProxyBeaconBalancer cluster
# A backend is dropped from rotation if it does not announce for 30 seconds.
@@ -126,7 +126,7 @@ ProxyPassReverse "/" "balancer://cluster/"
# Announce this backend's routable origin to the proxy every 10 seconds (UDP).
ProxyBeaconAddress proxy.example.com:5555
ProxyBeaconAdvertise http://10.0.0.5:8080
ProxyBeaconSecret "a-long-random-shared-cluster-secret"
ProxyBeaconSecret "hex:00112233445566778899aabbccddeeff"
ProxyBeaconInterval 10
</highlight>
@@ -446,7 +446,7 @@ is taken out of rotation</description>
<directivesynopsis>
<name>ProxyBeaconSecret</name>
<description>Pre-shared secret used to authenticate announcements</description>
<syntax>ProxyBeaconSecret <em>secret</em></syntax>
<syntax>ProxyBeaconSecret <em>secret</em>|hex:<em>key</em></syntax>
<contextlist><context>server config</context><context>virtual host</context>
</contextlist>
@@ -474,11 +474,16 @@ is taken out of rotation</description>
silently rejected (and logged), which appears as the backend never joining
the balancer.</p>
<p>The secret must be at least 16 characters; shorter values are rejected
when the configuration is read. The MAC key is derived from it with a fast
hash and the authentication tag is 64 bits, so anyone who captures a single
announcement can test passphrase guesses offline at full speed: use a long
random value, not a memorable password.</p>
<p>Use a random key. Generate one with <code>openssl rand -hex 16</code>
and configure it as <code>hex:</code> followed by the 32 hex digits; that
value is used directly as the 128-bit MAC key, so there is nothing to guess.
Any other value is treated as a passphrase: it must be at least 16
characters, and the MAC key is derived from it with a fast hash. Because the
authentication tag is 64 bits, anyone who captures a single announcement can
test passphrase guesses offline at full speed, so a passphrase protects only
as well as it is random and long. A value that begins with <code>hex:</code>
is always read as a key and is rejected if it is not exactly 32 hex
digits.</p>
<note type="warning"><title>The secret is a membership credential</title>
<p>A valid MAC proves only that the sender holds the secret. It does not

View File

@@ -208,9 +208,10 @@ Authentication (ProxyBeaconSecret, REQUIRED):
genuine sub-second announcements always advance. seq= is NOT used for replay
(it resets when a backend restarts); the wall-clock ts= does not.
The secret must be at least 16 characters. The key is MD5(passphrase) and the
tag is 64 bits: a captured announcement permits offline guessing at full speed,
so the secret should be long and random. A valid MAC shows possession of the
"hex:<32 hex digits>" is the 128-bit SipHash key itself (openssl rand -hex 16):
no passphrase, nothing to guess. Any other value is a passphrase of at least 16
characters; the key is then MD5(passphrase) and the tag is 64 bits, so a
captured announcement permits offline guessing at full speed. A valid MAC shows possession of the
secret, nothing more: every holder can register any reachable origin, speak for
other members and consume slots/records. The secret is a membership-
administrator credential, not a boundary between partially trusted workloads.

View File

@@ -80,7 +80,7 @@ httpd -l | grep mod_proxy_beacon # static build
```apache
# Receive backend announcements on the cluster-facing interface (UDP/5555).
ProxyBeaconListen 0.0.0.0:5555
ProxyBeaconSecret "a-long-random-shared-cluster-secret"
ProxyBeaconSecret "hex:00112233445566778899aabbccddeeff"
ProxyBeaconBalancer cluster
# Drop a backend from rotation if it goes silent for 30s; re-add on next beacon.
@@ -101,7 +101,7 @@ ProxyPassReverse "/" "balancer://cluster/"
# Announce this backend's routable origin to the proxy every 10s (UDP).
ProxyBeaconAddress proxy.example.com:5555
ProxyBeaconAdvertise http://10.0.0.5:8080
ProxyBeaconSecret "a-long-random-shared-cluster-secret"
ProxyBeaconSecret "hex:00112233445566778899aabbccddeeff"
ProxyBeaconInterval 10
```
@@ -125,7 +125,7 @@ as an enabled member of `balancer://cluster` on the proxy. Stop one, and after
| `ProxyBeaconListen [addr][:port]` | — | **Marks this server as the receiver.** Binds a UDP socket. `addr`/`port` are optional and inherited from the server's own `Listen`/`ServerName` when omitted (see note below). |
| `ProxyBeaconBalancer name` | — | Balancer that announced backends are added to. Bare name (`cluster`); a leading `balancer://` is stripped. Must already exist with spare `growth`. |
| `ProxyBeaconTimeout interval` | `0` (no eviction) | Seconds of silence before a member is disabled. `0` = add-only, never auto-remove. Set to a small multiple of the backends' interval to get self-healing. |
| `ProxyBeaconSecret secret` | — (**required**) | Shared cluster secret; **same value on proxy and all backends**, at least 16 characters (long and random). The server fails to start if a participating server omits it or it is too short. |
| `ProxyBeaconSecret secret` | — (**required**) | Shared cluster secret; **same value on proxy and all backends**, either `hex:` + 32 hex digits (a random key: `openssl rand -hex 16`) or a passphrase of at least 16 characters (weaker: guessable offline from one captured packet). The server fails to start if a participating server omits it or it is invalid. |
| `ProxyBeaconMaxSkew interval` | `30` | Anti-replay freshness window: reject announcements whose signed timestamp is more than this far from now (either direction). Sub-second values (`500ms`) are honoured. |
**Inheriting the listen address:** because UDP and TCP are separate port spaces,

View File

@@ -168,6 +168,8 @@ typedef struct {
#define BEACON_MAC_HEXLEN (APR_SIPHASH_DSIZE * 2)
/* Shortest ProxyBeaconSecret accepted. */
#define BEACON_MIN_SECRET_LEN 16
/* A secret of this form is used directly as the MAC key (no passphrase). */
#define BEACON_HEXKEY_PREFIX "hex:"
#define BEACON_MAXSKEW_DEFAULT apr_time_from_sec(30)
/* Max announcement datagram we accept. Our messages are a few hundred bytes;
@@ -409,6 +411,11 @@ static const char *beacon_set_timeout(cmd_parms *cmd, void *dummy,
return NULL;
}
static int beacon_hexval(char c)
{
return apr_isdigit(c) ? c - '0' : apr_tolower(c) - 'a' + 10;
}
static const char *beacon_set_secret(cmd_parms *cmd, void *dummy, const char *arg)
{
beacon_ctx_t *ctx = ap_get_module_config(cmd->server->module_config,
@@ -420,6 +427,29 @@ static const char *beacon_set_secret(cmd_parms *cmd, void *dummy, const char *ar
if (!*arg) {
return "ProxyBeaconSecret: empty secret";
}
/* hex:<32 hex digits> is the key itself: a random 128-bit key has no
* passphrase to guess, so it needs no key derivation. */
if (!strncmp(arg, BEACON_HEXKEY_PREFIX, sizeof(BEACON_HEXKEY_PREFIX) - 1)) {
const char *h = arg + sizeof(BEACON_HEXKEY_PREFIX) - 1;
int i;
if (strlen(h) != 2 * sizeof(ctx->mac_key)) {
return apr_psprintf(cmd->pool,
"ProxyBeaconSecret: hex: needs exactly %d hex "
"digits (e.g. from 'openssl rand -hex %d')",
(int)(2 * sizeof(ctx->mac_key)),
(int)sizeof(ctx->mac_key));
}
for (i = 0; i < (int)sizeof(ctx->mac_key); i++) {
if (!apr_isxdigit(h[2 * i]) || !apr_isxdigit(h[2 * i + 1])) {
return "ProxyBeaconSecret: hex: value has a non-hex digit";
}
ctx->mac_key[i] = (unsigned char)
((beacon_hexval(h[2 * i]) << 4) | beacon_hexval(h[2 * i + 1]));
}
ctx->has_secret = 1;
return NULL;
}
/* The MAC key is derived from the passphrase with a fast hash, so anyone who
* captures one announcement can test guesses offline at full speed; only a
* long random secret resists that. */