From 04c440624b3b0eb84ed042f96eb55f0bdc3f6b62 Mon Sep 17 00:00:00 2001 From: Dominik Chilla Date: Mon, 30 Nov 2020 10:49:58 +0100 Subject: [PATCH] Docs; more logging --- README.md | 24 +++++++++++++++++------- app/exota-milter.py | 33 ++++++++++++++++++--------------- tests/README.md | 28 ++++++++++++++++++++++------ 3 files changed, 57 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index 70d87a1..1a585c8 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,7 @@ Microsoft provides an ACL as [JSON file (ID: 10)](https://endpoints.office.com/e [...] ``` -The problem of this IP based ACL is that many other Exchange-Online customers/tenants are sending from the same IP-ranges as well! **This means that nearly any Exchange-Online smarthost tends to be an open relay unless additional authentication mechanism on a higher layer than IP takes place! IP-ACLs are definitely not enough!** +The problem of this IP based ACL is that many other Exchange-Online customers/tenants are sending from the same IP-ranges as well! **This means that many smarthost configured to relay mails comming from Exchange-Online tends to be an open relay (for Microsoft customers) unless additional authentication mechanism on a higher layer than IP takes place! IP-address based ACLs are definitely not the right way to achieve this!** ## x509 client certificate presented by Exchange-Online The Exchange-Online platform also *presents* a x509 client certificate to identitfy onself to the smarthost. Taking a closer look at the received header we´ll notice that the certificates common name (CN) *mail.protection.outlook.com* is not realy tenant specific. Although the certificate provides additional security regarding the identity of the client system, it does not provide identity regarding the tenant. **IMHO that´s stil not enough to permit relaying!** @@ -70,14 +70,24 @@ Authentication-Results: trusted.dkim.validating.relay; dkim=pass header.d=tenan [...] ``` -## Microsoft tenant-ID -Further each Microsoft Exchange-Online tenant has a unique tenant-ID in form of a UUID ([RFC 4122](https://tools.ietf.org/html/rfc4122)). **ExOTA-Milter** determines the tenant-ID from the *X-MS-Exchange-CrossTenant-Id* email header and uses it as an *mandatory* authentication factor. +## X-MS-Exchange-CrossTenant-Id header +Further each Microsoft Exchange-Online tenant has a unique tenant-ID in form of a UUID ([RFC 4122](https://tools.ietf.org/html/rfc4122)). **ExOTA-Milter** determines the tenant-ID from the *X-MS-Exchange-CrossTenant-Id* email header and uses it as a *mandatory* authentication factor. +``` +[...] +X-MS-Exchange-CrossTenant-Id: +[...] +``` # The solution -The answer to the question "*How can an Exchange-Online user/tenant be identified by a smarthost?*" can be answered as follows. +So, *how can an Exchange-Online user/tenant be identified by a third party smarthost?* -Finally it´s the combination of all of the above discussed aspects which result in a robust-enough smarthost setup for the Exchange-Online platform: +Finally it´s the combination of all of the above discussed aspects which may result in a robust-enough smarthost setup used by the Exchange-Online platform: * restriction of client IPs via ACL (MTA) -* verification of Microsoft´s x509 client certificate + CN (MTA + ExOTA-Milter) +* verification of Microsoft´s x509 client certificate (MTA) +* matching for client certificate´s CN (ExOTA-Milter) +* verification of DKIM signatures providing *Authentication-Results* header (another milter) * consideration of DKIM verification results per sender domain (ExOTA-Milter) -* matching of tenant-id provided in email header (ExOTA-Milter) +* matching for tenant-id provided in *X-MS-Exchange-CrossTenant-Id* header (ExOTA-Milter) + +# How to start? +First of all please take a look at how to set up the testing environment, which is described [here](tests/README.md) \ No newline at end of file diff --git a/app/exota-milter.py b/app/exota-milter.py index da75f8c..6c18d06 100644 --- a/app/exota-milter.py +++ b/app/exota-milter.py @@ -249,12 +249,17 @@ class ExOTAMilter(Milter.Base): ) self.setreply('550','5.7.1', g_milter_reject_message) return Milter.REJECT - - logging.info(self.mconn_id + "/" + self.getsymval('i') + - "/EOM: Tenant authentication successful (dkim_enabled={0})".format( - str(policy.is_dkim_enabled()) + + if g_milter_dkim_enabled: + logging.info(self.mconn_id + "/" + self.getsymval('i') + + "/EOM: Tenant authentication successful (dkim_enabled={0})".format( + str(policy.is_dkim_enabled()) + ) + ) + else: + logging.info(self.mconn_id + "/" + self.getsymval('i') + + "/EOM: Tenant successfully authenticated" ) - ) return Milter.CONTINUE def abort(self): @@ -289,34 +294,32 @@ if __name__ == "__main__": g_milter_socket = os.environ['MILTER_SOCKET'] if 'MILTER_REJECT_MESSAGE' in os.environ: g_milter_reject_message = os.environ['MILTER_REJECT_MESSAGE'] + logging.info("ENV[MILTER_REJECT_MESSAGE]: {0}".format(g_milter_reject_message)) if 'MILTER_TMPFAIL_MESSAGE' in os.environ: g_milter_tmpfail_message = os.environ['MILTER_TMPFAIL_MESSAGE'] + logging.info("ENV[MILTER_TMPFAIL_MESSAGE]: {0}".format(g_milter_tmpfail_message)) if 'MILTER_DKIM_ENABLED' in os.environ: g_milter_dkim_enabled = True - logging.info("DKIM signature authorisation enabled") if 'MILTER_TRUSTED_AUTHSERVID' in os.environ: g_milter_trusted_authservid = os.environ['MILTER_TRUSTED_AUTHSERVID'].lower() - logging.info("Trusted AuthServID: " + g_milter_trusted_authservid) + logging.info("ENV[MILTER_TRUSTED_AUTHSERVID]: {0}".format(g_milter_trusted_authservid)) else: logging.error("ENV[MILTER_TRUSTED_AUTHSERVID] is mandatory!") sys.exit(1) + logging.info("ENV[MILTER_DKIM_ENABLED]: {0}".format(g_milter_dkim_enabled)) if 'MILTER_X509_ENABLED' in os.environ: g_milter_x509_enabled = True - logging.info("x509 client certificate CN validation enabled") if 'MILTER_X509_TRUSTED_CN' in os.environ: g_milter_x509_trusted_cn = os.environ['MILTER_X509_TRUSTED_CN'] - logging.info("Trusted x509 client CN: '{0}'".format( - g_milter_x509_trusted_cn - )) - else: - logging.info("ENV[MILTER_X509_TRUSTED_CN]: using default '{0}'".format( - g_milter_x509_trusted_cn - )) + logging.info("ENV[MILTER_X509_TRUSTED_CN]: {0}".format(g_milter_x509_trusted_cn)) + logging.info("ENV[MILTER_X509_ENABLED]: {0}".format(g_milter_x509_enabled)) if 'MILTER_POLICY_SOURCE' in os.environ: g_milter_policy_source = os.environ['MILTER_POLICY_SOURCE'] + logging.info("ENV[MILTER_POLICY_SOURCE]: {0}".format(g_milter_policy_source)) if g_milter_policy_source == 'file': if 'MILTER_POLICY_FILE' in os.environ: g_milter_policy_file = os.environ['MILTER_POLICY_FILE'] + logging.info("ENV[MILTER_POLICY_FILE]: {0}".format(g_milter_policy_file)) try: g_milter_policy_backend = ExOTAPolicyBackendJSON(g_milter_policy_file) logging.info("JSON policy backend initialized") diff --git a/tests/README.md b/tests/README.md index 3c18449..3f87602 100644 --- a/tests/README.md +++ b/tests/README.md @@ -1,4 +1,16 @@ -# prepare testing env +# Prepare testing env +First of all, please configure a python virtual environment and install all necessary python packages listed under `requirements.txt`. Go to the root-directory of this repo and +1. `python3 -m venv venv` +1. `. venv/bin/activate` +1. `pip3 install -r requirements.txt` + +It´s not realy neccessary to configure a fully functional milter-aware MTA to see **ExOTA-Milter** in action. All you need is +* a binary called `miltertest`. Under debian based distros it´s located in the `opendkim-tools` package. +* a lua-script for miltertest: `tests/miltertest.lue` +* an **ExOTA-Milter** policy JSON-file: `tests/policy.json` + +Except for the `miltertest` binary you´ll find all mandatory resources to run a test in this repo. + ``` export LOG_LEVEL=debug export MILTER_SOCKET=/tmp/exota-milter @@ -9,11 +21,15 @@ export MILTER_X509_ENABLED=yepp export MILTER_X509_TRUSTED_CN=mail.protection.outlook.com ``` -# start milter -`python3 app/exota-milter.py` +# Shell-1: start ExOTA-Milter +``` +. venv/bin/activate +python3 app/exota-milter.py +``` -# execute `miltertest` -First of all install the `miltertest` binary. Under debian based distros -it´s located in the `opendkim-tools` package. +# Shell-2: execute `miltertest` +This must be done only once: `export MILTER_SOCKET=/tmp/exota-milter` + +Execute miltertest pointing to the test script written in lua to feed the **ExOTA-Milter**: `miltertest -v -D socket="${MILTER_SOCKET}" -s tests/miltertest.lua` \ No newline at end of file