summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorA. Wilcox <AWilcox@Wilcox-Tech.com>2023-12-01 21:39:58 -0600
committerA. Wilcox <AWilcox@Wilcox-Tech.com>2023-12-01 21:47:21 -0600
commit36dc1b7a2ea44321a2c233fd7ea576ed516d4ccc (patch)
tree323d215d395f5e7b276f7fe267d9952e884cce69
parent8e5abd31f932c51a1f153e02d769eaebd6a2a543 (diff)
downloadhorizon-36dc1b7a2ea44321a2c233fd7ea576ed516d4ccc.tar.gz
horizon-36dc1b7a2ea44321a2c233fd7ea576ed516d4ccc.tar.bz2
horizon-36dc1b7a2ea44321a2c233fd7ea576ed516d4ccc.tar.xz
horizon-36dc1b7a2ea44321a2c233fd7ea576ed516d4ccc.zip
Add 'rootshell' key to determine root's shell
* Key added to code and documentation. * Tests added and pass locally on gwyn (ppc64) and fran (aarch64). * Qt UI automatically sets /bin/zsh as root's shell (ref: packages#206). * ISO image creator backend no longer has root shell hack.
-rw-r--r--devel/requirements/3b_runner.xml16
-rw-r--r--devel/script/2_keys.xml31
-rw-r--r--hscript/meta.cc36
-rw-r--r--hscript/meta.hh11
-rw-r--r--hscript/script.cc3
-rw-r--r--hscript/script_e.cc12
-rw-r--r--hscript/script_i.hh13
-rw-r--r--image/backends/iso.cc5
-rw-r--r--tests/fixtures/0265-rootshell-basic.installfile6
-rw-r--r--tests/fixtures/0266-rootshell-invalid.installfile6
-rw-r--r--tests/fixtures/0267-rootshell-duplicate.installfile7
-rw-r--r--tests/spec/validator_spec.rb18
-rw-r--r--ui/qt5/horizonwizard.cc2
13 files changed, 159 insertions, 7 deletions
diff --git a/devel/requirements/3b_runner.xml b/devel/requirements/3b_runner.xml
index b5e9848..9ab06bd 100644
--- a/devel/requirements/3b_runner.xml
+++ b/devel/requirements/3b_runner.xml
@@ -378,6 +378,14 @@
<title>Runner.Validate.usergroups.Group</title>
<para>The system shall verify that each group specified is a valid system-defined group name that is present in the base system image <filename>/etc/group</filename> file.</para>
</formalpara>
+ <formalpara id="Runner.Validate.rootshell">
+ <title>Runner.Validate.rootshell</title>
+ <para>The system shall verify that the HorizonScript contains zero or one <literal>rootshell</literal> key.</para>
+ </formalpara>
+ <formalpara id="Runner.Validate.rootshell.Format">
+ <title>Runner.Validate.rootshell.Format</title>
+ <para>The system shall verify that the value of the <literal>rootshell</literal> key, if present, begins with a <literal>/</literal>.</para>
+ </formalpara>
<formalpara id="Runner.Validate.diskid">
<title>Runner.Validate.diskid</title>
<para>The system shall verify any <literal>diskid</literal> keys contained in the HorizonScript.</para>
@@ -828,6 +836,14 @@
<title>Runner.Execute.svcenable.AddlRunlevels</title>
<para>If a runlevel not defined by the system service manager is specified in a <literal>svcenable</literal> key, it shall be interpreted to be a stacked runlevel atop the default runlevel and shall be configured as such in the target namespace.</para>
</formalpara>
+ <formalpara id="Runner.Execute.rootshell">
+ <title>Runner.Execute.rootshell</title>
+ <para>If a <literal>rootshell</literal> key is specified in the HorizonScript, the system shall set the shell of the <literal>root</literal> user to the value specified.</para>
+ </formalpara>
+ <formalpara id="Runner.Execute.rootshell.Exists">
+ <title>Runner.Execute.rootshell.Exists</title>
+ <para>If the <literal>rootshell</literal> key specifies a file that does not exist or is not executable, the system shall set the shell of the <literal>root</literal> user to the value<literal>/bin/sh</literal>.</para>
+ </formalpara>
<formalpara id="Runner.Execute.bootloader">
<title>Runner.Execute.bootloader</title>
<para>If a <literal>bootloader</literal> key is specified in the HorizonScript, the system shall perform the requested bootloader configuration.</para>
diff --git a/devel/script/2_keys.xml b/devel/script/2_keys.xml
index bd8469c..854f0d5 100644
--- a/devel/script/2_keys.xml
+++ b/devel/script/2_keys.xml
@@ -695,6 +695,37 @@ signingkey https://packages/builder@ourcompany.net.pub
</para>
</formalpara>
</section>
+ <section id="rootshell">
+ <title><literal>rootshell</literal></title>
+ <formalpara id="rootshell.name">
+ <title>Name</title>
+ <para><literal>rootshell</literal></para>
+ </formalpara>
+ <formalpara id="rootshell.purpose">
+ <title>Purpose</title>
+ <para>The <literal>rootshell</literal> key specifies the shell to use when logging in to the target computer as the <literal>root</literal> user.</para>
+ </formalpara>
+ <formalpara id="rootshell.format">
+ <title>Format</title>
+ <para>The <literal>rootshell</literal> key is a single string value containing the full path inside the target to the executable to use as the shell.</para>
+ </formalpara>
+ <formalpara id="rootshell.default">
+ <title>Default</title>
+ <para>If no <literal>rootshell</literal> key is specified, the target computer will use <literal>/bin/sh</literal> as the shell when logging in as the <literal>root</literal> user.</para>
+ </formalpara>
+ <formalpara id="rootshell.example">
+ <title>Example</title>
+ <para>
+ <example>
+ <title>The <literal>rootshell</literal> Key</title>
+ <programlisting>
+ rootshell /bin/zsh
+ </programlisting>
+ <para>This will configure the target computer to use <literal>/bin/zsh</literal> as the shell when logging in as the <literal>root</literal> user.</para>
+ </example>
+ </para>
+ </formalpara>
+ </section>
<section id="bootloader">
<title><literal>bootloader</literal></title>
<formalpara id="bootloader.name">
diff --git a/hscript/meta.cc b/hscript/meta.cc
index ee34372..8079b1a 100644
--- a/hscript/meta.cc
+++ b/hscript/meta.cc
@@ -708,6 +708,42 @@ bool Version::execute() const {
/* LCOV_EXCL_STOP */
+Key* RootShell::parseFromData(const std::string &data,
+ const ScriptLocation &pos, int *errors, int *,
+ const Script *script) {
+ if(data.at(0) != '/') {
+ if(errors) *errors += 1;
+ output_error(pos, "rootshell: invalid shell specified", data);
+ return nullptr;
+ }
+
+ return new RootShell(script, pos, data);
+}
+
+bool RootShell::execute() const {
+ auto target = script->targetDirectory();
+
+ if(script->options().test(Simulate)) {
+ std::cout << "[ -x " << target << _value << "] && "
+ << "sed -i 's#/root:/bin/sh$#/root:" << _value << "#' "
+ << target << "/etc/passwd" << std::endl;
+ }
+#ifdef HAS_INSTALL_ENV
+ else {
+ if(fs::exists(target + _value)) {
+ run_command("sed", {"-i", "s#/root:/bin/sh$#/root:" + _value + "#",
+ script->targetDirectory() + "/etc/passwd"});
+ } else {
+ output_warning(pos, "shell " + _value +
+ " not found; leaving root shell as /bin/sh");
+ }
+ }
+#endif /* HAS_INSTALL_ENV */
+
+ return true;
+}
+
+
const std::string my_arch(const Horizon::Script *script) {
const Key *arch_key = script->getOneValue("arch");
if(arch_key != nullptr) {
diff --git a/hscript/meta.hh b/hscript/meta.hh
index 32f8eb7..3d1382e 100644
--- a/hscript/meta.hh
+++ b/hscript/meta.hh
@@ -146,6 +146,17 @@ public:
bool execute() const override;
};
+class RootShell : public StringKey {
+private:
+ RootShell(const Script *_s, const ScriptLocation &_p,
+ const std::string &_v) :
+ StringKey(_s, _p, _v) {}
+public:
+ static Key *parseFromData(const std::string &, const ScriptLocation &,
+ int *, int *, const Script *);
+ bool execute() const override;
+};
+
class Bootloader : public Key {
private:
const std::string _device;
diff --git a/hscript/script.cc b/hscript/script.cc
index d86216d..0126a99 100644
--- a/hscript/script.cc
+++ b/hscript/script.cc
@@ -52,6 +52,7 @@ const std::map<std::string, key_parse_fn> valid_keys = {
{"signingkey", &SigningKey::parseFromData},
{"svcenable", &SvcEnable::parseFromData},
{"version", &Version::parseFromData},
+ {"rootshell", &RootShell::parseFromData},
{"bootloader", &Bootloader::parseFromData},
{"netconfigtype", &NetConfigType::parseFromData},
@@ -129,6 +130,8 @@ bool Script::ScriptPrivate::store_key(const std::string &key_name, Key *obj,
return store_svcenable(obj, pos, errors, warnings, opts);
} else if(key_name == "version") {
return store_version(obj, pos, errors, warnings, opts);
+ } else if(key_name == "rootshell") {
+ return store_rootshell(obj, pos, errors, warnings, opts);
} else if(key_name == "bootloader") {
return store_bootloader(obj, pos, errors, warnings, opts);
} else if(key_name == "username") {
diff --git a/hscript/script_e.cc b/hscript/script_e.cc
index 50f92e1..738bfed 100644
--- a/hscript/script_e.cc
+++ b/hscript/script_e.cc
@@ -647,8 +647,12 @@ bool Script::execute() const {
if(internal->keymap) {
EXECUTE_OR_FAIL("keymap", internal->keymap)
- fs::create_symlink("/etc/init.d/keymaps",
- targ_etc + "/runlevels/default/keymaps", ec);
+#ifdef HAS_INSTALL_ENV
+ if(!opts.test(Simulate)) {
+ fs::create_symlink("/etc/init.d/keymaps",
+ targ_etc + "/runlevels/default/keymaps", ec);
+ }
+#endif /* HAS_INSTALL_ENV */
}
for(auto &acct : internal->accounts) {
@@ -697,6 +701,10 @@ bool Script::execute() const {
}
#endif
+ if(internal->root_shell) {
+ EXECUTE_OR_FAIL("rootshell", internal->root_shell)
+ }
+
if(internal->boot) {
EXECUTE_OR_FAIL("bootloader", internal->boot)
}
diff --git a/hscript/script_i.hh b/hscript/script_i.hh
index 12ec133..641c749 100644
--- a/hscript/script_i.hh
+++ b/hscript/script_i.hh
@@ -65,6 +65,8 @@ struct Script::ScriptPrivate {
std::unique_ptr<Version> version;
/*! The desired bootloader configuration. */
std::unique_ptr<Bootloader> boot;
+ /*! The desired root shell. */
+ std::unique_ptr<RootShell> root_shell;
/*! Network addressing configuration */
std::vector< std::unique_ptr<NetAddress> > addresses;
@@ -297,6 +299,17 @@ struct Script::ScriptPrivate {
return true;
}
+ bool store_rootshell(Key *obj, const ScriptLocation &pos, int *errors,
+ int *, const ScriptOptions &) {
+ if(root_shell) {
+ DUPLICATE_ERROR(root_shell, "rootshell", root_shell->value())
+ return false;
+ }
+ std::unique_ptr<RootShell> r(dynamic_cast<RootShell *>(obj));
+ root_shell = std::move(r);
+ return true;
+ }
+
bool store_bootloader(Key *obj, const ScriptLocation &pos, int *errors,
int *, const ScriptOptions &) {
if(boot) {
diff --git a/image/backends/iso.cc b/image/backends/iso.cc
index 8ef4503..52f0367 100644
--- a/image/backends/iso.cc
+++ b/image/backends/iso.cc
@@ -303,11 +303,6 @@ public:
output_info("CD backend", "creating live environment /etc/fstab");
if(!write_fstab_to(target)) return FS_ERROR;
- /* REQ: ISO.13 */
- output_info("CD backend", "setting root shell");
- run_command("sed", {"-i", "s#/root:/bin/sh$#/root:/bin/zsh#",
- target + "/etc/passwd"});
-
/* REQ: ISO.15 */
output_info("CD backend", "configuring login services");
run_command("sed", {"-i", "s/pam_unix.so$/pam_unix.so nullok_secure/",
diff --git a/tests/fixtures/0265-rootshell-basic.installfile b/tests/fixtures/0265-rootshell-basic.installfile
new file mode 100644
index 0000000..e23477b
--- /dev/null
+++ b/tests/fixtures/0265-rootshell-basic.installfile
@@ -0,0 +1,6 @@
+network false
+hostname 123lonelycorgi.street
+pkginstall adelie-base
+rootpw $6$gumtLGmHwOVIRpQR$2M9PUO24hy5mofzWWf9a.YLbzOgOlUby1g0hDj.wG67E2wrrvys59fq02PPdxBdbgkLZFtjfEx6MHZwMBamwu/
+mount /dev/sda1 /
+rootshell /bin/zsh
diff --git a/tests/fixtures/0266-rootshell-invalid.installfile b/tests/fixtures/0266-rootshell-invalid.installfile
new file mode 100644
index 0000000..012fc67
--- /dev/null
+++ b/tests/fixtures/0266-rootshell-invalid.installfile
@@ -0,0 +1,6 @@
+network false
+hostname 123lonelycorgi.street
+pkginstall adelie-base
+rootpw $6$gumtLGmHwOVIRpQR$2M9PUO24hy5mofzWWf9a.YLbzOgOlUby1g0hDj.wG67E2wrrvys59fq02PPdxBdbgkLZFtjfEx6MHZwMBamwu/
+mount /dev/sda1 /
+rootshell lol hi!
diff --git a/tests/fixtures/0267-rootshell-duplicate.installfile b/tests/fixtures/0267-rootshell-duplicate.installfile
new file mode 100644
index 0000000..3cd25c7
--- /dev/null
+++ b/tests/fixtures/0267-rootshell-duplicate.installfile
@@ -0,0 +1,7 @@
+network false
+hostname 123lonelycorgi.street
+pkginstall adelie-base
+rootpw $6$gumtLGmHwOVIRpQR$2M9PUO24hy5mofzWWf9a.YLbzOgOlUby1g0hDj.wG67E2wrrvys59fq02PPdxBdbgkLZFtjfEx6MHZwMBamwu/
+mount /dev/sda1 /
+rootshell /bin/zsh
+rootshell /bin/dash
diff --git a/tests/spec/validator_spec.rb b/tests/spec/validator_spec.rb
index 3a491a0..8f33330 100644
--- a/tests/spec/validator_spec.rb
+++ b/tests/spec/validator_spec.rb
@@ -732,6 +732,19 @@ RSpec.describe 'HorizonScript validation', :type => :aruba do
expect(last_command_started).to have_output(/error: .*version.*/)
end
end
+ context "for 'rootshell' key" do
+ it "succeeds with zsh" do
+ use_fixture '0265-rootshell-basic.installfile'
+ run_validate
+ expect(last_command_started).to have_output(PARSER_SUCCESS)
+ expect(last_command_started).to have_output(VALIDATOR_SUCCESS)
+ end
+ it "fails with an invalid shell" do
+ use_fixture '0266-rootshell-invalid.installfile'
+ run_validate
+ expect(last_command_started).to have_output(/error: .*rootshell.*/)
+ end
+ end
context "for 'pkginstall' key" do
it "warns when a package is listed twice in the same line" do
use_fixture '0216-pkginstall-dup-single.installfile'
@@ -1284,6 +1297,11 @@ RSpec.describe 'HorizonScript validation', :type => :aruba do
run_validate
expect(last_command_started).to have_output(/error: .*duplicate.*timezone/)
end
+ it "fails with a duplicate 'rootshell' key" do
+ use_fixture '0267-rootshell-duplicate.installfile'
+ run_validate
+ expect(last_command_started).to have_output(/error: .*duplicate.*rootshell/)
+ end
end
context "user account keys:" do
context "'username'" do
diff --git a/ui/qt5/horizonwizard.cc b/ui/qt5/horizonwizard.cc
index 53331ff..36746ea 100644
--- a/ui/qt5/horizonwizard.cc
+++ b/ui/qt5/horizonwizard.cc
@@ -702,6 +702,8 @@ QString HorizonWizard::toHScript() {
/* handwavy-future: When we have language support, set it here */
lines << "language en_US.UTF-8";
+ lines << "rootshell /bin/zsh";
+
auto iterator = valid_keymaps.begin();
std::advance(iterator, field("keymap").toInt());
lines << ("keymap " + QString::fromStdString(*iterator));