Prepare, configure, and manage Firecracker microVMs in seconds!
virtualization linux microvm firecracker

Merge pull request #12 from tsirysndr/feat/detect-updated-ssh-keys

feat: add SSH key change detection and image regeneration for rootfs preparations

authored by tsiry-sandratraina.com and committed by

GitHub 1df37615 b8a4c4f8

+141 -14
+1
Cargo.lock
··· 974 974 "owo-colors", 975 975 "regex", 976 976 "serde", 977 + "sha2", 977 978 ] 978 979 979 980 [[package]]
+1
crates/firecracker-prepare/Cargo.toml
··· 13 13 owo-colors = "4.2.2" 14 14 regex = "1.11.1" 15 15 serde = { version = "1.0.219", features = ["serde_derive", "derive"] } 16 + sha2 = "0.10.9"
+139 -14
crates/firecracker-prepare/src/lib.rs
··· 3 3 use anyhow::Result; 4 4 use owo_colors::OwoColorize; 5 5 use serde::{Deserialize, Serialize}; 6 + use sha2::{Digest, Sha256}; 6 7 7 8 use crate::{ 8 9 command::{run_command, run_command_with_stdout_inherit}, ··· 212 213 &["-p", &format!("{}/root/.ssh", debootstrap_dir)], 213 214 true, 214 215 )?; 216 + 217 + let img_file = format!("{}/debian-rootfs.img", app_dir); 218 + if ssh_keys_changed( 219 + &ssh_keys, 220 + &format!("{}/root/.ssh/authorized_keys", debootstrap_dir), 221 + )? { 222 + println!("[+] SSH keys have changed, removing existing image to regenerate."); 223 + run_command("rm", &["-f", &img_file], true)?; 224 + } 225 + 215 226 match ssh_keys { 216 227 Some(ref keys) => ssh::copy_ssh_keys(keys, &debootstrap_dir)?, 217 228 None => ssh::generate_and_copy_ssh_key(&ssh_key_name, &debootstrap_dir)?, ··· 244 255 )?; 245 256 } 246 257 247 - let img_file = format!("{}/debian-rootfs.img", app_dir); 248 258 rootfs::create_overlay_dirs(&debootstrap_dir)?; 249 259 rootfs::add_overlay_init(&debootstrap_dir)?; 250 260 rootfs::create_squashfs(&debootstrap_dir, &img_file)?; ··· 380 390 true, 381 391 )?; 382 392 393 + let img_file = format!("{}/alpine-rootfs.img", app_dir); 394 + if ssh_keys_changed( 395 + &ssh_keys, 396 + &format!("{}/root/.ssh/authorized_keys", minirootfs), 397 + )? { 398 + println!("[+] SSH keys have changed, removing existing image to regenerate."); 399 + run_command("rm", &["-f", &img_file], true)?; 400 + } 401 + 383 402 let ssh_key_name = "id_rsa"; 384 403 match ssh_keys { 385 404 Some(ref keys) => ssh::copy_ssh_keys(keys, &minirootfs)?, 386 405 None => ssh::generate_and_copy_ssh_key(&ssh_key_name, &minirootfs)?, 387 406 } 388 407 389 - let img_file = format!("{}/alpine-rootfs.img", app_dir); 390 408 rootfs::create_squashfs(&minirootfs, &img_file)?; 391 409 392 410 let ssh_key_file = match ssh_keys { ··· 448 466 true, 449 467 )?; 450 468 469 + let img_file = format!("{}/ubuntu-rootfs.img", app_dir); 470 + if ssh_keys_changed( 471 + &ssh_keys, 472 + &format!("{}/root/.ssh/authorized_keys", squashfs_root_dir), 473 + )? { 474 + println!("[+] SSH keys have changed, removing existing image to regenerate."); 475 + run_command("rm", &["-f", &img_file], true)?; 476 + } 477 + 451 478 let ssh_key_name = "id_rsa"; 452 479 match ssh_keys { 453 480 Some(ref keys) => ssh::copy_ssh_keys(keys, &squashfs_root_dir)?, 454 481 None => ssh::generate_and_copy_ssh_key(&ssh_key_name, &squashfs_root_dir)?, 455 482 } 456 483 457 - let img_file = format!("{}/ubuntu-rootfs.img", app_dir); 458 484 rootfs::create_overlay_dirs(&squashfs_root_dir)?; 459 485 rootfs::add_overlay_init(&squashfs_root_dir)?; 460 486 rootfs::create_squashfs(&squashfs_root_dir, &img_file)?; ··· 495 521 downloader::download_nixos_rootfs(arch)?; 496 522 rootfs::extract_squashfs(&squashfs_file, &nixos_rootfs)?; 497 523 524 + let img_file = format!("{}/nixos-rootfs.img", app_dir); 525 + if ssh_keys_changed( 526 + &ssh_keys, 527 + &format!("{}/root/.ssh/authorized_keys", nixos_rootfs), 528 + )? { 529 + println!("[+] SSH keys have changed, removing existing image to regenerate."); 530 + run_command("rm", &["-f", &img_file], true)?; 531 + } 532 + 498 533 let ssh_key_name = "id_rsa"; 499 534 match ssh_keys { 500 535 Some(ref keys) => ssh::copy_ssh_keys(keys, &nixos_rootfs)?, 501 536 None => ssh::generate_and_copy_ssh_key(&ssh_key_name, &nixos_rootfs)?, 502 537 } 503 538 504 - let img_file = format!("{}/nixos-rootfs.img", app_dir); 505 539 rootfs::create_squashfs(&nixos_rootfs, &img_file)?; 506 540 507 541 let ssh_key_file = match ssh_keys { ··· 547 581 downloader::download_fedora_rootfs(arch)?; 548 582 rootfs::extract_squashfs(&squashfs_file, &fedora_rootfs)?; 549 583 584 + let img_file = format!("{}/fedora-rootfs.img", app_dir); 585 + if ssh_keys_changed( 586 + &ssh_keys, 587 + &format!("{}/root/.ssh/authorized_keys", fedora_rootfs), 588 + )? { 589 + println!("[+] SSH keys have changed, removing existing image to regenerate."); 590 + run_command("rm", &["-f", &img_file], true)?; 591 + } 592 + 550 593 run_command( 551 594 "chroot", 552 595 &[&fedora_rootfs, "systemctl", "enable", "sshd"], ··· 559 602 None => ssh::generate_and_copy_ssh_key(&ssh_key_name, &fedora_rootfs)?, 560 603 } 561 604 562 - let img_file = format!("{}/fedora-rootfs.img", app_dir); 563 605 rootfs::create_squashfs(&fedora_rootfs, &img_file)?; 564 606 565 607 let ssh_key_file = match ssh_keys { ··· 613 655 true, 614 656 )?; 615 657 658 + let img_file = format!("{}/gentoo-rootfs.img", app_dir); 659 + if ssh_keys_changed( 660 + &ssh_keys, 661 + &format!("{}/root/.ssh/authorized_keys", gentoo_rootfs), 662 + )? { 663 + println!("[+] SSH keys have changed, removing existing image to regenerate."); 664 + run_command("rm", &["-f", &img_file], true)?; 665 + } 666 + 616 667 let ssh_key_name = "id_rsa"; 617 668 match ssh_keys { 618 669 Some(ref keys) => ssh::copy_ssh_keys(keys, &gentoo_rootfs)?, 619 670 None => ssh::generate_and_copy_ssh_key(&ssh_key_name, &gentoo_rootfs)?, 620 671 } 621 672 622 - let img_file = format!("{}/gentoo-rootfs.img", app_dir); 623 673 rootfs::create_squashfs(&gentoo_rootfs, &img_file)?; 624 674 625 675 let ssh_key_file = match ssh_keys { ··· 660 710 downloader::download_slackware_rootfs(arch)?; 661 711 rootfs::extract_squashfs(&squashfs_file, &slackware_rootfs)?; 662 712 713 + let img_file = format!("{}/slackware-rootfs.img", app_dir); 714 + if ssh_keys_changed( 715 + &ssh_keys, 716 + &format!("{}/root/.ssh/authorized_keys", slackware_rootfs), 717 + )? { 718 + println!("[+] SSH keys have changed, removing existing image to regenerate."); 719 + run_command("rm", &["-f", &img_file], true)?; 720 + } 721 + 663 722 run_command( 664 723 "chroot", 665 724 &[ ··· 678 737 None => ssh::generate_and_copy_ssh_key(&ssh_key_name, &slackware_rootfs)?, 679 738 } 680 739 681 - let img_file = format!("{}/slackware-rootfs.img", app_dir); 682 740 rootfs::create_squashfs(&slackware_rootfs, &img_file)?; 683 741 684 742 let ssh_key_file = match ssh_keys { ··· 719 777 downloader::download_opensuse_rootfs(arch)?; 720 778 rootfs::extract_squashfs(&squashfs_file, &opensuse_rootfs)?; 721 779 780 + let img_file = format!("{}/opensuse-rootfs.img", app_dir); 781 + if ssh_keys_changed( 782 + &ssh_keys, 783 + &format!("{}/root/.ssh/authorized_keys", opensuse_rootfs), 784 + )? { 785 + println!("[+] SSH keys have changed, removing existing image to regenerate."); 786 + run_command("rm", &["-f", &img_file], true)?; 787 + } 788 + 722 789 run_command( 723 790 "chroot", 724 791 &[&opensuse_rootfs, "systemctl", "enable", "sshd"], ··· 731 798 None => ssh::generate_and_copy_ssh_key(&ssh_key_name, &opensuse_rootfs)?, 732 799 } 733 800 734 - let img_file = format!("{}/opensuse-rootfs.img", app_dir); 735 801 rootfs::create_squashfs(&opensuse_rootfs, &img_file)?; 736 802 737 803 let ssh_key_file = match ssh_keys { ··· 772 838 downloader::download_almalinux_rootfs(arch)?; 773 839 rootfs::extract_squashfs(&squashfs_file, &almalinux_rootfs)?; 774 840 841 + let img_file = format!("{}/almalinux-rootfs.img", app_dir); 842 + if ssh_keys_changed( 843 + &ssh_keys, 844 + &format!("{}/root/.ssh/authorized_keys", almalinux_rootfs), 845 + )? { 846 + println!("[+] SSH keys have changed, removing existing image to regenerate."); 847 + run_command("rm", &["-f", &img_file], true)?; 848 + } 849 + 775 850 let ssh_key_name = "id_rsa"; 776 851 match ssh_keys { 777 852 Some(ref keys) => ssh::copy_ssh_keys(keys, &almalinux_rootfs)?, 778 853 None => ssh::generate_and_copy_ssh_key(&ssh_key_name, &almalinux_rootfs)?, 779 854 } 780 855 781 - let img_file = format!("{}/almalinux-rootfs.img", app_dir); 782 856 rootfs::create_squashfs(&almalinux_rootfs, &img_file)?; 783 857 784 858 let ssh_key_file = match ssh_keys { ··· 819 893 downloader::download_rockylinux_rootfs(arch)?; 820 894 rootfs::extract_squashfs(&squashfs_file, &rockylinux_rootfs)?; 821 895 896 + let img_file = format!("{}/rockylinux-rootfs.img", app_dir); 897 + if ssh_keys_changed( 898 + &ssh_keys, 899 + &format!("{}/root/.ssh/authorized_keys", rockylinux_rootfs), 900 + )? { 901 + println!("[+] SSH keys have changed, removing existing image to regenerate."); 902 + run_command("rm", &["-f", &img_file], true)?; 903 + } 904 + 822 905 let ssh_key_name = "id_rsa"; 823 906 match ssh_keys { 824 907 Some(ref keys) => ssh::copy_ssh_keys(keys, &rockylinux_rootfs)?, 825 908 None => ssh::generate_and_copy_ssh_key(&ssh_key_name, &rockylinux_rootfs)?, 826 909 } 827 - 828 - let img_file = format!("{}/rockylinux-rootfs.img", app_dir); 829 910 rootfs::create_squashfs(&rockylinux_rootfs, &img_file)?; 830 911 831 912 let ssh_key_file = match ssh_keys { ··· 865 946 downloader::download_archlinux_rootfs(arch)?; 866 947 rootfs::extract_squashfs(&squashfs_file, &archlinux_rootfs)?; 867 948 949 + let img_file = format!("{}/archlinux-rootfs.img", app_dir); 950 + if ssh_keys_changed( 951 + &ssh_keys, 952 + &format!("{}/root/.ssh/authorized_keys", archlinux_rootfs), 953 + )? { 954 + println!("[+] SSH keys have changed, removing existing image to regenerate."); 955 + run_command("rm", &["-f", &img_file], true)?; 956 + } 957 + 868 958 run_command( 869 959 "chroot", 870 960 &[&archlinux_rootfs, "systemctl", "enable", "sshd"], ··· 881 971 Some(ref keys) => ssh::copy_ssh_keys(keys, &archlinux_rootfs)?, 882 972 None => ssh::generate_and_copy_ssh_key(&ssh_key_name, &archlinux_rootfs)?, 883 973 } 884 - 885 - let img_file = format!("{}/archlinux-rootfs.img", app_dir); 886 974 887 975 rootfs::create_squashfs(&archlinux_rootfs, &img_file)?; 888 976 ··· 924 1012 downloader::download_opensuse_tumbleweed_rootfs(arch)?; 925 1013 rootfs::extract_squashfs(&squashfs_file, &opensuse_rootfs)?; 926 1014 1015 + let img_file = format!("{}/opensuse-tumbleweed-rootfs.img", app_dir); 1016 + if ssh_keys_changed( 1017 + &ssh_keys, 1018 + &format!("{}/root/.ssh/authorized_keys", opensuse_rootfs), 1019 + )? { 1020 + println!("[+] SSH keys have changed, removing existing image to regenerate."); 1021 + run_command("rm", &["-f", &img_file], true)?; 1022 + } 1023 + 927 1024 run_command( 928 1025 "chroot", 929 1026 &[&opensuse_rootfs, "systemctl", "enable", "sshd"], ··· 937 1034 None => ssh::generate_and_copy_ssh_key(&ssh_key_name, &opensuse_rootfs)?, 938 1035 } 939 1036 940 - let img_file = format!("{}/opensuse-tumbleweed-rootfs.img", app_dir); 941 1037 rootfs::create_squashfs(&opensuse_rootfs, &img_file)?; 942 1038 943 1039 let ssh_key_file = match ssh_keys { ··· 948 1044 Ok((kernel_file, img_file, ssh_key_file)) 949 1045 } 950 1046 } 1047 + 1048 + fn ssh_keys_changed(ssh_keys: &Option<Vec<String>>, authorized_keys_path: &str) -> Result<bool> { 1049 + if ssh_keys.is_none() { 1050 + return Ok(false); 1051 + } 1052 + let ssh_keys = ssh_keys.as_ref().unwrap(); 1053 + let mut hasher = Sha256::new(); 1054 + let ssh_keys_str = ssh_keys.join("\n"); 1055 + 1056 + let ssh_keys_str = match ssh_keys_str.ends_with('\n') { 1057 + true => ssh_keys_str, 1058 + false => format!("{}\n", ssh_keys_str), 1059 + }; 1060 + 1061 + hasher.update(ssh_keys_str.as_bytes()); 1062 + let ssh_keys_hash = hasher.finalize(); 1063 + 1064 + if !run_command("test", &["-e", authorized_keys_path], true).is_ok() { 1065 + return Ok(true); 1066 + } 1067 + 1068 + let output = run_command("cat", &[authorized_keys_path], true)?; 1069 + let authorized_keys_content = String::from_utf8_lossy(&output.stdout); 1070 + let mut hasher = Sha256::new(); 1071 + hasher.update(authorized_keys_content.as_bytes()); 1072 + let authorized_keys_hash = hasher.finalize(); 1073 + 1074 + Ok(ssh_keys_hash != authorized_keys_hash) 1075 + }