Commit Graph
1106 Commits
Author SHA1 Message Date
Dirkjan Ochtman 7cedd7c11e Use new rustls PEM API 2024-11-13 13:50:27 -07:00
Dirkjan Ochtman 96034a91b9 Upgrade testcontainers-modules to 0.11 2024-11-13 13:50:27 -07:00
Dirkjan Ochtman 16c73dfff8 Drop direct tungstenite dependency in favor of tokio-tungstenite 2024-11-13 13:50:27 -07:00
Dirkjan Ochtman 18db31f204 Manage all external dependencies at the workspace level 2024-11-13 13:50:27 -07:00
Wez Furlong 8946447785 add missing duration string handling for remember_broken_tls 2024-11-08 07:42:19 -07:00
Wez Furlong f74f0a7556 add ehlo_domain to the set of predefined connection metadata values 2024-11-08 05:27:02 -07:00
Wez Furlong ea53531bc6 rocks: purge memtables when memory is low, export metrics
Export the memory statistics for each spool database to prometheus for
charting and tracking.

Allow the hosting application to request a cache purge and set up a
monitor task to do that when memory usage is too high.  That step will
print the memory that it reclaimed when it kicks in.
2024-11-07 16:29:11 -07:00
Wez Furlong a510e0d90c fix kcli double counting the ready queue size 2024-11-07 16:29:11 -07:00
Wez Furlong b31f24a5fd explicitly launch lruttl monitor
having it tied to a global initializer is prone to initialization
order issues, let's just make it explicitly a part of kumod's
startup sequence.
2024-11-07 09:03:54 -07:00
Wez Furlong 88dd509d87 try to avoid a startup race with headroom on the CI systems
I noticed that some end to end tests are load shedding, and I think
it is because we initialize headroom to 0 and are racing with
the memory monitor thread startup.

Let's default to non-zero instead.

I think this is manifesting in CI because it has noisy neighbors
on the VM hosts.
2024-11-07 08:42:02 -07:00
Wez Furlong 721b6581bc dns-resolver: minor simplification
refs: https://github.com/KumoCorp/kumomta/pull/306
2024-11-07 08:03:29 -07:00
Dirkjan Ochtman a8d08f1238 test: add basic integration test for SPF 2024-11-07 07:52:37 -07:00
Dirkjan Ochtman 51d71e0c27 tests: allow KumoDaemon::smtp_client() callers to pass in a host 2024-11-07 07:52:37 -07:00
Dirkjan Ochtman fdcdad47d3 mod-dns-resolver: wire up test resolver setup 2024-11-07 07:52:37 -07:00
Dirkjan Ochtman 55dcf3e6f9 kumod: add basic SPF Lua API 2024-11-07 07:52:37 -07:00
Dirkjan Ochtman 603fd50ec5 dns-resolver: improve TestResolver logic 2024-11-07 07:52:37 -07:00
Dirkjan Ochtman 5afdffd1f3 spf: improve handling for initial processing 2024-11-07 07:52:37 -07:00
Dirkjan Ochtman 5c5a5ad123 spf: impl Serialize for SpfDisposition 2024-11-07 07:52:37 -07:00
Wez Furlong 7a9a33f836 apply cache-purge-on-low-mem to all LruCaches 2024-11-06 20:50:30 -07:00
Wez Furlong 1241846bf9 purge mod-memoize caches when memory is low
I realized that we hadn't connected this up to the memory monitor,
so this commit does that.

I'd like to hook this up for every LruCacheWithTtl, but that
requires some more extensive changes.

I'll look that in a follow up commit.
2024-11-06 17:31:29 -07:00
Wez Furlong fbed08567f add remember_broken_tls option
This provides a simpler and easier to use option for dealing with broken
TLS.

The back story here is that sites with broken TLS ideally should result
in a TLS failure and return us to a clear text SMTP session that we
could use for the remainder if we are in opportunistic mode.

In practice, it's not that simple:

* The rustls implementation provides into_fallible() to enable this mode
  of operation, but certain handshake errors result in the session being
  unusable once the failed handshake has been communicated
* Openssl is perfectly capable of operating this way, but the Rust
  ecosystem bindings do not expose the handshake failure in a way that
  allows the clear text session to continue

If we wanted to allow the session to communicate with the failed host in
clear text we will need to make a new separate connection for this case.

There is a concern that this will bump a connection counter in a
reputation system implemented on the destination host, but it is
reasonable to wonder if a site that has broken TLS would be sufficiently
sophisticated to employ such a system.

If we did want to allow making a new connection to the same host, it is
a bit awkward to fit into our current mx host connection plan system.

So, with all that in mind, this commit tries a slightly different
approach:

* A new `remember_broken_tls = "3 days"` field in the egress path config
  will enable an in-process cache to keep track of the site_names that
  advertise STARTTLS and for which we failed to handshake, or failed to
  re-EHLO after a handshake.

* That will cause the connection plan to move on to subsequent hosts, if
  any.  It won't cause us to re-attempt the current host.

* Subsequent connection attempts (which may be immediate if there are
  additional hosts in the current session plan) will consult the
  broken tls cache for the site, and use that to decide on the
  availability of TLS

* If we previously encountered broken TLS, we will effectively treat
  the site as not advertising TLS in EHLO, but will generate an
  appropriate error message if TLS is Required.

Since we track the breakage in a cache with per-item TTLs, you
can set the duration as appropriate to your situation.

The cache will be cleared when the process is restarted.

There is no sharing of the cache between nodes.

This is a much lower complexity solution than the existing workaround
using TSA automation rules to override the site with Disabled TLS, and
will help to minimize the number of rules for that purpose in the TSA
daemon.

Note that this commit doesn't change any behavior; we can do a followup
commit to replace those TSA rules with this mechanism.
2024-10-31 13:11:49 -07:00
Wez Furlong e997cb4856 Improve handling of errors in get_queue_config
If you have a get_queue_config event handler that raises an error, it
will propagate back to the injector as a transient error, which is the
desired behavior.

However, we didn't ensure that the message was removed from the spool in
that situation, which could lead to duplicate sends of the same logical
message.

This commit makes things more robust in the context of this kind of
local configuration issue:

* QueueManager::insert_or_unwind is now the preferred way to handle
  insertion of the message into the queue during reception at the point
  of ingress.  It will take care to remove the message from the spool
  and to adjust the accounting by logging a Bounce record to complement
  the Reception that it assumes that its caller has just logged for the
  message.

* Both SMTP and HTTP listeners have been updated to use
  insert_or_unwind.

* Spool enumeration has been updated to count failed inserts and to
  summarize that the failure is serious and that the messages are not
  going to go anywhere until the configuration issue is addressed and
  the server is restarted.  Note that we cannot recover from this while
  the server is online because we have to perform a full spool
  enumeration in order to re-discover those messages.  (We could
  potentially build a thing to remember the complete list of messages,
  but I'd rather not try to address what is essentially a configuration
  deployment validation problem with engineering on this end of things.)
2024-10-31 09:28:38 -07:00
Wez Furlong 73d811b8ef tsa: add SetDomainConfig action
The back story here is that we were investigating a report of
get_egress_path_config sometimes taking a long time (30-60 seconds!) to
complete.

The issue correlates with a large number of TSA entries that
disable the use of TLS for broken sites (>30,000 entries),
and some kind of DNS resolution.

Running the sample of the TSA output through validate-shaping on my
local system can take 30+ seconds to resolve, with a handful of the
entries timing out after 15 seconds (which is 5 seconds timeout on a
query, plus 2 retries of 5 seconds each).  In this sort of situation
with a persistently slow resolve on specific domains, the best case
result is therefore 15 seconds, but it could be longer depending on the
ordering of the results and prevailing winds on the upstream dns server.

What's tricky in this situation is that we have to resolve the site
name for each of the entries that have mx_rollup=true enabled in
order to correctly match any given domain when satisfying the
get_egress_path_config event callout.  Being lazy with resolution
doesn't help at load time because we're only loaded immediately
prior to invoking this event.

The next logical question to ask is: why do we need to have 30,000+
entries to disable TLS for these broken sites--can't we just retry in
clear text? Well, it's a bit tricky: we're not guaranteed to be able
to continue in clear text after STARTLS fails. If we were to disconnect
and reconnect to the same host immediately after, that's technically
2 connections being made in quick succession to the remote site, and
that might have reputation influencing consequences. I'd like to avoid
adding that sort of reconnection logic without providing a way to
control that behavior, and that is something for a separate change
from this one.

So, looking at the nature of the problematic rule, it doesn't
actually need to be keyed by the site name because there are
not shared limits or constraints that it might make sense to
apply, and in the context of this issue, it's not necessary
to use rollup.

This commit introduces a `SetDomainConfig` action that will
emit a config entry that explicitly sets `mx_rollup=false`
regardless of the value of `mx_rollup` for the defining rule
condition.

When mx_rollup=false, we do not need to resolve the MX
record for the entry when we load the shaping rules,
which eliminates the DNS latency for the aggregate
set of domains with broken TLS.

It would be great if we could automagically fixup the
TSA config database when updating to this version, but
since the rule is an optional part of the suggested
shaping configuration, which may not even be loaded
by a given overall configuration, it's not appropriate
to assume that we should do any fixup.

There's likely a one liner that can be run in sqlite to remove or force
the expiration of the old rules.
2024-10-31 09:28:38 -07:00
Wez Furlong fe819c3737 dns-resolver: include the elapsed time in failed resolve message
This just adds some more context when troubleshootin
2024-10-28 14:47:25 -07:00
Wez Furlong 9df648c111 enhance resolve-site-name script
Adds the ability to pass in --unbound and --dnssec parameters
to influence the resolver setup prior to resolving the site name.

In order to facilitate this, the --script-args handling in kumod
has been given a little facelift: that option has been deprecated
in favor of just collecting an argument list via:

`kumod --script -- arg1 arg2`

which is much more natural and easier to wrangle in the context
of the shell script wrapper that we use for this today.

The old way is preserved for compatibility in case someone is
using this already.

I've also added a requires flag on both the legacy and the
preferred script arguments list to ensure that --script is
used together with it.
2024-10-28 10:48:52 -07:00
Wez Furlong 1b8e8a8a54 employ a cache around the rustls client config
Since loading the system certificate store can be expensive,
we want to avoid doing it in a hot code path.

This commit introduces a small lru cache based on the relevant
fields of TlsOptions to cache the built client configuration
objects.

In the common case with the default settings, there will just
be a single cached config object that is shared by all sessions.
2024-10-21 08:15:29 -07:00
Wez Furlong 0e0765dd8b rfc5321: extract tlsoptions into separate module 2024-10-21 07:57:44 -07:00
Wez Furlong 28871e9eba more consistently apply the provider with its modified cipher selection 2024-10-21 07:57:43 -07:00
Wez Furlong da2ddb2ccc switch smtp client from webpki-roots to rustls-platform-verifier
We had some feedback that it would be nice to be able to influence
the set of ca-certs allowed by the client, which isn't possible
when using the hard-coded list in webpki-roots.

This commit is a fairly dumb application of the example shown
in the readme for rustls-platform-verifier.
2024-10-21 07:57:43 -07:00
Dirkjan Ochtman 7c9622b529 mta-sts: migrate to dns-resolver Resolver trait 2024-10-17 09:46:59 -07:00
Dirkjan Ochtman 197b73330b dkim: migrate to dns-resolver Resolver trait 2024-10-17 09:46:59 -07:00
Dirkjan Ochtman c82a8da64a dns-resolver: rename TestResolver::with_spf() to with_txt() 2024-10-17 09:46:59 -07:00
Dirkjan Ochtman a96802fcc0 spf: migrate to dns-resolver Resolver trait 2024-10-17 09:46:59 -07:00
Dirkjan Ochtman 967e20ba24 Move ptr_host() and IpDisplay into dns-resolver 2024-10-17 09:46:59 -07:00
Dirkjan Ochtman 3ba1ce03be dns-resolver: use specific error for resolution failures 2024-10-17 09:46:59 -07:00
Dirkjan Ochtman cac03dc91e Use workspace dependency for thiserror 2024-10-17 09:46:59 -07:00
Wez Furlong 6359820d3d startup: partially restore waiting for spool startup
We had a user report that launching kumod
post-7fbec8758720dad465d711458f6bf7923b778773 produced a panic about the
spool not being configured.

My supposition is that they were bringing the system up under load
(incoming traffic) and that a new message was being injected before we
had started the spool.

So what we do here is restore the spool_started check, but have it
be satisfied once the startup has been triggered, rather than
on completion of enumeration.

The result is that there is still a small window where we might turn
away a message during startup, but it should be very very small.
2024-10-16 16:35:13 -07:00
Wez Furlong b0a37c709b mailparsing: fix replace_text_body / msg:append_text_XXX
We weren't stripping out an existing Content-Transfer-Encoding,
which could lead to duplicate and ambiguous results.
2024-10-15 20:08:25 -07:00
Wez Furlong 9cae055af9 fix a mime rfc2047 qp encoding issue for unstructured fields
This is a bit of a weird one, but the gist of it is that a sequence
of qp encoded words separated by spaces is, per the spec, re-assembled
and the spaces between the encoded words is ignored.

That means that any pair of adjacent encoded words should really
collapsed into a single encoded word that internally includes the
space in order for it to round trip.

To make things a little more complicated, the underlying quoted
printable crate that we're using implements the base qp space rather
than the modified version used by rfc2047 and it will strip trailing
whitespace from the words that are being encoded.

What we do here in this commit is an improvement, but may still
be subject to nuance in the future:

When rebuilding a header value, instead of splitting by space and
emitting a series of encoded words, we will now produce a single
encoded word for the entire header. That word may be split and
wrapped as needed to ensure that the maximum line length is
respected.
2024-10-15 17:48:19 -07:00
Wez Furlong 34096a3b11 http inject: make it an error to pass unrecognized fields
This makes it easier to spot when the api is being used incorrectly.
2024-10-15 13:24:53 -07:00
Dirkjan Ochtman a28721f5ee dns-resolver: replace Resolver enum with a trait 2024-10-15 08:54:24 -07:00
Dirkjan Ochtman 2298a1791c dns-resolver: require Name argument for Resolver::resolve() 2024-10-15 08:54:24 -07:00
Dirkjan Ochtman d4ec9cf50e dns-resolver: use concrete type for Resolver::resolve_txt() 2024-10-15 08:54:24 -07:00
Dirkjan Ochtman bc7bf2ffa8 dns-resolver: create HickoryResolver wrapper 2024-10-15 08:54:24 -07:00
Dirkjan Ochtman 4553cafeca dns-resolver: externalize unwrapping on UnboundResolver initialization 2024-10-15 08:54:24 -07:00
Dirkjan Ochtman a9132e201d dns-resolver: create new UnboundResolver type 2024-10-15 08:54:24 -07:00
Dirkjan Ochtman e309c8d69a dns-resolver: avoid exposing internal structure 2024-10-15 08:54:24 -07:00
Dirkjan Ochtman be09a845d1 spf: reintroduce CheckHostParams 2024-10-15 06:04:53 -07:00
Dirkjan Ochtman 6c37410042 Use a workspace dependency for serde 2024-10-15 06:04:53 -07:00
Wez Furlong eecd11a640 http injector: enable trace headers
These are almost identical to those used by smtp, the main difference
being that we don't include the Received header by default for
http injection, whereas we do for smtp in order to be compliant
with the relevant rfcs.  No such rfcs exist for the injection
protocol, so we can pick a default that makes more sense for the
majority of our target audience.
2024-10-14 09:57:29 -07:00