add sudo to cmd and sh
This commit is contained in:
parent
b3afde6c93
commit
5eaf320d58
5 changed files with 67 additions and 19 deletions
|
@ -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)
|
||||||
|
|
|
@ -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()
|
||||||
|
}
|
|
@ -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")
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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)
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
Loading…
Reference in a new issue