1
0
mirror of https://github.com/pgbackrest/pgbackrest.git synced 2025-11-06 08:49:29 +02:00

Add known hosts checking for SFTP storage driver.

By default require a known hosts match as part of the SFTP storage driver's authentication process, i.e. repo-sftp-host-key-check-type=strict. The match is expected to be found in the default list or in a list of known hosts files provided by the user. An exception is made if a fingerprint has been manually configured with repo-sftp-host-fingerprint or repo-sftp-host-key-check-type=accept-new can be used to automatically add new hosts.

Also allow host key verification to be skipped, as before, but require the user to explicitly set this (repo-sftp-host-key-check-type=none) rather than it being the default.
This commit is contained in:
Reid Thompson
2023-09-15 20:22:38 -04:00
committed by GitHub
parent f5c730fd03
commit ce9ba0fade
17 changed files with 3855 additions and 231 deletions

View File

@@ -1322,6 +1322,7 @@ sub configCreate
$oParamHash{&CFGDEF_SECTION_GLOBAL}{'repo1-sftp-host-user'} = TEST_USER;
$oParamHash{&CFGDEF_SECTION_GLOBAL}{'repo1-sftp-private-key-file'} = testRunGet()->basePath() . SSH_PRIVATE_KEY;
$oParamHash{&CFGDEF_SECTION_GLOBAL}{'repo1-sftp-public-key-file'} = testRunGet()->basePath() . SSH_PUBLIC_KEY;
$oParamHash{&CFGDEF_SECTION_GLOBAL}{'repo1-sftp-host-key-check-type'} = "none";
$oParamHash{&CFGDEF_SECTION_GLOBAL}{'repo1-path'} = $self->repoPath();
# At what count do we hit diminishing returns

View File

@@ -165,6 +165,180 @@ libssh2_init(int flags)
return hrnLibSsh2->resultInt;
}
/***********************************************************************************************************************************
Shim for libssh2_knownhost_addc
***********************************************************************************************************************************/
int
libssh2_knownhost_addc(
LIBSSH2_KNOWNHOSTS *hosts, const char *host, const char *salt, const char *key, size_t keylen, const char *comment,
size_t commentlen, int typemask, struct libssh2_knownhost **store)
{
// Avoid compiler complaining of unused param
(void)store;
if (hosts == NULL)
{
snprintf(
hrnLibSsh2ScriptError, sizeof(hrnLibSsh2ScriptError),
"libssh2 script function 'libssh2_knownhost_adddc', expects hosts to be not NULL");
THROW(AssertError, hrnLibSsh2ScriptError);
}
HrnLibSsh2 *hrnLibSsh2 = NULL;
MEM_CONTEXT_TEMP_BEGIN()
{
hrnLibSsh2 = hrnLibSsh2ScriptRun(
HRNLIBSSH2_KNOWNHOST_ADDC,
varLstAdd(
varLstAdd(
varLstAdd(
varLstAdd(
varLstAdd(
varLstAdd(
varLstAdd(
varLstNew(), varNewStrZ(host)),
varNewStrZ(salt)),
varNewStrZ(key)),
varNewUInt64(keylen)),
varNewStrZ(comment)),
varNewUInt64(commentlen)),
varNewInt(typemask)),
(HrnLibSsh2 *)hosts);
}
MEM_CONTEXT_TEMP_END();
return hrnLibSsh2->resultInt;
}
/***********************************************************************************************************************************
Shim for libssh2_knownhost_checkp
***********************************************************************************************************************************/
int
libssh2_knownhost_checkp(
LIBSSH2_KNOWNHOSTS *hosts, const char *host, int port, const char *key, size_t keylen, int typemask,
struct libssh2_knownhost **knownhost)
{
// Avoid compiler complaining of unused param
(void)knownhost;
if (hosts == NULL)
{
snprintf(
hrnLibSsh2ScriptError, sizeof(hrnLibSsh2ScriptError),
"libssh2 script function 'libssh2_knownhost_checkp', expects hosts to be not NULL");
THROW(AssertError, hrnLibSsh2ScriptError);
}
HrnLibSsh2 *hrnLibSsh2 = NULL;
MEM_CONTEXT_TEMP_BEGIN()
{
hrnLibSsh2 = hrnLibSsh2ScriptRun(
HRNLIBSSH2_KNOWNHOST_CHECKP,
varLstAdd(
varLstAdd(
varLstAdd(
varLstAdd(
varLstAdd(
varLstNew(), varNewStrZ(host)),
varNewInt(port)),
varNewStrZ(key)),
varNewUInt64(keylen)),
varNewInt(typemask)),
(HrnLibSsh2 *)hosts);
}
MEM_CONTEXT_TEMP_END();
return hrnLibSsh2->resultInt;
}
/***********************************************************************************************************************************
Shim for libssh2_knownhost_free
***********************************************************************************************************************************/
void
libssh2_knownhost_free(LIBSSH2_KNOWNHOSTS *hosts)
{
if (hosts == NULL)
{
snprintf(
hrnLibSsh2ScriptError, sizeof(hrnLibSsh2ScriptError),
"libssh2 script function 'libssh2_session_knownhost_free', expects hosts to be not NULL");
THROW(AssertError, hrnLibSsh2ScriptError);
}
}
/***********************************************************************************************************************************
Shim for libssh2_knownhost_init
***********************************************************************************************************************************/
LIBSSH2_KNOWNHOSTS *
libssh2_knownhost_init(LIBSSH2_SESSION *session)
{
HrnLibSsh2 *hrnLibSsh2 = hrnLibSsh2ScriptRun(HRNLIBSSH2_KNOWNHOST_INIT, NULL, (HrnLibSsh2 *)session);
return hrnLibSsh2->resultNull ? NULL : (LIBSSH2_KNOWNHOSTS *)hrnLibSsh2;
}
/***********************************************************************************************************************************
Shim for libssh2_knownhost_readfile
***********************************************************************************************************************************/
int
libssh2_knownhost_readfile(LIBSSH2_KNOWNHOSTS *hosts, const char *filename, int type)
{
HrnLibSsh2 *hrnLibSsh2 = NULL;
MEM_CONTEXT_TEMP_BEGIN()
{
hrnLibSsh2 = hrnLibSsh2ScriptRun(
HRNLIBSSH2_KNOWNHOST_READFILE,
varLstAdd(
varLstAdd(
varLstNew(), varNewStrZ(filename)),
varNewInt(type)),
(HrnLibSsh2 *)hosts);
}
MEM_CONTEXT_TEMP_END();
return hrnLibSsh2->resultInt;
}
/***********************************************************************************************************************************
Shim for libssh2_knownhost_writefile
***********************************************************************************************************************************/
int
libssh2_knownhost_writefile(LIBSSH2_KNOWNHOSTS *hosts, const char *filename, int type)
{
HrnLibSsh2 *hrnLibSsh2 = NULL;
MEM_CONTEXT_TEMP_BEGIN()
{
hrnLibSsh2 = hrnLibSsh2ScriptRun(
HRNLIBSSH2_KNOWNHOST_WRITEFILE,
varLstAdd(
varLstAdd(
varLstNew(), varNewStrZ(filename)),
varNewInt(type)),
(HrnLibSsh2 *)hosts);
}
MEM_CONTEXT_TEMP_END();
return hrnLibSsh2->resultInt;
}
/***********************************************************************************************************************************
Shim for libssh2_session_hostkey
***********************************************************************************************************************************/
const char *
libssh2_session_hostkey(LIBSSH2_SESSION *session, size_t *len, int *type)
{
HrnLibSsh2 *hrnLibSsh2 = hrnLibSsh2ScriptRun(HRNLIBSSH2_SESSION_HOSTKEY, NULL, (HrnLibSsh2 *)session);
*len = (size_t)hrnLibSsh2->len;
*type = (int)hrnLibSsh2->type;
return hrnLibSsh2->resultNull ? NULL : (const char *)hrnLibSsh2->resultZ;
}
/***********************************************************************************************************************************
Shim for libssh2_session_init
***********************************************************************************************************************************/
@@ -197,28 +371,21 @@ libssh2_session_init_ex(
}
/***********************************************************************************************************************************
Shim for libssh2_session_set_blocking
Shim for libssh2_session_last_error
***********************************************************************************************************************************/
void
libssh2_session_set_blocking(LIBSSH2_SESSION *session, int blocking)
int
libssh2_session_last_error(LIBSSH2_SESSION *session, char **errmsg, int *errmsg_len, int want_buf)
{
if (session == NULL)
{
snprintf(
hrnLibSsh2ScriptError, sizeof(hrnLibSsh2ScriptError),
"libssh2 script function 'libssh2_session_set_blocking', expects session to be not NULL");
THROW(AssertError, hrnLibSsh2ScriptError);
}
// Avoid compiler complaining of unused params
(void)errmsg_len;
(void)want_buf;
if (!(INT_MIN <= blocking && blocking <= INT_MAX))
{
snprintf(
hrnLibSsh2ScriptError, sizeof(hrnLibSsh2ScriptError),
"libssh2 script function 'libssh2_session_set_blocking', expects blocking to be an integer value");
THROW(AssertError, hrnLibSsh2ScriptError);
}
HrnLibSsh2 *hrnLibSsh2 = hrnLibSsh2ScriptRun(HRNLIBSSH2_SESSION_LAST_ERROR, NULL, (HrnLibSsh2 *)session);
return;
if (hrnLibSsh2->errMsg != NULL)
*errmsg = hrnLibSsh2->errMsg;
return hrnLibSsh2->resultInt;
}
/***********************************************************************************************************************************
@@ -268,6 +435,14 @@ libssh2_userauth_publickey_fromfile_ex(
{
HrnLibSsh2 *hrnLibSsh2 = NULL;
if (privatekey == NULL)
{
snprintf(
hrnLibSsh2ScriptError, sizeof(hrnLibSsh2ScriptError),
"libssh2 script function 'libssh2_userauth_publickey_fromfile_ex', expects privatekey to be not NULL");
THROW(AssertError, hrnLibSsh2ScriptError);
}
MEM_CONTEXT_TEMP_BEGIN()
{
hrnLibSsh2 = hrnLibSsh2ScriptRun(

View File

@@ -25,19 +25,31 @@ 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 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"
#define ETC_KNOWNHOSTS2_FILE_CSTR "/etc/ssh/ssh_known_hosts2"
#define HOSTKEY "12345678901234567890"
/***********************************************************************************************************************************
Function constants
***********************************************************************************************************************************/
#define HRNLIBSSH2_HOSTKEY_HASH "libssh2_hostkey_hash"
#define HRNLIBSSH2_INIT "libssh2_init"
#define HRNLIBSSH2_SESSION_DISCONNECT_EX "libssh2_session_disconnect_ex"
#define HRNLIBSSH2_KNOWNHOST_ADDC "libssh2_knownhost_addc"
#define HRNLIBSSH2_KNOWNHOST_CHECKP "libssh2_knownhost_checkp"
#define HRNLIBSSH2_KNOWNHOST_FREE "libssh2_knownhost_free"
#define HRNLIBSSH2_KNOWNHOST_INIT "libssh2_knownhost_init"
#define HRNLIBSSH2_KNOWNHOST_READFILE "libssh2_knownhost_readfile"
#define HRNLIBSSH2_KNOWNHOST_WRITEFILE "libssh2_knownhost_writefile"
#define HRNLIBSSH2_SESSION_BLOCK_DIRECTIONS "libssh2_session_block_directions"
#define HRNLIBSSH2_SESSION_DISCONNECT_EX "libssh2_session_disconnect_ex"
#define HRNLIBSSH2_SESSION_FREE "libssh2_session_free"
#define HRNLIBSSH2_SESSION_HANDSHAKE "libssh2_session_handshake"
#define HRNLIBSSH2_SESSION_HOSTKEY "libssh2_session_hostkey"
#define HRNLIBSSH2_SESSION_INIT_EX "libssh2_session_init_ex"
#define HRNLIBSSH2_SESSION_LAST_ERRNO "libssh2_session_last_errno"
#define HRNLIBSSH2_SESSION_SET_BLOCKING "libssh2_session_set_blocking"
#define HRNLIBSSH2_SESSION_LAST_ERROR "libssh2_session_last_error"
#define HRNLIBSSH2_SFTP_CLOSE_HANDLE "libssh2_sftp_close_handle"
#define HRNLIBSSH2_SFTP_FSYNC "libssh2_sftp_fsync"
#define HRNLIBSSH2_SFTP_INIT "libssh2_sftp_init"
@@ -64,7 +76,11 @@ Macros for defining groups of functions that implement commands
{.function = HRNLIBSSH2_INIT, .param = "[0]", .resultInt = 0}, \
{.function = HRNLIBSSH2_SESSION_INIT_EX, .param = "[null,null,null,null]"}, \
{.function = HRNLIBSSH2_SESSION_HANDSHAKE, .param = HANDSHAKE_PARAM, .resultInt = 0}, \
{.function = HRNLIBSSH2_HOSTKEY_HASH, .param = "[2]", .resultZ = "12345678910123456789"}, \
{.function = HRNLIBSSH2_KNOWNHOST_INIT}, \
{.function = HRNLIBSSH2_KNOWNHOST_READFILE, .param = "[\"" KNOWNHOSTS_FILE_CSTR "\",1]", .resultInt = 5}, \
{.function = HRNLIBSSH2_SESSION_HOSTKEY, .len = 20, .type = LIBSSH2_HOSTKEY_TYPE_RSA, .resultZ = HOSTKEY}, \
{.function = HRNLIBSSH2_KNOWNHOST_CHECKP, .param = "[\"localhost\",22,\"" HOSTKEY "\",20,65537]", \
.resultInt = LIBSSH2_KNOWNHOST_CHECK_MATCH}, \
{.function = HRNLIBSSH2_USERAUTH_PUBLICKEY_FROMFILE_EX, \
.param = "[\"" TEST_USER "\"," TEST_USER_LEN ",\"" KEYPUB_CSTR "\",\"" KEYPRIV_CSTR "\",null]", \
.resultInt = 0}, \
@@ -77,7 +93,7 @@ Macros for defining groups of functions that implement commands
{.function = HRNLIBSSH2_SESSION_FREE, .resultInt = 0}, \
{.function = NULL} \
// older systems do not support LIBSSH2_HOSTKEY_HASH_SHA256
// Older systems do not support LIBSSH2_HOSTKEY_HASH_SHA256
#ifdef LIBSSH2_HOSTKEY_HASH_SHA256
#define HOSTKEY_HASH_ENTRY() \
{.function = HRNLIBSSH2_HOSTKEY_HASH, .param = "[3]", .resultZ = "12345678910123456789"}
@@ -108,6 +124,9 @@ typedef struct HrnLibSsh2
const String *fileName; // libssh2_readdir* libssh2_stat* filename
const String *readBuffer; // what to copy into read buffer
TimeMSec sleep; // Sleep specified milliseconds before returning from function
size_t len; // libssh2_session_hostkey len
int type; // libssh2_session_hostkey type
char *errMsg; // libssh2_session_last_error error msg
} HrnLibSsh2;
/***********************************************************************************************************************************

View File

@@ -319,9 +319,11 @@ testRun(void)
" --repo-s3-uri-style S3 URI Style [default=host]\n"
" --repo-sftp-host SFTP repository host\n"
" --repo-sftp-host-fingerprint SFTP repository host fingerprint\n"
" --repo-sftp-host-key-check-type SFTP host key check type [default=strict]\n"
" --repo-sftp-host-key-hash-type SFTP repository host key hash type\n"
" --repo-sftp-host-port SFTP repository host port [default=22]\n"
" --repo-sftp-host-user SFTP repository host user\n"
" --repo-sftp-known-host SFTP known hosts file\n"
" --repo-sftp-private-key-file SFTP private key file\n"
" --repo-sftp-private-key-passphrase SFTP private key passphrase\n"
" --repo-sftp-public-key-file SFTP public key file\n"

View File

@@ -640,6 +640,102 @@ testRun(void)
hrnCfgEnvKeyRemoveRaw(cfgOptRepoAzureAccount, 1);
hrnCfgEnvKeyRemoveRaw(cfgOptRepoAzureKey, 1);
// -------------------------------------------------------------------------------------------------------------------------
TEST_TITLE("error on missing SFTP fingerprint");
argList = strLstNew();
hrnCfgArgRawZ(argList, cfgOptStanza, "db");
hrnCfgArgRawZ(argList, cfgOptPgPath, "/path/to/pg");
hrnCfgArgKeyRawZ(argList, cfgOptRepoType, 1, "sftp");
hrnCfgArgKeyRawZ(argList, cfgOptRepoPath, 1, "/repo");
hrnCfgArgRawZ(argList, cfgOptRepoSftpHost, "localhost");
hrnCfgArgRawZ(argList, cfgOptRepoSftpHostKeyHashType, "sha1");
hrnCfgArgRawZ(argList, cfgOptRepoSftpHostUser, TEST_USER);
hrnCfgArgRawZ(argList, cfgOptRepoSftpPrivateKeyFile, "/privatekey");
hrnCfgArgRawZ(argList, cfgOptRepoSftpHostKeyCheckType, "fingerprint");
TEST_ERROR(
hrnCfgLoadP(cfgCmdArchivePush, argList), OptionRequiredError,
"archive-push command requires option: repo1-sftp-host-fingerprint");
// -------------------------------------------------------------------------------------------------------------------------
TEST_TITLE("success on SFTP fingerprint not needed");
argList = strLstNew();
hrnCfgArgRawZ(argList, cfgOptStanza, "db");
hrnCfgArgRawZ(argList, cfgOptPgPath, "/path/to/pg");
hrnCfgArgKeyRawZ(argList, cfgOptRepoType, 1, "sftp");
hrnCfgArgKeyRawZ(argList, cfgOptRepoPath, 1, "/repo");
hrnCfgArgRawZ(argList, cfgOptRepoSftpHost, "localhost");
hrnCfgArgRawZ(argList, cfgOptRepoSftpHostKeyHashType, "sha1");
hrnCfgArgRawZ(argList, cfgOptRepoSftpHostUser, TEST_USER);
hrnCfgArgRawZ(argList, cfgOptRepoSftpPrivateKeyFile, "/privatekey");
hrnCfgArgRawZ(argList, cfgOptRepoSftpHostKeyCheckType, "accept-new");
HRN_CFG_LOAD(cfgCmdArchivePush, argList);
// -------------------------------------------------------------------------------------------------------------------------
TEST_TITLE("error on incorrect SFTP fingerprint check type");
argList = strLstNew();
hrnCfgArgRawZ(argList, cfgOptStanza, "db");
hrnCfgArgRawZ(argList, cfgOptPgPath, "/path/to/pg");
hrnCfgArgKeyRawZ(argList, cfgOptRepoType, 1, "sftp");
hrnCfgArgKeyRawZ(argList, cfgOptRepoPath, 1, "/repo");
hrnCfgArgRawZ(argList, cfgOptRepoSftpHost, "localhost");
hrnCfgArgRawZ(argList, cfgOptRepoSftpHostKeyHashType, "sha1");
hrnCfgArgRawZ(argList, cfgOptRepoSftpHostUser, TEST_USER);
hrnCfgArgRawZ(argList, cfgOptRepoSftpPrivateKeyFile, "/privatekey");
hrnCfgArgRawZ(argList, cfgOptRepoSftpHostFingerprint, "xxx");
hrnCfgArgRawZ(argList, cfgOptRepoSftpHostKeyCheckType, "strict");
TEST_ERROR(
hrnCfgLoadP(cfgCmdArchivePush, argList), OptionInvalidError,
"option 'repo1-sftp-host-fingerprint' not valid without option 'repo1-sftp-host-key-check-type' = 'fingerprint'");
// -------------------------------------------------------------------------------------------------------------------------
TEST_TITLE("warn on default SFTP fingerprint check type");
argList = strLstNew();
hrnCfgArgRawZ(argList, cfgOptStanza, "db");
hrnCfgArgRawZ(argList, cfgOptPgPath, "/path/to/pg");
hrnCfgArgKeyRawZ(argList, cfgOptRepoType, 1, "sftp");
hrnCfgArgKeyRawZ(argList, cfgOptRepoPath, 1, "/repo");
hrnCfgArgRawZ(argList, cfgOptRepoSftpHost, "localhost");
hrnCfgArgRawZ(argList, cfgOptRepoSftpHostKeyHashType, "sha1");
hrnCfgArgRawZ(argList, cfgOptRepoSftpHostUser, TEST_USER);
hrnCfgArgRawZ(argList, cfgOptRepoSftpPrivateKeyFile, "/privatekey");
hrnCfgArgRawZ(argList, cfgOptRepoSftpHostFingerprint, "xxx");
HRN_CFG_LOAD(cfgCmdArchivePush, argList);
TEST_RESULT_LOG(
"P00 WARN: option 'repo1-sftp-host-fingerprint' without option 'repo1-sftp-host-key-check-type' = 'fingerprint' is"
" deprecated\n"
" HINT: set option 'repo1-sftp-host-key-check-type=fingerprint'");
TEST_RESULT_UINT(
cfgOptionIdxStrId(cfgOptRepoSftpHostKeyCheckType, 0), CFGOPTVAL_REPO_SFTP_HOST_KEY_CHECK_TYPE_FINGERPRINT,
"check type updated");
// -------------------------------------------------------------------------------------------------------------------------
TEST_TITLE("success on correct SFTP fingerprint check type");
argList = strLstNew();
hrnCfgArgRawZ(argList, cfgOptStanza, "db");
hrnCfgArgRawZ(argList, cfgOptPgPath, "/path/to/pg");
hrnCfgArgKeyRawZ(argList, cfgOptRepoType, 1, "sftp");
hrnCfgArgKeyRawZ(argList, cfgOptRepoPath, 1, "/repo");
hrnCfgArgRawZ(argList, cfgOptRepoSftpHost, "localhost");
hrnCfgArgRawZ(argList, cfgOptRepoSftpHostKeyHashType, "sha1");
hrnCfgArgRawZ(argList, cfgOptRepoSftpHostUser, TEST_USER);
hrnCfgArgRawZ(argList, cfgOptRepoSftpPrivateKeyFile, "/privatekey");
hrnCfgArgRawZ(argList, cfgOptRepoSftpHostFingerprint, "xxx");
hrnCfgArgRawZ(argList, cfgOptRepoSftpHostKeyCheckType, "fingerprint");
HRN_CFG_LOAD(cfgCmdArchivePush, argList);
TEST_RESULT_UINT(
cfgOptionIdxStrId(cfgOptRepoSftpHostKeyCheckType, 0), CFGOPTVAL_REPO_SFTP_HOST_KEY_CHECK_TYPE_FINGERPRINT,
"check type");
}
// *****************************************************************************************************************************

File diff suppressed because it is too large Load Diff