Skip site navigation (1)Skip section navigation (2)
Date:      Wed, 30 Apr 2025 07:32:25 GMT
From:      Baptiste Daroussin <bapt@FreeBSD.org>
To:        src-committers@FreeBSD.org, dev-commits-src-all@FreeBSD.org, dev-commits-src-main@FreeBSD.org
Subject:   git: c201a1198ad7 - main - nuageinit: implement chpasswd
Message-ID:  <202504300732.53U7WPP9087838@gitrepo.freebsd.org>

next in thread | raw e-mail | index | archive | help
The branch main has been updated by bapt:

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

commit c201a1198ad70e7d096ee32c364d539eed2dfec4
Author:     Baptiste Daroussin <bapt@FreeBSD.org>
AuthorDate: 2025-04-25 15:16:22 +0000
Commit:     Baptiste Daroussin <bapt@FreeBSD.org>
CommitDate: 2025-04-30 07:32:06 +0000

    nuageinit: implement chpasswd
    
    Add support for chpasswd, with all possible syntaxes, including
    deprecated one: chpasswd.list as a list or as a multiline string
    as some providers are still only providing this deprecated form
    
    Sponsored by:   OVHCloud
    MFC After:      1 week
    Reviewed by:    kevans, jlduran
    Differential Revision:  https://reviews.freebsd.org/D50021
---
 libexec/nuageinit/nuage.lua          | 105 ++++++++++++++++++++-
 libexec/nuageinit/nuageinit          |   6 +-
 libexec/nuageinit/tests/nuageinit.sh | 175 +++++++++++++++++++++++++++++++++++
 3 files changed, 283 insertions(+), 3 deletions(-)

diff --git a/libexec/nuageinit/nuage.lua b/libexec/nuageinit/nuage.lua
index e58069164130..15af5afbd9f0 100644
--- a/libexec/nuageinit/nuage.lua
+++ b/libexec/nuageinit/nuage.lua
@@ -1,7 +1,7 @@
 ---
 -- SPDX-License-Identifier: BSD-2-Clause
 --
--- Copyright(c) 2022 Baptiste Daroussin <bapt@FreeBSD.org>
+-- Copyright(c) 2022-2025 Baptiste Daroussin <bapt@FreeBSD.org>
 
 local unistd = require("posix.unistd")
 local sys_stat = require("posix.sys.stat")
@@ -261,6 +261,106 @@ local function update_sshd_config(key, value)
 	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 n = {
 	warn = warnmsg,
 	err = errmsg,
@@ -270,7 +370,8 @@ local n = {
 	adduser = adduser,
 	addgroup = addgroup,
 	addsshkey = addsshkey,
-	update_sshd_config = update_sshd_config
+	update_sshd_config = update_sshd_config,
+	chpasswd = chpasswd
 }
 
 return n
diff --git a/libexec/nuageinit/nuageinit b/libexec/nuageinit/nuageinit
index 341330e68128..74a75c88098a 100755
--- a/libexec/nuageinit/nuageinit
+++ b/libexec/nuageinit/nuageinit
@@ -2,7 +2,7 @@
 ---
 -- SPDX-License-Identifier: BSD-2-Clause-FreeBSD
 --
--- Copyright(c) 2022 Baptiste Daroussin <bapt@FreeBSD.org>
+-- Copyright(c) 2022-2025 Baptiste Daroussin <bapt@FreeBSD.org>
 
 local nuage = require("nuage")
 local ucl = require("ucl")
@@ -359,6 +359,10 @@ if line == "#cloud-config" then
 		end
 		nuage.update_sshd_config("PasswordAuthentication", value)
 	end
+	if obj.chpasswd ~= nil then
+		nuage.chpasswd(obj.chpasswd)
+	end
+
 else
 	local res, err = os.execute(path .. "/" .. ud)
 	if not res then
diff --git a/libexec/nuageinit/tests/nuageinit.sh b/libexec/nuageinit/tests/nuageinit.sh
index d3b1d5e6df2e..1b67468971a6 100644
--- a/libexec/nuageinit/tests/nuageinit.sh
+++ b/libexec/nuageinit/tests/nuageinit.sh
@@ -20,6 +20,9 @@ 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
 
 args_body()
 {
@@ -512,6 +515,175 @@ EOF
 	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 -e inline:"nuageinit: Invalid entry for chpasswd.users: missing 'name'\n" /usr/libexec/nuageinit "${PWD}"/media/nuageinit nocloud
+	# 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 nocloud
+	# 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 nocloud
+	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 nocloud
+	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 nocloud
+	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 -e inline:"nuageinit: chpasswd.list is deprecated consider using chpasswd.users\n" /usr/libexec/nuageinit "${PWD}"/media/nuageinit nocloud
+	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 nocloud
+	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 -e inline:"nuageinit: chpasswd.list is deprecated consider using chpasswd.users\n" /usr/libexec/nuageinit "${PWD}"/media/nuageinit nocloud
+	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 nocloud
+	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
+}
+
 atf_init_test_cases()
 {
 	atf_add_test_case args
@@ -528,4 +700,7 @@ atf_init_test_cases()
 	atf_add_test_case config2_network_static_v4
 	atf_add_test_case config2_ssh_keys
 	atf_add_test_case nocloud_userdata_cloudconfig_ssh_pwauth
+	atf_add_test_case nocloud_userdata_cloudconfig_chpasswd
+	atf_add_test_case nocloud_userdata_cloudconfig_chpasswd_list_string
+	atf_add_test_case nocloud_userdata_cloudconfig_chpasswd_list_list
 }



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?202504300732.53U7WPP9087838>