Files

183 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::{ApplyStatus, Client, ClientAddresses, ClientStates, 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, ApplyStatus::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(), &ApplyStatus::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(), &ApplyStatus::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());
}