mirror of
https://github.com/NVIDIA/OpenShell.git
synced 2026-10-21 06:11:31 +08:00
* feat(api)!: use sandbox names as canonical references Signed-off-by: Drew Newberry <anewberry@nvidia.com> * test(cli): update forward color fixture for workspace scope Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(supervisor): use sandbox names for settings lookup Signed-off-by: Drew Newberry <anewberry@nvidia.com> * test(e2e): use canonical sandbox request fields Signed-off-by: Drew Newberry <anewberry@nvidia.com> * test(e2e): use canonical sandbox receipt field Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(server): harden sandbox mutation handling Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(ci): update rebased sandbox references Signed-off-by: Drew Newberry <anewberry@nvidia.com> * feat(api)!: standardize canonical entity references Signed-off-by: Drew Newberry <anewberry@nvidia.com> * docs(api): codify protobuf API conventions Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(api): preserve workspace selector semantics Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(api): restore workspace selector parity Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(api): preserve descriptive name fields Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(api): update e2e request fixtures Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(core): omit workspace selector during bootstrap Signed-off-by: Drew Newberry <anewberry@nvidia.com> * chore(api): remove proto convention checker Signed-off-by: Drew Newberry <anewberry@nvidia.com> * chore(api): refresh schema fingerprints after rebase Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(cli): use canonical provider receipt field Signed-off-by: Drew Newberry <anewberry@nvidia.com> --------- Signed-off-by: Drew Newberry <anewberry@nvidia.com>
141 lines
4.9 KiB
Rust
141 lines
4.9 KiB
Rust
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
mod helpers;
|
|
|
|
use helpers::{build_ca, build_client_cert, build_server_cert};
|
|
use openshell_cli::run;
|
|
use openshell_cli::tls::{TlsOptions, grpc_client};
|
|
use openshell_core::proto::open_shell_server::OpenShellServer;
|
|
use openshell_core::proto::{CreateSandboxRequest, SandboxSpec};
|
|
use openshell_server::test_support::{authenticate_as_dev_user, gateway_service_with_driver};
|
|
use tempfile::TempDir;
|
|
use tokio::net::TcpListener;
|
|
use tokio_stream::wrappers::TcpListenerStream;
|
|
use tonic::transport::{Certificate as TlsCertificate, Identity, Server, ServerTlsConfig};
|
|
|
|
struct TestServer {
|
|
endpoint: String,
|
|
tls: TlsOptions,
|
|
_dir: TempDir,
|
|
}
|
|
|
|
async fn run_server() -> TestServer {
|
|
let (ca, ca_key) = build_ca();
|
|
let (server_cert, server_key) = build_server_cert(&ca, &ca_key);
|
|
let (client_cert, client_key) = build_client_cert(&ca, &ca_key);
|
|
let ca_cert = ca.pem();
|
|
|
|
let tls_config = ServerTlsConfig::new()
|
|
.identity(Identity::from_pem(server_cert, server_key))
|
|
.client_ca_root(TlsCertificate::from_pem(ca_cert.clone()));
|
|
let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
|
|
let addr = listener.local_addr().unwrap();
|
|
let service = gateway_service_with_driver("docker").await;
|
|
|
|
tokio::spawn(async move {
|
|
Server::builder()
|
|
.tls_config(tls_config)
|
|
.unwrap()
|
|
.add_service(OpenShellServer::with_interceptor(
|
|
service,
|
|
authenticate_as_dev_user,
|
|
))
|
|
.serve_with_incoming(TcpListenerStream::new(listener))
|
|
.await
|
|
.unwrap();
|
|
});
|
|
|
|
let dir = tempfile::tempdir().unwrap();
|
|
let ca_path = dir.path().join("ca.crt");
|
|
let cert_path = dir.path().join("tls.crt");
|
|
let key_path = dir.path().join("tls.key");
|
|
std::fs::write(&ca_path, ca_cert).unwrap();
|
|
std::fs::write(&cert_path, client_cert).unwrap();
|
|
std::fs::write(&key_path, client_key).unwrap();
|
|
|
|
TestServer {
|
|
endpoint: format!("https://localhost:{}", addr.port()),
|
|
tls: TlsOptions::new(Some(ca_path), Some(cert_path), Some(key_path)),
|
|
_dir: dir,
|
|
}
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn policy_only_preserves_all_process_identity_combinations_through_gateway() {
|
|
let server = run_server().await;
|
|
let cases = [
|
|
("neither", "process: {}", None),
|
|
(
|
|
"user-only",
|
|
"process:\n run_as_user: \"1500\"",
|
|
Some(("1500", "")),
|
|
),
|
|
(
|
|
"group-only",
|
|
"process:\n run_as_group: \"1600\"",
|
|
Some(("", "1600")),
|
|
),
|
|
(
|
|
"both",
|
|
"process:\n run_as_user: \"1500\"\n run_as_group: \"1600\"",
|
|
Some(("1500", "1600")),
|
|
),
|
|
];
|
|
|
|
for (name, process_yaml, expected) in cases {
|
|
let authored = format!("version: 1\n{process_yaml}\n");
|
|
let policy = openshell_policy::parse_sandbox_policy(&authored)
|
|
.unwrap_or_else(|error| panic!("{name}: authored policy should parse: {error}"));
|
|
|
|
let mut client = grpc_client(&server.endpoint, &server.tls).await.unwrap();
|
|
client
|
|
.create_sandbox(CreateSandboxRequest {
|
|
name: name.to_string(),
|
|
spec: Some(SandboxSpec {
|
|
policy: Some(policy),
|
|
..Default::default()
|
|
}),
|
|
workspace_scope: Some(openshell_core::proto::workspace_selector(
|
|
"default".to_string(),
|
|
)),
|
|
..Default::default()
|
|
})
|
|
.await
|
|
.unwrap_or_else(|error| panic!("{name}: gateway create should succeed: {error}"));
|
|
|
|
let mut output = Vec::new();
|
|
run::sandbox_get_to_writer(
|
|
&server.endpoint,
|
|
name,
|
|
true,
|
|
"table",
|
|
"default",
|
|
&server.tls,
|
|
&mut output,
|
|
)
|
|
.await
|
|
.unwrap_or_else(|error| panic!("{name}: --policy-only should succeed: {error}"));
|
|
|
|
let output = String::from_utf8(output).unwrap();
|
|
let exported = openshell_policy::parse_sandbox_policy(&output)
|
|
.unwrap_or_else(|error| panic!("{name}: exported YAML should parse: {error}"));
|
|
match expected {
|
|
None => {
|
|
assert!(
|
|
exported.process.is_none(),
|
|
"{name}: empty process must be omitted"
|
|
);
|
|
assert!(!output.contains("process:"), "{name}: {output}");
|
|
}
|
|
Some((user, group)) => {
|
|
let process = exported
|
|
.process
|
|
.expect("partial process should be retained");
|
|
assert_eq!(process.run_as_user, user, "{name}: {output}");
|
|
assert_eq!(process.run_as_group, group, "{name}: {output}");
|
|
}
|
|
}
|
|
}
|
|
}
|