Allow single-label hostnames as a Host target (#126)
The target ("ip") field validator required at least two dot-separated
labels, rejecting legitimate single-label hostnames like Docker Compose
service names ("sso-manager"), /etc/hosts entries, or anything resolved
via a search domain. This was enforced identically client-side
(public/lib/js/val.js) and server-side (utils/hostname_validate.js,
routes/host.js), so there was no way to set one through the UI or API
— only by writing to the Host model directly, bypassing validation
entirely (which is how theta-env's setup.sh registers sso-manager as a
target today, working only because it calls Host.create() directly).
Relax HOSTNAME in both places to accept either a bare single label or
the existing dotted-FQDN pattern. Flips the one existing test that
codified the old behavior (isValidHostname('localhost') was asserted
false) and adds coverage for the reported case.
This commit is contained in:
@@ -98,7 +98,9 @@
|
||||
// incoming host may be a wildcard ("*.example.com"); the target may not.
|
||||
(function(){
|
||||
var LABEL = /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/i;
|
||||
var HOSTNAME = /^(?=.{1,253}$)(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z]{2,63}$/i;
|
||||
// Either one bare label (Docker service names, /etc/hosts entries) or a
|
||||
// dotted hostname with an alphabetic TLD.
|
||||
var HOSTNAME = /^(?=.{1,253}$)(?:(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z]{2,63}|[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)$/i;
|
||||
var FORBIDDEN = /[\s/:]/;
|
||||
|
||||
function isIPv4( value ) {
|
||||
|
||||
@@ -32,8 +32,12 @@ describe('isValidHostname (strict, for target)', () => {
|
||||
assert.ok(isValidHostname('example.com'));
|
||||
assert.ok(isValidHostname('app.internal.net'));
|
||||
});
|
||||
test('rejects bare labels, numeric TLDs, wildcards', () => {
|
||||
assert.ok(!isValidHostname('localhost'));
|
||||
test('accepts a bare single-label hostname (Docker service names, /etc/hosts)', () => {
|
||||
assert.ok(isValidHostname('localhost'));
|
||||
assert.ok(isValidHostname('sso-manager'));
|
||||
assert.ok(isValidHostname('redis'));
|
||||
});
|
||||
test('rejects numeric-looking targets, wildcards', () => {
|
||||
assert.ok(!isValidHostname('10.10.10.10'));
|
||||
assert.ok(!isValidHostname('*.example.com'));
|
||||
assert.ok(!isValidHostname(''));
|
||||
@@ -81,6 +85,9 @@ describe('isValidTargetField (target: hostname or IP, no wildcard)', () => {
|
||||
assert.ok(isValidTargetField('app.internal.net'));
|
||||
assert.ok(isValidTargetField('10.0.0.5'));
|
||||
});
|
||||
test('accepts a bare single-label hostname (e.g. a Docker Compose service name)', () => {
|
||||
assert.ok(isValidTargetField('sso-manager'));
|
||||
});
|
||||
test('rejects wildcards, protocol, port, path', () => {
|
||||
assert.ok(!isValidTargetField('*.example.com'));
|
||||
assert.ok(!isValidTargetField('**'));
|
||||
|
||||
@@ -13,8 +13,11 @@
|
||||
* e.g. "*.example.com", "**.mysite.com", "payments.**", and
|
||||
* a bare "**" as a global catch-all. (Matched by
|
||||
* Host.lookUp in models/host.js.)
|
||||
* ip (target) — a concrete destination: an IPv4 address or a strict
|
||||
* hostname (dotted, alphabetic TLD). No wildcards.
|
||||
* ip (target) — a concrete destination: an IPv4 address, a single
|
||||
* unqualified label (e.g. "sso-manager" — a Docker
|
||||
* Compose service name, or any other host resolvable
|
||||
* via /etc/hosts or a search domain), or a dotted
|
||||
* hostname with an alphabetic TLD. No wildcards.
|
||||
*
|
||||
* Pure (no I/O) so it can be unit tested and reused. Enforced at the route layer
|
||||
* (routes/host.js) so internally-created entries (wildcard children, on-demand
|
||||
@@ -23,8 +26,9 @@
|
||||
|
||||
// A single DNS label.
|
||||
const LABEL = /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/i;
|
||||
// A strict hostname: dotted labels + alphabetic TLD (for the target).
|
||||
const HOSTNAME = /^(?=.{1,253}$)(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z]{2,63}$/i;
|
||||
// A strict hostname: either one bare label (Docker service names, /etc/hosts
|
||||
// entries, search-domain-relative names) or dotted labels + alphabetic TLD.
|
||||
const HOSTNAME = /^(?=.{1,253}$)(?:(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z]{2,63}|[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)$/i;
|
||||
// Scheme, path, port, or whitespace — anything that means it isn't a bare host.
|
||||
const FORBIDDEN = /[\s/:]/;
|
||||
|
||||
|
||||
@@ -489,7 +489,7 @@
|
||||
|
||||
<div class="form-group">
|
||||
<label for="ip" class="form-label">Target IP or host name</label>
|
||||
<input type="text" name="ip" class="form-control" placeholder="ex: 10.10.10.10 or app.internal.net" validate="target:3" />
|
||||
<input type="text" name="ip" class="form-control" placeholder="ex: 10.10.10.10, app.internal.net, or sso-manager" validate="target:3" />
|
||||
<b class="invalid-feedback"></b>
|
||||
<small class="field-help text-muted d-block">Where matching requests are proxied. Hostname or IP only — no protocol, port, or path.</small>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user