git: 41fe9d53005e - main - nuageinit: implement ssh_keys support

From: Baptiste Daroussin <bapt_at_FreeBSD.org>
Date: Wed, 27 Nov 2024 08:53:15 UTC
The branch main has been updated by bapt:

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

commit 41fe9d53005ef213ff16d9b095c0a88e3f2fb296
Author:     Baptiste Daroussin <bapt@FreeBSD.org>
AuthorDate: 2024-11-27 08:52:29 +0000
Commit:     Baptiste Daroussin <bapt@FreeBSD.org>
CommitDate: 2024-11-27 08:53:04 +0000

    nuageinit: implement ssh_keys support
    
    MFC After:      1 week
    Sponsored by:   OVHCloud
---
 libexec/nuageinit/nuageinit          | 42 +++++++++++++++++++++++----
 libexec/nuageinit/tests/nuageinit.sh | 56 ++++++++++++++++++++++++++++++++++++
 2 files changed, 93 insertions(+), 5 deletions(-)

diff --git a/libexec/nuageinit/nuageinit b/libexec/nuageinit/nuageinit
index c8f74d13b7fd..5249c09eb5f1 100755
--- a/libexec/nuageinit/nuageinit
+++ b/libexec/nuageinit/nuageinit
@@ -7,6 +7,7 @@
 local nuage = require("nuage")
 local ucl = require("ucl")
 local yaml = require("yaml")
+local sys_stat = require("posix.sys.stat")
 
 if #arg ~= 2 then
 	nuage.err("Usage: " .. arg[0] .. " <cloud-init-directory> (<config-2> | <nocloud>)", false)
@@ -28,13 +29,22 @@ 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")
+local function openat(dir, name)
+	local path_dir = root .. dir
+	local path_name = path_dir .. "/" .. name
+	nuage.mkdir_p(path_dir)
+	local f, err = io.open(path_name, "w")
 	if not f then
-		nuage.err("unable to open " .. name .. " config: " .. err)
+		nuage.err("unable to open " .. path_name .. ": " .. err)
 	end
-	return f
+	return f, path_name
+end
+local function open_ssh_key(name)
+	return openat("/etc/ssh", name)
+end
+
+local function open_config(name)
+	return openat("/etc/rc.conf.d", name)
 end
 
 local function get_ifaces()
@@ -268,6 +278,28 @@ if line == "#cloud-config" then
 	-- default user if none are defined
 		nuage.adduser(default_user)
 	end
+	if obj.ssh_keys and type(obj.ssh_keys) == "table" then
+		for key, val in pairs(obj.ssh_keys) do
+			for keyname, keytype in key:gmatch("(%w+)_(%w+)") do
+				local sshkn = nil
+				if keytype == "public" then
+					sshkn =  "ssh_host_" .. keyname .. "_key.pub"
+				elseif keytype == "private" then
+					sshkn = "ssh_host_" .. keyname .. "_key"
+				end
+				if sshkn then
+					local sshkey, path = open_ssh_key(sshkn)
+					if sshkey then
+						sshkey:write(val .. "\n")
+						sshkey:close()
+					end
+					if keytype == "private" then
+						sys_stat.chmod(path, 384)
+					end
+				end
+			end
+		end
+	end
 	if obj.ssh_authorized_keys then
 		local homedir = nuage.adduser(default_user)
 		for _, k in ipairs(obj.ssh_authorized_keys) do
diff --git a/libexec/nuageinit/tests/nuageinit.sh b/libexec/nuageinit/tests/nuageinit.sh
index f7f39ce32ad8..7e1310c4f0f9 100644
--- a/libexec/nuageinit/tests/nuageinit.sh
+++ b/libexec/nuageinit/tests/nuageinit.sh
@@ -18,6 +18,7 @@ atf_test_case config2_pubkeys_user_data
 atf_test_case config2_pubkeys_meta_data
 atf_test_case config2_network
 atf_test_case config2_network_static_v4
+atf_test_case config2_ssh_keys
 
 args_body()
 {
@@ -404,6 +405,60 @@ EOF
 	atf_check -o file:routing cat "${PWD}"/etc/rc.conf.d/routing
 }
 
+config2_ssh_keys_head()
+{
+	atf_set "require.user" root
+}
+config2_ssh_keys_body()
+{
+	here=$(pwd)
+	export NUAGE_FAKE_ROOTDIR=$(pwd)
+	mkdir -p media/nuageinit
+	touch media/nuageinit/meta_data.json
+	cat > media/nuageinit/user-data << EOF
+#cloud-config
+ssh_keys:
+  rsa_private: |
+    -----BEGIN RSA PRIVATE KEY-----
+    MIIBxwIBAAJhAKD0YSHy73nUgysO13XsJmd4fHiFyQ+00R7VVu2iV9Qco
+    ...
+    -----END RSA PRIVATE KEY-----
+  rsa_public: ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAGEAoPRhIfLvedSDKw7Xd ...
+  ed25519_private: |
+    -----BEGIN OPENSSH PRIVATE KEY-----
+    blabla
+    ...
+    -----END OPENSSH PRIVATE KEY-----
+  ed25519_public: ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIK+MH4E8KO32N5CXRvXVqvyZVl0+6ue4DobdhU0FqFd+
+EOF
+	mkdir -p etc/ssh
+	cat > etc/master.passwd << EOF
+root:*:0:0::0:0:Charlie &:/root:/bin/csh
+sys:*:1:0::0:0:Sys:/home/sys:/bin/csh
+EOF
+	pwd_mkdb -d etc ${here}/etc/master.passwd
+	cat > etc/group << EOF
+wheel:*:0:root
+users:*:1:
+EOF
+	atf_check /usr/libexec/nuageinit "${PWD}"/media/nuageinit config-2
+	_expected="-----BEGIN RSA PRIVATE KEY-----
+MIIBxwIBAAJhAKD0YSHy73nUgysO13XsJmd4fHiFyQ+00R7VVu2iV9Qco
+...
+-----END RSA PRIVATE KEY-----
+"
+	atf_check -o inline:"${_expected}" cat ${PWD}/etc/ssh/ssh_host_rsa_key
+	_expected="ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAGEAoPRhIfLvedSDKw7Xd ...\n"
+	atf_check -o inline:"${_expected}" cat ${PWD}/etc/ssh/ssh_host_rsa_key.pub
+	_expected="-----BEGIN OPENSSH PRIVATE KEY-----
+blabla
+...
+-----END OPENSSH PRIVATE KEY-----\n"
+	atf_check -o inline:"${_expected}" cat ${PWD}/etc/ssh/ssh_host_ed25519_key
+	_expected="ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIK+MH4E8KO32N5CXRvXVqvyZVl0+6ue4DobdhU0FqFd+\n"
+	atf_check -o inline:"${_expected}" cat ${PWD}/etc/ssh/ssh_host_ed25519_key.pub
+}
+
 atf_init_test_cases()
 {
 	atf_add_test_case args
@@ -418,4 +473,5 @@ atf_init_test_cases()
 	atf_add_test_case config2_pubkeys_meta_data
 	atf_add_test_case config2_network
 	atf_add_test_case config2_network_static_v4
+	atf_add_test_case config2_ssh_keys
 }