diff --git a/libexec/nuageinit/nuage.lua b/libexec/nuageinit/nuage.lua index c27b66b23be2..4e21405a443b 100644 --- a/libexec/nuageinit/nuage.lua +++ b/libexec/nuageinit/nuage.lua @@ -1,237 +1,237 @@ --- -- SPDX-License-Identifier: BSD-2-Clause -- -- Copyright(c) 2022 Baptiste Daroussin local unistd = require("posix.unistd") local sys_stat = require("posix.sys.stat") local lfs = require("lfs") local function warnmsg(str, prepend) if not str then return end local tag = "" if prepend ~= false then tag = "nuageinit: " end io.stderr:write(tag .. str .. "\n") end local function errmsg(str, prepend) warnmsg(str, prepend) os.exit(1) end local function dirname(oldpath) if not oldpath then return nil end local path = oldpath:gsub("[^/]+/*$", "") if path == "" then return nil end return path end local function mkdir_p(path) if lfs.attributes(path, "mode") ~= nil then return true end local r, err = mkdir_p(dirname(path)) if not r then return nil, err .. " (creating " .. path .. ")" end return lfs.mkdir(path) end local function sethostname(hostname) if hostname == nil then return end local root = os.getenv("NUAGE_FAKE_ROOTDIR") if not root then root = "" end local hostnamepath = root .. "/etc/rc.conf.d/hostname" mkdir_p(dirname(hostnamepath)) local f, err = io.open(hostnamepath, "w") if not f then warnmsg("Impossible to open " .. hostnamepath .. ":" .. err) return end f:write('hostname="' .. hostname .. '"\n') f:close() end local function splitlist(list) local ret = {} if type(list) == "string" then for str in list:gmatch("([^, ]+)") do ret[#ret + 1] = str end elseif type(list) == "table" then ret = list else warnmsg("Invalid type " .. type(list) .. ", expecting table or string") end return ret end local function adduser(pwd) if (type(pwd) ~= "table") then warnmsg("Argument should be a table") return nil end local root = os.getenv("NUAGE_FAKE_ROOTDIR") local cmd = "pw " if root then cmd = cmd .. "-R " .. root .. " " end local f = io.popen(cmd .. " usershow " .. pwd.name .. " -7 2> /dev/null") local pwdstr = f:read("*a") f:close() if pwdstr:len() ~= 0 then return pwdstr:match("%a+:.+:%d+:%d+:.*:(.*):.*") end if not pwd.gecos then pwd.gecos = pwd.name .. " User" end if not pwd.homedir then pwd.homedir = "/home/" .. pwd.name end local extraargs = "" if pwd.groups then local list = splitlist(pwd.groups) extraargs = " -G " .. table.concat(list, ",") end -- pw will automatically create a group named after the username -- do not add a -g option in this case if pwd.primary_group and pwd.primary_group ~= pwd.name then extraargs = extraargs .. " -g " .. pwd.primary_group end if not pwd.no_create_home then extraargs = extraargs .. " -m " end if not pwd.shell then pwd.shell = "/bin/sh" end local precmd = "" local postcmd = "" if pwd.passwd then - precmd = "echo " .. pwd.passwd .. "| " - postcmd = " -H 0 " + precmd = "echo '" .. pwd.passwd .. "' | " + postcmd = " -H 0" elseif pwd.plain_text_passwd then - precmd = "echo " .. pwd.plain_text_passwd .. "| " - postcmd = " -h 0 " + precmd = "echo '" .. pwd.plain_text_passwd .. "' | " + postcmd = " -h 0" end cmd = precmd .. "pw " if root then cmd = cmd .. "-R " .. root .. " " end cmd = cmd .. "useradd -n " .. pwd.name .. " -M 0755 -w none " cmd = cmd .. extraargs .. " -c '" .. pwd.gecos cmd = cmd .. "' -d '" .. pwd.homedir .. "' -s " .. pwd.shell .. postcmd local r = os.execute(cmd) if not r then warnmsg("fail to add user " .. pwd.name) warnmsg(cmd) return nil end if pwd.locked then cmd = "pw " if root then cmd = cmd .. "-R " .. root .. " " end cmd = cmd .. "lock " .. pwd.name os.execute(cmd) end return pwd.homedir end local function addgroup(grp) if (type(grp) ~= "table") then warnmsg("Argument should be a table") return false end local root = os.getenv("NUAGE_FAKE_ROOTDIR") local cmd = "pw " if root then cmd = cmd .. "-R " .. root .. " " end local f = io.popen(cmd .. " groupshow " .. grp.name .. " 2> /dev/null") local grpstr = f:read("*a") f:close() if grpstr:len() ~= 0 then return true end local extraargs = "" if grp.members then local list = splitlist(grp.members) extraargs = " -M " .. table.concat(list, ",") end cmd = "pw " if root then cmd = cmd .. "-R " .. root .. " " end cmd = cmd .. "groupadd -n " .. grp.name .. extraargs local r = os.execute(cmd) if not r then warnmsg("fail to add group " .. grp.name) warnmsg(cmd) return false end return true end local function addsshkey(homedir, key) local chownak = false local chowndotssh = false local root = os.getenv("NUAGE_FAKE_ROOTDIR") if root then homedir = root .. "/" .. homedir end local ak_path = homedir .. "/.ssh/authorized_keys" local dotssh_path = homedir .. "/.ssh" local dirattrs = lfs.attributes(ak_path) if dirattrs == nil then chownak = true dirattrs = lfs.attributes(dotssh_path) if dirattrs == nil then assert(lfs.mkdir(dotssh_path)) chowndotssh = true dirattrs = lfs.attributes(homedir) end end local f = io.open(ak_path, "a") if not f then warnmsg("impossible to open " .. ak_path) return end f:write(key .. "\n") f:close() if chownak then sys_stat.chmod(ak_path, 384) unistd.chown(ak_path, dirattrs.uid, dirattrs.gid) end if chowndotssh then sys_stat.chmod(dotssh_path, 448) unistd.chown(dotssh_path, dirattrs.uid, dirattrs.gid) end end local n = { warn = warnmsg, err = errmsg, dirname = dirname, mkdir_p = mkdir_p, sethostname = sethostname, adduser = adduser, addgroup = addgroup, addsshkey = addsshkey } return n diff --git a/libexec/nuageinit/tests/nuageinit.sh b/libexec/nuageinit/tests/nuageinit.sh index 6b8ea7ff3328..434fb3095929 100644 --- a/libexec/nuageinit/tests/nuageinit.sh +++ b/libexec/nuageinit/tests/nuageinit.sh @@ -1,434 +1,434 @@ #- # Copyright (c) 2022 Baptiste Daroussin # # SPDX-License-Identifier: BSD-2-Clause # atf_test_case args atf_test_case nocloud atf_test_case nocloud_userdata_script atf_test_case nocloud_user_data_script atf_test_case nocloud_userdata_cloudconfig_users atf_test_case nocloud_network atf_test_case config2 atf_test_case config2_pubkeys atf_test_case config2_pubkeys_user_data atf_test_case config2_pubkeys_meta_data atf_test_case config2_network atf_test_case config2_network_static_v4 args_body() { atf_check -s exit:1 -e inline:"Usage: /usr/libexec/nuageinit ( | )\n" /usr/libexec/nuageinit atf_check -s exit:1 -e inline:"Usage: /usr/libexec/nuageinit ( | )\n" /usr/libexec/nuageinit bla atf_check -s exit:1 -e inline:"Usage: /usr/libexec/nuageinit ( | )\n" /usr/libexec/nuageinit bla meh plop atf_check -s exit:1 -e inline:"nuageinit: Unknown cloud init type: meh\n" /usr/libexec/nuageinit bla meh } nocloud_body() { here=$(pwd) mkdir -p media/nuageinit atf_check -s exit:1 -e match:"nuageinit: error parsing nocloud.*" /usr/libexec/nuageinit ${here}/media/nuageinit/ nocloud export NUAGE_FAKE_ROOTDIR=$(pwd) printf "instance-id: iid-local01\nlocal-hostname: cloudimg\n" > ${here}/media/nuageinit/meta-data atf_check -s exit:0 /usr/libexec/nuageinit ${here}/media/nuageinit nocloud atf_check -o inline:"hostname=\"cloudimg\"\n" cat etc/rc.conf.d/hostname cat > media/nuageinit/meta-data << EOF instance-id: iid-local01 hostname: myhost EOF atf_check -s exit:0 /usr/libexec/nuageinit ${here}/media/nuageinit nocloud atf_check -o inline:"hostname=\"myhost\"\n" cat etc/rc.conf.d/hostname } nocloud_userdata_script_body() { here=$(pwd) mkdir -p media/nuageinit printf "instance-id: iid-local01\n" > ${here}/media/nuageinit/meta-data printf "#!/bin/sh\necho yeah\n" > ${here}/media/nuageinit/user-data chmod 755 ${here}/media/nuageinit/user-data atf_check -s exit:0 -o inline:"yeah\n" /usr/libexec/nuageinit ${here}/media/nuageinit nocloud } nocloud_user_data_script_body() { here=$(pwd) mkdir -p media/nuageinit printf "instance-id: iid-local01\n" > ${here}/media/nuageinit/meta-data printf "#!/bin/sh\necho yeah\n" > ${here}/media/nuageinit/user_data chmod 755 ${here}/media/nuageinit/user_data atf_check -s exit:0 -o inline:"yeah\n" /usr/libexec/nuageinit ${here}/media/nuageinit nocloud } nocloud_userdata_cloudconfig_users_body() { here=$(pwd) export NUAGE_FAKE_ROOTDIR=$(pwd) if [ $(id -u) -ne 0 ]; then atf_skip "root required" fi mkdir -p media/nuageinit printf "instance-id: iid-local01\n" > ${here}/media/nuageinit/meta-data mkdir -p etc cat > etc/master.passwd << EOF root:*:0:0::0:0:Charlie &:/root:/bin/csh sys:*:1:0::0:0:Sys:/home/sys:/bin/csh EOF pwd_mkdb -d etc ${here}/etc/master.passwd cat > etc/group << EOF wheel:*:0:root users:*:1: EOF - cat > media/nuageinit/user-data << EOF + cat > media/nuageinit/user-data << 'EOF' #cloud-config groups: - admingroup: [root,sys] - cloud-users users: - default - name: foobar gecos: Foo B. Bar primary_group: foobar groups: users passwd: $6$j212wezy$7H/1LT4f9/N3wpgNunhsIqtMj62OKiS3nyNwuizouQc3u7MbYCarYeAHWYPYb2FT.lbioDm2RrkJPb9BZMN1O/ EOF atf_check /usr/libexec/nuageinit ${here}/media/nuageinit nocloud cat > expectedgroup << EOF wheel:*:0:root,freebsd users:*:1:foobar admingroup:*:1001:root,sys cloud-users:*:1002: freebsd:*:1003: foobar:*:1004: EOF - cat > expectedpasswd << EOF + cat > expectedpasswd << 'EOF' root:*:0:0::0:0:Charlie &:/root:/bin/csh sys:*:1:0::0:0:Sys:/home/sys:/bin/csh freebsd:freebsd:1001:1003::0:0:FreeBSD User:/home/freebsd:/bin/sh -foobar:H/1LT4f9/N3wpgNunhsIqtMj62OKiS3nyNwuizouQc3u7MbYCarYeAHWYPYb2FT.lbioDm2RrkJPb9BZMN1O/:1002:1004::0:0:Foo B. Bar:/home/foobar:/bin/sh +foobar:$6$j212wezy$7H/1LT4f9/N3wpgNunhsIqtMj62OKiS3nyNwuizouQc3u7MbYCarYeAHWYPYb2FT.lbioDm2RrkJPb9BZMN1O/:1002:1004::0:0:Foo B. Bar:/home/foobar:/bin/sh EOF sed -i "" "s/freebsd:.*:1001/freebsd:freebsd:1001/" ${here}/etc/master.passwd atf_check -o file:expectedpasswd cat ${here}/etc/master.passwd atf_check -o file:expectedgroup cat ${here}/etc/group } nocloud_network_body() { here=$(pwd) mkdir -p media/nuageinit mkdir -p etc cat > etc/master.passwd << EOF root:*:0:0::0:0:Charlie &:/root:/bin/csh sys:*:1:0::0:0:Sys:/home/sys:/bin/csh EOF pwd_mkdb -d etc ${here}/etc/master.passwd cat > etc/group << EOF wheel:*:0:root users:*:1: EOF if [ $(id -u) -ne 0 ]; then atf_skip "root required" fi mynetworks=$(ifconfig -l ether) if [ -z "$mynetworks" ]; then atf_skip "a network interface is needed" fi set -- $mynetworks myiface=$1 myaddr=$(ifconfig $myiface ether | awk '/ether/ { print $2 }') printf "instance-id: iid-local01\n" > ${here}/media/nuageinit/meta-data cat > media/nuageinit/user-data << EOF #cloud-config network: version: 2 ethernets: # opaque ID for physical interfaces, only referred to by other stanzas id0: match: macaddress: "$myaddr" addresses: - 192.168.14.2/24 - 2001:1::1/64 gateway4: 192.168.14.1 gateway6: 2001:1::2 EOF export NUAGE_FAKE_ROOTDIR=$(pwd) atf_check /usr/libexec/nuageinit ${here}/media/nuageinit nocloud cat > network << EOF ifconfig_${myiface}="inet 192.168.14.2/24" ifconfig_${myiface}_ipv6="inet6 2001:1::1/64" ipv6_network_interfaces="${myiface}" ipv6_default_interface="${myiface}" EOF cat > routing << EOF defaultrouter="192.168.14.1" ipv6_defaultrouter="2001:1::2" ipv6_route_${myiface}="2001:1::2 -prefixlen 128 -interface ${myiface}" EOF atf_check -o file:network cat ${here}/etc/rc.conf.d/network atf_check -o file:routing cat ${here}/etc/rc.conf.d/routing } config2_body() { here=$(pwd) mkdir -p media/nuageinit atf_check -s exit:1 -e match:"nuageinit: error parsing config-2 meta_data.json:.*" /usr/libexec/nuageinit ${here}/media/nuageinit config-2 printf "{}" > media/nuageinit/meta_data.json atf_check /usr/libexec/nuageinit ${here}/media/nuageinit config-2 cat > media/nuageinit/meta_data.json << EOF { "hostname": "cloudimg" } EOF export NUAGE_FAKE_ROOTDIR=$(pwd) atf_check /usr/libexec/nuageinit ${here}/media/nuageinit config-2 atf_check -o inline:"hostname=\"cloudimg\"\n" cat etc/rc.conf.d/hostname } config2_pubkeys_body() { here=$(pwd) export NUAGE_FAKE_ROOTDIR=$(pwd) if [ $(id -u) -ne 0 ]; then atf_skip "root required" fi mkdir -p media/nuageinit touch media/nuageinit/meta_data.json cat > media/nuageinit/user-data << EOF #cloud-config ssh_authorized_keys: - "ssh-rsa AAAAB3NzaC1y...== Generated by Nova" EOF mkdir -p etc cat > etc/master.passwd << EOF root:*:0:0::0:0:Charlie &:/root:/bin/csh sys:*:1:0::0:0:Sys:/home/sys:/bin/csh EOF pwd_mkdb -d etc ${here}/etc/master.passwd cat > etc/group << EOF wheel:*:0:root users:*:1: EOF atf_check /usr/libexec/nuageinit ${here}/media/nuageinit config-2 atf_check -o inline:"ssh-rsa AAAAB3NzaC1y...== Generated by Nova\n" cat home/freebsd/.ssh/authorized_keys } config2_pubkeys_user_data_body() { here=$(pwd) export NUAGE_FAKE_ROOTDIR=$(pwd) if [ $(id -u) -ne 0 ]; then atf_skip "root required" fi mkdir -p media/nuageinit touch media/nuageinit/meta_data.json cat > media/nuageinit/user_data << EOF #cloud-config ssh_authorized_keys: - "ssh-rsa AAAAB3NzaC1y...== Generated by Nova" EOF mkdir -p etc cat > etc/master.passwd << EOF root:*:0:0::0:0:Charlie &:/root:/bin/csh sys:*:1:0::0:0:Sys:/home/sys:/bin/csh EOF pwd_mkdb -d etc ${here}/etc/master.passwd cat > etc/group << EOF wheel:*:0:root users:*:1: EOF atf_check /usr/libexec/nuageinit ${here}/media/nuageinit config-2 atf_check -o inline:"ssh-rsa AAAAB3NzaC1y...== Generated by Nova\n" cat home/freebsd/.ssh/authorized_keys } config2_pubkeys_meta_data_body() { here=$(pwd) export NUAGE_FAKE_ROOTDIR=$(pwd) if [ $(id -u) -ne 0 ]; then atf_skip "root required" fi mkdir -p media/nuageinit cat > media/nuageinit/meta_data.json << EOF { "uuid": "uuid_for_this_instance", "admin_pass": "a_generated_password", "public_keys": { "tdb": "ssh-ed25519 my_key_id tdb@host" }, "keys": [ { "name": "tdb", "type": "ssh", "data": "ssh-ed25519 my_key_id tdb@host" } ], "hostname": "freebsd-14-test.novalocal", "name": "freebsd-14-test", "launch_index": 0, "availability_zone": "nova", "random_seed": "long_random_seed", "project_id": "my_project_id", "devices": [], "dedicated_cpus": [] } EOF mkdir -p etc cat > etc/master.passwd << EOF root:*:0:0::0:0:Charlie &:/root:/bin/csh sys:*:1:0::0:0:Sys:/home/sys:/bin/csh EOF pwd_mkdb -d etc ${here}/etc/master.passwd cat > etc/group << EOF wheel:*:0:root users:*:1: EOF atf_check /usr/libexec/nuageinit ${here}/media/nuageinit config-2 atf_check -o inline:"ssh-ed25519 my_key_id tdb@host\n" cat home/freebsd/.ssh/authorized_keys } config2_network_body() { here=$(pwd) mkdir -p media/nuageinit printf "{}" > media/nuageinit/meta_data.json mynetworks=$(ifconfig -l ether) if [ -z "$mynetworks" ]; then atf_skip "a network interface is needed" fi set -- $mynetworks myiface=$1 myaddr=$(ifconfig $myiface ether | awk '/ether/ { print $2 }') cat > media/nuageinit/network_data.json << EOF { "links": [ { "ethernet_mac_address": "$myaddr", "id": "iface0", "mtu": null } ], "networks": [ { "id": "network0", "link": "iface0", "type": "ipv4_dhcp" }, { // IPv6 "id": "private-ipv4", "type": "ipv6", "link": "iface0", // supports condensed IPv6 with CIDR netmask "ip_address": "2001:cdba::3257:9652/24", "gateway": "fd00::1", "routes": [ { "network": "::", "netmask": "::", "gateway": "fd00::1" }, { "network": "::", "netmask": "ffff:ffff:ffff::", "gateway": "fd00::1:1" } ], "network_id": "da5bb487-5193-4a65-a3df-4a0055a8c0d8" } ] } EOF export NUAGE_FAKE_ROOTDIR=$(pwd) atf_check /usr/libexec/nuageinit ${here}/media/nuageinit config-2 cat > network << EOF ifconfig_${myiface}="DHCP" ifconfig_${myiface}_ipv6="inet6 2001:cdba::3257:9652/24" ipv6_network_interfaces="${myiface}" ipv6_default_interface="${myiface}" EOF cat > routing << EOF ipv6_defaultrouter="fd00::1" ipv6_route_${myiface}="fd00::1 -prefixlen 128 -interface ${myiface}" ipv6_static_routes="${myiface}" EOF atf_check -o file:network cat ${here}/etc/rc.conf.d/network atf_check -o file:routing cat ${here}/etc/rc.conf.d/routing } config2_network_static_v4_body() { here=$(pwd) mkdir -p media/nuageinit printf "{}" > media/nuageinit/meta_data.json mynetworks=$(ifconfig -l ether) if [ -z "$mynetworks" ]; then atf_skip "a network interface is needed" fi set -- $mynetworks myiface=$1 myaddr=$(ifconfig $myiface ether | awk '/ether/ { print $2 }') cat > media/nuageinit/network_data.json << EOF { "links": [ { "ethernet_mac_address": "$myaddr", "id": "iface0", "mtu": null } ], "networks": [ { "id": "network0", "link": "iface0", "type": "ipv4", "ip_address": "10.184.0.244", "netmask": "255.255.240.0", "routes": [ { "network": "10.0.0.0", "netmask": "255.0.0.0", "gateway": "11.0.0.1" }, { "network": "0.0.0.0", "netmask": "0.0.0.0", "gateway": "23.253.157.1" } ] } ] } EOF export NUAGE_FAKE_ROOTDIR=$(pwd) atf_check /usr/libexec/nuageinit ${here}/media/nuageinit config-2 cat > network << EOF ifconfig_${myiface}="inet 10.184.0.244 netmask 255.255.240.0" EOF cat > routing << EOF route_cloudinit1_${myiface}="-net 10.0.0.0 11.0.0.1 255.0.0.0" defaultrouter="23.253.157.1" static_routes="cloudinit1_${myiface}" EOF atf_check -o file:network cat ${here}/etc/rc.conf.d/network atf_check -o file:routing cat ${here}/etc/rc.conf.d/routing } atf_init_test_cases() { atf_add_test_case args atf_add_test_case nocloud atf_add_test_case nocloud_userdata_script atf_add_test_case nocloud_user_data_script atf_add_test_case nocloud_userdata_cloudconfig_users atf_add_test_case nocloud_network atf_add_test_case config2 atf_add_test_case config2_pubkeys atf_add_test_case config2_pubkeys_user_data atf_add_test_case config2_pubkeys_meta_data atf_add_test_case config2_network atf_add_test_case config2_network_static_v4 }