check github ssh keys against fingerprints
This commit is contained in:
parent
732b373200
commit
73ec38f576
2 changed files with 81 additions and 59 deletions
|
@ -9,48 +9,14 @@ import java.io.File
|
|||
|
||||
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 {
|
||||
val dir = cmdNoEval("basename $repo .git").out?.trim()
|
||||
|
||||
if (dir == null) {
|
||||
ProvResult(false, err = "$repo is not a valid git repository")
|
||||
} else {
|
||||
return@def ProvResult(false, err = "$repo is not a valid git repository")
|
||||
}
|
||||
|
||||
val pathToDir = if (path.endsWith("/")) path + dir else path + "/" + dir
|
||||
if (dirExists(pathToDir + "/.git/")) {
|
||||
if (pullIfExisting) {
|
||||
|
@ -61,27 +27,23 @@ fun Prov.gitClone(repo: String, path: String, pullIfExisting: Boolean = true): P
|
|||
} else {
|
||||
cmd("cd $path && git clone $repo")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun Prov.trustGithub() = def {
|
||||
// current see https://docs.github.com/en/github/authenticating-to-github/githubs-ssh-key-fingerprints
|
||||
|
||||
// todo needs (preferably automatic) conversion to encoding used by keyscan
|
||||
// current fingerprints from https://docs.github.com/en/github/authenticating-to-github/githubs-ssh-key-fingerprints
|
||||
val fingerprints = setOf(
|
||||
"2048 SHA256:nThbg6kXUpJWGl7E1IGOCspRomTxdCARLviKw6E5SY8 github.com (RSA)\n",
|
||||
"SHA256:nThbg6kXUpJWGl7E1IGOCspRomTxdCARLviKw6E5SY8 github.com", // (RSA)
|
||||
// supported beginning September 14, 2021:
|
||||
"2048 SHA256:p2QAMXNIC1TJYWeIOttrVc98/R1BUFWu3/LiyKgUfQM github.com (ECDSA)\n",
|
||||
"2048 SHA256:+DiY3wvvV6TuJJhbpZisF/zLDA0zPMSvHdkr4UvCOqU github.com (Ed25519)\n"
|
||||
"SHA256:p2QAMXNIC1TJYWeIOttrVc98/R1BUFWu3/LiyKgUfQM github.com", // (ECDSA)
|
||||
"SHA256:+DiY3wvvV6TuJJhbpZisF/zLDA0zPMSvHdkr4UvCOqU github.com" // (Ed25519)
|
||||
)
|
||||
|
||||
trustHost("github.com", null)
|
||||
trustHost("github.com", fingerprints)
|
||||
}
|
||||
|
||||
|
||||
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 = """
|
||||
gitlab.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAfuCHKVTjquxvt6CM6tdG4SLp1Btn/nOeHHE5UOzRdf
|
||||
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 ?: "" }
|
||||
}
|
||||
|
|
|
@ -17,15 +17,14 @@ internal class GitKtTest {
|
|||
|
||||
// when
|
||||
val res = a.trustGithub()
|
||||
val known = a.isHostKnown("github.com")
|
||||
val res2 = a.trustGitlab()
|
||||
val known2 = a.isHostKnown("gitlab.com")
|
||||
|
||||
// then
|
||||
assertTrue(res.success)
|
||||
assertTrue(known)
|
||||
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
|
||||
|
|
Loading…
Reference in a new issue