@push.rocks/smartnftables 2.6.0 → 4.0.0
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/changelog.md +33 -0
- package/dist_rust/smartnftables_linux_amd64_musl +0 -0
- package/dist_rust/smartnftables_linux_amd64_musl.tsrust-build.json +5 -5
- package/dist_rust/smartnftables_linux_arm64_musl +0 -0
- package/dist_rust/smartnftables_linux_arm64_musl.tsrust-build.json +5 -5
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/classes.manageddockerforwarding.js +3 -4
- package/dist_ts/classes.managednftables.d.ts +14 -2
- package/dist_ts/classes.managednftables.js +48 -8
- package/dist_ts/managed.egress.types.d.ts +25 -3
- package/dist_ts/managed.types.d.ts +14 -0
- package/package.json +7 -7
- package/readme.md +262 -64
- package/rust/src/docker.policy.rs +2 -1
- package/rust/src/egress.compile.rs +153 -76
- package/rust/src/egress.host.rs +24 -51
- package/rust/src/egress.hostgrant.rs +1 -1
- package/rust/src/egress.keys.rs +305 -0
- package/rust/src/egress.leased.rs +399 -0
- package/rust/src/egress.private.rs +197 -0
- package/rust/src/egress.published.rs +370 -0
- package/rust/src/egress.router.rs +61 -294
- package/rust/src/egress.rs +140 -19
- package/rust/src/egress_hostgrant_tests.rs +158 -20
- package/rust/src/egress_localport_tests.rs +11 -2
- package/rust/src/egress_publishedrange_tests.rs +812 -0
- package/rust/src/egress_routerequivalence_tests.rs +448 -0
- package/rust/src/egress_tests.rs +289 -230
- package/rust/src/egress_workloadgrant_tests.rs +21 -13
- package/rust/src/main.rs +57 -3
- package/rust/src/owner.rs +69 -14
- package/rust/src/owner_host_traffic_tests.rs +4 -0
- package/rust/src/owner_identity_tests.rs +166 -80
- package/rust/src/owner_publishedrange_traffic_tests.rs +306 -0
- package/rust/src/owner_router_traffic_tests.rs +2 -0
- package/rust/src/owner_scale_traffic_tests.rs +383 -0
- package/rust/src/owner_tests.rs +6 -0
- package/rust/src/policy.rs +30 -29
- package/rust/src/tests.rs +100 -0
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/classes.manageddockerforwarding.ts +2 -3
- package/ts/classes.managednftables.ts +46 -8
- package/ts/managed.egress.types.ts +25 -3
- package/ts/managed.types.ts +17 -0
|
@@ -1,9 +1,46 @@
|
|
|
1
1
|
import * as plugins from './plugins.js';
|
|
2
|
-
import type { IManagedNftOptions, IManagedNftPolicy, IManagedNftTransition, IAppliedManagedNftPolicy,
|
|
3
|
-
|
|
2
|
+
import type { IManagedNftOptions, IManagedNftPolicy, IManagedNftTransition, IAppliedManagedNftPolicy, IPreparedManagedNftPolicy,
|
|
3
|
+
IManagedNftStatus, IManagedNftTransitionRelease, TManagedNftCommands, TManagedNftPolicy, IManagedNftCapacity,
|
|
4
|
+
TManagedNftCapacityBound } from './managed.types.js';
|
|
4
5
|
|
|
6
|
+
/** `INVALID`: the input is outside the contract. `EXHAUSTED`: the policy exceeds a
|
|
7
|
+
* capacity bound of the atomic replacement budget, and a smaller policy may fit.
|
|
8
|
+
* Both carry the native refusal text in `reason`; `EXHAUSTED` also names its
|
|
9
|
+
* bound in `details`. Every other code, and the facade's own rejections, carry
|
|
10
|
+
* neither. */
|
|
5
11
|
export class ManagedNftablesError extends Error {
|
|
6
|
-
constructor(public readonly code: string
|
|
12
|
+
constructor(public readonly code: string, public readonly reason: string | null = null,
|
|
13
|
+
public readonly details: Readonly<IManagedNftCapacity> | null = null) {
|
|
14
|
+
super(reason === null ? `Managed nftables ${code}.` : `Managed nftables ${code}: ${reason}`);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const capacityBounds: ReadonlySet<string> = new Set<TManagedNftCapacityBound>(['ruleBytes', 'targetBytes', 'operations',
|
|
19
|
+
'endpoints', 'links', 'rules', 'grants', 'hostGrants', 'workloadGrants', 'publishedPorts', 'localTcpPortOwners',
|
|
20
|
+
'restoreRules', 'restoreBytes']);
|
|
21
|
+
|
|
22
|
+
function capacity(value: unknown): Readonly<IManagedNftCapacity> | null {
|
|
23
|
+
if (typeof value !== 'object' || value === null || Object.getPrototypeOf(value) !== Object.prototype
|
|
24
|
+
|| Object.keys(value).sort().join(',') !== 'actual,bound,limit') return null;
|
|
25
|
+
const { bound, limit, actual } = value as Record<string, unknown>;
|
|
26
|
+
if (typeof bound !== 'string' || !capacityBounds.has(bound) || typeof limit !== 'number' || typeof actual !== 'number'
|
|
27
|
+
|| !Number.isSafeInteger(limit) || !Number.isSafeInteger(actual) || limit < 0 || actual <= limit) return null;
|
|
28
|
+
return Object.freeze({ bound: bound as TManagedNftCapacityBound, limit, actual });
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Maps a failed native request. An `INVALID` or `EXHAUSTED` response keeps the
|
|
32
|
+
* bounded native refusal text and, for `EXHAUSTED`, its bound; a refusal outside
|
|
33
|
+
* that shape is `PROTOCOL`. Other responses keep only their code, and a failure
|
|
34
|
+
* without a native code is `UNAVAILABLE`. */
|
|
35
|
+
export function nativeError(error: unknown): ManagedNftablesError {
|
|
36
|
+
if (!(error instanceof plugins.smartrust.RustBridgeRequestError)) return new ManagedNftablesError('UNAVAILABLE');
|
|
37
|
+
const { responseErrorCode: code, message: reason, responseErrorData: data } = error;
|
|
38
|
+
if (typeof code !== 'string' || !/^[A-Z_]{1,64}$/.test(code)) return new ManagedNftablesError('UNAVAILABLE');
|
|
39
|
+
if (code !== 'INVALID' && code !== 'EXHAUSTED') return new ManagedNftablesError(code);
|
|
40
|
+
if (!/^[\x20-\x7e]{1,256}$/.test(reason)) return new ManagedNftablesError('PROTOCOL');
|
|
41
|
+
if (code === 'INVALID') return data === undefined ? new ManagedNftablesError(code, reason) : new ManagedNftablesError('PROTOCOL');
|
|
42
|
+
const details = capacity(data);
|
|
43
|
+
return details ? new ManagedNftablesError(code, reason, details) : new ManagedNftablesError('PROTOCOL');
|
|
7
44
|
}
|
|
8
45
|
|
|
9
46
|
/** Bounds of the private native IPC. A status carries up to three complete policies (applied,
|
|
@@ -240,16 +277,17 @@ export class ManagedNftables<TPolicy extends TManagedNftPolicy = IManagedNftPoli
|
|
|
240
277
|
#track<T>(work: Promise<T>, method: keyof TManagedNftCommands<TPolicy>): Promise<T> {
|
|
241
278
|
const owned = work.catch((error) => {
|
|
242
279
|
if (this.#state !== 'closing') this.#state = 'failed-owned';
|
|
243
|
-
const
|
|
244
|
-
// Only a native compiler rejection or explicit INVALID
|
|
245
|
-
// effect-free validation
|
|
280
|
+
const failure = nativeError(error);
|
|
281
|
+
// Only a native compiler rejection or explicit INVALID or EXHAUSTED
|
|
282
|
+
// response proves effect-free validation: the native owner refuses both
|
|
283
|
+
// before any kernel work. Other native errors can follow lost ownership
|
|
246
284
|
// or admitted kernel work. Recovery stays available; confidence is lost.
|
|
247
285
|
const validation = error instanceof plugins.smartrust.RustBridgeRequestError
|
|
248
286
|
&& error.code === 'ERR_RUST_BRIDGE_REQUEST_RUST_RESPONSE'
|
|
249
|
-
&& (method === 'preparePolicy' || code === 'INVALID');
|
|
287
|
+
&& (method === 'preparePolicy' || failure.code === 'INVALID' || failure.code === 'EXHAUSTED');
|
|
250
288
|
if (!validation && (this.#state !== 'closing'
|
|
251
289
|
|| ['reconcilePolicy', 'releasePolicy', 'releasePolicyTransition', 'detachPolicy'].includes(method))) this.#fail();
|
|
252
|
-
throw
|
|
290
|
+
throw failure;
|
|
253
291
|
});
|
|
254
292
|
this.#work = owned;
|
|
255
293
|
void owned.then(() => { this.#work = null; }, () => { this.#work = null; });
|
|
@@ -60,9 +60,14 @@ export interface IManagedNftEgressGenerationV2 extends IManagedNftHandoffAllocat
|
|
|
60
60
|
* `transitSourceAddress:transitPort`, this translates on to the workload. */
|
|
61
61
|
export interface IManagedNftRouterPublishedPortV2 {
|
|
62
62
|
protocol: 'tcp' | 'udp';
|
|
63
|
-
/** Published handoff port, 1–65535
|
|
64
|
-
* It is the `targetPort` of the matching host-transit publication. */
|
|
63
|
+
/** Published handoff port, 1–65535; published ports never overlap per protocol across the
|
|
64
|
+
* scope. It is the `targetPort` of the matching host-transit publication. */
|
|
65
65
|
transitPort: number;
|
|
66
|
+
/** Optional last port of a published range `transitPort..transitPortEnd`, greater than
|
|
67
|
+
* `transitPort`. A range keeps every port, so `endpointPort` must equal `transitPort`; like one
|
|
68
|
+
* port it is one interval set element per set, and its translation keeps the port. Absent for
|
|
69
|
+
* one port, which is its only canonical form. */
|
|
70
|
+
transitPortEnd?: number;
|
|
66
71
|
endpointPort: number;
|
|
67
72
|
/** Exact current workload endpoint address behind one veth endpoint of this
|
|
68
73
|
* scope. Never a router-local address, a gateway or a platform endpoint. */
|
|
@@ -70,6 +75,12 @@ export interface IManagedNftRouterPublishedPortV2 {
|
|
|
70
75
|
/** Exact leased transit source address of one current generation, present on
|
|
71
76
|
* `handoff`. No wildcard, secondary-address or route inference. */
|
|
72
77
|
transitSourceAddress: string;
|
|
78
|
+
/** Optional: the workload may open flows from `endpointAddress` and its published endpoint
|
|
79
|
+
* port(s) toward unprotected space, source-translated to `transitSourceAddress` and the
|
|
80
|
+
* published transit port (a range keeps the port). The protected union stays denied, and its
|
|
81
|
+
* endpoint ports may not overlap another publication's on the same address. Absent and
|
|
82
|
+
* `false` are the same canonical policy. */
|
|
83
|
+
symmetric?: boolean;
|
|
73
84
|
}
|
|
74
85
|
|
|
75
86
|
/** One exact host-origin flow: the node's own host network namespace dials one port of one local
|
|
@@ -136,13 +147,24 @@ export interface IManagedNftRouterEgressScopeV2 {
|
|
|
136
147
|
/** One inbound uplink publication, compiled inside the same host-transit generation. */
|
|
137
148
|
export interface IManagedNftPublishedPortV2 {
|
|
138
149
|
protocol: 'tcp' | 'udp';
|
|
139
|
-
/** Published uplink port, 1–65535
|
|
150
|
+
/** Published uplink port, 1–65535; published ports never overlap per protocol across the scope. */
|
|
140
151
|
hostPort: number;
|
|
152
|
+
/** Optional last port of a published range `hostPort..hostPortEnd`, greater than `hostPort`.
|
|
153
|
+
* A range keeps every port, so `targetPort` must equal `hostPort`; like one port it is one
|
|
154
|
+
* interval set element per set, and its translation keeps the port. Absent for one port, which
|
|
155
|
+
* is its only canonical form. */
|
|
156
|
+
hostPortEnd?: number;
|
|
141
157
|
targetPort: number;
|
|
142
158
|
/** Exact leased transit source address of one current allocation; it selects the handoff. */
|
|
143
159
|
targetAddress: string;
|
|
144
160
|
/** Exact current uplink address that publishes this port. No wildcard or route inference. */
|
|
145
161
|
hostIp: string;
|
|
162
|
+
/** Optional: flows from `targetAddress` and the published target port(s) may leave through the
|
|
163
|
+
* uplink toward unprotected space, source-translated to `hostIp` and the published uplink port
|
|
164
|
+
* (a range keeps the port). The protected union stays denied, its target ports may not fall in
|
|
165
|
+
* a leased range of `targetAddress` or overlap another publication's. Absent and `false` are
|
|
166
|
+
* the same canonical policy. */
|
|
167
|
+
symmetric?: boolean;
|
|
146
168
|
}
|
|
147
169
|
|
|
148
170
|
/** Shared-host capture barrier and explicit outer SNAT; Docker forwarding remains a separate owner. */
|
package/ts/managed.types.ts
CHANGED
|
@@ -93,6 +93,23 @@ export interface IManagedNftOptions {
|
|
|
93
93
|
|
|
94
94
|
export type TManagedNftPolicy = IManagedNftPolicy | IManagedNftPolicyV2;
|
|
95
95
|
|
|
96
|
+
/** Capacity bounds of the atomic replacement budget. The compiled target's
|
|
97
|
+
* `ruleBytes` (100,000), `targetBytes` (212,000) and `operations` (768), the input
|
|
98
|
+
* counts that exist to hold it (`endpoints`, `links`, `rules`, `grants`,
|
|
99
|
+
* `hostGrants`, `workloadGrants`, `publishedPorts`, `localTcpPortOwners`) and the
|
|
100
|
+
* Docker forwarding contribution's `restoreRules` (192) and `restoreBytes` (100,000). */
|
|
101
|
+
export type TManagedNftCapacityBound = 'ruleBytes' | 'targetBytes' | 'operations' | 'endpoints' | 'links' | 'rules'
|
|
102
|
+
| 'grants' | 'hostGrants' | 'workloadGrants' | 'publishedPorts' | 'localTcpPortOwners' | 'restoreRules' | 'restoreBytes';
|
|
103
|
+
|
|
104
|
+
/** The bound an `EXHAUSTED` refusal names. `actual` is always above `limit`. It is
|
|
105
|
+
* exact for input counts and the schema-v1 budget; the other compiled budgets stop
|
|
106
|
+
* at the first message or rule past the limit, so the policy needs at least `actual`. */
|
|
107
|
+
export interface IManagedNftCapacity {
|
|
108
|
+
bound: TManagedNftCapacityBound;
|
|
109
|
+
limit: number;
|
|
110
|
+
actual: number;
|
|
111
|
+
}
|
|
112
|
+
|
|
96
113
|
export type TManagedNftCommands<TPolicy extends TManagedNftPolicy = IManagedNftPolicy> = {
|
|
97
114
|
preparePolicy: { params: TPolicy; result: IPreparedManagedNftPolicy<TPolicy> };
|
|
98
115
|
openOwner: { params: Pick<IManagedNftOptions, 'ownerId' | 'instanceId' | 'tableName'>; result: IManagedNftStatus<TPolicy> };
|