add port to KnownHost
This commit is contained in:
parent
111d9951ed
commit
2fff923539
3 changed files with 77 additions and 20 deletions
|
@ -1,26 +1,35 @@
|
||||||
package org.domaindrivenarchitecture.provs.desktop.domain
|
package org.domaindrivenarchitecture.provs.desktop.domain
|
||||||
|
|
||||||
|
typealias HostKey = String
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A HostKey should contain space-separated: keytype, key and (optionally) a comment
|
* Represents a known host for ssh connections.
|
||||||
|
*
|
||||||
|
* @param hostName domain name or ip
|
||||||
|
* @param port (optional) to be specified if different from default port 22
|
||||||
|
* @param hostKeys list of keys, where each should contain separated by space: 1. keytype, 2. key and 3. (optionally) a comment
|
||||||
*
|
*
|
||||||
* See: https://man7.org/linux/man-pages/man8/sshd.8.html#SSH_KNOWN_HOSTS_FILE_FORMAT
|
* See: https://man7.org/linux/man-pages/man8/sshd.8.html#SSH_KNOWN_HOSTS_FILE_FORMAT
|
||||||
*/
|
*/
|
||||||
typealias HostKey = String
|
|
||||||
|
|
||||||
open class KnownHost(
|
open class KnownHost(
|
||||||
val hostName: String,
|
val hostName: String,
|
||||||
|
val port: Int? = null,
|
||||||
val hostKeys: List<HostKey>
|
val hostKeys: List<HostKey>
|
||||||
) {
|
) {
|
||||||
|
constructor(hostName: String, hostKeys: List<HostKey>) : this(hostName, null, hostKeys)
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val GITHUB = KnownHost(
|
val GITHUB = KnownHost(
|
||||||
"github.com", listOf(
|
"github.com",
|
||||||
|
listOf(
|
||||||
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOMqqnkVzrm0SdG6UOoqKLsabgH5C9okWi0dh2l9GKJl",
|
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOMqqnkVzrm0SdG6UOoqKLsabgH5C9okWi0dh2l9GKJl",
|
||||||
"ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBEmKSENjQEezOmxkZMy7opKgwFB9nkt5YRrYMjNuG5N87uRgg6CLrbo5wAdT/y6v0mKV0U2w0WZ2YB/++Tpockg=",
|
"ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBEmKSENjQEezOmxkZMy7opKgwFB9nkt5YRrYMjNuG5N87uRgg6CLrbo5wAdT/y6v0mKV0U2w0WZ2YB/++Tpockg=",
|
||||||
"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCj7ndNxQowgcQnjshcLrqPEiiphnt+VTTvDP6mHBL9j1aNUkY4Ue1gvwnGLVlOhGeYrnZaMgRK6+PKCUXaDbC7qtbW8gIkhL7aGCsOr/C56SJMy/BCZfxd1nWzAOxSDPgVsmerOBYfNqltV9/hWCqBywINIR+5dIg6JTJ72pcEpEjcYgXkE2YEFXV1JHnsKgbLWNlhScqb2UmyRkQyytRLtL+38TGxkxCflmO+5Z8CSSNY7GidjMIZ7Q4zMjA2n1nGrlTDkzwDCsw+wqFPGQA179cnfGWOWRVruj16z6XyvxvjJwbz0wQZ75XK5tKSb7FNyeIEs4TT4jk+S4dhPeAUC5y+bDYirYgM4GC7uEnztnZyaVWQ7B381AK4Qdrwt51ZqExKbQpTUNn+EjqoTwvqNj4kqx5QUCI0ThS/YkOxJCXmPUWZbhjpCg56i+2aB6CmK2JGhn57K5mj0MNdBXA4/WnwH6XoPWJzK5Nyu2zB3nAZp+S5hpQs+p1vN1/wsjk=",
|
"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCj7ndNxQowgcQnjshcLrqPEiiphnt+VTTvDP6mHBL9j1aNUkY4Ue1gvwnGLVlOhGeYrnZaMgRK6+PKCUXaDbC7qtbW8gIkhL7aGCsOr/C56SJMy/BCZfxd1nWzAOxSDPgVsmerOBYfNqltV9/hWCqBywINIR+5dIg6JTJ72pcEpEjcYgXkE2YEFXV1JHnsKgbLWNlhScqb2UmyRkQyytRLtL+38TGxkxCflmO+5Z8CSSNY7GidjMIZ7Q4zMjA2n1nGrlTDkzwDCsw+wqFPGQA179cnfGWOWRVruj16z6XyvxvjJwbz0wQZ75XK5tKSb7FNyeIEs4TT4jk+S4dhPeAUC5y+bDYirYgM4GC7uEnztnZyaVWQ7B381AK4Qdrwt51ZqExKbQpTUNn+EjqoTwvqNj4kqx5QUCI0ThS/YkOxJCXmPUWZbhjpCg56i+2aB6CmK2JGhn57K5mj0MNdBXA4/WnwH6XoPWJzK5Nyu2zB3nAZp+S5hpQs+p1vN1/wsjk=",
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
val GITLAB = KnownHost(
|
val GITLAB = KnownHost(
|
||||||
"gitlab.com", listOf(
|
"gitlab.com",
|
||||||
|
listOf(
|
||||||
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAfuCHKVTjquxvt6CM6tdG4SLp1Btn/nOeHHE5UOzRdf",
|
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAfuCHKVTjquxvt6CM6tdG4SLp1Btn/nOeHHE5UOzRdf",
|
||||||
"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCsj2bNKTBSpIYDEGk9KxsGh3mySTRgMtXL583qmBpzeQ+jqCMRgBqB98u3z++J1sKlXHWfM9dyhSevkMwSbhoR8XIq/U0tCNyokEi/ueaBMCvbcTHhO7FcwzY92WK4Yt0aGROY5qX2UKSeOvuP4D6TPqKF1onrSzH9bx9XUf2lEdWT/ia1NEKjunUqu1xOB/StKDHMoX4/OKyIzuS0q/T1zOATthvasJFoPrAjkohTyaDUz2LN5JoH839hViyEG82yB+MjcFV5MU3N1l1QL3cVUCh93xSaua1N85qivl+siMkPGbO5xR/En4iEY6K2XPASUEMaieWVNTRCtJ4S8H+9",
|
"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCsj2bNKTBSpIYDEGk9KxsGh3mySTRgMtXL583qmBpzeQ+jqCMRgBqB98u3z++J1sKlXHWfM9dyhSevkMwSbhoR8XIq/U0tCNyokEi/ueaBMCvbcTHhO7FcwzY92WK4Yt0aGROY5qX2UKSeOvuP4D6TPqKF1onrSzH9bx9XUf2lEdWT/ia1NEKjunUqu1xOB/StKDHMoX4/OKyIzuS0q/T1zOATthvasJFoPrAjkohTyaDUz2LN5JoH839hViyEG82yB+MjcFV5MU3N1l1QL3cVUCh93xSaua1N85qivl+siMkPGbO5xR/En4iEY6K2XPASUEMaieWVNTRCtJ4S8H+9",
|
||||||
"ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBFSMqzJeV9rUzU4kWitGjeR4PWSa29SPqJ1fVkhtj3Hw9xjLVXVYrU9QlYWrOLXBpQ6KWjbjTDTdDkoohFzgbEY=",
|
"ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBFSMqzJeV9rUzU4kWitGjeR4PWSa29SPqJ1fVkhtj3Hw9xjLVXVYrU9QlYWrOLXBpQ6KWjbjTDTdDkoohFzgbEY=",
|
||||||
|
|
|
@ -19,18 +19,21 @@ fun Prov.configureSshKeys(sshKeys: SshKeyPair) = task {
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if the specified hostname or Ip is in a known_hosts file
|
* Checks if the specified host (domain name or IP) and (optional) port is contained in the known_hosts file
|
||||||
*
|
|
||||||
* @return whether if was found
|
|
||||||
*/
|
*/
|
||||||
fun Prov.isKnownHost(hostOrIp: String): Boolean {
|
fun Prov.isKnownHost(hostOrIp: String, port: Int? = null): Boolean {
|
||||||
return cmdNoEval("ssh-keygen -F $hostOrIp").out?.isNotEmpty() ?: false
|
val hostWithPotentialPort = port?.let { hostInKnownHostsFileFormat(hostOrIp, port) } ?: hostOrIp
|
||||||
|
return cmdNoEval("ssh-keygen -F $hostWithPotentialPort").out?.isNotEmpty() ?: false
|
||||||
|
}
|
||||||
|
|
||||||
|
fun hostInKnownHostsFileFormat(hostOrIp: String, port: Int? = null): String {
|
||||||
|
return port?.let { "[$hostOrIp]:$port" } ?: hostOrIp
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds ssh keys for specified host (which also can be an ip-address) to the ssh-file "known_hosts".
|
* 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.
|
* If parameter verifyKeys is true, the keys are checked against the live keys of the host and added only if valid.
|
||||||
*/
|
*/
|
||||||
fun Prov.addKnownHost(knownHost: KnownHost, verifyKeys: Boolean = false) = task {
|
fun Prov.addKnownHost(knownHost: KnownHost, verifyKeys: Boolean = false) = task {
|
||||||
val knownHostsFile = "~/.ssh/known_hosts"
|
val knownHostsFile = "~/.ssh/known_hosts"
|
||||||
|
@ -44,9 +47,10 @@ fun Prov.addKnownHost(knownHost: KnownHost, verifyKeys: Boolean = false) = task
|
||||||
if (!verifyKeys) {
|
if (!verifyKeys) {
|
||||||
addTextToFile("\n$hostName $key\n", File(knownHostsFile))
|
addTextToFile("\n$hostName $key\n", File(knownHostsFile))
|
||||||
} else {
|
} else {
|
||||||
val validKeys = getSshKeys(hostName)
|
val validKeys = findSshKeys(hostName, port)
|
||||||
if (validKeys?.contains(key) == true) {
|
if (validKeys?.contains(key) == true) {
|
||||||
addTextToFile("\n$hostName $key\n", File(knownHostsFile))
|
val formattedHost = hostInKnownHostsFileFormat(hostName, port)
|
||||||
|
addTextToFile("\n$formattedHost $key\n", File(knownHostsFile))
|
||||||
} else {
|
} else {
|
||||||
addResultToEval(
|
addResultToEval(
|
||||||
ProvResult(
|
ProvResult(
|
||||||
|
@ -62,10 +66,14 @@ fun Prov.addKnownHost(knownHost: KnownHost, verifyKeys: Boolean = false) = task
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a list of valid ssh keys for the given host (host can also be an ip address), keys are returned as keytype and key BUT WITHOUT the host name
|
* Returns a list of valid ssh keys for the given host (host can also be an ip address),
|
||||||
|
* keys are returned (space-separated) as keytype and key, but WITHOUT the host name.*
|
||||||
|
* If no port is specified, the keys for the default port (22) are returned.
|
||||||
|
* If no keytype is specified, keys are returned for all keytypes.
|
||||||
*/
|
*/
|
||||||
private fun Prov.getSshKeys(host: String, keytype: String? = null): List<String>? {
|
fun Prov.findSshKeys(host: String, port: Int? = null, keytype: String? = null): List<String>? {
|
||||||
|
val portOption = port?.let { " -p $port " } ?: ""
|
||||||
val keytypeOption = keytype?.let { " -t $keytype " } ?: ""
|
val keytypeOption = keytype?.let { " -t $keytype " } ?: ""
|
||||||
val output = cmd("ssh-keyscan $keytypeOption $host 2>/dev/null").out?.trim()
|
val output = cmd("ssh-keyscan $portOption $keytypeOption $host 2>/dev/null").out?.trim()
|
||||||
return output?.split("\n")?.filter { x -> "" != x }?.map { x -> x.substringAfter(" ") }
|
return output?.split("\n")?.filter { x -> "" != x }?.map { x -> x.substringAfter(" ") }
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,10 +2,11 @@ package org.domaindrivenarchitecture.provs.desktop.domain
|
||||||
|
|
||||||
import org.domaindrivenarchitecture.provs.framework.ubuntu.filesystem.base.deleteFile
|
import org.domaindrivenarchitecture.provs.framework.ubuntu.filesystem.base.deleteFile
|
||||||
import org.domaindrivenarchitecture.provs.framework.ubuntu.install.base.aptInstall
|
import org.domaindrivenarchitecture.provs.framework.ubuntu.install.base.aptInstall
|
||||||
|
import org.domaindrivenarchitecture.provs.framework.ubuntu.keys.base.addKnownHost
|
||||||
|
import org.domaindrivenarchitecture.provs.framework.ubuntu.keys.base.isKnownHost
|
||||||
import org.domaindrivenarchitecture.provs.test.defaultTestContainer
|
import org.domaindrivenarchitecture.provs.test.defaultTestContainer
|
||||||
import org.domaindrivenarchitecture.provs.test.tags.ContainerTest
|
import org.domaindrivenarchitecture.provs.test.tags.ContainerTest
|
||||||
import org.junit.jupiter.api.Assertions.assertEquals
|
import org.junit.jupiter.api.Assertions.*
|
||||||
import org.junit.jupiter.api.Assertions.assertTrue
|
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
|
|
||||||
|
|
||||||
|
@ -29,10 +30,10 @@ class KnownHostTest {
|
||||||
|
|
||||||
|
|
||||||
// Subclass of KnownHost for test knownHostSubclass_includes_additional_host
|
// Subclass of KnownHost for test knownHostSubclass_includes_additional_host
|
||||||
class KnownHostsSubclass(hostName: String, hostKeys: List<HostKey>): KnownHost(hostName, hostKeys) {
|
class KnownHostsSubclass(hostName: String, port: Int?, hostKeys: List<HostKey>): KnownHost(hostName, port, hostKeys) {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val ANOTHER_HOST = KnownHostsSubclass("anotherhost.com", listOf("key1"))
|
val ANOTHER_HOST = KnownHostsSubclass("anotherhost.com", 2222, listOf("key1"))
|
||||||
|
|
||||||
fun values(): List<KnownHost> {
|
fun values(): List<KnownHost> {
|
||||||
return values + ANOTHER_HOST
|
return values + ANOTHER_HOST
|
||||||
|
@ -49,5 +50,44 @@ class KnownHostTest {
|
||||||
assertTrue(hosts.size > 1)
|
assertTrue(hosts.size > 1)
|
||||||
assertEquals("key1", hosts.last().hostKeys[0])
|
assertEquals("key1", hosts.last().hostKeys[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ContainerTest
|
||||||
|
fun knownHost_with_port_verified_successfully() {
|
||||||
|
// given
|
||||||
|
val prov = defaultTestContainer()
|
||||||
|
prov.task {
|
||||||
|
aptInstall("ssh")
|
||||||
|
deleteFile("~/.ssh/known_hosts")
|
||||||
|
}
|
||||||
|
|
||||||
|
// when
|
||||||
|
assertFalse(prov.isKnownHost(KnownHost.GITHUB.hostName))
|
||||||
|
assertFalse(prov.isKnownHost(KnownHost.GITHUB.hostName, 22))
|
||||||
|
val res = prov.addKnownHost(KnownHost(KnownHost.GITHUB.hostName, 22, KnownHost.GITHUB.hostKeys), verifyKeys = true)
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertTrue(res.success)
|
||||||
|
assertFalse(prov.isKnownHost(KnownHost.GITHUB.hostName))
|
||||||
|
assertTrue(prov.isKnownHost(KnownHost.GITHUB.hostName, 22))
|
||||||
|
}
|
||||||
|
|
||||||
|
@ContainerTest
|
||||||
|
fun knownHost_with_port_verification_failing() {
|
||||||
|
// given
|
||||||
|
val prov = defaultTestContainer()
|
||||||
|
prov.task {
|
||||||
|
aptInstall("ssh")
|
||||||
|
deleteFile("~/.ssh/known_hosts")
|
||||||
|
}
|
||||||
|
|
||||||
|
// when
|
||||||
|
assertFalse(prov.isKnownHost(KnownHost.GITHUB.hostName, 80))
|
||||||
|
val res2 = prov.addKnownHost(KnownHost(KnownHost.GITHUB.hostName, 80, KnownHost.GITHUB.hostKeys), verifyKeys = true)
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertFalse(res2.success)
|
||||||
|
assertFalse(prov.isKnownHost(KnownHost.GITHUB.hostName))
|
||||||
|
assertFalse(prov.isKnownHost(KnownHost.GITHUB.hostName, 80))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue