check github ssh keys against fingerprints

This commit is contained in:
az 2021-11-24 12:09:25 +01:00
parent 732b373200
commit 73ec38f576
2 changed files with 81 additions and 59 deletions

View file

@ -9,48 +9,14 @@ import java.io.File
val knownHostsFile = "~/.ssh/known_hosts" val knownHostsFile = "~/.ssh/known_hosts"
/**
* Adds ssh keys for specified host (which also can be an ip-address) to ssh-file "known_hosts"
* Either add the specified rsaFingerprints or - if null - add automatically retrieved keys.
* Note: adding keys automatically is vulnerable to a man-in-the-middle attack and not considered secure.
*/
private fun Prov.trustHost(host: String, rsaFingerprints: Set<String>?) = def {
if (!isHostKnown(host)) {
if (!fileExists(knownHostsFile)) {
createDir(".ssh")
createFile(knownHostsFile, null)
}
if (rsaFingerprints == null) {
// auto add keys
cmd("ssh-keyscan -H $host >> $knownHostsFile")
} else {
// logic based on https://serverfault.com/questions/447028/non-interactive-git-clone-ssh-fingerprint-prompt
val key = cmd("ssh-keyscan $host").out
if (key == null) {
ProvResult(false, "No key retrieved for $host")
} else {
val c = printToShell(key).trim()
val fpr = cmd(c + " | ssh-keygen -lf -").out
if (rsaFingerprints.contains(fpr)
) {
cmd(printToShell(key) + " >> $knownHostsFile")
} else {
ProvResult(false, "Fingerprint $fpr not valid for $host")
}
}
}
} else {
ProvResult(true, out = "Host already known")
}
}
fun Prov.gitClone(repo: String, path: String, pullIfExisting: Boolean = true): ProvResult = def { fun Prov.gitClone(repo: String, path: String, pullIfExisting: Boolean = true): ProvResult = def {
val dir = cmdNoEval("basename $repo .git").out?.trim() val dir = cmdNoEval("basename $repo .git").out?.trim()
if (dir == null) { if (dir == null) {
ProvResult(false, err = "$repo is not a valid git repository") return@def ProvResult(false, err = "$repo is not a valid git repository")
} else { }
val pathToDir = if (path.endsWith("/")) path + dir else path + "/" + dir val pathToDir = if (path.endsWith("/")) path + dir else path + "/" + dir
if (dirExists(pathToDir + "/.git/")) { if (dirExists(pathToDir + "/.git/")) {
if (pullIfExisting) { if (pullIfExisting) {
@ -62,26 +28,22 @@ fun Prov.gitClone(repo: String, path: String, pullIfExisting: Boolean = true): P
cmd("cd $path && git clone $repo") cmd("cd $path && git clone $repo")
} }
} }
}
fun Prov.trustGithub() = def { fun Prov.trustGithub() = def {
// current see https://docs.github.com/en/github/authenticating-to-github/githubs-ssh-key-fingerprints // current fingerprints from https://docs.github.com/en/github/authenticating-to-github/githubs-ssh-key-fingerprints
// todo needs (preferably automatic) conversion to encoding used by keyscan
val fingerprints = setOf( val fingerprints = setOf(
"2048 SHA256:nThbg6kXUpJWGl7E1IGOCspRomTxdCARLviKw6E5SY8 github.com (RSA)\n", "SHA256:nThbg6kXUpJWGl7E1IGOCspRomTxdCARLviKw6E5SY8 github.com", // (RSA)
// supported beginning September 14, 2021: // supported beginning September 14, 2021:
"2048 SHA256:p2QAMXNIC1TJYWeIOttrVc98/R1BUFWu3/LiyKgUfQM github.com (ECDSA)\n", "SHA256:p2QAMXNIC1TJYWeIOttrVc98/R1BUFWu3/LiyKgUfQM github.com", // (ECDSA)
"2048 SHA256:+DiY3wvvV6TuJJhbpZisF/zLDA0zPMSvHdkr4UvCOqU github.com (Ed25519)\n" "SHA256:+DiY3wvvV6TuJJhbpZisF/zLDA0zPMSvHdkr4UvCOqU github.com" // (Ed25519)
) )
trustHost("github.com", fingerprints)
trustHost("github.com", null)
} }
fun Prov.trustGitlab() = def { fun Prov.trustGitlab() = def {
// from https://docs.gitlab.com/ee/user/gitlab_com/ // entries for known_hosts from https://docs.gitlab.com/ee/user/gitlab_com/
val gitlabFingerprints = """ val gitlabFingerprints = """
gitlab.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAfuCHKVTjquxvt6CM6tdG4SLp1Btn/nOeHHE5UOzRdf gitlab.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAfuCHKVTjquxvt6CM6tdG4SLp1Btn/nOeHHE5UOzRdf
gitlab.com ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCsj2bNKTBSpIYDEGk9KxsGh3mySTRgMtXL583qmBpzeQ+jqCMRgBqB98u3z++J1sKlXHWfM9dyhSevkMwSbhoR8XIq/U0tCNyokEi/ueaBMCvbcTHhO7FcwzY92WK4Yt0aGROY5qX2UKSeOvuP4D6TPqKF1onrSzH9bx9XUf2lEdWT/ia1NEKjunUqu1xOB/StKDHMoX4/OKyIzuS0q/T1zOATthvasJFoPrAjkohTyaDUz2LN5JoH839hViyEG82yB+MjcFV5MU3N1l1QL3cVUCh93xSaua1N85qivl+siMkPGbO5xR/En4iEY6K2XPASUEMaieWVNTRCtJ4S8H+9 gitlab.com ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCsj2bNKTBSpIYDEGk9KxsGh3mySTRgMtXL583qmBpzeQ+jqCMRgBqB98u3z++J1sKlXHWfM9dyhSevkMwSbhoR8XIq/U0tCNyokEi/ueaBMCvbcTHhO7FcwzY92WK4Yt0aGROY5qX2UKSeOvuP4D6TPqKF1onrSzH9bx9XUf2lEdWT/ia1NEKjunUqu1xOB/StKDHMoX4/OKyIzuS0q/T1zOATthvasJFoPrAjkohTyaDUz2LN5JoH839hViyEG82yB+MjcFV5MU3N1l1QL3cVUCh93xSaua1N85qivl+siMkPGbO5xR/En4iEY6K2XPASUEMaieWVNTRCtJ4S8H+9
@ -91,3 +53,64 @@ fun Prov.trustGitlab() = def {
} }
/**
* Adds ssh keys for specified host (which also can be an ip-address) to ssh-file "known_hosts"
* Either add the specified rsaFingerprints or - if null - add automatically retrieved keys.
* Note: adding keys automatically is vulnerable to a man-in-the-middle attack and not considered secure.
*/
// todo: consider making function public and moving to ssh package
private fun Prov.trustHost(host: String, fingerprintsOfKeysToBeAdded: Set<String>?) = def {
if (isHostKnown(host)) {
return@def ProvResult(true, out = "Host already known")
}
if (!fileExists(knownHostsFile)) {
createDir(".ssh")
createFile(knownHostsFile, null)
}
if (fingerprintsOfKeysToBeAdded == null) {
// auto add keys
cmd("ssh-keyscan -H $host >> $knownHostsFile")
} else {
// logic based on https://serverfault.com/questions/447028/non-interactive-git-clone-ssh-fingerprint-prompt
val actualKeys = findSshKeys(host)
if (actualKeys == null || actualKeys.size == 0) {
return@def ProvResult(false, out = "No valid keys found for host: $host")
}
val actualFingerprints = getFingerprintsForKeys(actualKeys)
for (fingerprintToBeAdded in fingerprintsOfKeysToBeAdded) {
var indexOfKeyFound = -1
// search for fingerprint in actual fingerprints
for ((i, actualFingerprint) in actualFingerprints.withIndex()) {
if (actualFingerprint.contains(fingerprintToBeAdded)) {
indexOfKeyFound = i
break
}
}
if (indexOfKeyFound == -1) {
return@def ProvResult(
false,
err = "Fingerprint ($fingerprintToBeAdded) could not be found in actual fingerprints: $actualFingerprints"
)
}
cmd(printToShell(actualKeys.get(indexOfKeyFound)) + " >> $knownHostsFile")
}
ProvResult(true)
}
}
/**
* Returns a list of valid ssh keys for the given host (host can also be an ip address)
*/
private fun Prov.findSshKeys(host: String): List<String>? {
return cmd("ssh-keyscan $host 2>/dev/null").out?.split("\n")?.filter { x -> "" != x }
}
/**
* Returns a list of fingerprints of the given sshKeys; the returning list has same size and order as the specified list of sshKeys
*/
private fun Prov.getFingerprintsForKeys(sshKeys: List<String>): List<String> {
return sshKeys.map { x -> cmd("echo \"$x\" | ssh-keygen -lf -").out ?: "" }
}

View file

@ -17,15 +17,14 @@ internal class GitKtTest {
// when // when
val res = a.trustGithub() val res = a.trustGithub()
val known = a.isHostKnown("github.com")
val res2 = a.trustGitlab() val res2 = a.trustGitlab()
val known2 = a.isHostKnown("gitlab.com")
// then // then
assertTrue(res.success) assertTrue(res.success)
assertTrue(known)
assertTrue(res2.success) assertTrue(res2.success)
assertTrue(known2)
assertTrue(a.isHostKnown("github.com"), "github.com does not seem to be a known host")
assertTrue(a.isHostKnown("gitlab.com"), "gitlab.com does not seem to be a known host")
} }
@Test @Test