@lensmcp/cluster 1.18.4 → 1.18.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/basic-ssl.js +1 -241
- package/build-scope-patterns.js +1 -40
- package/create-webpack-dev.js +1 -186
- package/create-webpack-prod.js +1 -169
- package/executors/build/build.impl.js +1 -98
- package/executors/gateway/gateway-errors.js +1 -43
- package/executors/gateway/gateway.impl.js +1 -53
- package/executors/gateway/gateway.lib.js +1 -29
- package/executors/gateway/health-check.js +1 -66
- package/executors/gateway/jwks-verify.js +1 -121
- package/executors/gateway/main.prod-gateway.js +2 -573
- package/executors/gateway/main.rollout.js +11 -117
- package/executors/gateway/manifest.js +1 -374
- package/executors/gateway/metrics.js +1 -56
- package/executors/gateway/otel-tracing.js +1 -74
- package/executors/gateway/prod-gateway.lib.js +1 -22
- package/executors/gateway/prod-runtime/access-log.js +1 -24
- package/executors/gateway/prod-runtime/app.js +1 -123
- package/executors/gateway/prod-runtime/auth.js +1 -51
- package/executors/gateway/prod-runtime/cors.js +1 -40
- package/executors/gateway/prod-runtime/edge.js +1 -65
- package/executors/gateway/prod-runtime/handler.js +1 -226
- package/executors/gateway/prod-runtime/hooks.js +1 -42
- package/executors/gateway/prod-runtime/observability.js +1 -125
- package/executors/gateway/prod-runtime/rollout.js +1 -103
- package/executors/gateway/prod-runtime/routing.js +1 -40
- package/executors/gateway/prod-runtime/server.js +1 -79
- package/executors/gateway/prod-runtime/trust.js +1 -32
- package/executors/gateway/prod-runtime/types.js +1 -2
- package/executors/gateway/prod-runtime/upgrade.js +4 -116
- package/executors/gateway/prod-runtime/upstream.js +1 -21
- package/executors/gateway/providers-prod.js +1 -232
- package/executors/gateway/rate-limit.js +2 -75
- package/executors/gateway/registry-source.js +1 -131
- package/executors/gateway/rollout-ops.js +2 -167
- package/executors/gateway/runtime/auth.js +1 -64
- package/executors/gateway/runtime/chooser.js +12 -45
- package/executors/gateway/runtime/control.js +1 -128
- package/executors/gateway/runtime/dev-auth.js +1 -108
- package/executors/gateway/runtime/discovery.js +1 -123
- package/executors/gateway/runtime/edge.js +1 -47
- package/executors/gateway/runtime/handler.js +1 -183
- package/executors/gateway/runtime/hooks.js +1 -55
- package/executors/gateway/runtime/lens-children.js +1 -651
- package/executors/gateway/runtime/lifecycle.js +3 -842
- package/executors/gateway/runtime/observability.js +2 -148
- package/executors/gateway/runtime/pod-env.js +2 -89
- package/executors/gateway/runtime/proxy.js +1 -457
- package/executors/gateway/runtime/route-registry.js +1 -72
- package/executors/gateway/runtime/scope.js +1 -117
- package/executors/gateway/runtime/server.js +3 -487
- package/executors/gateway/runtime/service-keys.js +1 -49
- package/executors/gateway/runtime/types.js +1 -151
- package/executors/gateway/runtime/upgrade.js +1 -71
- package/executors/gateway/runtime/workspace-registry.js +1 -99
- package/executors/gateway/ssrf-guard.js +1 -190
- package/executors/serve/serve.impl.js +1 -280
- package/executors/trust/trust.impl.js +4 -162
- package/gateway.js +1 -35
- package/index.js +1 -16
- package/main.devserver.js +10 -1117
- package/package.json +4 -3
- package/tsgo-check-plugin.js +4 -364
- package/typecheck-bus.js +4 -256
|
@@ -1,125 +1 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.createObservability = createObservability;
|
|
4
|
-
const edge_1 = require("./edge");
|
|
5
|
-
function createObservability(opts, getRoutes) {
|
|
6
|
-
const emit = (severity, title, fingerprint, raw, ctx) => {
|
|
7
|
-
if (!opts.emit)
|
|
8
|
-
return; // lens off by default — no work, no allocation beyond the call
|
|
9
|
-
try {
|
|
10
|
-
opts.emit({
|
|
11
|
-
id: (0, edge_1.eid)(), sessionId: 'pending', timestamp: Date.now(),
|
|
12
|
-
source: 'gateway', category: 'cluster', severity,
|
|
13
|
-
context: { sessionId: 'pending', ...ctx }, fingerprint, title, raw,
|
|
14
|
-
});
|
|
15
|
-
}
|
|
16
|
-
catch { /* never let the bus break traffic */ }
|
|
17
|
-
};
|
|
18
|
-
// ---- traffic edges (host→service[@version]), flushed to the lens periodically ----
|
|
19
|
-
const edges = new Map();
|
|
20
|
-
const recordEdge = (route, ms, status, caller, version) => {
|
|
21
|
-
const host = route.host ?? '(default)';
|
|
22
|
-
const key = (caller ? caller + '⇒' : '') + host + '→' + route.service + (version ? '@' + version : '');
|
|
23
|
-
const e = edges.get(key) ?? { host, service: route.service, caller, version, count: 0, totalMs: 0, errors: 0 };
|
|
24
|
-
e.count += 1;
|
|
25
|
-
e.totalMs += ms;
|
|
26
|
-
if (status >= 500 || status === 0)
|
|
27
|
-
e.errors += 1;
|
|
28
|
-
edges.set(key, e);
|
|
29
|
-
};
|
|
30
|
-
// ---- per-service health rollup (feeds /statusz + the Redis status fan-out) ----
|
|
31
|
-
// A ring of the last N flush windows, summed at read time → a rolling view that
|
|
32
|
-
// doesn't flap to "idle" the instant traffic pauses. Derived purely from the edges.
|
|
33
|
-
const statusWindowCount = Math.max(1, opts.statusWindows ?? 12);
|
|
34
|
-
const statusWindows = [];
|
|
35
|
-
const lastSeenAt = new Map();
|
|
36
|
-
const routeServices = () => [...new Set(getRoutes().map((r) => r.service))];
|
|
37
|
-
const pushStatusWindow = () => {
|
|
38
|
-
const w = new Map();
|
|
39
|
-
for (const e of edges.values()) {
|
|
40
|
-
const s = w.get(e.service) ?? { requests: 0, errors: 0, totalMs: 0, versions: new Set() };
|
|
41
|
-
s.requests += e.count;
|
|
42
|
-
s.errors += e.errors;
|
|
43
|
-
s.totalMs += e.totalMs;
|
|
44
|
-
if (e.version)
|
|
45
|
-
s.versions.add(e.version);
|
|
46
|
-
w.set(e.service, s);
|
|
47
|
-
}
|
|
48
|
-
const now = Date.now();
|
|
49
|
-
for (const [svc, s] of w)
|
|
50
|
-
if (s.requests > 0)
|
|
51
|
-
lastSeenAt.set(svc, now);
|
|
52
|
-
statusWindows.push(w);
|
|
53
|
-
while (statusWindows.length > statusWindowCount)
|
|
54
|
-
statusWindows.shift();
|
|
55
|
-
};
|
|
56
|
-
const buildStatusSnapshot = () => {
|
|
57
|
-
const agg = new Map();
|
|
58
|
-
for (const w of statusWindows)
|
|
59
|
-
for (const [svc, s] of w) {
|
|
60
|
-
const a = agg.get(svc) ?? { requests: 0, errors: 0, totalMs: 0, versions: new Set() };
|
|
61
|
-
a.requests += s.requests;
|
|
62
|
-
a.errors += s.errors;
|
|
63
|
-
a.totalMs += s.totalMs;
|
|
64
|
-
for (const v of s.versions)
|
|
65
|
-
a.versions.add(v);
|
|
66
|
-
agg.set(svc, a);
|
|
67
|
-
}
|
|
68
|
-
const routes = getRoutes();
|
|
69
|
-
return routeServices().map((svc) => {
|
|
70
|
-
const a = agg.get(svc) ?? { requests: 0, errors: 0, totalMs: 0, versions: new Set() };
|
|
71
|
-
const hosts = [...new Set(routes.filter((r) => r.service === svc).map((r) => r.host ?? '(default)'))];
|
|
72
|
-
const er = a.requests ? a.errors / a.requests : 0;
|
|
73
|
-
let status = a.requests === 0 ? 'idle' : er >= 0.5 ? 'down' : er >= 0.05 ? 'degraded' : 'healthy';
|
|
74
|
-
// active liveness: if every known upstream for this service is failing probes, it's down (even with no traffic)
|
|
75
|
-
if (opts.healthChecker) {
|
|
76
|
-
const svcUrls = routes.filter((r) => r.service === svc && r.upstream).map((r) => r.upstream);
|
|
77
|
-
if (svcUrls.length && svcUrls.every((u) => !opts.healthChecker.isHealthy(u)))
|
|
78
|
-
status = 'down';
|
|
79
|
-
}
|
|
80
|
-
return { service: svc, hosts, status, requests: a.requests, errorRate: +er.toFixed(4), avgMs: a.requests ? Math.round(a.totalMs / a.requests) : 0, versions: [...a.versions], lastSeen: lastSeenAt.get(svc) ?? null };
|
|
81
|
-
});
|
|
82
|
-
};
|
|
83
|
-
const startFlusher = () => {
|
|
84
|
-
const t = setInterval(() => {
|
|
85
|
-
pushStatusWindow(); // always: roll the health window (independent of the lens)
|
|
86
|
-
if (opts.onStatus) {
|
|
87
|
-
try {
|
|
88
|
-
opts.onStatus(buildStatusSnapshot());
|
|
89
|
-
}
|
|
90
|
-
catch { /* never let a status sink break traffic */ }
|
|
91
|
-
}
|
|
92
|
-
if (opts.emit) {
|
|
93
|
-
for (const e of edges.values()) {
|
|
94
|
-
emit(e.errors ? 'warning' : 'info', `traffic ${e.host} → ${e.service}${e.version ? '@' + e.version : ''}: ${e.count} req`, `gateway-traffic:${e.host}->${e.service}${e.version ? '@' + e.version : ''}`, { kind: 'gateway-traffic', host: e.host, project: e.service, caller: e.caller, version: e.version, count: e.count, avgMs: Math.round(e.totalMs / e.count), errors: e.errors });
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
edges.clear();
|
|
98
|
-
}, opts.trafficFlushMs ?? 5000);
|
|
99
|
-
t.unref?.();
|
|
100
|
-
return t;
|
|
101
|
-
};
|
|
102
|
-
// ---- per-request flow trace (only for flow-header-carrying requests) ----
|
|
103
|
-
const beginTrace = (req) => {
|
|
104
|
-
if (!opts.emit)
|
|
105
|
-
return undefined; // no lens → no trace overhead
|
|
106
|
-
const flowId = (0, edge_1.hdr)(req.headers['x-lensmcp-flow-id']);
|
|
107
|
-
let requestId = (0, edge_1.hdr)(req.headers['x-request-id']);
|
|
108
|
-
if (!flowId && !requestId && !(0, edge_1.hdr)(req.headers['traceparent']))
|
|
109
|
-
return undefined;
|
|
110
|
-
if (!requestId) {
|
|
111
|
-
requestId = (0, edge_1.eid)();
|
|
112
|
-
req.headers['x-request-id'] = requestId;
|
|
113
|
-
}
|
|
114
|
-
return { startedAt: Date.now(), method: (req.method ?? 'GET').toUpperCase(), host: (req.headers.host || '').split(':')[0], url: req.url ?? '/', steps: [], ...(flowId ? { flowId } : {}), ...(requestId ? { requestId } : {}) };
|
|
115
|
-
};
|
|
116
|
-
const step = (t, name, info) => { if (t)
|
|
117
|
-
t.steps.push({ step: name, atMs: Date.now() - t.startedAt, ...info }); };
|
|
118
|
-
const finish = (t, service, status, extra) => {
|
|
119
|
-
if (!t || t.done)
|
|
120
|
-
return;
|
|
121
|
-
t.done = true;
|
|
122
|
-
emit(status >= 500 ? 'error' : 'info', `gateway ${t.method} ${t.host}${t.url} → ${status} (${Date.now() - t.startedAt}ms)`, `gateway-request:${service}`, { kind: 'gateway-request', project: service, host: t.host, method: t.method, url: t.url, status, durationMs: Date.now() - t.startedAt, startedAt: t.startedAt, steps: t.steps, ...extra }, { ...(t.flowId ? { flowId: t.flowId } : {}), ...(t.requestId ? { requestId: t.requestId } : {}) });
|
|
123
|
-
};
|
|
124
|
-
return { emit, beginTrace, step, finish, recordEdge, buildStatusSnapshot, startFlusher };
|
|
125
|
-
}
|
|
1
|
+
"use strict";var b=Object.defineProperty;var p=(o,d)=>b(o,"name",{value:d,configurable:!0});var y=Object.defineProperty,c=p((o,d)=>y(o,"name",{value:d,configurable:!0}),"c");Object.defineProperty(exports,"__esModule",{value:!0}),exports.createObservability=createObservability;const redact_1=require("@lensmcp/core/redact"),edge_1=require("./edge");function createObservability(o,d){const v=c((e,t,s,r,a)=>{if(o.emit)try{o.emit({id:(0,edge_1.eid)(),sessionId:"pending",timestamp:Date.now(),source:"gateway",category:"cluster",severity:e,context:(0,redact_1.redactDeep)({sessionId:"pending",...a}),fingerprint:s,title:(0,redact_1.safeTitle)(t),raw:r&&(0,redact_1.redactDeep)(r)})}catch{}},"emit"),h=new Map,q=c((e,t,s,r,a)=>{const n=e.host??"(default)",u=(r?r+"\u21D2":"")+n+"\u2192"+e.service+(a?"@"+a:""),i=h.get(u)??{host:n,service:e.service,caller:r,version:a,count:0,totalMs:0,errors:0};i.count+=1,i.totalMs+=t,(s>=500||s===0)&&(i.errors+=1),h.set(u,i)},"recordEdge"),m=Math.max(1,o.statusWindows??12),f=[],w=new Map,M=c(()=>[...new Set(d().map(e=>e.service))],"routeServices"),S=c(()=>{const e=new Map;for(const s of h.values()){const r=e.get(s.service)??{requests:0,errors:0,totalMs:0,versions:new Set};r.requests+=s.count,r.errors+=s.errors,r.totalMs+=s.totalMs,s.version&&r.versions.add(s.version),e.set(s.service,r)}const t=Date.now();for(const[s,r]of e)r.requests>0&&w.set(s,t);for(f.push(e);f.length>m;)f.shift()},"pushStatusWindow"),g=c(()=>{const e=new Map;for(const s of f)for(const[r,a]of s){const n=e.get(r)??{requests:0,errors:0,totalMs:0,versions:new Set};n.requests+=a.requests,n.errors+=a.errors,n.totalMs+=a.totalMs;for(const u of a.versions)n.versions.add(u);e.set(r,n)}const t=d();return M().map(s=>{const r=e.get(s)??{requests:0,errors:0,totalMs:0,versions:new Set},a=[...new Set(t.filter(i=>i.service===s).map(i=>i.host??"(default)"))],n=r.requests?r.errors/r.requests:0;let u=r.requests===0?"idle":n>=.5?"down":n>=.05?"degraded":"healthy";if(o.healthChecker){const i=t.filter(l=>l.service===s&&l.upstream).map(l=>l.upstream);i.length&&i.every(l=>!o.healthChecker.isHealthy(l))&&(u="down")}return{service:s,hosts:a,status:u,requests:r.requests,errorRate:+n.toFixed(4),avgMs:r.requests?Math.round(r.totalMs/r.requests):0,versions:[...r.versions],lastSeen:w.get(s)??null}})},"buildStatusSnapshot");return{emit:v,beginTrace:c(e=>{if(!o.emit)return;const t=(0,edge_1.hdr)(e.headers["x-lensmcp-flow-id"]);let s=(0,edge_1.hdr)(e.headers["x-request-id"]);if(!(!t&&!s&&!(0,edge_1.hdr)(e.headers.traceparent)))return s||(s=(0,edge_1.eid)(),e.headers["x-request-id"]=s),{startedAt:Date.now(),method:(e.method??"GET").toUpperCase(),host:(e.headers.host||"").split(":")[0],url:e.url??"/",steps:[],...t?{flowId:t}:{},...s?{requestId:s}:{}}},"beginTrace"),step:c((e,t,s)=>{e&&e.steps.push({step:t,atMs:Date.now()-e.startedAt,...s})},"step"),finish:c((e,t,s,r)=>{!e||e.done||(e.done=!0,v(s>=500?"error":"info",`gateway ${e.method} ${e.host}${e.url} \u2192 ${s} (${Date.now()-e.startedAt}ms)`,`gateway-request:${t}`,{kind:"gateway-request",project:t,host:e.host,method:e.method,url:e.url,status:s,durationMs:Date.now()-e.startedAt,startedAt:e.startedAt,steps:e.steps,...r},{...e.flowId?{flowId:e.flowId}:{},...e.requestId?{requestId:e.requestId}:{}}))},"finish"),recordEdge:q,buildStatusSnapshot:g,startFlusher:c(()=>{const e=setInterval(()=>{if(S(),o.onStatus)try{o.onStatus(g())}catch{}if(o.emit)for(const t of h.values())v(t.errors?"warning":"info",`traffic ${t.host} \u2192 ${t.service}${t.version?"@"+t.version:""}: ${t.count} req`,`gateway-traffic:${t.host}->${t.service}${t.version?"@"+t.version:""}`,{kind:"gateway-traffic",host:t.host,project:t.service,caller:t.caller,version:t.version,count:t.count,avgMs:Math.round(t.totalMs/t.count),errors:t.errors});h.clear()},o.trafficFlushMs??5e3);return e.unref?.(),e},"startFlusher")}}p(createObservability,"createObservability"),c(createObservability,"createObservability");
|
|
@@ -1,103 +1 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.createRollout = createRollout;
|
|
4
|
-
/**
|
|
5
|
-
* Targeted-rollout support: authoritative client-IP resolution (trusted
|
|
6
|
-
* front-proxy header, anti-spoof), the sticky device key (signed cookie → uid
|
|
7
|
-
* header → minted), and the flat ABAC attribute bag shared by the HTTP + WS
|
|
8
|
-
* paths so a rule sees an identical bag on both.
|
|
9
|
-
*/
|
|
10
|
-
const node_crypto_1 = require("node:crypto");
|
|
11
|
-
const manifest_1 = require("../manifest");
|
|
12
|
-
const edge_1 = require("./edge");
|
|
13
|
-
function createRollout(opts) {
|
|
14
|
-
const clientIpHeader = opts.clientIpHeader?.toLowerCase();
|
|
15
|
-
// `clientIpHeader` is only trusted from a known front proxy. Default: loopback only
|
|
16
|
-
// (a co-located cloudflared/sidecar) — an off-proxy attacker hitting the origin directly
|
|
17
|
-
// cannot spoof the `ip` ABAC attribute.
|
|
18
|
-
const trustedProxies = opts.clientIpTrustedProxies ?? [];
|
|
19
|
-
const peerTrusted = (peer) => {
|
|
20
|
-
if (!peer)
|
|
21
|
-
return false;
|
|
22
|
-
const ip = peer.startsWith('::ffff:') ? peer.slice(7) : peer;
|
|
23
|
-
if (trustedProxies.length === 0)
|
|
24
|
-
return ip === '127.0.0.1' || peer === '::1';
|
|
25
|
-
return trustedProxies.some((c) => (0, manifest_1.matchCidr)(ip, c));
|
|
26
|
-
};
|
|
27
|
-
const clientIpOf = (req, fallback) => {
|
|
28
|
-
if (clientIpHeader && peerTrusted(req.socket?.remoteAddress ?? undefined)) {
|
|
29
|
-
const h = (0, edge_1.hdr)(req.headers[clientIpHeader]);
|
|
30
|
-
if (h)
|
|
31
|
-
return h;
|
|
32
|
-
}
|
|
33
|
-
return fallback;
|
|
34
|
-
};
|
|
35
|
-
const attrHeaders = (opts.attributeHeaders ?? []).map((h) => h.toLowerCase());
|
|
36
|
-
const uidHeader = opts.uidHeader?.toLowerCase();
|
|
37
|
-
const cookieOn = !!opts.cookie;
|
|
38
|
-
const cookieName = opts.cookie?.name ?? 'lensmcp_did';
|
|
39
|
-
const cookieSecret = opts.cookie?.secret;
|
|
40
|
-
const cookieDomain = opts.cookie?.domain;
|
|
41
|
-
const cookieMaxAge = opts.cookie?.maxAge ?? 31_536_000; // 1y
|
|
42
|
-
if (cookieOn && !cookieSecret)
|
|
43
|
-
console.warn('[gateway] device cookie has no secret — values are UNSIGNED (set cookie.secret to prevent bucket-grinding).');
|
|
44
|
-
const signId = (id) => (0, node_crypto_1.createHmac)('sha256', cookieSecret).update(id).digest('base64url');
|
|
45
|
-
const signedCookie = (id) => (cookieSecret ? `${id}.${signId(id)}` : id);
|
|
46
|
-
const verifyCookie = (val) => {
|
|
47
|
-
if (!cookieSecret)
|
|
48
|
-
return val || undefined; // unsigned mode
|
|
49
|
-
const i = val.lastIndexOf('.');
|
|
50
|
-
if (i < 0)
|
|
51
|
-
return undefined;
|
|
52
|
-
const id = val.slice(0, i);
|
|
53
|
-
const a = Buffer.from(val.slice(i + 1)), b = Buffer.from(signId(id));
|
|
54
|
-
return a.length === b.length && (0, node_crypto_1.timingSafeEqual)(a, b) ? id : undefined;
|
|
55
|
-
};
|
|
56
|
-
const parseCookie = (header, name) => {
|
|
57
|
-
if (!header)
|
|
58
|
-
return undefined;
|
|
59
|
-
for (const part of header.split(';')) {
|
|
60
|
-
const eq = part.indexOf('=');
|
|
61
|
-
if (eq > 0 && part.slice(0, eq).trim() === name)
|
|
62
|
-
return decodeURIComponent(part.slice(eq + 1).trim());
|
|
63
|
-
}
|
|
64
|
-
return undefined;
|
|
65
|
-
};
|
|
66
|
-
const mintCookieHeader = (id) => {
|
|
67
|
-
let c = `${cookieName}=${encodeURIComponent(signedCookie(id))}; Path=/; Max-Age=${cookieMaxAge}; HttpOnly; SameSite=Lax; Secure`;
|
|
68
|
-
if (cookieDomain)
|
|
69
|
-
c += `; Domain=${cookieDomain}`;
|
|
70
|
-
return c;
|
|
71
|
-
};
|
|
72
|
-
const resolveStickyKey = (req) => {
|
|
73
|
-
if (cookieOn) {
|
|
74
|
-
const c = parseCookie(req.headers.cookie, cookieName);
|
|
75
|
-
const id = c ? verifyCookie(c) : undefined;
|
|
76
|
-
if (id)
|
|
77
|
-
return { key: id, mint: false };
|
|
78
|
-
}
|
|
79
|
-
if (uidHeader) {
|
|
80
|
-
const u = (0, edge_1.hdr)(req.headers[uidHeader]);
|
|
81
|
-
if (u)
|
|
82
|
-
return { key: u, mint: false };
|
|
83
|
-
}
|
|
84
|
-
if (cookieOn)
|
|
85
|
-
return { key: 'd_' + (0, node_crypto_1.randomBytes)(12).toString('base64url'), mint: true };
|
|
86
|
-
return { mint: false };
|
|
87
|
-
};
|
|
88
|
-
const buildAttrs = (req, fallbackIp, principal, key) => {
|
|
89
|
-
const a = { ...(principal ?? {}) };
|
|
90
|
-
a['ip'] = clientIpOf(req, fallbackIp);
|
|
91
|
-
a['method'] = (req.method ?? 'GET').toUpperCase();
|
|
92
|
-
a['path'] = (req.url ?? '/').split('?')[0];
|
|
93
|
-
if (key)
|
|
94
|
-
a['device'] = key;
|
|
95
|
-
for (const h of attrHeaders) {
|
|
96
|
-
const v = (0, edge_1.hdr)(req.headers[h]);
|
|
97
|
-
if (v !== undefined)
|
|
98
|
-
a['header.' + h] = v;
|
|
99
|
-
}
|
|
100
|
-
return a;
|
|
101
|
-
};
|
|
102
|
-
return { clientIpOf, buildAttrs, resolveStickyKey, mintCookieHeader };
|
|
103
|
-
}
|
|
1
|
+
"use strict";var H=Object.defineProperty;var u=(i,c)=>H(i,"name",{value:c,configurable:!0});var S=Object.defineProperty,r=u((i,c)=>S(i,"name",{value:c,configurable:!0}),"r");Object.defineProperty(exports,"__esModule",{value:!0}),exports.createRollout=createRollout;const node_crypto_1=require("node:crypto"),manifest_1=require("../manifest"),edge_1=require("./edge");function createRollout(i){const c=i.clientIpHeader?.toLowerCase(),l=i.clientIpTrustedProxies??[],y=r(e=>{if(!e)return!1;const t=e.startsWith("::ffff:")?e.slice(7):e;return l.length===0?t==="127.0.0.1"||e==="::1":l.some(o=>(0,manifest_1.matchCidr)(t,o))},"peerTrusted"),f=r((e,t)=>{if(c&&y(e.socket?.remoteAddress??void 0)){const o=(0,edge_1.hdr)(e.headers[c]);if(o)return o}return t},"clientIpOf"),C=(i.attributeHeaders??[]).map(e=>e.toLowerCase()),m=i.uidHeader?.toLowerCase(),d=!!i.cookie,p=i.cookie?.name??"lensmcp_did",a=i.cookie?.secret,k=i.cookie?.domain,I=i.cookie?.maxAge??31536e3;d&&!a&&console.warn("[gateway] device cookie has no secret \u2014 values are UNSIGNED (set cookie.secret to prevent bucket-grinding).");const h=r(e=>(0,node_crypto_1.createHmac)("sha256",a).update(e).digest("base64url"),"signId"),b=r(e=>a?`${e}.${h(e)}`:e,"signedCookie"),x=r(e=>{if(!a)return e||void 0;const t=e.lastIndexOf(".");if(t<0)return;const o=e.slice(0,t),n=Buffer.from(e.slice(t+1)),s=Buffer.from(h(o));return n.length===s.length&&(0,node_crypto_1.timingSafeEqual)(n,s)?o:void 0},"verifyCookie"),_=r((e,t)=>{if(e)for(const o of e.split(";")){const n=o.indexOf("=");if(n>0&&o.slice(0,n).trim()===t)return decodeURIComponent(o.slice(n+1).trim())}},"parseCookie");return{clientIpOf:f,buildAttrs:r((e,t,o,n)=>{const s={...o??{}};s.ip=f(e,t),s.method=(e.method??"GET").toUpperCase(),s.path=(e.url??"/").split("?")[0],n&&(s.device=n);for(const g of C){const v=(0,edge_1.hdr)(e.headers[g]);v!==void 0&&(s["header."+g]=v)}return s},"buildAttrs"),resolveStickyKey:r(e=>{if(d){const t=_(e.headers.cookie,p),o=t?x(t):void 0;if(o)return{key:o,mint:!1}}if(m){const t=(0,edge_1.hdr)(e.headers[m]);if(t)return{key:t,mint:!1}}return d?{key:"d_"+(0,node_crypto_1.randomBytes)(12).toString("base64url"),mint:!0}:{mint:!1}},"resolveStickyKey"),mintCookieHeader:r(e=>{let t=`${p}=${encodeURIComponent(b(e))}; Path=/; Max-Age=${I}; HttpOnly; SameSite=Lax; Secure`;return k&&(t+=`; Domain=${k}`),t},"mintCookieHeader")}}u(createRollout,"createRollout"),r(createRollout,"createRollout");
|
|
@@ -1,40 +1 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.createRouteTable = createRouteTable;
|
|
4
|
-
/**
|
|
5
|
-
* The route table — rebuilt atomically from the manifest source (the SAME
|
|
6
|
-
* `buildRouteTable` brain as dev). The `routes` array + `manifestByService` map
|
|
7
|
-
* keep STABLE identities (content-replaced in place) so every other layer can
|
|
8
|
-
* hold the reference and always read the live table.
|
|
9
|
-
*/
|
|
10
|
-
const manifest_1 = require("../manifest");
|
|
11
|
-
function createRouteTable(opts, obs) {
|
|
12
|
-
const routes = [];
|
|
13
|
-
const manifestByService = new Map();
|
|
14
|
-
let current = opts.manifests.list();
|
|
15
|
-
const apply = (next) => {
|
|
16
|
-
current = next;
|
|
17
|
-
const built = (0, manifest_1.buildRouteTable)(next);
|
|
18
|
-
routes.length = 0;
|
|
19
|
-
routes.push(...built);
|
|
20
|
-
manifestByService.clear();
|
|
21
|
-
for (const m of next)
|
|
22
|
-
manifestByService.set(m.service, m);
|
|
23
|
-
};
|
|
24
|
-
const rebuild = (next) => {
|
|
25
|
-
apply(next);
|
|
26
|
-
if (opts.healthChecker) { // monitor every reachable upstream: direct manifest URLs + pooled endpoints
|
|
27
|
-
const urls = new Set();
|
|
28
|
-
for (const r of routes)
|
|
29
|
-
if (r.upstream)
|
|
30
|
-
urls.add(r.upstream);
|
|
31
|
-
for (const u of opts.pods?.endpoints?.() ?? [])
|
|
32
|
-
urls.add(u);
|
|
33
|
-
opts.healthChecker.track([...urls]);
|
|
34
|
-
}
|
|
35
|
-
obs.emit('info', `gateway routes: ${routes.length} on ${current.length} services`, 'gateway-up', { kind: 'gateway-up', routes: routes.map((r) => ({ host: r.host ?? '(default)', service: r.service, internal: !!r.internal })) });
|
|
36
|
-
};
|
|
37
|
-
apply(current); // initial build (no emit yet; server.ts fires the initial gateway-up via rebuild)
|
|
38
|
-
const unwatch = opts.manifests.watch?.((next) => rebuild(next));
|
|
39
|
-
return { routes, manifestByService, rebuild, manifests: () => current, stop: () => unwatch?.() };
|
|
40
|
-
}
|
|
1
|
+
"use strict";var d=Object.defineProperty;var u=(e,o)=>d(e,"name",{value:o,configurable:!0});var h=Object.defineProperty,r=u((e,o)=>h(e,"name",{value:o,configurable:!0}),"r");Object.defineProperty(exports,"__esModule",{value:!0}),exports.createRouteTable=createRouteTable;const manifest_1=require("../manifest");function createRouteTable(e,o){const n=[],c=new Map;let i=e.manifests.list();const l=r(a=>{i=a;const t=(0,manifest_1.buildRouteTable)(a);n.length=0,n.push(...t),c.clear();for(const s of a)c.set(s.service,s)},"apply"),f=r(a=>{if(l(a),e.healthChecker){const t=new Set;for(const s of n)s.upstream&&t.add(s.upstream);for(const s of e.pods?.endpoints?.()??[])t.add(s);e.healthChecker.track([...t])}o.emit("info",`gateway routes: ${n.length} on ${i.length} services`,"gateway-up",{kind:"gateway-up",routes:n.map(t=>({host:t.host??"(default)",service:t.service,internal:!!t.internal}))})},"rebuild");l(i);const p=e.manifests.watch?.(a=>f(a));return{routes:n,manifestByService:c,rebuild:f,manifests:r(()=>i,"manifests"),stop:r(()=>p?.(),"stop")}}u(createRouteTable,"createRouteTable"),r(createRouteTable,"createRouteTable");
|
|
@@ -1,79 +1 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.startProdGateway = startProdGateway;
|
|
4
|
-
const observability_1 = require("./observability");
|
|
5
|
-
const routing_1 = require("./routing");
|
|
6
|
-
const rollout_1 = require("./rollout");
|
|
7
|
-
const upstream_1 = require("./upstream");
|
|
8
|
-
const auth_1 = require("./auth");
|
|
9
|
-
const hooks_1 = require("./hooks");
|
|
10
|
-
const cors_1 = require("./cors");
|
|
11
|
-
const handler_1 = require("./handler");
|
|
12
|
-
const upgrade_1 = require("./upgrade");
|
|
13
|
-
const app_1 = require("./app");
|
|
14
|
-
async function startProdGateway(opts) {
|
|
15
|
-
const ports = opts.ports?.length ? opts.ports : [opts.tls ? 8443 : 8080];
|
|
16
|
-
// Default authenticator FAILS CLOSED: a jwt route with no authenticator wired is rejected.
|
|
17
|
-
const authenticate = opts.authenticate ?? ((mode) => {
|
|
18
|
-
if (mode === 'jwt')
|
|
19
|
-
throw new Error('jwt route but no authenticator configured');
|
|
20
|
-
});
|
|
21
|
-
// ---- option-derived config (resolved once) ----
|
|
22
|
-
const versionHeader = (opts.versionHeader ?? 'x-lensmcp-version').toLowerCase();
|
|
23
|
-
const accessLog = !!opts.accessLog;
|
|
24
|
-
const corsCredentialOrigins = new Set((opts.corsCredentialOrigins ?? []).map((o) => o.toLowerCase()));
|
|
25
|
-
const keyToProject = new Map(Object.entries(opts.serviceKeys ?? {}).map(([p, k]) => [k, p]));
|
|
26
|
-
// ---- route table + observability (lazy getRoutes breaks the table↔obs cycle) ----
|
|
27
|
-
// A holder, so obs can read the routes lazily while `table` stays a const (assigned once,
|
|
28
|
-
// before any request — obs's getter only runs at request time).
|
|
29
|
-
const tableRef = {};
|
|
30
|
-
const obs = (0, observability_1.createObservability)(opts, () => tableRef.current.routes);
|
|
31
|
-
const table = (0, routing_1.createRouteTable)(opts, obs);
|
|
32
|
-
tableRef.current = table;
|
|
33
|
-
const rt = { opts, table, keyToProject, versionHeader, accessLog, corsCredentialOrigins };
|
|
34
|
-
// ---- layers ----
|
|
35
|
-
const rollout = (0, rollout_1.createRollout)(opts);
|
|
36
|
-
const upstream = (0, upstream_1.createUpstream)(opts, obs);
|
|
37
|
-
const edgeAuth = (0, auth_1.createEdgeAuth)(rt, obs, rollout, authenticate);
|
|
38
|
-
const hooks = (0, hooks_1.createHooks)(opts.hooks ?? {});
|
|
39
|
-
const reqState = new WeakMap();
|
|
40
|
-
const spanByReq = new WeakMap();
|
|
41
|
-
const ctxByReq = new WeakMap();
|
|
42
|
-
const cors = (0, cors_1.createCors)(rt, reqState);
|
|
43
|
-
const handler = (0, handler_1.createHandler)({ rt, obs, cors, auth: edgeAuth, upstream, rollout, hooks, reqState, spanByReq, ctxByReq });
|
|
44
|
-
const upgrade = (0, upgrade_1.createUpgrade)({ rt, auth: edgeAuth, upstream, hooks });
|
|
45
|
-
const { buildApp } = (0, app_1.createApp)({ rt, obs, cors, hooks, handler, upgrade, reqState, spanByReq, ctxByReq });
|
|
46
|
-
const flusher = obs.startFlusher();
|
|
47
|
-
table.rebuild(table.manifests()); // emit initial gateway-up (+ healthChecker.track)
|
|
48
|
-
// ---- listeners ----
|
|
49
|
-
const apps = [];
|
|
50
|
-
const boundPorts = [];
|
|
51
|
-
for (const port of ports) {
|
|
52
|
-
const app = await buildApp();
|
|
53
|
-
await app.listen({ port, host: '0.0.0.0' });
|
|
54
|
-
const addr = app.server.address();
|
|
55
|
-
const bound = typeof addr === 'object' && addr ? addr.port : port;
|
|
56
|
-
boundPorts.push(bound);
|
|
57
|
-
console.log(`[gateway] listening on ${opts.tls ? 'https' : 'http'}://0.0.0.0:${bound}`);
|
|
58
|
-
apps.push(app);
|
|
59
|
-
}
|
|
60
|
-
opts.healthChecker?.start(); // begin probing now that the route table (and tracked URLs) is warm
|
|
61
|
-
let stopped = false;
|
|
62
|
-
return {
|
|
63
|
-
ports: boundPorts,
|
|
64
|
-
routes: () => table.routes,
|
|
65
|
-
status: obs.buildStatusSnapshot,
|
|
66
|
-
stop: async () => {
|
|
67
|
-
if (stopped)
|
|
68
|
-
return;
|
|
69
|
-
stopped = true;
|
|
70
|
-
clearInterval(flusher);
|
|
71
|
-
table.stop();
|
|
72
|
-
opts.healthChecker?.stop();
|
|
73
|
-
opts.rateLimiter?.stop();
|
|
74
|
-
opts.metrics?.stop();
|
|
75
|
-
obs.emit('info', 'gateway down', 'gateway-down', { kind: 'gateway-down' });
|
|
76
|
-
await Promise.all(apps.map((a) => a.close())); // reply-from closes its undici pool on close
|
|
77
|
-
},
|
|
78
|
-
};
|
|
79
|
-
}
|
|
1
|
+
"use strict";var B=Object.defineProperty;var l=(e,o)=>B(e,"name",{value:o,configurable:!0});var x=Object.defineProperty,n=l((e,o)=>x(e,"name",{value:o,configurable:!0}),"n");Object.defineProperty(exports,"__esModule",{value:!0}),exports.startProdGateway=startProdGateway;const observability_1=require("./observability"),routing_1=require("./routing"),rollout_1=require("./rollout"),upstream_1=require("./upstream"),auth_1=require("./auth"),hooks_1=require("./hooks"),cors_1=require("./cors"),handler_1=require("./handler"),upgrade_1=require("./upgrade"),app_1=require("./app");async function startProdGateway(e){const o=e.ports?.length?e.ports:[e.tls?8443:8080],f=e.authenticate??(t=>{if(t==="jwt")throw new Error("jwt route but no authenticator configured")}),j=(e.versionHeader??"x-lensmcp-version").toLowerCase(),C=!!e.accessLog,P=new Set((e.corsCredentialOrigins??[]).map(t=>t.toLowerCase())),O=new Map(Object.entries(e.serviceKeys??{}).map(([t,i])=>[i,t])),h={},r=(0,observability_1.createObservability)(e,()=>h.current.routes),a=(0,routing_1.createRouteTable)(e,r);h.current=a;const s={opts:e,table:a,keyToProject:O,versionHeader:j,accessLog:C,corsCredentialOrigins:P},d=(0,rollout_1.createRollout)(e),w=(0,upstream_1.createUpstream)(e,r),y=(0,auth_1.createEdgeAuth)(s,r,d,f),u=(0,hooks_1.createHooks)(e.hooks??{}),c=new WeakMap,g=new WeakMap,b=new WeakMap,q=(0,cors_1.createCors)(s,c),R=(0,handler_1.createHandler)({rt:s,obs:r,cors:q,auth:y,upstream:w,rollout:d,hooks:u,reqState:c,spanByReq:g,ctxByReq:b}),L=(0,upgrade_1.createUpgrade)({rt:s,auth:y,upstream:w,hooks:u}),{buildApp:M}=(0,app_1.createApp)({rt:s,obs:r,cors:q,hooks:u,handler:R,upgrade:L,reqState:c,spanByReq:g,ctxByReq:b}),S=r.startFlusher();a.rebuild(a.manifests());const m=[],k=[];for(const t of o){const i=await M();await i.listen({port:t,host:"0.0.0.0"});const p=i.server.address(),_=typeof p=="object"&&p?p.port:t;k.push(_),console.log(`[gateway] listening on ${e.tls?"https":"http"}://0.0.0.0:${_}`),m.push(i)}e.healthChecker?.start();let v=!1;return{ports:k,routes:n(()=>a.routes,"routes"),status:r.buildStatusSnapshot,stop:n(async()=>{v||(v=!0,clearInterval(S),a.stop(),e.healthChecker?.stop(),e.rateLimiter?.stop(),e.metrics?.stop(),r.emit("info","gateway down","gateway-down",{kind:"gateway-down"}),await Promise.all(m.map(t=>t.close())))},"stop")}}l(startProdGateway,"startProdGateway"),n(startProdGateway,"startProdGateway");
|
|
@@ -1,32 +1 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.stampIdentity = exports.stripTrust = exports.HOP_BY_HOP = exports.TRUST_HEADERS = void 0;
|
|
4
|
-
// Drop any client-supplied copy at ingress so identity can only originate from a verified token.
|
|
5
|
-
exports.TRUST_HEADERS = ['x-internal-token', 'x-api-key-id', 'x-user-id', 'x-tenant-id', 'x-user-roles', 'x-user-permissions'];
|
|
6
|
-
// Also drop client-supplied hop-by-hop control headers: a `Connection: x-internal-token`
|
|
7
|
-
// would otherwise make undici strip the gateway's OWN injected trust headers before the
|
|
8
|
-
// upstream (CVE-2026-33805). The proxy manages its own upstream connection semantics.
|
|
9
|
-
exports.HOP_BY_HOP = ['connection', 'proxy-connection', 'keep-alive'];
|
|
10
|
-
const stripTrust = (req) => {
|
|
11
|
-
for (const h of exports.TRUST_HEADERS)
|
|
12
|
-
delete req.headers[h];
|
|
13
|
-
for (const h of exports.HOP_BY_HOP)
|
|
14
|
-
delete req.headers[h];
|
|
15
|
-
};
|
|
16
|
-
exports.stripTrust = stripTrust;
|
|
17
|
-
/** Stamp the verified identity onto the forwarded request — tenant from the `tid` CLAIM, never a client header. */
|
|
18
|
-
const stampIdentity = (req, claims) => {
|
|
19
|
-
// Only stamp SCALAR claims — an object/array sub/tid must never coerce to
|
|
20
|
-
// "[object Object]" / a CSV tenant header (claim type-confusion).
|
|
21
|
-
const set = (k, v) => { if ((typeof v === 'string' && v !== '') || typeof v === 'number')
|
|
22
|
-
req.headers[k] = String(v); };
|
|
23
|
-
set('x-user-id', claims['sub']);
|
|
24
|
-
set('x-tenant-id', claims['tid']);
|
|
25
|
-
const roles = claims['roles'];
|
|
26
|
-
if (Array.isArray(roles) && roles.length)
|
|
27
|
-
req.headers['x-user-roles'] = roles.map(String).join(',');
|
|
28
|
-
const perms = claims['perms'];
|
|
29
|
-
if (Array.isArray(perms) && perms.length)
|
|
30
|
-
req.headers['x-user-permissions'] = perms.map(String).join(',');
|
|
31
|
-
};
|
|
32
|
-
exports.stampIdentity = stampIdentity;
|
|
1
|
+
"use strict";var d=Object.defineProperty;var p=(t,e)=>d(t,"name",{value:e,configurable:!0});var x=Object.defineProperty,r=p((t,e)=>x(t,"name",{value:e,configurable:!0}),"r");Object.defineProperty(exports,"__esModule",{value:!0}),exports.stampIdentity=exports.stripTrust=exports.HOP_BY_HOP=exports.TRUST_HEADERS=void 0,exports.TRUST_HEADERS=["x-internal-token","x-api-key-id","x-user-id","x-tenant-id","x-user-roles","x-user-permissions"],exports.HOP_BY_HOP=["connection","proxy-connection","keep-alive"];const stripTrust=r(t=>{for(const e of exports.TRUST_HEADERS)delete t.headers[e];for(const e of exports.HOP_BY_HOP)delete t.headers[e]},"stripTrust");exports.stripTrust=stripTrust;const stampIdentity=r((t,e)=>{const i=r((a,s)=>{(typeof s=="string"&&s!==""||typeof s=="number")&&(t.headers[a]=String(s))},"set");i("x-user-id",e.sub),i("x-tenant-id",e.tid);const o=e.roles;Array.isArray(o)&&o.length&&(t.headers["x-user-roles"]=o.map(String).join(","));const n=e.perms;Array.isArray(n)&&n.length&&(t.headers["x-user-permissions"]=n.map(String).join(","))},"stampIdentity");exports.stampIdentity=stampIdentity;
|
|
@@ -1,2 +1 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:!0});
|
|
@@ -1,116 +1,4 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Raw WebSocket upgrade proxy (same trust model; net-level pipe). The upgrade
|
|
7
|
-
* request is rebuilt from `req.rawHeaders` verbatim — minus the gateway-owned
|
|
8
|
-
* trust headers + the client's hop-by-hop control — plus the validated caller +
|
|
9
|
-
* target token + verified identity. Reuses the shared auth + upstream layers.
|
|
10
|
-
*/
|
|
11
|
-
const net = tslib_1.__importStar(require("node:net"));
|
|
12
|
-
const manifest_1 = require("../manifest");
|
|
13
|
-
const edge_1 = require("./edge");
|
|
14
|
-
const trust_1 = require("./trust");
|
|
15
|
-
function createUpgrade(deps) {
|
|
16
|
-
const { rt, auth, upstream, hooks } = deps;
|
|
17
|
-
const run = async (req, socket, head) => {
|
|
18
|
-
(0, trust_1.stripTrust)(req);
|
|
19
|
-
const host = (req.headers.host || '').split(':')[0];
|
|
20
|
-
const cpath = (0, edge_1.canonicalPath)(req.url ?? '/');
|
|
21
|
-
if (cpath === undefined) {
|
|
22
|
-
socket.destroy();
|
|
23
|
-
return;
|
|
24
|
-
} // reject path-canonicalization attacks
|
|
25
|
-
const route = (0, manifest_1.matchRoute)(rt.table.routes, host, req.url ?? '/');
|
|
26
|
-
if (!route) {
|
|
27
|
-
socket.destroy();
|
|
28
|
-
return;
|
|
29
|
-
}
|
|
30
|
-
const ctx = hooks.any ? hooks.upgradeContext(req, socket, undefined) : undefined;
|
|
31
|
-
if (ctx)
|
|
32
|
-
ctx.route = route;
|
|
33
|
-
const claims = route.internal ? undefined : rt.opts.identify?.(req);
|
|
34
|
-
let caller;
|
|
35
|
-
if (route.internal) {
|
|
36
|
-
const v = auth.internal(req, route, undefined);
|
|
37
|
-
if (!v.ok) {
|
|
38
|
-
socket.destroy();
|
|
39
|
-
return;
|
|
40
|
-
}
|
|
41
|
-
caller = v.caller;
|
|
42
|
-
if (ctx)
|
|
43
|
-
ctx.caller = caller;
|
|
44
|
-
}
|
|
45
|
-
else {
|
|
46
|
-
// Same fail-closed ABAC + identical attribute bag as the HTTP path (no divergence).
|
|
47
|
-
const v = auth.public(req, route, cpath, claims, req.socket.remoteAddress ?? '', undefined);
|
|
48
|
-
if (!v.ok) {
|
|
49
|
-
socket.destroy();
|
|
50
|
-
return;
|
|
51
|
-
}
|
|
52
|
-
if (ctx)
|
|
53
|
-
ctx.claims = claims;
|
|
54
|
-
if (ctx && hooks.has('afterAuth')) {
|
|
55
|
-
await hooks.run('afterAuth', ctx);
|
|
56
|
-
if (ctx.responded())
|
|
57
|
-
return;
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
if (route.prependPrefix) {
|
|
61
|
-
const u = req.url ?? '/';
|
|
62
|
-
const pp = route.prependPrefix;
|
|
63
|
-
if (u !== pp && !u.startsWith(pp + '/'))
|
|
64
|
-
req.url = pp + u;
|
|
65
|
-
} // segment-boundary
|
|
66
|
-
if (ctx && hooks.has('onUpgrade')) {
|
|
67
|
-
await hooks.run('onUpgrade', ctx);
|
|
68
|
-
if (ctx.responded())
|
|
69
|
-
return;
|
|
70
|
-
}
|
|
71
|
-
const pinned = (0, edge_1.hdr)(req.headers[rt.versionHeader]);
|
|
72
|
-
const up = await upstream.resolveUpstream(route, undefined, pinned);
|
|
73
|
-
if (!up || !('url' in up)) {
|
|
74
|
-
socket.destroy();
|
|
75
|
-
return;
|
|
76
|
-
}
|
|
77
|
-
const u = new URL(up.url);
|
|
78
|
-
const upstreamSock = net.connect(Number(u.port) || (u.protocol === 'https:' ? 443 : 80), u.hostname, () => {
|
|
79
|
-
// rebuild the upgrade request from rawHeaders (verbatim), minus the trust headers we own (incl.
|
|
80
|
-
// client identity) + the client's hop-by-hop control (so a `Connection: x-internal-token` can't
|
|
81
|
-
// strip the gateway's injected headers downstream), plus the validated caller + token + identity.
|
|
82
|
-
const strip = new Set(['x-internal-token', 'x-api-key-id', 'x-api-key', 'x-user-id', 'x-tenant-id', 'x-user-roles', 'x-user-permissions', 'connection', 'proxy-connection', 'keep-alive']);
|
|
83
|
-
const lines = [`${req.method} ${req.url} HTTP/1.1`, 'Connection: Upgrade']; // gateway-owned, clean upgrade
|
|
84
|
-
for (let i = 0; i < req.rawHeaders.length; i += 2) {
|
|
85
|
-
const k = req.rawHeaders[i];
|
|
86
|
-
if (!strip.has(k.toLowerCase()))
|
|
87
|
-
lines.push(`${k}: ${req.rawHeaders[i + 1]}`);
|
|
88
|
-
}
|
|
89
|
-
if (caller)
|
|
90
|
-
lines.push(`x-api-key-id: ${caller}`);
|
|
91
|
-
const token = rt.opts.serviceKeys?.[route.service];
|
|
92
|
-
if (token)
|
|
93
|
-
lines.push(`x-internal-token: ${token}`);
|
|
94
|
-
if (claims) { // stamp the verified identity (mirrors the HTTP path's stampIdentity)
|
|
95
|
-
if (claims['sub'])
|
|
96
|
-
lines.push(`x-user-id: ${String(claims['sub'])}`);
|
|
97
|
-
if (claims['tid'])
|
|
98
|
-
lines.push(`x-tenant-id: ${String(claims['tid'])}`);
|
|
99
|
-
const roles = claims['roles'];
|
|
100
|
-
if (Array.isArray(roles) && roles.length)
|
|
101
|
-
lines.push(`x-user-roles: ${roles.map(String).join(',')}`);
|
|
102
|
-
const perms = claims['perms'];
|
|
103
|
-
if (Array.isArray(perms) && perms.length)
|
|
104
|
-
lines.push(`x-user-permissions: ${perms.map(String).join(',')}`);
|
|
105
|
-
}
|
|
106
|
-
upstreamSock.write(lines.join('\r\n') + '\r\n\r\n');
|
|
107
|
-
if (head?.length)
|
|
108
|
-
upstreamSock.write(head);
|
|
109
|
-
socket.pipe(upstreamSock);
|
|
110
|
-
upstreamSock.pipe(socket);
|
|
111
|
-
});
|
|
112
|
-
upstreamSock.on('error', () => socket.destroy());
|
|
113
|
-
socket.on('error', () => upstreamSock.destroy());
|
|
114
|
-
};
|
|
115
|
-
return (req, socket, head) => { void run(req, socket, head); };
|
|
116
|
-
}
|
|
1
|
+
"use strict";var U=Object.defineProperty;var g=(l,i)=>U(l,"name",{value:i,configurable:!0});var S=Object.defineProperty,m=g((l,i)=>S(l,"name",{value:i,configurable:!0}),"m");Object.defineProperty(exports,"__esModule",{value:!0}),exports.createUpgrade=createUpgrade;const tslib_1=require("tslib"),net=tslib_1.__importStar(require("node:net")),manifest_1=require("../manifest"),edge_1=require("./edge"),trust_1=require("./trust");function createUpgrade(l){const{rt:i,auth:v,upstream:$,hooks:d}=l,k=m(async(e,r,h)=>{(0,trust_1.stripTrust)(e);const _=(e.headers.host||"").split(":")[0],w=(0,edge_1.canonicalPath)(e.url??"/");if(w===void 0){r.destroy();return}const s=(0,manifest_1.matchRoute)(i.table.routes,_,e.url??"/");if(!s){r.destroy();return}const t=d.any?d.upgradeContext(e,r,void 0):void 0;t&&(t.route=s);const o=s.internal?void 0:i.opts.identify?.(e);let f;if(s.internal){const a=v.internal(e,s,void 0);if(!a.ok){r.destroy();return}f=a.caller,t&&(t.caller=f)}else{if(!v.public(e,s,w,o,e.socket.remoteAddress??"",void 0).ok){r.destroy();return}if(t&&(t.claims=o),t&&d.has("afterAuth")&&(await d.run("afterAuth",t),t.responded()))return}if(s.prependPrefix){const a=e.url??"/",n=s.prependPrefix;a!==n&&!a.startsWith(n+"/")&&(e.url=n+a)}if(t&&d.has("onUpgrade")&&(await d.run("onUpgrade",t),t.responded()))return;const A=(0,edge_1.hdr)(e.headers[i.versionHeader]),y=await $.resolveUpstream(s,void 0,A);if(!y||!("url"in y)){r.destroy();return}const x=new URL(y.url),p=net.connect(Number(x.port)||(x.protocol==="https:"?443:80),x.hostname,()=>{const a=new Set(["x-internal-token","x-api-key-id","x-api-key","x-user-id","x-tenant-id","x-user-roles","x-user-permissions","connection","proxy-connection","keep-alive"]),n=[`${e.method} ${e.url} HTTP/1.1`,"Connection: Upgrade"];for(let u=0;u<e.rawHeaders.length;u+=2){const c=e.rawHeaders[u];a.has(c.toLowerCase())||n.push(`${c}: ${e.rawHeaders[u+1]}`)}f&&n.push(`x-api-key-id: ${f}`);const b=i.opts.serviceKeys?.[s.service];if(b&&n.push(`x-internal-token: ${b}`),o){o.sub&&n.push(`x-user-id: ${String(o.sub)}`),o.tid&&n.push(`x-tenant-id: ${String(o.tid)}`);const u=o.roles;Array.isArray(u)&&u.length&&n.push(`x-user-roles: ${u.map(String).join(",")}`);const c=o.perms;Array.isArray(c)&&c.length&&n.push(`x-user-permissions: ${c.map(String).join(",")}`)}p.write(n.join(`\r
|
|
2
|
+
`)+`\r
|
|
3
|
+
\r
|
|
4
|
+
`),h?.length&&p.write(h),r.pipe(p),p.pipe(r)});p.on("error",()=>r.destroy()),r.on("error",()=>p.destroy())},"run");return(e,r,h)=>{k(e,r,h)}}g(createUpgrade,"createUpgrade"),m(createUpgrade,"createUpgrade");
|
|
@@ -1,21 +1 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.createUpstream = createUpstream;
|
|
4
|
-
function createUpstream(opts, obs) {
|
|
5
|
-
const resolveUpstream = async (route, trace, pinned) => {
|
|
6
|
-
if (route.upstream)
|
|
7
|
-
return { url: route.upstream };
|
|
8
|
-
const opt = {
|
|
9
|
-
...(pinned ? { version: pinned } : {}),
|
|
10
|
-
...(opts.healthChecker ? { isHealthy: (u) => opts.healthChecker.isHealthy(u) } : {}),
|
|
11
|
-
};
|
|
12
|
-
let up = opts.pods?.pick(route.service, opt);
|
|
13
|
-
if (!up && opts.pods?.ensureUp) {
|
|
14
|
-
obs.step(trace, 'ensure-up', { project: route.service });
|
|
15
|
-
if (await opts.pods.ensureUp(route.service))
|
|
16
|
-
up = opts.pods.pick(route.service, opt);
|
|
17
|
-
}
|
|
18
|
-
return up;
|
|
19
|
-
};
|
|
20
|
-
return { resolveUpstream };
|
|
21
|
-
}
|
|
1
|
+
"use strict";var l=Object.defineProperty;var p=(e,t)=>l(e,"name",{value:t,configurable:!0});var u=Object.defineProperty,s=p((e,t)=>u(e,"name",{value:t,configurable:!0}),"s");Object.defineProperty(exports,"__esModule",{value:!0}),exports.createUpstream=createUpstream;function createUpstream(e,t){return{resolveUpstream:s(async(r,o,c)=>{if(r.upstream)return{url:r.upstream};const i={...c?{version:c}:{},...e.healthChecker?{isHealthy:s(n=>e.healthChecker.isHealthy(n),"isHealthy")}:{}};let a=e.pods?.pick(r.service,i);return!a&&e.pods?.ensureUp&&(t.step(o,"ensure-up",{project:r.service}),await e.pods.ensureUp(r.service)&&(a=e.pods.pick(r.service,i))),a},"resolveUpstream")}}p(createUpstream,"createUpstream"),s(createUpstream,"createUpstream");
|