36e9d5b0b3
An SSH jump host that authenticates users against the shared LDAP directory, authorizes them from the SSO Manager's inventory graph, and bridges them to downstream hosts — auditing everything. - Username-grammar routing (uid_-_target@jump) + interactive TUI picker - Inbound LDAP auth (publickey / password with off|local|all policy) - Directory-driven access (LDAP groups x /api/discovery/resources?group=) - Per-user key injection into sshPublicKey, connects downstream as the user - Shell / exec / SFTP-subsystem bridging (WinSCP works) - Web UI + HTTP API (:3002) for audit + metrics; LDAP-admin gated - Packaged like proxy: ops/install.sh + systemd, all-in-one Docker, compose - Tests: 23 unit + 3 integration (node --test), all green Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
70 lines
2.5 KiB
JavaScript
70 lines
2.5 KiB
JavaScript
'use strict';
|
|
|
|
// Match a requested target string against the list of directory host
|
|
// resources the user may access (from utils/access.js). Matching order:
|
|
//
|
|
// 1. exact slug (host_web01)
|
|
// 2. host_-prefixed slug (web01 -> host_web01)
|
|
// 3. exact name (the directory display name, case-insensitive)
|
|
// 4. metadata.ip exact
|
|
// 5. metadata.address hostname exact (with or without scheme)
|
|
//
|
|
// A raw IPv4 target that matches no accessible host is allowed through only
|
|
// when allowRawIPs is set (the caller audits it as such); anything else that
|
|
// doesn't match is a no-access/no-such-target denial — the caller cannot
|
|
// tell those apart (by design: don't leak the inventory to unauthorized
|
|
// users).
|
|
//
|
|
// Returns { host, raw } — `host` is the matched resource (null for a
|
|
// permitted raw IP), `raw` is the literal address to dial when host is null.
|
|
// Throws { code: 'no-such-target' } when nothing matches.
|
|
|
|
const { isIPv4 } = require('./username_grammar');
|
|
|
|
function addrHost(address) {
|
|
if (!address) return null;
|
|
try {
|
|
return new URL(address.includes('://') ? address : `ssh://${address}`).hostname;
|
|
} catch (_) {
|
|
return address;
|
|
}
|
|
}
|
|
|
|
function matchTarget(target, hosts, { allowRawIPs = false } = {}) {
|
|
const t = String(target).toLowerCase();
|
|
|
|
const bySlug = hosts.find((h) => h.slug && h.slug.toLowerCase() === t);
|
|
if (bySlug) return { host: bySlug, raw: null };
|
|
|
|
const byPrefixed = hosts.find((h) => h.slug && h.slug.toLowerCase() === `host_${t}`);
|
|
if (byPrefixed) return { host: byPrefixed, raw: null };
|
|
|
|
const byName = hosts.find((h) => h.name && h.name.toLowerCase() === t);
|
|
if (byName) return { host: byName, raw: null };
|
|
|
|
const byIp = hosts.find((h) => h.metadata && h.metadata.ip === target);
|
|
if (byIp) return { host: byIp, raw: null };
|
|
|
|
const byAddr = hosts.find((h) => {
|
|
const a = addrHost(h.metadata && h.metadata.address);
|
|
return a && a.toLowerCase() === t;
|
|
});
|
|
if (byAddr) return { host: byAddr, raw: null };
|
|
|
|
if (isIPv4(target) && allowRawIPs) return { host: null, raw: target };
|
|
|
|
const err = new Error(`No accessible host matches '${target}'`);
|
|
err.code = 'no-such-target';
|
|
throw err;
|
|
}
|
|
|
|
// Resolve the address/port to dial for a matched host resource.
|
|
function hostEndpoint(host, defaultPort = 22) {
|
|
const md = host.metadata || {};
|
|
const address = md.ip || addrHost(md.address) || null;
|
|
const port = Number(md.sshPort) || defaultPort;
|
|
return { address, port };
|
|
}
|
|
|
|
module.exports = { matchTarget, hostEndpoint };
|