diff --git a/bindings/python/tests/fdbcli_tests.py b/bindings/python/tests/fdbcli_tests.py index 552bba8f49..0fbb0adfa4 100755 --- a/bindings/python/tests/fdbcli_tests.py +++ b/bindings/python/tests/fdbcli_tests.py @@ -7,6 +7,7 @@ import subprocess import logging import functools import json +import tempfile import time import random from argparse import ArgumentParser, RawDescriptionHelpFormatter @@ -770,6 +771,68 @@ def integer_options(): assert lines[1].startswith('Committed') assert error_output == b'' +def tls_address_suffix(): + # fdbcli shall prevent a non-TLS fdbcli run from connecting to an all-TLS cluster, and vice versa + preamble = 'eNW1yf1M:eNW1yf1M@' + def make_addr(port: int, tls: bool = False): + return "127.0.0.1:{}{}".format(port, ":tls" if tls else "") + testcases = [ + # IsServerTLS, NumServerAddrs + (True, 1), + (False, 1), + (True, 3), + (False, 3), + ] + err_output_server_no_tls = "ERROR: fdbcli is configured with TLS, but none of the coordinators have TLS addresses." + err_output_server_tls = "ERROR: fdbcli is not configured with TLS, but all of the coordinators have TLS addresses." + + # technically the contents of the certs and key files are not evaluated + # before tls-suffix check against tls configuration takes place, + # but we generate the certs and keys anyway to avoid + # imposing nuanced TLSConfig evaluation ordering requirement on the testcase + with tempfile.TemporaryDirectory() as tmpdir: + cert_file = tmpdir + "/client-cert.pem" + key_file = tmpdir + "/client-key.pem" + ca_file = tmpdir + "/server-ca.pem" + mkcert_process = subprocess.run([ + args.build_dir + "/bin/mkcert", + "--server-chain-length", "1", + "--client-chain-length", "1", + "--server-cert-file", tmpdir + "/server-cert.pem", + "--client-cert-file", tmpdir + "/client-cert.pem", + "--server-key-file", tmpdir + "/server-key.pem", + "--client-key-file", tmpdir + "/client-key.pem", + "--server-ca-file", tmpdir + "/server-ca.pem", + "--client-ca-file", tmpdir + "/client-ca.pem", + ], + capture_output=True) + if mkcert_process.returncode != 0: + print("mkcert returned with code {}".format(mkcert_process.returncode)) + print("Output:\n{}{}\n".format( + mkcert_process.stdout.decode("utf8").strip(), + mkcert_process.stderr.decode("utf8").strip())) + assert False + cluster_fn = tmpdir + "/fdb.cluster" + for testcase in testcases: + is_server_tls, num_server_addrs = testcase + with open(cluster_fn, "w") as fp: + fp.write(preamble + ",".join( + [make_addr(port=4000 + addr_idx, tls=is_server_tls) for addr_idx in range(num_server_addrs)])) + fp.close() + tls_args = ["--tls-certificate-file", + cert_file, + "--tls-key-file", + key_file, + "--tls-ca-file", + ca_file] if not is_server_tls else [] + fdbcli_process = subprocess.run(command_template[:2] + [cluster_fn] + tls_args, capture_output=True) + assert fdbcli_process.returncode != 0 + err_out = fdbcli_process.stderr.decode("utf8").strip() + if is_server_tls: + assert err_out == err_output_server_tls, f"unexpected output: {err_out}" + else: + assert err_out == err_output_server_no_tls, f"unexpected output: {err_out}" + if __name__ == '__main__': parser = ArgumentParser(formatter_class=RawDescriptionHelpFormatter, description=""" @@ -816,6 +879,7 @@ if __name__ == '__main__': tenants() versionepoch() integer_options() + tls_address_suffix() else: assert args.process_number > 1, "Process number should be positive" coordinators() diff --git a/flow/MkCertCli.cpp b/flow/MkCertCli.cpp index 7517e2a203..c809bf8eb5 100644 --- a/flow/MkCertCli.cpp +++ b/flow/MkCertCli.cpp @@ -49,6 +49,7 @@ enum EMkCertOpt : int { OPT_PRINT_SERVER_CERT, OPT_PRINT_CLIENT_CERT, OPT_PRINT_ARGUMENTS, + OPT_ENABLE_TRACE, }; CSimpleOpt::SOption gOptions[] = { { OPT_HELP, "--help", SO_NONE }, @@ -68,6 +69,7 @@ CSimpleOpt::SOption gOptions[] = { { OPT_HELP, "--help", SO_NONE }, { OPT_PRINT_SERVER_CERT, "--print-server-cert", SO_NONE }, { OPT_PRINT_CLIENT_CERT, "--print-client-cert", SO_NONE }, { OPT_PRINT_ARGUMENTS, "--print-args", SO_NONE }, + { OPT_ENABLE_TRACE, "--trace", SO_NONE }, SO_END_OF_OPTIONS }; template @@ -191,6 +193,7 @@ int main(int argc, char** argv) { auto printServerCert = false; auto printClientCert = false; auto printArguments = false; + auto enableTrace = false; auto args = CSimpleOpt(argc, argv, gOptions, SO_O_EXACT | SO_O_HYPHEN_TO_UNDERSCORE); while (args.Next()) { if (auto err = args.LastError()) { @@ -266,6 +269,8 @@ int main(int argc, char** argv) { case OPT_PRINT_ARGUMENTS: printArguments = true; break; + case OPT_ENABLE_TRACE: + enableTrace = true; default: fmt::print(stderr, "ERROR: Unknown option {}\n", args.OptionText()); return FDB_EXIT_ERROR; @@ -277,12 +282,14 @@ int main(int argc, char** argv) { platformInit(); Error::init(); g_network = newNet2(TLSConfig()); - openTraceFile(NetworkAddress(), 10 << 20, 10 << 20, ".", "mkcert"); + if (enableTrace) + openTraceFile(NetworkAddress(), 10 << 20, 10 << 20, ".", "mkcert"); auto thread = std::thread([]() { g_network->run(); }); - auto cleanUpGuard = ScopeExit([&thread]() { + auto cleanUpGuard = ScopeExit([&thread, enableTrace]() { g_network->stop(); thread.join(); - closeTraceFile(); + if (enableTrace) + closeTraceFile(); }); serverArgs.transformPathToAbs();