181ca8c9cb
See CHANGELOG.md for the full breakdown. Summary:
- POST /api/v1/ldap/{bind,search}: LDAP-over-HTTPS so a client stops
speaking raw LDAP and instead calls the SSO, which binds/searches its
own OpenLDAP on the caller's behalf (DESIGN.md §3).
- LDAP byte-pump relay (utils/ldap_tunnel.js): forwards raw LDAP bytes
from an agent's local socket into OpenLDAP over the existing agent WSS
channel; the SSO never parses LDAP (DESIGN.md §4).
- POST /api/v1/agent/secrets: node-scoped OpenBao secret fetch for
agents, enforced to each agent's own secret/data/nodes/<id>/* prefix
(DESIGN.md §5).
- iam_apply signed command: push node-scoped IAM config (sudo rules, SSH
keys, access control, revocation) to an agent (DESIGN.md §6).
- Agent capability badges on the Directory Metrics tab, sourced from the
agent's own discovery frame.
- Join key management: GET /api/agent/join-keys/:id/agents (which hosts
enrolled through a key) plus a Manage join keys table in the Install
Agent modal with Revoke/Delete actions, confirmed inline per-row rather
than a blocking native confirm() or the shared app.messages.confirm()
banner (which desyncs across concurrent rows -- see CHANGELOG).
- docs/agents.md: capability matrix updated for the three new
capabilities, a full secrets-engine walkthrough with screenshots
(bash + Node consuming a rendered secret, plus the direct-API
alternative), and the join-key reuse/UI/audit questions answered.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
85 lines
2.4 KiB
JavaScript
85 lines
2.4 KiB
JavaScript
'use strict';
|
|
|
|
// LDAP byte-pump relay (DESIGN.md §4). The agent forwards raw LDAP bytes from a
|
|
// local socket (SSSD) over the WSS channel as `ldap_tunnel` messages; this
|
|
// module relays them into the SSO's real OpenLDAP and pipes the responses back.
|
|
// The SSO does not parse LDAP either — it is a transparent socket relay.
|
|
|
|
const net = require('net');
|
|
const conf = require('@simpleworkjs/conf').ldap;
|
|
|
|
// Parse host:port from an ldap:// or ldaps:// URL. The relay connects plaintext
|
|
// to the SSO's own slapd (which is plaintext on localhost); an ldaps:// URL
|
|
// would need TLS termination here and is not supported yet (DESIGN.md §9.5).
|
|
function ldapTarget() {
|
|
const url = conf.url || 'ldap://localhost:389';
|
|
const m = /^ldaps?:\/\/([^:/]+)(?::(\d+))?/.exec(url);
|
|
const host = m ? m[1] : 'localhost';
|
|
const port = m && m[2] ? Number(m[2]) : 389;
|
|
return { host, port };
|
|
}
|
|
|
|
// Per-agent relay state: agentId -> Map(conn_id -> LDAP socket).
|
|
const relays = new Map();
|
|
|
|
function relayFor(agentId) {
|
|
if (!relays.has(agentId)) relays.set(agentId, new Map());
|
|
return relays.get(agentId);
|
|
}
|
|
|
|
// Handle one ldap_tunnel message from an agent.
|
|
function handleTunnel(agentId, ws, payload) {
|
|
const connId = payload.conn_id;
|
|
if (!connId) return;
|
|
const conns = relayFor(agentId);
|
|
|
|
// End of connection: close the relay socket.
|
|
if (payload.close) {
|
|
const sock = conns.get(connId);
|
|
if (sock) { sock.destroy(); conns.delete(connId); }
|
|
return;
|
|
}
|
|
|
|
const data = Buffer.from(payload.data || '', 'base64');
|
|
if (data.length === 0) return;
|
|
|
|
let sock = conns.get(connId);
|
|
if (!sock) {
|
|
const { host, port } = ldapTarget();
|
|
sock = net.connect(port, host);
|
|
conns.set(connId, sock);
|
|
|
|
// Relay OpenLDAP's responses back to the agent.
|
|
sock.on('data', (chunk) => {
|
|
if (ws.readyState === 1) {
|
|
ws.send(JSON.stringify({
|
|
type: 'ldap_tunnel',
|
|
payload: { conn_id: connId, data: chunk.toString('base64') }
|
|
}));
|
|
}
|
|
});
|
|
sock.on('close', () => {
|
|
conns.delete(connId);
|
|
if (ws.readyState === 1) {
|
|
ws.send(JSON.stringify({
|
|
type: 'ldap_tunnel',
|
|
payload: { conn_id: connId, close: true }
|
|
}));
|
|
}
|
|
});
|
|
sock.on('error', () => { sock.destroy(); });
|
|
}
|
|
sock.write(data);
|
|
}
|
|
|
|
// Drop every relay socket for an agent (on WSS disconnect).
|
|
function cleanup(agentId) {
|
|
const conns = relays.get(agentId);
|
|
if (conns) {
|
|
for (const sock of conns.values()) sock.destroy();
|
|
relays.delete(agentId);
|
|
}
|
|
}
|
|
|
|
module.exports = { handleTunnel, cleanup };
|