]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
feat(dnsdist): Add IPCrypt2 PFX mode to RemoteLogAction
authorPieter Lexis <pieter.lexis@powerdns.com>
Mon, 15 Sep 2025 08:58:35 +0000 (10:58 +0200)
committerPieter Lexis <pieter.lexis@powerdns.com>
Thu, 2 Oct 2025 12:07:03 +0000 (14:07 +0200)
Signed-off-by: Pieter Lexis <pieter.lexis@powerdns.com>
pdns/dnsdistdist/Makefile.am
pdns/dnsdistdist/configure.ac
pdns/dnsdistdist/dnsdist-actions-definitions.yml
pdns/dnsdistdist/dnsdist-actions-factory.cc
pdns/dnsdistdist/dnsdist-actions-factory.hh
pdns/dnsdistdist/dnsdist-configuration-yaml.cc
pdns/dnsdistdist/dnsdist-ipcrypt2.cc [new file with mode: 0644]
pdns/dnsdistdist/dnsdist-ipcrypt2.hh [new file with mode: 0644]
pdns/dnsdistdist/dnsdist-lua-actions.cc
pdns/dnsdistdist/meson.build

index 951637a4c6fab6b57ddee4da5aa6f18397297e12..19a64dc7eb2d8c59a28838058c77bfca82fba48b 100644 (file)
@@ -7,6 +7,7 @@ AM_CPPFLAGS += $(SYSTEMD_CFLAGS) \
        $(NET_SNMP_CFLAGS) \
        $(NGHTTP2_CFLAGS) \
        $(LIBCAP_CFLAGS) \
+       $(IPCRYPT2_CFLAGS) \
        -I$(top_srcdir)/ext/protozero/include \
        -I$(top_srcdir)/dnsdist-rust-lib \
        -I$(top_builddir)/dnsdist-rust-lib \
@@ -22,6 +23,7 @@ SUBDIRS=dnsdist-rust-lib \
        dnsdist-rust-lib/rust \
        ext/arc4random \
        ext/ipcrypt \
+       ext/ipcrypt2 \
        ext/yahttp
 
 CLEANFILES = \
@@ -204,6 +206,7 @@ dnsdist_SOURCES = \
        dnsdist-healthchecks.cc dnsdist-healthchecks.hh \
        dnsdist-idstate.cc dnsdist-idstate.hh \
        dnsdist-internal-queries.cc dnsdist-internal-queries.hh \
+       dnsdist-ipcrypt2.cc dnsdist-ipcrypt2.hh \
        dnsdist-kvs.hh dnsdist-kvs.cc \
        dnsdist-lbpolicies.cc dnsdist-lbpolicies.hh \
        dnsdist-lua-actions.cc \
@@ -439,6 +442,7 @@ dnsdist_LDADD = \
        $(NET_SNMP_LIBS) \
        $(LIBCAP_LIBS) \
        $(IPCRYPT_LIBS) \
+       $(IPCRYPT2_LIBS) \
        $(ARC4RANDOM_LIBS)
 
 testrunner_LDFLAGS = \
index fbfd93d68e417d5ef981753b1767cf11920fb9f8..afe8ce47289c6ef86f29a331c201c6e53e2af93b 100644 (file)
@@ -66,6 +66,8 @@ AC_SUBST([YAHTTP_CFLAGS], ['-I$(top_srcdir)/ext/yahttp'])
 AC_SUBST([YAHTTP_LIBS], ['$(top_builddir)/ext/yahttp/yahttp/libyahttp.la'])
 AC_SUBST([IPCRYPT_CFLAGS], ['-I$(top_srcdir)/ext/ipcrypt'])
 AC_SUBST([IPCRYPT_LIBS], ['$(top_builddir)/ext/ipcrypt/libipcrypt.la'])
+AC_SUBST([IPCRYPT2_CFLAGS], ['-I$(top_srcdir)/ext/ipcrypt2/include'])
+AC_SUBST([IPCRYPT2_LIBS], ['$(top_builddir)/ext/ipcrypt2/libipcrypt2.la'])
 AC_SUBST([ARC4RANDOM_LIBS], ['$(top_builddir)/ext/arc4random/libarc4random.la'])
 
 AC_CHECK_HEADERS([sys/random.h])
@@ -204,7 +206,8 @@ AC_CONFIG_FILES([Makefile
         ext/arc4random/Makefile
         ext/yahttp/Makefile
         ext/yahttp/yahttp/Makefile
-        ext/ipcrypt/Makefile])
+        ext/ipcrypt/Makefile
+        ext/ipcrypt2/Makefile])
 
 AC_OUTPUT
 
index 32e36556bfcaca5a1c3b1a82a8912a43e8bde73a..05e7f99c9802625339250cf28c276a3de5138889 100644 (file)
@@ -291,7 +291,14 @@ The function will be invoked in a per-thread Lua state, without access to the gl
     - name: "ip_encrypt_key"
       type: "String"
       default: ""
-      description: "A key, that can be generated via the :func:`makeIPCipherKey` function, to encrypt the IP address of the requestor for anonymization purposes. The encryption is done using ipcrypt for IPv4 and a 128-bit AES ECB operation for IPv6"
+      description: "A key to encrypt the IP address of the requestor for anonymization purposes. For the \"legacy\" method, it can be generated via the :func:`makeIPCipherKey` function. The encryption method can be set using ``ip_encrypt_method``"
+    - name: "ip_encrypt_method"
+      type: "String"
+      default: "legacy"
+      description: "
+        The method to encrypt the IP addresses with.
+          * legacy: The encryption is done using ipcrypt for IPv4 and a 128-bit AES ECB operation for IPv6. This is the default.
+          * ipcrypt-pfx: IPCrypt2, using prefix-preserving encryption. See `the ipcrypt website <https://ipcrypt-std.github.io/>__`. ``ip_encrypt_key`` must be 32 bytes."
     - name: "export_tags"
       type: "Vec<String>"
       default: ""
index 320428d2769f106be23a2810e45f1852a0997ba0..694d733b285de11abc3c61770780d3277451fed1 100644 (file)
@@ -19,6 +19,7 @@
  * along with this program; if not, write to the Free Software
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  */
+#include <optional>
 #include <unordered_map>
 
 #include "dnsdist-actions-factory.hh"
@@ -44,6 +45,8 @@
 #include "ednsoptions.hh"
 #include "fstrm_logger.hh"
 #include "ipcipher.hh"
+#include "dnsdist-ipcrypt2.hh"
+#include "iputils.hh"
 #include "remote_logger.hh"
 #include "svc-records.hh"
 #include "threadname.hh"
@@ -1598,8 +1601,11 @@ class RemoteLogAction : public DNSAction, public boost::noncopyable
 public:
   // this action does not stop the processing
   RemoteLogAction(RemoteLogActionConfiguration& config) :
-    d_tagsToExport(std::move(config.tagsToExport)), d_metas(std::move(config.metas)), d_logger(config.logger), d_alterFunc(std::move(config.alterQueryFunc)), d_serverID(config.serverID), d_ipEncryptKey(config.ipEncryptKey)
+    d_tagsToExport(std::move(config.tagsToExport)), d_metas(std::move(config.metas)), d_logger(config.logger), d_alterFunc(std::move(config.alterQueryFunc)), d_serverID(config.serverID), d_ipEncryptKey(config.ipEncryptKey), d_ipEncryptMethod(config.ipEncryptMethod)
   {
+    if (!d_ipEncryptKey.empty() && d_ipEncryptMethod == "ipcrypt-pfx") {
+      d_ipcrypt2 = pdns::ipcrypt2::IPCrypt2(pdns::ipcrypt2::IPCryptMethod::pfx, d_ipEncryptKey);
+    }
   }
 
   DNSAction::Action operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const override
@@ -1618,10 +1624,15 @@ public:
     }
 
 #ifdef HAVE_IPCIPHER
-    if (!d_ipEncryptKey.empty()) {
+    if (!d_ipEncryptKey.empty() && d_ipEncryptMethod == "legacy") {
       message.setRequestor(encryptCA(dnsquestion->ids.origRemote, d_ipEncryptKey));
     }
 #endif /* HAVE_IPCIPHER */
+    if (d_ipcrypt2) {
+      auto encryptedAddress = d_ipcrypt2->encrypt(dnsquestion->ids.origRemote);
+      encryptedAddress.setPort(dnsquestion->ids.origRemote.getPort());
+      message.setRequestor(encryptedAddress);
+    }
 
     if (d_tagsToExport) {
       addTagsToProtobuf(message, *dnsquestion, *d_tagsToExport);
@@ -1656,6 +1667,8 @@ private:
   std::optional<std::function<void(DNSQuestion*, DNSDistProtoBufMessage*)>> d_alterFunc;
   std::string d_serverID;
   std::string d_ipEncryptKey;
+  std::string d_ipEncryptMethod;
+  std::optional<pdns::ipcrypt2::IPCrypt2> d_ipcrypt2{std::nullopt};
 };
 
 #endif /* DISABLE_PROTOBUF */
index 7ef95d3810d03982fc5fa0650ce8afd3728f6616..589467046859f251b43c2b2e5e63b630475648df 100644 (file)
@@ -113,6 +113,7 @@ struct RemoteLogActionConfiguration
   std::shared_ptr<RemoteLoggerInterface> logger;
   std::string serverID;
   std::string ipEncryptKey;
+  std::string ipEncryptMethod{"legacy"};
   std::optional<std::string> exportExtendedErrorsToMeta{std::nullopt};
   bool includeCNAME{false};
 };
index e2b36bc2c88d1b1bef450082dd4291f2245a81b2..a84c4f8ead7990f8335c1a0444114f220a47b617 100644 (file)
@@ -1666,6 +1666,7 @@ std::shared_ptr<DNSActionWrapper> getRemoteLogAction(const RemoteLogActionConfig
   actionConfig.logger = std::move(logger);
   actionConfig.serverID = std::string(config.server_id);
   actionConfig.ipEncryptKey = std::string(config.ip_encrypt_key);
+  actionConfig.ipEncryptMethod = std::string(config.ip_encrypt_method);
   for (const auto& meta : config.metas) {
     actionConfig.metas.emplace_back(std::string(meta.key), ProtoBufMetaKey(std::string(meta.value)));
   }
diff --git a/pdns/dnsdistdist/dnsdist-ipcrypt2.cc b/pdns/dnsdistdist/dnsdist-ipcrypt2.cc
new file mode 100644 (file)
index 0000000..6e2c0a0
--- /dev/null
@@ -0,0 +1,83 @@
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <memory>
+#include <stdexcept>
+#include <string>
+
+#include "dnsdist-ipcrypt2.hh"
+#include "ipcrypt2.h"
+#include "iputils.hh"
+
+// ipcrypt2 namespace does not have to be dnsdist-specific
+namespace pdns::ipcrypt2
+{
+IPCrypt2::IPCrypt2(const IPCryptMethod& method, const std::string& key) :
+  d_method(method)
+{
+  switch (method) {
+  case IPCryptMethod::pfx: {
+    if (key.size() != IPCRYPT_PFX_KEYBYTES) {
+      throw std::runtime_error("Key for IPCrypt PFX method is not " + std::to_string(IPCRYPT_PFX_KEYBYTES) + " bytes");
+    }
+    d_ipcryptCtxPfx = std::make_unique<IPCryptPFX>();
+    auto ret = ipcrypt_pfx_init(d_ipcryptCtxPfx.get(), reinterpret_cast<const uint8_t*>(key.data()));
+    if (ret != 0) {
+      throw std::runtime_error("Could not initialize IPCrypt2 PFX context");
+    }
+  } break;
+  default:
+    throw std::runtime_error("Unsupported IPCrypt2 method");
+    break;
+  }
+}
+
+IPCrypt2::~IPCrypt2()
+{
+  switch (d_method) {
+  case IPCryptMethod::pfx:
+    if (d_ipcryptCtxPfx != nullptr) {
+      ipcrypt_pfx_deinit(d_ipcryptCtxPfx.get());
+    }
+    break;
+  default:
+    return;
+  }
+};
+
+ComboAddress IPCrypt2::encrypt(const ComboAddress& address) const
+{
+  switch (d_method) {
+  case IPCryptMethod::pfx: {
+    std::string encryptedIP;
+    encryptedIP.resize(IPCRYPT_MAX_IP_STR_BYTES);
+    auto ret = ipcrypt_pfx_encrypt_ip_str(d_ipcryptCtxPfx.get(), encryptedIP.data(), address.toString().c_str());
+    // XXX: Do we *need* to resize?
+    encryptedIP.resize(ret);
+    return ComboAddress(encryptedIP);
+  } break;
+  default:
+    throw std::runtime_error("Unsupported method");
+    break;
+  }
+}
+}
diff --git a/pdns/dnsdistdist/dnsdist-ipcrypt2.hh b/pdns/dnsdistdist/dnsdist-ipcrypt2.hh
new file mode 100644 (file)
index 0000000..9222f37
--- /dev/null
@@ -0,0 +1,61 @@
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#pragma once
+
+#include <memory>
+#include <string>
+
+#include "iputils.hh"
+#include "ipcrypt2.h"
+
+namespace pdns::ipcrypt2
+{
+
+enum IPCryptMethod : uint8_t
+{
+  deterministic,
+  pfx,
+  nd,
+  ndx
+};
+
+class IPCrypt2
+{
+public:
+  IPCrypt2(const IPCryptMethod& method, const std::string& key);
+
+  IPCrypt2(IPCrypt2&& rhs) = default;
+  IPCrypt2& operator=(IPCrypt2&& rhs) = default;
+
+  IPCrypt2(const IPCrypt2& orig) = delete;
+  IPCrypt2& operator=(const IPCrypt2& orig) = delete;
+
+  ~IPCrypt2();
+
+  [[nodiscard]] ComboAddress encrypt(const ComboAddress& address) const;
+
+private:
+  std::unique_ptr<struct IPCryptPFX> d_ipcryptCtxPfx;
+  IPCryptMethod d_method;
+};
+}
index 11066b005e322ced28853d47fa4eab5c79c09cce..3d10df07531994b5b6113960a5a34d034e3e42b8 100644 (file)
@@ -29,6 +29,7 @@
 #include "dnsdist-rule-chains.hh"
 #include "dnstap.hh"
 #include "remote_logger.hh"
+#include <stdexcept>
 
 template <typename ActionT, typename IdentifierT>
 static void addAction(IdentifierT identifier, const luadnsrule_t& var, const std::shared_ptr<ActionT>& action, boost::optional<luaruleparams_t>& params)
@@ -284,6 +285,9 @@ void setupLuaActions(LuaContext& luaCtx)
   });
 
 #ifndef DISABLE_PROTOBUF
+  // Used for both RemoteLogAction and RemoteLogResponseAction
+  static const std::array<std::string, 2> s_validIpEncryptMethods = {"legacy", "ipcrypt-pfx"};
+
   luaCtx.writeFunction("RemoteLogAction", [](std::shared_ptr<RemoteLoggerInterface> logger, boost::optional<dnsdist::actions::ProtobufAlterFunction> alterFunc, boost::optional<LuaAssociativeTable<std::string>> vars, boost::optional<LuaAssociativeTable<std::string>> metas) {
     if (logger) {
       // avoids potentially-evaluated-expression warning with clang.
@@ -302,6 +306,7 @@ void setupLuaActions(LuaContext& luaCtx)
     }
     getOptionalValue<std::string>(vars, "serverID", config.serverID);
     getOptionalValue<std::string>(vars, "ipEncryptKey", config.ipEncryptKey);
+    getOptionalValue<std::string>(vars, "ipEncryptMethod", config.ipEncryptMethod);
     getOptionalValue<std::string>(vars, "exportTags", tags);
 
     if (metas) {
@@ -310,6 +315,10 @@ void setupLuaActions(LuaContext& luaCtx)
       }
     }
 
+    if (std::find(s_validIpEncryptMethods.begin(), s_validIpEncryptMethods.end(), config.ipEncryptMethod) == s_validIpEncryptMethods.end()) {
+      throw std::runtime_error("Invalid IP Encryption method in RemoteLogAction");
+    }
+
     if (!tags.empty()) {
       config.tagsToExport = std::unordered_set<std::string>();
       if (tags != "*") {
index 75ff2e05994b2294c7ba2dd59f07b27ca27bc483..4cc1eb63664c5adbd63ad1e7a470707d7963ac47 100644 (file)
@@ -97,6 +97,7 @@ dep_pdns = declare_dependency(include_directories: include_directories('.', src_
 # Ext
 subdir('ext' / 'arc4random')
 subdir('ext' / 'ipcrypt')
+subdir('ext' / 'ipcrypt2')
 subdir('ext' / 'json11')
 subdir('ext' / 'luawrapper')
 subdir('ext' / 'protozero')
@@ -143,6 +144,7 @@ common_sources += files(
   src_dir / 'dnsdist-healthchecks.cc',
   src_dir / 'dnsdist-idstate.cc',
   src_dir / 'dnsdist-internal-queries.cc',
+  src_dir / 'dnsdist-ipcrypt2.cc',
   src_dir / 'dnsdist-kvs.cc',
   src_dir / 'dnsdist-lbpolicies.cc',
   src_dir / 'dnsdist-lua-actions.cc',
@@ -381,6 +383,7 @@ deps = [
   dep_dnstap,
   dep_htmlfiles,
   dep_ipcrypt,
+  dep_ipcrypt2,
   dep_libcap,
   dep_libcrypto,
   dep_gnutls,