1#!/usr/libexec/flua 2 3-- SPDX-License-Identifier: BSD-2-Clause-FreeBSD 4-- 5-- Copyright(c) 2022 Baptiste Daroussin <bapt@FreeBSD.org> 6 7local nuage = require("nuage") 8local yaml = require("yaml") 9 10if #arg ~= 2 then 11 nuage.err("Usage ".. arg[0] .." <cloud-init directory> [config-2|nocloud]") 12end 13local path = arg[1] 14local citype = arg[2] 15local ucl = require("ucl") 16 17local default_user = { 18 name = "freebsd", 19 homedir = "/home/freebsd", 20 groups = "wheel", 21 gecos = "FreeBSD User", 22 shell = "/bin/sh", 23 plain_text_passwd = "freebsd" 24} 25 26local root = os.getenv("NUAGE_FAKE_ROOTDIR") 27if not root then 28 root = "" 29end 30 31local function open_config(name) 32 nuage.mkdir_p(root .. "/etc/rc.conf.d") 33 local f,err = io.open(root .. "/etc/rc.conf.d/" .. name, "w") 34 if not f then 35 nuage.err("nuageinit: unable to open "..name.." config: " .. err) 36 end 37 return f 38end 39 40local function get_ifaces() 41 local parser = ucl.parser() 42 -- grab ifaces 43 local ns = io.popen('netstat -i --libxo json') 44 local netres = ns:read("*a") 45 ns:close() 46 local res,err = parser:parse_string(netres) 47 if not res then 48 nuage.warn("Error parsing netstat -i --libxo json outout: " .. err) 49 return nil 50 end 51 local ifaces = parser:get_object() 52 local myifaces = {} 53 for _,iface in pairs(ifaces["statistics"]["interface"]) do 54 if iface["network"]:match("<Link#%d>") then 55 local s = iface["address"] 56 myifaces[s:lower()] = iface["name"] 57 end 58 end 59 return myifaces 60end 61 62local function config2_network(p) 63 local parser = ucl.parser() 64 local f = io.open(p .. "/network_data.json") 65 if not f then 66 -- silently return no network configuration is provided 67 return 68 end 69 f:close() 70 local res,err = parser:parse_file(p .. "/network_data.json") 71 if not res then 72 nuage.warn("nuageinit: error parsing network_data.json: " .. err) 73 return 74 end 75 local obj = parser:get_object() 76 77 local ifaces = get_ifaces() 78 if not ifaces then 79 nuage.warn("nuageinit: no network interfaces found") 80 return 81 end 82 local mylinks = {} 83 for _,v in pairs(obj["links"]) do 84 local s = v["ethernet_mac_address"]:lower() 85 mylinks[v["id"]] = ifaces[s] 86 end 87 88 nuage.mkdir_p(root .. "/etc/rc.conf.d") 89 local network = open_config("network") 90 local routing = open_config("routing") 91 local ipv6 = {} 92 local ipv6_routes = {} 93 local ipv4 = {} 94 for _,v in pairs(obj["networks"]) do 95 local interface = mylinks[v["link"]] 96 if v["type"] == "ipv4_dhcp" then 97 network:write("ifconfig_"..interface.."=\"DHCP\"\n") 98 end 99 if v["type"] == "ipv4" then 100 network:write("ifconfig_"..interface.."=\"inet "..v["ip_address"].." netmask " .. v["netmask"] .. "\"\n") 101 if v["gateway"] then 102 routing:write("defaultrouter=\""..v["gateway"].."\"\n") 103 end 104 if v["routes"] then 105 for i,r in ipairs(v["routes"]) do 106 local rname = "cloudinit" .. i .. "_" .. interface 107 if v["gateway"] and v["gateway"] == r["gateway"] then goto next end 108 if r["network"] == "0.0.0.0" then 109 routing:write("defaultrouter=\""..r["gateway"].."\"\n") 110 goto next 111 end 112 routing:write("route_".. rname .. "=\"-net ".. r["network"] .. " ") 113 routing:write(r["gateway"] .. " " .. r["netmask"] .. "\"\n") 114 ipv4[#ipv4 + 1] = rname 115 ::next:: 116 end 117 end 118 end 119 if v["type"] == "ipv6" then 120 ipv6[#ipv6+1] = interface 121 ipv6_routes[#ipv6_routes+1] = interface 122 network:write("ifconfig_"..interface.."_ipv6=\"inet6 "..v["ip_address"].."\"\n") 123 if v["gateway"] then 124 routing:write("ipv6_defaultrouter=\""..v["gateway"].."\"\n") 125 routing:write("ipv6_route_"..interface.."=\""..v["gateway"]) 126 routing:write(" -prefixlen 128 -interface "..interface.."\"\n") 127 end 128 -- TODO compute the prefixlen for the routes 129 --if v["routes"] then 130 -- for i,r in ipairs(v["routes"]) do 131 -- local rname = "cloudinit" .. i .. "_" .. mylinks[v["link"]] 132 -- -- skip all the routes which are already covered by the default gateway, some provider 133 -- -- still list plenty of them. 134 -- if v["gateway"] == r["gateway"] then goto next end 135 -- routing:write("ipv6_route_" .. rname .. "\"\n") 136 -- ipv6_routes[#ipv6_routes+1] = rname 137 -- ::next:: 138 -- end 139 --end 140 end 141 end 142 if #ipv4 > 0 then 143 routing:write("static_routes=\"") 144 routing:write(table.concat(ipv4, " ") .. "\"\n") 145 end 146 if #ipv6 > 0 then 147 network:write("ipv6_network_interfaces=\"") 148 network:write(table.concat(ipv6, " ") .. "\"\n") 149 network:write("ipv6_default_interface=\""..ipv6[1].."\"\n") 150 end 151 if #ipv6_routes > 0 then 152 routing:write("ipv6_static_routes=\"") 153 routing:write(table.concat(ipv6, " ") .. "\"\n") 154 end 155 network:close() 156 routing:close() 157end 158 159if citype == "config-2" then 160 local parser = ucl.parser() 161 local res,err = parser:parse_file(path..'/meta_data.json') 162 163 if not res then 164 nuage.err("nuageinit: error parsing config-2: meta_data.json: " .. err) 165 end 166 local obj = parser:get_object() 167 nuage.sethostname(obj["hostname"]) 168 169 -- network 170 config2_network(path) 171elseif citype == "nocloud" then 172 local f,err = io.open(path.."/meta-data") 173 if err then 174 nuage.err("nuageinit: error parsing nocloud meta-data: ".. err) 175 end 176 local obj = yaml.eval(f:read("*a")) 177 f:close() 178 if not obj then 179 nuage.err("nuageinit: error parsing nocloud meta-data") 180 end 181 local hostname = obj['local-hostname'] 182 if not hostname then 183 hostname = obj['hostname'] 184 end 185 if hostname then 186 nuage.sethostname(hostname) 187 end 188else 189 nuage.err("Unknown cloud init type: ".. citype) 190end 191 192-- deal with user-data 193local f = io.open(path..'/user-data', "r") 194if not f then 195 os.exit(0) 196end 197local line = f:read('*l') 198f:close() 199if line == "#cloud-config" then 200 f = io.open(path.."/user-data") 201 local obj = yaml.eval(f:read("*a")) 202 f:close() 203 if not obj then 204 nuage.err("nuageinit: error parsing cloud-config file: user-data") 205 end 206 if obj.groups then 207 for n,g in pairs(obj.groups) do 208 if (type(g) == "string") then 209 local r = nuage.addgroup({name = g}) 210 if not r then 211 nuage.warn("nuageinit: failed to add group: ".. g) 212 end 213 elseif type(g) == "table" then 214 for k,v in pairs(g) do 215 nuage.addgroup({name = k, members = v}) 216 end 217 else 218 nuage.warn("nuageinit: invalid type : "..type(g).." for users entry number "..n); 219 end 220 end 221 end 222 if obj.users then 223 for n,u in pairs(obj.users) do 224 if type(u) == "string" then 225 if u == "default" then 226 nuage.adduser(default_user) 227 else 228 nuage.adduser({name = u}) 229 end 230 elseif type(u) == "table" then 231 -- ignore users without a username 232 if u.name == nil then 233 goto unext 234 end 235 local homedir = nuage.adduser(u) 236 if u.ssh_authorized_keys then 237 for _,v in ipairs(u.ssh_authorized_keys) do 238 nuage.addsshkey(homedir, v) 239 end 240 end 241 else 242 nuage.warn("nuageinit: invalid type : "..type(u).." for users entry number "..n); 243 end 244 ::unext:: 245 end 246 else 247 -- default user if none are defined 248 nuage.adduser(default_user) 249 end 250 if obj.ssh_authorized_keys then 251 local homedir = nuage.adduser(default_user) 252 for _,k in ipairs(obj.ssh_authorized_keys) do 253 nuage.addsshkey(homedir, k) 254 end 255 end 256 if obj.network then 257 local ifaces = get_ifaces() 258 nuage.mkdir_p(root .. "/etc/rc.conf.d") 259 local network = open_config("network") 260 local routing = open_config("routing") 261 local ipv6={} 262 for _,v in pairs(obj.network.ethernets) do 263 if not v.match then goto next end 264 if not v.match.macaddress then goto next end 265 if not ifaces[v.match.macaddress] then 266 nuage.warn("nuageinit: not interface matching: "..v.match.macaddress) 267 goto next 268 end 269 local interface = ifaces[v.match.macaddress] 270 if v.dhcp4 then 271 network:write("ifconfig_"..interface.."=\"DHCP\"\n") 272 elseif v.addresses then 273 for _,a in pairs(v.addresses) do 274 if a:match("^(%d+)%.(%d+)%.(%d+)%.(%d+)") then 275 network:write("ifconfig_"..interface.."=\"inet "..a.."\"\n") 276 else 277 network:write("ifconfig_"..interface.."_ipv6=\"inet6 "..a.."\"\n") 278 ipv6[#ipv6 +1] = interface 279 end 280 end 281 end 282 if v.gateway4 then 283 routing:write("defaultrouter=\""..v.gateway4.."\"\n") 284 end 285 if v.gateway6 then 286 routing:write("ipv6_defaultrouter=\""..v.gateway6.."\"\n") 287 routing:write("ipv6_route_"..interface.."=\""..v.gateway6) 288 routing:write(" -prefixlen 128 -interface "..interface.."\"\n") 289 end 290 ::next:: 291 end 292 if #ipv6 > 0 then 293 network:write("ipv6_network_interfaces=\"") 294 network:write(table.concat(ipv6, " ") .. "\"\n") 295 network:write("ipv6_default_interface=\""..ipv6[1].."\"\n") 296 end 297 network:close() 298 routing:close() 299 end 300else 301 local res,err = os.execute(path..'/user-data') 302 if not res then 303 nuage.err("nuageinit: error executing user-data script: ".. err) 304 end 305end 306