@profullstack/fleet 0.1.0 → 0.1.1
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/README.md +1 -1
- package/observe.d.ts +1 -0
- package/package.json +1 -1
- package/src/observe.js +12 -0
package/README.md
CHANGED
|
@@ -60,7 +60,7 @@ matchAgents(inv, threatcrushServers); // { devices: [{ device, agents: [...] }
|
|
|
60
60
|
|
|
61
61
|
### Observation rules
|
|
62
62
|
|
|
63
|
-
Only what anyone could see without the device's credentials, and only for devices you run. Private addresses (RFC 1918, CGNAT, link-local, ULA) are never probed. Client profiles (desktop, phone, tablet) are not network-probed.
|
|
63
|
+
Only what anyone could see without the device's credentials, and only for devices you run. A device the observer runs on is marked `from_self` and not port-scanned: from inside, loopback skips the firewall. Observe each box from another one. Private addresses (RFC 1918, CGNAT, link-local, ULA) are never probed. Client profiles (desktop, phone, tablet) are not network-probed.
|
|
64
64
|
|
|
65
65
|
## License
|
|
66
66
|
|
package/observe.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Device, Inventory } from './index.js';
|
|
2
2
|
export const PORTS: number[];
|
|
3
3
|
export const WELLKNOWN: string[];
|
|
4
|
+
export function localAddresses(): Set<string>;
|
|
4
5
|
export function observe(device: Device, opts?: { timeoutMs?: number; ports?: number[]; now?: () => Date }): Promise<Record<string, unknown> & { at: string }>;
|
|
5
6
|
export function observeInventory(inv: Inventory, opts?: { concurrency?: number; timeoutMs?: number; ports?: number[] }): Promise<Inventory>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@profullstack/fleet",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Read an OpenDevice inventory (servers, desktops, phones, tablets), add up what the fleet costs a month, observe each box's public metadata, and export the costs for CoinPay. Used by ThreatCrush and w3bs.org/fleet.",
|
|
6
6
|
"keywords": ["fleet", "opendevice", "openserver", "inventory", "hosting", "cost", "coinpay", "threatcrush", "w3bs"],
|
package/src/observe.js
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
// Only observe devices you run. Node-only.
|
|
8
8
|
import { promises as dns } from 'node:dns';
|
|
9
9
|
import net from 'node:net';
|
|
10
|
+
import { networkInterfaces } from 'node:os';
|
|
10
11
|
import tls from 'node:tls';
|
|
11
12
|
|
|
12
13
|
export const PORTS = [22, 25, 53, 80, 443, 465, 587, 993, 2202, 3000, 5432, 6379, 8080, 8443];
|
|
@@ -26,6 +27,13 @@ function timeout(ms) {
|
|
|
26
27
|
|
|
27
28
|
const race = (p, ms) => Promise.race([p, timeout(ms)]);
|
|
28
29
|
|
|
30
|
+
// Addresses of the machine running the observer. Probing one of these goes over
|
|
31
|
+
// loopback and skips the firewall, so open ports would be reported that the
|
|
32
|
+
// internet cannot reach.
|
|
33
|
+
export function localAddresses() {
|
|
34
|
+
return new Set(Object.values(networkInterfaces()).flat().filter(Boolean).map((i) => i.address.toLowerCase()));
|
|
35
|
+
}
|
|
36
|
+
|
|
29
37
|
function isPrivate(ip) {
|
|
30
38
|
if (net.isIPv4(ip)) {
|
|
31
39
|
const [a, b] = ip.split('.').map(Number);
|
|
@@ -128,10 +136,14 @@ export async function observe(device, { timeoutMs = 4000, ports = PORTS, now = (
|
|
|
128
136
|
if (!publicIps.length) return out;
|
|
129
137
|
const target = [...a].find((ip) => !isPrivate(ip)) ?? publicIps[0];
|
|
130
138
|
|
|
139
|
+
const self = publicIps.some((ip) => localAddresses().has(ip.toLowerCase()));
|
|
140
|
+
if (self) out.from_self = true;
|
|
141
|
+
|
|
131
142
|
out.ptr = [];
|
|
132
143
|
for (const ip of publicIps) for (const n of (await settle(race(dns.reverse(ip), timeoutMs), [])) ?? []) out.ptr.includes(n) || out.ptr.push(n);
|
|
133
144
|
out.asn = await settle(race(asnOf(target), timeoutMs));
|
|
134
145
|
|
|
146
|
+
if (self) return out; // ports, banners and TLS would be seen from inside; observe from another box
|
|
135
147
|
const open = await Promise.all(ports.map((p) => portOpen(target, p, timeoutMs).then((ok) => (ok ? p : null))));
|
|
136
148
|
out.ports = open.filter((p) => p != null);
|
|
137
149
|
|