Skip to content

Commit e74da3a

Browse files
amphiphip1611
authored andcommitted
vmm: use TLS encrypted live migration when TLS parameters are provided
For TLS we need certificates (and a key for the TLS server). This commits adds parameters for that and encrypts the connection with TLS if the necessary parameters are provided. On-behalf-of: SAP sebastian.eydam@sap.com Signed-off-by: Sebastian Eydam <sebastian.eydam@cyberus-technology.de>
1 parent d9330c0 commit e74da3a

File tree

3 files changed

+76
-33
lines changed

3 files changed

+76
-33
lines changed

src/bin/ch-remote.rs

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ use std::io::Read;
1111
use std::marker::PhantomData;
1212
use std::num::NonZeroU32;
1313
use std::os::unix::net::UnixStream;
14+
use std::path::PathBuf;
1415
use std::process;
1516

1617
use api_client::{
@@ -491,7 +492,8 @@ fn rest_api_do_command(matches: &ArgMatches, socket: &mut UnixStream) -> ApiResu
491492
.subcommand_matches("send-migration")
492493
.unwrap()
493494
.get_one::<String>("send_migration_config")
494-
.unwrap(),
495+
.unwrap()
496+
.to_owned(),
495497
matches
496498
.subcommand_matches("send-migration")
497499
.unwrap()
@@ -513,6 +515,11 @@ fn rest_api_do_command(matches: &ArgMatches, socket: &mut UnixStream) -> ApiResu
513515
.copied()
514516
.and_then(NonZeroU32::new)
515517
.unwrap_or(NonZeroU32::new(1).unwrap()),
518+
matches
519+
.subcommand_matches("send-migration")
520+
.unwrap()
521+
.get_one::<PathBuf>("tls_dir")
522+
.cloned(),
516523
);
517524
simple_api_command(socket, "PUT", "send-migration", Some(&send_migration_data))
518525
.map_err(Error::HttpApiClient)
@@ -523,7 +530,13 @@ fn rest_api_do_command(matches: &ArgMatches, socket: &mut UnixStream) -> ApiResu
523530
.subcommand_matches("receive-migration")
524531
.unwrap()
525532
.get_one::<String>("receive_migration_config")
526-
.unwrap(),
533+
.unwrap()
534+
.to_owned(),
535+
matches
536+
.subcommand_matches("receive_migration")
537+
.unwrap()
538+
.get_one::<PathBuf>("tls_dir")
539+
.cloned(),
527540
);
528541
simple_api_command(
529542
socket,
@@ -930,32 +943,35 @@ fn coredump_config(destination_url: &str) -> String {
930943
serde_json::to_string(&coredump_config).unwrap()
931944
}
932945

933-
fn receive_migration_data(url: &str) -> String {
946+
fn receive_migration_data(url: String, tls_dir: Option<PathBuf>) -> String {
934947
let receive_migration_data = vmm::api::VmReceiveMigrationData {
935-
receiver_url: url.to_owned(),
948+
receiver_url: url,
936949
tcp_serial_url: None,
937950
// Only FDs transmitted via an SCM_RIGHTS UNIX Domain Socket message
938951
// are valid. Transmitting specific FD nums via the HTTP API is
939952
// almost always invalid.
940953
net_fds: None,
954+
tls_dir,
941955
};
942956

943957
serde_json::to_string(&receive_migration_data).unwrap()
944958
}
945959

946960
fn send_migration_data(
947-
url: &str,
961+
url: String,
948962
local: bool,
949963
downtime: u64,
950964
migration_timeout: u64,
951965
connections: NonZeroU32,
966+
tls_dir: Option<PathBuf>,
952967
) -> String {
953968
let send_migration_data = vmm::api::VmSendMigrationData {
954-
destination_url: url.to_owned(),
969+
destination_url: url,
955970
local,
956971
downtime,
957972
migration_timeout,
958973
connections,
974+
tls_dir,
959975
};
960976

961977
serde_json::to_string(&send_migration_data).unwrap()

vmm/src/api/mod.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ pub mod http;
3535

3636
use std::io;
3737
use std::num::NonZeroU32;
38+
use std::path::PathBuf;
3839
use std::sync::mpsc::{RecvError, SendError, Sender, channel};
3940

4041
use micro_http::Body;
@@ -265,6 +266,9 @@ pub struct VmReceiveMigrationData {
265266
pub tcp_serial_url: Option<String>,
266267
/// Map with new network FDs on the new host.
267268
pub net_fds: Option<Vec<RestoredNetConfig>>,
269+
/// Directory containing the TLS server certificate (server-cert.pem) and TLS server key (server-key.pem).
270+
#[serde(default)]
271+
pub tls_dir: Option<PathBuf>,
268272
}
269273

270274
#[derive(Clone, Deserialize, Serialize, Debug)]
@@ -287,6 +291,9 @@ pub struct VmSendMigrationData {
287291
/// The number of parallel connections for migration
288292
#[serde(default = "default_connections")]
289293
pub connections: NonZeroU32,
294+
/// Directory containing the TLS root CA certificate (ca-cert.pem)
295+
#[serde(default)]
296+
pub tls_dir: Option<PathBuf>,
290297
}
291298

292299
// Default value for downtime the same as qemu.

vmm/src/lib.rs

Lines changed: 47 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ use std::collections::HashMap;
2121
use std::fs::File;
2222
use std::io::{ErrorKind, Read, Write, stdout};
2323
use std::net::{TcpListener, TcpStream};
24-
use std::num::NonZeroU32;
2524
use std::os::fd::{AsFd, BorrowedFd};
2625
use std::os::unix::io::{AsRawFd, FromRawFd, RawFd};
2726
use std::os::unix::net::{UnixListener, UnixStream};
@@ -60,7 +59,9 @@ use vm_memory::{
6059
};
6160
use vm_migration::protocol::*;
6261
use vm_migration::tls::{TlsConnectionWrapper, TlsStream};
63-
use vm_migration::{Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable};
62+
use vm_migration::{
63+
Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable, tls,
64+
};
6465
use vmm_sys_util::eventfd::EventFd;
6566
use vmm_sys_util::signal::unblock_signal;
6667
use vmm_sys_util::sock_ctrl_msg::ScmSocket;
@@ -1232,15 +1233,15 @@ impl SendAdditionalConnections {
12321233
const CHUNK_SIZE: u64 = 64 /* MiB */ << 20;
12331234

12341235
fn new(
1235-
destination: &str,
1236-
connections: NonZeroU32,
1236+
send_data_migration: &VmSendMigrationData,
12371237
guest_mem: &GuestMemoryAtomic<GuestMemoryMmap>,
12381238
) -> std::result::Result<Self, MigratableError> {
12391239
let mut threads = Vec::new();
12401240
let mut channels = Vec::new();
12411241

1242-
for n in 0..(connections.get() - 1) {
1243-
let socket = (match send_migration_socket(destination) {
1242+
let additional_connections = send_data_migration.connections.get() - 1;
1243+
for n in 0..(additional_connections) {
1244+
let socket = (match send_migration_socket(send_data_migration) {
12441245
Err(e) if n == 0 => {
12451246
// If we encounter a problem on the first additional
12461247
// connection, we just assume the other side doesn't support
@@ -1384,17 +1385,31 @@ impl Drop for SendAdditionalConnections {
13841385

13851386
/// Establishes a connection to a migration destination socket (TCP or UNIX).
13861387
fn send_migration_socket(
1387-
destination_url: &str,
1388+
send_data_migration: &VmSendMigrationData,
13881389
) -> std::result::Result<SocketStream, MigratableError> {
1389-
if let Some(address) = destination_url.strip_prefix("tcp:") {
1390+
if let Some(address) = send_data_migration.destination_url.strip_prefix("tcp:") {
13901391
info!("Connecting to TCP socket at {}", address);
13911392

13921393
let socket = TcpStream::connect(address).map_err(|e| {
13931394
MigratableError::MigrateSend(anyhow!("Error connecting to TCP socket: {}", e))
13941395
})?;
13951396

1396-
Ok(SocketStream::Tcp(socket))
1397-
} else if let Some(path) = destination_url.strip_prefix("unix:") {
1397+
if send_data_migration.tls_dir.is_none() {
1398+
Ok(SocketStream::Tcp(socket))
1399+
} else {
1400+
info!("Live Migration will be encrypted using TLS.");
1401+
// The address may still contain a port. I think we should build something more robust to also handle IPv6.
1402+
let tls_stream = tls::client_stream(
1403+
socket,
1404+
send_data_migration.tls_dir.as_ref().unwrap(),
1405+
address
1406+
.split_once(':')
1407+
.map(|(host, _)| host)
1408+
.unwrap_or(address),
1409+
)?;
1410+
Ok(SocketStream::Tls(TlsStream::Client(tls_stream)))
1411+
}
1412+
} else if let Some(path) = &send_data_migration.destination_url.strip_prefix("unix:") {
13981413
info!("Connecting to UNIX socket at {:?}", path);
13991414

14001415
let socket = UnixStream::connect(path).map_err(|e| {
@@ -1404,30 +1419,39 @@ fn send_migration_socket(
14041419
Ok(SocketStream::Unix(socket))
14051420
} else {
14061421
Err(MigratableError::MigrateSend(anyhow!(
1407-
"Invalid destination: {destination_url}"
1422+
"Invalid destination: {}",
1423+
send_data_migration.destination_url
14081424
)))
14091425
}
14101426
}
14111427

14121428
/// Creates a listener socket for receiving incoming migration connections (TCP or UNIX).
14131429
fn receive_migration_listener(
1414-
receiver_url: &str,
1430+
receiver_data_migration: &VmReceiveMigrationData,
14151431
) -> std::result::Result<ReceiveListener, MigratableError> {
1416-
if let Some(address) = receiver_url.strip_prefix("tcp:") {
1417-
TcpListener::bind(address)
1418-
.map_err(|e| {
1419-
MigratableError::MigrateReceive(anyhow!("Error binding to TCP socket: {}", e))
1420-
})
1421-
.map(ReceiveListener::Tcp)
1422-
} else if let Some(path) = receiver_url.strip_prefix("unix:") {
1432+
if let Some(address) = receiver_data_migration.receiver_url.strip_prefix("tcp:") {
1433+
let listener = TcpListener::bind(address).map_err(|e| {
1434+
MigratableError::MigrateReceive(anyhow!("Error binding to TCP socket: {}", e))
1435+
})?;
1436+
1437+
if receiver_data_migration.tls_dir.is_none() {
1438+
Ok(ReceiveListener::Tcp(listener))
1439+
} else {
1440+
Ok(ReceiveListener::Tls(
1441+
listener,
1442+
TlsConnectionWrapper::new(receiver_data_migration.tls_dir.as_ref().unwrap()),
1443+
))
1444+
}
1445+
} else if let Some(path) = receiver_data_migration.receiver_url.strip_prefix("unix:") {
14231446
UnixListener::bind(path)
14241447
.map_err(|e| {
14251448
MigratableError::MigrateReceive(anyhow!("Error binding to UNIX socket: {}", e))
14261449
})
14271450
.map(|listener| ReceiveListener::Unix(listener, Some(path.into())))
14281451
} else {
14291452
Err(MigratableError::MigrateSend(anyhow!(
1430-
"Invalid source: {receiver_url}"
1453+
"Invalid source: {}",
1454+
receiver_data_migration.receiver_url
14311455
)))
14321456
}
14331457
}
@@ -2054,11 +2078,7 @@ impl Vmm {
20542078
s: &mut MigrationState,
20552079
send_data_migration: &VmSendMigrationData,
20562080
) -> result::Result<(), MigratableError> {
2057-
let mem_send = SendAdditionalConnections::new(
2058-
&send_data_migration.destination_url,
2059-
send_data_migration.connections,
2060-
&vm.guest_memory(),
2061-
)?;
2081+
let mem_send = SendAdditionalConnections::new(send_data_migration, &vm.guest_memory())?;
20622082

20632083
// Start logging dirty pages
20642084
vm.start_dirty_log()?;
@@ -2139,7 +2159,7 @@ impl Vmm {
21392159
let mut s = MigrationState::new();
21402160

21412161
// Set up the socket connection
2142-
let mut socket = send_migration_socket(&send_data_migration.destination_url)?;
2162+
let mut socket = send_migration_socket(&send_data_migration)?;
21432163

21442164
// Start the migration
21452165
Request::start().write_to(&mut socket)?;
@@ -3317,7 +3337,7 @@ impl RequestHandler for Vmm {
33173337
receive_data_migration.receiver_url, &receive_data_migration.net_fds
33183338
);
33193339

3320-
let mut listener = receive_migration_listener(&receive_data_migration.receiver_url)?;
3340+
let mut listener = receive_migration_listener(&receive_data_migration)?;
33213341
// Accept the connection and get the socket
33223342
let mut socket = listener.accept().map_err(|e| {
33233343
warn!("Failed to accept migration connection: {}", e);

0 commit comments

Comments
 (0)