diff --git a/src/build/help/help.xml b/src/build/help/help.xml index 03db73cb0b..a22c90f007 100644 --- a/src/build/help/help.xml +++ b/src/build/help/help.xml @@ -1149,7 +1149,7 @@ SFTP private key file. -

SFTP private key file used for authentication. The {[dash]}-repo-sftp-private-key-file option can be passed multiple times to specify more than one private key file.

+

SFTP private key file used for authentication. The {[dash]}-repo-sftp-private-key-file option can be passed multiple times to specify more than one private key file. If unspecified, will attempt authentication via any default private key files (~/.ssh/id_dsa, ~/.ssh/id_ecdsa, ~/.ssh/id_ecdsa_sk, ~/.ssh/id_ed25519, ~/.ssh/id_ed25519_sk, ~/.ssh/id_rsa) that are present.

NOTE: If {[dash]}-repo-sftp-public-key-file is not specified, the public key path will be generated by appending .pub to the private key path and paired with it's private key for authentication. If it is specified, then it will be paired with each private key to attempt authentication.

NOTE: libssh2 versions before 1.9.0 expect a PEM format keypair, ssh-keygen -m PEM -t rsa -P will generate a PEM keypair without a passphrase.

diff --git a/src/storage/sftp/storage.c b/src/storage/sftp/storage.c index a57f7df7ea..1c3028af71 100644 --- a/src/storage/sftp/storage.c +++ b/src/storage/sftp/storage.c @@ -607,7 +607,8 @@ storageSftpKnownHostsFilesList(const StringList *const knownHosts) } /*********************************************************************************************************************************** -Build private key file list. privKeys requires full path and/or leading tilde path entries. +Build identity file list. If privKeys is empty build the default file list, otherwise build the list provided. privKeys +requires full path and/or leading tilde path entries. ***********************************************************************************************************************************/ static StringList * storageSftpIdentityFilesList(const StringList *const privKeys) @@ -620,17 +621,41 @@ storageSftpIdentityFilesList(const StringList *const privKeys) MEM_CONTEXT_TEMP_BEGIN() { - // Process the privKey file list entries and add them to the result list - for (unsigned int listIdx = 0; listIdx < strLstSize(privKeys); listIdx++) + if (strLstEmpty(privKeys)) { - // Get the trimmed file path and add it to the result list - const String *const filePath = strTrim(strLstGet(privKeys, listIdx)); + // Create default file list, do not include non-existent files, reduces log noise + const Storage *const sshStorage = storagePosixNewP(strNewFmt("%s%s", strZ(userHome()), "/.ssh")); + + StringList *const sshDefaultIdentityFiles = strLstNew(); + strLstAddFmt(sshDefaultIdentityFiles, "%s%s", strZ(userHome()), "/.ssh/id_dsa"); + strLstAddFmt(sshDefaultIdentityFiles, "%s%s", strZ(userHome()), "/.ssh/id_ecdsa"); + strLstAddFmt(sshDefaultIdentityFiles, "%s%s", strZ(userHome()), "/.ssh/id_ecdsa_sk"); + strLstAddFmt(sshDefaultIdentityFiles, "%s%s", strZ(userHome()), "/.ssh/id_ed25519"); + strLstAddFmt(sshDefaultIdentityFiles, "%s%s", strZ(userHome()), "/.ssh/id_ed25519_sk"); + strLstAddFmt(sshDefaultIdentityFiles, "%s%s", strZ(userHome()), "/.ssh/id_rsa"); + + for (unsigned int listIdx = 0; listIdx < strLstSize(sshDefaultIdentityFiles); listIdx++) + { + const String *const filePath = strLstGet(sshDefaultIdentityFiles, listIdx); - // Expand leading tilde and add to the result list - if (strBeginsWithZ(filePath, "~/")) - strLstAddFmt(result, "%s", strZ(storageSftpExpandTildePath(filePath))); - else - strLstAdd(result, filePath); + if (storageExistsP(sshStorage, filePath)) + strLstAdd(result, filePath); + } + } + else + { + // Process the privKey file list entries and add them to the result list + for (unsigned int listIdx = 0; listIdx < strLstSize(privKeys); listIdx++) + { + // Get the trimmed file path and add it to the result list + const String *const filePath = strTrim(strLstGet(privKeys, listIdx)); + + // Expand leading tilde and add to the result list + if (strBeginsWithZ(filePath, "~/")) + strLstAddFmt(result, "%s", strZ(storageSftpExpandTildePath(filePath))); + else + strLstAdd(result, filePath); + } } } MEM_CONTEXT_TEMP_END(); @@ -1359,56 +1384,52 @@ storageSftpNew( libssh2_knownhost_free(knownHostsList); } - // Attempt to authenticate with any provided private keys - // Build/normalize private keys list - StringList *const privateKeys = storageSftpIdentityFilesList(privKeys); - // If provided a public key normalize it if necessary String *const pubKeyPath = param.keyPub != NULL && regExpMatchOne(STRDEF("^ *~"), param.keyPub) ? storageSftpExpandTildePath(param.keyPub) : strDup(param.keyPub); + // Build/normalize private keys list + StringList *const privateKeys = storageSftpIdentityFilesList(privKeys); + + // Attempt to authenticate with private keys bool authSuccess = false; - if (!strLstEmpty(privateKeys)) + for (unsigned int listIdx = 0; listIdx < strLstSize(privateKeys); listIdx++) { - // Attempt to authenticate with each private key - for (unsigned int listIdx = 0; listIdx < strLstSize(privateKeys); listIdx++) - { - const String *const privateKey = strLstGet(privateKeys, listIdx); + const String *const privateKey = strLstGet(privateKeys, listIdx); - // If a public key has been provided use only that public key, otherwise use the private key with a .pub extension - do - { - rc = libssh2_userauth_publickey_fromfile( - this->session, strZ(user), - pubKeyPath != NULL ? strZ(pubKeyPath) : strZ(strCatFmt(strNew(), "%s.pub", strZ(privateKey))), - strZ(privateKey), strZNull(param.keyPassphrase)); - } - while (storageSftpWaitFd(this, rc)); + // If a public key has been provided use only that public key, otherwise use the private key with a .pub extension + do + { + rc = libssh2_userauth_publickey_fromfile( + this->session, strZ(user), + pubKeyPath != NULL ? strZ(pubKeyPath) : strZ(strCatFmt(strNew(),"%s.pub", strZ(privateKey))), + strZ(privateKey), strZNull(param.keyPassphrase)); + } + while (storageSftpWaitFd(this, rc)); - // Log the result of the authentication attempt - if (rc != 0) - { - if (rc == LIBSSH2_ERROR_EAGAIN) - LOG_DETAIL_FMT("timeout during public key authentication"); - else - { - LOG_DETAIL_FMT( - "public key authentication with username %s and key %s failed [%d]", strZ(user), strZ(privateKey), rc); - } - } + // Log the result of the authentication attempt + if (rc != 0) + { + if (rc == LIBSSH2_ERROR_EAGAIN) + LOG_DETAIL_FMT("timeout during public key authentication"); else { - authSuccess = true; - - LOG_DETAIL_FMT("public key authentication with username %s and key %s succeeded", strZ(user), strZ(privateKey)); - break; + LOG_DETAIL_FMT( + "public key authentication with username %s and key %s failed [%d]", strZ(user), strZ(privateKey), rc); } } + else + { + authSuccess = true; + + LOG_DETAIL_FMT("public key authentication with username %s and key %s succeeded", strZ(user), strZ(privateKey)); + break; + } } - // Free private key list and public key path + // Free the private key list, and the public key path strLstFree(privateKeys); strFree(pubKeyPath); diff --git a/test/src/common/harnessLibSsh2.h b/test/src/common/harnessLibSsh2.h index 16689c7bce..9546f5c511 100644 --- a/test/src/common/harnessLibSsh2.h +++ b/test/src/common/harnessLibSsh2.h @@ -25,6 +25,16 @@ libssh2 authorization constants #define KEYPUB STRDEF("/home/" TEST_USER "/.ssh/id_rsa.pub") #define KEYPRIV_CSTR "/home/" TEST_USER "/.ssh/id_rsa" #define KEYPUB_CSTR "/home/" TEST_USER "/.ssh/id_rsa.pub" +#define KEYPRIV_DSA_CSTR "/home/" TEST_USER "/.ssh/id_dsa" +#define KEYPUB_DSA_CSTR "/home/" TEST_USER "/.ssh/id_dsa.pub" +#define KEYPRIV_ECDSA_CSTR "/home/" TEST_USER "/.ssh/id_ecdsa" +#define KEYPUB_ECDSA_CSTR "/home/" TEST_USER "/.ssh/id_ecdsa.pub" +#define KEYPRIV_ECDSA_SK_CSTR "/home/" TEST_USER "/.ssh/id_ecdsa_sk" +#define KEYPUB_ECDSA_SK_CSTR "/home/" TEST_USER "/.ssh/id_ecdsa_sk.pub" +#define KEYPRIV_ED25519_CSTR "/home/" TEST_USER "/.ssh/id_ed25519" +#define KEYPUB_ED25519_CSTR "/home/" TEST_USER "/.ssh/id_ed25519.pub" +#define KEYPRIV_ED25519_SK_CSTR "/home/" TEST_USER "/.ssh/id_ed25519_sk" +#define KEYPUB_ED25519_SK_CSTR "/home/" TEST_USER "/.ssh/id_ed25519_sk.pub" #define KNOWNHOSTS_FILE_CSTR "/home/" TEST_USER "/.ssh/known_hosts" #define KNOWNHOSTS2_FILE_CSTR "/home/" TEST_USER "/.ssh/known_hosts2" #define ETC_KNOWNHOSTS_FILE_CSTR "/etc/ssh/ssh_known_hosts" diff --git a/test/src/module/storage/sftpTest.c b/test/src/module/storage/sftpTest.c index a3e645b87f..b93ed27833 100644 --- a/test/src/module/storage/sftpTest.c +++ b/test/src/module/storage/sftpTest.c @@ -326,6 +326,55 @@ testRun(void) hrnCfgArgRawZ(argList, cfgOptRepoSftpHostFingerprint, "3132333435363738393039383736353433323130"); HRN_CFG_LOAD(cfgCmdArchiveGet, argList); + TEST_ERROR( + storageSftpNewP( + cfgOptionIdxStr(cfgOptRepoPath, repoIdx), cfgOptionIdxStr(cfgOptRepoSftpHost, repoIdx), + cfgOptionIdxUInt(cfgOptRepoSftpHostPort, repoIdx), cfgOptionIdxStr(cfgOptRepoSftpHostUser, repoIdx), + cfgOptionUInt64(cfgOptIoTimeout), strLstNewVarLst(cfgOptionIdxLst(cfgOptRepoSftpPrivateKeyFile, repoIdx)), + cfgOptionIdxStrId(cfgOptRepoSftpHostKeyHashType, repoIdx), .modeFile = STORAGE_MODE_FILE_DEFAULT, + .modePath = STORAGE_MODE_PATH_DEFAULT, .keyPub = cfgOptionIdxStrNull(cfgOptRepoSftpPublicKeyFile, repoIdx), + .keyPassphrase = cfgOptionIdxStrNull(cfgOptRepoSftpPrivateKeyPassphrase, repoIdx), + .hostFingerprint = cfgOptionIdxStrNull(cfgOptRepoSftpHostFingerprint, repoIdx), + .hostKeyCheckType = cfgOptionIdxStrId(cfgOptRepoSftpHostKeyCheckType, repoIdx), + .knownHosts = strLstNewVarLst(cfgOptionIdxLst(cfgOptRepoSftpKnownHost, repoIdx)), + .useSshAgent = cfgOptionIdxBool(cfgOptRepoSftpUseSshAgent, repoIdx)), + ServiceError, + "failure initializing ssh-agent support [-6]: Unable to allocate space for agent connection"); + + // ------------------------------------------------------------------------------------------------------------------------- + TEST_TITLE("public key from file auth failure, no public key"); + + hrnLibSsh2ScriptSet((HrnLibSsh2 []) + { + {.function = HRNLIBSSH2_INIT, .param = "[0]", .resultInt = LIBSSH2_ERROR_NONE}, + {.function = HRNLIBSSH2_SESSION_INIT_EX, .param = "[null,null,null,null]"}, + {.function = HRNLIBSSH2_SESSION_HANDSHAKE, .param = HANDSHAKE_PARAM, .resultInt = LIBSSH2_ERROR_NONE}, + {.function = HRNLIBSSH2_HOSTKEY_HASH, .param = "[2]", .resultZ = "12345678909876543210"}, + {.function = HRNLIBSSH2_USERAUTH_PUBLICKEY_FROMFILE_EX, + .param = "[\"" TEST_USER "\"," TEST_USER_LEN ",\"" KEYPUB_CSTR "\",\"" KEYPRIV_CSTR "\",null]", + .resultInt = LIBSSH2_ERROR_ALLOC}, + {.function = HRNLIBSSH2_AGENT_INIT, .resultNull = true}, + {.function = HRNLIBSSH2_SESSION_LAST_ERROR, .errMsg = (char *)"Unable to allocate space for agent connection", + .resultInt = LIBSSH2_ERROR_ALLOC}, + {.function = NULL} + }); + + // Load configuration + argList = strLstNew(); + hrnCfgArgRawZ(argList, cfgOptStanza, "test"); + hrnCfgArgRawZ(argList, cfgOptPgPath, "/path/to/pg"); + hrnCfgArgRawZ(argList, cfgOptRepo, "1"); + hrnCfgArgRawZ(argList, cfgOptRepoPath, TEST_PATH); + hrnCfgArgRawZ(argList, cfgOptRepoSftpHostUser, TEST_USER); + hrnCfgArgRawZ(argList, cfgOptRepoType, "sftp"); + hrnCfgArgRawZ(argList, cfgOptRepoSftpHost, "localhost"); + hrnCfgArgRawZ(argList, cfgOptRepoSftpHostKeyHashType, "sha1"); + hrnCfgArgRawZ(argList, cfgOptRepoSftpHostKeyCheckType, "fingerprint"); + hrnCfgArgRawZ(argList, cfgOptRepoSftpPrivateKeyFile, "~/.ssh/id_rsa"); + hrnCfgArgRawZ(argList, cfgOptRepoSftpPublicKeyFile, "~/.ssh/id_rsa.pub"); + hrnCfgArgRawZ(argList, cfgOptRepoSftpHostFingerprint, "3132333435363738393039383736353433323130"); + HRN_CFG_LOAD(cfgCmdArchiveGet, argList); + TEST_ERROR( storageSftpNewP( cfgOptionIdxStr(cfgOptRepoPath, repoIdx), cfgOptionIdxStr(cfgOptRepoSftpHost, repoIdx), @@ -776,6 +825,13 @@ testRun(void) // ------------------------------------------------------------------------------------------------------------------------- TEST_TITLE("libssh2_agent_userauth success - identityAgent populated full path"); + Storage *storageSsh = storagePosixNewP(strNewFmt("%s%s", strZ(userHome()), "/.ssh"), .write = true); + HRN_STORAGE_PUT_EMPTY(storageSsh, KEYPRIV_DSA_CSTR); + HRN_STORAGE_PUT_EMPTY(storageSsh, KEYPRIV_ECDSA_CSTR); + HRN_STORAGE_PUT_EMPTY(storageSsh, KEYPRIV_ECDSA_SK_CSTR); + HRN_STORAGE_PUT_EMPTY(storageSsh, KEYPRIV_ED25519_CSTR); + HRN_STORAGE_PUT_EMPTY(storageSsh, KEYPRIV_CSTR); + // Load configuration argList = strLstNew(); hrnCfgArgRawZ(argList, cfgOptStanza, "test"); @@ -798,6 +854,21 @@ testRun(void) {.function = HRNLIBSSH2_SESSION_INIT_EX, .param = "[null,null,null,null]"}, {.function = HRNLIBSSH2_SESSION_HANDSHAKE, .param = HANDSHAKE_PARAM, .resultInt = LIBSSH2_ERROR_NONE}, {.function = HRNLIBSSH2_HOSTKEY_HASH, .param = "[2]", .resultZ = "12345678909876543210"}, + {.function = HRNLIBSSH2_USERAUTH_PUBLICKEY_FROMFILE_EX, + .param = "[\"" TEST_USER "\"," TEST_USER_LEN ",\"" KEYPUB_DSA_CSTR "\",\"" KEYPRIV_DSA_CSTR "\",null]", + .resultInt = LIBSSH2_ERROR_ALLOC}, + {.function = HRNLIBSSH2_USERAUTH_PUBLICKEY_FROMFILE_EX, + .param = "[\"" TEST_USER "\"," TEST_USER_LEN ",\"" KEYPUB_ECDSA_CSTR "\",\"" KEYPRIV_ECDSA_CSTR "\",null]", + .resultInt = LIBSSH2_ERROR_ALLOC}, + {.function = HRNLIBSSH2_USERAUTH_PUBLICKEY_FROMFILE_EX, + .param = "[\"" TEST_USER "\"," TEST_USER_LEN ",\"" KEYPUB_ECDSA_SK_CSTR "\",\"" KEYPRIV_ECDSA_SK_CSTR "\",null]", + .resultInt = LIBSSH2_ERROR_ALLOC}, + {.function = HRNLIBSSH2_USERAUTH_PUBLICKEY_FROMFILE_EX, + .param = "[\"" TEST_USER "\"," TEST_USER_LEN ",\"" KEYPUB_ED25519_CSTR "\",\"" KEYPRIV_ED25519_CSTR "\",null]", + .resultInt = LIBSSH2_ERROR_ALLOC}, + {.function = HRNLIBSSH2_USERAUTH_PUBLICKEY_FROMFILE_EX, + .param = "[\"" TEST_USER "\"," TEST_USER_LEN ",\"" KEYPUB_CSTR "\",\"" KEYPRIV_CSTR "\",null]", + .resultInt = LIBSSH2_ERROR_ALLOC}, {.function = HRNLIBSSH2_AGENT_INIT}, {.function = HRNLIBSSH2_AGENT_SET_IDENTITY_PATH}, {.function = HRNLIBSSH2_AGENT_CONNECT, .resultInt = LIBSSH2_ERROR_NONE}, @@ -841,6 +912,21 @@ testRun(void) {.function = HRNLIBSSH2_SESSION_INIT_EX, .param = "[null,null,null,null]"}, {.function = HRNLIBSSH2_SESSION_HANDSHAKE, .param = HANDSHAKE_PARAM, .resultInt = LIBSSH2_ERROR_NONE}, {.function = HRNLIBSSH2_HOSTKEY_HASH, .param = "[2]", .resultZ = "12345678909876543210"}, + {.function = HRNLIBSSH2_USERAUTH_PUBLICKEY_FROMFILE_EX, + .param = "[\"" TEST_USER "\"," TEST_USER_LEN ",\"" KEYPUB_DSA_CSTR "\",\"" KEYPRIV_DSA_CSTR "\",null]", + .resultInt = LIBSSH2_ERROR_ALLOC}, + {.function = HRNLIBSSH2_USERAUTH_PUBLICKEY_FROMFILE_EX, + .param = "[\"" TEST_USER "\"," TEST_USER_LEN ",\"" KEYPUB_ECDSA_CSTR "\",\"" KEYPRIV_ECDSA_CSTR "\",null]", + .resultInt = LIBSSH2_ERROR_ALLOC}, + {.function = HRNLIBSSH2_USERAUTH_PUBLICKEY_FROMFILE_EX, + .param = "[\"" TEST_USER "\"," TEST_USER_LEN ",\"" KEYPUB_ECDSA_SK_CSTR "\",\"" KEYPRIV_ECDSA_SK_CSTR "\",null]", + .resultInt = LIBSSH2_ERROR_ALLOC}, + {.function = HRNLIBSSH2_USERAUTH_PUBLICKEY_FROMFILE_EX, + .param = "[\"" TEST_USER "\"," TEST_USER_LEN ",\"" KEYPUB_ED25519_CSTR "\",\"" KEYPRIV_ED25519_CSTR "\",null]", + .resultInt = LIBSSH2_ERROR_ALLOC}, + {.function = HRNLIBSSH2_USERAUTH_PUBLICKEY_FROMFILE_EX, + .param = "[\"" TEST_USER "\"," TEST_USER_LEN ",\"" KEYPUB_CSTR "\",\"" KEYPRIV_CSTR "\",null]", + .resultInt = LIBSSH2_ERROR_ALLOC}, {.function = HRNLIBSSH2_AGENT_INIT}, {.function = NULL} }); @@ -861,6 +947,11 @@ testRun(void) ServiceError, "libssh2 version " LIBSSH2_VERSION " does not support ssh-agent identity path, requires version 1.9 or greater"); #endif + HRN_STORAGE_REMOVE(storageSsh, KEYPRIV_DSA_CSTR); + HRN_STORAGE_REMOVE(storageSsh, KEYPRIV_ECDSA_CSTR); + HRN_STORAGE_REMOVE(storageSsh, KEYPRIV_ECDSA_SK_CSTR); + HRN_STORAGE_REMOVE(storageSsh, KEYPRIV_ED25519_CSTR); + HRN_STORAGE_REMOVE(storageSsh, KEYPRIV_CSTR); // ------------------------------------------------------------------------------------------------------------------------- TEST_TITLE("known host init failure"); @@ -1152,6 +1243,7 @@ testRun(void) {.function = NULL} }); + // Load configuration argList = strLstNew(); hrnCfgArgRawZ(argList, cfgOptStanza, "test"); hrnCfgArgRawZ(argList, cfgOptPgPath, "/path/to/pg");