185 lines
6.5 KiB
Rust
185 lines
6.5 KiB
Rust
use std::fs;
|
|
use std::os::unix::fs::PermissionsExt;
|
|
use std::path::Path;
|
|
|
|
use wireguard_admin::app::{Application, RestartChoice, RestartStage};
|
|
use wireguard_admin::config::{ResolvedSettings, SettingsFile};
|
|
use wireguard_admin::domain::{
|
|
Client, ClientAddresses, ClientStates, RuntimeObservation, Snapshot,
|
|
};
|
|
use wireguard_admin::persistence::SavedStore;
|
|
use wireguard_admin::vault::WireGuardPrivateKey;
|
|
|
|
fn settings(service: &str, timeout: u64) -> String {
|
|
format!(
|
|
r#"
|
|
interface_name = "wg0"
|
|
server_config_path = "server.conf"
|
|
client_source_directory = "clients"
|
|
server_endpoint = "vpn.example.test:51820"
|
|
ipv4_allocation_range = "10.20.0.0/24"
|
|
ipv6_allocation_range = "fd20::/64"
|
|
dns_addresses = ["10.20.0.1"]
|
|
systemd_unit = "{service}"
|
|
restart_timeout_seconds = {timeout}
|
|
"#
|
|
)
|
|
}
|
|
|
|
struct Fixture {
|
|
_root: tempfile::TempDir,
|
|
store: SavedStore,
|
|
initial_settings: String,
|
|
}
|
|
|
|
fn fixture(service: &str, timeout: u64) -> Fixture {
|
|
let root = tempfile::tempdir().expect("temporary saved store");
|
|
let initial_settings = settings(service, timeout);
|
|
let settings = SettingsFile::parse_toml(&initial_settings)
|
|
.expect("synthetic settings parse")
|
|
.validate()
|
|
.expect("synthetic settings validate")
|
|
.resolve_paths(root.path());
|
|
fs::create_dir_all(&settings.client_source_directory).expect("client directory");
|
|
fs::create_dir_all(&settings.data_directory).expect("data directory");
|
|
fs::create_dir_all(&settings.archives_directory).expect("archive directory");
|
|
fs::write(
|
|
ResolvedSettings::settings_path(root.path()),
|
|
initial_settings.as_bytes(),
|
|
)
|
|
.expect("install synthetic settings");
|
|
fs::write(
|
|
&settings.server_config_path,
|
|
b"[Interface]\nPrivateKey = synthetic-server-key\nAddress = 10.20.0.1/24\n",
|
|
)
|
|
.expect("install synthetic server configuration");
|
|
fs::set_permissions(
|
|
&settings.server_config_path,
|
|
fs::Permissions::from_mode(0o600),
|
|
)
|
|
.expect("restrict synthetic server configuration");
|
|
let store = SavedStore::new(root.path(), settings.clone()).expect("saved store mapping");
|
|
Fixture {
|
|
_root: root,
|
|
store,
|
|
initial_settings,
|
|
}
|
|
}
|
|
|
|
fn open(fixture: &Fixture) -> Application {
|
|
let states = ClientStates::new(Snapshot::default(), None, RuntimeObservation::Unknown)
|
|
.expect("empty saved states");
|
|
Application::open(fixture.store.clone(), states).expect("open synthetic application")
|
|
}
|
|
|
|
fn archive_name(path: &Path) -> &str {
|
|
path.file_name()
|
|
.and_then(|name| name.to_str())
|
|
.expect("archive filename")
|
|
}
|
|
|
|
#[test]
|
|
fn public_restart_preview_and_cancel_preserve_draft_and_saved_files() {
|
|
let fixture = fixture("wg-quick@wg0.service", 30);
|
|
let mut app = open(&fixture);
|
|
let original_server_config = app.saved_bytes().server_config.clone();
|
|
let secret = WireGuardPrivateKey::generate().expect("synthetic client key");
|
|
let client = Client::new(
|
|
secret.public_key(),
|
|
"draft client",
|
|
ClientAddresses {
|
|
ipv4: "10.20.0.2".parse().expect("synthetic IPv4"),
|
|
ipv6: Some("fd20::2".parse().expect("synthetic IPv6")),
|
|
},
|
|
)
|
|
.expect("synthetic client");
|
|
app.add_client(client.clone(), None)
|
|
.expect("stage client draft");
|
|
|
|
let preview = app.preview_restart().expect("preview saved generation");
|
|
assert!(preview.has_draft());
|
|
assert_eq!(preview.interface(), "wg0");
|
|
assert_eq!(preview.systemd_unit(), "wg-quick@wg0.service");
|
|
assert!(app.list_backups().expect("list archives").is_empty());
|
|
|
|
// The public cancellation path returns before either real host adapter can run.
|
|
let report = app.restart(preview, RestartChoice::Cancel);
|
|
|
|
assert_eq!(report.stage, RestartStage::Cancelled);
|
|
assert!(!report.restart_submitted);
|
|
assert_eq!(app.saved_bytes().server_config, original_server_config);
|
|
assert!(!app.document().render().is_empty());
|
|
assert_eq!(
|
|
app.states()
|
|
.draft()
|
|
.get(&client.identity)
|
|
.map(|saved| saved.alias.as_str()),
|
|
Some("draft client")
|
|
);
|
|
assert!(
|
|
app.list_backups()
|
|
.expect("no cancellation backup")
|
|
.is_empty()
|
|
);
|
|
assert_eq!(app.states().applied(), &RuntimeObservation::Unknown);
|
|
}
|
|
|
|
#[test]
|
|
fn public_restart_preview_accepts_metadata_free_ipv4_and_broader_legacy_routes() {
|
|
let fixture = fixture("wg-quick@wg0.service", 30);
|
|
let server_path = fixture._root.path().join("server.conf");
|
|
let mut server = fs::read(&server_path).expect("synthetic server config reads");
|
|
server.extend_from_slice(
|
|
b"\n[Peer]\nPublicKey = AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\nAllowedIPs = 10.20.0.19/32\n\n[Peer]\nPublicKey = AQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQE=\nAllowedIPs = 192.0.2.0/24, 2001:db8::/48\n",
|
|
);
|
|
fs::write(&server_path, &server).expect("install synthetic legacy peers");
|
|
let app = Application::open_from_saved_store(fixture.store.clone())
|
|
.expect("open metadata-free legacy peers");
|
|
|
|
let preview = app.preview_restart().expect("valid routes are restartable");
|
|
|
|
assert_eq!(preview.interface(), "wg0");
|
|
assert_eq!(preview.systemd_unit(), "wg-quick@wg0.service");
|
|
assert_eq!(
|
|
fs::read(server_path).expect("server config remains readable"),
|
|
server
|
|
);
|
|
assert!(app.list_backups().expect("preview is read-only").is_empty());
|
|
}
|
|
|
|
#[test]
|
|
fn restore_changes_the_settings_used_by_the_next_restart_preview() {
|
|
let fixture = fixture("wg-quick@wg0.service", 30);
|
|
let app = open(&fixture);
|
|
let archive = app
|
|
.create_manual_backup()
|
|
.expect("archive original saved settings");
|
|
|
|
let replacement = settings("wg-quick@wg9.service", 90);
|
|
fs::write(
|
|
ResolvedSettings::settings_path(fixture._root.path()),
|
|
replacement.as_bytes(),
|
|
)
|
|
.expect("install a second synthetic saved settings generation");
|
|
drop(app);
|
|
|
|
let mut app = open(&fixture);
|
|
let preview = app
|
|
.preview_restore(archive_name(&archive))
|
|
.expect("preview archived saved settings");
|
|
app.confirm_restore(preview)
|
|
.expect("restore archived saved settings");
|
|
|
|
assert_eq!(
|
|
app.saved_bytes().settings,
|
|
fixture.initial_settings.as_bytes()
|
|
);
|
|
assert_eq!(app.states().applied(), &RuntimeObservation::Unknown);
|
|
let restart = app
|
|
.preview_restart()
|
|
.expect("preview restored generation without invoking adapters");
|
|
assert_eq!(restart.interface(), "wg0");
|
|
assert_eq!(restart.systemd_unit(), "wg-quick@wg0.service");
|
|
assert!(!restart.has_draft());
|
|
}
|