From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from firstgate.proxmox.com (firstgate.proxmox.com [IPv6:2a01:7e0:0:424::9]) by lore.proxmox.com (Postfix) with ESMTPS id 752301FF161 for ; Tue, 30 Jul 2024 17:15:50 +0200 (CEST) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id 9B6193F0B5; Tue, 30 Jul 2024 17:15:51 +0200 (CEST) From: Mira Limbeck To: pve-devel@lists.proxmox.com Date: Tue, 30 Jul 2024 17:15:39 +0200 Message-Id: <20240730151540.308217-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.569 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 URIBL_BLOCKED 0.001 ADMINISTRATOR NOTICE: The query to URIBL was blocked. See http://wiki.apache.org/spamassassin/DnsBlocklists#dnsbl-block for more information. [cloudinit.pm, readthedocs.io, qemu.pm] Subject: [pve-devel] [PATCH v3 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. DNS search domains were removed because Cloudbase-Init's ENI parser doesn't handle it at all. The password set via `cipassword` is used for the Admin user configured in the cloudbase-init.conf in the guest while the `ciuser` parameter is ignored. The Admin user has to be set in the cloudbase-init.conf file instead. Specifying a different user does not work. For the password to work the `ostype` needs to be any Windows variant before `cipassword` is set. Otherwise the password will be encrypted and the encrypted password used as plaintext password in the guest. The `citype` needs to be `configdrive2`, which is the default for Windows guests, for the generated configs to be compatible with Cloudbase-Init. [0] https://cloudbase-init.readthedocs.io/en/latest/index.html Signed-off-by: Mira Limbeck --- v3: - removed `use URI` since we already `use URI::Escape` - sent a separate patch adding `liburi-perl` dependency in d/control v2: - unchanged PVE/API2/Qemu.pm | 13 ++--- PVE/QemuServer/Cloudinit.pm | 99 +++++++++++++++++++++++++++++++++++-- 2 files changed, 101 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..4efbdf5 100644 --- a/PVE/QemuServer/Cloudinit.pm +++ b/PVE/QemuServer/Cloudinit.pm @@ -8,6 +8,7 @@ use Digest::SHA; use URI::Escape; use MIME::Base64 qw(encode_base64); use Storable qw(dclone); +use JSON; use PVE::Tools qw(run_command file_set_contents); use PVE::Storage; @@ -232,12 +233,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 +266,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