diff --git a/libexec/nuageinit/nuage.lua b/libexec/nuageinit/nuage.lua index 3eeb2ea0b44c..2d962b540b23 100644 --- a/libexec/nuageinit/nuage.lua +++ b/libexec/nuageinit/nuage.lua @@ -1,710 +1,708 @@ --- -- SPDX-License-Identifier: BSD-2-Clause -- -- Copyright(c) 2022-2025 Baptiste Daroussin -- Copyright(c) 2025 Jesús Daniel Colmenares Oviedo local unistd = require("posix.unistd") local sys_stat = require("posix.sys.stat") local lfs = require("lfs") local function getlocalbase() local f = io.popen("sysctl -in user.localbase 2> /dev/null") local localbase = f:read("*l") f:close() if localbase == nil or localbase:len() == 0 then -- fallback localbase = "/usr/local" end return localbase end local function decode_base64(input) local b = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/' input = string.gsub(input, '[^'..b..'=]', '') local result = {} local bits = '' -- convert all characters in bits for i = 1, #input do local x = input:sub(i, i) if x == '=' then break end local f = b:find(x) - 1 for j = 6, 1, -1 do bits = bits .. (f % 2^j - f % 2^(j-1) > 0 and '1' or '0') end end for i = 1, #bits, 8 do local byte = bits:sub(i, i + 7) if #byte == 8 then local c = 0 for j = 1, 8 do c = c + (byte:sub(j, j) == '1' and 2^(8 - j) or 0) end table.insert(result, string.char(c)) end end return table.concat(result) end 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 chmod(path, mode) - local mode = tonumber(mode, 8) + mode = tonumber(mode, 8) local _, err, msg = sys_stat.chmod(path, mode) if err then errmsg("chmod(" .. path .. ", " .. mode .. ") failed: " .. msg) end end local function chown(path, owner, group) local _, err, msg = unistd.chown(path, owner, group) if err then errmsg("chown(" .. path .. ", " .. owner .. ", " .. group .. ") failed: " .. msg) end 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 splitlines(s) local ret = {} for line in string.gmatch(s, "[^\n]+") do ret[#ret + 1] = line end return ret end local function getgroups() - local ret = {} - local root = os.getenv("NUAGE_FAKE_ROOTDIR") local cmd = "pw " if root then cmd = cmd .. "-R " .. root .. " " end local f = io.popen(cmd .. "groupshow -a 2> /dev/null | cut -d: -f1") local groups = f:read("*a") f:close() return splitlines(groups) end local function checkgroup(group) local groups = getgroups() for _, group2chk in ipairs(groups) do if group == group2chk then return true end end return false end local function purge_group(groups) local ret = {} for _, group in ipairs(groups) do if checkgroup(group) then ret[#ret + 1] = group else warnmsg("ignoring non-existent group '" .. group .. "'") end 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) -- pw complains if the group does not exist, so if the user -- specifies one that cannot be found, nuageinit will generate -- an exception and exit, unlike cloud-init, which only issues -- a warning but creates the user anyway. list = purge_group(list) if #list > 0 then extraargs = " -G " .. table.concat(list, ",") end 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 = "" local input = nil if pwd.passwd then input = pwd.passwd postcmd = " -H 0" elseif pwd.plain_text_passwd then input = 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 f = io.popen(cmd, "w") if input then f:write(input) end local r = f:close(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 chmod(ak_path, "0600") chown(ak_path, dirattrs.uid, dirattrs.gid) end if chowndotssh then chmod(dotssh_path, "0700") chown(dotssh_path, dirattrs.uid, dirattrs.gid) end end local function adddoas(pwd) local chmodetcdir = false local chmoddoasconf = false local root = os.getenv("NUAGE_FAKE_ROOTDIR") local localbase = getlocalbase() local etcdir = localbase .. "/etc" if root then etcdir= root .. etcdir end local doasconf = etcdir .. "/doas.conf" local doasconf_attr = lfs.attributes(doasconf) if doasconf_attr == nil then chmoddoasconf = true local dirattrs = lfs.attributes(etcdir) if dirattrs == nil then local r, err = mkdir_p(etcdir) if not r then return nil, err .. " (creating " .. etcdir .. ")" end chmodetcdir = true end end local f = io.open(doasconf, "a") if not f then warnmsg("impossible to open " .. doasconf) return end if type(pwd.doas) == "string" then local rule = pwd.doas rule = rule:gsub("%%u", pwd.name) f:write(rule .. "\n") elseif type(pwd.doas) == "table" then for _, str in ipairs(pwd.doas) do local rule = str rule = rule:gsub("%%u", pwd.name) f:write(rule .. "\n") end end f:close() if chmoddoasconf then chmod(doasconf, "0640") end if chmodetcdir then chmod(etcdir, "0755") end end local function addsudo(pwd) local chmodsudoersd = false local chmodsudoers = false local root = os.getenv("NUAGE_FAKE_ROOTDIR") local localbase = getlocalbase() local sudoers_dir = localbase .. "/etc/sudoers.d" if root then sudoers_dir= root .. sudoers_dir end local sudoers = sudoers_dir .. "/90-nuageinit-users" local sudoers_attr = lfs.attributes(sudoers) if sudoers_attr == nil then chmodsudoers = true local dirattrs = lfs.attributes(sudoers_dir) if dirattrs == nil then local r, err = mkdir_p(sudoers_dir) if not r then return nil, err .. " (creating " .. sudoers_dir .. ")" end chmodsudoersd = true end end local f = io.open(sudoers, "a") if not f then warnmsg("impossible to open " .. sudoers) return end if type(pwd.sudo) == "string" then f:write(pwd.name .. " " .. pwd.sudo .. "\n") elseif type(pwd.sudo) == "table" then for _, str in ipairs(pwd.sudo) do f:write(pwd.name .. " " .. str .. "\n") end end f:close() if chmodsudoers then chmod(sudoers, "0440") end if chmodsudoersd then chmod(sudoers_dir, "0750") end end local function update_sshd_config(key, value) local sshd_config = "/etc/ssh/sshd_config" local root = os.getenv("NUAGE_FAKE_ROOTDIR") if root then sshd_config = root .. sshd_config end local f = assert(io.open(sshd_config, "r+")) local tgt = assert(io.open(sshd_config .. ".nuageinit", "w")) local found = false local pattern = "^%s*"..key:lower().."%s+(%w+)%s*#?.*$" while true do local line = f:read() if line == nil then break end local _, _, val = line:lower():find(pattern) if val then found = true if val == value then assert(tgt:write(line .. "\n")) else assert(tgt:write(key .. " " .. value .. "\n")) end else assert(tgt:write(line .. "\n")) end end if not found then assert(tgt:write(key .. " " .. value .. "\n")) end assert(f:close()) assert(tgt:close()) os.rename(sshd_config .. ".nuageinit", sshd_config) end local function exec_change_password(user, password, type, expire) local root = os.getenv("NUAGE_FAKE_ROOTDIR") local cmd = "pw " if root then cmd = cmd .. "-R " .. root .. " " end local postcmd = " -H 0" local input = password if type ~= nil and type == "text" then postcmd = " -h 0" else if password == "RANDOM" then input = nil postcmd = " -w random" end end cmd = cmd .. "usermod " .. user .. postcmd if expire then cmd = cmd .. " -p 1" else cmd = cmd .. " -p 0" end local f = io.popen(cmd .. " >/dev/null", "w") if input then f:write(input) end -- ignore stdout to avoid printing the password in case of random password local r = f:close(cmd) if not r then warnmsg("fail to change user password ".. user) warnmsg(cmd) end end local function change_password_from_line(line, expire) local user, password = line:match("%s*(%w+):(%S+)%s*") local type = nil if user and password then if password == "R" then password = "RANDOM" end if not password:match("^%$%d+%$%w+%$") then if password ~= "RANDOM" then type = "text" end end exec_change_password(user, password, type, expire) end end local function chpasswd(obj) if type(obj) ~= "table" then warnmsg("Invalid chpasswd entry, expecting an object") return end local expire = false if obj.expire ~= nil then if type(obj.expire) == "boolean" then expire = obj.expire else warnmsg("Invalid type for chpasswd.expire, expecting a boolean, got a ".. type(obj.expire)) end end if obj.users ~= nil then if type(obj.users) ~= "table" then warnmsg("Invalid type for chpasswd.users, expecting a list, got a ".. type(obj.users)) goto list end for _, u in ipairs(obj.users) do if type(u) ~= "table" then warnmsg("Invalid chpasswd.users entry, expecting an object, got a " .. type(u)) goto next end if not u.name then warnmsg("Invalid entry for chpasswd.users: missing 'name'") goto next end if not u.password then warnmsg("Invalid entry for chpasswd.users: missing 'password'") goto next end exec_change_password(u.name, u.password, u.type, expire) ::next:: end end ::list:: if obj.list ~= nil then warnmsg("chpasswd.list is deprecated consider using chpasswd.users") if type(obj.list) == "string" then for line in obj.list:gmatch("[^\n]+") do change_password_from_line(line, expire) end elseif type(obj.list) == "table" then for _, u in ipairs(obj.list) do change_password_from_line(u, expire) end end end end local function settimezone(timezone) if timezone == nil then return end local root = os.getenv("NUAGE_FAKE_ROOTDIR") if not root then root = "/" end - f, _, rc = os.execute("tzsetup -s -C " .. root .. " " .. timezone) + local f, _, rc = os.execute("tzsetup -s -C " .. root .. " " .. timezone) if not f then warnmsg("Impossible to configure time zone ( rc = " .. rc .. " )") return end end local function pkg_bootstrap() if os.getenv("NUAGE_RUN_TESTS") then return true end if os.execute("pkg -N 2>/dev/null") then return true end print("Bootstrapping pkg") return os.execute("env ASSUME_ALWAYS_YES=YES pkg bootstrap") end local function install_package(package) if package == nil then return true end local install_cmd = "pkg install -y " .. package local test_cmd = "pkg info -q " .. package if os.getenv("NUAGE_RUN_TESTS") then print(install_cmd) print(test_cmd) return true end if os.execute(test_cmd) then return true end return os.execute(install_cmd) end local function run_pkg_cmd(subcmd) local cmd = "env ASSUME_ALWAYS_YES=yes pkg " .. subcmd if os.getenv("NUAGE_RUN_TESTS") then print(cmd) return true end return os.execute(cmd) end local function update_packages() return run_pkg_cmd("update") end local function upgrade_packages() return run_pkg_cmd("upgrade") end local function addfile(file, defer) if type(file) ~= "table" then return false, "Invalid object" end if defer and not file.defer then return true end if not defer and file.defer then return true end if not file.path then return false, "No path provided for the file to write" end local content = nil if file.content then if file.encoding then if file.encoding == "b64" or file.encoding == "base64" then content = decode_base64(file.content) else return false, "Unsupported encoding: " .. file.encoding end else content = file.content end end local mode = "w" if file.append then mode = "a" end local root = os.getenv("NUAGE_FAKE_ROOTDIR") if not root then root = "" end local filepath = root .. file.path local f = assert(io.open(filepath, mode)) if content then f:write(content) end f:close() if file.permissions then chmod(filepath, file.permissions) end if file.owner then local owner, group = string.match(file.owner, "([^:]+):([^:]+)") if not owner then owner = file.owner end chown(filepath, owner, group) end return true end local n = { warn = warnmsg, err = errmsg, chmod = chmod, chown = chown, dirname = dirname, mkdir_p = mkdir_p, sethostname = sethostname, settimezone = settimezone, adduser = adduser, addgroup = addgroup, addsshkey = addsshkey, update_sshd_config = update_sshd_config, chpasswd = chpasswd, pkg_bootstrap = pkg_bootstrap, install_package = install_package, update_packages = update_packages, upgrade_packages = upgrade_packages, addsudo = addsudo, adddoas = adddoas, addfile = addfile } return n diff --git a/libexec/nuageinit/nuageinit b/libexec/nuageinit/nuageinit index f68e45701b6c..148f5b3b30cd 100755 --- a/libexec/nuageinit/nuageinit +++ b/libexec/nuageinit/nuageinit @@ -1,719 +1,717 @@ #!/usr/libexec/flua --- -- SPDX-License-Identifier: BSD-2-Clause-FreeBSD -- -- Copyright(c) 2022-2025 Baptiste Daroussin -- Copyright(c) 2025 Jesús Daniel Colmenares Oviedo local nuage = require("nuage") local ucl = require("ucl") local yaml = require("lyaml") if #arg ~= 2 then nuage.err("Usage: " .. arg[0] .. " ( | )", false) end local ni_path = arg[1] local citype = arg[2] local default_user = { name = "freebsd", homedir = "/home/freebsd", groups = "wheel", gecos = "FreeBSD User", shell = "/bin/sh", plain_text_passwd = "freebsd" } local root = os.getenv("NUAGE_FAKE_ROOTDIR") if not root then root = "" end local function openat(dir, name) local path_dir = root .. dir local path_name = path_dir .. "/" .. name nuage.mkdir_p(path_dir) local f, err = io.open(path_name, "w") if not f then nuage.err("unable to open " .. path_name .. ": " .. err) end return f, path_name end local function open_ssh_key(name) return openat("/etc/ssh", name) end local function open_config(name) return openat("/etc/rc.conf.d", name) end local function open_resolv_conf() return openat("/etc", "resolv.conf") end local function open_resolvconf_conf() return openat("/etc", "resolvconf.conf") end local function get_ifaces_by_mac() local parser = ucl.parser() -- grab ifaces local ns = io.popen("netstat -i --libxo json") local netres = ns:read("*a") ns:close() local res, err = parser:parse_string(netres) if not res then nuage.warn("Error parsing netstat -i --libxo json output: " .. err) return nil end local ifaces = parser:get_object() local myifaces = {} for _, iface in pairs(ifaces["statistics"]["interface"]) do if iface["network"]:match("") then local s = iface["address"] myifaces[s:lower()] = iface["name"] end end return myifaces end local function sethostname(obj) -- always prefer fqdn if specified over hostname if obj.fqdn then nuage.sethostname(obj.fqdn) elseif obj.hostname then nuage.sethostname(obj.hostname) end end local function settimezone(obj) nuage.settimezone(obj.timezone) end local function groups(obj) if obj.groups == nil then return end for n, g in pairs(obj.groups) do if (type(g) == "string") then local r = nuage.addgroup({name = g}) if not r then nuage.warn("failed to add group: " .. g) end elseif type(g) == "table" then for k, v in pairs(g) do nuage.addgroup({name = k, members = v}) end else nuage.warn("invalid type: " .. type(g) .. " for users entry number " .. n) end end end local function create_default_user(obj) if not obj.users then -- default user if none are defined nuage.adduser(default_user) end end local function users(obj) if obj.users == nil then return end for n, u in pairs(obj.users) do if type(u) == "string" then if u == "default" then nuage.adduser(default_user) else nuage.adduser({name = u}) end elseif type(u) == "table" then -- ignore users without a username if u.name == nil then goto unext end local homedir = nuage.adduser(u) if u.ssh_authorized_keys then for _, v in ipairs(u.ssh_authorized_keys) do nuage.addsshkey(homedir, v) end end if u.sudo then nuage.addsudo(u) end if u.doas then nuage.adddoas(u) end else nuage.warn("invalid type : " .. type(u) .. " for users entry number " .. n) end ::unext:: end end local function ssh_keys(obj) if obj.ssh_keys == nil then return end if type(obj.ssh_keys) ~= "table" then nuage.warn("Invalid type for ssh_keys") return end for key, val in pairs(obj.ssh_keys) do for keyname, keytype in key:gmatch("(%w+)_(%w+)") do local sshkn = nil if keytype == "public" then sshkn = "ssh_host_" .. keyname .. "_key.pub" elseif keytype == "private" then sshkn = "ssh_host_" .. keyname .. "_key" end if sshkn then local sshkey, path = open_ssh_key(sshkn) if sshkey then sshkey:write(val .. "\n") sshkey:close() end if keytype == "private" then nuage.chmod(path, "0600") end end end end end local function ssh_authorized_keys(obj) if obj.ssh_authorized_keys == nil then return end local homedir = nuage.adduser(default_user) for _, k in ipairs(obj.ssh_authorized_keys) do nuage.addsshkey(homedir, k) end end local function nameservers(interface, obj) local resolvconf_conf_handler = open_resolvconf_conf() if obj.search then local with_space = false resolvconf_conf_handler:write('search_domains="') for _, d in ipairs(obj.search) do if with_space then resolvconf_conf_handler:write(" " .. d) else resolvconf_conf_handler:write(d) with_space = true end end resolvconf_conf_handler:write('"\n') end if obj.addresses then local with_space = false resolvconf_conf_handler:write('name_servers="') for _, a in ipairs(obj.addresses) do if with_space then resolvconf_conf_handler:write(" " .. a) else resolvconf_conf_handler:write(a) with_space = true end end resolvconf_conf_handler:write('"\n') end resolvconf_conf_handler:close() local resolv_conf = root .. "/etc/resolv.conf" - resolv_conf_attr = lfs.attributes(resolv_conf) + local resolv_conf_attr = lfs.attributes(resolv_conf) if resolv_conf_attr == nil then - resolv_conf_handler = open_resolv_conf() + local resolv_conf_handler = open_resolv_conf() resolv_conf_handler:close() end if not os.execute("resolvconf -a " .. interface .. " < " .. resolv_conf) then nuage.warn("Failed to execute resolvconf(8)") end end local function install_packages(packages) if not nuage.pkg_bootstrap() then nuage.warn("Failed to bootstrap pkg, skip installing packages") return end for n, p in pairs(packages) do if type(p) == "string" then if not nuage.install_package(p) then nuage.warn("Failed to install : " .. p) end else nuage.warn("Invalid type: " .. type(p) .. " for packages entry number " .. n) end end end local function list_ifaces() local proc = io.popen("ifconfig -l") local raw_ifaces = proc:read("*a") proc:close() local ifaces = {} for i in raw_ifaces:gmatch("[^%s]+") do table.insert(ifaces, i) end return ifaces end local function get_ifaces_by_driver() local proc = io.popen("ifconfig -D") local drivers = {} local last_interface = nil for line in proc:lines() do local interface = line:match("^([%S]+): ") if interface then last_interface = interface end local driver = line:match("^[%s]+drivername: ([%S]+)$") if driver then drivers[driver] = last_interface end end proc:close() return drivers end local function match_rules(rules) -- To comply with the cloud-init specification, all rules must match and a table -- with the matching interfaces must be returned. This changes the way we initially -- thought about our implementation, since at first we only needed one interface, -- but cloud-init performs actions on a group of matching interfaces. + local interface local interfaces = {} if rules.macaddress then local ifaces = get_ifaces_by_mac() - local interface = ifaces[rules.macaddress] + interface = ifaces[rules.macaddress] if not interface then nuage.warn("not interface matching by MAC address: " .. rules.macaddress) return end interfaces[interface] = 1 end if rules.name then local match = false for _, i in pairs(list_ifaces()) do if i:match(rules.name) then match = true interfaces[i] = 1 end end if not match then nuage.warn("not interface matching by name: " .. rules.name) return end end if rules.driver then local match = false local drivers = get_ifaces_by_driver() for d in pairs(drivers) do if d:match(rules.driver) then match = true interface = drivers[d] interfaces[interface] = 1 end end if not match then nuage.warn("not interface matching by driver: " .. rules.driver) return end end return interfaces end local function write_files(files, defer) if not files then return end for n, file in pairs(files) do local r, errstr = nuage.addfile(file, defer) if not r then nuage.warn("Skipping write_files entry number " .. n .. ": " .. errstr) end end end -local function write_files_not_defered(obj) +local function write_files_not_deferred(obj) write_files(obj.write_files, false) end -local function write_files_defered(obj) +local function write_files_deferred(obj) write_files(obj.write_files, true) end -- Set network configuration from user_data local function network_config(obj) if obj.network == nil then return end local network = open_config("network") local routing = open_config("routing") local ipv6 = {} local set_defaultrouter = true local set_defaultrouter6 = true local set_nameservers = true for i, v in pairs(obj.network.ethernets) do local interfaces = {} if v.match then interfaces = match_rules(v.match) if next(interfaces) == nil then goto next end else interfaces[i] = 1 end local extra_opts = "" if v.wakeonlan then extra_opts = extra_opts .. " wol" end if v.mtu then + local mtu if type(v.mtu) == "number" then mtu = tostring(v.mtu) else mtu = v.mtu end if mtu:match("%d") then extra_opts = extra_opts .. " mtu " .. mtu else nuage.warn("MTU is not set because the specified value is invalid: " .. mtu) end end for interface in pairs(interfaces) do if v.match and v.match.macaddress and v["set-name"] then local ifaces = get_ifaces_by_mac() local matched = ifaces[v.match.macaddress] if matched and matched == interface then network:write("ifconfig_" .. interface .. '_name=' .. v["set-name"] .. '\n') interface = v["set-name"] end end if v.dhcp4 then network:write("ifconfig_" .. interface .. '="DHCP"' .. extra_opts .. '\n') elseif v.addresses then for _, a in pairs(v.addresses) do if a:match("^(%d+)%.(%d+)%.(%d+)%.(%d+)") then network:write("ifconfig_" .. interface .. '="inet ' .. a .. extra_opts .. '"\n') else network:write("ifconfig_" .. interface .. '_ipv6="inet6 ' .. a .. extra_opts .. '"\n') ipv6[#ipv6 + 1] = interface end end if set_nameservers and v.nameservers then set_nameservers = false nameservers(interface, v.nameservers) end if set_defaultrouter and v.gateway4 then set_defaultrouter = false routing:write('defaultrouter="' .. v.gateway4 .. '"\n') end if v.gateway6 then if set_defaultrouter6 then set_defaultrouter6 = false routing:write('ipv6_defaultrouter="' .. v.gateway6 .. '"\n') end routing:write("ipv6_route_" .. interface .. '="' .. v.gateway6) routing:write(" -prefixlen 128 -interface " .. interface .. '"\n') end end end ::next:: end if #ipv6 > 0 then network:write('ipv6_network_interfaces="') network:write(table.concat(ipv6, " ") .. '"\n') network:write('ipv6_default_interface="' .. ipv6[1] .. '"\n') end network:close() routing:close() end local function ssh_pwauth(obj) if obj.ssh_pwauth == nil then return end local value = "no" if obj.ssh_pwauth then value = "yes" end nuage.update_sshd_config("PasswordAuthentication", value) end local function runcmd(obj) if obj.runcmd == nil then return end local f = nil for _, c in ipairs(obj.runcmd) do if f == nil then nuage.mkdir_p(root .. "/var/cache/nuageinit") f = assert(io.open(root .. "/var/cache/nuageinit/runcmds", "w")) f:write("#!/bin/sh\n") end f:write(c .. "\n") end if f ~= nil then f:close() nuage.chmod(root .. "/var/cache/nuageinit/runcmds", "0755") end end local function packages(obj) if obj.package_update then nuage.update_packages() end if obj.package_upgrade then nuage.upgrade_packages() end if obj.packages then install_packages(obj.packages) end end local function chpasswd(obj) if obj.chpasswd == nil then return end nuage.chpasswd(obj.chpasswd) end local function config2_network(p) local parser = ucl.parser() local f = io.open(p .. "/network_data.json") if not f then -- silently return no network configuration is provided return end f:close() local res, err = parser:parse_file(p .. "/network_data.json") if not res then nuage.warn("error parsing network_data.json: " .. err) return end local obj = parser:get_object() local ifaces = get_ifaces_by_mac() if not ifaces then nuage.warn("no network interfaces found") return end local mylinks = {} for _, v in pairs(obj["links"]) do local s = v["ethernet_mac_address"]:lower() mylinks[v["id"]] = ifaces[s] end local network = open_config("network") local routing = open_config("routing") local ipv6 = {} local ipv6_routes = {} local ipv4 = {} for _, v in pairs(obj["networks"]) do local interface = mylinks[v["link"]] if v["type"] == "ipv4_dhcp" then network:write("ifconfig_" .. interface .. '="DHCP"\n') end if v["type"] == "ipv4" then network:write( "ifconfig_" .. interface .. '="inet ' .. v["ip_address"] .. " netmask " .. v["netmask"] .. '"\n' ) if v["gateway"] then routing:write('defaultrouter="' .. v["gateway"] .. '"\n') end if v["routes"] then for i, r in ipairs(v["routes"]) do local rname = "cloudinit" .. i .. "_" .. interface if v["gateway"] and v["gateway"] == r["gateway"] then goto next end if r["network"] == "0.0.0.0" then routing:write('defaultrouter="' .. r["gateway"] .. '"\n') goto next end routing:write("route_" .. rname .. '="-net ' .. r["network"] .. " ") routing:write(r["gateway"] .. " " .. r["netmask"] .. '"\n') ipv4[#ipv4 + 1] = rname ::next:: end end end if v["type"] == "ipv6" then ipv6[#ipv6 + 1] = interface ipv6_routes[#ipv6_routes + 1] = interface network:write("ifconfig_" .. interface .. '_ipv6="inet6 ' .. v["ip_address"] .. '"\n') if v["gateway"] then routing:write('ipv6_defaultrouter="' .. v["gateway"] .. '"\n') routing:write("ipv6_route_" .. interface .. '="' .. v["gateway"]) routing:write(" -prefixlen 128 -interface " .. interface .. '"\n') end -- TODO compute the prefixlen for the routes --if v["routes"] then -- for i, r in ipairs(v["routes"]) do -- local rname = "cloudinit" .. i .. "_" .. mylinks[v["link"]] -- -- skip all the routes which are already covered by the default gateway, some provider -- -- still list plenty of them. -- if v["gateway"] == r["gateway"] then -- goto next -- end -- routing:write("ipv6_route_" .. rname .. '"\n') -- ipv6_routes[#ipv6_routes + 1] = rname -- ::next:: -- end --end end end if #ipv4 > 0 then routing:write('static_routes="') routing:write(table.concat(ipv4, " ") .. '"\n') end if #ipv6 > 0 then network:write('ipv6_network_interfaces="') network:write(table.concat(ipv6, " ") .. '"\n') network:write('ipv6_default_interface="' .. ipv6[1] .. '"\n') end if #ipv6_routes > 0 then routing:write('ipv6_static_routes="') routing:write(table.concat(ipv6, " ") .. '"\n') end network:close() routing:close() end local function parse_network_config() local nc_file = ni_path .. "/network-config" local nc_file_attr = lfs.attributes(nc_file) if nc_file_attr == nil then return end local f, err = io.open(nc_file) if err then nuage.err("error parsing nocloud network-config: " .. err) end local obj = yaml.load(f:read("*a")) f:close() if not obj then nuage.err("error parsing nocloud network-config") end local netobj = {} netobj["network"] = obj return netobj end if citype == "config-2" then local parser = ucl.parser() local res, err = parser:parse_file(ni_path .. "/meta_data.json") if not res then nuage.err("error parsing config-2 meta_data.json: " .. err) end local obj = parser:get_object() if obj.public_keys then local homedir = nuage.adduser(default_user) for _,v in pairs(obj.public_keys) do nuage.addsshkey(homedir, v) end end nuage.sethostname(obj["hostname"]) -- network config2_network(ni_path) elseif citype == "nocloud" then local f, err = io.open(ni_path .. "/meta-data") if err then nuage.err("error parsing nocloud meta-data: " .. err) end local obj = yaml.load(f:read("*a")) f:close() if not obj then nuage.err("error parsing nocloud meta-data") end local hostname = obj["local-hostname"] if not hostname then hostname = obj["hostname"] end if hostname then nuage.sethostname(hostname) end elseif citype ~= "postnet" then nuage.err("Unknown cloud init type: " .. citype) end -- deal with user-data local ud = nil local f = nil local userdatas = {"user-data", "user_data"} for _, v in pairs(userdatas) do f = io.open(ni_path .. "/" .. v, "r") if f then ud = v break end end if not f then os.exit(0) end local line = f:read("*l") if citype ~= "postnet" then local content = f:read("*a") nuage.mkdir_p(root .. "/var/cache/nuageinit") local tof = assert(io.open(root .. "/var/cache/nuageinit/user_data", "w")) tof:write(line .. "\n" .. content) tof:close() end f:close() if line == "#cloud-config" then local pre_network_calls = { sethostname, settimezone, groups, create_default_user, ssh_keys, ssh_authorized_keys, network_config, ssh_pwauth, runcmd, - write_files_not_defered, + write_files_not_deferred, } local post_network_calls = { packages, users, chpasswd, - write_files_defered, + write_files_deferred, } f = io.open(ni_path .. "/" .. ud) local obj = yaml.load(f:read("*a")) f:close() if not obj then nuage.err("error parsing cloud-config file: " .. ud) end local calls_table = pre_network_calls if citype == "postnet" then calls_table = post_network_calls end for i = 1, #calls_table do if citype == "nocloud" and calls_table[i] == network_config then - netobj = parse_network_config() - if netobj == nil then - network_config(obj) - else - network_config(netobj) - end + local netobj = parse_network_config() or obj + network_config(netobj) else calls_table[i](obj) end end elseif line:sub(1, 2) == "#!" then -- delay for execution at rc.local time -- nuage.chmod(root .. "/var/cache/nuageinit/user_data", "0755") end diff --git a/libexec/nuageinit/tests/addfile.lua b/libexec/nuageinit/tests/addfile.lua index 98d020e557c0..ea98369f1909 100644 --- a/libexec/nuageinit/tests/addfile.lua +++ b/libexec/nuageinit/tests/addfile.lua @@ -1,71 +1,71 @@ #!/bin/libexec/flua local n = require("nuage") local lfs = require("lfs") local f = { content = "plop" } local r, err = n.addfile(f, false) if r or err ~= "No path provided for the file to write" then n.err("addfile should not accept a file to write without a path") end local function addfile_and_getres(file) local r, err = n.addfile(file, false) if not r then n.err(err) end local root = os.getenv("NUAGE_FAKE_ROOTDIR") if not root then root = "" end local filepath = root .. file.path local resf = assert(io.open(filepath, "r")) local str = resf:read("*all") resf:close() return str end -- simple file f.path="/tmp/testnuage" local str = addfile_and_getres(f) if str ~= f.content then n.err("Invalid file content") end --- the file is overwriten +-- the file is overwritten f.content = "test" str = addfile_and_getres(f) if str ~= f.content then n.err("Invalid file content, not overwritten") end -- try to append now f.content = "more" f.append = true str = addfile_and_getres(f) if str ~= "test" .. f.content then n.err("Invalid file content, not appended") end -- base64 f.content = "YmxhCg==" f.encoding = "base64" f.append = false str = addfile_and_getres(f) if str ~= "bla\n" then n.err("Invalid file content, base64 decode") end -- b64 f.encoding = "b64" str = addfile_and_getres(f) if str ~= "bla\n" then n.err("Invalid file content, b64 decode") print("==>" .. str .. "<==") end diff --git a/libexec/nuageinit/tests/nuageinit.sh b/libexec/nuageinit/tests/nuageinit.sh index 2b7c5226c97a..851f7110378a 100644 --- a/libexec/nuageinit/tests/nuageinit.sh +++ b/libexec/nuageinit/tests/nuageinit.sh @@ -1,947 +1,947 @@ #- # Copyright (c) 2022-2025 Baptiste Daroussin # Copyright (c) 2025 Jesús Daniel Colmenares Oviedo # # SPDX-License-Identifier: BSD-2-Clause # export NUAGE_FAKE_ROOTDIR="$PWD" 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 atf_test_case config2_ssh_keys atf_test_case nocloud_userdata_cloudconfig_ssh_pwauth atf_test_case nocloud_userdata_cloudconfig_chpasswd atf_test_case nocloud_userdata_cloudconfig_chpasswd_list_string atf_test_case nocloud_userdata_cloudconfig_chpasswd_list_list atf_test_case config2_userdata_runcmd atf_test_case config2_userdata_packages atf_test_case config2_userdata_update_packages atf_test_case config2_userdata_upgrade_packages atf_test_case config2_userdata_shebang atf_test_case config2_userdata_fqdn_and_hostname atf_test_case config2_userdata_write_files setup_test_adduser() { here=$(pwd) export NUAGE_FAKE_ROOTDIR=$(pwd) mkdir -p etc/ssh 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 } 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() { mkdir -p media/nuageinit atf_check -s exit:1 -e match:"nuageinit: error parsing nocloud.*" /usr/libexec/nuageinit "${PWD}"/media/nuageinit/ nocloud printf "instance-id: iid-local01\nlocal-hostname: cloudimg\n" > "${PWD}"/media/nuageinit/meta-data atf_check -s exit:0 /usr/libexec/nuageinit "${PWD}"/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 "${PWD}"/media/nuageinit nocloud atf_check -o inline:"hostname=\"myhost\"\n" cat etc/rc.conf.d/hostname } nocloud_userdata_script_body() { mkdir -p media/nuageinit printf "instance-id: iid-local01\n" > "${PWD}"/media/nuageinit/meta-data printf "#!/bin/sh\necho yeah\n" > "${PWD}"/media/nuageinit/user-data chmod 755 "${PWD}"/media/nuageinit/user-data atf_check -s exit:0 /usr/libexec/nuageinit "${PWD}"/media/nuageinit nocloud atf_check -o inline:"#!/bin/sh\necho yeah\n" cat var/cache/nuageinit/user_data } nocloud_user_data_script_body() { mkdir -p media/nuageinit printf "instance-id: iid-local01\n" > "${PWD}"/media/nuageinit/meta-data printf "#!/bin/sh\necho yeah\n" > "${PWD}"/media/nuageinit/user_data chmod 755 "${PWD}"/media/nuageinit/user_data atf_check -s exit:0 /usr/libexec/nuageinit "${PWD}"/media/nuageinit nocloud atf_check -o inline:"#!/bin/sh\necho yeah\n" cat var/cache/nuageinit/user_data } nocloud_userdata_cloudconfig_users_head() { atf_set "require.user" root } nocloud_userdata_cloudconfig_users_body() { mkdir -p media/nuageinit printf "instance-id: iid-local01\n" > "${PWD}"/media/nuageinit/meta-data mkdir -p etc cat > etc/master.passwd << EOF root:*:0:0::0:0:Charlie &:/root:/bin/sh sys:*:1:0::0:0:Sys:/home/sys:/bin/sh EOF pwd_mkdb -d etc "${PWD}"/etc/master.passwd cat > etc/group << EOF wheel:*:0:root users:*:1: 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 sudo: ALL=(ALL) NOPASSWD:ALL doas: permit persist %u as root groups: users passwd: $6$j212wezy$7H/1LT4f9/N3wpgNunhsIqtMj62OKiS3nyNwuizouQc3u7MbYCarYeAHWYPYb2FT.lbioDm2RrkJPb9BZMN1O/ - name: bla sudo: - "ALL=(ALL) NOPASSWD:/usr/sbin/pw" - "ALL=(ALL) ALL" doas: - "deny %u as foobar" - "permit persist %u as root cmd whoami" EOF atf_check /usr/libexec/nuageinit "${PWD}"/media/nuageinit nocloud atf_check /usr/libexec/nuageinit "${PWD}"/media/nuageinit postnet cat > expectedgroup << EOF wheel:*:0:root,freebsd users:*:1:foobar admingroup:*:1001:root,sys cloud-users:*:1002: freebsd:*:1003: foobar:*:1004: bla:*:1005: EOF cat > expectedpasswd << 'EOF' root:*:0:0::0:0:Charlie &:/root:/bin/sh sys:*:1:0::0:0:Sys:/home/sys:/bin/sh freebsd:freebsd:1001:1003::0:0:FreeBSD User:/home/freebsd:/bin/sh foobar:$6$j212wezy$7H/1LT4f9/N3wpgNunhsIqtMj62OKiS3nyNwuizouQc3u7MbYCarYeAHWYPYb2FT.lbioDm2RrkJPb9BZMN1O/:1002:1004::0:0:Foo B. Bar:/home/foobar:/bin/sh bla::1003:1005::0:0:bla User:/home/bla:/bin/sh EOF sed -i "" "s/freebsd:.*:1001/freebsd:freebsd:1001/" "${PWD}"/etc/master.passwd atf_check -o file:expectedpasswd cat "${PWD}"/etc/master.passwd atf_check -o file:expectedgroup cat "${PWD}"/etc/group localbase=`sysctl -ni user.localbase 2> /dev/null` if [ -z "${localbase}" ]; then # fallback localbase="/usr/local" fi atf_check -o inline:"foobar ALL=(ALL) NOPASSWD:ALL\nbla ALL=(ALL) NOPASSWD:/usr/sbin/pw\nbla ALL=(ALL) ALL\n" cat "${PWD}/${localbase}/etc/sudoers.d/90-nuageinit-users" atf_check -o inline:"permit persist foobar as root\ndeny bla as foobar\npermit persist bla as root cmd whoami\n" cat "${PWD}/${localbase}/etc/doas.conf" } nocloud_network_head() { atf_set "require.user" root } nocloud_network_body() { mkdir -p media/nuageinit mkdir -p etc cat > etc/master.passwd << EOF root:*:0:0::0:0:Charlie &:/root:/bin/sh sys:*:1:0::0:0:Sys:/home/sys:/bin/sh EOF pwd_mkdb -d etc "${PWD}"/etc/master.passwd cat > etc/group << EOF wheel:*:0:root users:*:1: EOF 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" > "${PWD}"/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.0.2.2/24 - 2001:db8::2/64 gateway4: 192.0.2.1 gateway6: 2001:db8::1 EOF atf_check /usr/libexec/nuageinit "${PWD}"/media/nuageinit nocloud cat > network << EOF ifconfig_${myiface}="inet 192.0.2.2/24" ifconfig_${myiface}_ipv6="inet6 2001:db8::2/64" ipv6_network_interfaces="${myiface}" ipv6_default_interface="${myiface}" EOF cat > routing << EOF defaultrouter="192.0.2.1" ipv6_defaultrouter="2001:db8::1" ipv6_route_${myiface}="2001:db8::1 -prefixlen 128 -interface ${myiface}" EOF atf_check -o file:network cat "${PWD}"/etc/rc.conf.d/network atf_check -o file:routing cat "${PWD}"/etc/rc.conf.d/routing } config2_body() { mkdir -p media/nuageinit atf_check -s exit:1 -e match:"nuageinit: error parsing config-2 meta_data.json:.*" /usr/libexec/nuageinit "${PWD}"/media/nuageinit config-2 printf "{}" > media/nuageinit/meta_data.json atf_check /usr/libexec/nuageinit "${PWD}"/media/nuageinit config-2 cat > media/nuageinit/meta_data.json << EOF { "hostname": "cloudimg" } EOF atf_check /usr/libexec/nuageinit "${PWD}"/media/nuageinit config-2 atf_check -o inline:"hostname=\"cloudimg\"\n" cat etc/rc.conf.d/hostname } config2_pubkeys_head() { atf_set "require.user" root } config2_pubkeys_body() { 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/sh sys:*:1:0::0:0:Sys:/home/sys:/bin/sh EOF pwd_mkdb -d etc "${PWD}"/etc/master.passwd cat > etc/group << EOF wheel:*:0:root users:*:1: EOF atf_check /usr/libexec/nuageinit "${PWD}"/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_head() { atf_set "require.user" root } config2_pubkeys_user_data_body() { 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/sh sys:*:1:0::0:0:Sys:/home/sys:/bin/sh EOF pwd_mkdb -d etc "${PWD}"/etc/master.passwd cat > etc/group << EOF wheel:*:0:root users:*:1: EOF atf_check /usr/libexec/nuageinit "${PWD}"/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() { 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:db8::3257:9652/64", "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 atf_check /usr/libexec/nuageinit "${PWD}"/media/nuageinit config-2 cat > network << EOF ifconfig_${myiface}="DHCP" ifconfig_${myiface}_ipv6="inet6 2001:db8::3257:9652/64" 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 "${PWD}"/etc/rc.conf.d/network atf_check -o file:routing cat "${PWD}"/etc/rc.conf.d/routing } config2_network_static_v4_body() { 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 atf_check /usr/libexec/nuageinit "${PWD}"/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 "${PWD}"/etc/rc.conf.d/network atf_check -o file:routing cat "${PWD}"/etc/rc.conf.d/routing } config2_ssh_keys_head() { atf_set "require.user" root } config2_ssh_keys_body() { here=$(pwd) export NUAGE_FAKE_ROOTDIR=$(pwd) mkdir -p media/nuageinit touch media/nuageinit/meta_data.json cat > media/nuageinit/user-data << EOF #cloud-config ssh_keys: rsa_private: | -----BEGIN RSA PRIVATE KEY----- MIIBxwIBAAJhAKD0YSHy73nUgysO13XsJmd4fHiFyQ+00R7VVu2iV9Qco ... -----END RSA PRIVATE KEY----- rsa_public: ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAGEAoPRhIfLvedSDKw7Xd ... ed25519_private: | -----BEGIN OPENSSH PRIVATE KEY----- blabla ... -----END OPENSSH PRIVATE KEY----- ed25519_public: ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIK+MH4E8KO32N5CXRvXVqvyZVl0+6ue4DobdhU0FqFd+ EOF mkdir -p etc/ssh 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 "${PWD}"/media/nuageinit config-2 _expected="-----BEGIN RSA PRIVATE KEY----- MIIBxwIBAAJhAKD0YSHy73nUgysO13XsJmd4fHiFyQ+00R7VVu2iV9Qco ... -----END RSA PRIVATE KEY----- " atf_check -o inline:"${_expected}" cat ${PWD}/etc/ssh/ssh_host_rsa_key _expected="ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAGEAoPRhIfLvedSDKw7Xd ...\n" atf_check -o inline:"${_expected}" cat ${PWD}/etc/ssh/ssh_host_rsa_key.pub _expected="-----BEGIN OPENSSH PRIVATE KEY----- blabla ... -----END OPENSSH PRIVATE KEY----- " atf_check -o inline:"${_expected}" cat ${PWD}/etc/ssh/ssh_host_ed25519_key _expected="ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIK+MH4E8KO32N5CXRvXVqvyZVl0+6ue4DobdhU0FqFd+\n" atf_check -o inline:"${_expected}" cat ${PWD}/etc/ssh/ssh_host_ed25519_key.pub } nocloud_userdata_cloudconfig_ssh_pwauth_head() { atf_set "require.user" root } nocloud_userdata_cloudconfig_ssh_pwauth_body() { mkdir -p etc cat > etc/master.passwd << EOF root:*:0:0::0:0:Charlie &:/root:/bin/sh sys:*:1:0::0:0:Sys:/home/sys:/bin/sh EOF pwd_mkdb -d etc "${PWD}"/etc/master.passwd cat > etc/group << EOF wheel:*:0:root users:*:1: EOF mkdir -p media/nuageinit printf "instance-id: iid-local01\n" > "${PWD}"/media/nuageinit/meta-data cat > media/nuageinit/user-data << 'EOF' #cloud-config ssh_pwauth: true EOF mkdir -p etc/ssh/ touch etc/ssh/sshd_config atf_check -o empty -e empty /usr/libexec/nuageinit "${PWD}"/media/nuageinit nocloud atf_check -o inline:"PasswordAuthentication yes\n" cat etc/ssh/sshd_config # Same value we don't touch anything printf " PasswordAuthentication yes # I want password\n" > etc/ssh/sshd_config atf_check -o empty -e empty /usr/libexec/nuageinit "${PWD}"/media/nuageinit nocloud atf_check -o inline:" PasswordAuthentication yes # I want password\n" cat etc/ssh/sshd_config printf " PasswordAuthentication no # Should change\n" > etc/ssh/sshd_config atf_check -o empty -e empty /usr/libexec/nuageinit "${PWD}"/media/nuageinit nocloud atf_check -o inline:"PasswordAuthentication yes\n" cat etc/ssh/sshd_config cat > media/nuageinit/user-data << 'EOF' #cloud-config ssh_pwauth: false EOF printf " PasswordAuthentication no # no passwords\n" > etc/ssh/sshd_config atf_check -o empty -e empty /usr/libexec/nuageinit "${PWD}"/media/nuageinit nocloud atf_check -o inline:" PasswordAuthentication no # no passwords\n" cat etc/ssh/sshd_config printf " PasswordAuthentication yes # Should change\n" > etc/ssh/sshd_config atf_check -o empty -e empty /usr/libexec/nuageinit "${PWD}"/media/nuageinit nocloud atf_check -o inline:"PasswordAuthentication no\n" cat etc/ssh/sshd_config } nocloud_userdata_cloudconfig_chpasswd_head() { atf_set "require.user" root } nocloud_userdata_cloudconfig_chpasswd_body() { mkdir -p etc cat > etc/master.passwd << EOF root:*:0:0::0:0:Charlie &:/root:/bin/sh sys:*:1:0::0:0:Sys:/home/sys:/bin/sh user:*:1:0::0:0:Sys:/home/sys:/bin/sh EOF pwd_mkdb -d etc "${PWD}"/etc/master.passwd cat > etc/group << EOF wheel:*:0:root users:*:1: EOF mkdir -p media/nuageinit printf "instance-id: iid-local01\n" > "${PWD}"/media/nuageinit/meta-data cat > media/nuageinit/user-data << 'EOF' #cloud-config chpasswd: expire: true users: - { user: "sys", password: RANDOM } EOF atf_check -o empty /usr/libexec/nuageinit "${PWD}"/media/nuageinit nocloud atf_check -o empty -e inline:"nuageinit: Invalid entry for chpasswd.users: missing 'name'\n" /usr/libexec/nuageinit "${PWD}"/media/nuageinit postnet # nothing modified atf_check -o inline:"sys:*:1:0::0:0:Sys:/home/sys:/bin/sh\n" pw -R $(pwd) usershow sys cat > media/nuageinit/user-data << 'EOF' #cloud-config chpasswd: expire: true users: - { name: "sys", pwd: RANDOM } EOF atf_check -o empty -e inline:"nuageinit: Invalid entry for chpasswd.users: missing 'password'\n" /usr/libexec/nuageinit "${PWD}"/media/nuageinit postnet # nothing modified atf_check -o inline:"sys:*:1:0::0:0:Sys:/home/sys:/bin/sh\n" pw -R $(pwd) usershow sys cat > media/nuageinit/user-data << 'EOF' #cloud-config chpasswd: expire: false users: - { name: "sys", password: RANDOM } EOF # not empty because the password is printed to stdout atf_check -o empty -e empty /usr/libexec/nuageinit "${PWD}"/media/nuageinit postnet atf_check -o match:'sys:\$.*:1:0::0:0:Sys:/home/sys:/bin/sh$' pw -R $(pwd) usershow sys cat > media/nuageinit/user-data << 'EOF' #cloud-config chpasswd: expire: true users: - { name: "sys", password: RANDOM } EOF # not empty because the password is printed to stdout atf_check -o empty -e empty /usr/libexec/nuageinit "${PWD}"/media/nuageinit postnet atf_check -o match:'sys:\$.*:1:0::1:0:Sys:/home/sys:/bin/sh$' pw -R $(pwd) usershow sys cat > media/nuageinit/user-data << 'EOF' #cloud-config chpasswd: expire: true users: - { name: "user", password: "$6$j212wezy$7H/1LT4f9/N3wpgNunhsIqtMj62OKiS3nyNwuizouQc3u7MbYCarYeAHWYPYb2FT.lbioDm2RrkJPb9BZMN1O/" } EOF # not empty because the password is printed to stdout atf_check -o empty -e empty /usr/libexec/nuageinit "${PWD}"/media/nuageinit postnet atf_check -o inline:'user:$6$j212wezy$7H/1LT4f9/N3wpgNunhsIqtMj62OKiS3nyNwuizouQc3u7MbYCarYeAHWYPYb2FT.lbioDm2RrkJPb9BZMN1O/:1:0::1:0:Sys:/home/sys:/bin/sh\n' pw -R $(pwd) usershow user } nocloud_userdata_cloudconfig_chpasswd_list_string_head() { atf_set "require.user" root } nocloud_userdata_cloudconfig_chpasswd_list_string_body() { mkdir -p etc cat > etc/master.passwd << EOF root:*:0:0::0:0:Charlie &:/root:/bin/sh sys:*:1:0::0:0:Sys:/home/sys:/bin/sh user:*:1:0::0:0:Sys:/home/sys:/bin/sh EOF pwd_mkdb -d etc "${PWD}"/etc/master.passwd cat > etc/group << EOF wheel:*:0:root users:*:1: EOF mkdir -p media/nuageinit printf "instance-id: iid-local01\n" > "${PWD}"/media/nuageinit/meta-data cat > media/nuageinit/user-data << 'EOF' #cloud-config chpasswd: expire: true list: | sys:RANDOM EOF atf_check -o empty /usr/libexec/nuageinit "${PWD}"/media/nuageinit nocloud atf_check -o empty -e inline:"nuageinit: chpasswd.list is deprecated consider using chpasswd.users\n" /usr/libexec/nuageinit "${PWD}"/media/nuageinit postnet atf_check -o match:'sys:\$.*:1:0::1:0:Sys:/home/sys:/bin/sh$' pw -R $(pwd) usershow sys cat > media/nuageinit/user-data << 'EOF' #cloud-config chpasswd: expire: false list: | sys:plop user:$6$j212wezy$7H/1LT4f9/N3wpgNunhsIqtMj62OKiS3nyNwuizouQc3u7MbYCarYeAHWYPYb2FT.lbioDm2RrkJPb9BZMN1O/ root:R EOF atf_check -o empty -e ignore /usr/libexec/nuageinit "${PWD}"/media/nuageinit postnet atf_check -o match:'sys:\$.*:1:0::0:0:Sys:/home/sys:/bin/sh$' pw -R $(pwd) usershow sys atf_check -o inline:'user:$6$j212wezy$7H/1LT4f9/N3wpgNunhsIqtMj62OKiS3nyNwuizouQc3u7MbYCarYeAHWYPYb2FT.lbioDm2RrkJPb9BZMN1O/:1:0::0:0:Sys:/home/sys:/bin/sh\n' pw -R $(pwd) usershow user atf_check -o match:'root:\$.*:0:0::0:0:Charlie &:/root:/bin/sh$' pw -R $(pwd) usershow root } nocloud_userdata_cloudconfig_chpasswd_list_list_head() { atf_set "require.user" root } nocloud_userdata_cloudconfig_chpasswd_list_list_body() { mkdir -p etc cat > etc/master.passwd << EOF root:*:0:0::0:0:Charlie &:/root:/bin/sh sys:*:1:0::0:0:Sys:/home/sys:/bin/sh user:*:1:0::0:0:Sys:/home/sys:/bin/sh EOF pwd_mkdb -d etc "${PWD}"/etc/master.passwd cat > etc/group << EOF wheel:*:0:root users:*:1: EOF mkdir -p media/nuageinit printf "instance-id: iid-local01\n" > "${PWD}"/media/nuageinit/meta-data cat > media/nuageinit/user-data << 'EOF' #cloud-config chpasswd: expire: true list: - sys:RANDOM EOF atf_check -o empty /usr/libexec/nuageinit "${PWD}"/media/nuageinit nocloud atf_check -o empty -e inline:"nuageinit: chpasswd.list is deprecated consider using chpasswd.users\n" /usr/libexec/nuageinit "${PWD}"/media/nuageinit postnet atf_check -o match:'sys:\$.*:1:0::1:0:Sys:/home/sys:/bin/sh$' pw -R $(pwd) usershow sys cat > media/nuageinit/user-data << 'EOF' #cloud-config chpasswd: expire: false list: - sys:plop - user:$6$j212wezy$7H/1LT4f9/N3wpgNunhsIqtMj62OKiS3nyNwuizouQc3u7MbYCarYeAHWYPYb2FT.lbioDm2RrkJPb9BZMN1O/ - root:R EOF atf_check -o empty -e ignore /usr/libexec/nuageinit "${PWD}"/media/nuageinit postnet atf_check -o match:'sys:\$.*:1:0::0:0:Sys:/home/sys:/bin/sh$' pw -R $(pwd) usershow sys atf_check -o inline:'user:$6$j212wezy$7H/1LT4f9/N3wpgNunhsIqtMj62OKiS3nyNwuizouQc3u7MbYCarYeAHWYPYb2FT.lbioDm2RrkJPb9BZMN1O/:1:0::0:0:Sys:/home/sys:/bin/sh\n' pw -R $(pwd) usershow user atf_check -o match:'root:\$.*:0:0::0:0:Charlie &:/root:/bin/sh$' pw -R $(pwd) usershow root } config2_userdata_runcmd_head() { atf_set "require.user" root } config2_userdata_runcmd_body() { mkdir -p media/nuageinit setup_test_adduser printf "{}" > media/nuageinit/meta_data.json cat > media/nuageinit/user_data << 'EOF' #cloud-config runcmd: EOF chmod 755 "${PWD}"/media/nuageinit/user_data atf_check /usr/libexec/nuageinit "${PWD}"/media/nuageinit config-2 cat > media/nuageinit/user_data << 'EOF' #cloud-config runcmd: - plop EOF chmod 755 "${PWD}"/media/nuageinit/user_data atf_check -s exit:0 /usr/libexec/nuageinit "${PWD}"/media/nuageinit config-2 test -f var/cache/nuageinit/runcmds || atf_fail "File not created" test -x var/cache/nuageinit/runcmds || atf_fail "Missing execution permission" atf_check -o inline:"#!/bin/sh\nplop\n" cat var/cache/nuageinit/runcmds cat > media/nuageinit/user_data << 'EOF' #cloud-config runcmd: - echo "yeah!" - uname -s EOF chmod 755 "${PWD}"/media/nuageinit/user_data atf_check /usr/libexec/nuageinit "${PWD}"/media/nuageinit config-2 atf_check -o inline:"#!/bin/sh\necho \"yeah!\"\nuname -s\n" cat var/cache/nuageinit/runcmds } config2_userdata_packages_head() { atf_set "require.user" root } config2_userdata_packages_body() { mkdir -p media/nuageinit setup_test_adduser export NUAGE_RUN_TESTS=1 printf "{}" > media/nuageinit/meta_data.json cat > media/nuageinit/user_data << 'EOF' #cloud-config packages: EOF chmod 755 "${PWD}"/media/nuageinit/user_data atf_check /usr/libexec/nuageinit "${PWD}"/media/nuageinit postnet cat > media/nuageinit/user_data << 'EOF' #cloud-config packages: - yeah/plop EOF chmod 755 "${PWD}"/media/nuageinit/user_data atf_check -s exit:0 -o inline:"pkg install -y yeah/plop\npkg info -q yeah/plop\n" /usr/libexec/nuageinit "${PWD}"/media/nuageinit postnet cat > media/nuageinit/user_data << 'EOF' #cloud-config packages: - curl EOF chmod 755 "${PWD}"/media/nuageinit/user_data atf_check -o inline:"pkg install -y curl\npkg info -q curl\n" /usr/libexec/nuageinit "${PWD}"/media/nuageinit postnet cat > media/nuageinit/user_data << 'EOF' #cloud-config packages: - curl - meh: bla EOF chmod 755 "${PWD}"/media/nuageinit/user_data atf_check -o inline:"pkg install -y curl\npkg info -q curl\n" -e inline:"nuageinit: Invalid type: table for packages entry number 2\n" /usr/libexec/nuageinit "${PWD}"/media/nuageinit postnet } config2_userdata_update_packages_body() { mkdir -p media/nuageinit setup_test_adduser export NUAGE_RUN_TESTS=1 printf "{}" > media/nuageinit/meta_data.json cat > media/nuageinit/user_data << 'EOF' #cloud-config package_update: true EOF chmod 755 "${PWD}"/media/nuageinit/user_data atf_check -o inline:"env ASSUME_ALWAYS_YES=yes pkg update\n" /usr/libexec/nuageinit "${PWD}"/media/nuageinit postnet } config2_userdata_upgrade_packages_body() { mkdir -p media/nuageinit setup_test_adduser export NUAGE_RUN_TESTS=1 printf "{}" > media/nuageinit/meta_data.json cat > media/nuageinit/user_data << 'EOF' #cloud-config package_upgrade: true EOF chmod 755 "${PWD}"/media/nuageinit/user_data atf_check -o inline:"env ASSUME_ALWAYS_YES=yes pkg upgrade\n" /usr/libexec/nuageinit "${PWD}"/media/nuageinit postnet } config2_userdata_shebang_body() { mkdir -p media/nuageinit setup_test_adduser printf "{}" > media/nuageinit/meta_data.json cat > media/nuageinit/user_data < media/nuageinit/user_data < media/nuageinit/meta_data.json cat > media/nuageinit/user_data < media/nuageinit/meta_data.json cat > media/nuageinit/user_data < media/nuageinit/user_data <