Docs; more logging

This commit is contained in:
Dominik Chilla 2020-11-30 10:49:58 +01:00
parent da1b91b7e3
commit 04c440624b
3 changed files with 57 additions and 28 deletions

View File

@ -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: <UUID-of-tenant>
[...]
```
# 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)

View File

@ -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")

View File

@ -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`