git: cbd62452bff6 - stable/14 - nuageinit: Add doas support

From: Baptiste Daroussin <bapt_at_FreeBSD.org>
Date: Wed, 17 Sep 2025 19:42:28 UTC
The branch stable/14 has been updated by bapt:

URL: https://cgit.FreeBSD.org/src/commit/?id=cbd62452bff6bc8837c9cffeaa4c9f43b99995ce

commit cbd62452bff6bc8837c9cffeaa4c9f43b99995ce
Author:     Jesús Daniel Colmenares Oviedo <dtxdf@FreeBSD.org>
AuthorDate: 2025-09-11 16:54:24 +0000
Commit:     Baptiste Daroussin <bapt@FreeBSD.org>
CommitDate: 2025-09-17 19:42:03 +0000

    nuageinit: Add doas support
    
    * Set mode of etc directory to 0755.
    * Use user.localbase sysctl instead of /usr/local.
    * Add test case for doas.
    * Set ${LOCALBASE} instead of /usr/local in nuageinit(7) man page.
    
    Reviewed by:            bapt@
    Approved by:            bapt@
    Differential Revision:  https://reviews.freebsd.org/D52437
    
    (cherry picked from commit 9a829e865697e623a046800545be7781a117125e)
---
 libexec/nuageinit/nuage.lua          | 62 +++++++++++++++++++++++++++++++++++-
 libexec/nuageinit/nuageinit          |  3 ++
 libexec/nuageinit/nuageinit.7        |  9 +++++-
 libexec/nuageinit/tests/nuageinit.sh | 12 ++++++-
 4 files changed, 83 insertions(+), 3 deletions(-)

diff --git a/libexec/nuageinit/nuage.lua b/libexec/nuageinit/nuage.lua
index b042698f97e7..ef3cfd994fe1 100644
--- a/libexec/nuageinit/nuage.lua
+++ b/libexec/nuageinit/nuage.lua
@@ -8,6 +8,17 @@ 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..'=]', '')
@@ -277,11 +288,59 @@ local function addsshkey(homedir, key)
 	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 sudoers_dir = "/usr/local/etc/sudoers.d"
+	local localbase = getlocalbase()
+	local sudoers_dir = localbase .. "/etc/sudoers.d"
 	if root then
 		sudoers_dir= root .. sudoers_dir
 	end
@@ -585,6 +644,7 @@ local n = {
 	update_packages = update_packages,
 	upgrade_packages = upgrade_packages,
 	addsudo = addsudo,
+	adddoas = adddoas,
 	addfile = addfile
 }
 
diff --git a/libexec/nuageinit/nuageinit b/libexec/nuageinit/nuageinit
index 5541f6d0f164..29340a3d91ea 100755
--- a/libexec/nuageinit/nuageinit
+++ b/libexec/nuageinit/nuageinit
@@ -140,6 +140,9 @@ local function users(obj)
 			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
diff --git a/libexec/nuageinit/nuageinit.7 b/libexec/nuageinit/nuageinit.7
index e5da5cf342e1..b527c984970c 100644
--- a/libexec/nuageinit/nuageinit.7
+++ b/libexec/nuageinit/nuageinit.7
@@ -308,7 +308,14 @@ Ignored if an encrypted password is already provided.
 Boolean to determine if the user account should be locked.
 .It Ic sudo
 A string or an array of strings which should be appended to
-.Pa /usr/local/etc/sudoers.d/90-nuageinit-users
+.Pa ${LOCALBASE}/etc/sudoers.d/90-nuageinit-users
+.It Ic doas
+A string or an array of strings which should be appended to
+.Pa ${LOCALBASE}/etc/doas.conf
+.Pp
+Instead of hardcoding the username, you can use
+.Sy %u Ns ,
+which will be replaced by the current username.
 .El
 .Pp
 A special case exist: if the entry is a simple string with the value
diff --git a/libexec/nuageinit/tests/nuageinit.sh b/libexec/nuageinit/tests/nuageinit.sh
index 619df019cc4f..2b7c5226c97a 100644
--- a/libexec/nuageinit/tests/nuageinit.sh
+++ b/libexec/nuageinit/tests/nuageinit.sh
@@ -120,12 +120,16 @@ users:
     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
@@ -148,7 +152,13 @@ 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
-	atf_check -o inline:"foobar ALL=(ALL) NOPASSWD:ALL\nbla ALL=(ALL) NOPASSWD:/usr/sbin/pw\nbla ALL=(ALL) ALL\n" cat ${PWD}/usr/local/etc/sudoers.d/90-nuageinit-users
+	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()