[skip ci] refactor ensureSudoWithoutPassword to application layer
This commit is contained in:
parent
8a4bbe97f9
commit
1497d390f6
5 changed files with 159 additions and 77 deletions
|
@ -0,0 +1,32 @@
|
||||||
|
package org.domaindrivenarchitecture.provs.configuration.application
|
||||||
|
|
||||||
|
import org.domaindrivenarchitecture.provs.configuration.domain.TargetCliCommand
|
||||||
|
import org.domaindrivenarchitecture.provs.framework.core.Prov
|
||||||
|
import org.domaindrivenarchitecture.provs.framework.core.cli.createRemoteProvInstance
|
||||||
|
import org.domaindrivenarchitecture.provs.framework.core.cli.getPasswordToConfigureSudoWithoutPassword
|
||||||
|
import org.domaindrivenarchitecture.provs.framework.ubuntu.user.base.currentUserCanSudoWithoutPassword
|
||||||
|
import org.domaindrivenarchitecture.provs.framework.ubuntu.user.base.makeCurrentUserSudoerWithoutPasswordRequired
|
||||||
|
|
||||||
|
|
||||||
|
fun ensureSudoWithoutPassword(prov: Prov, targetCommand: TargetCliCommand): Prov {
|
||||||
|
|
||||||
|
return if (prov.currentUserCanSudoWithoutPassword()) {
|
||||||
|
prov
|
||||||
|
} else {
|
||||||
|
val password = targetCommand.remoteTarget()?.password ?: getPasswordToConfigureSudoWithoutPassword()
|
||||||
|
|
||||||
|
val result = prov.makeCurrentUserSudoerWithoutPasswordRequired(password)
|
||||||
|
|
||||||
|
check(result.success) {
|
||||||
|
"Could not make user a sudoer without password required. (E.g. the password provided may be incorrect.)"
|
||||||
|
}
|
||||||
|
|
||||||
|
return if (targetCommand.isValidRemote()) {
|
||||||
|
// return a new instance as for remote instances a new ssh client is required after user was made sudoer without password
|
||||||
|
createRemoteProvInstance(targetCommand.remoteTarget())
|
||||||
|
} else {
|
||||||
|
prov
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
package org.domaindrivenarchitecture.provs.desktop.application
|
package org.domaindrivenarchitecture.provs.desktop.application
|
||||||
|
|
||||||
import kotlinx.serialization.SerializationException
|
import kotlinx.serialization.SerializationException
|
||||||
|
import org.domaindrivenarchitecture.provs.configuration.application.ensureSudoWithoutPassword
|
||||||
import org.domaindrivenarchitecture.provs.desktop.domain.provisionDesktopCommand
|
import org.domaindrivenarchitecture.provs.desktop.domain.provisionDesktopCommand
|
||||||
import org.domaindrivenarchitecture.provs.framework.core.cli.createProvInstance
|
import org.domaindrivenarchitecture.provs.framework.core.cli.createProvInstance
|
||||||
import java.io.FileNotFoundException
|
import java.io.FileNotFoundException
|
||||||
|
@ -18,9 +19,10 @@ fun main(args: Array<String>) {
|
||||||
}
|
}
|
||||||
|
|
||||||
val prov = createProvInstance(cmd.target)
|
val prov = createProvInstance(cmd.target)
|
||||||
|
val provWithSudo = ensureSudoWithoutPassword(prov, cmd.target)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
provisionDesktopCommand(prov, cmd)
|
provisionDesktopCommand(provWithSudo, cmd)
|
||||||
} catch (e: SerializationException) {
|
} catch (e: SerializationException) {
|
||||||
println(
|
println(
|
||||||
"Error: File \"${cmd.configFile?.fileName}\" has an invalid format and or invalid data.\n"
|
"Error: File \"${cmd.configFile?.fileName}\" has an invalid format and or invalid data.\n"
|
||||||
|
|
|
@ -6,9 +6,6 @@ import org.domaindrivenarchitecture.provs.framework.core.Secret
|
||||||
import org.domaindrivenarchitecture.provs.framework.core.local
|
import org.domaindrivenarchitecture.provs.framework.core.local
|
||||||
import org.domaindrivenarchitecture.provs.framework.core.remote
|
import org.domaindrivenarchitecture.provs.framework.core.remote
|
||||||
import org.domaindrivenarchitecture.provs.framework.ubuntu.secret.secretSources.PromptSecretSource
|
import org.domaindrivenarchitecture.provs.framework.ubuntu.secret.secretSources.PromptSecretSource
|
||||||
import org.domaindrivenarchitecture.provs.framework.ubuntu.user.base.currentUserCanSudoWithoutPassword
|
|
||||||
import org.domaindrivenarchitecture.provs.framework.ubuntu.user.base.makeCurrentUserSudoerWithoutPasswordRequired
|
|
||||||
import org.domaindrivenarchitecture.provs.framework.ubuntu.user.base.whoami
|
|
||||||
import kotlin.system.exitProcess
|
import kotlin.system.exitProcess
|
||||||
|
|
||||||
|
|
||||||
|
@ -21,17 +18,10 @@ fun createProvInstance(targetCommand: TargetCliCommand): Prov {
|
||||||
if (targetCommand.isValid()) {
|
if (targetCommand.isValid()) {
|
||||||
val password: Secret? = targetCommand.remoteTarget()?.password
|
val password: Secret? = targetCommand.remoteTarget()?.password
|
||||||
|
|
||||||
val remoteTarget = targetCommand.remoteTarget()
|
|
||||||
|
|
||||||
return if (targetCommand.isValidLocalhost()) {
|
return if (targetCommand.isValidLocalhost()) {
|
||||||
createLocalProvInstance()
|
local()
|
||||||
} else if (targetCommand.isValidRemote() && remoteTarget != null) {
|
} else if (targetCommand.isValidRemote()) {
|
||||||
createRemoteProvInstance(
|
createRemoteProvInstance(targetCommand.remoteTarget(), password)
|
||||||
remoteTarget.host,
|
|
||||||
remoteTarget.user,
|
|
||||||
remoteTarget.password == null,
|
|
||||||
password
|
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
throw IllegalArgumentException(
|
throw IllegalArgumentException(
|
||||||
"Error: neither a valid localHost nor a valid remoteHost was specified! Use option -h for help."
|
"Error: neither a valid localHost nor a valid remoteHost was specified! Use option -h for help."
|
||||||
|
@ -44,62 +34,21 @@ fun createProvInstance(targetCommand: TargetCliCommand): Prov {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createLocalProvInstance(): Prov {
|
|
||||||
val prov = local()
|
|
||||||
if (!prov.currentUserCanSudoWithoutPassword()) {
|
|
||||||
val passwordNonNull = getPasswordToConfigureSudoWithoutPassword()
|
|
||||||
|
|
||||||
prov.makeCurrentUserSudoerWithoutPasswordRequired(passwordNonNull)
|
internal fun createRemoteProvInstance(
|
||||||
|
target: TargetCliCommand.RemoteTarget?,
|
||||||
check(prov.currentUserCanSudoWithoutPassword()) {
|
password: Secret? = null
|
||||||
"ERROR: User ${prov.whoami()} cannot sudo without enteringa password."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return prov
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private fun createRemoteProvInstance(
|
|
||||||
host: String,
|
|
||||||
remoteUser: String,
|
|
||||||
sshWithKey: Boolean,
|
|
||||||
password: Secret?
|
|
||||||
): Prov {
|
): Prov {
|
||||||
val prov =
|
return if (target != null) {
|
||||||
if (sshWithKey) {
|
remote(target.host, target.user, target.password ?: password)
|
||||||
remote(host, remoteUser)
|
|
||||||
} else {
|
} else {
|
||||||
require(password != null) {
|
throw IllegalArgumentException(
|
||||||
"No password available for provisioning without ssh keys. " +
|
"Error: no valid remote target (host & user) was specified!"
|
||||||
"Either specify provisioning by ssh-keys or provide password."
|
)
|
||||||
}
|
|
||||||
remote(host, remoteUser, password)
|
|
||||||
}
|
|
||||||
|
|
||||||
return if (prov.currentUserCanSudoWithoutPassword()) {
|
|
||||||
prov
|
|
||||||
} else {
|
|
||||||
|
|
||||||
val passwordNonNull = password
|
|
||||||
?: getPasswordToConfigureSudoWithoutPassword()
|
|
||||||
|
|
||||||
val result = prov.makeCurrentUserSudoerWithoutPasswordRequired(passwordNonNull)
|
|
||||||
|
|
||||||
check(result.success) {
|
|
||||||
"Could not make user a sudoer without password required. (Maybe the provided password is incorrect.)"
|
|
||||||
}
|
|
||||||
|
|
||||||
// a new session is required after the user has become a sudoer without password
|
|
||||||
val provWithNewSshClient = remote(host, remoteUser, password)
|
|
||||||
|
|
||||||
check(provWithNewSshClient.currentUserCanSudoWithoutPassword()) {
|
|
||||||
"ERROR: User ${provWithNewSshClient.whoami()} on $host cannot sudo without entering a password."
|
|
||||||
}
|
|
||||||
|
|
||||||
provWithNewSshClient
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
internal fun getPasswordToConfigureSudoWithoutPassword(): Secret {
|
internal fun getPasswordToConfigureSudoWithoutPassword(): Secret {
|
||||||
return PromptSecretSource("password to configure sudo without password.").secret()
|
return PromptSecretSource("password to configure sudo without password.").secret()
|
||||||
}
|
}
|
|
@ -5,7 +5,6 @@ import org.domaindrivenarchitecture.provs.framework.core.Prov
|
||||||
import org.domaindrivenarchitecture.provs.framework.core.docker.provideContainer
|
import org.domaindrivenarchitecture.provs.framework.core.docker.provideContainer
|
||||||
import org.domaindrivenarchitecture.provs.framework.core.escapeAndEncloseByDoubleQuoteForShell
|
import org.domaindrivenarchitecture.provs.framework.core.escapeAndEncloseByDoubleQuoteForShell
|
||||||
import org.domaindrivenarchitecture.provs.framework.core.platforms.SHELL
|
import org.domaindrivenarchitecture.provs.framework.core.platforms.SHELL
|
||||||
import org.domaindrivenarchitecture.provs.framework.core.tags.Api
|
|
||||||
|
|
||||||
enum class ContainerStartMode {
|
enum class ContainerStartMode {
|
||||||
USE_RUNNING_ELSE_CREATE,
|
USE_RUNNING_ELSE_CREATE,
|
||||||
|
@ -20,26 +19,24 @@ enum class ContainerEndMode {
|
||||||
|
|
||||||
open class ContainerUbuntuHostProcessor(
|
open class ContainerUbuntuHostProcessor(
|
||||||
private val containerName: String = "default_provs_container",
|
private val containerName: String = "default_provs_container",
|
||||||
@Api // suppress false positive warning
|
dockerImage: String = "ubuntu",
|
||||||
private val dockerImage: String = "ubuntu",
|
startMode: ContainerStartMode = ContainerStartMode.USE_RUNNING_ELSE_CREATE,
|
||||||
@Api // suppress false positive warning
|
|
||||||
private val startMode: ContainerStartMode = ContainerStartMode.USE_RUNNING_ELSE_CREATE,
|
|
||||||
private val endMode: ContainerEndMode = ContainerEndMode.KEEP_RUNNING,
|
private val endMode: ContainerEndMode = ContainerEndMode.KEEP_RUNNING,
|
||||||
@Api // suppress false positive warning
|
sudo: Boolean = true,
|
||||||
private val sudo: Boolean = true
|
options: String = ""
|
||||||
) : Processor {
|
) : Processor {
|
||||||
|
|
||||||
|
private val hostShell = "/bin/bash"
|
||||||
private val dockerCmd = if (sudo) "sudo docker " else "docker "
|
private val dockerCmd = if (sudo) "sudo docker " else "docker "
|
||||||
private var localExecution = LocalProcessor()
|
private var localExecution = LocalProcessor()
|
||||||
private var a = Prov.newInstance(name = "LocalProcessor for Docker operations", progressType = ProgressType.NONE)
|
private var a = Prov.newInstance(name = "LocalProcessor for Docker operations", progressType = ProgressType.NONE)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
val r = a.provideContainer(containerName, dockerImage, startMode, sudo)
|
val result = a.provideContainer(containerName, dockerImage, startMode, sudo, options)
|
||||||
if (!r.success)
|
if (!result.success)
|
||||||
throw RuntimeException("Could not start docker image: " + r.toString(), r.exception)
|
throw RuntimeException("Could not start docker image: " + result.toString(), result.exception)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val hostShell = "/bin/bash"
|
|
||||||
|
|
||||||
override fun exec(vararg args: String): ProcessResult {
|
override fun exec(vararg args: String): ProcessResult {
|
||||||
return localExecution.exec(hostShell, "-c", dockerCmd + "exec $containerName " + buildCommand(*args))
|
return localExecution.exec(hostShell, "-c", dockerCmd + "exec $containerName " + buildCommand(*args))
|
||||||
}
|
}
|
||||||
|
@ -57,7 +54,7 @@ open class ContainerUbuntuHostProcessor(
|
||||||
return s.escapeAndEncloseByDoubleQuoteForShell()
|
return s.escapeAndEncloseByDoubleQuoteForShell()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun buildCommand(vararg args: String) : String {
|
private fun buildCommand(vararg args: String): String {
|
||||||
return if (args.size == 1) quoteString(args[0]) else
|
return if (args.size == 1) quoteString(args[0]) else
|
||||||
if (args.size == 3 && SHELL == args[0] && "-c" == args[1]) SHELL + " -c " + quoteString(args[2])
|
if (args.size == 3 && SHELL == args[0] && "-c" == args[1]) SHELL + " -c " + quoteString(args[2])
|
||||||
else args.joinToString(separator = " ")
|
else args.joinToString(separator = " ")
|
||||||
|
|
|
@ -0,0 +1,102 @@
|
||||||
|
package org.domaindrivenarchitecture.provs.configuration.application
|
||||||
|
|
||||||
|
import io.mockk.every
|
||||||
|
import io.mockk.mockkStatic
|
||||||
|
import io.mockk.unmockkStatic
|
||||||
|
import org.domaindrivenarchitecture.provs.configuration.domain.TargetCliCommand
|
||||||
|
import org.domaindrivenarchitecture.provs.framework.core.*
|
||||||
|
import org.domaindrivenarchitecture.provs.framework.core.cli.getPasswordToConfigureSudoWithoutPassword
|
||||||
|
import org.domaindrivenarchitecture.provs.framework.core.docker.provideContainer
|
||||||
|
import org.domaindrivenarchitecture.provs.framework.core.processors.ContainerStartMode
|
||||||
|
import org.domaindrivenarchitecture.provs.framework.core.processors.ContainerUbuntuHostProcessor
|
||||||
|
import org.domaindrivenarchitecture.provs.framework.ubuntu.filesystem.base.deleteFile
|
||||||
|
import org.domaindrivenarchitecture.provs.framework.ubuntu.install.base.aptInstall
|
||||||
|
import org.domaindrivenarchitecture.provs.framework.ubuntu.user.base.currentUserCanSudoWithoutPassword
|
||||||
|
import org.domaindrivenarchitecture.provs.framework.ubuntu.user.base.makeCurrentUserSudoerWithoutPasswordRequired
|
||||||
|
import org.domaindrivenarchitecture.provs.test.tags.ExtensiveContainerTest
|
||||||
|
import org.junit.jupiter.api.Assertions.assertFalse
|
||||||
|
import org.junit.jupiter.api.Assertions.assertTrue
|
||||||
|
|
||||||
|
|
||||||
|
class ProvWithSudoKtTest {
|
||||||
|
|
||||||
|
|
||||||
|
@ExtensiveContainerTest
|
||||||
|
fun test_ensureSudoWithoutPassword_local_Prov() {
|
||||||
|
|
||||||
|
mockkStatic(::getPasswordToConfigureSudoWithoutPassword)
|
||||||
|
every { getPasswordToConfigureSudoWithoutPassword() } returns Secret("testuserpw")
|
||||||
|
|
||||||
|
// given
|
||||||
|
val containerName = "prov-test-sudo-no-pw"
|
||||||
|
local().provideContainer(containerName, "ubuntu_plus_user")
|
||||||
|
val prov = Prov.newInstance(
|
||||||
|
ContainerUbuntuHostProcessor(
|
||||||
|
containerName,
|
||||||
|
startMode = ContainerStartMode.USE_RUNNING_ELSE_CREATE,
|
||||||
|
sudo = true,
|
||||||
|
dockerImage = "ubuntu_plus_user"
|
||||||
|
),
|
||||||
|
progressType = ProgressType.NONE
|
||||||
|
)
|
||||||
|
prov.deleteFile("/etc/sudoers.d/testuser", sudo = true) // remove no password required config
|
||||||
|
|
||||||
|
// when
|
||||||
|
val canSudo1 = prov.currentUserCanSudoWithoutPassword()
|
||||||
|
val provWithSudo = ensureSudoWithoutPassword(
|
||||||
|
prov, TargetCliCommand("local")
|
||||||
|
)
|
||||||
|
val canSudo2 = provWithSudo.currentUserCanSudoWithoutPassword()
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertFalse(canSudo1)
|
||||||
|
assertTrue(canSudo2)
|
||||||
|
|
||||||
|
unmockkStatic(::getPasswordToConfigureSudoWithoutPassword)
|
||||||
|
}
|
||||||
|
|
||||||
|
@ExtensiveContainerTest
|
||||||
|
fun test_ensureSudoWithoutPassword_remote_Prov() {
|
||||||
|
|
||||||
|
// mockkStatic(::getPasswordToConfigureSudoWithoutPassword)
|
||||||
|
// every { getPasswordToConfigureSudoWithoutPassword() } returns Secret("testuserpw")
|
||||||
|
|
||||||
|
// given
|
||||||
|
val containerName = "prov-test-sudo-no-pw-ssh"
|
||||||
|
val password = Secret("testuserpw")
|
||||||
|
|
||||||
|
// local().provideContainer(containerName, "ubuntu_plus_user", options = "")
|
||||||
|
val prov = Prov.newInstance(
|
||||||
|
ContainerUbuntuHostProcessor(
|
||||||
|
containerName,
|
||||||
|
startMode = ContainerStartMode.USE_RUNNING_ELSE_CREATE,
|
||||||
|
sudo = true,
|
||||||
|
dockerImage = "ubuntu_plus_user",
|
||||||
|
options = "--expose=22"
|
||||||
|
),
|
||||||
|
progressType = ProgressType.NONE
|
||||||
|
)
|
||||||
|
prov.makeCurrentUserSudoerWithoutPasswordRequired(password)
|
||||||
|
prov.task {
|
||||||
|
aptInstall("openssh-server")
|
||||||
|
cmd("sudo service ssh start")
|
||||||
|
deleteFile("/etc/sudoers.d/testuser", sudo = true) // remove no password required config
|
||||||
|
}
|
||||||
|
val ip = local().cmd("sudo docker inspect -f \"{{ .NetworkSettings.IPAddress }}\" $containerName").out?.trim()
|
||||||
|
?: throw IllegalStateException("Ip not found")
|
||||||
|
val remoteProvBySsh = remote(ip, "testuser", password)
|
||||||
|
|
||||||
|
// when
|
||||||
|
val canSudo1 = remoteProvBySsh.currentUserCanSudoWithoutPassword()
|
||||||
|
val provWithSudo = ensureSudoWithoutPassword(
|
||||||
|
remoteProvBySsh, TargetCliCommand("testuser:${password.plain()}@$ip")
|
||||||
|
)
|
||||||
|
val canSudo2 = provWithSudo.currentUserCanSudoWithoutPassword()
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertFalse(canSudo1)
|
||||||
|
assertTrue(canSudo2)
|
||||||
|
|
||||||
|
// unmockkStatic(::getPasswordToConfigureSudoWithoutPassword)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue