mirror of
https://github.com/NVIDIA/OpenShell.git
synced 2026-11-07 08:55:37 +08:00
feat(mxc): pass driver_config denied_paths to MXC deniedPaths (#5033)
Add optional MXC denied paths with validation against resolved filesystem grants and directories the workload can rename. Pass the denies to MXC with host ACL mutation fallback disabled. Document path-based enforcement limits and cover namespace aliases and directory renames with regression tests. Closes #5032 Signed-off-by: Jeffrey Quesnelle <emozilla@nousresearch.com> Signed-off-by: Shailendra Singh <shailendras@nvidia.com>
This commit is contained in:
@@ -148,6 +148,62 @@ command is supplied, the driver passes `mxc.command` to the supervisor rather
|
||||
than starting a scratch workload. An explicit generic canonical command retains
|
||||
precedence. Environment belongs in `--env` or `--env-from`, not gateway configuration.
|
||||
|
||||
The optional `mxc.denied_paths` lists paths the workload cannot open, including
|
||||
inside the policy's filesystem grants: it cannot read, write or delete a denied
|
||||
file, list a denied directory, or create a denied path by name. The driver
|
||||
passes the list to MXC as `filesystem.deniedPaths`. For example, with a policy
|
||||
that grants `C:\work\profile` read-write, to hide the credential files in it:
|
||||
|
||||
```powershell
|
||||
$config = '{"mxc":{"command":["C:\\Windows\\System32\\cmd.exe","/d","/c","agent.cmd"],"cwd":"C:\\work","denied_paths":["C:\\work\\profile\\.env","C:\\work\\profile\\auth.json"]}}'
|
||||
```
|
||||
|
||||
Each entry must be an absolute path that starts with a drive letter, such as
|
||||
`C:\work\profile\.env`; forward slashes are converted to backslashes. A denied
|
||||
path may be absent, but its parent directory must exist because MXC refuses to
|
||||
start the ProcessContainer otherwise; to cover a missing directory, deny the
|
||||
directory itself. An entry must not equal or contain a filesystem grant,
|
||||
including the generation staging directory under `cwd` and the sandbox
|
||||
executable: MXC drops a grant that equals a denied path and keeps access to a
|
||||
grant nested inside a denied directory.
|
||||
|
||||
MXC applies denied paths and grants to paths. The workload can rename a
|
||||
directory when the directory and its parent are both writable to it, and
|
||||
everything below that directory then sits at a path no denied path or read-only
|
||||
grant names. Preflight therefore rejects an entry below such a directory: two
|
||||
or more levels below a read-write grant root, inside a read-write grant nested
|
||||
in another read-write grant, or below a read-only grant whose root is two or
|
||||
more levels inside a read-write grant. The error names the directory; deny it
|
||||
instead, or move the entry directly into the read-write grant root. Entries
|
||||
directly inside a read-write grant root whose parent the workload cannot write,
|
||||
and entries below a read-only grant root that cannot be renamed, are accepted.
|
||||
|
||||
MXC resolves 8.3 short names, junctions and letter case, and accepts grants in
|
||||
the `\\.\C:\`, `\\?\C:\`, `\??\C:\`, `\\?\GLOBALROOT\Device\...` and
|
||||
`\\?\Volume{...}\` spellings, so preflight compares resolved drive paths
|
||||
(following symbolic links too). The parts of a drive path that do not exist, or
|
||||
that cannot be resolved, are compared as written. While `denied_paths` is not
|
||||
empty, preflight rejects a grant it cannot resolve to a drive path: a UNC path,
|
||||
which MXC grants no access through, a path without a drive letter, or a device
|
||||
path that does not resolve.
|
||||
|
||||
A denied path protects contents. The file's name, size, timestamps and
|
||||
attributes remain visible to the workload, for example when it lists the parent
|
||||
directory, and a hard link to the same file, created on the host before launch
|
||||
at a path the workload can read, still exposes the contents. MXC does not check
|
||||
the target of a rename against `deniedPaths`: when the parent directory is
|
||||
writable, the workload can rename one of its own files onto a denied name,
|
||||
which replaces a denied file on the host, or creates the file or directory when
|
||||
it is absent. The original contents stay unreadable, and an existing denied
|
||||
directory cannot be replaced. Denying the directory that holds such files
|
||||
prevents this, because nothing can be renamed into a denied directory.
|
||||
|
||||
Denied paths only remove access and are fixed at sandbox creation. They require
|
||||
a host whose `wxc-exec.exe --probe` reports
|
||||
`probes.baseContainerSupportsDenyPaths: true`. When the list is not empty, the
|
||||
driver sends MXC `fallback.allowDaclMutation: false`, so on a host without that
|
||||
support the launch fails and MXC does not change the ACLs of host files.
|
||||
|
||||
When gateway TLS is enabled, configure the gateway-owned `guest_tls_ca`.
|
||||
The host supervisor verifies the gateway certificate with that CA and uses
|
||||
main's launch authentication bundle. Gateway client certificates and keys are
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -165,7 +165,7 @@ fn network_json(network: &MxcNetwork) -> serde_json::Value {
|
||||
value
|
||||
}
|
||||
|
||||
fn oneshot_config_json(
|
||||
pub fn oneshot_config_json(
|
||||
container_id: &str,
|
||||
filesystem: &MxcFilesystem,
|
||||
pc: &MxcProcessContainer,
|
||||
@@ -209,6 +209,13 @@ fn oneshot_config_json(
|
||||
"processContainer": serde_json::Value::Object(pc_json),
|
||||
"filesystem": serde_json::Value::Object(filesystem_json),
|
||||
});
|
||||
// MXC 1.0 defaults `fallback.allowDaclMutation` to true, so on a host whose
|
||||
// base container cannot enforce `deniedPaths` it would add deny entries to
|
||||
// the ACLs of host files instead. Withhold that consent whenever paths are
|
||||
// denied; such a host then fails the launch.
|
||||
if !filesystem.denied_paths.is_empty() {
|
||||
config["fallback"] = serde_json::json!({ "allowDaclMutation": false });
|
||||
}
|
||||
if let Some(network) = network {
|
||||
config["network"] = network_json(network);
|
||||
}
|
||||
|
||||
@@ -94,7 +94,7 @@ pub trait PolicyMapper: Send + Sync {
|
||||
/// Normalize forward-slash paths to Windows backslash form. Path normalization
|
||||
/// lives here, in one place — the embedded mapper copies path strings through
|
||||
/// unchanged.
|
||||
fn normalize_path(p: &str) -> String {
|
||||
pub fn normalize_path(p: &str) -> String {
|
||||
p.replace('/', "\\")
|
||||
}
|
||||
|
||||
|
||||
@@ -1005,6 +1005,369 @@ fn pc_oneshot_out_of_policy_write_denied() {
|
||||
);
|
||||
}
|
||||
|
||||
// ── Denied paths and directory renames ───────────────────────────────────────
|
||||
|
||||
/// Set in the environment of a `ProcessContainer` that runs this test binary
|
||||
/// as the workload of `pc_accepted_denied_paths_survive_directory_renames`.
|
||||
/// The value is the JSON plan `denied_paths_rename_workload` follows.
|
||||
const RENAME_WORKLOAD_PLAN: &str = "OPENSHELL_MXC_RENAME_WORKLOAD_PLAN";
|
||||
|
||||
/// Contents of every denied file in the rename fixtures.
|
||||
const RENAME_SECRET: &str = "openshell-mxc-rename-fixture-secret";
|
||||
|
||||
/// Workload half of `pc_accepted_denied_paths_survive_directory_renames`. It
|
||||
/// does nothing unless `RENAME_WORKLOAD_PLAN` is set. With a plan it reads
|
||||
/// each protected file, then renames each listed path through the Win32
|
||||
/// rename call, reads every protected file below it at its new path, renames
|
||||
/// it back, and writes what happened to the report file. Command-shell `ren` and `move` cannot stand in for this: in a
|
||||
/// `ProcessContainer` they fail for every path in a read-write grant, with or
|
||||
/// without denied paths.
|
||||
#[test]
|
||||
#[ignore = "requires real wxc-exec"]
|
||||
fn denied_paths_rename_workload() {
|
||||
let Ok(plan) = std::env::var(RENAME_WORKLOAD_PLAN) else {
|
||||
return;
|
||||
};
|
||||
let plan: serde_json::Value = serde_json::from_str(&plan).expect("workload plan");
|
||||
let strings = |key: &str| -> Vec<PathBuf> {
|
||||
plan[key]
|
||||
.as_array()
|
||||
.expect("plan list")
|
||||
.iter()
|
||||
.map(|value| PathBuf::from(value.as_str().expect("plan path")))
|
||||
.collect()
|
||||
};
|
||||
let (protected, renamed) = (strings("protected"), strings("renamed"));
|
||||
let read = |path: &Path| match std::fs::read_to_string(path) {
|
||||
Ok(contents) => format!("read {}: {contents}", path.display()),
|
||||
Err(error) => format!("read {}: error {error}", path.display()),
|
||||
};
|
||||
let mut report = Vec::new();
|
||||
for path in &protected {
|
||||
report.push(read(path));
|
||||
}
|
||||
for target in &renamed {
|
||||
let mut moved = target.clone().into_os_string();
|
||||
moved.push(".moved");
|
||||
let moved = PathBuf::from(moved);
|
||||
if let Err(error) = std::fs::rename(target, &moved) {
|
||||
report.push(format!("rename {}: error {error}", target.display()));
|
||||
continue;
|
||||
}
|
||||
report.push(format!("rename {}: renamed", target.display()));
|
||||
for path in &protected {
|
||||
if let Ok(rest) = path.strip_prefix(target) {
|
||||
report.push(read(&moved.join(rest)));
|
||||
}
|
||||
}
|
||||
if let Err(error) = std::fs::rename(&moved, target) {
|
||||
report.push(format!("rename back {}: error {error}", moved.display()));
|
||||
}
|
||||
}
|
||||
report.push("done".to_string());
|
||||
std::fs::write(
|
||||
plan["report"].as_str().expect("report path"),
|
||||
report.join("\n"),
|
||||
)
|
||||
.expect("write report");
|
||||
}
|
||||
|
||||
/// One filesystem layout for `pc_accepted_denied_paths_survive_directory_renames`,
|
||||
/// relative to its fixture directory.
|
||||
struct RenameShape {
|
||||
name: &'static str,
|
||||
read_write: &'static [&'static str],
|
||||
read_only: &'static [&'static str],
|
||||
denied: &'static [&'static str],
|
||||
}
|
||||
|
||||
/// The driver's preflight result for a layout under `root`.
|
||||
fn rename_shape_preflight(
|
||||
wxc: &Path,
|
||||
root: &Path,
|
||||
shape: &RenameShape,
|
||||
) -> Result<(), tonic::Status> {
|
||||
use openshell_core::proto::compute::v1::{
|
||||
DriverSandbox, DriverSandboxSpec, DriverSandboxTemplate,
|
||||
};
|
||||
let absolute = |paths: &[&str]| -> Vec<String> {
|
||||
paths
|
||||
.iter()
|
||||
.map(|path| root.join(path).to_string_lossy().into_owned())
|
||||
.collect()
|
||||
};
|
||||
let test_binary = std::env::current_exe()
|
||||
.expect("test binary")
|
||||
.to_string_lossy()
|
||||
.into_owned();
|
||||
let backend = openshell_driver_mxc::MxcComputeBackend::new(
|
||||
"rename-shapes",
|
||||
openshell_driver_mxc::MxcComputeConfig {
|
||||
allow_driver_config: true,
|
||||
wxc_exec_path: wxc.to_string_lossy().into_owned(),
|
||||
grpc_endpoint: "http://127.0.0.1:1".into(),
|
||||
supervisor_binary_path: test_binary.clone(),
|
||||
sandbox_binary_path: test_binary,
|
||||
state_dir: root.join("not-created"),
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
let serde_json::Value::Object(config) = serde_json::json!({
|
||||
"command": ["C:\\Windows\\System32\\cmd.exe", "/c", "exit 0"],
|
||||
"cwd": root.join("run"),
|
||||
"denied_paths": absolute(shape.denied),
|
||||
}) else {
|
||||
unreachable!()
|
||||
};
|
||||
let sandbox = DriverSandbox {
|
||||
id: "rename-shapes".into(),
|
||||
name: "rename-shapes".into(),
|
||||
spec: Some(DriverSandboxSpec {
|
||||
policy: Some(SandboxPolicy {
|
||||
version: 1,
|
||||
filesystem: Some(FilesystemPolicy {
|
||||
read_write: absolute(shape.read_write),
|
||||
read_only: absolute(shape.read_only),
|
||||
..Default::default()
|
||||
}),
|
||||
..Default::default()
|
||||
}),
|
||||
template: Some(DriverSandboxTemplate {
|
||||
driver_config: Some(
|
||||
openshell_core::proto_struct::json_object_to_struct(config).expect("config"),
|
||||
),
|
||||
..Default::default()
|
||||
}),
|
||||
..Default::default()
|
||||
}),
|
||||
..Default::default()
|
||||
};
|
||||
backend.validate_sandbox_create(&sandbox)
|
||||
}
|
||||
|
||||
/// Whether `wxc-exec --probe` reports a base container that enforces
|
||||
/// `deniedPaths` without changing host ACLs.
|
||||
fn probe_denied_paths(wxc: &Path) -> Result<(), String> {
|
||||
let out = Command::new(wxc)
|
||||
.arg("--probe")
|
||||
.output()
|
||||
.map_err(|e| format!("wxc-exec --probe spawn failed: {e}"))?;
|
||||
let probe: serde_json::Value = serde_json::from_slice(&out.stdout)
|
||||
.map_err(|e| format!("could not parse wxc-exec --probe JSON: {e}"))?;
|
||||
let supported = probe
|
||||
.pointer("/probes/baseContainerSupportsDenyPaths")
|
||||
.and_then(serde_json::Value::as_bool);
|
||||
let augmentation = probe["needsDaclAugmentation"].as_bool();
|
||||
if supported == Some(true) && augmentation == Some(false) {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(format!(
|
||||
"baseContainerSupportsDenyPaths={supported:?}, needsDaclAugmentation={augmentation:?}"
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
/// Every denied-path layout the driver accepts keeps the denied files
|
||||
/// unreadable when the workload renames the directories above them (or the
|
||||
/// denied paths themselves). The layouts include the ones the driver rejects
|
||||
/// because a rename exposes them; on a driver that accepted those, this test
|
||||
/// fails with the denied contents in the workload's report.
|
||||
#[test]
|
||||
#[ignore = "requires real wxc-exec"]
|
||||
fn pc_accepted_denied_paths_survive_directory_renames() {
|
||||
let Some(wxc) = wxc_path() else {
|
||||
eprintln!("SKIP: wxc-exec not found");
|
||||
return;
|
||||
};
|
||||
if let Err(reason) = probe_processcontainer(&wxc) {
|
||||
eprintln!("SKIP: processcontainer not live: {reason}");
|
||||
return;
|
||||
}
|
||||
if let Err(reason) = probe_denied_paths(&wxc) {
|
||||
eprintln!("SKIP: denied paths not enforced by the base container: {reason}");
|
||||
return;
|
||||
}
|
||||
let shapes = [
|
||||
RenameShape {
|
||||
name: "two levels below a read-write grant",
|
||||
read_write: &["work"],
|
||||
read_only: &[],
|
||||
denied: &["work\\profile\\.env"],
|
||||
},
|
||||
RenameShape {
|
||||
name: "directly inside a read-write grant nested in another",
|
||||
read_write: &["work", "work\\profile"],
|
||||
read_only: &[],
|
||||
denied: &["work\\profile\\.env"],
|
||||
},
|
||||
RenameShape {
|
||||
name: "below a read-only grant two levels inside a read-write grant",
|
||||
read_write: &["work"],
|
||||
read_only: &["work\\profile\\sub"],
|
||||
denied: &["work\\profile\\sub\\.env"],
|
||||
},
|
||||
RenameShape {
|
||||
name: "directly inside a read-write grant",
|
||||
read_write: &["work"],
|
||||
read_only: &[],
|
||||
denied: &["work\\.env", "work\\secrets"],
|
||||
},
|
||||
RenameShape {
|
||||
name: "below a read-only grant",
|
||||
read_write: &[],
|
||||
read_only: &["work"],
|
||||
denied: &["work\\profile\\.env"],
|
||||
},
|
||||
RenameShape {
|
||||
name: "below a read-only grant inside a read-write grant",
|
||||
read_write: &["work"],
|
||||
read_only: &["work\\profile"],
|
||||
denied: &["work\\profile\\.env", "work\\profile\\sub\\.env"],
|
||||
},
|
||||
RenameShape {
|
||||
name: "directly inside a read-write grant within a read-only grant",
|
||||
read_write: &["work\\profile"],
|
||||
read_only: &["work"],
|
||||
denied: &["work\\profile\\.env"],
|
||||
},
|
||||
];
|
||||
let must_accept = [
|
||||
"directly inside a read-write grant",
|
||||
"below a read-only grant",
|
||||
"below a read-only grant inside a read-write grant",
|
||||
"directly inside a read-write grant within a read-only grant",
|
||||
];
|
||||
let test_binary = std::env::current_exe().expect("test binary");
|
||||
let mut failures = Vec::new();
|
||||
for (index, shape) in shapes.iter().enumerate() {
|
||||
let fixture = tempfile::tempdir().expect("rename fixture");
|
||||
let root = fixture.path();
|
||||
let run = root.join("run");
|
||||
std::fs::create_dir_all(&run).unwrap();
|
||||
for directory in ["work\\profile\\sub", "work\\secrets"] {
|
||||
std::fs::create_dir_all(root.join(directory)).unwrap();
|
||||
}
|
||||
let secret_files = [
|
||||
"work\\.env",
|
||||
"work\\secrets\\key.txt",
|
||||
"work\\profile\\.env",
|
||||
"work\\profile\\sub\\.env",
|
||||
];
|
||||
for file in secret_files {
|
||||
std::fs::write(root.join(file), RENAME_SECRET).unwrap();
|
||||
}
|
||||
match rename_shape_preflight(&wxc, root, shape) {
|
||||
Err(status) => {
|
||||
assert_eq!(
|
||||
status.code(),
|
||||
tonic::Code::InvalidArgument,
|
||||
"{}: {}",
|
||||
shape.name,
|
||||
status.message()
|
||||
);
|
||||
eprintln!("{}: refused at preflight: {}", shape.name, status.message());
|
||||
if must_accept.contains(&shape.name) {
|
||||
failures.push(format!("{}: refused: {}", shape.name, status.message()));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
Ok(()) => eprintln!("{}: accepted at preflight", shape.name),
|
||||
}
|
||||
// The workload renames each denied path and every directory above it
|
||||
// inside the fixture, and reads the fixture files they protect.
|
||||
let mut renamed: Vec<PathBuf> = Vec::new();
|
||||
for denied in shape.denied {
|
||||
for relative in Path::new(denied).ancestors() {
|
||||
let path = root.join(relative);
|
||||
if !relative.as_os_str().is_empty() && !renamed.contains(&path) {
|
||||
renamed.push(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
let protected: Vec<PathBuf> = secret_files
|
||||
.iter()
|
||||
.map(|file| root.join(file))
|
||||
.filter(|file| {
|
||||
shape
|
||||
.denied
|
||||
.iter()
|
||||
.any(|denied| file.starts_with(root.join(denied)))
|
||||
})
|
||||
.collect();
|
||||
let report = run.join("report.txt");
|
||||
let absolute = |paths: &[&str]| -> Vec<String> {
|
||||
paths
|
||||
.iter()
|
||||
.map(|path| root.join(path).to_string_lossy().into_owned())
|
||||
.collect()
|
||||
};
|
||||
let plan = serde_json::json!({
|
||||
"report": report,
|
||||
"protected": protected,
|
||||
"renamed": renamed,
|
||||
});
|
||||
let mut read_write = vec![run.to_string_lossy().into_owned()];
|
||||
read_write.extend(absolute(shape.read_write));
|
||||
let mut read_only = absolute(shape.read_only);
|
||||
read_only.push(test_binary.to_string_lossy().into_owned());
|
||||
let config = serde_json::json!({
|
||||
"version": "1.0.0",
|
||||
"containerId": format!("pc-denied-rename-{index}"),
|
||||
"containment": "processcontainer",
|
||||
"process": {
|
||||
"commandLine": format!(
|
||||
"\"{}\" denied_paths_rename_workload --exact --ignored --test-threads=1",
|
||||
test_binary.display()
|
||||
),
|
||||
"cwd": run,
|
||||
"env": [format!("{RENAME_WORKLOAD_PLAN}={plan}")],
|
||||
"inheritDefaultEnv": true,
|
||||
"timeout": 60_000,
|
||||
},
|
||||
"filesystem": {
|
||||
"readwritePaths": read_write,
|
||||
"readonlyPaths": read_only,
|
||||
"deniedPaths": absolute(shape.denied),
|
||||
},
|
||||
"processContainer": { "leastPrivilege": false },
|
||||
"fallback": { "allowDaclMutation": false },
|
||||
"ui": { "disable": true, "clipboard": "none", "injection": false },
|
||||
});
|
||||
let b64 = base64::engine::general_purpose::STANDARD
|
||||
.encode(serde_json::to_string(&config).unwrap().as_bytes());
|
||||
let out = Command::new(&wxc)
|
||||
.arg("--config-base64")
|
||||
.arg(&b64)
|
||||
.output()
|
||||
.expect("wxc-exec spawn");
|
||||
let report = std::fs::read_to_string(&report).unwrap_or_else(|error| {
|
||||
panic!(
|
||||
"{}: no workload report ({error})\nstdout={}\nstderr={}",
|
||||
shape.name,
|
||||
String::from_utf8_lossy(&out.stdout),
|
||||
String::from_utf8_lossy(&out.stderr)
|
||||
)
|
||||
});
|
||||
eprintln!("{}:\n{report}", shape.name);
|
||||
assert!(report.ends_with("done"), "{}: {report}", shape.name);
|
||||
if report.contains(RENAME_SECRET) {
|
||||
failures.push(format!(
|
||||
"{}: denied contents read after a rename",
|
||||
shape.name
|
||||
));
|
||||
}
|
||||
for file in secret_files {
|
||||
assert_eq!(
|
||||
std::fs::read_to_string(root.join(file)).ok().as_deref(),
|
||||
Some(RENAME_SECRET),
|
||||
"{}: {file} changed on the host",
|
||||
shape.name
|
||||
);
|
||||
}
|
||||
}
|
||||
assert!(failures.is_empty(), "{failures:#?}");
|
||||
}
|
||||
|
||||
// ── Isolation session enforcement tests ──────────────────────────────────────
|
||||
|
||||
/// Full `isolation_session` round trip: provision → start → exec → stop →
|
||||
|
||||
@@ -1330,6 +1330,37 @@ openshell sandbox create --name mxc-demo --detach --policy policy.yaml `
|
||||
--driver-config-json $config
|
||||
```
|
||||
|
||||
To hide files or directories inside the policy's filesystem grants, such as
|
||||
credential files in a writable working directory, add `denied_paths`. With a
|
||||
policy that grants `C:\work\profile` read-write:
|
||||
|
||||
```shell
|
||||
$config = '{"mxc":{"command":["C:\\Windows\\System32\\cmd.exe","/d","/c","agent.cmd"],"cwd":"C:\\work","denied_paths":["C:\\work\\profile\\.env","C:\\work\\profile\\auth.json"]}}'
|
||||
```
|
||||
|
||||
The workload cannot open a denied path: it cannot read, write or delete a
|
||||
denied file, list a denied directory, or create a denied path by name. A denied
|
||||
file's name, size, timestamps and attributes remain visible, and a hard link to
|
||||
the file at another path the workload can read still exposes its contents. MXC
|
||||
does not check the target of a rename against denied paths, so in a writable
|
||||
directory the workload can rename its own file onto a denied name: this
|
||||
replaces the denied file on the host, or creates it when it is absent, without
|
||||
exposing the original contents. An existing denied directory cannot be
|
||||
replaced, and denying the directory that holds such files prevents this.
|
||||
|
||||
Each entry must be an absolute path that starts with a drive letter and whose
|
||||
parent directory exists, and must not equal or contain a filesystem grant.
|
||||
MXC enforces paths: the workload can rename a directory that is writable to it
|
||||
when the directory's parent is writable too, and an entry below that directory
|
||||
is then readable under a new path. The driver rejects such entries, including
|
||||
entries two or more levels below a read-write grant root and entries inside a
|
||||
read-write grant nested in another one; the error names the directory to deny
|
||||
instead. Paths are compared after resolving 8.3 short names, junctions, letter
|
||||
case, and the `\\.\`, `\\?\`, `\??\`, `GLOBALROOT` and volume GUID spellings
|
||||
MXC accepts for grants; with denied paths, a UNC grant or one that does not
|
||||
resolve to a local drive is rejected. These checks run before provisioning.
|
||||
Denied paths are fixed at sandbox creation.
|
||||
|
||||
Supply workload environment through `sandbox create --env` or `--env-from`;
|
||||
it is not part of gateway configuration. The command runs from host files,
|
||||
not an OCI workload image.
|
||||
|
||||
Reference in New Issue
Block a user