v0.8.0: dep prune, memtest introspection fix, concurrent uploads, x-api-key
Dependency cleanup (ponytail audit): - Drop 14 unused dependency declarations across 7 crates; quick-xml and x509-parser leave the tree entirely (SAML cert/XML work is handled by bergshamra + roxmltree). Fixes: - introspect: drop the over-broad "microsoft" UTF-16 bulk-scan marker that mislabeled Secure-Boot-signed non-Windows bootables (memtest86, signed BSDs, firmware tools) as Windows — the string lives in their MS-signed EFI loader's FAT long-filename entries. INTROSPECT_REV 3 -> 4 re-probes existing local ISOs on startup so the bogus label clears on upgrade. - upload: begin_upload now reclaims an abandoned <id>.partial instead of rejecting the re-upload with "already uploading". Robust against browser refresh, tab close, and dropped connections (the chunked protocol can't resume a dead session anyway). Features: - Storage upload: multi-file + concurrent. Each dropped/selected .iso gets its own progress row and uploads independently; a single page-leave guard plus a pagehide keepalive-abort replace the old shared singletons. - Operator API key (x-api-key): a persisted key authenticates /api/* exactly like an operator session, for Postman/scripts. New core ApiKeyStore (generated on first run, regenerable), accepted in require_auth alongside the session cookie, surfaced in Settings -> Advanced with copy + regenerate and a usage reference. GET /api/api-key + POST /api/api-key/regenerate. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
27703c437a
commit
1c262a6d61
@@ -12,16 +12,13 @@ workspace = true
|
||||
[dependencies]
|
||||
openpxe-core.workspace = true
|
||||
tokio = { workspace = true }
|
||||
tokio-util = { workspace = true }
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
tracing.workspace = true
|
||||
thiserror.workspace = true
|
||||
anyhow.workspace = true
|
||||
sha2.workspace = true
|
||||
hex.workspace = true
|
||||
bcrypt.workspace = true
|
||||
uuid.workspace = true
|
||||
time.workspace = true
|
||||
parking_lot.workspace = true
|
||||
bytes.workspace = true
|
||||
|
||||
@@ -60,7 +60,12 @@ pub enum DistroFamily {
|
||||
/// descriptor scans. Without this bump, images the rev-2 logic flagged
|
||||
/// as data ISOs (mangled-primary appliance images, filler-sector boot
|
||||
/// records) would never re-probe and stay mislabeled.
|
||||
pub const INTROSPECT_REV: u32 = 3;
|
||||
/// rev 4 (v0.8.0): dropped the over-broad "microsoft" UTF-16 bulk-scan
|
||||
/// marker that classified any Secure-Boot-signed non-Windows bootable
|
||||
/// (memtest86, signed BSDs, firmware tools) as Windows — the string
|
||||
/// lives in the FAT long-filename entries of their MS-signed EFI loader.
|
||||
/// The bump re-probes those so they drop the bogus Windows label.
|
||||
pub const INTROSPECT_REV: u32 = 4;
|
||||
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
||||
pub struct IntrospectionReport {
|
||||
@@ -414,7 +419,13 @@ async fn bulk_windows_scan<R: IsoReadAt + Send>(r: &mut R, total_len: u64) -> Op
|
||||
return Some(true);
|
||||
}
|
||||
let ascii_markers: [&[u8]; 3] = [b"bootmgr", b"sources/install.wim", b"sources/install.esd"];
|
||||
let utf16_markers = ["bootmgr", "install.wim", "microsoft"];
|
||||
// v0.8.0: dropped the bare "microsoft" marker. It matched the
|
||||
// Microsoft-signed Secure-Boot EFI loader that memtest86 (and signed
|
||||
// BSDs / firmware tools) ship — the string lives in the loader's FAT
|
||||
// long-filename entries — so any signed non-Windows bootable
|
||||
// false-classified as Windows. The remaining markers are all
|
||||
// Windows-exclusive filenames.
|
||||
let utf16_markers = ["bootmgr", "install.wim"];
|
||||
let hit = ascii_markers.iter().any(|m| contains_ascii(&haystack, m))
|
||||
|| utf16_markers
|
||||
.iter()
|
||||
@@ -775,4 +786,26 @@ mod tests {
|
||||
assert_eq!(r.family, DistroFamily::WindowsPe);
|
||||
assert!(!r.has_boot_wim);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn memtest_signed_efi_is_not_windows() {
|
||||
// v0.8.0 regression: PassMark MemTest86 ships a Microsoft-signed
|
||||
// Secure-Boot EFI loader, and "Microsoft" appears in its FAT
|
||||
// long-filename entries as UTF-16LE. The old bulk-scan "microsoft"
|
||||
// marker classified it (and any signed BSD / firmware tool) as
|
||||
// Windows. It must now classify as a generic bootable (sanboot).
|
||||
let mut img = TestIsoBuilder::new("MEMTEST86")
|
||||
.el_torito(true)
|
||||
.file("/EFI/BOOT/BOOTX64.EFI", b"signed-efi-app")
|
||||
.build();
|
||||
let marker: Vec<u8> = "Microsoft".bytes().flat_map(|b| [b, 0]).collect();
|
||||
img.extend_from_slice(&marker);
|
||||
let r = introspect_mem(img, "memtest86-iso.iso", true);
|
||||
assert_ne!(
|
||||
r.family,
|
||||
DistroFamily::WindowsPe,
|
||||
"a Microsoft-signed EFI loader is not Windows media"
|
||||
);
|
||||
assert!(r.el_torito, "still a bootable image");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -300,7 +300,19 @@ impl IsoStore {
|
||||
}
|
||||
let partial_path = self.iso_dir.join(format!("{id}.partial"));
|
||||
if partial_path.exists() {
|
||||
return Err(Error::Invalid(format!("iso '{id}' is already uploading")));
|
||||
// A leftover .partial is an upload abandoned mid-flight (browser
|
||||
// refresh, tab close, dropped connection) — nothing reaps it
|
||||
// otherwise, and the operator hits a bogus "already uploading"
|
||||
// on retry. The chunked protocol can't resume it anyway (a
|
||||
// fresh session restarts at offset 0), so reclaim it.
|
||||
// ponytail: two tabs uploading the *same filename* at once would
|
||||
// race here — last writer wins, and the truncating create below
|
||||
// keeps that from corrupting a half-written file.
|
||||
tracing::info!(
|
||||
target: "openpxe::iso", %id,
|
||||
"reclaiming abandoned .partial from a prior upload attempt"
|
||||
);
|
||||
tokio::fs::remove_file(&partial_path).await.ok();
|
||||
}
|
||||
let file = tokio::fs::File::create(&partial_path).await?;
|
||||
Ok(UploadHandle {
|
||||
@@ -872,15 +884,39 @@ mod tests {
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn begin_upload_rejects_existing_partial_file() {
|
||||
async fn begin_upload_reclaims_stale_partial_file() {
|
||||
// v0.8.0: an abandoned .partial (browser refresh / crash / dropped
|
||||
// connection) must not block a re-upload with a bogus "already
|
||||
// uploading" — begin_upload reclaims it and starts fresh, since the
|
||||
// chunked protocol can't resume a dead session anyway.
|
||||
let dir = tempdir().unwrap();
|
||||
let store = IsoStore::new(dir.path().to_path_buf());
|
||||
store.ensure_dirs().await.unwrap();
|
||||
tokio::fs::write(dir.path().join("ubuntu.partial"), b"in-flight")
|
||||
let partial = dir.path().join("ubuntu.partial");
|
||||
tokio::fs::write(&partial, b"in-flight").await.unwrap();
|
||||
|
||||
let handle = store
|
||||
.begin_upload("ubuntu.iso")
|
||||
.await
|
||||
.expect("stale .partial is reclaimed, not rejected");
|
||||
assert_eq!(handle.id, "ubuntu");
|
||||
// Reclaimed: the leftover bytes are gone (fresh, empty file).
|
||||
let meta = tokio::fs::metadata(&partial).await.unwrap();
|
||||
assert_eq!(meta.len(), 0, "stale .partial must be truncated on reclaim");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn begin_upload_still_rejects_completed_iso() {
|
||||
// A finished upload (final .iso on disk) is a genuine duplicate, not
|
||||
// an abandoned attempt — that case must still be refused.
|
||||
let dir = tempdir().unwrap();
|
||||
let store = IsoStore::new(dir.path().to_path_buf());
|
||||
store.ensure_dirs().await.unwrap();
|
||||
tokio::fs::write(dir.path().join("rocky.iso"), b"done")
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let r = store.begin_upload("ubuntu.iso").await;
|
||||
let r = store.begin_upload("rocky.iso").await;
|
||||
assert!(matches!(r, Err(Error::Invalid(_))));
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user