@sysid/sandbox-runtime-improved 0.0.56-sysid.1 → 0.0.61-sysid.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 +43 -2
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/sandbox/credential-mask-files.d.ts +72 -0
- package/dist/sandbox/credential-mask-files.d.ts.map +1 -0
- package/dist/sandbox/credential-mask-files.js +146 -0
- package/dist/sandbox/credential-mask-files.js.map +1 -0
- package/dist/sandbox/credential-sentinel.d.ts +72 -0
- package/dist/sandbox/credential-sentinel.d.ts.map +1 -0
- package/dist/sandbox/credential-sentinel.js +130 -0
- package/dist/sandbox/credential-sentinel.js.map +1 -0
- package/dist/sandbox/domain-pattern.d.ts +36 -0
- package/dist/sandbox/domain-pattern.d.ts.map +1 -0
- package/dist/sandbox/domain-pattern.js +60 -0
- package/dist/sandbox/domain-pattern.js.map +1 -0
- package/dist/sandbox/http-proxy.d.ts +32 -1
- package/dist/sandbox/http-proxy.d.ts.map +1 -1
- package/dist/sandbox/http-proxy.js +10 -2
- package/dist/sandbox/http-proxy.js.map +1 -1
- package/dist/sandbox/linux-sandbox-utils.d.ts +15 -0
- package/dist/sandbox/linux-sandbox-utils.d.ts.map +1 -1
- package/dist/sandbox/linux-sandbox-utils.js +106 -47
- package/dist/sandbox/linux-sandbox-utils.js.map +1 -1
- package/dist/sandbox/listen-in-range.d.ts +12 -0
- package/dist/sandbox/listen-in-range.d.ts.map +1 -0
- package/dist/sandbox/listen-in-range.js +43 -0
- package/dist/sandbox/listen-in-range.js.map +1 -0
- package/dist/sandbox/macos-sandbox-utils.d.ts +11 -0
- package/dist/sandbox/macos-sandbox-utils.d.ts.map +1 -1
- package/dist/sandbox/macos-sandbox-utils.js +69 -14
- package/dist/sandbox/macos-sandbox-utils.js.map +1 -1
- package/dist/sandbox/mitm-ca.d.ts +18 -2
- package/dist/sandbox/mitm-ca.d.ts.map +1 -1
- package/dist/sandbox/mitm-ca.js +49 -9
- package/dist/sandbox/mitm-ca.js.map +1 -1
- package/dist/sandbox/mitm-leaf.d.ts.map +1 -1
- package/dist/sandbox/mitm-leaf.js +24 -6
- package/dist/sandbox/mitm-leaf.js.map +1 -1
- package/dist/sandbox/mux-proxy.d.ts +59 -0
- package/dist/sandbox/mux-proxy.d.ts.map +1 -0
- package/dist/sandbox/mux-proxy.js +159 -0
- package/dist/sandbox/mux-proxy.js.map +1 -0
- package/dist/sandbox/request-filter.d.ts +11 -1
- package/dist/sandbox/request-filter.d.ts.map +1 -1
- package/dist/sandbox/request-filter.js.map +1 -1
- package/dist/sandbox/sandbox-config.d.ts +254 -25
- package/dist/sandbox/sandbox-config.d.ts.map +1 -1
- package/dist/sandbox/sandbox-config.js +200 -13
- package/dist/sandbox/sandbox-config.js.map +1 -1
- package/dist/sandbox/sandbox-manager.d.ts +4 -0
- package/dist/sandbox/sandbox-manager.d.ts.map +1 -1
- package/dist/sandbox/sandbox-manager.js +601 -194
- package/dist/sandbox/sandbox-manager.js.map +1 -1
- package/dist/sandbox/sandbox-schemas.d.ts +15 -0
- package/dist/sandbox/sandbox-schemas.d.ts.map +1 -1
- package/dist/sandbox/sandbox-utils.d.ts +42 -6
- package/dist/sandbox/sandbox-utils.d.ts.map +1 -1
- package/dist/sandbox/sandbox-utils.js +115 -27
- package/dist/sandbox/sandbox-utils.js.map +1 -1
- package/dist/sandbox/socks-proxy.d.ts +11 -5
- package/dist/sandbox/socks-proxy.d.ts.map +1 -1
- package/dist/sandbox/socks-proxy.js +13 -81
- package/dist/sandbox/socks-proxy.js.map +1 -1
- package/dist/sandbox/tls-terminate-proxy.d.ts +2 -2
- package/dist/sandbox/tls-terminate-proxy.d.ts.map +1 -1
- package/dist/sandbox/tls-terminate-proxy.js +31 -5
- package/dist/sandbox/tls-terminate-proxy.js.map +1 -1
- package/dist/sandbox/windows-sandbox-utils.d.ts +347 -11
- package/dist/sandbox/windows-sandbox-utils.d.ts.map +1 -1
- package/dist/sandbox/windows-sandbox-utils.js +437 -45
- package/dist/sandbox/windows-sandbox-utils.js.map +1 -1
- package/package.json +7 -4
- package/vendor/seccomp/build.ts +8 -30
- package/vendor/srt-win/build.ts +21 -0
- package/dist/vendor/seccomp/Dockerfile.build +0 -6
- package/dist/vendor/seccomp/arm64/apply-seccomp +0 -0
- package/dist/vendor/seccomp/build.ts +0 -91
- package/dist/vendor/seccomp/x64/apply-seccomp +0 -0
- package/dist/vendor/seccomp-src/apply-seccomp.c +0 -280
- package/dist/vendor/seccomp-src/seccomp-unix-block.c +0 -148
- package/dist/vendor/srt-win/Cargo.lock +0 -361
- package/dist/vendor/srt-win/Cargo.toml +0 -46
- package/dist/vendor/srt-win/ci/cleanup.ps1 +0 -49
- package/dist/vendor/srt-win/ci/smoke-exec.ps1 +0 -446
- package/dist/vendor/srt-win/ci/smoke.ps1 +0 -307
- package/dist/vendor/srt-win/src/job.rs +0 -102
- package/dist/vendor/srt-win/src/launch.rs +0 -732
- package/dist/vendor/srt-win/src/lib.rs +0 -20
- package/dist/vendor/srt-win/src/main.rs +0 -669
- package/dist/vendor/srt-win/src/self_protect.rs +0 -169
- package/dist/vendor/srt-win/src/sid.rs +0 -296
- package/dist/vendor/srt-win/src/token.rs +0 -341
- package/dist/vendor/srt-win/src/util.rs +0 -42
- package/dist/vendor/srt-win/src/wfp.rs +0 -992
- package/dist/vendor/srt-win/src/winsta.rs +0 -209
- package/dist/vendor/srt-win/tests/sd_access_check_matrix.rs +0 -259
- package/vendor/seccomp-src/apply-seccomp.c +0 -280
- package/vendor/seccomp-src/seccomp-unix-block.c +0 -148
- package/vendor/srt-win/Cargo.lock +0 -361
- package/vendor/srt-win/Cargo.toml +0 -46
- package/vendor/srt-win/ci/cleanup.ps1 +0 -49
- package/vendor/srt-win/ci/smoke-exec.ps1 +0 -446
- package/vendor/srt-win/ci/smoke.ps1 +0 -307
- package/vendor/srt-win/src/job.rs +0 -102
- package/vendor/srt-win/src/launch.rs +0 -732
- package/vendor/srt-win/src/lib.rs +0 -20
- package/vendor/srt-win/src/main.rs +0 -669
- package/vendor/srt-win/src/self_protect.rs +0 -169
- package/vendor/srt-win/src/sid.rs +0 -296
- package/vendor/srt-win/src/token.rs +0 -341
- package/vendor/srt-win/src/util.rs +0 -42
- package/vendor/srt-win/src/wfp.rs +0 -992
- package/vendor/srt-win/src/winsta.rs +0 -209
- package/vendor/srt-win/tests/sd_access_check_matrix.rs +0 -259
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
//! `srt-win` — Windows network sandbox helper for sandbox-runtime.
|
|
2
|
-
//!
|
|
3
|
-
//! This crate is the Rust half of the Windows backend. The library
|
|
4
|
-
//! exposes the SID, group, and WFP primitives so they can be unit-
|
|
5
|
-
//! tested; the binary (`main.rs`) is a thin CLI over them.
|
|
6
|
-
//!
|
|
7
|
-
//! Windows-only. Building on other platforms yields an empty crate so
|
|
8
|
-
//! `cargo check` from a non-Windows host doesn't error.
|
|
9
|
-
|
|
10
|
-
#![cfg(windows)]
|
|
11
|
-
|
|
12
|
-
pub mod sid;
|
|
13
|
-
pub mod util;
|
|
14
|
-
pub mod wfp;
|
|
15
|
-
|
|
16
|
-
pub mod token;
|
|
17
|
-
pub mod job;
|
|
18
|
-
pub mod winsta;
|
|
19
|
-
pub mod self_protect;
|
|
20
|
-
pub mod launch;
|
|
@@ -1,669 +0,0 @@
|
|
|
1
|
-
//! `srt-win` — CLI for the sandbox-runtime Windows network fence.
|
|
2
|
-
//!
|
|
3
|
-
//! Subcommands:
|
|
4
|
-
//! install | uninstall — convenience: group + WFP in one
|
|
5
|
-
//! elevated call (one UAC prompt)
|
|
6
|
-
//! group create | status | delete — manage the discriminator local group
|
|
7
|
-
//! wfp install | status | uninstall — manage the persistent WFP filters
|
|
8
|
-
//! exec -- <target> [args...] — spawn under the deny-only-group
|
|
9
|
-
//! token + job + hardening stack
|
|
10
|
-
//!
|
|
11
|
-
//! `status` subcommands write one line of JSON to stdout and exit 0.
|
|
12
|
-
//! Mutating subcommands require elevation and write human-readable
|
|
13
|
-
//! progress to stderr. `exec` propagates the child's exit code.
|
|
14
|
-
|
|
15
|
-
use clap::{Args, Parser, Subcommand};
|
|
16
|
-
|
|
17
|
-
/// Default group name. Lives here (not in the `#[cfg(windows)]`
|
|
18
|
-
/// library crate) so the clap-derive CLI structs compile on
|
|
19
|
-
/// non-Windows hosts where the library is empty.
|
|
20
|
-
const DEFAULT_GROUP_NAME: &str = "sandbox-runtime-net";
|
|
21
|
-
|
|
22
|
-
#[derive(Parser)]
|
|
23
|
-
#[command(name = "srt-win", version, about)]
|
|
24
|
-
struct Cli {
|
|
25
|
-
#[command(subcommand)]
|
|
26
|
-
cmd: Cmd,
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
#[derive(Subcommand)]
|
|
30
|
-
enum Cmd {
|
|
31
|
-
/// Group create + WFP install in one elevated step.
|
|
32
|
-
///
|
|
33
|
-
/// Self-elevates via UAC if not already running as admin
|
|
34
|
-
/// (one prompt; the elevated child does the work and the
|
|
35
|
-
/// parent relays its exit code). With the machine-wide
|
|
36
|
-
/// filter design, a token where the group is **absent**
|
|
37
|
-
/// (i.e. this session, before logout) matches filter-0
|
|
38
|
-
/// (PERMIT non-members) — so installing the WFP filters
|
|
39
|
-
/// here does NOT break the user's network. Logout is still
|
|
40
|
-
/// required before `srt-win exec` works (the broker
|
|
41
|
-
/// pre-flight needs the group **enabled** to build a
|
|
42
|
-
/// deny-only child token), but the install itself is one
|
|
43
|
-
/// safe step → one UAC prompt.
|
|
44
|
-
///
|
|
45
|
-
/// Equivalent to `group create --name <N> --user-sid <U>`
|
|
46
|
-
/// followed by `wfp install --name <N> …`. With `--group-sid`,
|
|
47
|
-
/// the group is assumed to already exist (e.g. provisioned by
|
|
48
|
-
/// domain GPO) and only the filters are installed.
|
|
49
|
-
///
|
|
50
|
-
/// Exit codes:
|
|
51
|
-
/// 0 — installed (or already installed with the same
|
|
52
|
-
/// port-range; no changes)
|
|
53
|
-
/// 10 — UAC prompt cancelled by the user
|
|
54
|
-
/// 11 — group create / lookup failed
|
|
55
|
-
/// 12 — WFP filter install failed
|
|
56
|
-
/// 13 — already installed under this sublayer with a
|
|
57
|
-
/// DIFFERENT port-range; pass `--force` to replace
|
|
58
|
-
/// 1 — other error (parse, elevation check, etc.)
|
|
59
|
-
Install {
|
|
60
|
-
#[command(flatten)]
|
|
61
|
-
group: GroupRef,
|
|
62
|
-
/// User SID to add to the group (default: current user).
|
|
63
|
-
/// Ignored with `--group-sid`.
|
|
64
|
-
#[arg(long)]
|
|
65
|
-
user_sid: Option<String>,
|
|
66
|
-
/// Sublayer GUID (default: compile-time constant).
|
|
67
|
-
#[arg(long)]
|
|
68
|
-
sublayer_guid: Option<String>,
|
|
69
|
-
/// Loopback port range (`LOW-HIGH`, default 60080-60089).
|
|
70
|
-
#[arg(long, value_name = "LOW-HIGH")]
|
|
71
|
-
proxy_port_range: Option<String>,
|
|
72
|
-
/// Replace an existing install whose port-range differs
|
|
73
|
-
/// (otherwise exits 13).
|
|
74
|
-
#[arg(long)]
|
|
75
|
-
force: bool,
|
|
76
|
-
},
|
|
77
|
-
/// Remove the srt-win WFP filters under the sublayer.
|
|
78
|
-
///
|
|
79
|
-
/// Self-elevates via UAC if not already admin. Does NOT
|
|
80
|
-
/// delete the discriminator group — use `srt-win group
|
|
81
|
-
/// delete --name <N>` for that explicitly.
|
|
82
|
-
Uninstall {
|
|
83
|
-
#[arg(long)]
|
|
84
|
-
sublayer_guid: Option<String>,
|
|
85
|
-
},
|
|
86
|
-
/// Manage the local discriminator group.
|
|
87
|
-
Group {
|
|
88
|
-
#[command(subcommand)]
|
|
89
|
-
sub: GroupCmd,
|
|
90
|
-
},
|
|
91
|
-
/// Manage the persistent WFP filters.
|
|
92
|
-
Wfp {
|
|
93
|
-
#[command(subcommand)]
|
|
94
|
-
sub: WfpCmd,
|
|
95
|
-
},
|
|
96
|
-
/// Spawn a process under the deny-only-group sandbox.
|
|
97
|
-
///
|
|
98
|
-
/// Builds a restricted token (group + Admins flipped deny-only,
|
|
99
|
-
/// LUA, Medium IL, all privs stripped except SeChangeNotify),
|
|
100
|
-
/// self-protects the broker, assigns the child to a
|
|
101
|
-
/// kill-on-close job with full UI lockdown, places it on a
|
|
102
|
-
/// non-interactive desktop, applies process-mitigation
|
|
103
|
-
/// policies + an explicit handle whitelist, and waits for it
|
|
104
|
-
/// to exit. Propagates the child's exit code.
|
|
105
|
-
///
|
|
106
|
-
/// The child inherits this process's environment verbatim — proxy
|
|
107
|
-
/// configuration is single-sourced by the caller, which sets the
|
|
108
|
-
/// proxy vars (TS `generateProxyEnvVars`) in the environment it
|
|
109
|
-
/// spawns `srt-win exec` with. There are intentionally no
|
|
110
|
-
/// `--http-proxy` / `--socks-proxy` flags and no proxy fallback.
|
|
111
|
-
Exec {
|
|
112
|
-
#[command(flatten)]
|
|
113
|
-
group: GroupRef,
|
|
114
|
-
/// Skip the "is the group enabled in the broker's token"
|
|
115
|
-
/// pre-flight. **Fail-open** — the WFP fence depends on
|
|
116
|
-
/// that membership; with this set the child may run with
|
|
117
|
-
/// weaker isolation if the install was incomplete.
|
|
118
|
-
/// Surfaced as a flag (not an env var) so the bypass is
|
|
119
|
-
/// intentional and not accidentally inherited. Use ONLY
|
|
120
|
-
/// in ephemeral CI runners that create the group in-job
|
|
121
|
-
/// and cannot logout/login mid-run.
|
|
122
|
-
#[arg(long)]
|
|
123
|
-
skip_group_check: bool,
|
|
124
|
-
/// Target executable followed by its arguments. Use `--`
|
|
125
|
-
/// to terminate srt-win's own option parsing.
|
|
126
|
-
#[arg(
|
|
127
|
-
trailing_var_arg = true,
|
|
128
|
-
allow_hyphen_values = true,
|
|
129
|
-
required = true,
|
|
130
|
-
num_args = 1..,
|
|
131
|
-
)]
|
|
132
|
-
target: Vec<String>,
|
|
133
|
-
},
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
/// Group resolution: either by name (looked up via
|
|
137
|
-
/// `LookupAccountNameW`) or directly by SID. If both are given the
|
|
138
|
-
/// SID wins; `group create`/`delete` always need a name.
|
|
139
|
-
#[derive(Args, Clone)]
|
|
140
|
-
struct GroupRef {
|
|
141
|
-
/// Group name (local or `DOMAIN\name`). Default
|
|
142
|
-
/// `sandbox-runtime-net`.
|
|
143
|
-
#[arg(long, default_value = DEFAULT_GROUP_NAME)]
|
|
144
|
-
name: String,
|
|
145
|
-
/// Group SID (`S-1-…`). Overrides `--name` for SID resolution.
|
|
146
|
-
/// Use when the group is provisioned by external tooling and name
|
|
147
|
-
/// lookup may be unreliable.
|
|
148
|
-
#[arg(long)]
|
|
149
|
-
group_sid: Option<String>,
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
#[derive(Subcommand)]
|
|
153
|
-
enum GroupCmd {
|
|
154
|
-
/// Create the local group and add the current (or `--user-sid`)
|
|
155
|
-
/// user to it. Idempotent. Self-elevates via UAC if not already
|
|
156
|
-
/// admin.
|
|
157
|
-
Create {
|
|
158
|
-
#[command(flatten)]
|
|
159
|
-
group: GroupRef,
|
|
160
|
-
/// User SID to add (default: current user).
|
|
161
|
-
#[arg(long)]
|
|
162
|
-
user_sid: Option<String>,
|
|
163
|
-
},
|
|
164
|
-
/// Print group state as JSON: `{state, sid?, warning?}`.
|
|
165
|
-
Status {
|
|
166
|
-
#[command(flatten)]
|
|
167
|
-
group: GroupRef,
|
|
168
|
-
},
|
|
169
|
-
/// Delete the local group. Idempotent. Self-elevates via UAC if
|
|
170
|
-
/// not already admin.
|
|
171
|
-
Delete {
|
|
172
|
-
#[command(flatten)]
|
|
173
|
-
group: GroupRef,
|
|
174
|
-
},
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
#[derive(Subcommand)]
|
|
178
|
-
enum WfpCmd {
|
|
179
|
-
/// Install (or refresh) the machine-wide persistent WFP filters
|
|
180
|
-
/// keyed on the group SID. Idempotent. Self-elevates via UAC if
|
|
181
|
-
/// not already admin.
|
|
182
|
-
Install {
|
|
183
|
-
#[command(flatten)]
|
|
184
|
-
group: GroupRef,
|
|
185
|
-
/// Sublayer GUID. Default is the compile-time constant; pass
|
|
186
|
-
/// when integrating with externally-managed WFP state.
|
|
187
|
-
#[arg(long)]
|
|
188
|
-
sublayer_guid: Option<String>,
|
|
189
|
-
/// Loopback port range the sandboxed child may reach
|
|
190
|
-
/// (`LOW-HIGH`, inclusive; default 60080-60089). The host
|
|
191
|
-
/// http/socks proxies bind inside this range on Windows.
|
|
192
|
-
#[arg(long, value_name = "LOW-HIGH")]
|
|
193
|
-
proxy_port_range: Option<String>,
|
|
194
|
-
},
|
|
195
|
-
/// Print WFP fence state as JSON: `{state, filters,
|
|
196
|
-
/// port_range?}`. Filters are identified by their
|
|
197
|
-
/// `providerData` tag, so only `--sublayer-guid` is relevant.
|
|
198
|
-
Status {
|
|
199
|
-
#[arg(long)]
|
|
200
|
-
sublayer_guid: Option<String>,
|
|
201
|
-
},
|
|
202
|
-
/// Remove every srt-win-tagged WFP filter under the sublayer.
|
|
203
|
-
/// Self-elevates via UAC if not already admin.
|
|
204
|
-
Uninstall {
|
|
205
|
-
#[arg(long)]
|
|
206
|
-
sublayer_guid: Option<String>,
|
|
207
|
-
},
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
#[cfg(windows)]
|
|
211
|
-
fn main() {
|
|
212
|
-
if let Err(e) = run() {
|
|
213
|
-
eprintln!("srt-win: error: {e:#}");
|
|
214
|
-
std::process::exit(1);
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
#[cfg(windows)]
|
|
219
|
-
fn run() -> anyhow::Result<()> {
|
|
220
|
-
use anyhow::{anyhow, Context};
|
|
221
|
-
use serde_json::json;
|
|
222
|
-
use srt_win::{sid, wfp};
|
|
223
|
-
|
|
224
|
-
let cli = Cli::parse();
|
|
225
|
-
|
|
226
|
-
// Validate a caller-supplied SID string up front so a typo
|
|
227
|
-
// surfaces as "invalid --<flag>" rather than an SDDL parse
|
|
228
|
-
// error three calls deep. Returns the CANONICAL `S-1-…` form
|
|
229
|
-
// (round-tripped through ConvertSidToStringSidW) so SDDL
|
|
230
|
-
// shorthands like `BA` or lower-case `s-1-…` collapse to a
|
|
231
|
-
// single comparable representation; downstream
|
|
232
|
-
// `eq_ignore_ascii_case("S-1-5-32-544")` dedup checks rely on
|
|
233
|
-
// that.
|
|
234
|
-
let canonicalize_sid =
|
|
235
|
-
|flag: &str, s: &str| -> anyhow::Result<String> {
|
|
236
|
-
let p = sid::LocalPsid::from_string(s)
|
|
237
|
-
.with_context(|| format!("invalid --{flag} '{s}'"))?;
|
|
238
|
-
sid::psid_to_string(p.as_psid())
|
|
239
|
-
.with_context(|| format!("canonicalize --{flag} '{s}'"))
|
|
240
|
-
};
|
|
241
|
-
let resolve_group_sid = |g: &GroupRef| -> anyhow::Result<String> {
|
|
242
|
-
if let Some(s) = &g.group_sid {
|
|
243
|
-
return canonicalize_sid("group-sid", s);
|
|
244
|
-
}
|
|
245
|
-
sid::lookup_account_sid(&g.name)
|
|
246
|
-
.with_context(|| format!("resolve group '{}'", g.name))
|
|
247
|
-
};
|
|
248
|
-
let resolve_sublayer = |s: &Option<String>| -> anyhow::Result<windows::core::GUID> {
|
|
249
|
-
match s {
|
|
250
|
-
Some(g) => wfp::parse_guid(g),
|
|
251
|
-
None => Ok(wfp::DEFAULT_SUBLAYER_GUID),
|
|
252
|
-
}
|
|
253
|
-
};
|
|
254
|
-
|
|
255
|
-
match cli.cmd {
|
|
256
|
-
// ─── install / uninstall (convenience) ─────────────────────
|
|
257
|
-
Cmd::Install {
|
|
258
|
-
group,
|
|
259
|
-
user_sid,
|
|
260
|
-
sublayer_guid,
|
|
261
|
-
proxy_port_range,
|
|
262
|
-
force,
|
|
263
|
-
} => {
|
|
264
|
-
if let Some(code) = maybe_self_elevate()? {
|
|
265
|
-
std::process::exit(code);
|
|
266
|
-
}
|
|
267
|
-
let sl = resolve_sublayer(&sublayer_guid)?;
|
|
268
|
-
let range = match &proxy_port_range {
|
|
269
|
-
Some(s) => wfp::parse_port_range(s)
|
|
270
|
-
.with_context(|| format!("invalid --proxy-port-range '{s}'"))?,
|
|
271
|
-
None => wfp::DEFAULT_PROXY_PORT_RANGE,
|
|
272
|
-
};
|
|
273
|
-
// Idempotency / conflict pre-check. If filters are
|
|
274
|
-
// already installed under this sublayer with the SAME
|
|
275
|
-
// port-range, this is a no-op (exit 0). With a
|
|
276
|
-
// DIFFERENT range and no --force, refuse (exit 13) so
|
|
277
|
-
// an unintended config drift surfaces instead of
|
|
278
|
-
// silently overwriting. A pre-existing install whose
|
|
279
|
-
// tags lack a port_range (legacy) is treated as
|
|
280
|
-
// "different" and requires --force.
|
|
281
|
-
if !force
|
|
282
|
-
&& let Ok(st) = wfp::filter_status(&sl)
|
|
283
|
-
&& st.state == "installed"
|
|
284
|
-
{
|
|
285
|
-
let want = [range.0, range.1];
|
|
286
|
-
if st.port_range == Some(want) {
|
|
287
|
-
eprintln!(
|
|
288
|
-
"srt-win: already installed (sublayer={sl:?}, \
|
|
289
|
-
port_range={}-{}, filters={}); no changes",
|
|
290
|
-
range.0, range.1, st.filters,
|
|
291
|
-
);
|
|
292
|
-
return Ok(());
|
|
293
|
-
}
|
|
294
|
-
let have = st
|
|
295
|
-
.port_range
|
|
296
|
-
.map(|[l, h]| format!("{l}-{h}"))
|
|
297
|
-
.unwrap_or_else(|| "<unknown>".into());
|
|
298
|
-
eprintln!(
|
|
299
|
-
"srt-win: error: already installed under sublayer \
|
|
300
|
-
{sl:?} with port_range={have}; pass --force to \
|
|
301
|
-
replace, or run `srt-win uninstall` first."
|
|
302
|
-
);
|
|
303
|
-
std::process::exit(13);
|
|
304
|
-
}
|
|
305
|
-
// With --group-sid the group is externally managed;
|
|
306
|
-
// just canonicalize. With --name (or the default),
|
|
307
|
-
// create the local group, add the user, then resolve
|
|
308
|
-
// the SID. Failures here exit 11.
|
|
309
|
-
let group_step = || -> anyhow::Result<(String, String)> {
|
|
310
|
-
if let Some(s) = &group.group_sid {
|
|
311
|
-
let g = canonicalize_sid("group-sid", s)?;
|
|
312
|
-
Ok((g.clone(), g))
|
|
313
|
-
} else {
|
|
314
|
-
let user = match &user_sid {
|
|
315
|
-
Some(s) => canonicalize_sid("user-sid", s)?,
|
|
316
|
-
None => sid::current_user_sid()
|
|
317
|
-
.context("resolve current user")?,
|
|
318
|
-
};
|
|
319
|
-
wfp::ensure_group(&group.name, &user)?;
|
|
320
|
-
let g = sid::lookup_account_sid(&group.name)?;
|
|
321
|
-
Ok((group.name.clone(), g))
|
|
322
|
-
}
|
|
323
|
-
};
|
|
324
|
-
let (label, gsid) = match group_step() {
|
|
325
|
-
Ok(v) => v,
|
|
326
|
-
Err(e) => {
|
|
327
|
-
eprintln!("srt-win: error: group step: {e:#}");
|
|
328
|
-
std::process::exit(11);
|
|
329
|
-
}
|
|
330
|
-
};
|
|
331
|
-
if let Err(e) = wfp::install_filters(&sl, &gsid, range) {
|
|
332
|
-
eprintln!("srt-win: error: WFP install: {e:#}");
|
|
333
|
-
std::process::exit(12);
|
|
334
|
-
}
|
|
335
|
-
eprintln!(
|
|
336
|
-
"srt-win: installed (group={label} sid={gsid}, sublayer={sl:?}, \
|
|
337
|
-
proxy_port_range={}-{}, filters=8)",
|
|
338
|
-
range.0, range.1,
|
|
339
|
-
);
|
|
340
|
-
eprintln!(
|
|
341
|
-
"srt-win: NOTE — log out and back in before running \
|
|
342
|
-
`srt-win exec` (the group SID enters TokenGroups at \
|
|
343
|
-
logon; your network is unaffected meanwhile)."
|
|
344
|
-
);
|
|
345
|
-
}
|
|
346
|
-
Cmd::Uninstall { sublayer_guid } => {
|
|
347
|
-
if let Some(code) = maybe_self_elevate()? {
|
|
348
|
-
std::process::exit(code);
|
|
349
|
-
}
|
|
350
|
-
let sl = resolve_sublayer(&sublayer_guid)?;
|
|
351
|
-
let n = wfp::uninstall_filters(&sl)?;
|
|
352
|
-
eprintln!(
|
|
353
|
-
"srt-win: uninstalled ({n} filter(s) removed). \
|
|
354
|
-
Group is left intact — run `srt-win group delete` \
|
|
355
|
-
to remove it."
|
|
356
|
-
);
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
// ─── group ─────────────────────────────────────────────────
|
|
360
|
-
Cmd::Group { sub: GroupCmd::Create { group, user_sid } } => {
|
|
361
|
-
if let Some(code) = maybe_self_elevate()? {
|
|
362
|
-
std::process::exit(code);
|
|
363
|
-
}
|
|
364
|
-
if group.group_sid.is_some() {
|
|
365
|
-
return Err(anyhow!(
|
|
366
|
-
"`group create` needs --name; --group-sid is for \
|
|
367
|
-
referencing an existing group"
|
|
368
|
-
));
|
|
369
|
-
}
|
|
370
|
-
let user = match &user_sid {
|
|
371
|
-
Some(s) => canonicalize_sid("user-sid", s)?,
|
|
372
|
-
None => sid::current_user_sid()
|
|
373
|
-
.context("resolve current user")?,
|
|
374
|
-
};
|
|
375
|
-
wfp::ensure_group(&group.name, &user)?;
|
|
376
|
-
let gsid = sid::lookup_account_sid(&group.name)?;
|
|
377
|
-
eprintln!(
|
|
378
|
-
"srt-win: group '{}' present (sid={gsid}); user {user} added",
|
|
379
|
-
group.name
|
|
380
|
-
);
|
|
381
|
-
eprintln!(
|
|
382
|
-
"srt-win: NOTE — the group SID enters TokenGroups at logon. \
|
|
383
|
-
Log out and back in before running `srt-win exec`."
|
|
384
|
-
);
|
|
385
|
-
}
|
|
386
|
-
Cmd::Group { sub: GroupCmd::Status { group } } => {
|
|
387
|
-
// Resolve SID first; if that fails the group is absent.
|
|
388
|
-
let gsid = match &group.group_sid {
|
|
389
|
-
Some(s) => {
|
|
390
|
-
// --group-sid bypasses the name lookup, so do a
|
|
391
|
-
// reverse lookup to distinguish "exists but not on
|
|
392
|
-
// this token yet" from "no such account at all".
|
|
393
|
-
// Tolerate transient lookup failure (domain
|
|
394
|
-
// unreachable) by falling through to the token
|
|
395
|
-
// check.
|
|
396
|
-
match sid::sid_account_exists(s) {
|
|
397
|
-
Ok(sid::SidExistence::Unmapped) => {
|
|
398
|
-
println!("{}", json!({"state": "absent"}));
|
|
399
|
-
return Ok(());
|
|
400
|
-
}
|
|
401
|
-
Ok(_) => {}
|
|
402
|
-
Err(e) => {
|
|
403
|
-
// Malformed SID string.
|
|
404
|
-
println!(
|
|
405
|
-
"{}",
|
|
406
|
-
json!({"state": "absent", "error": e.to_string()})
|
|
407
|
-
);
|
|
408
|
-
return Ok(());
|
|
409
|
-
}
|
|
410
|
-
}
|
|
411
|
-
s.clone()
|
|
412
|
-
}
|
|
413
|
-
None => match sid::lookup_account_sid(&group.name) {
|
|
414
|
-
Ok(s) => s,
|
|
415
|
-
Err(_) => {
|
|
416
|
-
println!("{}", json!({"state": "absent"}));
|
|
417
|
-
return Ok(());
|
|
418
|
-
}
|
|
419
|
-
},
|
|
420
|
-
};
|
|
421
|
-
let out = match sid::group_state_for_self(&gsid)? {
|
|
422
|
-
sid::GroupState::Enabled => {
|
|
423
|
-
json!({"state": "ready", "sid": gsid})
|
|
424
|
-
}
|
|
425
|
-
sid::GroupState::Absent => {
|
|
426
|
-
json!({"state": "created-not-on-token", "sid": gsid})
|
|
427
|
-
}
|
|
428
|
-
sid::GroupState::DenyOnly => json!({
|
|
429
|
-
"state": "created-not-on-token",
|
|
430
|
-
"sid": gsid,
|
|
431
|
-
"warning": "group is deny-only in this token — running \
|
|
432
|
-
inside a sandbox child?"
|
|
433
|
-
}),
|
|
434
|
-
sid::GroupState::Present => json!({
|
|
435
|
-
"state": "created-not-on-token",
|
|
436
|
-
"sid": gsid,
|
|
437
|
-
"warning": "group present but neither enabled nor \
|
|
438
|
-
deny-only (unexpected)"
|
|
439
|
-
}),
|
|
440
|
-
};
|
|
441
|
-
println!("{out}");
|
|
442
|
-
}
|
|
443
|
-
Cmd::Group { sub: GroupCmd::Delete { group } } => {
|
|
444
|
-
if let Some(code) = maybe_self_elevate()? {
|
|
445
|
-
std::process::exit(code);
|
|
446
|
-
}
|
|
447
|
-
if group.group_sid.is_some() {
|
|
448
|
-
return Err(anyhow!(
|
|
449
|
-
"`group delete` needs --name; cannot delete by SID"
|
|
450
|
-
));
|
|
451
|
-
}
|
|
452
|
-
wfp::delete_group(&group.name)?;
|
|
453
|
-
eprintln!("srt-win: group '{}' deleted (if it existed)", group.name);
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
// ─── wfp ───────────────────────────────────────────────────
|
|
457
|
-
Cmd::Wfp {
|
|
458
|
-
sub:
|
|
459
|
-
WfpCmd::Install {
|
|
460
|
-
group,
|
|
461
|
-
sublayer_guid,
|
|
462
|
-
proxy_port_range,
|
|
463
|
-
},
|
|
464
|
-
} => {
|
|
465
|
-
if let Some(code) = maybe_self_elevate()? {
|
|
466
|
-
std::process::exit(code);
|
|
467
|
-
}
|
|
468
|
-
let gsid = resolve_group_sid(&group)?;
|
|
469
|
-
let sl = resolve_sublayer(&sublayer_guid)?;
|
|
470
|
-
let range = match &proxy_port_range {
|
|
471
|
-
Some(s) => wfp::parse_port_range(s)
|
|
472
|
-
.with_context(|| format!("invalid --proxy-port-range '{s}'"))?,
|
|
473
|
-
None => wfp::DEFAULT_PROXY_PORT_RANGE,
|
|
474
|
-
};
|
|
475
|
-
wfp::install_filters(&sl, &gsid, range)?;
|
|
476
|
-
eprintln!(
|
|
477
|
-
"srt-win: WFP filters installed (group_sid={gsid}, \
|
|
478
|
-
sublayer={sl:?}, proxy_port_range={}-{})",
|
|
479
|
-
range.0, range.1,
|
|
480
|
-
);
|
|
481
|
-
}
|
|
482
|
-
Cmd::Wfp { sub: WfpCmd::Status { sublayer_guid } } => {
|
|
483
|
-
let sl = resolve_sublayer(&sublayer_guid)?;
|
|
484
|
-
let st = wfp::filter_status(&sl)?;
|
|
485
|
-
println!("{}", serde_json::to_string(&st)?);
|
|
486
|
-
}
|
|
487
|
-
Cmd::Wfp { sub: WfpCmd::Uninstall { sublayer_guid } } => {
|
|
488
|
-
if let Some(code) = maybe_self_elevate()? {
|
|
489
|
-
std::process::exit(code);
|
|
490
|
-
}
|
|
491
|
-
let sl = resolve_sublayer(&sublayer_guid)?;
|
|
492
|
-
let n = wfp::uninstall_filters(&sl)?;
|
|
493
|
-
eprintln!("srt-win: removed {n} WFP filter(s)");
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
// ─── exec ──────────────────────────────────────────────────
|
|
497
|
-
Cmd::Exec {
|
|
498
|
-
group,
|
|
499
|
-
skip_group_check,
|
|
500
|
-
target,
|
|
501
|
-
} => {
|
|
502
|
-
use srt_win::launch;
|
|
503
|
-
let gsid = resolve_group_sid(&group)?;
|
|
504
|
-
// `target` is `required, num_args=1..` so non-empty.
|
|
505
|
-
let exe = std::path::PathBuf::from(&target[0]);
|
|
506
|
-
let args = &target[1..];
|
|
507
|
-
let spec = launch::ExecSpec {
|
|
508
|
-
group_sid: &gsid,
|
|
509
|
-
skip_group_check,
|
|
510
|
-
target_exe: &exe,
|
|
511
|
-
target_args: args,
|
|
512
|
-
};
|
|
513
|
-
let code = launch::run(&spec)?;
|
|
514
|
-
// Propagate the child's exit code verbatim.
|
|
515
|
-
std::process::exit(code as i32);
|
|
516
|
-
}
|
|
517
|
-
}
|
|
518
|
-
Ok(())
|
|
519
|
-
}
|
|
520
|
-
|
|
521
|
-
#[cfg(windows)]
|
|
522
|
-
fn is_elevated() -> anyhow::Result<bool> {
|
|
523
|
-
use anyhow::Context;
|
|
524
|
-
use std::ffi::c_void;
|
|
525
|
-
use std::mem::size_of;
|
|
526
|
-
use windows::Win32::Foundation::{CloseHandle, HANDLE};
|
|
527
|
-
use windows::Win32::Security::{
|
|
528
|
-
GetTokenInformation, TokenElevation, TOKEN_ELEVATION, TOKEN_QUERY,
|
|
529
|
-
};
|
|
530
|
-
use windows::Win32::System::Threading::{
|
|
531
|
-
GetCurrentProcess, OpenProcessToken,
|
|
532
|
-
};
|
|
533
|
-
unsafe {
|
|
534
|
-
let mut tok = HANDLE::default();
|
|
535
|
-
OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &mut tok)
|
|
536
|
-
.context("OpenProcessToken")?;
|
|
537
|
-
let mut elev = TOKEN_ELEVATION::default();
|
|
538
|
-
let mut ret = 0u32;
|
|
539
|
-
let r = GetTokenInformation(
|
|
540
|
-
tok,
|
|
541
|
-
TokenElevation,
|
|
542
|
-
Some(&mut elev as *mut _ as *mut c_void),
|
|
543
|
-
size_of::<TOKEN_ELEVATION>() as u32,
|
|
544
|
-
&mut ret,
|
|
545
|
-
);
|
|
546
|
-
let _ = CloseHandle(tok);
|
|
547
|
-
r.context("GetTokenInformation(TokenElevation)")?;
|
|
548
|
-
Ok(elev.TokenIsElevated != 0)
|
|
549
|
-
}
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
/// Hard elevation gate: returns an error (no UAC relaunch) when not
|
|
553
|
-
/// admin. The granular admin mutators now self-elevate via
|
|
554
|
-
/// [`maybe_self_elevate`], so this currently has no caller — it's
|
|
555
|
-
/// retained as the non-interactive counterpart for `acl recover`
|
|
556
|
-
/// (a later batch), hence `allow(dead_code)`.
|
|
557
|
-
#[cfg(windows)]
|
|
558
|
-
#[allow(dead_code)]
|
|
559
|
-
fn require_elevated() -> anyhow::Result<()> {
|
|
560
|
-
if is_elevated()? {
|
|
561
|
-
Ok(())
|
|
562
|
-
} else {
|
|
563
|
-
Err(anyhow::anyhow!(
|
|
564
|
-
"this command requires elevation — run from an \
|
|
565
|
-
administrator prompt"
|
|
566
|
-
))
|
|
567
|
-
}
|
|
568
|
-
}
|
|
569
|
-
|
|
570
|
-
/// If not already elevated, re-launch ourselves with the same
|
|
571
|
-
/// argv via `ShellExecuteExW(verb="runas")` — one UAC prompt —
|
|
572
|
-
/// wait for the elevated child, and return its exit code. If
|
|
573
|
-
/// already elevated, returns `Ok(None)` and the caller proceeds
|
|
574
|
-
/// in-process. If the user cancels the UAC dialog
|
|
575
|
-
/// (`ERROR_CANCELLED`), exits with code **10** so the caller's
|
|
576
|
-
/// exit-code contract holds without the caller needing a
|
|
577
|
-
/// separate match.
|
|
578
|
-
///
|
|
579
|
-
/// The elevated child runs in its own (hidden) console, so its
|
|
580
|
-
/// stdout/stderr are NOT relayed to the parent. For
|
|
581
|
-
/// `install`/`uninstall` that's acceptable: the exit code is the
|
|
582
|
-
/// contract; the convenience commands' stderr is informational
|
|
583
|
-
/// only. The granular `group create|delete` and `wfp
|
|
584
|
-
/// install|uninstall` admin mutators call this too; their stderr is
|
|
585
|
-
/// likewise informational. Read-only subcommands (`group status`,
|
|
586
|
-
/// `wfp status`, `exec`) run as the broker and never self-elevate.
|
|
587
|
-
#[cfg(windows)]
|
|
588
|
-
fn maybe_self_elevate() -> anyhow::Result<Option<i32>> {
|
|
589
|
-
use anyhow::Context;
|
|
590
|
-
use srt_win::launch::quote_arg;
|
|
591
|
-
use srt_win::util::wstr;
|
|
592
|
-
use windows::core::PCWSTR;
|
|
593
|
-
use windows::Win32::Foundation::{
|
|
594
|
-
CloseHandle, ERROR_CANCELLED, GetLastError,
|
|
595
|
-
};
|
|
596
|
-
use windows::Win32::System::Threading::{
|
|
597
|
-
GetExitCodeProcess, WaitForSingleObject, INFINITE,
|
|
598
|
-
};
|
|
599
|
-
use windows::Win32::UI::Shell::{
|
|
600
|
-
ShellExecuteExW, SEE_MASK_NOCLOSEPROCESS, SEE_MASK_NO_CONSOLE,
|
|
601
|
-
SHELLEXECUTEINFOW,
|
|
602
|
-
};
|
|
603
|
-
use windows::Win32::UI::WindowsAndMessaging::SW_HIDE;
|
|
604
|
-
|
|
605
|
-
if is_elevated()? {
|
|
606
|
-
return Ok(None);
|
|
607
|
-
}
|
|
608
|
-
|
|
609
|
-
let exe = std::env::current_exe().context("current_exe")?;
|
|
610
|
-
let exe_w = wstr(&exe.to_string_lossy());
|
|
611
|
-
// Rebuild the original argv (minus argv[0]) using
|
|
612
|
-
// CommandLineToArgvW-compatible quoting so the elevated
|
|
613
|
-
// child parses identically.
|
|
614
|
-
let params: String = std::env::args()
|
|
615
|
-
.skip(1)
|
|
616
|
-
.map(|a| quote_arg(&a))
|
|
617
|
-
.collect::<Vec<_>>()
|
|
618
|
-
.join(" ");
|
|
619
|
-
let params_w = wstr(¶ms);
|
|
620
|
-
let verb_w = wstr("runas");
|
|
621
|
-
|
|
622
|
-
let mut sei = SHELLEXECUTEINFOW {
|
|
623
|
-
cbSize: std::mem::size_of::<SHELLEXECUTEINFOW>() as u32,
|
|
624
|
-
fMask: SEE_MASK_NOCLOSEPROCESS | SEE_MASK_NO_CONSOLE,
|
|
625
|
-
lpVerb: PCWSTR(verb_w.as_ptr()),
|
|
626
|
-
lpFile: PCWSTR(exe_w.as_ptr()),
|
|
627
|
-
lpParameters: PCWSTR(params_w.as_ptr()),
|
|
628
|
-
nShow: SW_HIDE.0,
|
|
629
|
-
..Default::default()
|
|
630
|
-
};
|
|
631
|
-
// SAFETY: sei is fully initialized; the wide-string buffers
|
|
632
|
-
// outlive the call.
|
|
633
|
-
let ok = unsafe { ShellExecuteExW(&mut sei) };
|
|
634
|
-
if ok.is_err() {
|
|
635
|
-
let err = unsafe { GetLastError() };
|
|
636
|
-
if err == ERROR_CANCELLED {
|
|
637
|
-
eprintln!("srt-win: UAC prompt cancelled by user");
|
|
638
|
-
std::process::exit(10);
|
|
639
|
-
}
|
|
640
|
-
return Err(anyhow::anyhow!(
|
|
641
|
-
"ShellExecuteExW(runas): {} ({}",
|
|
642
|
-
std::io::Error::from_raw_os_error(err.0 as i32),
|
|
643
|
-
err.0,
|
|
644
|
-
));
|
|
645
|
-
}
|
|
646
|
-
let h = sei.hProcess;
|
|
647
|
-
if h.is_invalid() {
|
|
648
|
-
return Err(anyhow::anyhow!(
|
|
649
|
-
"ShellExecuteExW returned no process handle"
|
|
650
|
-
));
|
|
651
|
-
}
|
|
652
|
-
unsafe { WaitForSingleObject(h, INFINITE) };
|
|
653
|
-
let mut code: u32 = 1;
|
|
654
|
-
unsafe {
|
|
655
|
-
GetExitCodeProcess(h, &mut code)
|
|
656
|
-
.context("GetExitCodeProcess(elevated child)")?;
|
|
657
|
-
let _ = CloseHandle(h);
|
|
658
|
-
}
|
|
659
|
-
Ok(Some(code as i32))
|
|
660
|
-
}
|
|
661
|
-
|
|
662
|
-
#[cfg(not(windows))]
|
|
663
|
-
fn main() {
|
|
664
|
-
// The clap-derived structs above keep `clap` referenced; just
|
|
665
|
-
// print the platform error.
|
|
666
|
-
let _ = <Cli as clap::CommandFactory>::command();
|
|
667
|
-
eprintln!("srt-win: Windows only");
|
|
668
|
-
std::process::exit(2);
|
|
669
|
-
}
|