With settable verification mode, provider, subject name and some more.
Signed-off-by: Otto Moerbeek <otto.moerbeek@open-xchange.com>
{
};
-static bool tcpconnect(const OptLog& log, const ComboAddress& remote, const std::optional<ComboAddress> localBind, TCPOutConnectionManager::Connection& connection, bool& dnsOverTLS, const std::string& nsName)
+static bool tcpconnect(const OptLog& log, const ComboAddress& remote, const std::optional<ComboAddress> localBind, TCPOutConnectionManager::Connection& connection, bool& dnsOverTLS, const std::string& nsName, std::string& subjectName)
{
dnsOverTLS = SyncRes::s_dot_to_port_853 && remote.getPort() == 853;
}
std::shared_ptr<TLSCtx> tlsCtx{nullptr};
+ bool subjectIsAddress = false;
if (dnsOverTLS) {
- tlsCtx = TCPOutConnectionManager::getTLSContext(nsName, remote);
+ subjectName = nsName;
+ std::string subjectAddress;
+ tlsCtx = TCPOutConnectionManager::getTLSContext(nsName, remote, connection.d_verboseLogging, subjectName, subjectAddress);
if (tlsCtx == nullptr) {
g_slogout->info(Logr::Error, "DoT requested but not available", "server", Logging::Loggable(remote));
dnsOverTLS = false;
}
+ else if (subjectName.empty() && !subjectAddress.empty()) {
+ subjectName = subjectAddress;
+ subjectIsAddress = true;
+ }
}
- connection.d_handler = std::make_shared<TCPIOHandler>(nsName, false, sock.releaseHandle(), timeout, tlsCtx);
+ connection.d_handler = std::make_shared<TCPIOHandler>(subjectName, subjectIsAddress, sock.releaseHandle(), timeout, tlsCtx);
connection.d_local = localBind;
// Returned state ignored
// This can throw an exception, retry will need to happen at higher level
}
static LWResult::Result tcpsendrecv(const ComboAddress& ip, TCPOutConnectionManager::Connection& connection,
- ComboAddress& localip, const vector<uint8_t>& vpacket, size_t& len, PacketBuffer& buf)
+ ComboAddress& localip, const vector<uint8_t>& vpacket, size_t& len, PacketBuffer& buf,
+ const std::string& nsName, const std::string subjectName)
{
socklen_t slen = ip.getSocklen();
uint16_t tlen = htons(vpacket.size());
LWResult::Result ret = asendtcp(packet, connection.d_handler);
if (ret != LWResult::Result::Success) {
- auto result = connection.d_handler->getVerifyResult();
- cerr << "ASENDTCP RETURNED FAIL " << ip.toString() << ' ' << result.first << ' ' << result.second << endl;
+ if (connection.d_handler->isTLS() && connection.d_verboseLogging) {
+ auto result = connection.d_handler->getVerifyResult();
+ g_slogout->info(Logr::Error, "Failed to setup TLS connection",
+ "errorcode", Logging::Loggable(result.first),
+ "remote", Logging::Loggable(ip),
+ "nsname", Logging::Loggable(nsName),
+ "subjectName", Logging::Loggable(subjectName),
+ "tlsmessage", Logging::Loggable(result.second));
+ }
return ret;
}
// peer has closed it on error, so we retry. At some point we
// *will* get a new connection, so this loop is not endless.
isNew = true; // tcpconnect() might throw for new connections. In that case, we want to break the loop, scanbuild complains here, which is a false positive afaik
- isNew = tcpconnect(log, address, addressToBindTo, connection, dnsOverTLS, nsName);
- ret = tcpsendrecv(address, connection, localip, vpacket, len, buf);
+ std::string subjectName;
+ isNew = tcpconnect(log, address, addressToBindTo, connection, dnsOverTLS, nsName, subjectName);
+ ret = tcpsendrecv(address, connection, localip, vpacket, len, buf, nsName, subjectName);
#ifdef HAVE_FSTRM
if (fstrmQEnabled) {
logFstreamQuery(fstrmLoggers, queryTime, localip, address, !dnsOverTLS ? DnstapMessage::ProtocolType::DoTCP : DnstapMessage::ProtocolType::DoT, context.d_auth, vpacket);
// Cookie info already has been added to packet, so we must retry from a higher level
auto lock = s_cookiestore.lock();
lock->erase(address);
+ VLOG(log, "BindError remote: " << address.toString() << " localAddress: " << (addressToBindTo ? addressToBindTo->toString() : "none") << endl);
return LWResult::Result::BindError;
}
- catch (const NetworkError&) {
+ catch (const NetworkError& nwe) {
+ VLOG(log, "NetworkException: " << address.toString() << ": " << nwe.what() << endl);
ret = LWResult::Result::OSLimitError; // OS limits error
}
- catch (const runtime_error&) {
+ catch (const runtime_error& rte) {
+ VLOG(log, "runtime_error: " << address.toString() << ": " << rte.what() << endl);
ret = LWResult::Result::OSLimitError; // OS limits error (PermanentError is transport related)
}
} while (!isNew);
TCPOutConnectionManager::s_maxIdlePerAuth = ::arg().asNum("tcp-out-max-idle-per-auth");
TCPOutConnectionManager::s_maxQueries = ::arg().asNum("tcp-out-max-queries");
TCPOutConnectionManager::s_maxIdlePerThread = ::arg().asNum("tcp-out-max-idle-per-thread");
+ TCPOutConnectionManager::setupOutgoingTLSTables();
g_gettagNeedsEDNSOptions = ::arg().mustDo("gettag-needs-edns-options");
ListSubnets = auto()
ListTrustAnchors = auto()
ListZoneToCaches = auto()
+ ListOutgoingTLSConfigurations = auto()
String = auto()
Uint64 = auto()
listOfStructuredTypes = (LType.ListAuthZones, LType.ListForwardZones, LType.ListTrustAnchors, LType.ListNegativeTrustAnchors,
LType.ListProtobufServers, LType.ListDNSTapFrameStreamServers, LType.ListDNSTapNODFrameStreamServers,
LType.ListSortLists, LType.ListRPZs, LType.ListZoneToCaches, LType.ListAllowedAdditionalQTypes,
- LType.ListProxyMappings, LType.ListForwardingCatalogZones, LType.ListIncomingWSConfigs)
+ LType.ListProxyMappings, LType.ListForwardingCatalogZones, LType.ListIncomingWSConfigs,
+ LType.ListOutgoingTLSConfigurations)
def get_olddoc_typename(typ):
"""Given a type from table.py, return the old-style type name"""
return 'Sequence of `ForwardingCatalogZone`_'
if typ == LType.ListIncomingWSConfigs:
return 'Sequence of `IncomingWSConfig`_'
+ if typ == LType.ListOutgoingTLSConfigurations:
+ return 'Sequence of `OutgoingTLSConfiguration`_'
return 'Unknown2' + str(typ)
def get_default_olddoc_value(typ, val):
// #[serde(default, skip_serializing_if = "crate::is_default")]
// password: String, Not currently supported, as rusttls does not support this out of the box
}
+
#[derive(Deserialize, Serialize, Clone, Debug, PartialEq)]
#[serde(deny_unknown_fields)]
pub struct IncomingWSConfig {
tls: IncomingTLS,
}
+#[derive(Deserialize, Serialize, Clone, Debug, PartialEq)]
+#[serde(deny_unknown_fields)]
+struct OutgoingTLSConfiguration {
+ #[serde(default, skip_serializing_if = "crate::is_default")]
+ name: String,
+ #[serde(default, skip_serializing_if = "crate::is_default")]
+ provider: String,
+ #[serde(default, skip_serializing_if = "crate::is_default")]
+ suffixes: Vec<String>,
+ #[serde(default, skip_serializing_if = "crate::is_default")]
+ subnets: Vec<String>,
+ #[serde(default, skip_serializing_if = "crate::is_default")]
+ validate_certificate: bool,
+ #[serde(default, skip_serializing_if = "crate::is_default")]
+ ca_store: String,
+ #[serde(default, skip_serializing_if = "crate::is_default")]
+ verbose_logging: bool,
+ #[serde(default, skip_serializing_if = "crate::is_default")]
+ subject_name: String,
+ #[serde(default, skip_serializing_if = "crate::is_default")]
+ subject_address: String,
+ #[serde(default, skip_serializing_if = "crate::is_default")]
+ ciphers: String,
+ #[serde(default, skip_serializing_if = "crate::is_default")]
+ ciphers_tls_13: String,
+}
+
// Two structs used to generated YAML based on a vector of name to value mappings
// Cannot use Enum as CXX has only very basic Enum support
struct Value {
}
}
+impl OutgoingTLSConfiguration {
+ pub fn validate(&self, field: &str) -> Result<(), ValidationError> {
+ if self.name.is_empty() {
+ let msg = format!("{}: value may not be empty", field);
+ return Err(ValidationError { msg });
+ }
+ validate_vec(
+ &(field.to_string() + ".suffixes"),
+ &self.suffixes,
+ validate_name,
+ )?;
+ validate_vec(
+ &(field.to_string() + ".subnets"),
+ &self.subnets,
+ validate_subnet,
+ )?;
+ Ok(())
+ }
+}
+
#[allow(clippy::ptr_arg)] //# Avoids creating a rust::Slice object on the C++ side.
pub fn validate_auth_zones(field: &str, vec: &Vec<AuthZone>) -> Result<(), ValidationError> {
validate_vec(field, vec, |field, element| element.validate(field))
''',
'versionadded': '5.3.0',
},
+ {
+ 'name' : 'tls_configurations',
+ 'section' : 'outgoing',
+ 'type' : LType.ListOutgoingTLSConfigurations,
+ 'default' : '',
+ 'help' : 'Sequence of OutgoingTLSConfiguration',
+ 'doc' : '''
+Sequence of OutgoingTLSConfiguration.`
+ ''',
+ 'skip-old' : 'No equivalent old style setting',
+ 'versionadded': '5.4.0',
+ 'runtime': ['reload-lua-config', 'reload-yaml'], # XXX
+ },
]
#undef CERT
#include "syncres.hh"
+#include "dnsname.hh"
+#include "rec-main.hh"
+
+#include "cxxsettings.hh"
timeval TCPOutConnectionManager::s_maxIdleTime;
size_t TCPOutConnectionManager::s_maxQueries;
return Connection{};
}
-std::shared_ptr<TLSCtx> TCPOutConnectionManager::getTLSContext(const std::string& name, const ComboAddress& address)
+static SuffixMatchTree<pdns::rust::settings::rec::OutgoingTLSConfiguration> s_suffixToConfig;
+static NetmaskTree<pdns::rust::settings::rec::OutgoingTLSConfiguration> s_netmaskToConfig;
+
+void TCPOutConnectionManager::setupOutgoingTLSTables()
{
+ auto settings = g_yamlStruct.lock();
+ auto& vec = settings->outgoing.tls_configurations;
+ for (const auto& entry : vec) {
+ for (const auto& element : entry.suffixes) {
+ DNSName name = DNSName(std::string(element));
+ auto copy = entry;
+ s_suffixToConfig.add(name, std::move(copy));
+ }
+ for (const auto& element : entry.subnets) {
+ s_netmaskToConfig.insert(std::string(element)).second = entry;
+ }
+ }
+}
+
+std::shared_ptr<TLSCtx> TCPOutConnectionManager::getTLSContext(const std::string& name, const ComboAddress& address, bool& verboseLogging, std::string& subjectName, std::string &subjectAddress)
+{
+ pdns::rust::settings::rec::OutgoingTLSConfiguration* config{nullptr};
+
+ if (auto* node = s_netmaskToConfig.lookup(address); node != nullptr) {
+ config = &node->second;
+ }
+ else if (auto* found = s_suffixToConfig.lookup(DNSName(name)); found != nullptr) {
+ config = found;
+ }
+
TLSContextParameters tlsParams;
tlsParams.d_provider = "openssl";
- tlsParams.d_validateCertificates = true;
- // tlsParams.d_caStore
+ tlsParams.d_validateCertificates = false;
+ if (config != nullptr) {
+ tlsParams.d_provider = std::string(config->provider);
+ tlsParams.d_validateCertificates = config->validate_certificate;
+ tlsParams.d_caStore = std::string(config->ca_store);
+ if (!config->subject_name.empty()) {
+ subjectName = std::string(config->subject_name);
+ };
+ if (!config->subject_address.empty()) {
+ subjectAddress = std::string(config->subject_address);
+ };
+ verboseLogging = config->verbose_logging = true;
+ tlsParams.d_ciphers = std::string(config->ciphers);
+ tlsParams.d_ciphers13 = std::string(config->ciphers_tls_13);
+ }
return ::getTLSContext(tlsParams);
}
std::optional<ComboAddress> d_local;
timeval d_last_used{0, 0};
size_t d_numqueries{0};
+ bool d_verboseLogging{false};
};
using endpoints_t = std::pair<ComboAddress, std::optional<ComboAddress>>;
return new uint64_t(size()); // NOLINT(cppcoreguidelines-owning-memory): it's the API
}
- static std::shared_ptr<TLSCtx> getTLSContext(const std::string& name, const ComboAddress& address);
+ static void setupOutgoingTLSTables();
+ static std::shared_ptr<TLSCtx> getTLSContext(const std::string& name, const ComboAddress& address, bool& verboseLogging, std::string& subjectName, std::string& subjectAddress);
private:
// This does not take into account that we can have multiple connections with different hosts (via SNI) to the same IP.