git: 6c912470030b - stable/14 - nuageinit: implement chpasswd
- Go to: [ bottom of page ] [ top of archives ] [ this month ]
Date: Wed, 07 May 2025 12:33:32 UTC
The branch stable/14 has been updated by bapt:
URL: https://cgit.FreeBSD.org/src/commit/?id=6c912470030ba958f2e41a00b44f6430919b1389
commit 6c912470030ba958f2e41a00b44f6430919b1389
Author: Baptiste Daroussin <bapt@FreeBSD.org>
AuthorDate: 2025-04-25 15:16:22 +0000
Commit: Baptiste Daroussin <bapt@FreeBSD.org>
CommitDate: 2025-05-07 12:33:22 +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
(cherry picked from commit c201a1198ad70e7d096ee32c364d539eed2dfec4)
---
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
}