@neat.is/core 0.3.7 → 0.3.8
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/dist/{chunk-75IBA4UL.js → chunk-7TYESDAI.js} +69 -2
- package/dist/chunk-7TYESDAI.js.map +1 -0
- package/dist/{chunk-ZU2RQRCN.js → chunk-CZ3T6TE2.js} +251 -245
- package/dist/{chunk-ZU2RQRCN.js.map → chunk-CZ3T6TE2.js.map} +1 -1
- package/dist/{chunk-EDHOCFOG.js → chunk-LQ3JFBTX.js} +15 -5
- package/dist/chunk-LQ3JFBTX.js.map +1 -0
- package/dist/{chunk-CY67YKNO.js → chunk-V4TU7OKZ.js} +16 -2
- package/dist/chunk-V4TU7OKZ.js.map +1 -0
- package/dist/cli.cjs +1185 -405
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.d.cts +5 -0
- package/dist/cli.d.ts +5 -0
- package/dist/cli.js +879 -156
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +98 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +6 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +4 -4
- package/dist/neatd.cjs +111 -3
- package/dist/neatd.cjs.map +1 -1
- package/dist/neatd.js +14 -4
- package/dist/neatd.js.map +1 -1
- package/dist/{otel-grpc-PM4SWPZE.js → otel-grpc-S3AENOZ6.js} +3 -3
- package/dist/server.cjs +110 -6
- package/dist/server.cjs.map +1 -1
- package/dist/server.js +25 -7
- package/dist/server.js.map +1 -1
- package/package.json +2 -2
- package/dist/chunk-75IBA4UL.js.map +0 -1
- package/dist/chunk-CY67YKNO.js.map +0 -1
- package/dist/chunk-EDHOCFOG.js.map +0 -1
- /package/dist/{otel-grpc-PM4SWPZE.js.map → otel-grpc-S3AENOZ6.js.map} +0 -0
package/dist/index.d.cts
CHANGED
|
@@ -114,6 +114,8 @@ interface BuildApiOptions {
|
|
|
114
114
|
errorsPath?: string;
|
|
115
115
|
staleEventsPath?: string;
|
|
116
116
|
searchIndex?: SearchIndex;
|
|
117
|
+
authToken?: string;
|
|
118
|
+
trustProxy?: boolean;
|
|
117
119
|
}
|
|
118
120
|
declare function buildApi(opts: BuildApiOptions): Promise<FastifyInstance>;
|
|
119
121
|
|
|
@@ -145,6 +147,8 @@ interface BuildOtelReceiverOptions {
|
|
|
145
147
|
onSpan: SpanHandler;
|
|
146
148
|
onErrorSpanSync?: (span: ParsedSpan) => Promise<void>;
|
|
147
149
|
bodyLimit?: number;
|
|
150
|
+
authToken?: string;
|
|
151
|
+
trustProxy?: boolean;
|
|
148
152
|
}
|
|
149
153
|
interface OtlpKeyValue {
|
|
150
154
|
key: string;
|
|
@@ -200,6 +204,8 @@ declare function logSpanHandler(span: ParsedSpan): void;
|
|
|
200
204
|
|
|
201
205
|
interface BuildOtelGrpcReceiverOptions {
|
|
202
206
|
onSpan: SpanHandler;
|
|
207
|
+
authToken?: string;
|
|
208
|
+
trustProxy?: boolean;
|
|
203
209
|
}
|
|
204
210
|
interface OtelGrpcReceiver {
|
|
205
211
|
address: string;
|
package/dist/index.d.ts
CHANGED
|
@@ -114,6 +114,8 @@ interface BuildApiOptions {
|
|
|
114
114
|
errorsPath?: string;
|
|
115
115
|
staleEventsPath?: string;
|
|
116
116
|
searchIndex?: SearchIndex;
|
|
117
|
+
authToken?: string;
|
|
118
|
+
trustProxy?: boolean;
|
|
117
119
|
}
|
|
118
120
|
declare function buildApi(opts: BuildApiOptions): Promise<FastifyInstance>;
|
|
119
121
|
|
|
@@ -145,6 +147,8 @@ interface BuildOtelReceiverOptions {
|
|
|
145
147
|
onSpan: SpanHandler;
|
|
146
148
|
onErrorSpanSync?: (span: ParsedSpan) => Promise<void>;
|
|
147
149
|
bodyLimit?: number;
|
|
150
|
+
authToken?: string;
|
|
151
|
+
trustProxy?: boolean;
|
|
148
152
|
}
|
|
149
153
|
interface OtlpKeyValue {
|
|
150
154
|
key: string;
|
|
@@ -200,6 +204,8 @@ declare function logSpanHandler(span: ParsedSpan): void;
|
|
|
200
204
|
|
|
201
205
|
interface BuildOtelGrpcReceiverOptions {
|
|
202
206
|
onSpan: SpanHandler;
|
|
207
|
+
authToken?: string;
|
|
208
|
+
trustProxy?: boolean;
|
|
203
209
|
}
|
|
204
210
|
interface OtelGrpcReceiver {
|
|
205
211
|
address: string;
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
routeSpanToProject,
|
|
3
3
|
startDaemon
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-LQ3JFBTX.js";
|
|
5
5
|
import {
|
|
6
6
|
ProjectNameCollisionError,
|
|
7
7
|
addProject,
|
|
@@ -37,15 +37,15 @@ import {
|
|
|
37
37
|
thresholdForEdgeType,
|
|
38
38
|
touchLastSeen,
|
|
39
39
|
writeAtomically
|
|
40
|
-
} from "./chunk-
|
|
40
|
+
} from "./chunk-CZ3T6TE2.js";
|
|
41
41
|
import {
|
|
42
42
|
startOtelGrpcReceiver
|
|
43
|
-
} from "./chunk-
|
|
43
|
+
} from "./chunk-V4TU7OKZ.js";
|
|
44
44
|
import {
|
|
45
45
|
buildOtelReceiver,
|
|
46
46
|
logSpanHandler,
|
|
47
47
|
parseOtlpRequest
|
|
48
|
-
} from "./chunk-
|
|
48
|
+
} from "./chunk-7TYESDAI.js";
|
|
49
49
|
export {
|
|
50
50
|
ProjectNameCollisionError,
|
|
51
51
|
addProject,
|
package/dist/neatd.cjs
CHANGED
|
@@ -40,6 +40,75 @@ var init_cjs_shims = __esm({
|
|
|
40
40
|
}
|
|
41
41
|
});
|
|
42
42
|
|
|
43
|
+
// src/auth.ts
|
|
44
|
+
function isLoopbackHost(host) {
|
|
45
|
+
if (!host) return false;
|
|
46
|
+
return LOOPBACK_HOSTS.has(host);
|
|
47
|
+
}
|
|
48
|
+
function assertBindAuthority(host, token) {
|
|
49
|
+
if (token && token.length > 0) return;
|
|
50
|
+
if (isLoopbackHost(host)) return;
|
|
51
|
+
throw new BindAuthorityError(host);
|
|
52
|
+
}
|
|
53
|
+
function mountBearerAuth(app, opts) {
|
|
54
|
+
if (!opts.token || opts.token.length === 0) return;
|
|
55
|
+
if (opts.trustProxy) return;
|
|
56
|
+
const expected = Buffer.from(opts.token, "utf8");
|
|
57
|
+
const suffixes = [...DEFAULT_UNAUTH_SUFFIXES, ...opts.extraUnauthenticatedSuffixes ?? []];
|
|
58
|
+
app.addHook("preHandler", (req2, reply, done) => {
|
|
59
|
+
const path39 = (req2.url.split("?")[0] ?? "").replace(/\/+$/, "");
|
|
60
|
+
for (const suffix of suffixes) {
|
|
61
|
+
if (path39 === suffix || path39.endsWith(suffix)) {
|
|
62
|
+
done();
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
const header = req2.headers.authorization;
|
|
67
|
+
if (typeof header !== "string" || !header.startsWith("Bearer ")) {
|
|
68
|
+
void reply.code(401).send({ error: "unauthorized" });
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
const provided = Buffer.from(header.slice("Bearer ".length).trim(), "utf8");
|
|
72
|
+
if (provided.length !== expected.length || !(0, import_node_crypto.timingSafeEqual)(provided, expected)) {
|
|
73
|
+
void reply.code(401).send({ error: "unauthorized" });
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
done();
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
function readAuthEnv(env = process.env) {
|
|
80
|
+
const t = env.NEAT_AUTH_TOKEN;
|
|
81
|
+
const ot = env.NEAT_OTEL_TOKEN;
|
|
82
|
+
return {
|
|
83
|
+
authToken: t && t.length > 0 ? t : void 0,
|
|
84
|
+
otelToken: ot && ot.length > 0 ? ot : t && t.length > 0 ? t : void 0,
|
|
85
|
+
trustProxy: env.NEAT_AUTH_PROXY === "true"
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
var import_node_crypto, LOOPBACK_HOSTS, BindAuthorityError, DEFAULT_UNAUTH_SUFFIXES;
|
|
89
|
+
var init_auth = __esm({
|
|
90
|
+
"src/auth.ts"() {
|
|
91
|
+
"use strict";
|
|
92
|
+
init_cjs_shims();
|
|
93
|
+
import_node_crypto = require("crypto");
|
|
94
|
+
LOOPBACK_HOSTS = /* @__PURE__ */ new Set([
|
|
95
|
+
"127.0.0.1",
|
|
96
|
+
"localhost",
|
|
97
|
+
"::1",
|
|
98
|
+
"::ffff:127.0.0.1"
|
|
99
|
+
]);
|
|
100
|
+
BindAuthorityError = class extends Error {
|
|
101
|
+
constructor(host) {
|
|
102
|
+
super(
|
|
103
|
+
`NEAT refuses to bind on a public interface without \`NEAT_AUTH_TOKEN\` set (host="${host}"). Set the token or bind to loopback only.`
|
|
104
|
+
);
|
|
105
|
+
this.name = "BindAuthorityError";
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
DEFAULT_UNAUTH_SUFFIXES = ["/health", "/healthz", "/readyz"];
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
|
|
43
112
|
// src/otel-grpc.ts
|
|
44
113
|
var otel_grpc_exports = {};
|
|
45
114
|
__export(otel_grpc_exports, {
|
|
@@ -122,8 +191,21 @@ function loadTraceService() {
|
|
|
122
191
|
async function startOtelGrpcReceiver(opts) {
|
|
123
192
|
const server = new grpc.Server();
|
|
124
193
|
const service = loadTraceService();
|
|
194
|
+
const requiresAuth = !opts.trustProxy && !!opts.authToken && opts.authToken.length > 0;
|
|
195
|
+
const expectedHeader = requiresAuth ? `Bearer ${opts.authToken}` : "";
|
|
125
196
|
server.addService(service, {
|
|
126
197
|
Export: (call, callback) => {
|
|
198
|
+
if (requiresAuth) {
|
|
199
|
+
const meta = call.metadata.get("authorization");
|
|
200
|
+
const got = meta.length > 0 ? String(meta[0]) : "";
|
|
201
|
+
const a = Buffer.from(got, "utf8");
|
|
202
|
+
const b = Buffer.from(expectedHeader, "utf8");
|
|
203
|
+
const ok = a.length === b.length && (0, import_node_crypto2.timingSafeEqual)(a, b);
|
|
204
|
+
if (!ok) {
|
|
205
|
+
callback({ code: grpc.status.UNAUTHENTICATED, message: "unauthorized" });
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
127
209
|
void (async () => {
|
|
128
210
|
try {
|
|
129
211
|
const reshaped = reshapeGrpcRequest(call.request ?? {});
|
|
@@ -156,13 +238,14 @@ async function startOtelGrpcReceiver(opts) {
|
|
|
156
238
|
})
|
|
157
239
|
};
|
|
158
240
|
}
|
|
159
|
-
var import_node_url, import_node_path34, grpc, protoLoader;
|
|
241
|
+
var import_node_url, import_node_path34, import_node_crypto2, grpc, protoLoader;
|
|
160
242
|
var init_otel_grpc = __esm({
|
|
161
243
|
"src/otel-grpc.ts"() {
|
|
162
244
|
"use strict";
|
|
163
245
|
init_cjs_shims();
|
|
164
246
|
import_node_url = require("url");
|
|
165
247
|
import_node_path34 = __toESM(require("path"), 1);
|
|
248
|
+
import_node_crypto2 = require("crypto");
|
|
166
249
|
grpc = __toESM(require("@grpc/grpc-js"), 1);
|
|
167
250
|
protoLoader = __toESM(require("@grpc/proto-loader"), 1);
|
|
168
251
|
init_otel();
|
|
@@ -303,6 +386,7 @@ async function buildOtelReceiver(opts) {
|
|
|
303
386
|
logger: false,
|
|
304
387
|
bodyLimit: opts.bodyLimit ?? 16 * 1024 * 1024
|
|
305
388
|
});
|
|
389
|
+
mountBearerAuth(app, { token: opts.authToken, trustProxy: opts.trustProxy });
|
|
306
390
|
const queue = [];
|
|
307
391
|
let draining = false;
|
|
308
392
|
let drainPromise = Promise.resolve();
|
|
@@ -390,6 +474,7 @@ var init_otel = __esm({
|
|
|
390
474
|
import_node_url2 = require("url");
|
|
391
475
|
import_fastify2 = __toESM(require("fastify"), 1);
|
|
392
476
|
import_protobufjs = __toESM(require("protobufjs"), 1);
|
|
477
|
+
init_auth();
|
|
393
478
|
exportTraceServiceRequestType = null;
|
|
394
479
|
exportTraceServiceResponseType = null;
|
|
395
480
|
cachedProtobufResponseBody = null;
|
|
@@ -4646,6 +4731,7 @@ data: ${JSON.stringify(envelope.payload)}
|
|
|
4646
4731
|
}
|
|
4647
4732
|
|
|
4648
4733
|
// src/api.ts
|
|
4734
|
+
init_auth();
|
|
4649
4735
|
function serializeGraph(graph) {
|
|
4650
4736
|
const nodes = [];
|
|
4651
4737
|
graph.forEachNode((_id, attrs) => {
|
|
@@ -5011,6 +5097,7 @@ function registerRoutes(scope, ctx) {
|
|
|
5011
5097
|
async function buildApi(opts) {
|
|
5012
5098
|
const app = (0, import_fastify.default)({ logger: false });
|
|
5013
5099
|
await app.register(import_cors.default, { origin: true });
|
|
5100
|
+
mountBearerAuth(app, { token: opts.authToken, trustProxy: opts.trustProxy });
|
|
5014
5101
|
const startedAt = opts.startedAt ?? Date.now();
|
|
5015
5102
|
const registry = buildLegacyRegistry(opts);
|
|
5016
5103
|
const legacyErrorsExplicit = !opts.projects && opts.errorsPath !== void 0;
|
|
@@ -5074,6 +5161,7 @@ async function buildApi(opts) {
|
|
|
5074
5161
|
|
|
5075
5162
|
// src/daemon.ts
|
|
5076
5163
|
init_otel();
|
|
5164
|
+
init_auth();
|
|
5077
5165
|
function neatHomeFor(opts) {
|
|
5078
5166
|
if (opts.neatHome && opts.neatHome.length > 0) return import_node_path36.default.resolve(opts.neatHome);
|
|
5079
5167
|
const env = process.env.NEAT_HOME;
|
|
@@ -5280,8 +5368,14 @@ async function startDaemon(opts = {}) {
|
|
|
5280
5368
|
const host = resolveHost(opts);
|
|
5281
5369
|
const restPort = resolveRestPort(opts);
|
|
5282
5370
|
const otlpPort = resolveOtlpPort(opts);
|
|
5371
|
+
const auth = readAuthEnv();
|
|
5372
|
+
assertBindAuthority(host, auth.authToken);
|
|
5283
5373
|
try {
|
|
5284
|
-
restApp = await buildApi({
|
|
5374
|
+
restApp = await buildApi({
|
|
5375
|
+
projects: registry,
|
|
5376
|
+
authToken: auth.authToken,
|
|
5377
|
+
trustProxy: auth.trustProxy
|
|
5378
|
+
});
|
|
5285
5379
|
restAddress = await restApp.listen({ port: restPort, host });
|
|
5286
5380
|
console.log(`neatd: REST listening on ${restAddress}`);
|
|
5287
5381
|
} catch (err) {
|
|
@@ -5318,6 +5412,8 @@ async function startDaemon(opts = {}) {
|
|
|
5318
5412
|
}
|
|
5319
5413
|
try {
|
|
5320
5414
|
otlpApp = await buildOtelReceiver({
|
|
5415
|
+
authToken: auth.otelToken,
|
|
5416
|
+
trustProxy: auth.trustProxy,
|
|
5321
5417
|
onSpan: async (span) => {
|
|
5322
5418
|
const slot = await resolveTargetSlot(span.service);
|
|
5323
5419
|
if (!slot) return;
|
|
@@ -5397,6 +5493,9 @@ async function startDaemon(opts = {}) {
|
|
|
5397
5493
|
return { slots, reload, stop, pidPath, restAddress, otlpAddress };
|
|
5398
5494
|
}
|
|
5399
5495
|
|
|
5496
|
+
// src/neatd.ts
|
|
5497
|
+
init_auth();
|
|
5498
|
+
|
|
5400
5499
|
// src/web-spawn.ts
|
|
5401
5500
|
init_cjs_shims();
|
|
5402
5501
|
var import_node_child_process = require("child_process");
|
|
@@ -5592,7 +5691,16 @@ function restPortFromEnv() {
|
|
|
5592
5691
|
return raw && raw.length > 0 ? Number.parseInt(raw, 10) : 8080;
|
|
5593
5692
|
}
|
|
5594
5693
|
async function cmdStart() {
|
|
5595
|
-
|
|
5694
|
+
let handle;
|
|
5695
|
+
try {
|
|
5696
|
+
handle = await startDaemon();
|
|
5697
|
+
} catch (err) {
|
|
5698
|
+
if (err instanceof BindAuthorityError) {
|
|
5699
|
+
console.error(`neatd: ${err.message}`);
|
|
5700
|
+
process.exit(1);
|
|
5701
|
+
}
|
|
5702
|
+
throw err;
|
|
5703
|
+
}
|
|
5596
5704
|
console.log(`neatd: started, PID ${process.pid}, ${handle.slots.size} project(s)`);
|
|
5597
5705
|
console.log(`neatd: registry at ${registryPath()}`);
|
|
5598
5706
|
if (handle.restAddress) console.log(`neatd: REST \u2192 ${handle.restAddress}`);
|