From 5041123c687ea59e21e2a40f56370d61acab0c30 Mon Sep 17 00:00:00 2001 From: "Park.Jiyeon" Date: Fri, 1 Aug 2025 09:32:39 +0800 Subject: [PATCH] agent: avoid full file reads when scanning sealed secrets. Read only the sealed secret prefix instead of the whole file. Improves performance and reduces memory usage in I/O-heavy environments. Fixes: #11643 Signed-off-by: Park.Jiyeon --- src/agent/src/confidential_data_hub/mod.rs | 49 ++++++++++++++++++++-- 1 file changed, 46 insertions(+), 3 deletions(-) diff --git a/src/agent/src/confidential_data_hub/mod.rs b/src/agent/src/confidential_data_hub/mod.rs index 7099251d31..07a9e386ad 100644 --- a/src/agent/src/confidential_data_hub/mod.rs +++ b/src/agent/src/confidential_data_hub/mod.rs @@ -22,6 +22,8 @@ use protocols::{ }; use safe_path::scoped_join; use std::fs; +use std::fs::File; +use std::io::{self, Read}; use std::path::Path; use std::{os::unix::fs::symlink, path::PathBuf}; use tokio::sync::OnceCell; @@ -235,8 +237,8 @@ pub async fn unseal_file(path: &str) -> Result<()> { } let secret_name = entry.file_name(); - let contents = fs::read_to_string(&target_path)?; - if contents.starts_with(SEALED_SECRET_PREFIX) { + if content_starts_with_prefix(&target_path, SEALED_SECRET_PREFIX).await? { + let contents = fs::read_to_string(&target_path)?; // Get the directory name of the sealed secret file let dir_name = target_path .parent() @@ -262,6 +264,17 @@ pub async fn unseal_file(path: &str) -> Result<()> { Ok(()) } +pub async fn content_starts_with_prefix(path: &Path, prefix: &str) -> io::Result { + let mut file = File::open(path)?; + let mut buffer = vec![0u8; prefix.len()]; + + match file.read_exact(&mut buffer) { + Ok(()) => Ok(buffer == prefix.as_bytes()), + Err(ref e) if e.kind() == io::ErrorKind::UnexpectedEof => Ok(false), + Err(e) => Err(e), + } +} + pub async fn secure_mount( volume_type: &str, options: &std::collections::HashMap, @@ -294,7 +307,7 @@ mod tests { use std::fs::File; use std::io::{Read, Write}; use std::sync::Arc; - use tempfile::tempdir; + use tempfile::{tempdir, NamedTempFile}; use test_utils::skip_if_not_root; use tokio::signal::unix::{signal, SignalKind}; struct TestService; @@ -416,4 +429,34 @@ mod tests { rt.shutdown_background(); std::thread::sleep(std::time::Duration::from_secs(2)); } + + #[tokio::test] + fn test_content_starts_with_prefix() { + // Normal case: content matches the prefix + let mut f = NamedTempFile::new().unwrap(); + write!(f, "sealed.hello_world").unwrap(); + assert!(content_starts_with_prefix(f.path(), "sealed.") + .await + .unwrap()); + + // Does not match the prefix + let mut f2 = NamedTempFile::new().unwrap(); + write!(f2, "notsealed.hello_world").unwrap(); + assert!(!content_starts_with_prefix(f2.path(), "sealed.") + .await + .unwrap()); + + // File length < prefix.len() + let mut f3 = NamedTempFile::new().unwrap(); + write!(f3, "seal").unwrap(); + assert!(!content_starts_with_prefix(f3.path(), "sealed.") + .await + .unwrap()); + + // Empty file + let f4 = NamedTempFile::new().unwrap(); + assert!(!content_starts_with_prefix(f4.path(), "sealed.") + .await + .unwrap()); + } }