blob: 88e97746397e48313b5117a52db71529a8a4fecb [file]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#include <catch.hpp>
#include <algorithm>
#include <cstdlib>
#include <memory>
#include <string>
#include "Session.h"
#include "SessionBuilder.h"
#include "SessionC.h"
#include "SessionDataSet.h"
#include "SessionImpl.h"
#include "SessionPool.h"
#include "TableSessionBuilder.h"
class SessionTestAccessor {
public:
static std::vector<TEndPoint> availableNodes(Session& session) {
return session.impl_->nodesSupplier_->getEndPointList();
}
static bool hasConnectionTo(Session& session, const TEndPoint& endpoint) {
return session.impl_->endPointToSessionConnection.find(endpoint) !=
session.impl_->endPointToSessionConnection.end();
}
static void makeInitialConnectionUnavailable(Session& session,
const TEndPoint& bootstrapEndpoint) {
auto initialConnection = session.impl_->defaultSessionConnection_;
initialConnection->close();
session.impl_->endPointToSessionConnection.clear();
session.impl_->endPointToSessionConnection.emplace(bootstrapEndpoint, initialConnection);
}
};
namespace {
std::string fixture(const std::string& name) {
return std::string(IOTDB_TEST_FIXTURES_DIR) + "/tls/" + name;
}
bool mutualTlsEnabled() {
const char* value = std::getenv("IOTDB_CPP_SSL_MUTUAL_AUTH");
return value != nullptr && std::string(value) == "1";
}
template <typename Builder> void configureTls(Builder& builder) {
builder.useSSL(true)->trustCertFilePath(fixture("ca.crt"));
if (mutualTlsEnabled()) {
builder.clientCertificateFilePath(fixture("client.crt"))
->clientPrivateKeyFilePath(fixture("client.key"));
}
}
SslConfig sslConfig() {
SslConfig config;
config.useSsl = true;
config.trustCertFilePath = fixture("ca.crt");
if (mutualTlsEnabled()) {
config.clientCertificateFilePath = fixture("client.crt");
config.clientPrivateKeyFilePath = fixture("client.key");
}
return config;
}
void requireDataSet(std::unique_ptr<SessionDataSet> dataSet) {
REQUIRE(dataSet != nullptr);
REQUIRE(dataSet->hasNext());
REQUIRE(dataSet->next() != nullptr);
dataSet->closeOperationHandle();
}
void requireCDataSet(CSessionDataSet* dataSet) {
REQUIRE(dataSet != nullptr);
REQUIRE(ts_dataset_has_next(dataSet));
CRowRecord* row = ts_dataset_next(dataSet);
REQUIRE(row != nullptr);
ts_row_record_destroy(row);
ts_dataset_destroy(dataSet);
}
} // namespace
TEST_CASE("C++ APIs communicate with a TLS-enabled IoTDB", "[tls]") {
SessionBuilder treeBuilder;
treeBuilder.host("127.0.0.1")->rpcPort(6667)->username("root")->password("root");
configureTls(treeBuilder);
auto treeSession = treeBuilder.build();
requireDataSet(treeSession->executeQueryStatement("SHOW VERSION"));
treeSession->close();
TableSessionBuilder tableBuilder;
tableBuilder.host("127.0.0.1")->rpcPort(6667)->username("root")->password("root");
configureTls(tableBuilder);
auto tableSession = tableBuilder.build();
requireDataSet(tableSession->executeQueryStatement("SHOW VERSION"));
tableSession->close();
SessionPoolBuilder poolBuilder;
poolBuilder.host("127.0.0.1")->rpcPort(6667)->username("root")->password("root");
configureTls(poolBuilder);
auto pool = poolBuilder.build();
auto pooledDataSet = pool->executeQueryStatement("SHOW VERSION");
REQUIRE(pooledDataSet->hasNext());
REQUIRE(pooledDataSet->next() != nullptr);
pool->close();
}
TEST_CASE("C APIs communicate with a TLS-enabled IoTDB", "[tls]") {
const std::string certificatePath = fixture("client.crt");
const std::string privateKeyPath = fixture("client.key");
const char* cert = mutualTlsEnabled() ? certificatePath.c_str() : nullptr;
const char* key = mutualTlsEnabled() ? privateKeyPath.c_str() : nullptr;
CSession* treeSession = ts_session_new("127.0.0.1", 6667, "root", "root");
REQUIRE(treeSession != nullptr);
REQUIRE(ts_session_set_ssl_config(treeSession, fixture("ca.crt").c_str(), cert, key) == TS_OK);
REQUIRE(ts_session_open(treeSession) == TS_OK);
CSessionDataSet* treeDataSet = nullptr;
REQUIRE(ts_session_execute_query(treeSession, "SHOW VERSION", &treeDataSet) == TS_OK);
requireCDataSet(treeDataSet);
REQUIRE(ts_session_close(treeSession) == TS_OK);
ts_session_destroy(treeSession);
CTableSession* tableSession = ts_table_session_new_with_ssl("127.0.0.1", 6667, "root", "root", "",
fixture("ca.crt").c_str(), cert, key);
REQUIRE(tableSession != nullptr);
CSessionDataSet* tableDataSet = nullptr;
REQUIRE(ts_table_session_execute_query(tableSession, "SHOW VERSION", &tableDataSet) == TS_OK);
requireCDataSet(tableDataSet);
REQUIRE(ts_table_session_close(tableSession) == TS_OK);
ts_table_session_destroy(tableSession);
}
TEST_CASE("TLS node discovery uses the final SSL configuration and supports failover", "[tls]") {
std::vector<std::string> bootstrapNodes = {"127.0.0.1:6667"};
Session session(bootstrapNodes, "root", "root");
session.setSslConfig(sslConfig());
session.open();
auto discoveredNodes = SessionTestAccessor::availableNodes(session);
auto discovered =
std::find_if(discoveredNodes.begin(), discoveredNodes.end(), [](const TEndPoint& node) {
return node.ip == "localhost" && node.port == 6667;
});
REQUIRE(discovered != discoveredNodes.end());
TEndPoint bootstrapEndpoint;
bootstrapEndpoint.ip = "127.0.0.1";
bootstrapEndpoint.port = 6667;
REQUIRE(std::none_of(discoveredNodes.begin(), discoveredNodes.end(),
[&bootstrapEndpoint](const TEndPoint& node) {
return node.ip == bootstrapEndpoint.ip &&
node.port == bootstrapEndpoint.port;
}));
// Model an unavailable initial bootstrap after discovery. The query must
// establish a new TLS connection to the endpoint learned from the server.
SessionTestAccessor::makeInitialConnectionUnavailable(session, bootstrapEndpoint);
REQUIRE_FALSE(SessionTestAccessor::hasConnectionTo(session, *discovered));
requireDataSet(session.executeQueryStatement("SHOW VERSION"));
REQUIRE(SessionTestAccessor::hasConnectionTo(session, *discovered));
session.close();
}
TEST_CASE("mTLS server rejects a client without a certificate", "[mtls]") {
SessionBuilder builder;
builder.host("127.0.0.1")
->rpcPort(6667)
->username("root")
->password("root")
->useSSL(true)
->trustCertFilePath(fixture("ca.crt"));
REQUIRE_THROWS(builder.build());
}