add sudo to cmd and sh

This commit is contained in:
az 2021-04-27 18:14:58 +02:00
parent b3afde6c93
commit 5eaf320d58
5 changed files with 67 additions and 19 deletions

View file

@ -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 * 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. * 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. * 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") 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. * 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. * 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") 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. * 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 * 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") 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. * 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. * 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 lines = script.trimIndent().replace("\r\n", "\n").split("\n")
val linesWithoutComments = lines.stream().map { it.split("#")[0] } val linesWithoutComments = lines.stream().map { it.split("#")[0] }
val linesNonEmpty = linesWithoutComments.filter { it.trim().isNotEmpty() } 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) { for (cmd in linesNonEmpty) {
if (success) { if (success) {
success = success && cmd(cmd).success success = success && cmd(cmd, dir, sudo).success
} }
} }
ProvResult(success) ProvResult(success)

View file

@ -2,6 +2,7 @@ package io.provs.platforms
import io.provs.Prov import io.provs.Prov
import io.provs.ProvResult import io.provs.ProvResult
import io.provs.escapeAndEncloseByDoubleQuoteForShell
import io.provs.processors.LocalProcessor import io.provs.processors.LocalProcessor
import io.provs.processors.Processor 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) { class UbuntuProv internal constructor(processor : Processor = LocalProcessor(), name: String? = null) : Prov (processor, name) {
override fun cmd(cmd: String, dir: String?) : ProvResult = def { override fun cmd(cmd: String, dir: String?, sudo: Boolean) : ProvResult = def {
xec(SHELL, "-c", if (dir == null) cmd else "cd $dir && $cmd") xec(SHELL, "-c", commandWithDirAndSudo(cmd, dir, sudo))
} }
override fun cmdNoLog(cmd: String, dir: String?) : ProvResult { override fun cmdNoLog(cmd: String, dir: String?, sudo: Boolean) : ProvResult {
return xecNoLog(SHELL, "-c", if (dir == null) cmd else "cd $dir && $cmd") return xecNoLog(SHELL, "-c", commandWithDirAndSudo(cmd, dir, sudo))
} }
override fun cmdNoEval(cmd: String, dir: String?) : ProvResult { override fun cmdNoEval(cmd: String, dir: String?, sudo: Boolean) : ProvResult {
return xec(SHELL, "-c", if (dir == null) cmd else "cd $dir && $cmd") 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()
}

View file

@ -8,18 +8,21 @@ import io.provs.processors.Processor
class WinProv internal constructor(processor : Processor = LocalProcessor(), name: String? = null) : Prov (processor, name) { 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 { override fun cmd(cmd: String, dir: String?, sudo: Boolean) : ProvResult = def {
xec("cmd.exe", "/c", if (dir == null) cmd else "cd $dir && $cmd") 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 { override fun cmdNoLog(cmd: String, dir: String?, sudo: Boolean) : ProvResult = def {
xecNoLog("cmd.exe", "/c", if (dir == null) cmd else "cd $dir && $cmd") require(!sudo, {"sudo not supported"})
xecNoLog(SHELL, "/c", if (dir == null) cmd else "cd $dir && $cmd")
} }
override fun cmdNoEval(cmd: String, dir: String?) : ProvResult { override fun cmdNoEval(cmd: String, dir: String?, sudo: Boolean) : ProvResult {
return xec("cmd.exe", "/c", if (dir == null) cmd else "cd $dir && $cmd") require(!sudo, {"sudo not supported"})
return xec(SHELL, "/c", if (dir == null) cmd else "cd $dir && $cmd")
} }
} }

View file

@ -53,6 +53,27 @@ internal class ProvTest {
assert(res) 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 @Test
@EnabledOnOs(OS.WINDOWS) @EnabledOnOs(OS.WINDOWS)

View file

@ -44,6 +44,20 @@ internal class UbuntuProvTests {
assert(res2.out?.trim() == "abc") 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 @Test
@EnabledOnOs(OS.LINUX) @EnabledOnOs(OS.LINUX)
fun that_nested_shells_work() { fun that_nested_shells_work() {