From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from firstgate.proxmox.com (firstgate.proxmox.com [212.224.123.68]) by lore.proxmox.com (Postfix) with ESMTPS id 9B9F61FF16E for ; Mon, 29 Jul 2024 17:19:36 +0200 (CEST) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id E35631FAEA; Mon, 29 Jul 2024 17:19:36 +0200 (CEST) From: Mira Limbeck To: pve-devel@lists.proxmox.com Date: Mon, 29 Jul 2024 17:19:30 +0200 Message-Id: <20240729151931.363107-1-m.limbeck@proxmox.com> X-Mailer: git-send-email 2.39.2 MIME-Version: 1.0 X-SPAM-LEVEL: Spam detection results: 0 AWL -0.586 Adjusted score from AWL reputation of From: address BAYES_00 -1.9 Bayes spam probability is 0 to 1% DMARC_MISSING 0.1 Missing DMARC policy KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment KAM_LAZY_DOMAIN_SECURITY 1 Sending domain does not have any anti-forgery methods RDNS_NONE 0.793 Delivered to internal network by a host with no rDNS SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_NONE 0.001 SPF: sender does not publish an SPF Record Subject: [pve-devel] [PATCH v2 qemu-server] fix 4493: cloud-init: fix generated Windows config X-BeenThere: pve-devel@lists.proxmox.com X-Mailman-Version: 2.1.29 Precedence: list List-Id: Proxmox VE development discussion List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Reply-To: Proxmox VE development discussion Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit Errors-To: pve-devel-bounces@lists.proxmox.com Sender: "pve-devel" cloudbase-init, a cloud-init reimplementation for Windows, supports only a subset of the configuration options of cloud-init. Some features depend on support by the Metadata Service (ConfigDrive2 here) and have further limitations [0]. To support a basic setup the following changes were made: - password is saved as plaintext for any Windows guests (ostype) - DNS servers are added to each of the interfaces - SSH public keys are passed via metadata Network and metadata generation for cloudbase-init is separate from the default ConfigDrive2 one so as to not interfere with any other OSes that depend on the current ConfigDrive2 implementation. [0] https://cloudbase-init.readthedocs.io/en/latest/index.html Signed-off-by: Mira Limbeck --- v2: - unchanged v1: DNS search domains are not handled at all by the cloudbase-init ENI parser. The password is used for the Admin user specified in the cloudbase-init.conf inside the guest. Specifying a different user does not work. This would require rewriting the userdata handling as described in #5384 [1] which is a breaking change. Userdata generation is currently shared between all implementations, but the new one could be made cloudbase-init only for now. To know if the password needs to be unencrypted, we have to check the `ostype`. For this we need access to the config. That's why I moved the `cipassword` handling inside $updatefn. When no `citype` is specified, it will default to `configdrive2` on Windows. The check requires `ostype` to be set correctly. Any other `citype`s may not work correctly if used for cloudbase-init. The docs patch adds a section on how to configure a Windows guest for cloudbase-init. [1] https://bugzilla.proxmox.com/show_bug.cgi?id=5384 PVE/API2/Qemu.pm | 13 ++--- PVE/QemuServer/Cloudinit.pm | 100 ++++++++++++++++++++++++++++++++++-- 2 files changed, 102 insertions(+), 11 deletions(-) diff --git a/PVE/API2/Qemu.pm b/PVE/API2/Qemu.pm index a3313f3..d25a79f 100644 --- a/PVE/API2/Qemu.pm +++ b/PVE/API2/Qemu.pm @@ -1674,12 +1674,6 @@ my $update_vm_api = sub { my $skip_cloud_init = extract_param($param, 'skip_cloud_init'); - if (defined(my $cipassword = $param->{cipassword})) { - # Same logic as in cloud-init (but with the regex fixed...) - $param->{cipassword} = PVE::Tools::encrypt_pw($cipassword) - if $cipassword !~ /^\$(?:[156]|2[ay])(\$.+){2}/; - } - my @paramarr = (); # used for log message foreach my $key (sort keys %$param) { my $value = $key eq 'cipassword' ? '' : $param->{$key}; @@ -2029,6 +2023,13 @@ my $update_vm_api = sub { my $machine_conf = PVE::QemuServer::Machine::parse_machine($param->{$opt}); PVE::QemuServer::Machine::assert_valid_machine_property($conf, $machine_conf); $conf->{pending}->{$opt} = $param->{$opt}; + } elsif ($opt eq 'cipassword') { + if (!PVE::QemuServer::Helpers::windows_version($conf->{ostype})) { + # Same logic as in cloud-init (but with the regex fixed...) + $param->{cipassword} = PVE::Tools::encrypt_pw($param->{cipassword}) + if $param->{cipassword} !~ /^\$(?:[156]|2[ay])(\$.+){2}/; + } + $conf->{cipassword} = $param->{cipassword}; } else { $conf->{pending}->{$opt} = $param->{$opt}; diff --git a/PVE/QemuServer/Cloudinit.pm b/PVE/QemuServer/Cloudinit.pm index abc6b14..d4ecfac 100644 --- a/PVE/QemuServer/Cloudinit.pm +++ b/PVE/QemuServer/Cloudinit.pm @@ -8,6 +8,8 @@ use Digest::SHA; use URI::Escape; use MIME::Base64 qw(encode_base64); use Storable qw(dclone); +use JSON; +use URI; use PVE::Tools qw(run_command file_set_contents); use PVE::Storage; @@ -232,12 +234,23 @@ sub generate_configdrive2 { my ($conf, $vmid, $drive, $volname, $storeid) = @_; my ($user_data, $network_data, $meta_data, $vendor_data) = get_custom_cloudinit_files($conf); - $user_data = cloudinit_userdata($conf, $vmid) if !defined($user_data); - $network_data = configdrive2_network($conf) if !defined($network_data); - $vendor_data = '' if !defined($vendor_data); + if (PVE::QemuServer::Helpers::windows_version($conf->{ostype})) { + $user_data = cloudinit_userdata($conf, $vmid) if !defined($user_data); + $network_data = cloudbase_network_eni($conf) if !defined($network_data); + $vendor_data = '' if !defined($vendor_data); + + if (!defined($meta_data)) { + my $instance_id = cloudbase_gen_instance_id($user_data, $network_data); + $meta_data = cloudbase_configdrive2_metadata($instance_id, $conf); + } + } else { + $user_data = cloudinit_userdata($conf, $vmid) if !defined($user_data); + $network_data = configdrive2_network($conf) if !defined($network_data); + $vendor_data = '' if !defined($vendor_data); - if (!defined($meta_data)) { - $meta_data = configdrive2_gen_metadata($user_data, $network_data); + if (!defined($meta_data)) { + $meta_data = configdrive2_gen_metadata($user_data, $network_data); + } } # we always allocate a 4MiB disk for cloudinit and with the overhead of the ISO @@ -254,6 +267,83 @@ sub generate_configdrive2 { commit_cloudinit_disk($conf, $vmid, $drive, $volname, $storeid, $files, 'config-2'); } +sub cloudbase_network_eni { + my ($conf) = @_; + + my $content = ""; + + my ($searchdomains, $nameservers) = get_dns_conf($conf); + if ($nameservers && @$nameservers) { + $nameservers = join(' ', @$nameservers); + } + + my @ifaces = grep { /^net(\d+)$/ } keys %$conf; + foreach my $iface (sort @ifaces) { + (my $id = $iface) =~ s/^net//; + next if !$conf->{"ipconfig$id"}; + my $net = PVE::QemuServer::parse_ipconfig($conf->{"ipconfig$id"}); + $id = "eth$id"; + + $content .="auto $id\n"; + if ($net->{ip}) { + if ($net->{ip} eq 'dhcp') { + $content .= "iface $id inet dhcp\n"; + } else { + my ($addr, $mask) = split_ip4($net->{ip}); + $content .= "iface $id inet static\n"; + $content .= " address $addr\n"; + $content .= " netmask $mask\n"; + $content .= " gateway $net->{gw}\n" if $net->{gw}; + $content .= " dns-nameservers $nameservers\n" if $nameservers; + } + } + if ($net->{ip6}) { + if ($net->{ip6} =~ /^(auto|dhcp)$/) { + $content .= "iface $id inet6 $1\n"; + } else { + my ($addr, $mask) = split('/', $net->{ip6}); + $content .= "iface $id inet6 static\n"; + $content .= " address $addr\n"; + $content .= " netmask $mask\n"; + $content .= " gateway $net->{gw6}\n" if $net->{gw6}; + $content .= " dns-nameservers $nameservers\n" if $nameservers; + } + } + } + + return $content; +} + +sub cloudbase_configdrive2_metadata { + my ($uuid, $conf) = @_; + my $meta_data = { + uuid => $uuid, + 'network_config' => { + 'content_path' => '/content/0000', + }, + }; + $meta_data->{'admin_pass'} = $conf->{cipassword} if $conf->{cipassword}; + if (defined(my $keys = $conf->{sshkeys})) { + $keys = URI::Escape::uri_unescape($keys); + $keys = [map { my $key = $_; chomp $key; $key } split(/\n/, $keys)]; + $keys = [grep { /\S/ } @$keys]; + my $i = 0; + foreach my $k (@$keys) { + $meta_data->{'public_keys'}->{"key-$i"} = $k; + $i++; + } + } + my $json = encode_json($meta_data); + return $json; +} + +sub cloudbase_gen_instance_id { + my ($user, $network) = @_; + + my $uuid_str = Digest::SHA::sha1_hex($user.$network); + return $uuid_str; +} + sub generate_opennebula { my ($conf, $vmid, $drive, $volname, $storeid) = @_; -- 2.39.2 _______________________________________________ pve-devel mailing list pve-devel@lists.proxmox.com https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel