Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions attestation-agent/attestation-agent/src/bin/grpc-aa/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ rpc: grpc

aa.init().await.context("init AA")?;
debug!(
"Attestation gRPC service listening on: {:?}",
"Starting Attestation gRPC service on: {:?}",
cli.attestation_sock
);

Expand All @@ -155,7 +155,10 @@ rpc: grpc
tokio::select! {
_ = hangup.recv() => info!("Client terminal disconnected."),
_ = interrupt.recv() => info!("SIGINT received, gracefully shutdown."),
_ = server::start_grpc_service(attestation_socket, aa) => info!("AA exits."),
result = server::start_grpc_service(attestation_socket, aa) => {
result.with_context(|| format!("serve Attestation gRPC service on {attestation_socket}"))?;
info!("AA exits.");
},
}

Ok(())
Expand Down
25 changes: 25 additions & 0 deletions attestation-agent/attestation-agent/src/bin/grpc-aa/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -218,3 +218,28 @@ pub async fn start_grpc_service(socket: SocketAddr, aa: AttestationAgent) -> Res
.await?;
Ok(())
}

#[cfg(test)]
mod tests {
use super::*;
use attestation_agent::config::Config;
use std::net::TcpListener;
use std::time::Duration;

#[tokio::test]
async fn occupied_port_returns_server_error() {
let listener = TcpListener::bind("127.0.0.1:0").unwrap();
let address = listener.local_addr().unwrap();
let aa = AttestationAgent::new(Config::default()).unwrap();
let error = tokio::time::timeout(Duration::from_secs(5), start_grpc_service(address, aa))
.await
.expect("binding an occupied address should fail promptly")
.expect_err("the server must propagate the bind error");
assert!(
error.chain().any(|cause| cause
.downcast_ref::<std::io::Error>()
.is_some_and(|error| error.kind() == std::io::ErrorKind::AddrInUse)),
"expected an AddrInUse error in the cause chain: {error:#}"
);
}
}
74 changes: 74 additions & 0 deletions attestation-agent/attestation-agent/tests/grpc_startup.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// SPDX-License-Identifier: Apache-2.0

#![cfg(all(unix, feature = "bin", feature = "grpc"))]

use std::io::Write;
use std::net::TcpListener;
use std::process::{Command, Output, Stdio};
use std::time::{Duration, Instant};

fn run_grpc_aa(address: &str) -> Output {
// Disable eventlog and token configuration: no TEE, KBS, or /run access
// is needed to exercise the process's server startup error handling.
let mut config = tempfile::Builder::new().suffix(".toml").tempfile().unwrap();
write!(
config,
"[eventlog_config]\ninit_pcr = 17\nenable_eventlog = false\n[log]\nlevel = \"debug\"\n"
)
.unwrap();
let mut child = Command::new(env!("CARGO_BIN_EXE_grpc-aa"))
.args(["--attestation_sock", address, "--config-file"])
.arg(config.path())
.env("RUST_LOG", "debug")
.stdin(Stdio::null())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.unwrap();
let deadline = Instant::now() + Duration::from_secs(10);
loop {
if child.try_wait().unwrap().is_some() {
return child.wait_with_output().unwrap();
}
if Instant::now() >= deadline {
child.kill().unwrap();
let output = child.wait_with_output().unwrap();
panic!("grpc-aa did not exit within 10 seconds: {output:?}");
}
std::thread::sleep(Duration::from_millis(20));
}
}

#[test]
fn invalid_address_reports_failure() {
let output = run_grpc_aa("not-a-socket-address");
assert!(!output.status.success(), "{output:?}");
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(stderr.contains("invalid socket address"), "{stderr}");
}

#[test]
fn occupied_port_reports_failure() {
let listener = TcpListener::bind("127.0.0.1:0").unwrap();
let address = listener.local_addr().unwrap().to_string();
// Keep the listener alive until the subprocess exits to avoid a port race.
let output = run_grpc_aa(&address);
let logs = format!(
"{}\n{}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
assert!(
!output.status.success(),
"grpc-aa exited with {} despite occupied address {address}:\n{logs}",
output.status
);
assert!(
logs.contains("Address already in use") || logs.contains("address already in use"),
"missing bind error details:\n{logs}"
);
assert!(
!logs.contains("service listening on"),
"reported listening despite a bind failure:\n{logs}"
);
}