From 5eaf320d582b6038faaca432638a022b499756f7 Mon Sep 17 00:00:00 2001 From: az Date: Tue, 27 Apr 2021 18:14:58 +0200 Subject: [PATCH] add sudo to cmd and sh --- src/main/kotlin/io/provs/Prov.kt | 12 +++++----- .../kotlin/io/provs/platforms/UbuntuProv.kt | 22 ++++++++++++++----- src/main/kotlin/io/provs/platforms/WinProv.kt | 17 ++++++++------ src/test/kotlin/io/provs/ProvTest.kt | 21 ++++++++++++++++++ .../io/provs/platformTest/UbuntuProvTests.kt | 14 ++++++++++++ 5 files changed, 67 insertions(+), 19 deletions(-) diff --git a/src/main/kotlin/io/provs/Prov.kt b/src/main/kotlin/io/provs/Prov.kt index 40860f3..744eb30 100644 --- a/src/main/kotlin/io/provs/Prov.kt +++ b/src/main/kotlin/io/provs/Prov.kt @@ -118,12 +118,12 @@ open class Prov protected constructor(private val processor: Processor, val name /** - * Execute commands using the shell + * Executes a command by using the shell. * Be aware: Executing shell commands that incorporate unsanitized input from an untrusted source * makes a program vulnerable to shell injection, a serious security flaw which can result in arbitrary command execution. * Thus, the use of this method is strongly discouraged in cases where the command string is constructed from external input. */ - open fun cmd(cmd: String, dir: String? = null): ProvResult { + open fun cmd(cmd: String, dir: String? = null, sudo: Boolean = false): ProvResult { throw Exception("Not implemented") } @@ -132,7 +132,7 @@ open class Prov protected constructor(private val processor: Processor, val name * Same as method cmd but without logging of the result/output, should be used e.g. if secrets are involved. * Attention: only result is NOT logged the executed command still is. */ - open fun cmdNoLog(cmd: String, dir: String? = null): ProvResult { + open fun cmdNoLog(cmd: String, dir: String? = null, sudo: Boolean = false): ProvResult { throw Exception("Not implemented") } @@ -141,7 +141,7 @@ open class Prov protected constructor(private val processor: Processor, val name * Same as method cmd but without evaluating the result for the overall success. * Can be used e.g. for checks which might succeed or fail but where failure should not influence overall success */ - open fun cmdNoEval(cmd: String, dir: String? = null): ProvResult { + open fun cmdNoEval(cmd: String, dir: String? = null, sudo: Boolean = false): ProvResult { throw Exception("Not implemented") } @@ -184,7 +184,7 @@ open class Prov protected constructor(private val processor: Processor, val name * Multi-line commands within the script are not supported. * Empty lines and comments (all text behind # in a line) are supported, i.e. they are ignored. */ - fun sh(script: String) = def { + fun sh(script: String, dir: String? = null, sudo: Boolean = false) = def { val lines = script.trimIndent().replace("\r\n", "\n").split("\n") val linesWithoutComments = lines.stream().map { it.split("#")[0] } val linesNonEmpty = linesWithoutComments.filter { it.trim().isNotEmpty() } @@ -193,7 +193,7 @@ open class Prov protected constructor(private val processor: Processor, val name for (cmd in linesNonEmpty) { if (success) { - success = success && cmd(cmd).success + success = success && cmd(cmd, dir, sudo).success } } ProvResult(success) diff --git a/src/main/kotlin/io/provs/platforms/UbuntuProv.kt b/src/main/kotlin/io/provs/platforms/UbuntuProv.kt index c4e4f34..1b3dddf 100644 --- a/src/main/kotlin/io/provs/platforms/UbuntuProv.kt +++ b/src/main/kotlin/io/provs/platforms/UbuntuProv.kt @@ -2,6 +2,7 @@ package io.provs.platforms import io.provs.Prov import io.provs.ProvResult +import io.provs.escapeAndEncloseByDoubleQuoteForShell import io.provs.processors.LocalProcessor import io.provs.processors.Processor @@ -10,15 +11,24 @@ const val SHELL = "/bin/bash" // could be changed to another shell like "sh", " class UbuntuProv internal constructor(processor : Processor = LocalProcessor(), name: String? = null) : Prov (processor, name) { - override fun cmd(cmd: String, dir: String?) : ProvResult = def { - xec(SHELL, "-c", if (dir == null) cmd else "cd $dir && $cmd") + override fun cmd(cmd: String, dir: String?, sudo: Boolean) : ProvResult = def { + xec(SHELL, "-c", commandWithDirAndSudo(cmd, dir, sudo)) } - override fun cmdNoLog(cmd: String, dir: String?) : ProvResult { - return xecNoLog(SHELL, "-c", if (dir == null) cmd else "cd $dir && $cmd") + override fun cmdNoLog(cmd: String, dir: String?, sudo: Boolean) : ProvResult { + return xecNoLog(SHELL, "-c", commandWithDirAndSudo(cmd, dir, sudo)) } - override fun cmdNoEval(cmd: String, dir: String?) : ProvResult { - return xec(SHELL, "-c", if (dir == null) cmd else "cd $dir && $cmd") + override fun cmdNoEval(cmd: String, dir: String?, sudo: Boolean) : ProvResult { + return xec(SHELL, "-c", commandWithDirAndSudo(cmd, dir, sudo)) } +} + +private fun commandWithDirAndSudo(cmd: String, dir: String?, sudo: Boolean): String { + val cmdWithDir= if (dir == null) cmd else "cd $dir && $cmd" + return if (sudo) cmdWithDir.sudoize() else cmdWithDir +} + +private fun String.sudoize(): String { + return "sudo " + SHELL + " -c " + this.escapeAndEncloseByDoubleQuoteForShell() } \ No newline at end of file diff --git a/src/main/kotlin/io/provs/platforms/WinProv.kt b/src/main/kotlin/io/provs/platforms/WinProv.kt index f8a5703..628cf66 100644 --- a/src/main/kotlin/io/provs/platforms/WinProv.kt +++ b/src/main/kotlin/io/provs/platforms/WinProv.kt @@ -8,18 +8,21 @@ import io.provs.processors.Processor class WinProv internal constructor(processor : Processor = LocalProcessor(), name: String? = null) : Prov (processor, name) { - // todo put cmd.exe in variable SHELL + private val SHELL = "cmd.exe" - override fun cmd(cmd: String, dir: String?) : ProvResult = def { - xec("cmd.exe", "/c", if (dir == null) cmd else "cd $dir && $cmd") + override fun cmd(cmd: String, dir: String?, sudo: Boolean) : ProvResult = def { + require(!sudo, {"sudo not supported"}) + xec(SHELL, "/c", if (dir == null) cmd else "cd $dir && $cmd") } - override fun cmdNoLog(cmd: String, dir: String?) : ProvResult = def { - xecNoLog("cmd.exe", "/c", if (dir == null) cmd else "cd $dir && $cmd") + override fun cmdNoLog(cmd: String, dir: String?, sudo: Boolean) : ProvResult = def { + require(!sudo, {"sudo not supported"}) + xecNoLog(SHELL, "/c", if (dir == null) cmd else "cd $dir && $cmd") } - override fun cmdNoEval(cmd: String, dir: String?) : ProvResult { - return xec("cmd.exe", "/c", if (dir == null) cmd else "cd $dir && $cmd") + override fun cmdNoEval(cmd: String, dir: String?, sudo: Boolean) : ProvResult { + require(!sudo, {"sudo not supported"}) + return xec(SHELL, "/c", if (dir == null) cmd else "cd $dir && $cmd") } } \ No newline at end of file diff --git a/src/test/kotlin/io/provs/ProvTest.kt b/src/test/kotlin/io/provs/ProvTest.kt index e44ac62..a02f048 100644 --- a/src/test/kotlin/io/provs/ProvTest.kt +++ b/src/test/kotlin/io/provs/ProvTest.kt @@ -53,6 +53,27 @@ internal class ProvTest { assert(res) } + @Test + @EnabledOnOs(OS.LINUX) + @ContainerTest + fun sh_onLinux_with_dir_and_sudo() { + // given + val script = """ + # test some script commands + + ping -c1 google.com + echo something + ping -c1 github.com + echo 1 # comment behind command + """ + + // when + val res = Prov.newInstance(name = "provs_test").sh(script, "/root", true).success + + // then + assert(res) + } + @Test @EnabledOnOs(OS.WINDOWS) diff --git a/src/test/kotlin/io/provs/platformTest/UbuntuProvTests.kt b/src/test/kotlin/io/provs/platformTest/UbuntuProvTests.kt index 910b69e..3363442 100644 --- a/src/test/kotlin/io/provs/platformTest/UbuntuProvTests.kt +++ b/src/test/kotlin/io/provs/platformTest/UbuntuProvTests.kt @@ -44,6 +44,20 @@ internal class UbuntuProvTests { assert(res2.out?.trim() == "abc") } + @Test + @EnabledOnOs(OS.LINUX) + fun that_cmd_works_with_sudo() { + // given + val a = Prov.defaultInstance() + + // when + val res1 = a.cmd("echo abc", "/root", sudo = true) + + // then + assert(res1.success) + assert(res1.out?.trim() == "abc") + } + @Test @EnabledOnOs(OS.LINUX) fun that_nested_shells_work() {