mirror of
https://github.com/opnsense/src.git
synced 2026-06-05 23:04:36 -04:00
this is a very early script to support cloudinit, it does not intend to be a full featured cloudinit client, but will support a good enough subset to be viable in most case. It support nocloud and openstack config-2 config drive mode (iso9660 or msdosfs) The following features are currently supported: - adding users (including a default user named 'freebsd' with password 'freebsd' - adding groups - adding ssh keys - static ipv4, static ipv6, dynamic ipv4 With this one is able to use the 'bring your own image feature" out of box. It is expected that the script grows the support of other clouds supporting cloud-init, contributions are welcomed. It is designed to be only run once via the firstboot mecanism. Sponsored by: OVHCloud MFC After: 3 weeks Differential Revision: https://reviews.freebsd.org/D44141
312 lines
8.6 KiB
Text
Executable file
312 lines
8.6 KiB
Text
Executable file
#!/usr/libexec/flua
|
|
|
|
-- SPDX-License-Identifier: BSD-2-Clause-FreeBSD
|
|
--
|
|
-- Copyright(c) 2022 Baptiste Daroussin <bapt@FreeBSD.org>
|
|
|
|
local nuage = require("nuage")
|
|
local yaml = require("yaml")
|
|
|
|
if #arg ~= 2 then
|
|
nuage.err("Usage ".. arg[0] .." <cloud-init directory> [config-2|nocloud]")
|
|
end
|
|
local path = arg[1]
|
|
local citype = arg[2]
|
|
local ucl = require("ucl")
|
|
|
|
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 open_config(name)
|
|
nuage.mkdir_p(root .. "/etc/rc.conf.d")
|
|
local f,err = io.open(root .. "/etc/rc.conf.d/" .. name, "w")
|
|
if not f then
|
|
nuage.err("nuageinit: unable to open "..name.." config: " .. err)
|
|
end
|
|
return f
|
|
end
|
|
|
|
local function get_ifaces()
|
|
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 outout: " .. err)
|
|
return nil
|
|
end
|
|
local ifaces = parser:get_object()
|
|
local myifaces = {}
|
|
for _,iface in pairs(ifaces["statistics"]["interface"]) do
|
|
if iface["network"]:match("<Link#%d>") then
|
|
local s = iface["address"]
|
|
myifaces[s:lower()] = iface["name"]
|
|
end
|
|
end
|
|
return myifaces
|
|
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("nuageinit: error parsing network_data.json: " .. err)
|
|
return
|
|
end
|
|
local obj = parser:get_object()
|
|
|
|
local ifaces = get_ifaces()
|
|
if not ifaces then
|
|
nuage.warn("nuageinit: 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
|
|
|
|
nuage.mkdir_p(root .. "/etc/rc.conf.d")
|
|
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
|
|
|
|
if citype == "config-2" then
|
|
local parser = ucl.parser()
|
|
local res,err = parser:parse_file(path..'/meta_data.json')
|
|
|
|
if not res then
|
|
nuage.err("nuageinit: error parsing config-2: meta_data.json: " .. err)
|
|
end
|
|
local obj = parser:get_object()
|
|
local sshkeys = obj["public_keys"]
|
|
if sshkeys then
|
|
local homedir = nuage.adduser(default_user)
|
|
for _,v in pairs(sshkeys) do
|
|
nuage.addsshkey(root .. homedir, v)
|
|
end
|
|
end
|
|
nuage.sethostname(obj["hostname"])
|
|
|
|
-- network
|
|
config2_network(path)
|
|
elseif citype == "nocloud" then
|
|
local f,err = io.open(path.."/meta-data")
|
|
if err then
|
|
nuage.err("nuageinit: error parsing nocloud meta-data: ".. err)
|
|
end
|
|
local obj = yaml.eval(f:read("*a"))
|
|
f:close()
|
|
if not obj then
|
|
nuage.err("nuageinit: 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
|
|
else
|
|
nuage.err("Unknown cloud init type: ".. citype)
|
|
end
|
|
|
|
-- deal with user-data
|
|
local f = io.open(path..'/user-data', "r")
|
|
if not f then
|
|
os.exit(0)
|
|
end
|
|
local line = f:read('*l')
|
|
f:close()
|
|
if line == "#cloud-config" then
|
|
f = io.open(path.."/user-data")
|
|
local obj = yaml.eval(f:read("*a"))
|
|
f:close()
|
|
if not obj then
|
|
nuage.err("nuageinit: error parsing cloud-config file: user-data")
|
|
end
|
|
if obj.groups then
|
|
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("nuageinit: 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("nuageinit: invalid type : "..type(g).." for users entry number "..n);
|
|
end
|
|
end
|
|
end
|
|
if obj.users then
|
|
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
|
|
else
|
|
nuage.warn("nuageinit: invalid type : "..type(u).." for users entry number "..n);
|
|
end
|
|
::unext::
|
|
end
|
|
else
|
|
-- default user if none are defined
|
|
nuage.adduser(default_user)
|
|
end
|
|
if obj.ssh_authorized_keys then
|
|
local homedir = nuage.adduser(default_user)
|
|
for _,k in ipairs(obj.ssh_authorized_keys) do
|
|
nuage.addsshkey(homedir, k)
|
|
end
|
|
end
|
|
if obj.network then
|
|
local ifaces = get_ifaces()
|
|
nuage.mkdir_p(root .. "/etc/rc.conf.d")
|
|
local network = open_config("network")
|
|
local routing = open_config("routing")
|
|
local ipv6={}
|
|
for _,v in pairs(obj.network.ethernets) do
|
|
if not v.match then goto next end
|
|
if not v.match.macaddress then goto next end
|
|
if not ifaces[v.match.macaddress] then
|
|
nuage.warn("nuageinit: not interface matching: "..v.match.macaddress)
|
|
goto next
|
|
end
|
|
local interface = ifaces[v.match.macaddress]
|
|
if v.dhcp4 then
|
|
network:write("ifconfig_"..interface.."=\"DHCP\"\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.."\"\n")
|
|
else
|
|
network:write("ifconfig_"..interface.."_ipv6=\"inet6 "..a.."\"\n")
|
|
ipv6[#ipv6 +1] = interface
|
|
end
|
|
end
|
|
end
|
|
if v.gateway4 then
|
|
routing:write("defaultrouter=\""..v.gateway4.."\"\n")
|
|
end
|
|
if v.gateway6 then
|
|
routing:write("ipv6_defaultrouter=\""..v.gateway6.."\"\n")
|
|
routing:write("ipv6_route_"..interface.."=\""..v.gateway6)
|
|
routing:write(" -prefixlen 128 -interface "..interface.."\"\n")
|
|
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
|
|
else
|
|
local res,err = os.execute(path..'/user-data')
|
|
if not res then
|
|
nuage.err("nuageinit: error executing user-data script: ".. err)
|
|
end
|
|
end
|