Compare commits

...

8 Commits

Author SHA1 Message Date
a82d27004f docs: ENV-options + tests refactoring 2023-06-05 21:55:04 +02:00
d395344401 kubernetes via kustomize 2023-06-05 19:40:36 +02:00
a25ee7fd11 refactoring + purge 2023-06-05 18:56:00 +02:00
686a5de1a0 docs: fix for docker-image uri 2023-01-31 00:40:11 +01:00
fadf6966be Make X-MS-Exchange-CrossTenant-Id header optional - take 2 2023-01-31 00:29:44 +01:00
3e9409717f docs + some OCI enhancements 2022-10-20 21:19:50 +02:00
Dominik Chilla
6c844f22e7
Merge pull request #47 from chillout2k/oci-patch
OCI build plan changed
2022-10-20 20:38:40 +02:00
Dominik Chilla
931570677b
OCI build plan changed 2022-10-20 20:38:20 +02:00
22 changed files with 171 additions and 174 deletions

View File

@ -32,19 +32,23 @@ docker-compose-exota-milter-1 | 2022-06-06 21:54:04,511: INFO 140529821924168 S
```
## kubernetes <a name="kubernetes"/>
By default this example installs the Exota-milter workload into the `exota-milter` namespace, which must be created in advance:
```
~/src/ExOTA-Milter/INSTALL/kubernetes$ kubectl apply -f 01_config-map.yaml
kubectl create ns exota-milter
namespace/exota-milter created
```
Deploy stateless workload (type `Deployment`) with `kustomize`:
```
~/src/ExOTA-Milter/INSTALL/kubernetes$ kubectl apply -k .
configmap/exota-milter-policy-cmap created
~/src/ExOTA-Milter/INSTALL/kubernetes$ kubectl apply -f 02_deployment.yaml
deployment.apps/exota-milter created
~/src/ExOTA-Milter/INSTALL/kubernetes$ kubectl apply -f 03_service.yaml
service/exota-milter created
deployment.apps/exota-milter created
```
Check status of pods, replica-sets and service
Check status of pods, replica-sets and cluster internal service:
```
~/src/ExOTA-Milter/INSTALL/kubernetes$ kubectl -n devel get all
~/src/ExOTA-Milter/INSTALL/kubernetes$ kubectl -n exota-milter get all
NAME READY STATUS RESTARTS AGE
pod/exota-milter-547dbccd8b-j69mn 1/1 Running 0 64s
pod/exota-milter-547dbccd8b-7hl6c 1/1 Running 0 64s
@ -59,9 +63,9 @@ deployment.apps/exota-milter 3/3 3 3 64s
NAME DESIRED CURRENT READY AGE
replicaset.apps/exota-milter-547dbccd8b 3 3 3 65s
```
Get logs of one of the pods:
Get logs of the pods:
```
~/src/ExOTA-Milter/INSTALL/kubernetes$ kubectl -n devel logs exota-milter-547dbccd8b-7hl6c
~/src/ExOTA-Milter/INSTALL/kubernetes$ kubectl -n exota-milter logs -l app=exota-milter
2022-06-06 21:57:03,515: INFO Logger initialized
2022-06-06 21:57:03,515: INFO ENV[MILTER_NAME]: exota-milter
2022-06-06 21:57:03,515: INFO ENV[MILTER_SOCKET]: inet:4321@127.0.0.1
@ -80,6 +84,18 @@ Get logs of one of the pods:
2022-06-06 21:57:03,516: INFO JSON policy backend initialized
2022-06-06 21:57:03,516: INFO Startup exota-milter@socket: inet:4321@127.0.0.1
```
Remove workload from cluster:
```
~/src/ExOTA-Milter/INSTALL/kubernetes$ kubectl delete -k .
configmap "exota-milter-policy-cmap" deleted
service "exota-milter" deleted
deployment.apps "exota-milter" deleted
~/src/ExOTA-Milter/INSTALL/kubernetes$ kubectl delete ns exota-milter
namespace "exota-milter" deleted
```
## systemd <a name="systemd"/>
If you do not want to run the ExOTA-Milter in a containerized environment but directly as a systemd-unit/-service, first you´ll need to install all necessary python and build dependencies. Start with build deps (examples refere to ubuntu/debian):

View File

@ -1,7 +1,7 @@
version: '2.4'
services:
exota-milter:
image: chillout2k/exota-milter-amd64
image: chillout2k/exota-milter
restart: unless-stopped
environment:
LOG_LEVEL: 'debug'

View File

@ -3,7 +3,6 @@ kind: ConfigMap
apiVersion: v1
metadata:
name: exota-milter-policy-cmap
namespace: devel
data:
exota-milter-policy: |
{

View File

@ -2,7 +2,6 @@
kind: Deployment
apiVersion: apps/v1
metadata:
namespace: devel
name: exota-milter
labels:
app: exota-milter
@ -45,7 +44,7 @@ spec:
path: 'exota-milter-policy.json'
containers:
- name: exota-milter
image: chillout2k/exota-milter-amd64
image: chillout2k/exota-milter
imagePullPolicy: Always
volumeMounts:
- mountPath: /data

View File

@ -2,7 +2,6 @@
apiVersion: v1
kind: Service
metadata:
namespace: devel
name: exota-milter
spec:
selector:

View File

@ -0,0 +1,7 @@
namespace: exota-milter
commonLabels:
app: exota-milter
resources:
- 01_config-map.yaml
- 02_deployment.yaml
- 03_service.yaml

View File

@ -1,12 +1,13 @@
ARG ARCH
FROM $ARCH/alpine
ARG PARENT_IMAGE=alpine:3.16
FROM ${PARENT_IMAGE}
LABEL maintainer="Dominik Chilla <dominik@zwackl.de>"
LABEL git_repo="https://github.com/chillout2k/exota-milter"
ADD ./requirements.txt /requirements.txt
RUN apk update \
&& apk add python3 python3-dev py3-pip gcc libc-dev libmilter-dev \
&& apk add --no-cache python3 python3-dev py3-pip \
gcc libc-dev libmilter-dev \
&& pip3 install -r requirements.txt \
&& apk del gcc libc-dev libmilter-dev python3-dev py3-pip \
&& apk add libmilter \
@ -23,4 +24,4 @@ RUN chown -R exota-milter /app /cmd \
VOLUME [ "/socket", "/data" ]
USER exota-milter
CMD [ "/cmd" ]
CMD [ "/cmd" ]

View File

@ -99,8 +99,8 @@ By the way, the global setting `ENV[MILTER_DKIM_ALIGNMENT_REQUIRED]` can be over
}
```
## X-MS-Exchange-CrossTenant-Id header (policy binding)
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** extracts 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 header (OPTIONAL!)
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** extracts the tenant-ID from the *X-MS-Exchange-CrossTenant-Id* email header and uses it as a *mandatory* authentication factor. Since September 2022 Microsoft did not set this header anymore reliably.
```
[...]
X-MS-Exchange-CrossTenant-Id: <UUID-of-tenant>
@ -125,17 +125,18 @@ Finally it´s the combination of all of the above discussed aspects which may re
* matching for client certificate´s CN (ExOTA-Milter)
* verification of DKIM signatures providing *Authentication-Results* header (another milter, e.g. OpenDKIM)
* consideration of DKIM verification results per sender domain (ExOTA-Milter)
* matching for tenant-id provided in *X-MS-Exchange-CrossTenant-Id* header (ExOTA-Milter)
* *OPTIONAL* matching for tenant-id provided in *X-MS-Exchange-CrossTenant-Id* header (ExOTA-Milter)
![Activity policy](http://www.plantuml.com/plantuml/png/5SKn3W8W30NGg-W1f8cZcuEZSN4tM8aq5ahAhyhjZMzvM-ciyIZXkgd0c0SYpv_q5DIunopErb4w4biZhg9gWVsBJj_BzRWxYw8ujJp_POQy1UisJ8LN6j7q1m00)
![Activity policy](http://www.plantuml.com/plantuml/png/bPLHJzi-5CNVyoaERqKY_FzZuc7fmyYI8BhHTae3GffGPUBRriArkzXXyEsNrWsrIa3T8xw-zvppezUvC9PLjbxAm0eh2Tdpk8Z3eP2MAXWgwqhO5woq5EKBPbB_GR3f2A9X4QFKIb5fYVSH1D5LcaT8j9JDaL1pC2bHaQGdfYmMn3XLfXyeRGcIPZR2PQMN9uXhko1bHSciq2hCoTJIcXFSXSD9c3sN2wRc52QLDgOWrSmA1xnLowdKSoNCMiwGQXJ0zP89vUkWO8-aC6lKa5ycvv_FpaxNqbjFO8h_fwlNKcE06X7lnkcszkcq6ItH8_L4Kg_e6C9WD2vUKnurlhBnCCArrezhJ_MgPISK7bZP-E2-DNnZXZYqUbNVk7GPWa3CqFkvPQzhnRyUOyqAlHSonm6mhhl_LOJdy_-_e9IYyOvaX791vSO2AVOWwKqh4ErCxZNtDtNFPJQw_JKSN1TPFhrhRawB_6PIPEaq6Tq7WFDnkJO8MzN64jyR-5QSf27qb7P_0L6UVS-ImYa3nkgLkwUuc0N-_TsPdpxzCHWUaDa3kc3ciAvzLct4xj_jxeSEl4-n1HZV55UJBByTf0va1qfdCix3xU0kUpwT3fuUKPyfqC6GIJ5NIrBs42xTHCFTZ0zRWIXfDSJCjPgcazcw8WZZl13VXV1nEhkJT91YsO02_QwPXDNmDsdgDkae07p30-E5xRBNXRKJRGTU7t7tdBRULVLhVkNmLR3RHp9jtxT_UWhQTi4XrFRnwdSl-KwfdNZ0GZltJld3KDw9DymDSZUYd9RwgVtIt8Nks_NnMgvJhAhD2zyFV6gCy-sTVJnHp7aZSfX0FtXlvUCqdLvbSPhbzxjiRP9aF2e6evki7spoNJJ7zLB-1G00)
# How about using LDAP as policy backend?
For small setups, with not so many domains, the JSON-file policy backend (default) may be sufficient. If you´re an email service provider (ESP) maintaining a lot of customer domains in a LDAP server, you may want to use the LDAP backend instead. Details regarding the LDAP backend can be found [in the LDAP readme](LDAP/README.md).
# How about a docker/OCI image?
## Using prebuilt images from [dockerhub](https://hub.docker.com/)
* AMD64: https://hub.docker.com/r/chillout2k/exota-milter-amd64
* ARM32v6: https://hub.docker.com/r/chillout2k/exota-milter-arm32v6
* **OBSOLETE!** ~~AMD64: https://hub.docker.com/r/chillout2k/exota-milter-amd64~~
* **OBSOLETE!** ~~ARM32v6: https://hub.docker.com/r/chillout2k/exota-milter-arm32v6~~
* **NEW multi-architecture image:** https://hub.docker.com/r/chillout2k/exota-milter
The images are built on a weekly basis. The corresponding *Dockerfile* is located [here](OCI/Dockerfile)
@ -146,4 +147,33 @@ Take a look [here](OCI/README.md)
First of all please take a look at how to set up the testing environment, which is described [here](tests/README.md)
# How to install on docker/kubernetes/systemd?
The installation procedure is documented [here](INSTALL/README.md)
The installation procedure is documented [here](INSTALL/README.md)
# How to *configure* the ExOTA-Milter?
|ENV variable|type|default|description|
|---|---|---|---|
|MILTER_NAME|`string`|`exota-milter`|Name of the milter instance. Base for socket path. Name appears in logs |
|MILTER_SOCKET|`string`|`/socket/<ENV[MITLER_NAME]>`|Defines the filesystem path of milter socket. The milter can be also exposed as a tcp-socket like `inet:4321@127.0.0.1`|
|MILTER_REJECT_MESSAGE|`string`|`Security policy violation!`|Milter reject (SMTP 5xx code) message presented to the calling MTA|
|MILTER_TMPFAIL_MESSAGE|`string`|`Service temporarily not available! Please try again later.`|Milter temporary fail (SMTP 4xx code) message presentetd to the calling MTA.|
|MILTER_TENANT_ID_REQUIRED|`bool`|`false`|Controls the requirement of the presence of the unofficial `X-MS-Exchange-CrossTenant-Id` header. Used as additional authentication factor.|
|MILTER_DKIM_ENABLED|`bool`|`false`|Enables/disables the checking of DKIM authentication results. Used as additional but strong authentication factor.|
|MILTER_DKIM_ALIGNMENT_REQUIRED|`bool`|`false`|Enables/disables the alighment checks of DKIM SDID with RFC-5322.from_domain. Requires ENV[MILTER_DKIM_ENABLED] = `true`|
|MILTER_TRUSTED_AUTHSERVID|`string`|`invalid`|Specifies the trusted DKIM-signature validating entity (DKIM-validator - producer of Authentication-Results header). The DKIM-validator must place exactly the same string as configured here into the Authentication-Results header! Requires ENV[MILTER_DKIM_ENABLED] = `true`|
|MILTER_POLICY_SOURCE|`string`|`file`|Policy source - Possible values `file` (JSON) or `ldap`|
|MILTER_POLICY_FILE|`string`|`/data/policy.json`|Filesystem path to the (JSON) policy file. Requires ENV[MILTER_POLICY_SOURCE] = `file`|
|MILTER_X509_ENABLED|`bool`|`false`|Enables/disables the checking of client x509-certificate. Used as additional authentication factor.|
|MILTER_X509_TRUSTED_CN|`string`|`mail.protection.outlook.com`|FQDN of authenticating client MTA. Requires ENV[MILTER_X509_ENABLED] = `true`|
|MILTER_X509_IP_WHITELIST|Whitespace or comma separated list of `string`|`127.0.0.1,::1`|List of IP-addresses for which the ExOTA-Milter skips x509 checks. Requires ENV[MILTER_X509_ENABLED] = `true`|
|MILTER_ADD_HEADER|`bool`|`false`|Controls if the ExOTA-Milter should write an additional `X-ExOTA-Authentication-Results` header with authentication information|
|MILTER_AUTHSERVID|`string`|empty|Provides ID of authenticating entity within `X-ExOTA-Authentication-Results` header to further validating instances. Required when ENV[MILTER_ADD_HEADER] = `true`|
|MILTER_LDAP_SERVER_URI|`string`|empty|LDAP-URI of LDAP server holding ExOTA policies. Required when ENV[MILTER_POLICY_SOURCE] = `ldap`|
|MILTER_LDAP_RECEIVE_TIMEOUT|`int`|5|Timespan the ExOTA-Milter waits for the LDAP server to respond to a request. This NOT the TCP-connect timeout! Requires ENV[MILTER_POLICY_SOURCE] = `ldap`|
|MILTER_LDAP_BINDDN|`string`|empty|Distinguished name of the binding (authenticating) *user*|
|MILTER_LDAP_BINDPW|`string`|empty|Password of the binding (authenticating) *user*|
|MILTER_LDAP_SEARCH_BASE|`string`|empty|Search base-DN on the LDAP server. Required when ENV[MILTER_POLICY_SOURCE] = `ldap`|
|MILTER_LDAP_QUERY|`string`|empty|LDAP query/filter used to match for a ExOTA-policy. A placeholder must be used to filter for the authenticating domain (`%d`), e.g. `(domain_attribute=%d)`|
|MILTER_LDAP_TENANT_ID_ATTR|`string`|`exotaMilterTenantId`|Custom LDAP attribute name unless using the ExOTA-milter LDAP schema|
|MILTER_LDAP_DKIM_ENABLED_ATTR|`string`|`exotaMilterDkimEnabled`|Custom LDAP attribute name unless using the ExOTA-milter LDAP schema|
|MILTER_LDAP_DKIM_ALIGNMENT_REQIRED_ATTR|`string`|`exotaMilterDkimAlignmentRequired`|Custom LDAP attribute name unless using the ExOTA-milter LDAP schema|

View File

@ -50,11 +50,14 @@ if (Policy found?) then (yes)
endif
else (no)
endif
:Looking up tenant-id in policy;
if (Found trusted tenant-ID?) then (no)
:REJECT;
stop
else (yes)
if (Milter: tenant-ID header checking enabled?) then (yes)
:Looking up tenant-id in policy;
if (Found trusted tenant-ID?) then (no)
:REJECT;
stop
else (yes)
endif
else (no)
endif
else (no)
:REJECT;

View File

@ -165,12 +165,11 @@ class ExOTAMilter(Milter.Base):
return self.smfir_continue()
def header(self, name, hval):
log_debug(self.mconn_id + "/" + str(self.getsymval('i')) +
"/HDR: Header: {0}, Value: {1}".format(name, hval)
)
# Parse RFC-5322-From header
if(name.lower() == "From".lower()):
if(name.lower() == "from"):
log_debug(self.mconn_id + "/" + str(self.getsymval('i')) +
"/HDR: Header: {0}, Value: {1}".format(name, hval)
)
hdr_5322_from = email.utils.parseaddr(hval)
self.hdr_from = hdr_5322_from[1].lower()
m = re.match(g_re_domain, self.hdr_from)
@ -187,7 +186,10 @@ class ExOTAMilter(Milter.Base):
)
# Parse RFC-5322-Resent-From header (Forwarded)
if(name.lower() == "Resent-From".lower()):
if(name.lower() == "resent-from"):
log_debug(self.mconn_id + "/" + str(self.getsymval('i')) +
"/HDR: Header: {0}, Value: {1}".format(name, hval)
)
hdr_5322_resent_from = email.utils.parseaddr(hval)
self.hdr_resent_from = hdr_5322_resent_from[1].lower()
m = re.match(g_re_domain, self.hdr_resent_from)
@ -204,7 +206,10 @@ class ExOTAMilter(Milter.Base):
)
# Parse non-standardized X-MS-Exchange-CrossTenant-Id header
elif(name.lower() == "X-MS-Exchange-CrossTenant-Id".lower()):
elif(name.lower() == "x-ms-exchange-crosstenant-id"):
log_debug(self.mconn_id + "/" + str(self.getsymval('i')) +
"/HDR: Header: {0}, Value: {1}".format(name, hval)
)
if g_milter_tenant_id_required == True:
log_debug(self.mconn_id + "/" + str(self.getsymval('i')) +
"/HDR: Tenant-ID: {0}".format(hval.lower())
@ -219,8 +224,17 @@ class ExOTAMilter(Milter.Base):
self.hdr_tenant_id_count += 1
self.hdr_tenant_id = hval.lower()
# Just for debugging cases
elif(name.lower() == "dkim-signature"):
log_debug(self.mconn_id + "/" + str(self.getsymval('i')) +
"/HDR: Header: {0}, Value: {1}".format(name, hval)
)
# Parse RFC-7601 Authentication-Results header
elif(name.lower() == "Authentication-Results".lower()):
elif(name.lower() == "authentication-results"):
log_debug(self.mconn_id + "/" + str(self.getsymval('i')) +
"/HDR: Header: {0}, Value: {1}".format(name, hval)
)
if g_milter_dkim_enabled == True:
ar = None
try:
@ -248,6 +262,9 @@ class ExOTAMilter(Milter.Base):
)
elif(name == "X-ExOTA-Authentication-Results"):
log_debug(self.mconn_id + "/" + str(self.getsymval('i')) +
"/HDR: Header: {0}, Value: {1}".format(name, hval)
)
log_debug(self.mconn_id + "/" + str(self.getsymval('i')) +
"/HDR: Found X-ExOTA-Authentication-Results header. Marking for deletion."
)

View File

@ -14,10 +14,6 @@ def init_logger():
elif re.match(r'debug', os.environ['LOG_LEVEL'], re.IGNORECASE):
log_level = logging.DEBUG
log_format = '%(asctime)s: %(levelname)s %(message)s '
if log_level == logging.DEBUG:
# Log thread-ID too. This helps to correlate DEBUG logs,
# as Backend-logs do not have a queue_id nor a mconn_id!
log_format = '%(asctime)s: %(levelname)s %(thread)d %(message)s '
logging.basicConfig(
filename = None, # log to stdout
format = log_format,

View File

@ -25,7 +25,10 @@ class ExOTAPolicyBackendException(Exception):
class ExOTAPolicy():
def __init__(self, policy_dict):
self.tenant_id = policy_dict['tenant_id']
if 'tenant_id' in policy_dict:
self.tenant_id = policy_dict['tenant_id']
else:
self.tenant_id = ''
if 'dkim_enabled' in policy_dict:
self.dkim_enabled = policy_dict['dkim_enabled']
else:
@ -50,18 +53,11 @@ class ExOTAPolicy():
@staticmethod
def check_policy(policy_dict):
if 'tenant_id' not in policy_dict:
raise ExOTAPolicyInvalidException(
"Policy must have a 'tenant_id' key!"
)
if policy_dict['tenant_id'] is None:
raise ExOTAPolicyInvalidException(
"'tenant_id' needs a value!"
)
for policy_key in policy_dict:
if policy_key == 'tenant_id':
try:
UUID(policy_dict[policy_key])
if policy_dict[policy_key] != '':
UUID(policy_dict[policy_key])
except ValueError as e:
raise ExOTAPolicyInvalidException(
"Invalid 'tenant_id': {0}".format(str(e))
@ -200,7 +196,10 @@ class ExOTAPolicyBackendLDAP(ExOTAPolicyBackend):
entry = response[0]['attributes']
policy_dict = {}
if self.tenant_id_attr in entry:
policy_dict['tenant_id'] = entry[self.tenant_id_attr][0]
if len(entry[self.tenant_id_attr]) > 0:
policy_dict['tenant_id'] = entry[self.tenant_id_attr][0]
else:
policy_dict['tenant_id'] = ''
if self.dkim_enabled_attr in entry:
if entry[self.dkim_enabled_attr][0] == 'TRUE':
policy_dict['dkim_enabled'] = True

View File

@ -1,64 +0,0 @@
Return-Path: <>
Received: from DEU01-FR2-obe.outbound.protection.outlook.com (mail-fr2deu01lp2173.outbound.protection.outlook.com [104.47.11.173])
(using TLSv1.2 with cipher ECDHE-ECDSA-AES256-GCM-SHA384 (256/256 bits))
(Client CN "mail.protection.outlook.com", Issuer "GlobalSign Organization Validation CA - SHA256 - G3" (verified OK))
by outbound.connector.blahblubb.de (Postfix) with ESMTPS id 4CjqCQ2WRCzGjg6
for <some.recipient@example.org>; Sat, 28 Nov 2020 12:34:26 +0100 (CET)
ARC-Seal: i=1; a=rsa-sha256; s=arcselector9901; d=microsoft.com; cv=none;
b=RuGSfIN1OzQHDqrF0erLAHZ3fyhtmoE5Sllj+Qp6CtbcNUkkmdhR44b8capz/J1mBpyb13udY1mhkPZCK1Cmt+mpg9yFXgkv5BxY+dV9647Fq+MboUE60Psn84d4vXFvyrWDrFW1jWZi7/NdXhjLcCqTHpAzDaRfAOfGhG/VWYJAXnD/EBpCzPfd8hh9ZOONI2UN2HQfRnx0P3WXyeVSGilP4RGPdmcCZV5ZzpjlQoKUshjq293+ZltXaeKfF/LHGX0yScHhKO2f9O+qY3hnH0P+NGwFvhIky3IyszfxpANaJnz2Jpp0sK1W16rwGSTI2gl9bpJsj+wzKLGkJV75+Q==
ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=microsoft.com;
s=arcselector9901;
h=From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-SenderADCheck;
bh=KWHUKEKZxeQyFbKe45TV1PJMC2XrVCMTFkdwSYWR6o0=;
b=go8dFv6srV3NnETxQxaANld1if9BOsIgrhjefC4WkRrrgwEjZSNnm9DyO+GC2ZZo60At5JHOVLjqN9kjz2pFdAG0qnFEj3Wx/6NnuTfBUk0n4s32RoFuhADu8BC+aOU9Ec909uu2QQ9ucEMiVSjuyQ3QpGS5DR0yCAZLZ12B61hmoMgkXJ9ah6rluUV4GeMGKTsUn16u6mrJycXp0OoD4n19JomPpQo5o8gouK3Zz4F7DxX4lshNJ+VCsOznqS+FI4rQ2LSyU8Y0AZa9clyCSN94AJa6K0TiDgQ/gLZEWsZ1tZkgPrdMlyqi58ONW/dNQ7lyrEFz6deB4YmsusJPbQ==
ARC-Authentication-Results: i=1; mx.microsoft.com 1; spf=none; dmarc=none
action=none header.from=lalalulu.onmicrosoft.com; dkim=none (message not
signed); arc=none
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
d=lalalulu.onmicrosoft.com; s=selector1-lalalulu-onmicrosoft-com;
h=From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-SenderADCheck;
bh=KWHUKEKZxeQyFbKe45TV1PJMC2XrVCMTFkdwSYWR6o0=;
b=DYTLJtLFjvVrSZtZQagTwuEe5PQYqrNGi7hR5bkhO+GYUV4dcQZnDO4hAPzJkOWhz8JCVJ+/yt5K8L/exegk80g9m0GJjZzJBxMy0ZE/7wg8yqiHNE+iQqWhJLtwsD23kx2+09G5dBSDI1QVqFKkL0YKBWVffSuXi+tjM4/BztffZ7ok7XZdKCFfKzK3TLdiAWYTRIp1214zdnIE0CLBhnOIWC4gnML2fXsVZsWb/CMgaW0vBsZGI/yaSivaNFPZloSb0/sEnMFMEbv2GXt9mN913M0thwCi/+NLwzaW6TNlw2Vz7l4SGRVvciGaa4s2sFnJ0ANMD2u5qBbJ8j8Z0w==
Authentication-Results: blahblubb.de; dkim=none (message not signed)
header.d=none;blahblubb.de; dmarc=none action=none
header.from=lalalulu.onmicrosoft.com;
Received: from AM6P193CA0087.EURP193.PROD.OUTLOOK.COM (2603:10a6:209:88::28)
by BEXP281MB0216.DEUP281.PROD.OUTLOOK.COM (2603:10a6:b10:6::12) with
Microsoft SMTP Server (version=TLS1_2,
cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.20.3632.6; Sat, 28 Nov
2020 11:34:25 +0000
Received: from BE0P281MB0257.DEUP281.PROD.OUTLOOK.COM
(2603:10a6:209:88:cafe::a2) by AM6P193CA0087.outlook.office365.com
(2603:10a6:209:88::28) with Microsoft SMTP Server (version=TLS1_2,
cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.20.3611.20 via Frontend
Transport; Sat, 28 Nov 2020 11:34:24 +0000
From: O365ConnectorValidation@lalalulu.onmicrosoft.com
Date: Sat, 28 Nov 2020 11:34:24 +0000
Message-Id: <b6d9c673-d0f3-4538-bb4e-9e099fb9a388@substrate-int.office.com>
To: some.recipient@example.org
Subject: Test email for connector validation
MIME-Version: 1.0
Content-Type: text/plain; charset=us-ascii
X-MS-PublicTrafficType: Email
X-MS-Office365-Filtering-Correlation-Id: abcd1234-abcd-471a-1234-08d893918edd
X-MS-TrafficTypeDiagnostic: BEXP281MB0216:
X-Microsoft-Antispam-PRVS:
<BEXP281MB021624EF3E3FC35524889C2AB8F70@BEXP281MB0216.DEUP281.PROD.OUTLOOK.COM>
X-MS-Oob-TLC-OOBClassifiers: OLM:2733;
X-MS-Exchange-SenderADCheck: 1
X-Microsoft-Antispam: BCL:0;
X-Microsoft-Antispam-Message-Info:
P2dut4iALZ4EsHFmDE6p0OBg/Q4PvbmhUGI6BnGbHo/u7Vza6tyXE6BPK0VrJQ8WnCYXNx7lEKtiZs8nakJ9EghgxvFRNuYyRBJcGAdlN2TJAb2/7Wp5m7vzuGp1JJhES0RC/hypLDL8miRoP1xYl/pQHZVUGczSddujsZT6im0EgDJvAB0L1vzyKvZJ1QH3vTWDKMAgetlQHiPvCfzZmUgY92g1+sfF9UwGTRXDj8cd83H+TLI7GL8kZF1H219l+DLDiZ3u+qUdprwMn9XDEBljZpczY8BhiFdmnbyJ26ePVNa5JluRboz2Gfaa6GZE+ar8FyKtepxFOyNlI+hyL/vcWNwmnjL+pyYFVPPHnODjxu8JixWg00ThTUiZbclJ
X-Forefront-Antispam-Report:
CIP:255.255.255.255;CTRY:;LANG:en;SCL:1;SRV:;IPV:NLI;SFV:NSPM;H:BE0P281MB0257.DEUP281.PROD.OUTLOOK.COM;PTR:;CAT:NONE;SFS:(376002)(346002)(39830400003)(34036004)(366004)(396003)(136003)(31686004)(78352004)(6916009)(508600001)(5660300002)(42882007)(2906002)(8936002)(558084003)(31696002)(17440700003)(316002)(9686003)(85236043)(68406010)(8676002)(83380400001)(16130700016)(100380200003)(20230700015);DIR:OUT;SFP:1501;
X-OriginatorOrg: lalalulu.onmicrosoft.com
X-MS-Exchange-CrossTenant-OriginalArrivalTime: 28 Nov 2020 11:34:24.7460
(UTC)
X-MS-Exchange-CrossTenant-Network-Message-Id: abcd1234-abcd-471a-1234-08d893918edd
X-MS-Exchange-CrossTenant-AuthSource: AM6P193CA0087.EURP193.PROD.OUTLOOK.COM
X-MS-Exchange-CrossTenant-AuthAs: Internal
X-MS-Exchange-CrossTenant-Id: 1234abcd-18c5-45e8-88de-123456789abc
X-MS-Exchange-CrossTenant-FromEntityHeader: Internet
X-MS-Exchange-Transport-CrossTenantHeadersStamped: BEXP281MB0216
This test email message was sent from Office 365 to check that email can be delivered to you using your new or modified connector. No need to reply.

View File

@ -1,2 +0,0 @@
From: from2@example.com
From: from1@example.org

View File

@ -1,16 +0,0 @@
import sys
import email, email.header
from email.utils import getaddresses
f = open("../samples/exo_validator.eml", "r")
email = email.message_from_file(f)
from_hdr = email.get_all("From")
print("from_hdr: " + str(from_hdr))
if(len(from_hdr) > 1):
print("Multiple From-headers found!")
sys.exit(1)
elif(len(from_hdr) == 1):
print("Exactly one From-header found :)")
print(from_hdr)
from_addr = getaddresses(from_hdr)
print(str(from_addr[0][1]))

View File

@ -33,41 +33,49 @@ end
--if mt.header(conn, "fRoM", '"Blah Blubb" <O365ConnectorValidation@yad.onmicrosoft.com>') ~= nil then
-- error "mt.header(From) failed"
--end
if mt.header(conn, "fRoM", '"Blah Blubb" <O365ConnectorValidation@chillout2k.de>') ~= nil then
if mt.header(conn, "fRoM", '"Blah Blubb" <O365ConnectorValidation@staging.zwackl.de>') ~= nil then
error "mt.header(From) failed"
end
if mt.header(conn, "resent-fRoM", '"Blah Blubb" <blah@yad.onmicrosoft.COM>') ~= nil then
error "mt.header(From) failed"
error "mt.header(Resent-From) failed"
end
if mt.header(conn, "x-mS-EXCHANGE-crosstenant-id", "1234abcd-18c5-45e8-88de-123456789abc") ~= nil then
error "mt.header(Subject) failed"
error "mt.header(X-MS-Exchange-CrossTenant-Id) failed"
end
dkim_sig = "v=1; a=rsa-sha256; c=relaxed/simple; d=staging.zwackl.de;\n"
.."\ts=selector-xyz; t=1685872089;\n"
.."\tbh=5/ZUJAdcuyAn6J+J6apWtAaJLbDCKkI5Ie31qVKiY0w=;\n"
.."\th=Date:From:To:Subject:MIME-Version:Content-Type;\n"
.."\tb=Bn/xAbFFjAg1b9bBFPHAYSaupsnL4pzPPDUauetfGB0hu0Qz0Dio+4Z2Vi6PMOesA\n"
.."\t72VbehuxG+b++XVL/hs3+K6p7vTgVAWiWAZLvfs5bHE5HAalsCrNenpKTk6RUcSYtw\n"
.."\tLiiYhvw0TR5LbyNoSPG2J16mXEcS+k2q+K7WfwMg="
if mt.header(conn, "DKIM-Signature", dkim_sig) ~= nil then
error "mt.header(DKIM-Signature) failed"
end
--if mt.header(conn, "X-MS-Exchange-CrossTenant-Id", "4321abcd-18c5-45e8-88de-blahblubb") ~= nil then
-- error "mt.header(Subject) failed"
--end
if mt.header(conn, "Authentication-Results", "another-wrong-auth-serv-id;\n dkim=fail header.d=yad.onmicrosoft.com header.s=selector1-yad-onmicrosoft-com header.b=mmmjFpv8") ~= nil then
error "mt.header(Subject) failed"
error "mt.header(Authentication-Results) failed"
end
if mt.header(conn, "Authentication-Results", "wrong-auth-serv-id;\n dkim=pass header.d=yad.onmicrosoft.com header.s=selector1-yad-onmicrosoft-com header.b=mmmjFpv8") ~= nil then
error "mt.header(Subject) failed"
error "mt.header(Authentication-Results) failed"
end
if mt.header(conn, "Authentication-Results", "my-auth-serv-id;\n exota=pass") ~= nil then
error "mt.header(Subject) failed"
error "mt.header(Authentication-Results) failed"
end
if mt.header(conn, "Authentication-RESULTS", "my-auth-serv-id;\n dkim=pass header.d=yad.onmicrosoft.comx header.s=selector1-yad-onmicrosoft-com header.b=mmmjFpv8") ~= nil then
error "mt.header(Subject) failed"
error "mt.header(Authentication-Results) failed"
end
if mt.header(conn, "Authentication-RESULTS", "my-auth-serv-id;\n dkim=pass header.d=Chillout2k.de header.s=selector1-yad-onmicrosoft-com header.b=mmmjFpv8") ~= nil then
error "mt.header(Subject) failed"
if mt.header(conn, "Authentication-RESULTS", "my-auth-serv-id;\n dkim=pass header.d=staging.zwackl.de header.s=selector1-yad-onmicrosoft-com header.b=mmmjFpv8") ~= nil then
error "mt.header(Authentication-Results) failed"
end
if mt.header(conn, "Authentication-Results", "my-auth-serv-id;\n dkim=fail header.d=yad.onmicrosoft.com header.s=selector2-asdf header.b=mmmjFpv8") ~= nil then
error "mt.header(Subject) failed"
error "mt.header(Authentication-Results) failed"
end
if mt.header(conn, "Authentication-Results", "some-validating-host;\n dkim=pass header.d=paypal.de header.s=pp-dkim1 header.b=PmTtUzer;\n dmarc=pass (policy=reject) header.from=paypal.de;\n spf=pass (some-validating-host: domain of service@paypal.de designates 173.0.84.226 as permitted sender) smtp.mailfrom=service@paypal.de") ~= nil then
error "mt.header(Subject) failed"
error "mt.header(Authentication-Results) failed"
end
if mt.header(conn, "X-ExOTA-Authentication-Results", "my-auth-serv-id;\n exota=pass") ~= nil then
error "mt.header(Subject) failed"
error "mt.header(X-ExOTA-Authentication-Results) failed"
end
-- EOM

View File

@ -33,7 +33,7 @@ end
--if mt.header(conn, "fRoM", '"Blah Blubb" <O365ConnectorValidation@yad.onmicrosoft.com>') ~= nil then
-- error "mt.header(From) failed"
--end
if mt.header(conn, "fRoM", '"Blah Blubb" <O365ConnectorValidation@chillout2k.de>') ~= nil then
if mt.header(conn, "fRoM", '"Blah Blubb" <O365ConnectorValidation@staging.zwackl.de>') ~= nil then
error "mt.header(From) failed"
end
if mt.header(conn, "resent-fRoM", '"Blah Blubb" <blah@yad.onmicrosoft.COM>') ~= nil then
@ -57,7 +57,7 @@ end
if mt.header(conn, "Authentication-RESULTS", "my-auth-serv-id;\n dkim=pass header.d=yad.onmicrosoft.comx header.s=selector1-yad-onmicrosoft-com header.b=mmmjFpv8") ~= nil then
error "mt.header(Subject) failed"
end
if mt.header(conn, "Authentication-RESULTS", "my-auth-serv-id;\n dkim=pass header.d=chillout2k.de header.s=selector1-yad-onmicrosoft-com header.b=mmmjFpv8") ~= nil then
if mt.header(conn, "Authentication-RESULTS", "my-auth-serv-id;\n dkim=pass header.d=staging.zwackl.de header.s=selector1-yad-onmicrosoft-com header.b=mmmjFpv8") ~= nil then
error "mt.header(Subject) failed"
end
if mt.header(conn, "Authentication-Results", "my-auth-serv-id;\n dkim=fail header.d=yad.onmicrosoft.com header.s=selector2-asdf header.b=mmmjFpv8") ~= nil then
@ -107,13 +107,13 @@ if mt.getreply(conn) ~= SMFIR_CONTINUE then
end
-- HEADER
if mt.header(conn, "fRoM", '"Blah Blubb" <O365ConnectorValidation@chillout2k.de>') ~= nil then
if mt.header(conn, "fRoM", '"Blah Blubb" <O365ConnectorValidation@staging.zwackl.de>') ~= nil then
error "mt.header(From) failed"
end
if mt.header(conn, "x-mS-EXCHANGE-crosstenant-id", "1234abcd-18c5-45e8-88de-123456789abc") ~= nil then
error "mt.header(Subject) failed"
end
if mt.header(conn, "Authentication-RESULTS", "my-auth-serv-id;\n dkim=pass header.d=chillout2k.de header.s=selector1-yad-onmicrosoft-com header.b=mmmjFpv8") ~= nil then
if mt.header(conn, "Authentication-RESULTS", "my-auth-serv-id;\n dkim=pass header.d=staging.zwackl.de header.s=selector1-yad-onmicrosoft-com header.b=mmmjFpv8") ~= nil then
error "mt.header(Subject) failed"
end

View File

@ -31,17 +31,17 @@ if mt.getreply(conn) ~= SMFIR_CONTINUE then
end
-- HEADER
if mt.header(conn, "fRoM", '"Blah Blubb" <O365ConnectorValidation@chillout2k.de>') ~= nil then
if mt.header(conn, "fRoM", '"Blah Blubb" <O365ConnectorValidation@staging.zwackl.de>') ~= nil then
error "mt.header(From) failed"
end
if mt.header(conn, "x-mS-EXCHANGE-crosstenant-id", "1234abcd-18c5-45e8-88de-123456789abcXXX") ~= nil then
error "mt.header(Subject) failed"
error "mt.header(x-mS-EXCHANGE-crosstenant-id) failed"
end
if mt.header(conn, "Authentication-RESULTS", "my-auth-serv-id;\n dkim=fail header.d=chillout2k.de header.s=selector1-yad-onmicrosoft-com header.b=mmmjFpv8") ~= nil then
error "mt.header(Subject) failed"
if mt.header(conn, "Authentication-RESULTS", "my-auth-serv-id;\n dkim=fail header.d=staging.zwackl.de header.s=selector1-yad-onmicrosoft-com header.b=mmmjFpv8") ~= nil then
error "mt.header(Authentication-RESULTS) failed"
end
if mt.header(conn, "X-ExOTA-Authentication-Results", "my-auth-serv-id;\n exota=pass") ~= nil then
error "mt.header(Subject) failed"
error "mt.header(X-ExOTA-Authentication-Results) failed"
end
-- EOM
@ -81,14 +81,14 @@ if mt.getreply(conn) ~= SMFIR_CONTINUE then
end
-- HEADER
if mt.header(conn, "fRoM", '"Blah Blubb" <O365ConnectorValidation@chillout2k.de>') ~= nil then
if mt.header(conn, "fRoM", '"Blah Blubb" <O365ConnectorValidation@staging.zwackl.de>') ~= nil then
error "mt.header(From) failed"
end
if mt.header(conn, "x-mS-EXCHANGE-crosstenant-id", "1234abcd-18c5-45e8-88de-123456789abc") ~= nil then
error "mt.header(Subject) failed"
error "mt.header(x-mS-EXCHANGE-crosstenant-id) failed"
end
if mt.header(conn, "Authentication-RESULTS", "my-auth-serv-id;\n dkim=pass header.d=chillout2k.de header.s=selector1-yad-onmicrosoft-com header.b=mmmjFpv8") ~= nil then
error "mt.header(Subject) failed"
if mt.header(conn, "Authentication-RESULTS", "my-auth-serv-id;\n dkim=pass header.d=staging.zwackl.de header.s=selector1-yad-onmicrosoft-com header.b=mmmjFpv8") ~= nil then
error "mt.header(Authentication-RESULTS) failed"
end
-- EOM
@ -127,17 +127,17 @@ if mt.getreply(conn) ~= SMFIR_CONTINUE then
end
-- HEADER
if mt.header(conn, "fRoM", '"Blah Blubb" <O365ConnectorValidation@chillout2k.de>') ~= nil then
if mt.header(conn, "fRoM", '"Blah Blubb" <O365ConnectorValidation@staging.zwackl.de>') ~= nil then
error "mt.header(From) failed"
end
if mt.header(conn, "x-mS-EXCHANGE-crosstenant-id", "1234abcd-18c5-45e8-88de-123456789abcXXX") ~= nil then
error "mt.header(Subject) failed"
error "mt.header(x-mS-EXCHANGE-crosstenant-id) failed"
end
if mt.header(conn, "Authentication-RESULTS", "my-auth-serv-id;\n dkim=fail header.d=chillout2k.de header.s=selector1-yad-onmicrosoft-com header.b=mmmjFpv8") ~= nil then
error "mt.header(Subject) failed"
if mt.header(conn, "Authentication-RESULTS", "my-auth-serv-id;\n dkim=fail header.d=staging.zwackl.de header.s=selector1-yad-onmicrosoft-com header.b=mmmjFpv8") ~= nil then
error "mt.header(Authentication-RESULTS) failed"
end
if mt.header(conn, "X-ExOTA-Authentication-Results", "my-auth-serv-id;\n exota=pass") ~= nil then
error "mt.header(Subject) failed"
error "mt.header(X-ExOTA-Authentication-Results) failed"
end
-- EOM

View File

@ -30,7 +30,7 @@ if mt.getreply(conn) ~= SMFIR_CONTINUE then
end
-- HEADER
if mt.header(conn, "fRoM", '"Blah Blubb" <O365ConnectorValidation@chillout2kx.de>') ~= nil then
if mt.header(conn, "fRoM", '"Blah Blubb" <O365ConnectorValidation@staging.zwacklx.de>') ~= nil then
error "mt.header(From) failed"
end
if mt.header(conn, "x-mS-EXCHANGE-crosstenant-id", "1234abcd-18c5-45e8-88de-123456789abc") ~= nil then

View File

@ -30,7 +30,7 @@ if mt.getreply(conn) ~= SMFIR_CONTINUE then
end
-- HEADER
if mt.header(conn, "fRoM", '"Blah Blubb" <O365ConnectorValidation@chillout2k.de>') ~= nil then
if mt.header(conn, "fRoM", '"Blah Blubb" <O365ConnectorValidation@staging.zwackl.de>') ~= nil then
error "mt.header(From) failed"
end
if mt.header(conn, "x-mS-EXCHANGE-crosstenant-id", "1234abcd-18c5-45e8-88de-123456789abc") ~= nil then
@ -42,7 +42,7 @@ end
if mt.header(conn, "X-MS-Exchange-CrossTenant-Id", "4321abcd-18c5-45e8-88de-blahblubb") ~= nil then
error "mt.header(tenant-id fail) failed"
end
if mt.header(conn, "Authentication-RESULTS", "my-auth-serv-id;\n dkim=pass header.d=chillout2k.de header.s=selector1-yad-onmicrosoft-com header.b=mmmjFpv8") ~= nil then
if mt.header(conn, "Authentication-RESULTS", "my-auth-serv-id;\n dkim=pass header.d=staging.zwackl.de header.s=selector1-yad-onmicrosoft-com header.b=mmmjFpv8") ~= nil then
error "mt.header(DKIM-AR) failed"
end
if mt.header(conn, "X-ExOTA-Authentication-Results", "my-auth-serv-id;\n exota=pass") ~= nil then

View File

@ -30,7 +30,7 @@ if mt.getreply(conn) ~= SMFIR_CONTINUE then
end
-- HEADER
if mt.header(conn, "fRoM", '"Blah Blubb" <O365ConnectorValidation@chillout2k.de>') ~= nil then
if mt.header(conn, "fRoM", '"Blah Blubb" <O365ConnectorValidation@staging.zwackl.de>') ~= nil then
error "mt.header(From) failed"
end
if mt.header(conn, "resent-fRoM", '"Blah Blubb" <blah@yad.onmicrosoft.COM>') ~= nil then
@ -48,7 +48,7 @@ end
if mt.header(conn, "Authentication-RESULTS", "my-auth-serv-id;\n dkim=pass header.d=yad.onmicrosoft.comx header.s=selector1-yad-onmicrosoft-com header.b=mmmjFpv8") ~= nil then
error "mt.header(Subject) failed"
end
if mt.header(conn, "Authentication-RESULTS", "my-auth-serv-id;\n dkim=pass header.d=Chillout2k.de header.s=selector1-yad-onmicrosoft-com header.b=mmmjFpv8") ~= nil then
if mt.header(conn, "Authentication-RESULTS", "my-auth-serv-id;\n dkim=pass header.d=staging.zwackl.de header.s=selector1-yad-onmicrosoft-com header.b=mmmjFpv8") ~= nil then
error "mt.header(Subject) failed"
end
if mt.header(conn, "Authentication-Results", "my-auth-serv-id;\n dkim=fail header.d=yad.onmicrosoft.com header.s=selector2-asdf header.b=mmmjFpv8") ~= nil then

View File

@ -7,5 +7,10 @@
"example.com": {
"tenant_id": "abcd1234-18c5-45e8-88de-987654321cba",
"dkim_enabled": false
},
"staging.zwackl.de": {
"tenant_id": "",
"dkim_enabled": true,
"dkim_alignment_required": true
}
}