diff --git a/src/main/kotlin/org/domaindrivenarchitecture/provs/desktop/domain/KnownHost.kt b/src/main/kotlin/org/domaindrivenarchitecture/provs/desktop/domain/KnownHost.kt index 443bd3a..75b1e49 100644 --- a/src/main/kotlin/org/domaindrivenarchitecture/provs/desktop/domain/KnownHost.kt +++ b/src/main/kotlin/org/domaindrivenarchitecture/provs/desktop/domain/KnownHost.kt @@ -7,7 +7,7 @@ package org.domaindrivenarchitecture.provs.desktop.domain */ typealias HostKey = String -open class KnownHost protected constructor( +open class KnownHost( val hostName: String, val hostKeys: List ) { diff --git a/src/main/kotlin/org/domaindrivenarchitecture/provs/desktop/domain/KnownHostService.kt b/src/main/kotlin/org/domaindrivenarchitecture/provs/desktop/domain/KnownHostService.kt index 4ceb6bb..4468f73 100644 --- a/src/main/kotlin/org/domaindrivenarchitecture/provs/desktop/domain/KnownHostService.kt +++ b/src/main/kotlin/org/domaindrivenarchitecture/provs/desktop/domain/KnownHostService.kt @@ -6,6 +6,6 @@ import org.domaindrivenarchitecture.provs.framework.ubuntu.keys.base.addKnownHos fun Prov.addKnownHosts(knownHosts: List = KnownHost.values()) = task { for (knownHost in knownHosts) { - addKnownHost(knownHost.hostName, knownHost.hostKeys, verifyKeys = true) + addKnownHost(knownHost, verifyKeys = true) } } \ No newline at end of file diff --git a/src/main/kotlin/org/domaindrivenarchitecture/provs/framework/ubuntu/keys/base/Ssh.kt b/src/main/kotlin/org/domaindrivenarchitecture/provs/framework/ubuntu/keys/base/Ssh.kt index cee5133..d4d4cbc 100644 --- a/src/main/kotlin/org/domaindrivenarchitecture/provs/framework/ubuntu/keys/base/Ssh.kt +++ b/src/main/kotlin/org/domaindrivenarchitecture/provs/framework/ubuntu/keys/base/Ssh.kt @@ -1,5 +1,6 @@ package org.domaindrivenarchitecture.provs.framework.ubuntu.keys.base +import org.domaindrivenarchitecture.provs.desktop.domain.KnownHost import org.domaindrivenarchitecture.provs.framework.core.Prov import org.domaindrivenarchitecture.provs.framework.core.ProvResult import org.domaindrivenarchitecture.provs.framework.ubuntu.filesystem.base.* @@ -7,8 +8,6 @@ import org.domaindrivenarchitecture.provs.framework.ubuntu.keys.SshKeyPair import java.io.File -const val KNOWN_HOSTS_FILE = "~/.ssh/known_hosts" - /** * Installs ssh keys for active user; ssh filenames depend on the ssh key type, e.g. for public key file: "id_rsa.pub", "id_id_ed25519.pub", etc */ @@ -24,34 +23,37 @@ fun Prov.configureSshKeys(sshKeys: SshKeyPair) = task { * * @return whether if was found */ -fun Prov.isHostKnown(hostOrIp: String) : Boolean { +fun Prov.isHostKnown(hostOrIp: String): Boolean { return cmdNoEval("ssh-keygen -F $hostOrIp").out?.isNotEmpty() ?: false } /** - * Adds ssh keys for specified host (which also can be an ip-address) to ssh-file "known_hosts" - * Either add the specified keys or - if null - add keys automatically retrieved. - * Note: adding keys automatically is vulnerable to a man-in-the-middle attack, thus considered insecure and not recommended. + * Adds ssh keys for specified host (which also can be an ip-address) to the ssh-file "known_hosts". + * If parameter verifyKeys is true the keys are checked against the live keys of the host and only added if valid. */ -fun Prov.addKnownHost(host: String, keysToBeAdded: List?, verifyKeys: Boolean = false) = task { - if (!checkFile(KNOWN_HOSTS_FILE)) { +fun Prov.addKnownHost(knownHost: KnownHost, verifyKeys: Boolean = false) = task { + val knownHostsFile = "~/.ssh/known_hosts" + + if (!checkFile(knownHostsFile)) { createDir(".ssh") - createFile(KNOWN_HOSTS_FILE, null) + createFile(knownHostsFile, null) } - if (keysToBeAdded == null) { - // auto add keys - cmd("ssh-keyscan $host >> $KNOWN_HOSTS_FILE") - } else { - for (key in keysToBeAdded) { + with(knownHost) { + for (key in hostKeys) { if (!verifyKeys) { - addTextToFile("\n$host $key\n", File(KNOWN_HOSTS_FILE)) + addTextToFile("\n$hostName $key\n", File(knownHostsFile)) } else { - val validKeys = getSshKeys(host) + val validKeys = getSshKeys(hostName) if (validKeys?.contains(key) == true) { - addTextToFile("\n$host $key\n", File(KNOWN_HOSTS_FILE)) + addTextToFile("\n$hostName $key\n", File(knownHostsFile)) } else { - addResultToEval(ProvResult(false, err = "The following key of host [$host] could not be verified successfully: " + key)) + addResultToEval( + ProvResult( + false, + err = "The following key of host [$hostName] could not be verified successfully: " + key + ) + ) } } } diff --git a/src/test/kotlin/org/domaindrivenarchitecture/provs/desktop/domain/KnownHostTest.kt b/src/test/kotlin/org/domaindrivenarchitecture/provs/desktop/domain/KnownHostTest.kt index ddae547..b4b785d 100644 --- a/src/test/kotlin/org/domaindrivenarchitecture/provs/desktop/domain/KnownHostTest.kt +++ b/src/test/kotlin/org/domaindrivenarchitecture/provs/desktop/domain/KnownHostTest.kt @@ -2,7 +2,6 @@ package org.domaindrivenarchitecture.provs.desktop.domain import org.domaindrivenarchitecture.provs.framework.ubuntu.filesystem.base.deleteFile import org.domaindrivenarchitecture.provs.framework.ubuntu.install.base.aptInstall -import org.domaindrivenarchitecture.provs.framework.ubuntu.keys.base.KNOWN_HOSTS_FILE import org.domaindrivenarchitecture.provs.test.defaultTestContainer import org.domaindrivenarchitecture.provs.test.tags.ContainerTest import org.junit.jupiter.api.Assertions.assertEquals @@ -18,7 +17,7 @@ class KnownHostTest { val prov = defaultTestContainer() prov.task { aptInstall("ssh") - deleteFile(KNOWN_HOSTS_FILE) + deleteFile("~/.ssh/known_hosts") } // when diff --git a/src/test/kotlin/org/domaindrivenarchitecture/provs/framework/ubuntu/keys/base/SshKtTest.kt b/src/test/kotlin/org/domaindrivenarchitecture/provs/framework/ubuntu/keys/base/SshKtTest.kt index 5c317b8..8ad7367 100644 --- a/src/test/kotlin/org/domaindrivenarchitecture/provs/framework/ubuntu/keys/base/SshKtTest.kt +++ b/src/test/kotlin/org/domaindrivenarchitecture/provs/framework/ubuntu/keys/base/SshKtTest.kt @@ -1,5 +1,6 @@ package org.domaindrivenarchitecture.provs.framework.ubuntu.keys.base +import org.domaindrivenarchitecture.provs.desktop.domain.KnownHost import org.domaindrivenarchitecture.provs.framework.core.Secret import org.domaindrivenarchitecture.provs.framework.ubuntu.filesystem.base.deleteFile import org.domaindrivenarchitecture.provs.framework.ubuntu.filesystem.base.fileContainsText @@ -10,6 +11,8 @@ import org.domaindrivenarchitecture.provs.test.defaultTestContainer import org.domaindrivenarchitecture.provs.test.tags.ContainerTest import org.junit.jupiter.api.Assertions.* +const val KNOWN_HOSTS_FILE = "~/.ssh/known_hosts" + internal class SshKtTest { @ContainerTest fun configureSshKeys_for_ssh_type_rsa() { @@ -48,7 +51,7 @@ internal class SshKtTest { } @ContainerTest - fun addKnownHost() { + fun addKnownHost_without_verification() { // given val prov = defaultTestContainer() prov.task { @@ -57,10 +60,8 @@ internal class SshKtTest { } // when - val res = prov.addKnownHost("github.com", listOf("dummyProtocol dummyKey", "dummyProtocol2 dummyKey2", )) - val res2 = prov.addKnownHost("github.com", listOf("dummyProtocol dummyKey", "dummyProtocol2 dummyKey2", )) - val res3 = prov.addKnownHost("github.com", listOf("ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOMqqnkVzrm0SdG6UOoqKLsabgH5C9okWi0dh2l9GKJl", ), verifyKeys = true) - val res4 = prov.addKnownHost("github.com", listOf("ssh-ed25519 AAAAC3Nzalwrongkey!!!", ), verifyKeys = true) + val res = prov.addKnownHost(KnownHost("github.com", listOf("dummyProtocol dummyKey", "dummyProtocol2 dummyKey2", ))) + val res2 = prov.addKnownHost(KnownHost("github.com", listOf("dummyProtocol dummyKey", "dummyProtocol2 dummyKey2", ))) // then assertTrue(res.success) @@ -70,8 +71,32 @@ internal class SshKtTest { assertTrue(res2.success) val keyCount = prov.cmd("grep -o -i dummyKey2 $KNOWN_HOSTS_FILE | wc -l").out?.trim() assertEquals("1", keyCount) + } - assertTrue(res3.success) - assertFalse(res4.success) + @ContainerTest + fun addKnownHost_with_verifications() { + // given + val prov = defaultTestContainer() + prov.task { + aptInstall("ssh") + deleteFile(KNOWN_HOSTS_FILE) + } + + // when + val res1 = prov.addKnownHost(KnownHost.GITHUB, verifyKeys = true) + val res2 = prov.addKnownHost(KnownHost.GITHUB, verifyKeys = true) + + val invalidKey = "ssh-ed25519 AAAAC3Nzalwrongkey!!!" + val res3 = prov.addKnownHost(KnownHost("github.com", listOf(invalidKey )), verifyKeys = true) + + // then + assertTrue(res1.success) + assertTrue(prov.fileContainsText(KNOWN_HOSTS_FILE, KnownHost.GITHUB.hostKeys[0])) + + assertTrue(res2.success) + assertTrue(prov.fileContainsText(KNOWN_HOSTS_FILE, KnownHost.GITHUB.hostKeys[0])) + + assertFalse(res3.success) + assertFalse(prov.fileContainsText(KNOWN_HOSTS_FILE, invalidKey)) } } \ No newline at end of file