Merge branch 'local-sudoer-without-pw' into 'master'
Local sudoer without pw See merge request domaindrivenarchitecture/provs!6
This commit is contained in:
commit
c82abbb3db
10 changed files with 122 additions and 71 deletions
55
doc/CreateProvInstanceSequence.md
Normal file
55
doc/CreateProvInstanceSequence.md
Normal file
|
@ -0,0 +1,55 @@
|
|||
```plantuml
|
||||
@startuml
|
||||
|
||||
autonumber
|
||||
|
||||
skinparam sequenceBox {
|
||||
borderColor White
|
||||
}
|
||||
|
||||
participant Cli
|
||||
participant Application
|
||||
participant CliArgumentsParser
|
||||
participant CliTargetCommand
|
||||
participant CliUtils
|
||||
participant "CliUtils\ncreateLocalProv" as CliUtilsL
|
||||
participant "CliUtils\ncreateRemoteProv" as CliUtilsR
|
||||
participant Prov
|
||||
participant PromptSecretSource
|
||||
participant User
|
||||
|
||||
Cli -> Application ++ : main(args...)
|
||||
Application -> CliArgumentsParser : parseCommand
|
||||
|
||||
CliArgumentsParser -> CliTargetCommand : create()
|
||||
Application -> CliUtils : createProvInstance( targetCliCommand )
|
||||
alt target.isValidLocal
|
||||
CliUtils -> CliUtilsL : createLocalProv
|
||||
CliUtilsL -> Prov : createLocalInstance
|
||||
alt userCannotSudoWithoutPw
|
||||
CliUtilsL -> PromptSecretSource : getPassword
|
||||
CliUtilsL -> User : makeUserSudoWithoutPw
|
||||
CliUtilsL --> CliUtils : provInstance
|
||||
CliUtils --> Application : provInstance
|
||||
end
|
||||
else target.isValidRemote
|
||||
CliUtils -> CliUtilsR : createRemoteProv
|
||||
CliUtilsR -> Prov : createRemoteInstance
|
||||
alt userCannotSudoWithoutPw
|
||||
CliUtilsR -> PromptSecretSource : getPassword
|
||||
CliUtilsR -> User : makeUserSudoWithoutPw
|
||||
CliUtilsR -> Prov : createRemoteInstance\n[new ssh-client is required]
|
||||
CliUtilsR --> CliUtils : provInstance
|
||||
CliUtils --> Application : provInstance
|
||||
end
|
||||
end
|
||||
|
||||
Application -> DesktopService1 : provisionDesktopCommand ( provInstance, desktopCliCommand )
|
||||
|
||||
|
||||
'DesktopService1 -> DesktopService2 : provisionDesktop( config )
|
||||
'DesktopService1 -> ConfigRepository : getConfig
|
||||
|
||||
@enduml
|
||||
|
||||
```
|
|
@ -24,7 +24,6 @@ CliArgumentsParser -> DesktopCliCommand : create(desktopType, cliTargetCmd, ...)
|
|||
CliArgumentsParser --> Application: desktopCliCommand
|
||||
Application -> DesktopCliCommand : isValid ?
|
||||
Application -> CliUtils : createProvInstance
|
||||
ProvInstance <- CliUtils : create
|
||||
alt target.isValidLocal
|
||||
CliUtils -> CliUtils : createLocalProv
|
||||
else target.isValidRemote
|
||||
|
|
|
@ -17,7 +17,7 @@ fun main(args: Array<String>) {
|
|||
exitProcess(1)
|
||||
}
|
||||
|
||||
val prov = createProvInstance(cmd.target, remoteHostSetSudoWithoutPasswordRequired = true)
|
||||
val prov = createProvInstance(cmd.target)
|
||||
|
||||
try {
|
||||
provisionDesktopCommand(prov, cmd)
|
||||
|
|
|
@ -7,42 +7,39 @@ import org.domaindrivenarchitecture.provs.framework.core.local
|
|||
import org.domaindrivenarchitecture.provs.framework.core.remote
|
||||
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.makeUserSudoerWithNoSudoPasswordRequired
|
||||
import org.domaindrivenarchitecture.provs.framework.ubuntu.user.base.makeCurrentUserSudoerWithoutPasswordRequired
|
||||
import org.domaindrivenarchitecture.provs.framework.ubuntu.user.base.whoami
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
|
||||
/**
|
||||
* Returns a Prov instance according to the targetCommand.
|
||||
* E.g. it returns a local Prov instance if targetCommand.isValidLocalhost() is true or
|
||||
* Returns a local Prov instance if targetCommand.isValidLocalhost() is true resp.
|
||||
* returns a remote Prov instance if targetCommand.isValidRemote() is true.
|
||||
*
|
||||
* If the target is remote and if parameter remoteHostSetSudoWithoutPasswordRequired is set to true,
|
||||
* it will enable sudo without password on the remote machine (in case this was not yet enabled).
|
||||
*/
|
||||
fun createProvInstance(
|
||||
targetCommand: TargetCliCommand,
|
||||
remoteHostSetSudoWithoutPasswordRequired: Boolean = false
|
||||
): Prov {
|
||||
fun createProvInstance(targetCommand: TargetCliCommand): Prov {
|
||||
if (targetCommand.isValid()) {
|
||||
val password: Secret? = targetCommand.remoteTarget()?.password
|
||||
|
||||
val remoteTarget = targetCommand.remoteTarget()
|
||||
if (targetCommand.isValidLocalhost()) {
|
||||
return createLocalProvInstance()
|
||||
|
||||
return if (targetCommand.isValidLocalhost()) {
|
||||
createLocalProvInstance()
|
||||
} else if (targetCommand.isValidRemote() && remoteTarget != null) {
|
||||
return createRemoteProvInstance(
|
||||
createRemoteProvInstance(
|
||||
remoteTarget.host,
|
||||
remoteTarget.user,
|
||||
remoteTarget.password == null,
|
||||
password,
|
||||
remoteHostSetSudoWithoutPasswordRequired
|
||||
password
|
||||
)
|
||||
} else {
|
||||
throw IllegalArgumentException("Error: neither a valid localHost nor a valid remoteHost was specified! Use option -h for help.")
|
||||
throw IllegalArgumentException(
|
||||
"Error: neither a valid localHost nor a valid remoteHost was specified! Use option -h for help."
|
||||
)
|
||||
}
|
||||
} else {
|
||||
println("Invalid command line options.\nPlease use option -h for help.")
|
||||
println("ERROR: Invalid target (${targetCommand.target}). Please use option -h for help.")
|
||||
System.out.flush()
|
||||
exitProcess(1)
|
||||
}
|
||||
}
|
||||
|
@ -50,9 +47,13 @@ fun createProvInstance(
|
|||
private fun createLocalProvInstance(): Prov {
|
||||
val prov = local()
|
||||
if (!prov.currentUserCanSudoWithoutPassword()) {
|
||||
val password = PromptSecretSource("Please enter password to configure sudo without password in the future." +
|
||||
"\nWarning: This will permanently allow your user to use sudo privileges without a password.").secret()
|
||||
prov.makeUserSudoerWithNoSudoPasswordRequired(password)
|
||||
val passwordNonNull = getPasswordToConfigureSudoWithoutPassword()
|
||||
|
||||
prov.makeCurrentUserSudoerWithoutPasswordRequired(passwordNonNull)
|
||||
|
||||
check(prov.currentUserCanSudoWithoutPassword()) {
|
||||
"ERROR: User ${prov.whoami()} cannot sudo without enteringa password."
|
||||
}
|
||||
}
|
||||
return prov
|
||||
}
|
||||
|
@ -62,42 +63,43 @@ private fun createRemoteProvInstance(
|
|||
host: String,
|
||||
remoteUser: String,
|
||||
sshWithKey: Boolean,
|
||||
password: Secret?,
|
||||
remoteHostSetSudoWithoutPasswordRequired: Boolean
|
||||
password: Secret?
|
||||
): Prov {
|
||||
val prov =
|
||||
if (sshWithKey) {
|
||||
remote(host, remoteUser)
|
||||
} else {
|
||||
require(
|
||||
password != null,
|
||||
{ "No password available for provisioning without ssh keys. Either specify provisioning by ssh-keys or provide password." })
|
||||
require(password != null) {
|
||||
"No password available for provisioning without ssh keys. " +
|
||||
"Either specify provisioning by ssh-keys or provide password."
|
||||
}
|
||||
remote(host, remoteUser, password)
|
||||
}
|
||||
|
||||
if (!prov.currentUserCanSudoWithoutPassword()) {
|
||||
if (remoteHostSetSudoWithoutPasswordRequired) {
|
||||
require(
|
||||
password != null,
|
||||
{ "User ${prov.whoami()} not able to sudo on remote machine without password and no password available for the user." })
|
||||
prov.makeUserSudoerWithNoSudoPasswordRequired(password)
|
||||
return if (prov.currentUserCanSudoWithoutPassword()) {
|
||||
prov
|
||||
} else {
|
||||
|
||||
// a new session is required after making the user a sudoer without password
|
||||
return remote(host, remoteUser, password)
|
||||
} else {
|
||||
throw IllegalStateException("User ${prov.whoami()} not able to sudo on remote machine without password and option not set to enable user to sudo without password.")
|
||||
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
|
||||
}
|
||||
return prov
|
||||
}
|
||||
|
||||
|
||||
internal fun retrievePassword(cliCommand: TargetCliCommand): Secret? {
|
||||
var password: Secret? = null
|
||||
if (cliCommand.isValidRemote() && cliCommand.passwordInteractive) {
|
||||
password =
|
||||
PromptSecretSource("Password for user $cliCommand.userName!! on $cliCommand.remoteHost!!").secret()
|
||||
|
||||
}
|
||||
return password
|
||||
}
|
||||
internal fun getPasswordToConfigureSudoWithoutPassword(): Secret {
|
||||
return PromptSecretSource("password to configure sudo without password.").secret()
|
||||
}
|
|
@ -13,13 +13,6 @@ class UbuntuProv internal constructor(
|
|||
progressType: ProgressType
|
||||
) : Prov(processor, name, progressType) {
|
||||
|
||||
init {
|
||||
val user = cmdNoLog("whoami").out?.trim()
|
||||
if ("root" != user && !cmdNoLog("timeout 1 sudo id").success) {
|
||||
println("IMPORTANT INFO:\nUser $user cannot sudo without entering a password, i.e. some functions may fail!\nIf you need to run functions with sudo, please ensure $user can sudo without password.")
|
||||
}
|
||||
}
|
||||
|
||||
override fun cmd(cmd: String, dir: String?, sudo: Boolean): ProvResult = taskWithResult {
|
||||
exec(SHELL, "-c", commandWithDirAndSudo(cmd, dir, sudo))
|
||||
}
|
||||
|
|
|
@ -38,6 +38,8 @@ class RemoteProcessor(host: InetAddress, user: String, password: Secret? = null)
|
|||
|
||||
// Attention: host key is not verified
|
||||
ssh.addHostKeyVerifier(PromiscuousVerifier())
|
||||
|
||||
ssh.connectTimeout = 30000 // ms
|
||||
ssh.connect(host)
|
||||
|
||||
if (password != null) {
|
||||
|
@ -50,8 +52,9 @@ class RemoteProcessor(host: InetAddress, user: String, password: Secret? = null)
|
|||
try {
|
||||
ssh.disconnect()
|
||||
} finally {
|
||||
log.error("Got exception when initializing ssh (Username, password or ssh-key might be wrong): " + e.message)
|
||||
throw RuntimeException("Error when initializing ssh (Username, password or ssh-key might be wrong) ", e)
|
||||
val errorMag = "Error when initializing ssh (Host, username, password or ssh-key might be wrong) "
|
||||
log.error(errorMag + e.message)
|
||||
throw RuntimeException(errorMag, e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ import javax.swing.*
|
|||
|
||||
class PasswordPanel : JPanel(FlowLayout()) {
|
||||
|
||||
private val passwordField = JPasswordField(20)
|
||||
private val passwordField = JPasswordField(30)
|
||||
private var entered = false
|
||||
|
||||
val enteredPassword
|
||||
|
|
|
@ -33,7 +33,7 @@ fun Prov.createUser(
|
|||
}
|
||||
password?.let { cmdNoLog("sudo echo \"$userName:${password.plain()}\" | sudo chpasswd") } ?: ProvResult(true)
|
||||
if (userCanSudoWithoutPassword) {
|
||||
makeUserSudoerWithNoSudoPasswordRequired(userName)
|
||||
makeUserSudoerWithoutPasswordRequired(userName)
|
||||
}
|
||||
val authorizedKeysFile = userHome() + ".ssh/authorized_keys"
|
||||
if (copyAuthorizedSshKeysFromCurrentUser && checkFile(authorizedKeysFile)) {
|
||||
|
@ -85,11 +85,11 @@ fun Prov.deleteUser(userName: String, deleteHomeDir: Boolean = false): ProvResul
|
|||
* The current (executing) user must already be a sudoer. If he is a sudoer with password required then
|
||||
* his password must be provided.
|
||||
*/
|
||||
fun Prov.makeUserSudoerWithNoSudoPasswordRequired(
|
||||
fun Prov.makeUserSudoerWithoutPasswordRequired(
|
||||
userName: String,
|
||||
password: Secret? = null,
|
||||
overwriteFile: Boolean = false
|
||||
): ProvResult = task {
|
||||
): ProvResult = taskWithResult {
|
||||
val userSudoFile = "/etc/sudoers.d/$userName"
|
||||
if (!checkFile(userSudoFile) || overwriteFile) {
|
||||
val sudoPrefix = if (password == null) "sudo" else "echo ${password.plain()} | sudo -S"
|
||||
|
@ -107,11 +107,10 @@ fun Prov.makeUserSudoerWithNoSudoPasswordRequired(
|
|||
* Makes the current (executing) user be able to sudo without password.
|
||||
* IMPORTANT: Current user must already by sudoer when calling this function.
|
||||
*/
|
||||
@Suppress("unused") // used externally
|
||||
fun Prov.makeUserSudoerWithNoSudoPasswordRequired(password: Secret) = task {
|
||||
fun Prov.makeCurrentUserSudoerWithoutPasswordRequired(password: Secret) = taskWithResult {
|
||||
val currentUser = whoami()
|
||||
if (currentUser != null) {
|
||||
makeUserSudoerWithNoSudoPasswordRequired(currentUser, password, overwriteFile = true)
|
||||
makeUserSudoerWithoutPasswordRequired(currentUser, password, overwriteFile = true)
|
||||
} else {
|
||||
ProvResult(false, "Current user could not be determined.")
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@ import io.mockk.*
|
|||
import org.domaindrivenarchitecture.provs.framework.core.Prov
|
||||
import org.domaindrivenarchitecture.provs.framework.core.Secret
|
||||
import org.domaindrivenarchitecture.provs.framework.core.cli.createProvInstance
|
||||
import org.domaindrivenarchitecture.provs.framework.core.cli.retrievePassword
|
||||
import org.domaindrivenarchitecture.provs.framework.core.cli.getPasswordToConfigureSudoWithoutPassword
|
||||
import org.domaindrivenarchitecture.provs.framework.core.local
|
||||
import org.domaindrivenarchitecture.provs.framework.core.processors.PrintOnlyProcessor
|
||||
import org.domaindrivenarchitecture.provs.framework.core.remote
|
||||
|
@ -24,8 +24,8 @@ internal class CliTargetCommandKtTest {
|
|||
mockkStatic(::remote)
|
||||
every { remote(any(), any(), any(), any()) } returns Prov.newInstance(PrintOnlyProcessor())
|
||||
|
||||
mockkStatic(::retrievePassword)
|
||||
every { retrievePassword(any()) } returns Secret("sec")
|
||||
mockkStatic(::getPasswordToConfigureSudoWithoutPassword)
|
||||
every { getPasswordToConfigureSudoWithoutPassword() } returns Secret("sec")
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
|
@ -34,7 +34,7 @@ internal class CliTargetCommandKtTest {
|
|||
unmockkObject(Prov)
|
||||
unmockkStatic(::local)
|
||||
unmockkStatic(::remote)
|
||||
unmockkStatic(::retrievePassword)
|
||||
unmockkStatic(::getPasswordToConfigureSudoWithoutPassword)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ import org.domaindrivenarchitecture.provs.configuration.domain.TargetCliCommand
|
|||
import org.domaindrivenarchitecture.provs.desktop.domain.*
|
||||
import org.domaindrivenarchitecture.provs.desktop.infrastructure.getConfig
|
||||
import org.domaindrivenarchitecture.provs.framework.core.*
|
||||
import org.domaindrivenarchitecture.provs.framework.core.cli.retrievePassword
|
||||
import org.domaindrivenarchitecture.provs.framework.core.cli.getPasswordToConfigureSudoWithoutPassword
|
||||
import org.domaindrivenarchitecture.provs.framework.core.processors.DummyProcessor
|
||||
import org.domaindrivenarchitecture.provs.test.setRootLoggingLevel
|
||||
import org.junit.jupiter.api.AfterAll
|
||||
|
@ -52,8 +52,8 @@ internal class ApplicationKtTest {
|
|||
cmd = "mocked command"
|
||||
)
|
||||
|
||||
mockkStatic(::retrievePassword)
|
||||
every { retrievePassword(any()) } returns Secret("sec")
|
||||
mockkStatic(::getPasswordToConfigureSudoWithoutPassword)
|
||||
every { getPasswordToConfigureSudoWithoutPassword() } returns Secret("sec")
|
||||
}
|
||||
|
||||
@Suppress("unused") // false positive
|
||||
|
@ -65,7 +65,7 @@ internal class ApplicationKtTest {
|
|||
unmockkStatic(::remote)
|
||||
unmockkStatic(::getConfig)
|
||||
unmockkStatic(Prov::provisionDesktop)
|
||||
unmockkStatic(::retrievePassword)
|
||||
unmockkStatic(::getPasswordToConfigureSudoWithoutPassword)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue