mirror of
https://github.com/XRPLF/rippled.git
synced 2026-04-20 19:02:25 +00:00
Compare commits
2 Commits
develop
...
ximinez/lo
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7c248f3fe6 | ||
|
|
669617af99 |
@@ -1416,12 +1416,6 @@
|
|||||||
# in this section to a comma-separated list of the addresses
|
# in this section to a comma-separated list of the addresses
|
||||||
# of your Clio servers, in order to bypass xrpld's rate limiting.
|
# of your Clio servers, in order to bypass xrpld's rate limiting.
|
||||||
#
|
#
|
||||||
# TLS/SSL can be enabled for gRPC by specifying ssl_cert and ssl_key.
|
|
||||||
# Both parameters must be provided together. The ssl_cert_chain parameter
|
|
||||||
# is optional and provides intermediate CA certificates for the certificate
|
|
||||||
# chain. The ssl_client_ca parameter is optional and enables mutual TLS
|
|
||||||
# (client certificate verification).
|
|
||||||
#
|
|
||||||
# This port is commented out but can be enabled by removing
|
# This port is commented out but can be enabled by removing
|
||||||
# the '#' from each corresponding line including the entry under [server]
|
# the '#' from each corresponding line including the entry under [server]
|
||||||
#
|
#
|
||||||
@@ -1471,74 +1465,11 @@ admin = 127.0.0.1
|
|||||||
protocol = ws
|
protocol = ws
|
||||||
send_queue_limit = 500
|
send_queue_limit = 500
|
||||||
|
|
||||||
# gRPC TLS/SSL Configuration
|
|
||||||
#
|
|
||||||
# The gRPC port supports optional TLS/SSL encryption. When TLS is not
|
|
||||||
# configured, the gRPC server will accept unencrypted connections.
|
|
||||||
#
|
|
||||||
# ssl_cert = <filename>
|
|
||||||
# ssl_key = <filename>
|
|
||||||
#
|
|
||||||
# To enable TLS for gRPC, both ssl_cert and ssl_key must be specified.
|
|
||||||
# If only one is provided, xrpld will fail to start.
|
|
||||||
#
|
|
||||||
# ssl_cert: Path to the server's SSL certificate file in PEM format.
|
|
||||||
# ssl_key: Path to the server's SSL private key file in PEM format.
|
|
||||||
#
|
|
||||||
# When configured, the gRPC server will only accept TLS-encrypted
|
|
||||||
# connections. Clients must use TLS (secure) channel credentials rather
|
|
||||||
# than plaintext / insecure connections.
|
|
||||||
#
|
|
||||||
# ssl_cert_chain = <filename>
|
|
||||||
#
|
|
||||||
# Optional. Path to intermediate CA certificate(s) in PEM format that
|
|
||||||
# complete the server's certificate chain.
|
|
||||||
#
|
|
||||||
# This file should contain the intermediate CA certificate(s) needed
|
|
||||||
# to build a trust chain from the server certificate (ssl_cert) to a
|
|
||||||
# root CA that clients trust. Multiple certificates should be
|
|
||||||
# concatenated in PEM format.
|
|
||||||
#
|
|
||||||
# This is needed when your server certificate was signed by an
|
|
||||||
# intermediate CA rather than directly by a root CA. Without this,
|
|
||||||
# clients may fail to verify your server certificate.
|
|
||||||
#
|
|
||||||
# If not specified, only the server certificate from ssl_cert will be
|
|
||||||
# presented to clients.
|
|
||||||
#
|
|
||||||
# ssl_client_ca = <filename>
|
|
||||||
#
|
|
||||||
# Optional. Path to a CA certificate file in PEM format for verifying
|
|
||||||
# client certificates (mutual TLS / mTLS).
|
|
||||||
#
|
|
||||||
# When specified, the gRPC server will verify client certificates
|
|
||||||
# against this CA. This enables mutual authentication where both the
|
|
||||||
# server and client verify each other's identity.
|
|
||||||
#
|
|
||||||
# This is typically NOT needed for public-facing gRPC servers. Only
|
|
||||||
# use this if you want to restrict access to clients with valid
|
|
||||||
# certificates signed by the specified CA.
|
|
||||||
#
|
|
||||||
# If not specified, the server will use one-way TLS (server
|
|
||||||
# authentication only) and will accept connections from any client.
|
|
||||||
#
|
|
||||||
[port_grpc]
|
[port_grpc]
|
||||||
port = 50051
|
port = 50051
|
||||||
ip = 127.0.0.1
|
ip = 127.0.0.1
|
||||||
secure_gateway = 127.0.0.1
|
secure_gateway = 127.0.0.1
|
||||||
|
|
||||||
# Optional TLS/SSL configuration for gRPC
|
|
||||||
# To enable TLS, uncomment and configure both ssl_cert and ssl_key:
|
|
||||||
#ssl_cert = /etc/ssl/certs/grpc-server.crt
|
|
||||||
#ssl_key = /etc/ssl/private/grpc-server.key
|
|
||||||
|
|
||||||
# Optional: Include intermediate CA certificates for complete certificate chain
|
|
||||||
#ssl_cert_chain = /etc/ssl/certs/grpc-intermediate-ca.crt
|
|
||||||
|
|
||||||
# Optional: Enable mutual TLS (client certificate verification)
|
|
||||||
# Uncomment to require and verify client certificates:
|
|
||||||
#ssl_client_ca = /etc/ssl/certs/grpc-client-ca.crt
|
|
||||||
|
|
||||||
#[port_ws_public]
|
#[port_ws_public]
|
||||||
#port = 6005
|
#port = 6005
|
||||||
#ip = 127.0.0.1
|
#ip = 127.0.0.1
|
||||||
|
|||||||
@@ -11,8 +11,7 @@ namespace xrpl {
|
|||||||
class ValidPermissionedDEX
|
class ValidPermissionedDEX
|
||||||
{
|
{
|
||||||
bool regularOffers_ = false;
|
bool regularOffers_ = false;
|
||||||
bool badHybridsOld_ = false; // pre-fixSecurity3_1_3: missing field/domain or size > 1
|
bool badHybrids_ = false;
|
||||||
bool badHybrids_ = false; // post-fixSecurity3_1_3: also catches size == 0 (size != 1)
|
|
||||||
hash_set<uint256> domains_;
|
hash_set<uint256> domains_;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|||||||
@@ -6,7 +6,6 @@
|
|||||||
#include <xrpl/ledger/ReadView.h>
|
#include <xrpl/ledger/ReadView.h>
|
||||||
#include <xrpl/ledger/helpers/CredentialHelpers.h>
|
#include <xrpl/ledger/helpers/CredentialHelpers.h>
|
||||||
#include <xrpl/protocol/AccountID.h>
|
#include <xrpl/protocol/AccountID.h>
|
||||||
#include <xrpl/protocol/Feature.h>
|
|
||||||
#include <xrpl/protocol/Indexes.h>
|
#include <xrpl/protocol/Indexes.h>
|
||||||
#include <xrpl/protocol/LedgerFormats.h>
|
#include <xrpl/protocol/LedgerFormats.h>
|
||||||
#include <xrpl/protocol/SField.h>
|
#include <xrpl/protocol/SField.h>
|
||||||
@@ -62,26 +61,10 @@ offerInDomain(
|
|||||||
if (sleOffer->getFieldH256(sfDomainID) != domainID)
|
if (sleOffer->getFieldH256(sfDomainID) != domainID)
|
||||||
return false; // LCOV_EXCL_LINE
|
return false; // LCOV_EXCL_LINE
|
||||||
|
|
||||||
if (view.rules().enabled(fixSecurity3_1_3))
|
if (sleOffer->isFlag(lsfHybrid) && !sleOffer->isFieldPresent(sfAdditionalBooks))
|
||||||
{
|
{
|
||||||
// post-fixSecurity3_1_3: also catches empty sfAdditionalBooks (size == 0)
|
JLOG(j.error()) << "Hybrid offer " << offerID << " missing AdditionalBooks field";
|
||||||
if (sleOffer->isFlag(lsfHybrid) &&
|
return false; // LCOV_EXCL_LINE
|
||||||
(!sleOffer->isFieldPresent(sfAdditionalBooks) ||
|
|
||||||
sleOffer->getFieldArray(sfAdditionalBooks).size() != 1))
|
|
||||||
{
|
|
||||||
JLOG(j.error()) << "Hybrid offer " << offerID
|
|
||||||
<< " missing or malformed AdditionalBooks field";
|
|
||||||
return false; // LCOV_EXCL_LINE
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// pre-fixSecurity3_1_3: only check for missing sfAdditionalBooks
|
|
||||||
if (sleOffer->isFlag(lsfHybrid) && !sleOffer->isFieldPresent(sfAdditionalBooks))
|
|
||||||
{
|
|
||||||
JLOG(j.error()) << "Hybrid offer " << offerID << " missing AdditionalBooks field";
|
|
||||||
return false; // LCOV_EXCL_LINE
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return accountInDomain(view, sleOffer->getAccountID(sfAccount), domainID);
|
return accountInDomain(view, sleOffer->getAccountID(sfAccount), domainID);
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
#include <xrpl/basics/Log.h>
|
#include <xrpl/basics/Log.h>
|
||||||
#include <xrpl/beast/utility/Journal.h>
|
#include <xrpl/beast/utility/Journal.h>
|
||||||
#include <xrpl/ledger/ReadView.h>
|
#include <xrpl/ledger/ReadView.h>
|
||||||
#include <xrpl/protocol/Feature.h>
|
|
||||||
#include <xrpl/protocol/Indexes.h>
|
#include <xrpl/protocol/Indexes.h>
|
||||||
#include <xrpl/protocol/LedgerFormats.h>
|
#include <xrpl/protocol/LedgerFormats.h>
|
||||||
#include <xrpl/protocol/SField.h>
|
#include <xrpl/protocol/SField.h>
|
||||||
@@ -41,17 +40,11 @@ ValidPermissionedDEX::visitEntry(
|
|||||||
regularOffers_ = true;
|
regularOffers_ = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// pre-fixSecurity3_1_3: hybrid offer missing domain, missing
|
// if a hybrid offer is missing domain or additional book, there's
|
||||||
// sfAdditionalBooks, or sfAdditionalBooks has more than one entry
|
// something wrong
|
||||||
if (after->isFlag(lsfHybrid) &&
|
if (after->isFlag(lsfHybrid) &&
|
||||||
(!after->isFieldPresent(sfDomainID) || !after->isFieldPresent(sfAdditionalBooks) ||
|
(!after->isFieldPresent(sfDomainID) || !after->isFieldPresent(sfAdditionalBooks) ||
|
||||||
after->getFieldArray(sfAdditionalBooks).size() > 1))
|
after->getFieldArray(sfAdditionalBooks).size() > 1))
|
||||||
badHybridsOld_ = true;
|
|
||||||
|
|
||||||
// post-fixSecurity3_1_3: same as above but also catches size == 0
|
|
||||||
if (after->isFlag(lsfHybrid) &&
|
|
||||||
(!after->isFieldPresent(sfDomainID) || !after->isFieldPresent(sfAdditionalBooks) ||
|
|
||||||
after->getFieldArray(sfAdditionalBooks).size() != 1))
|
|
||||||
badHybrids_ = true;
|
badHybrids_ = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -70,8 +63,7 @@ ValidPermissionedDEX::finalize(
|
|||||||
|
|
||||||
// For each offercreate transaction, check if
|
// For each offercreate transaction, check if
|
||||||
// permissioned offers are valid
|
// permissioned offers are valid
|
||||||
bool const isMalformed = view.rules().enabled(fixSecurity3_1_3) ? badHybrids_ : badHybridsOld_;
|
if (txType == ttOFFER_CREATE && badHybrids_)
|
||||||
if (txType == ttOFFER_CREATE && isMalformed)
|
|
||||||
{
|
{
|
||||||
JLOG(j.fatal()) << "Invariant failed: hybrid offer is malformed";
|
JLOG(j.fatal()) << "Invariant failed: hybrid offer is malformed";
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -594,7 +594,7 @@ OfferCreate::applyGuts(Sandbox& sb, Sandbox& sbCancel)
|
|||||||
|
|
||||||
auto const cancelSequence = ctx_.tx[~sfOfferSequence];
|
auto const cancelSequence = ctx_.tx[~sfOfferSequence];
|
||||||
|
|
||||||
// Note that we use the value from the sequence or ticket as the
|
// Note that we we use the value from the sequence or ticket as the
|
||||||
// offer sequence. For more explanation see comments in SeqProxy.h.
|
// offer sequence. For more explanation see comments in SeqProxy.h.
|
||||||
auto const offerSequence = ctx_.tx.getSeqValue();
|
auto const offerSequence = ctx_.tx.getSeqValue();
|
||||||
|
|
||||||
|
|||||||
@@ -439,7 +439,7 @@ EscrowCreate::doApply()
|
|||||||
return tecDST_TAG_NEEDED;
|
return tecDST_TAG_NEEDED;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create escrow in ledger. Note that we use the value from the
|
// Create escrow in ledger. Note that we we use the value from the
|
||||||
// sequence or ticket. For more explanation see comments in SeqProxy.h.
|
// sequence or ticket. For more explanation see comments in SeqProxy.h.
|
||||||
Keylet const escrowKeylet = keylet::escrow(account_, ctx_.tx.getSeqValue());
|
Keylet const escrowKeylet = keylet::escrow(account_, ctx_.tx.getSeqValue());
|
||||||
auto const slep = std::make_shared<SLE>(escrowKeylet);
|
auto const slep = std::make_shared<SLE>(escrowKeylet);
|
||||||
|
|||||||
@@ -132,6 +132,9 @@ LoanPay::calculateBaseFee(ReadView const& view, STTx const& tx)
|
|||||||
|
|
||||||
auto const regularPayment = roundPeriodicPayment(asset, loanSle->at(sfPeriodicPayment), scale) +
|
auto const regularPayment = roundPeriodicPayment(asset, loanSle->at(sfPeriodicPayment), scale) +
|
||||||
loanSle->at(sfLoanServiceFee);
|
loanSle->at(sfLoanServiceFee);
|
||||||
|
if (view.rules().enabled(fixSecurity3_1_3) &&
|
||||||
|
amount >= regularPayment * loanMaximumPaymentsPerTransaction)
|
||||||
|
return loanMaximumPaymentsPerTransaction * normalCost;
|
||||||
|
|
||||||
// If making an overpayment, count it as a full payment because it will do
|
// If making an overpayment, count it as a full payment because it will do
|
||||||
// about the same amount of work, if not more.
|
// about the same amount of work, if not more.
|
||||||
|
|||||||
@@ -134,7 +134,7 @@ PaymentChannelCreate::doApply()
|
|||||||
|
|
||||||
// Create PayChan in ledger.
|
// Create PayChan in ledger.
|
||||||
//
|
//
|
||||||
// Note that we use the value from the sequence or ticket as the
|
// Note that we we use the value from the sequence or ticket as the
|
||||||
// payChan sequence. For more explanation see comments in SeqProxy.h.
|
// payChan sequence. For more explanation see comments in SeqProxy.h.
|
||||||
Keylet const payChanKeylet = keylet::payChan(account, dst, ctx_.tx.getSeqValue());
|
Keylet const payChanKeylet = keylet::payChan(account, dst, ctx_.tx.getSeqValue());
|
||||||
auto const slep = std::make_shared<SLE>(payChanKeylet);
|
auto const slep = std::make_shared<SLE>(payChanKeylet);
|
||||||
|
|||||||
@@ -1,851 +0,0 @@
|
|||||||
#include <test/jtx/Env.h>
|
|
||||||
#include <test/jtx/envconfig.h>
|
|
||||||
|
|
||||||
#include <xrpld/core/ConfigSections.h>
|
|
||||||
|
|
||||||
#include <xrpl/beast/unit_test/suite.h>
|
|
||||||
#include <xrpl/proto/org/xrpl/rpc/v1/get_ledger.pb.h>
|
|
||||||
#include <xrpl/proto/org/xrpl/rpc/v1/xrp_ledger.grpc.pb.h>
|
|
||||||
|
|
||||||
#include <boost/filesystem/operations.hpp>
|
|
||||||
|
|
||||||
#include <grpcpp/client_context.h>
|
|
||||||
#include <grpcpp/create_channel.h>
|
|
||||||
#include <grpcpp/grpcpp.h>
|
|
||||||
#include <grpcpp/security/credentials.h>
|
|
||||||
#include <grpcpp/support/status.h>
|
|
||||||
|
|
||||||
#include <chrono>
|
|
||||||
#include <filesystem>
|
|
||||||
#include <fstream>
|
|
||||||
#include <memory>
|
|
||||||
#include <stdexcept>
|
|
||||||
#include <string>
|
|
||||||
#include <string_view>
|
|
||||||
#include <system_error>
|
|
||||||
#include <utility>
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
|
|
||||||
constexpr std::string_view kCA_CERT_CONTENT =
|
|
||||||
"-----BEGIN CERTIFICATE-----\n"
|
|
||||||
"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\n"
|
|
||||||
"fAbAYqu0rfMFHUYjzIVnu8WRCC56qYHO5tU=\n"
|
|
||||||
"-----END CERTIFICATE-----\n";
|
|
||||||
|
|
||||||
constexpr std::string_view kSERVER_CERT_CONTENT =
|
|
||||||
"-----BEGIN CERTIFICATE-----\n"
|
|
||||||
"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\n"
|
|
||||||
"-----END CERTIFICATE-----\n";
|
|
||||||
|
|
||||||
constexpr std::string_view kSERVER_KEY_CONTENT =
|
|
||||||
"-----BEGIN PRIVATE KEY-----\n"
|
|
||||||
"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\n"
|
|
||||||
"S/RYUSUkZ4VvqFUfo7wT8x18urb87w==\n"
|
|
||||||
"-----END PRIVATE KEY-----\n";
|
|
||||||
|
|
||||||
constexpr std::string_view kCLIENT_CERT_CONTENT =
|
|
||||||
"-----BEGIN CERTIFICATE-----\n"
|
|
||||||
"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\n"
|
|
||||||
"cTe8jkzRqYdUfAoV\n"
|
|
||||||
"-----END CERTIFICATE-----\n";
|
|
||||||
|
|
||||||
constexpr std::string_view kCLIENT_KEY_CONTENT =
|
|
||||||
"-----BEGIN PRIVATE KEY-----\n"
|
|
||||||
"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\n"
|
|
||||||
"+6f4hsbp29hg+BUZ+UPdk7QyCKpZD1A=\n"
|
|
||||||
"-----END PRIVATE KEY-----\n";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* RAII helper for managing temporary TLS certificates in tests.
|
|
||||||
*
|
|
||||||
* Creates a temporary directory and writes test certificates to it.
|
|
||||||
* Automatically cleans up the directory when destroyed.
|
|
||||||
*/
|
|
||||||
class TemporaryTLSCertificates
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
static constexpr std::string_view kCA_CERT_FILENAME = "ca.pem";
|
|
||||||
static constexpr std::string_view kSERVER_CERT_FILENAME = "server_cert.pem";
|
|
||||||
static constexpr std::string_view kSERVER_KEY_FILENAME = "server_key.pem";
|
|
||||||
static constexpr std::string_view kCLIENT_CERT_FILENAME = "client_cert.pem";
|
|
||||||
static constexpr std::string_view kCLIENT_KEY_FILENAME = "client_key.pem";
|
|
||||||
static constexpr std::string_view kCERTS_DIR_PREFIX = "grpc_tls_test_";
|
|
||||||
|
|
||||||
TemporaryTLSCertificates()
|
|
||||||
{
|
|
||||||
auto tmpDir = std::filesystem::temp_directory_path();
|
|
||||||
auto uniqueDirName =
|
|
||||||
boost::filesystem::unique_path(std::string(kCERTS_DIR_PREFIX) + "%%%%%%%%");
|
|
||||||
tempDir_ = tmpDir / uniqueDirName.string();
|
|
||||||
std::filesystem::create_directories(tempDir_);
|
|
||||||
|
|
||||||
writeFile(tempDir_ / kCA_CERT_FILENAME, kCA_CERT_CONTENT);
|
|
||||||
writeFile(tempDir_ / kSERVER_CERT_FILENAME, kSERVER_CERT_CONTENT);
|
|
||||||
writeFile(tempDir_ / kSERVER_KEY_FILENAME, kSERVER_KEY_CONTENT);
|
|
||||||
writeFile(tempDir_ / kCLIENT_CERT_FILENAME, kCLIENT_CERT_CONTENT);
|
|
||||||
writeFile(tempDir_ / kCLIENT_KEY_FILENAME, kCLIENT_KEY_CONTENT);
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual ~TemporaryTLSCertificates()
|
|
||||||
{
|
|
||||||
std::error_code ec;
|
|
||||||
std::filesystem::remove_all(tempDir_, ec);
|
|
||||||
}
|
|
||||||
|
|
||||||
TemporaryTLSCertificates(TemporaryTLSCertificates const&) = delete;
|
|
||||||
TemporaryTLSCertificates&
|
|
||||||
operator=(TemporaryTLSCertificates const&) = delete;
|
|
||||||
TemporaryTLSCertificates(TemporaryTLSCertificates&&) = delete;
|
|
||||||
TemporaryTLSCertificates&
|
|
||||||
operator=(TemporaryTLSCertificates&&) = delete;
|
|
||||||
|
|
||||||
[[nodiscard]] std::filesystem::path
|
|
||||||
getCACertPath() const
|
|
||||||
{
|
|
||||||
return tempDir_ / kCA_CERT_FILENAME;
|
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]] std::filesystem::path
|
|
||||||
getServerCertPath() const
|
|
||||||
{
|
|
||||||
return tempDir_ / kSERVER_CERT_FILENAME;
|
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]] std::filesystem::path
|
|
||||||
getServerKeyPath() const
|
|
||||||
{
|
|
||||||
return tempDir_ / kSERVER_KEY_FILENAME;
|
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]] std::filesystem::path
|
|
||||||
getClientCertPath() const
|
|
||||||
{
|
|
||||||
return tempDir_ / kCLIENT_CERT_FILENAME;
|
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]] std::filesystem::path
|
|
||||||
getClientKeyPath() const
|
|
||||||
{
|
|
||||||
return tempDir_ / kCLIENT_KEY_FILENAME;
|
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]] std::filesystem::path
|
|
||||||
getTempDir() const
|
|
||||||
{
|
|
||||||
return tempDir_;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
static void
|
|
||||||
writeFile(std::filesystem::path const& path, std::string_view content)
|
|
||||||
{
|
|
||||||
std::ofstream file(path);
|
|
||||||
if (!file)
|
|
||||||
throw std::runtime_error("Failed to create file: " + path.string());
|
|
||||||
file << content;
|
|
||||||
if (!file)
|
|
||||||
throw std::runtime_error("Failed to write file: " + path.string());
|
|
||||||
}
|
|
||||||
|
|
||||||
std::filesystem::path tempDir_;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
namespace xrpl {
|
|
||||||
namespace test {
|
|
||||||
/**
|
|
||||||
* Helper function to make a simple gRPC call to test connectivity.
|
|
||||||
* Returns true if the call succeeded, false otherwise.
|
|
||||||
*/
|
|
||||||
bool
|
|
||||||
makeTestGRPCCall(std::unique_ptr<org::xrpl::rpc::v1::XRPLedgerAPIService::Stub> const& stub)
|
|
||||||
{
|
|
||||||
grpc::ClientContext context;
|
|
||||||
org::xrpl::rpc::v1::GetLedgerRequest const request;
|
|
||||||
org::xrpl::rpc::v1::GetLedgerResponse response;
|
|
||||||
|
|
||||||
// Set a short deadline to avoid hanging on failed connections
|
|
||||||
context.set_deadline(std::chrono::system_clock::now() + std::chrono::seconds(2));
|
|
||||||
|
|
||||||
grpc::Status const status = stub->GetLedger(&context, request, &response);
|
|
||||||
return status.ok();
|
|
||||||
}
|
|
||||||
|
|
||||||
class GRPCServerTLS_test : public beast::unit_test::suite, public TemporaryTLSCertificates
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
void
|
|
||||||
testWithoutTLS()
|
|
||||||
{
|
|
||||||
testcase("GRPCServer without TLS");
|
|
||||||
|
|
||||||
using namespace jtx;
|
|
||||||
|
|
||||||
// Create config without TLS settings
|
|
||||||
auto cfg = envconfig(addGrpcConfig);
|
|
||||||
Env env(*this, std::move(cfg));
|
|
||||||
|
|
||||||
// Verify the server actually started by checking the port
|
|
||||||
auto const grpcPort = env.app().config()[SECTION_PORT_GRPC].get<unsigned int>("port");
|
|
||||||
BEAST_EXPECT(grpcPort.has_value());
|
|
||||||
BEAST_EXPECT(*grpcPort > 0);
|
|
||||||
|
|
||||||
// Test 1: Plaintext client should connect successfully
|
|
||||||
std::string const serverAddress = "localhost:" + std::to_string(*grpcPort);
|
|
||||||
auto plaintextStub = org::xrpl::rpc::v1::XRPLedgerAPIService::NewStub(
|
|
||||||
grpc::CreateChannel(serverAddress, grpc::InsecureChannelCredentials()));
|
|
||||||
BEAST_EXPECT(makeTestGRPCCall(plaintextStub));
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
testWithValidTLS()
|
|
||||||
{
|
|
||||||
testcase("GRPCServer with valid TLS configuration (no mutual TLS)");
|
|
||||||
|
|
||||||
using namespace jtx;
|
|
||||||
|
|
||||||
// Test with just server cert and key (no client verification)
|
|
||||||
auto cfg = envconfig(
|
|
||||||
addGrpcConfigWithTLS, getServerCertPath().string(), getServerKeyPath().string());
|
|
||||||
Env env(*this, std::move(cfg));
|
|
||||||
|
|
||||||
// Verify the server actually started by checking the port
|
|
||||||
auto const grpcPort = env.app().config()[SECTION_PORT_GRPC].get<unsigned int>("port");
|
|
||||||
BEAST_EXPECT(grpcPort.has_value());
|
|
||||||
BEAST_EXPECT(*grpcPort > 0);
|
|
||||||
|
|
||||||
std::string const serverAddress = "localhost:" + std::to_string(*grpcPort);
|
|
||||||
|
|
||||||
// Test 1: Plaintext client should FAIL against TLS server
|
|
||||||
auto plaintextStub = org::xrpl::rpc::v1::XRPLedgerAPIService::NewStub(
|
|
||||||
grpc::CreateChannel(serverAddress, grpc::InsecureChannelCredentials()));
|
|
||||||
BEAST_EXPECT(!makeTestGRPCCall(plaintextStub));
|
|
||||||
|
|
||||||
// Test 2: TLS client with server CA should succeed
|
|
||||||
grpc::SslCredentialsOptions sslOpts;
|
|
||||||
sslOpts.pem_root_certs = std::string(kCA_CERT_CONTENT);
|
|
||||||
auto tlsStub = org::xrpl::rpc::v1::XRPLedgerAPIService::NewStub(
|
|
||||||
grpc::CreateChannel(serverAddress, grpc::SslCredentials(sslOpts)));
|
|
||||||
BEAST_EXPECT(makeTestGRPCCall(tlsStub));
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
testWithMutualTLS()
|
|
||||||
{
|
|
||||||
testcase("GRPCServer with mutual TLS (client verification enabled)");
|
|
||||||
|
|
||||||
using namespace jtx;
|
|
||||||
|
|
||||||
// Test with server cert, key, and CA for client verification
|
|
||||||
auto cfg = envconfig(
|
|
||||||
addGrpcConfigWithTLSAndClientCA,
|
|
||||||
getServerCertPath().string(),
|
|
||||||
getServerKeyPath().string(),
|
|
||||||
getCACertPath().string());
|
|
||||||
Env env(*this, std::move(cfg));
|
|
||||||
|
|
||||||
// Verify the server actually started by checking the port
|
|
||||||
auto const grpcPort = env.app().config()[SECTION_PORT_GRPC].get<unsigned int>("port");
|
|
||||||
BEAST_EXPECT(grpcPort.has_value());
|
|
||||||
BEAST_EXPECT(*grpcPort > 0);
|
|
||||||
|
|
||||||
auto const serverAddress = "localhost:" + std::to_string(*grpcPort);
|
|
||||||
|
|
||||||
// Test 1: TLS client WITHOUT client certificate should FAIL (mTLS requires client cert)
|
|
||||||
grpc::SslCredentialsOptions sslOptsNoClient;
|
|
||||||
sslOptsNoClient.pem_root_certs = std::string(kCA_CERT_CONTENT);
|
|
||||||
auto tlsStubNoClient = org::xrpl::rpc::v1::XRPLedgerAPIService::NewStub(
|
|
||||||
grpc::CreateChannel(serverAddress, grpc::SslCredentials(sslOptsNoClient)));
|
|
||||||
BEAST_EXPECT(!makeTestGRPCCall(tlsStubNoClient));
|
|
||||||
|
|
||||||
// Test 2: TLS client WITH client certificate should succeed
|
|
||||||
grpc::SslCredentialsOptions sslOptsWithClient;
|
|
||||||
sslOptsWithClient.pem_root_certs = std::string(kCA_CERT_CONTENT);
|
|
||||||
sslOptsWithClient.pem_cert_chain = std::string(kCLIENT_CERT_CONTENT);
|
|
||||||
sslOptsWithClient.pem_private_key = std::string(kCLIENT_KEY_CONTENT);
|
|
||||||
auto tlsStubWithClient = org::xrpl::rpc::v1::XRPLedgerAPIService::NewStub(
|
|
||||||
grpc::CreateChannel(serverAddress, grpc::SslCredentials(sslOptsWithClient)));
|
|
||||||
BEAST_EXPECT(makeTestGRPCCall(tlsStubWithClient));
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
testWithMissingKey()
|
|
||||||
{
|
|
||||||
testcase("GRPCServer with cert but no key");
|
|
||||||
|
|
||||||
using namespace jtx;
|
|
||||||
|
|
||||||
// Create config with only cert (missing key)
|
|
||||||
auto cfg = envconfig();
|
|
||||||
(*cfg)[SECTION_PORT_GRPC].set("ip", "127.0.0.1");
|
|
||||||
(*cfg)[SECTION_PORT_GRPC].set("port", "0");
|
|
||||||
(*cfg)[SECTION_PORT_GRPC].set("ssl_cert", getServerCertPath().string());
|
|
||||||
// Intentionally omit ssl_key
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Env const env(*this, std::move(cfg));
|
|
||||||
fail("Should have thrown exception for incomplete TLS config");
|
|
||||||
}
|
|
||||||
catch (std::runtime_error const& e)
|
|
||||||
{
|
|
||||||
BEAST_EXPECT(
|
|
||||||
std::string(e.what()).find("Incomplete TLS configuration") != std::string::npos);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
testWithMissingCert()
|
|
||||||
{
|
|
||||||
testcase("GRPCServer with key but no cert");
|
|
||||||
|
|
||||||
using namespace jtx;
|
|
||||||
|
|
||||||
// Create config with only key (missing cert)
|
|
||||||
auto cfg = envconfig();
|
|
||||||
(*cfg)[SECTION_PORT_GRPC].set("ip", "127.0.0.1");
|
|
||||||
(*cfg)[SECTION_PORT_GRPC].set("port", "0");
|
|
||||||
(*cfg)[SECTION_PORT_GRPC].set("ssl_key", getServerKeyPath().string());
|
|
||||||
// Intentionally omit ssl_cert
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Env const env(*this, std::move(cfg));
|
|
||||||
fail("Should have thrown exception for incomplete TLS config");
|
|
||||||
}
|
|
||||||
catch (std::runtime_error const& e)
|
|
||||||
{
|
|
||||||
BEAST_EXPECT(
|
|
||||||
std::string(e.what()).find("Incomplete TLS configuration") != std::string::npos);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
testWithClientCAButNoTLS()
|
|
||||||
{
|
|
||||||
testcase("GRPCServer with ssl_client_ca but without both ssl_cert and ssl_key");
|
|
||||||
|
|
||||||
using namespace jtx;
|
|
||||||
|
|
||||||
// Test 1: ssl_client_ca specified without any TLS config
|
|
||||||
{
|
|
||||||
auto cfg = envconfig();
|
|
||||||
(*cfg)[SECTION_PORT_GRPC].set("ip", "127.0.0.1");
|
|
||||||
(*cfg)[SECTION_PORT_GRPC].set("port", "0");
|
|
||||||
(*cfg)[SECTION_PORT_GRPC].set("ssl_client_ca", getCACertPath().string());
|
|
||||||
// Intentionally omit both ssl_cert and ssl_key
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Env const env(*this, std::move(cfg));
|
|
||||||
fail("Should have thrown exception for ssl_client_ca without TLS config");
|
|
||||||
}
|
|
||||||
catch (std::runtime_error const& e)
|
|
||||||
{
|
|
||||||
BEAST_EXPECT(
|
|
||||||
std::string(e.what()).find(
|
|
||||||
"ssl_client_ca requires both ssl_cert and ssl_key") != std::string::npos);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test 2: ssl_client_ca with only ssl_cert (missing ssl_key)
|
|
||||||
{
|
|
||||||
auto cfg = envconfig();
|
|
||||||
(*cfg)[SECTION_PORT_GRPC].set("ip", "127.0.0.1");
|
|
||||||
(*cfg)[SECTION_PORT_GRPC].set("port", "0");
|
|
||||||
(*cfg)[SECTION_PORT_GRPC].set("ssl_cert", getServerCertPath().string());
|
|
||||||
(*cfg)[SECTION_PORT_GRPC].set("ssl_client_ca", getCACertPath().string());
|
|
||||||
// Intentionally omit ssl_key
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Env const env(*this, std::move(cfg));
|
|
||||||
fail("Should have thrown exception for ssl_client_ca with only ssl_cert");
|
|
||||||
}
|
|
||||||
catch (std::runtime_error const& e)
|
|
||||||
{
|
|
||||||
// This should fail with "Incomplete TLS configuration" first
|
|
||||||
// because ssl_cert is specified without ssl_key
|
|
||||||
BEAST_EXPECT(
|
|
||||||
std::string(e.what()).find("Incomplete TLS configuration") !=
|
|
||||||
std::string::npos);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test 3: ssl_client_ca with only ssl_key (missing ssl_cert)
|
|
||||||
{
|
|
||||||
auto cfg = envconfig();
|
|
||||||
(*cfg)[SECTION_PORT_GRPC].set("ip", "127.0.0.1");
|
|
||||||
(*cfg)[SECTION_PORT_GRPC].set("port", "0");
|
|
||||||
(*cfg)[SECTION_PORT_GRPC].set("ssl_key", getServerKeyPath().string());
|
|
||||||
(*cfg)[SECTION_PORT_GRPC].set("ssl_client_ca", getCACertPath().string());
|
|
||||||
// Intentionally omit ssl_cert
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Env const env(*this, std::move(cfg));
|
|
||||||
fail("Should have thrown exception for ssl_client_ca with only ssl_key");
|
|
||||||
}
|
|
||||||
catch (std::runtime_error const& e)
|
|
||||||
{
|
|
||||||
// This should fail with "Incomplete TLS configuration" first
|
|
||||||
// because ssl_key is specified without ssl_cert
|
|
||||||
BEAST_EXPECT(
|
|
||||||
std::string(e.what()).find("Incomplete TLS configuration") !=
|
|
||||||
std::string::npos);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
testWithCertChainButNoTLS()
|
|
||||||
{
|
|
||||||
testcase("GRPCServer with ssl_cert_chain but without both ssl_cert and ssl_key");
|
|
||||||
|
|
||||||
using namespace jtx;
|
|
||||||
|
|
||||||
// Test 1: ssl_cert_chain specified without any TLS config
|
|
||||||
{
|
|
||||||
auto cfg = envconfig();
|
|
||||||
(*cfg)[SECTION_PORT_GRPC].set("ip", "127.0.0.1");
|
|
||||||
(*cfg)[SECTION_PORT_GRPC].set("port", "0");
|
|
||||||
(*cfg)[SECTION_PORT_GRPC].set("ssl_cert_chain", getCACertPath().string());
|
|
||||||
// Intentionally omit both ssl_cert and ssl_key
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Env const env(*this, std::move(cfg));
|
|
||||||
fail("Should have thrown exception for ssl_cert_chain without TLS config");
|
|
||||||
}
|
|
||||||
catch (std::runtime_error const& e)
|
|
||||||
{
|
|
||||||
BEAST_EXPECT(
|
|
||||||
std::string(e.what()).find(
|
|
||||||
"ssl_cert_chain requires both ssl_cert and ssl_key") != std::string::npos);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test 2: ssl_cert_chain with only ssl_cert (missing ssl_key)
|
|
||||||
{
|
|
||||||
auto cfg = envconfig();
|
|
||||||
(*cfg)[SECTION_PORT_GRPC].set("ip", "127.0.0.1");
|
|
||||||
(*cfg)[SECTION_PORT_GRPC].set("port", "0");
|
|
||||||
(*cfg)[SECTION_PORT_GRPC].set("ssl_cert", getServerCertPath().string());
|
|
||||||
(*cfg)[SECTION_PORT_GRPC].set("ssl_cert_chain", getCACertPath().string());
|
|
||||||
// Intentionally omit ssl_key
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Env const env(*this, std::move(cfg));
|
|
||||||
fail("Should have thrown exception for ssl_cert_chain with only ssl_cert");
|
|
||||||
}
|
|
||||||
catch (std::runtime_error const& e)
|
|
||||||
{
|
|
||||||
// This should fail with "Incomplete TLS configuration" first
|
|
||||||
// because ssl_cert is specified without ssl_key
|
|
||||||
BEAST_EXPECT(
|
|
||||||
std::string(e.what()).find("Incomplete TLS configuration") !=
|
|
||||||
std::string::npos);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
testWithCertChain()
|
|
||||||
{
|
|
||||||
testcase("GRPCServer with ssl_cert_chain for intermediate CA certificates");
|
|
||||||
|
|
||||||
using namespace jtx;
|
|
||||||
|
|
||||||
// Test with server cert, key, and cert chain (intermediate CA)
|
|
||||||
// In this test, we use the CA cert as a stand-in for an intermediate CA cert
|
|
||||||
auto cfg = envconfig(
|
|
||||||
addGrpcConfigWithTLSAndCertChain,
|
|
||||||
getServerCertPath().string(),
|
|
||||||
getServerKeyPath().string(),
|
|
||||||
getCACertPath().string());
|
|
||||||
Env env(*this, std::move(cfg));
|
|
||||||
|
|
||||||
// Verify the server actually started by checking the port
|
|
||||||
auto const grpcPort = env.app().config()[SECTION_PORT_GRPC].get<unsigned int>("port");
|
|
||||||
BEAST_EXPECT(grpcPort.has_value());
|
|
||||||
BEAST_EXPECT(*grpcPort > 0);
|
|
||||||
|
|
||||||
auto const serverAddress = "localhost:" + std::to_string(*grpcPort);
|
|
||||||
|
|
||||||
// Test: TLS client should be able to connect (no client cert required)
|
|
||||||
grpc::SslCredentialsOptions sslOpts;
|
|
||||||
sslOpts.pem_root_certs = std::string(kCA_CERT_CONTENT);
|
|
||||||
auto tlsStub = org::xrpl::rpc::v1::XRPLedgerAPIService::NewStub(
|
|
||||||
grpc::CreateChannel(serverAddress, grpc::SslCredentials(sslOpts)));
|
|
||||||
BEAST_EXPECT(makeTestGRPCCall(tlsStub));
|
|
||||||
|
|
||||||
// Insecure client should fail
|
|
||||||
auto insecureStub = org::xrpl::rpc::v1::XRPLedgerAPIService::NewStub(
|
|
||||||
grpc::CreateChannel(serverAddress, grpc::InsecureChannelCredentials()));
|
|
||||||
BEAST_EXPECT(!makeTestGRPCCall(insecureStub));
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
testWithInvalidCertFile()
|
|
||||||
{
|
|
||||||
testcase("GRPCServer with invalid/non-existent certificate file");
|
|
||||||
|
|
||||||
using namespace jtx;
|
|
||||||
|
|
||||||
auto cfg = envconfig();
|
|
||||||
(*cfg)[SECTION_PORT_GRPC].set("ip", "127.0.0.1");
|
|
||||||
(*cfg)[SECTION_PORT_GRPC].set("port", "0");
|
|
||||||
(*cfg)[SECTION_PORT_GRPC].set("ssl_cert", "/nonexistent/path/to/cert.pem");
|
|
||||||
(*cfg)[SECTION_PORT_GRPC].set("ssl_key", getServerKeyPath().string());
|
|
||||||
|
|
||||||
Env env(*this, std::move(cfg));
|
|
||||||
|
|
||||||
// Server should fail to start - verify port is 0
|
|
||||||
auto const grpcPort = env.app().config()[SECTION_PORT_GRPC].get<unsigned int>("port");
|
|
||||||
BEAST_EXPECT(grpcPort.has_value());
|
|
||||||
BEAST_EXPECT(*grpcPort == 0); // Server should not have started
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
testWithInvalidKeyFile()
|
|
||||||
{
|
|
||||||
testcase("GRPCServer with invalid/non-existent key file");
|
|
||||||
|
|
||||||
using namespace jtx;
|
|
||||||
|
|
||||||
auto cfg = envconfig();
|
|
||||||
(*cfg)[SECTION_PORT_GRPC].set("ip", "127.0.0.1");
|
|
||||||
(*cfg)[SECTION_PORT_GRPC].set("port", "0");
|
|
||||||
(*cfg)[SECTION_PORT_GRPC].set("ssl_cert", getServerCertPath().string());
|
|
||||||
(*cfg)[SECTION_PORT_GRPC].set("ssl_key", "/nonexistent/path/to/key.pem");
|
|
||||||
|
|
||||||
Env env(*this, std::move(cfg));
|
|
||||||
|
|
||||||
// Server should fail to start - verify port is 0
|
|
||||||
auto const grpcPort = env.app().config()[SECTION_PORT_GRPC].get<unsigned int>("port");
|
|
||||||
BEAST_EXPECT(grpcPort.has_value());
|
|
||||||
BEAST_EXPECT(*grpcPort == 0); // Server should not have started
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
testWithInvalidCertChainFile()
|
|
||||||
{
|
|
||||||
testcase("GRPCServer with invalid/non-existent cert chain file");
|
|
||||||
|
|
||||||
using namespace jtx;
|
|
||||||
|
|
||||||
auto cfg = envconfig();
|
|
||||||
(*cfg)[SECTION_PORT_GRPC].set("ip", "127.0.0.1");
|
|
||||||
(*cfg)[SECTION_PORT_GRPC].set("port", "0");
|
|
||||||
(*cfg)[SECTION_PORT_GRPC].set("ssl_cert", getServerCertPath().string());
|
|
||||||
(*cfg)[SECTION_PORT_GRPC].set("ssl_key", getServerKeyPath().string());
|
|
||||||
(*cfg)[SECTION_PORT_GRPC].set("ssl_cert_chain", "/nonexistent/path/to/chain.pem");
|
|
||||||
|
|
||||||
Env env(*this, std::move(cfg));
|
|
||||||
|
|
||||||
// Server should fail to start - verify port is 0
|
|
||||||
auto const grpcPort = env.app().config()[SECTION_PORT_GRPC].get<unsigned int>("port");
|
|
||||||
BEAST_EXPECT(grpcPort.has_value());
|
|
||||||
BEAST_EXPECT(*grpcPort == 0); // Server should not have started
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
testWithInvalidClientCAFile()
|
|
||||||
{
|
|
||||||
testcase("GRPCServer with invalid/non-existent client CA file");
|
|
||||||
|
|
||||||
using namespace jtx;
|
|
||||||
|
|
||||||
auto cfg = envconfig();
|
|
||||||
(*cfg)[SECTION_PORT_GRPC].set("ip", "127.0.0.1");
|
|
||||||
(*cfg)[SECTION_PORT_GRPC].set("port", "0");
|
|
||||||
(*cfg)[SECTION_PORT_GRPC].set("ssl_cert", getServerCertPath().string());
|
|
||||||
(*cfg)[SECTION_PORT_GRPC].set("ssl_key", getServerKeyPath().string());
|
|
||||||
(*cfg)[SECTION_PORT_GRPC].set("ssl_client_ca", "/nonexistent/path/to/ca.pem");
|
|
||||||
|
|
||||||
Env env(*this, std::move(cfg));
|
|
||||||
|
|
||||||
// Server should fail to start - verify port is 0
|
|
||||||
auto const grpcPort = env.app().config()[SECTION_PORT_GRPC].get<unsigned int>("port");
|
|
||||||
BEAST_EXPECT(grpcPort.has_value());
|
|
||||||
BEAST_EXPECT(*grpcPort == 0); // Server should not have started
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
testWithEmptyClientCAFile()
|
|
||||||
{
|
|
||||||
testcase("GRPCServer with empty client CA file");
|
|
||||||
|
|
||||||
using namespace jtx;
|
|
||||||
|
|
||||||
// Create an empty file for client CA
|
|
||||||
auto emptyCAPath = getTempDir() / "empty_ca.pem";
|
|
||||||
std::ofstream emptyFile(emptyCAPath);
|
|
||||||
emptyFile.close();
|
|
||||||
|
|
||||||
auto cfg = envconfig();
|
|
||||||
(*cfg)[SECTION_PORT_GRPC].set("ip", "127.0.0.1");
|
|
||||||
(*cfg)[SECTION_PORT_GRPC].set("port", "0");
|
|
||||||
(*cfg)[SECTION_PORT_GRPC].set("ssl_cert", getServerCertPath().string());
|
|
||||||
(*cfg)[SECTION_PORT_GRPC].set("ssl_key", getServerKeyPath().string());
|
|
||||||
(*cfg)[SECTION_PORT_GRPC].set("ssl_client_ca", emptyCAPath.string());
|
|
||||||
|
|
||||||
Env env(*this, std::move(cfg));
|
|
||||||
|
|
||||||
// Server should fail to start due to empty CA file
|
|
||||||
auto const grpcPort = env.app().config()[SECTION_PORT_GRPC].get<unsigned int>("port");
|
|
||||||
BEAST_EXPECT(grpcPort.has_value());
|
|
||||||
BEAST_EXPECT(*grpcPort == 0); // Server should not have started
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
testWithBothCertChainAndClientCA()
|
|
||||||
{
|
|
||||||
testcase("GRPCServer with both cert chain and client CA (full mTLS with intermediates)");
|
|
||||||
|
|
||||||
using namespace jtx;
|
|
||||||
|
|
||||||
// Test with all TLS features enabled: cert, key, cert_chain, and client_ca
|
|
||||||
auto cfg = envconfig();
|
|
||||||
(*cfg)[SECTION_PORT_GRPC].set("ip", getEnvLocalhostAddr());
|
|
||||||
(*cfg)[SECTION_PORT_GRPC].set("port", "0");
|
|
||||||
(*cfg)[SECTION_PORT_GRPC].set("ssl_cert", getServerCertPath().string());
|
|
||||||
(*cfg)[SECTION_PORT_GRPC].set("ssl_key", getServerKeyPath().string());
|
|
||||||
(*cfg)[SECTION_PORT_GRPC].set(
|
|
||||||
"ssl_cert_chain", getCACertPath().string()); // Using CA as intermediate
|
|
||||||
(*cfg)[SECTION_PORT_GRPC].set("ssl_client_ca", getCACertPath().string());
|
|
||||||
|
|
||||||
Env env(*this, std::move(cfg));
|
|
||||||
|
|
||||||
// Verify the server started successfully
|
|
||||||
auto const grpcPort = env.app().config()[SECTION_PORT_GRPC].get<unsigned int>("port");
|
|
||||||
BEAST_EXPECT(grpcPort.has_value());
|
|
||||||
BEAST_EXPECT(*grpcPort > 0);
|
|
||||||
|
|
||||||
auto const serverAddress = "localhost:" + std::to_string(*grpcPort);
|
|
||||||
|
|
||||||
// Test 1: TLS client WITHOUT client certificate should FAIL (mTLS requires client cert)
|
|
||||||
grpc::SslCredentialsOptions sslOptsNoClient;
|
|
||||||
sslOptsNoClient.pem_root_certs = std::string(kCA_CERT_CONTENT);
|
|
||||||
auto tlsStubNoClient = org::xrpl::rpc::v1::XRPLedgerAPIService::NewStub(
|
|
||||||
grpc::CreateChannel(serverAddress, grpc::SslCredentials(sslOptsNoClient)));
|
|
||||||
BEAST_EXPECT(!makeTestGRPCCall(tlsStubNoClient));
|
|
||||||
|
|
||||||
// Test 2: TLS client WITH client certificate should succeed
|
|
||||||
grpc::SslCredentialsOptions sslOptsWithClient;
|
|
||||||
sslOptsWithClient.pem_root_certs = std::string(kCA_CERT_CONTENT);
|
|
||||||
sslOptsWithClient.pem_cert_chain = std::string(kCLIENT_CERT_CONTENT);
|
|
||||||
sslOptsWithClient.pem_private_key = std::string(kCLIENT_KEY_CONTENT);
|
|
||||||
auto tlsStubWithClient = org::xrpl::rpc::v1::XRPLedgerAPIService::NewStub(
|
|
||||||
grpc::CreateChannel(serverAddress, grpc::SslCredentials(sslOptsWithClient)));
|
|
||||||
BEAST_EXPECT(makeTestGRPCCall(tlsStubWithClient));
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
run() override
|
|
||||||
{
|
|
||||||
testWithoutTLS();
|
|
||||||
testWithValidTLS();
|
|
||||||
testWithMutualTLS();
|
|
||||||
testWithMissingKey();
|
|
||||||
testWithMissingCert();
|
|
||||||
testWithClientCAButNoTLS();
|
|
||||||
testWithCertChainButNoTLS();
|
|
||||||
testWithCertChain();
|
|
||||||
testWithInvalidCertFile();
|
|
||||||
testWithInvalidKeyFile();
|
|
||||||
testWithInvalidCertChainFile();
|
|
||||||
testWithInvalidClientCAFile();
|
|
||||||
testWithEmptyClientCAFile();
|
|
||||||
testWithBothCertChainAndClientCA();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
BEAST_DEFINE_TESTSUITE(GRPCServerTLS, app, xrpl);
|
|
||||||
|
|
||||||
} // namespace test
|
|
||||||
} // namespace xrpl
|
|
||||||
@@ -1774,10 +1774,8 @@ class Invariants_test : public beast::unit_test::suite
|
|||||||
using namespace test::jtx;
|
using namespace test::jtx;
|
||||||
|
|
||||||
bool const fixPDEnabled = features[fixPermissionedDomainInvariant];
|
bool const fixPDEnabled = features[fixPermissionedDomainInvariant];
|
||||||
bool const fixS313Enabled = features[fixSecurity3_1_3];
|
|
||||||
|
|
||||||
testcase << "PermissionedDEX" + std::string(fixPDEnabled ? " fixPD" : "") +
|
testcase << "PermissionedDEX" + std::string(fixPDEnabled ? " fix" : "");
|
||||||
std::string(fixS313Enabled ? " fixS313" : "");
|
|
||||||
|
|
||||||
doInvariantCheck(
|
doInvariantCheck(
|
||||||
Env(*this, features),
|
Env(*this, features),
|
||||||
@@ -1865,45 +1863,6 @@ class Invariants_test : public beast::unit_test::suite
|
|||||||
{tecINVARIANT_FAILED, tecINVARIANT_FAILED});
|
{tecINVARIANT_FAILED, tecINVARIANT_FAILED});
|
||||||
}
|
}
|
||||||
|
|
||||||
// empty sfAdditionalBooks (size 0)
|
|
||||||
{
|
|
||||||
Env env1(*this, features);
|
|
||||||
|
|
||||||
Account const A1{"A1"};
|
|
||||||
Account const A2{"A2"};
|
|
||||||
env1.fund(XRP(1000), A1, A2);
|
|
||||||
env1.close();
|
|
||||||
|
|
||||||
[[maybe_unused]] auto [seq1, pd1] = createPermissionedDomainEnv(env1, A1, A2);
|
|
||||||
env1.close();
|
|
||||||
|
|
||||||
doInvariantCheck(
|
|
||||||
std::move(env1),
|
|
||||||
A1,
|
|
||||||
A2,
|
|
||||||
fixS313Enabled ? std::vector<std::string>{{"hybrid offer is malformed"}}
|
|
||||||
: std::vector<std::string>{},
|
|
||||||
[&pd1](Account const& A1, Account const& A2, ApplyContext& ac) {
|
|
||||||
Keylet const offerKey = keylet::offer(A2.id(), 10);
|
|
||||||
auto sleOffer = std::make_shared<SLE>(offerKey);
|
|
||||||
sleOffer->setAccountID(sfAccount, A2);
|
|
||||||
sleOffer->setFieldAmount(sfTakerPays, A1["USD"](10));
|
|
||||||
sleOffer->setFieldAmount(sfTakerGets, XRP(1));
|
|
||||||
sleOffer->setFlag(lsfHybrid);
|
|
||||||
sleOffer->setFieldH256(sfDomainID, pd1);
|
|
||||||
|
|
||||||
STArray const bookArr; // empty array, size 0
|
|
||||||
sleOffer->setFieldArray(sfAdditionalBooks, bookArr);
|
|
||||||
ac.view().insert(sleOffer);
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
XRPAmount{},
|
|
||||||
STTx{ttOFFER_CREATE, [&](STObject&) {}},
|
|
||||||
fixS313Enabled
|
|
||||||
? std::initializer_list<TER>{tecINVARIANT_FAILED, tecINVARIANT_FAILED}
|
|
||||||
: std::initializer_list<TER>{tesSUCCESS, tesSUCCESS});
|
|
||||||
}
|
|
||||||
|
|
||||||
// hybrid offer missing sfAdditionalBooks
|
// hybrid offer missing sfAdditionalBooks
|
||||||
{
|
{
|
||||||
Env env1(*this, features);
|
Env env1(*this, features);
|
||||||
@@ -4102,10 +4061,6 @@ public:
|
|||||||
testPermissionedDomainInvariants(defaultAmendments() - fixPermissionedDomainInvariant);
|
testPermissionedDomainInvariants(defaultAmendments() - fixPermissionedDomainInvariant);
|
||||||
testPermissionedDEX(defaultAmendments() | fixPermissionedDomainInvariant);
|
testPermissionedDEX(defaultAmendments() | fixPermissionedDomainInvariant);
|
||||||
testPermissionedDEX(defaultAmendments() - fixPermissionedDomainInvariant);
|
testPermissionedDEX(defaultAmendments() - fixPermissionedDomainInvariant);
|
||||||
testPermissionedDEX(
|
|
||||||
(defaultAmendments() | fixPermissionedDomainInvariant) - fixSecurity3_1_3);
|
|
||||||
testPermissionedDEX(
|
|
||||||
defaultAmendments() - fixPermissionedDomainInvariant - fixSecurity3_1_3);
|
|
||||||
testNoModifiedUnmodifiableFields();
|
testNoModifiedUnmodifiableFields();
|
||||||
testValidPseudoAccounts();
|
testValidPseudoAccounts();
|
||||||
testValidLoanBroker();
|
testValidLoanBroker();
|
||||||
|
|||||||
@@ -20,8 +20,6 @@
|
|||||||
|
|
||||||
#include <xrpl/basics/base_uint.h>
|
#include <xrpl/basics/base_uint.h>
|
||||||
#include <xrpl/beast/unit_test/suite.h>
|
#include <xrpl/beast/unit_test/suite.h>
|
||||||
#include <xrpl/beast/utility/Journal.h>
|
|
||||||
#include <xrpl/ledger/OpenView.h>
|
|
||||||
#include <xrpl/protocol/Book.h>
|
#include <xrpl/protocol/Book.h>
|
||||||
#include <xrpl/protocol/Feature.h>
|
#include <xrpl/protocol/Feature.h>
|
||||||
#include <xrpl/protocol/Indexes.h>
|
#include <xrpl/protocol/Indexes.h>
|
||||||
@@ -30,8 +28,6 @@
|
|||||||
#include <xrpl/protocol/LedgerFormats.h>
|
#include <xrpl/protocol/LedgerFormats.h>
|
||||||
#include <xrpl/protocol/SField.h>
|
#include <xrpl/protocol/SField.h>
|
||||||
#include <xrpl/protocol/STAmount.h>
|
#include <xrpl/protocol/STAmount.h>
|
||||||
#include <xrpl/protocol/STArray.h>
|
|
||||||
#include <xrpl/protocol/STLedgerEntry.h>
|
|
||||||
#include <xrpl/protocol/TER.h>
|
#include <xrpl/protocol/TER.h>
|
||||||
#include <xrpl/protocol/TxFlags.h>
|
#include <xrpl/protocol/TxFlags.h>
|
||||||
|
|
||||||
@@ -39,7 +35,6 @@
|
|||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <memory>
|
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
@@ -1390,73 +1385,6 @@ class PermissionedDEX_test : public beast::unit_test::suite
|
|||||||
BEAST_EXPECT(!offerExists(env, bob, carolOfferSeq));
|
BEAST_EXPECT(!offerExists(env, bob, carolOfferSeq));
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
|
||||||
testHybridMalformedOffer(FeatureBitset features)
|
|
||||||
{
|
|
||||||
bool const fixS313Enabled = features[fixSecurity3_1_3];
|
|
||||||
|
|
||||||
testcase << "Hybrid offer with empty AdditionalBooks"
|
|
||||||
<< (fixS313Enabled ? " (fixSecurity3_1_3 enabled)"
|
|
||||||
: " (fixSecurity3_1_3 disabled)");
|
|
||||||
|
|
||||||
// offerInDomain has two code paths gated by fixSecurity3_1_3:
|
|
||||||
//
|
|
||||||
// pre-fix: only rejects a hybrid offer when sfAdditionalBooks is
|
|
||||||
// entirely absent — an empty array (size 0) passes through.
|
|
||||||
// post-fix: also rejects a hybrid offer whose sfAdditionalBooks array
|
|
||||||
// has size != 1 (i.e. 0 or >1 entries).
|
|
||||||
//
|
|
||||||
// We create a valid hybrid offer, then directly manipulate its SLE to
|
|
||||||
// produce the size==0 case that cannot occur via normal transactions,
|
|
||||||
// and verify that the two code paths produce the expected outcomes.
|
|
||||||
//
|
|
||||||
// Note: the PermissionedDEX invariant checker (ValidPermissionedDEX)
|
|
||||||
// does not flag this malformation for ttPAYMENT — only for
|
|
||||||
// ttOFFER_CREATE — so the without-fix payment completes as tesSUCCESS.
|
|
||||||
|
|
||||||
Env env(*this, features);
|
|
||||||
auto const& [gw, domainOwner, alice, bob, carol, USD, domainID, credType] =
|
|
||||||
PermissionedDEX(env);
|
|
||||||
|
|
||||||
// Create a valid hybrid offer (sfAdditionalBooks has exactly 1 entry)
|
|
||||||
auto const bobOfferSeq{env.seq(bob)};
|
|
||||||
env(offer(bob, XRP(10), USD(10)), txflags(tfHybrid), domain(domainID));
|
|
||||||
env.close();
|
|
||||||
BEAST_EXPECT(offerExists(env, bob, bobOfferSeq));
|
|
||||||
|
|
||||||
// Directly manipulate the offer SLE in the open ledger so that
|
|
||||||
// sfAdditionalBooks is present but empty (size 0). This is the
|
|
||||||
// malformed state that fixSecurity3_1_3 is designed to catch.
|
|
||||||
auto const offerKey = keylet::offer(bob.id(), bobOfferSeq);
|
|
||||||
env.app().getOpenLedger().modify([&offerKey](OpenView& view, beast::Journal) {
|
|
||||||
auto const sle = view.read(offerKey);
|
|
||||||
if (!sle)
|
|
||||||
return false;
|
|
||||||
auto replacement = std::make_shared<SLE>(*sle, sle->key());
|
|
||||||
replacement->setFieldArray(sfAdditionalBooks, STArray{});
|
|
||||||
view.rawReplace(replacement);
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (fixS313Enabled)
|
|
||||||
{
|
|
||||||
// post-fixSecurity3_1_3: offerInDomain rejects the malformed
|
|
||||||
// offer (size == 0), so no valid domain offer is found.
|
|
||||||
env(pay(alice, carol, USD(10)),
|
|
||||||
path(~USD),
|
|
||||||
sendmax(XRP(10)),
|
|
||||||
domain(domainID),
|
|
||||||
ter(tecPATH_PARTIAL));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// pre-fixSecurity3_1_3: offerInDomain only checks for a missing
|
|
||||||
// sfAdditionalBooks field; size == 0 passes through, so the
|
|
||||||
// malformed offer is crossed and the payment succeeds.
|
|
||||||
env(pay(alice, carol, USD(10)), path(~USD), sendmax(XRP(10)), domain(domainID));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
void
|
void
|
||||||
run() override
|
run() override
|
||||||
@@ -1478,8 +1406,6 @@ public:
|
|||||||
testHybridBookStep(all);
|
testHybridBookStep(all);
|
||||||
testHybridInvalidOffer(all);
|
testHybridInvalidOffer(all);
|
||||||
testHybridOfferDirectories(all);
|
testHybridOfferDirectories(all);
|
||||||
testHybridMalformedOffer(all);
|
|
||||||
testHybridMalformedOffer(all - fixSecurity3_1_3);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -107,52 +107,6 @@ std::unique_ptr<Config> addGrpcConfig(std::unique_ptr<Config>);
|
|||||||
std::unique_ptr<Config>
|
std::unique_ptr<Config>
|
||||||
addGrpcConfigWithSecureGateway(std::unique_ptr<Config>, std::string const& secureGateway);
|
addGrpcConfigWithSecureGateway(std::unique_ptr<Config>, std::string const& secureGateway);
|
||||||
|
|
||||||
/// @brief add a grpc address, port and TLS certificate/key paths to config
|
|
||||||
///
|
|
||||||
/// This is intended for use with envconfig, for tests that require a grpc
|
|
||||||
/// server with TLS enabled.
|
|
||||||
///
|
|
||||||
/// @param cfg config instance to be modified
|
|
||||||
/// @param certPath path to SSL certificate file
|
|
||||||
/// @param keyPath path to SSL private key file
|
|
||||||
std::unique_ptr<Config>
|
|
||||||
addGrpcConfigWithTLS(
|
|
||||||
std::unique_ptr<Config>,
|
|
||||||
std::string const& certPath,
|
|
||||||
std::string const& keyPath);
|
|
||||||
|
|
||||||
/// @brief add a grpc address, port and TLS certificate/key/client CA paths to config
|
|
||||||
///
|
|
||||||
/// This is intended for use with envconfig, for tests that require a grpc
|
|
||||||
/// server with mutual TLS (client certificate verification) enabled.
|
|
||||||
///
|
|
||||||
/// @param cfg config instance to be modified
|
|
||||||
/// @param certPath path to SSL certificate file
|
|
||||||
/// @param keyPath path to SSL private key file
|
|
||||||
/// @param clientCAPath path to SSL client CA certificate file for mTLS
|
|
||||||
std::unique_ptr<Config>
|
|
||||||
addGrpcConfigWithTLSAndClientCA(
|
|
||||||
std::unique_ptr<Config>,
|
|
||||||
std::string const& certPath,
|
|
||||||
std::string const& keyPath,
|
|
||||||
std::string const& clientCAPath);
|
|
||||||
|
|
||||||
/// @brief add a grpc address, port and TLS with server cert chain to config
|
|
||||||
///
|
|
||||||
/// This is intended for use with envconfig, for tests that require a grpc
|
|
||||||
/// server with TLS enabled and intermediate CA certificates.
|
|
||||||
///
|
|
||||||
/// @param cfg config instance to be modified
|
|
||||||
/// @param certPath path to SSL certificate file
|
|
||||||
/// @param keyPath path to SSL private key file
|
|
||||||
/// @param certChainPath path to SSL intermediate CA certificate(s) file
|
|
||||||
std::unique_ptr<Config>
|
|
||||||
addGrpcConfigWithTLSAndCertChain(
|
|
||||||
std::unique_ptr<Config>,
|
|
||||||
std::string const& certPath,
|
|
||||||
std::string const& keyPath,
|
|
||||||
std::string const& certChainPath);
|
|
||||||
|
|
||||||
std::unique_ptr<Config>
|
std::unique_ptr<Config>
|
||||||
makeConfig(
|
makeConfig(
|
||||||
std::map<std::string, std::string> extraTxQ = {},
|
std::map<std::string, std::string> extraTxQ = {},
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ class JSONRPCClient : public AbstractClient
|
|||||||
continue;
|
continue;
|
||||||
ParsedPort pp;
|
ParsedPort pp;
|
||||||
parse_Port(pp, cfg[name], log);
|
parse_Port(pp, cfg[name], log);
|
||||||
if (not pp.protocol.contains("http"))
|
if (pp.protocol.count("http") == 0)
|
||||||
continue;
|
continue;
|
||||||
using namespace boost::asio::ip;
|
using namespace boost::asio::ip;
|
||||||
if (pp.ip && pp.ip->is_unspecified())
|
if (pp.ip && pp.ip->is_unspecified())
|
||||||
@@ -91,6 +91,12 @@ public:
|
|||||||
stream_.connect(ep_);
|
stream_.connect(ep_);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
~JSONRPCClient() override
|
||||||
|
{
|
||||||
|
// stream_.shutdown(boost::asio::ip::tcp::socket::shutdown_both);
|
||||||
|
// stream_.close();
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Return value is an Object type with up to three keys:
|
Return value is an Object type with up to three keys:
|
||||||
status
|
status
|
||||||
|
|||||||
@@ -132,49 +132,6 @@ addGrpcConfigWithSecureGateway(std::unique_ptr<Config> cfg, std::string const& s
|
|||||||
return cfg;
|
return cfg;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<Config>
|
|
||||||
addGrpcConfigWithTLS(
|
|
||||||
std::unique_ptr<Config> cfg,
|
|
||||||
std::string const& certPath,
|
|
||||||
std::string const& keyPath)
|
|
||||||
{
|
|
||||||
(*cfg)[SECTION_PORT_GRPC].set("ip", getEnvLocalhostAddr());
|
|
||||||
(*cfg)[SECTION_PORT_GRPC].set("port", "0");
|
|
||||||
(*cfg)[SECTION_PORT_GRPC].set("ssl_cert", certPath);
|
|
||||||
(*cfg)[SECTION_PORT_GRPC].set("ssl_key", keyPath);
|
|
||||||
return cfg;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::unique_ptr<Config>
|
|
||||||
addGrpcConfigWithTLSAndClientCA(
|
|
||||||
std::unique_ptr<Config> cfg,
|
|
||||||
std::string const& certPath,
|
|
||||||
std::string const& keyPath,
|
|
||||||
std::string const& clientCAPath)
|
|
||||||
{
|
|
||||||
(*cfg)[SECTION_PORT_GRPC].set("ip", getEnvLocalhostAddr());
|
|
||||||
(*cfg)[SECTION_PORT_GRPC].set("port", "0");
|
|
||||||
(*cfg)[SECTION_PORT_GRPC].set("ssl_cert", certPath);
|
|
||||||
(*cfg)[SECTION_PORT_GRPC].set("ssl_key", keyPath);
|
|
||||||
(*cfg)[SECTION_PORT_GRPC].set("ssl_client_ca", clientCAPath);
|
|
||||||
return cfg;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::unique_ptr<Config>
|
|
||||||
addGrpcConfigWithTLSAndCertChain(
|
|
||||||
std::unique_ptr<Config> cfg,
|
|
||||||
std::string const& certPath,
|
|
||||||
std::string const& keyPath,
|
|
||||||
std::string const& certChainPath)
|
|
||||||
{
|
|
||||||
(*cfg)[SECTION_PORT_GRPC].set("ip", getEnvLocalhostAddr());
|
|
||||||
(*cfg)[SECTION_PORT_GRPC].set("port", "0");
|
|
||||||
(*cfg)[SECTION_PORT_GRPC].set("ssl_cert", certPath);
|
|
||||||
(*cfg)[SECTION_PORT_GRPC].set("ssl_key", keyPath);
|
|
||||||
(*cfg)[SECTION_PORT_GRPC].set("ssl_cert_chain", certChainPath);
|
|
||||||
return cfg;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::unique_ptr<Config>
|
std::unique_ptr<Config>
|
||||||
makeConfig(
|
makeConfig(
|
||||||
std::map<std::string, std::string> extraTxQ,
|
std::map<std::string, std::string> extraTxQ,
|
||||||
|
|||||||
@@ -8,7 +8,6 @@
|
|||||||
#include <xrpld/rpc/detail/Handler.h>
|
#include <xrpld/rpc/detail/Handler.h>
|
||||||
|
|
||||||
#include <xrpl/basics/BasicConfig.h>
|
#include <xrpl/basics/BasicConfig.h>
|
||||||
#include <xrpl/basics/FileUtilities.h>
|
|
||||||
#include <xrpl/basics/Log.h>
|
#include <xrpl/basics/Log.h>
|
||||||
#include <xrpl/basics/contract.h>
|
#include <xrpl/basics/contract.h>
|
||||||
#include <xrpl/beast/core/CurrentThreadName.h>
|
#include <xrpl/beast/core/CurrentThreadName.h>
|
||||||
@@ -28,7 +27,6 @@
|
|||||||
#include <boost/asio/ip/tcp.hpp>
|
#include <boost/asio/ip/tcp.hpp>
|
||||||
#include <boost/icl/interval_set.hpp>
|
#include <boost/icl/interval_set.hpp>
|
||||||
|
|
||||||
#include <grpc/grpc_security_constants.h>
|
|
||||||
#include <grpcpp/completion_queue.h>
|
#include <grpcpp/completion_queue.h>
|
||||||
#include <grpcpp/security/server_credentials.h>
|
#include <grpcpp/security/server_credentials.h>
|
||||||
#include <grpcpp/server_builder.h>
|
#include <grpcpp/server_builder.h>
|
||||||
@@ -389,48 +387,6 @@ GRPCServerImpl::GRPCServerImpl(Application& app)
|
|||||||
Throw<std::runtime_error>("Error parsing secure_gateway section");
|
Throw<std::runtime_error>("Error parsing secure_gateway section");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read TLS certificate configuration (optional)
|
|
||||||
sslCertPath_ = section.get("ssl_cert");
|
|
||||||
sslKeyPath_ = section.get("ssl_key");
|
|
||||||
sslCertChainPath_ = section.get("ssl_cert_chain");
|
|
||||||
sslClientCAPath_ = section.get("ssl_client_ca");
|
|
||||||
|
|
||||||
// If cert or key is specified, both must be specified
|
|
||||||
if (sslCertPath_.has_value() || sslKeyPath_.has_value())
|
|
||||||
{
|
|
||||||
if (!sslCertPath_.has_value() || !sslKeyPath_.has_value())
|
|
||||||
{
|
|
||||||
JLOG(journal_.error())
|
|
||||||
<< "Both ssl_cert and ssl_key must be specified for gRPC TLS";
|
|
||||||
Throw<std::runtime_error>("Incomplete TLS configuration for gRPC");
|
|
||||||
}
|
|
||||||
JLOG(journal_.info()) << "gRPC TLS enabled with certificate: " << *sslCertPath_;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate TLS configuration consistency: ssl_cert_chain only makes sense when TLS is
|
|
||||||
// enabled
|
|
||||||
if (sslCertChainPath_.has_value() &&
|
|
||||||
(!sslCertPath_.has_value() || !sslKeyPath_.has_value()))
|
|
||||||
{
|
|
||||||
JLOG(journal_.error())
|
|
||||||
<< "ssl_cert_chain specified for gRPC without both ssl_cert and ssl_key; "
|
|
||||||
<< "this is an invalid TLS configuration";
|
|
||||||
Throw<std::runtime_error>(
|
|
||||||
"Invalid gRPC TLS configuration: ssl_cert_chain requires both ssl_cert and "
|
|
||||||
"ssl_key");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate TLS configuration consistency: ssl_client_ca only makes sense when TLS is
|
|
||||||
// enabled
|
|
||||||
if (sslClientCAPath_.has_value() && (!sslCertPath_.has_value() || !sslKeyPath_.has_value()))
|
|
||||||
{
|
|
||||||
JLOG(journal_.error())
|
|
||||||
<< "ssl_client_ca specified for gRPC without both ssl_cert and ssl_key; "
|
|
||||||
<< "this is an invalid TLS configuration";
|
|
||||||
Throw<std::runtime_error>(
|
|
||||||
"Invalid gRPC TLS configuration: ssl_client_ca requires both ssl_cert and ssl_key");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -602,104 +558,6 @@ GRPCServerImpl::setupListeners()
|
|||||||
return requests;
|
return requests;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<grpc::ServerCredentials>
|
|
||||||
GRPCServerImpl::createServerCredentials()
|
|
||||||
{
|
|
||||||
if (not sslCertPath_.has_value() or not sslKeyPath_.has_value())
|
|
||||||
{
|
|
||||||
JLOG(journal_.info()) << "Configuring gRPC server without TLS";
|
|
||||||
return grpc::InsecureServerCredentials();
|
|
||||||
}
|
|
||||||
|
|
||||||
JLOG(journal_.info()) << "Configuring gRPC server with TLS";
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
boost::system::error_code ec;
|
|
||||||
grpc::SslServerCredentialsOptions sslOpts;
|
|
||||||
grpc::SslServerCredentialsOptions::PemKeyCertPair keyCertPair;
|
|
||||||
|
|
||||||
std::string const certContents = getFileContents(ec, *sslCertPath_);
|
|
||||||
if (ec)
|
|
||||||
{
|
|
||||||
JLOG(journal_.error()) << "Failed to read gRPC SSL certificate file: " << *sslCertPath_
|
|
||||||
<< " - " << ec.message(); // LCOV_EXCL_LINE
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string const keyContents = getFileContents(ec, *sslKeyPath_);
|
|
||||||
if (ec)
|
|
||||||
{
|
|
||||||
JLOG(journal_.error()) << "Failed to read gRPC SSL key file: " << *sslKeyPath_ << " - "
|
|
||||||
<< ec.message(); // LCOV_EXCL_LINE
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
keyCertPair.private_key = keyContents;
|
|
||||||
|
|
||||||
// Read intermediate CA certificates for server certificate chain (optional)
|
|
||||||
std::string certChainContents;
|
|
||||||
if (sslCertChainPath_.has_value())
|
|
||||||
{
|
|
||||||
certChainContents = getFileContents(ec, *sslCertChainPath_);
|
|
||||||
if (ec)
|
|
||||||
{
|
|
||||||
JLOG(journal_.error())
|
|
||||||
<< "Failed to read gRPC SSL cert chain file: " << *sslCertChainPath_ << " - "
|
|
||||||
<< ec.message(); // LCOV_EXCL_LINE
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read CA certificate for client verification (mTLS, optional)
|
|
||||||
if (sslClientCAPath_.has_value())
|
|
||||||
{
|
|
||||||
auto const clientCAContents = getFileContents(ec, *sslClientCAPath_);
|
|
||||||
if (ec)
|
|
||||||
{
|
|
||||||
JLOG(journal_.error())
|
|
||||||
<< "Failed to read gRPC SSL client CA file: " << *sslClientCAPath_ << " - "
|
|
||||||
<< ec.message(); // LCOV_EXCL_LINE
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (clientCAContents.empty())
|
|
||||||
{
|
|
||||||
JLOG(journal_.error())
|
|
||||||
<< "Empty/truncated gRPC SSL client CA file: " << *sslClientCAPath_
|
|
||||||
<< " - failed to configure mutual TLS"; // LCOV_EXCL_LINE
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
sslOpts.pem_root_certs = clientCAContents;
|
|
||||||
sslOpts.client_certificate_request =
|
|
||||||
GRPC_SSL_REQUEST_AND_REQUIRE_CLIENT_CERTIFICATE_AND_VERIFY;
|
|
||||||
JLOG(journal_.info()) << "gRPC mutual TLS enabled - client certificates will be "
|
|
||||||
"required and verified";
|
|
||||||
}
|
|
||||||
|
|
||||||
// Combine server cert with intermediate CA certs for complete chain
|
|
||||||
keyCertPair.cert_chain = certContents;
|
|
||||||
if (!certChainContents.empty())
|
|
||||||
{
|
|
||||||
keyCertPair.cert_chain += '\n' + certChainContents;
|
|
||||||
JLOG(journal_.info()) << "gRPC server certificate chain configured with "
|
|
||||||
"intermediate CA certificates"; // LCOV_EXCL_LINE
|
|
||||||
}
|
|
||||||
|
|
||||||
sslOpts.pem_key_cert_pairs.push_back(keyCertPair);
|
|
||||||
|
|
||||||
JLOG(journal_.info()) << "gRPC TLS credentials configured successfully"; // LCOV_EXCL_LINE
|
|
||||||
return grpc::SslServerCredentials(sslOpts);
|
|
||||||
}
|
|
||||||
catch (std::exception const& e)
|
|
||||||
{
|
|
||||||
JLOG(journal_.error()) << "Exception while configuring gRPC TLS: "
|
|
||||||
<< e.what(); // LCOV_EXCL_LINE
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool
|
bool
|
||||||
GRPCServerImpl::start()
|
GRPCServerImpl::start()
|
||||||
{
|
{
|
||||||
@@ -707,63 +565,24 @@ GRPCServerImpl::start()
|
|||||||
if (serverAddress_.empty())
|
if (serverAddress_.empty())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
// Determine TLS mode for logging
|
JLOG(journal_.info()) << "Starting gRPC server at " << serverAddress_;
|
||||||
bool const tlsEnabled = sslCertPath_.has_value() && sslKeyPath_.has_value();
|
|
||||||
bool const mtlsEnabled = tlsEnabled && sslClientCAPath_.has_value();
|
|
||||||
|
|
||||||
std::string tlsMode = "without TLS";
|
|
||||||
if (mtlsEnabled)
|
|
||||||
{
|
|
||||||
tlsMode = "with mutual TLS (mTLS)";
|
|
||||||
}
|
|
||||||
else if (tlsEnabled)
|
|
||||||
{
|
|
||||||
tlsMode = "with TLS";
|
|
||||||
}
|
|
||||||
|
|
||||||
JLOG(journal_.info()) << "Starting gRPC server at " << serverAddress_ << " "
|
|
||||||
<< tlsMode; // LCOV_EXCL_LINE
|
|
||||||
|
|
||||||
grpc::ServerBuilder builder;
|
grpc::ServerBuilder builder;
|
||||||
|
|
||||||
|
// Listen on the given address without any authentication mechanism.
|
||||||
|
// Actually binded port will be returned into "port" variable.
|
||||||
int port = 0;
|
int port = 0;
|
||||||
|
builder.AddListeningPort(serverAddress_, grpc::InsecureServerCredentials(), &port);
|
||||||
// Create credentials (TLS or insecure) based on configuration
|
|
||||||
auto credentials = createServerCredentials();
|
|
||||||
if (!credentials)
|
|
||||||
{
|
|
||||||
JLOG(journal_.error()) << "Failed to create gRPC server credentials for " << serverAddress_
|
|
||||||
<< " (TLS mode: " << tlsMode
|
|
||||||
<< ") - server will not start"; // LCOV_EXCL_LINE
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add listening port with appropriate credentials
|
|
||||||
builder.AddListeningPort(serverAddress_, credentials, &port);
|
|
||||||
|
|
||||||
// Register "service_" as the instance through which we'll communicate with
|
// Register "service_" as the instance through which we'll communicate with
|
||||||
// clients. In this case it corresponds to an *asynchronous* service.
|
// clients. In this case it corresponds to an *asynchronous* service.
|
||||||
builder.RegisterService(&service_);
|
builder.RegisterService(&service_);
|
||||||
|
|
||||||
// Get hold of the completion queue used for the asynchronous communication
|
// Get hold of the completion queue used for the asynchronous communication
|
||||||
// with the gRPC runtime.
|
// with the gRPC runtime.
|
||||||
cq_ = builder.AddCompletionQueue();
|
cq_ = builder.AddCompletionQueue();
|
||||||
|
|
||||||
// Finally assemble the server.
|
// Finally assemble the server.
|
||||||
server_ = builder.BuildAndStart();
|
server_ = builder.BuildAndStart();
|
||||||
serverPort_ = static_cast<std::uint16_t>(port);
|
serverPort_ = static_cast<std::uint16_t>(port);
|
||||||
|
|
||||||
if (serverPort_ != 0u)
|
|
||||||
{
|
|
||||||
JLOG(journal_.info()) << "gRPC server started successfully on port " << serverPort_;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
JLOG(journal_.error())
|
|
||||||
<< "Failed to start gRPC server at " << serverAddress_ << " (TLS mode: " << tlsMode
|
|
||||||
<< "); Possible causes: address already in use, invalid address format, or permission "
|
|
||||||
"denied"; // LCOV_EXCL_LINE
|
|
||||||
}
|
|
||||||
|
|
||||||
return static_cast<bool>(serverPort_);
|
return static_cast<bool>(serverPort_);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -66,13 +66,6 @@ private:
|
|||||||
|
|
||||||
std::vector<boost::asio::ip::address> secureGatewayIPs_;
|
std::vector<boost::asio::ip::address> secureGatewayIPs_;
|
||||||
|
|
||||||
// TLS certificate paths
|
|
||||||
std::optional<std::string> sslCertPath_;
|
|
||||||
std::optional<std::string> sslKeyPath_;
|
|
||||||
std::optional<std::string> sslCertChainPath_; // Intermediate CA certs for server cert chain
|
|
||||||
std::optional<std::string>
|
|
||||||
sslClientCAPath_; // CA cert for client certificate verification (mTLS)
|
|
||||||
|
|
||||||
beast::Journal journal_;
|
beast::Journal journal_;
|
||||||
|
|
||||||
// typedef for function to bind a listener
|
// typedef for function to bind a listener
|
||||||
@@ -131,10 +124,6 @@ public:
|
|||||||
getEndpoint() const;
|
getEndpoint() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Create server credentials (TLS or insecure) based on configuration
|
|
||||||
std::shared_ptr<grpc::ServerCredentials>
|
|
||||||
createServerCredentials();
|
|
||||||
|
|
||||||
// Class encompassing the state and logic needed to serve a request.
|
// Class encompassing the state and logic needed to serve a request.
|
||||||
template <class Request, class Response>
|
template <class Request, class Response>
|
||||||
class CallData : public Processor,
|
class CallData : public Processor,
|
||||||
|
|||||||
Reference in New Issue
Block a user