Compare commits

...

12 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
Dominik Chilla
7b60d6a845
Merge pull request #46 from chillout2k/devel
Make X-MS-Exchange-CrossTenant-Id header optional
2022-08-31 13:21:34 +02:00
ca703bee9c Make X-MS-Exchange-CrossTenant-Id header optional 2022-08-31 13:15:13 +02:00
Dominik Chilla
0dc507a425
Merge pull request #45 from chillout2k/devel
systemd install typo
2022-08-24 21:52:14 +02:00
010e9eadfa systemd install typo 2022-08-24 21:51:31 +02:00
23 changed files with 397 additions and 302 deletions

View File

@ -1,98 +1,122 @@
# How to install ExOTA-Milter
#### Table of contents
[docker-compose](#docker-compose)
[kubernetes](#kubernetes)
[systemd](#systemd)
## docker-compose <a name="docker-compose"/>
```
~/src/ExOTA-Milter/INSTALL/docker-compose$ docker-compose up
[+] Running 2/2
⠿ Network docker-compose_default Created 0.8s
⠿ Container docker-compose-exota-milter-1 Created 0.1s
Attaching to docker-compose-exota-milter-1
docker-compose-exota-milter-1 | 2022-06-06 21:54:04,503: INFO 140529821924168 Logger initialized
docker-compose-exota-milter-1 | 2022-06-06 21:54:04,503: INFO 140529821924168 ENV[MILTER_NAME]: exota-milter
docker-compose-exota-milter-1 | 2022-06-06 21:54:04,503: INFO 140529821924168 ENV[MILTER_SOCKET]: inet:4321@0.0.0.0
docker-compose-exota-milter-1 | 2022-06-06 21:54:04,504: INFO 140529821924168 ENV[MILTER_REJECT_MESSAGE]: CUSTOMIZE THIS! - Security policy violation!!
docker-compose-exota-milter-1 | 2022-06-06 21:54:04,504: INFO 140529821924168 ENV[MILTER_TMPFAIL_MESSAGE]: Service temporarily not available! Please try again later.
docker-compose-exota-milter-1 | 2022-06-06 21:54:04,504: INFO 140529821924168 ENV[MILTER_TRUSTED_AUTHSERVID]: dkimauthservid
docker-compose-exota-milter-1 | 2022-06-06 21:54:04,504: INFO 140529821924168 ENV[MILTER_DKIM_ALIGNMENT_REQUIRED]: True
docker-compose-exota-milter-1 | 2022-06-06 21:54:04,504: INFO 140529821924168 ENV[MILTER_DKIM_ENABLED]: True
docker-compose-exota-milter-1 | 2022-06-06 21:54:04,504: INFO 140529821924168 ENV[MILTER_X509_TRUSTED_CN]: mail.protection.outlook.com
docker-compose-exota-milter-1 | 2022-06-06 21:54:04,504: INFO 140529821924168 ENV[MILTER_X509_IP_WHITELIST]: ['127.0.0.1', '::1']
docker-compose-exota-milter-1 | 2022-06-06 21:54:04,504: INFO 140529821924168 ENV[MILTER_X509_ENABLED]: True
docker-compose-exota-milter-1 | 2022-06-06 21:54:04,505: INFO 140529821924168 ENV[MILTER_AUTHSERVID]: ThisAuthservID
docker-compose-exota-milter-1 | 2022-06-06 21:54:04,505: INFO 140529821924168 ENV[MILTER_ADD_HEADER]: True
docker-compose-exota-milter-1 | 2022-06-06 21:54:04,505: INFO 140529821924168 ENV[MILTER_POLICY_SOURCE]: file
docker-compose-exota-milter-1 | 2022-06-06 21:54:04,505: INFO 140529821924168 ENV[MILTER_POLICY_FILE]: /data/exota-milter-policy.json
docker-compose-exota-milter-1 | 2022-06-06 21:54:04,511: INFO 140529821924168 JSON policy backend initialized
docker-compose-exota-milter-1 | 2022-06-06 21:54:04,511: INFO 140529821924168 Startup exota-milter@socket: inet:4321@0.0.0.0
```
## kubernetes <a name="kubernetes"/>
```
~/src/ExOTA-Milter/INSTALL/kubernetes$ kubectl apply -f 01_config-map.yaml
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
```
Check status of pods, replica-sets and service
```
~/src/ExOTA-Milter/INSTALL/kubernetes$ kubectl -n devel 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
pod/exota-milter-547dbccd8b-k4ng8 1/1 Running 0 64s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/exota-milter ClusterIP 10.43.78.163 <none> 4321/TCP 61s
NAME READY UP-TO-DATE AVAILABLE AGE
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:
```
~/src/ExOTA-Milter/INSTALL/kubernetes$ kubectl -n devel logs exota-milter-547dbccd8b-7hl6c
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
2022-06-06 21:57:03,515: INFO ENV[MILTER_REJECT_MESSAGE]: Security policy violation!!
2022-06-06 21:57:03,515: INFO ENV[MILTER_TMPFAIL_MESSAGE]: Service temporarily not available! Please try again later.
2022-06-06 21:57:03,515: INFO ENV[MILTER_TRUSTED_AUTHSERVID]: dkimauthservid
2022-06-06 21:57:03,515: INFO ENV[MILTER_DKIM_ALIGNMENT_REQUIRED]: True
2022-06-06 21:57:03,515: INFO ENV[MILTER_DKIM_ENABLED]: True
2022-06-06 21:57:03,515: INFO ENV[MILTER_X509_TRUSTED_CN]: mail.protection.outlook.com
2022-06-06 21:57:03,515: INFO ENV[MILTER_X509_IP_WHITELIST]: ['127.0.0.1', '::1']
2022-06-06 21:57:03,515: INFO ENV[MILTER_X509_ENABLED]: True
2022-06-06 21:57:03,516: INFO ENV[MILTER_AUTHSERVID]: some-auth-serv-id
2022-06-06 21:57:03,516: INFO ENV[MILTER_ADD_HEADER]: True
2022-06-06 21:57:03,516: INFO ENV[MILTER_POLICY_SOURCE]: file
2022-06-06 21:57:03,516: INFO ENV[MILTER_POLICY_FILE]: /data/exota-milter-policy.json
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
```
## 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 dependencies:
```
~/src/ExOTA-Milter/INSTALL/systemd# sudo pip3 install -r ../../requirements.txt
Requirement already satisfied: authres==1.2.0 in /usr/local/lib/python3.8/dist-packages (from -r ../../requirements.txt (line 1)) (1.2.0)
Requirement already satisfied: pymilter==1.0.4 in /usr/local/lib/python3.8/dist-packages (from -r ../../requirements.txt (line 2)) (1.0.4)
Requirement already satisfied: ldap3 in /usr/local/lib/python3.8/dist-packages (from -r ../../requirements.txt (line 3)) (2.9.1)
Requirement already satisfied: pyasn1>=0.4.6 in /usr/local/lib/python3.8/dist-packages (from ldap3->-r ../../requirements.txt (line 3)) (0.4.8)
```
Next you should be able to install the ExOTA-Milter as well as the systemd-stuff by running the `install.sh` script:
```
~/src/ExOTA-Milter/INSTALL/systemd$ sudo ./install.sh
Created symlink /etc/systemd/system/multi-user.target.wants/exota-milter.service → /lib/systemd/system/exota-milter.service.
```
Use the `uninstall.sh` script to uninstall the ExOTA-Milter from your systemd environment. Files under `/etc/exota-milter/` (config and policy) are kept undeleted!
# How to install ExOTA-Milter
#### Table of contents
[docker-compose](#docker-compose)
[kubernetes](#kubernetes)
[systemd](#systemd)
## docker-compose <a name="docker-compose"/>
```
~/src/ExOTA-Milter/INSTALL/docker-compose$ docker-compose up
[+] Running 2/2
⠿ Network docker-compose_default Created 0.8s
⠿ Container docker-compose-exota-milter-1 Created 0.1s
Attaching to docker-compose-exota-milter-1
docker-compose-exota-milter-1 | 2022-06-06 21:54:04,503: INFO 140529821924168 Logger initialized
docker-compose-exota-milter-1 | 2022-06-06 21:54:04,503: INFO 140529821924168 ENV[MILTER_NAME]: exota-milter
docker-compose-exota-milter-1 | 2022-06-06 21:54:04,503: INFO 140529821924168 ENV[MILTER_SOCKET]: inet:4321@0.0.0.0
docker-compose-exota-milter-1 | 2022-06-06 21:54:04,504: INFO 140529821924168 ENV[MILTER_REJECT_MESSAGE]: CUSTOMIZE THIS! - Security policy violation!!
docker-compose-exota-milter-1 | 2022-06-06 21:54:04,504: INFO 140529821924168 ENV[MILTER_TMPFAIL_MESSAGE]: Service temporarily not available! Please try again later.
docker-compose-exota-milter-1 | 2022-06-06 21:54:04,504: INFO 140529821924168 ENV[MILTER_TRUSTED_AUTHSERVID]: dkimauthservid
docker-compose-exota-milter-1 | 2022-06-06 21:54:04,504: INFO 140529821924168 ENV[MILTER_DKIM_ALIGNMENT_REQUIRED]: True
docker-compose-exota-milter-1 | 2022-06-06 21:54:04,504: INFO 140529821924168 ENV[MILTER_DKIM_ENABLED]: True
docker-compose-exota-milter-1 | 2022-06-06 21:54:04,504: INFO 140529821924168 ENV[MILTER_X509_TRUSTED_CN]: mail.protection.outlook.com
docker-compose-exota-milter-1 | 2022-06-06 21:54:04,504: INFO 140529821924168 ENV[MILTER_X509_IP_WHITELIST]: ['127.0.0.1', '::1']
docker-compose-exota-milter-1 | 2022-06-06 21:54:04,504: INFO 140529821924168 ENV[MILTER_X509_ENABLED]: True
docker-compose-exota-milter-1 | 2022-06-06 21:54:04,505: INFO 140529821924168 ENV[MILTER_AUTHSERVID]: ThisAuthservID
docker-compose-exota-milter-1 | 2022-06-06 21:54:04,505: INFO 140529821924168 ENV[MILTER_ADD_HEADER]: True
docker-compose-exota-milter-1 | 2022-06-06 21:54:04,505: INFO 140529821924168 ENV[MILTER_POLICY_SOURCE]: file
docker-compose-exota-milter-1 | 2022-06-06 21:54:04,505: INFO 140529821924168 ENV[MILTER_POLICY_FILE]: /data/exota-milter-policy.json
docker-compose-exota-milter-1 | 2022-06-06 21:54:04,511: INFO 140529821924168 JSON policy backend initialized
docker-compose-exota-milter-1 | 2022-06-06 21:54:04,511: INFO 140529821924168 Startup exota-milter@socket: inet:4321@0.0.0.0
```
## kubernetes <a name="kubernetes"/>
By default this example installs the Exota-milter workload into the `exota-milter` namespace, which must be created in advance:
```
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
service/exota-milter created
deployment.apps/exota-milter created
```
Check status of pods, replica-sets and cluster internal service:
```
~/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
pod/exota-milter-547dbccd8b-k4ng8 1/1 Running 0 64s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/exota-milter ClusterIP 10.43.78.163 <none> 4321/TCP 61s
NAME READY UP-TO-DATE AVAILABLE AGE
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 the pods:
```
~/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
2022-06-06 21:57:03,515: INFO ENV[MILTER_REJECT_MESSAGE]: Security policy violation!!
2022-06-06 21:57:03,515: INFO ENV[MILTER_TMPFAIL_MESSAGE]: Service temporarily not available! Please try again later.
2022-06-06 21:57:03,515: INFO ENV[MILTER_TRUSTED_AUTHSERVID]: dkimauthservid
2022-06-06 21:57:03,515: INFO ENV[MILTER_DKIM_ALIGNMENT_REQUIRED]: True
2022-06-06 21:57:03,515: INFO ENV[MILTER_DKIM_ENABLED]: True
2022-06-06 21:57:03,515: INFO ENV[MILTER_X509_TRUSTED_CN]: mail.protection.outlook.com
2022-06-06 21:57:03,515: INFO ENV[MILTER_X509_IP_WHITELIST]: ['127.0.0.1', '::1']
2022-06-06 21:57:03,515: INFO ENV[MILTER_X509_ENABLED]: True
2022-06-06 21:57:03,516: INFO ENV[MILTER_AUTHSERVID]: some-auth-serv-id
2022-06-06 21:57:03,516: INFO ENV[MILTER_ADD_HEADER]: True
2022-06-06 21:57:03,516: INFO ENV[MILTER_POLICY_SOURCE]: file
2022-06-06 21:57:03,516: INFO ENV[MILTER_POLICY_FILE]: /data/exota-milter-policy.json
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):
```
sudo apt install --no-install-recommends gcc libpython3-dev libmilter-dev python3-pip
```
Now install all python dependencies:
```
~/src/ExOTA-Milter/INSTALL/systemd# sudo pip3 install -r ../../requirements.txt
Requirement already satisfied: authres==1.2.0 in /usr/local/lib/python3.8/dist-packages (from -r ../../requirements.txt (line 1)) (1.2.0)
Requirement already satisfied: pymilter==1.0.4 in /usr/local/lib/python3.8/dist-packages (from -r ../../requirements.txt (line 2)) (1.0.4)
Requirement already satisfied: ldap3 in /usr/local/lib/python3.8/dist-packages (from -r ../../requirements.txt (line 3)) (2.9.1)
Requirement already satisfied: pyasn1>=0.4.6 in /usr/local/lib/python3.8/dist-packages (from ldap3->-r ../../requirements.txt (line 3)) (0.4.8)
```
At last uninstall all build dependencies, as they are not needed anymore:
```
apt purge gcc libpython3-dev libmilter-dev python3-pip
```
Next you should be able to install the ExOTA-Milter as well as the systemd-stuff by running the `install.sh` script:
```
~/src/ExOTA-Milter/INSTALL/systemd$ sudo ./install.sh
Created symlink /etc/systemd/system/multi-user.target.wants/exota-milter.service → /lib/systemd/system/exota-milter.service.
```
Use the `uninstall.sh` script to uninstall the ExOTA-Milter from your systemd environment. Files under `/etc/exota-milter/` (config and policy) are kept undeleted!

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

@ -3,7 +3,7 @@ Description=ExOTA-Milter
[Service]
Restart=always
ExecStart=/usr/loca/sbin/exota-milter.sh
ExecStart=/usr/local/sbin/exota-milter.sh
[Install]
WantedBy=multi-user.target

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

@ -25,6 +25,8 @@ g_milter_socket = '/socket/' + g_milter_name
g_milter_reject_message = 'Security policy violation!'
# ENV[MILTER_TMPFAIL_MESSAGE]
g_milter_tmpfail_message = 'Service temporarily not available! Please try again later.'
# ENV[MILTER_TENANT_ID_REQUIRED]
g_milter_tenant_id_required = False
# ENV[MILTER_DKIM_ENABLED]
g_milter_dkim_enabled = False
# ENV[MILTER_DKIM_ALIGNMENT_REQUIRED]
@ -163,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)
@ -185,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)
@ -200,24 +204,37 @@ class ExOTAMilter(Milter.Base):
self.hdr_resent_from, self.hdr_resent_from_domain
)
)
# 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: Tenant-ID: {0}".format(hval.lower())
)
if self.hdr_tenant_id_count > 0:
if not self.hdr_tenant_id == hval.lower():
self.hdr_different_tenant_id = True
log_info(self.mconn_id + "/" + str(self.getsymval('i')) +
"/HDR: Different Tenant-IDs found!"
"/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())
)
else:
self.hdr_tenant_id_count += 1
self.hdr_tenant_id = hval.lower()
if self.hdr_tenant_id_count > 0:
if not self.hdr_tenant_id == hval.lower():
self.hdr_different_tenant_id = True
log_info(self.mconn_id + "/" + str(self.getsymval('i')) +
"/HDR: Different Tenant-IDs found!"
)
else:
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:
@ -245,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."
)
@ -300,16 +320,17 @@ class ExOTAMilter(Milter.Base):
reason = '5322.from header missing'
)
if self.hdr_different_tenant_id == True:
log_info(self.mconn_id + "/" + str(self.getsymval('i')) +
"/EOM: Multiple/different tenant-ID headers found for {0} - action=reject".format(
self.hdr_from_domain
if g_milter_tenant_id_required == True:
if self.hdr_different_tenant_id == True:
log_info(self.mconn_id + "/" + str(self.getsymval('i')) +
"/EOM: Multiple/different tenant-ID headers found for {0} - action=reject".format(
self.hdr_from_domain
)
)
return self.smfir_reject(
queue_id = self.getsymval('i'),
reason = 'Multiple/different tenant-ID headers found!'
)
)
return self.smfir_reject(
queue_id = self.getsymval('i'),
reason = 'Multiple/different tenant-ID headers found!'
)
# Get policy for 5322.from_domain
policy = None
@ -395,28 +416,29 @@ class ExOTAMilter(Milter.Base):
reason = "No policy for 5322.from_domain {0}".format(self.hdr_from_domain)
)
if self.hdr_tenant_id is None:
log_error(self.mconn_id + "/" + str(self.getsymval('i')) +
"/EOM: exception: could not determine X-MS-Exchange-CrossTenant-Id - action=reject"
)
return self.smfir_reject(
queue_id = self.getsymval('i'),
reason = 'Tenant-ID is missing!'
)
if self.hdr_tenant_id == policy.get_tenant_id():
log_info(self.mconn_id + "/" + str(self.getsymval('i')) +
"/EOM: tenant_id={0} status=match".format(self.hdr_tenant_id)
)
else:
log_info(self.mconn_id + "/" + str(self.getsymval('i')) +
"/EOM: tenant_id={0} status=no_match - action=reject".format(
self.hdr_tenant_id
if g_milter_tenant_id_required == True:
if self.hdr_tenant_id is None:
log_error(self.mconn_id + "/" + str(self.getsymval('i')) +
"/EOM: exception: could not determine X-MS-Exchange-CrossTenant-Id - action=reject"
)
return self.smfir_reject(
queue_id = self.getsymval('i'),
reason = 'Tenant-ID is missing!'
)
if self.hdr_tenant_id == policy.get_tenant_id():
log_info(self.mconn_id + "/" + str(self.getsymval('i')) +
"/EOM: tenant_id={0} status=match".format(self.hdr_tenant_id)
)
else:
log_info(self.mconn_id + "/" + str(self.getsymval('i')) +
"/EOM: tenant_id={0} status=no_match - action=reject".format(
self.hdr_tenant_id
)
)
return self.smfir_reject(
queue_id = self.getsymval('i'),
reason = 'No policy match for tenant-id'
)
)
return self.smfir_reject(
queue_id = self.getsymval('i'),
reason = 'No policy match for tenant-id'
)
if g_milter_dkim_enabled and policy.is_dkim_enabled():
log_debug(self.mconn_id + "/" + str(self.getsymval('i')) +
@ -529,6 +551,9 @@ if __name__ == "__main__":
if 'MILTER_TMPFAIL_MESSAGE' in os.environ:
g_milter_tmpfail_message = os.environ['MILTER_TMPFAIL_MESSAGE']
log_info("ENV[MILTER_TMPFAIL_MESSAGE]: {0}".format(g_milter_tmpfail_message))
if 'MILTER_TENANT_ID_REQUIRED' in os.environ:
g_milter_tenant_id_required = True
log_info("ENV[MILTER_TENANT_ID_REQUIRED]: {0}".format(g_milter_tenant_id_required))
if 'MILTER_DKIM_ENABLED' in os.environ:
g_milter_dkim_enabled = True
if 'MILTER_TRUSTED_AUTHSERVID' in os.environ:

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

@ -0,0 +1,82 @@
-- https://mopano.github.io/sendmail-filter-api/constant-values.html#com.sendmail.milter.MilterConstants
-- http://www.opendkim.org/miltertest.8.html
-- socket must be defined as miltertest global variable (-D)
conn = mt.connect(socket)
if conn == nil then
error "mt.connect() failed"
end
if mt.conninfo(conn, "localhost", "::1") ~= nil then
error "mt.conninfo() failed"
end
mt.set_timeout(60)
-- 5321.FROM
if mt.mailfrom(conn, "envelope.sender@example.org") ~= nil then
error "mt.mailfrom() failed"
end
if mt.getreply(conn) ~= SMFIR_CONTINUE then
error "mt.mailfrom() unexpected reply"
end
-- 5321.RCPT+MACROS
mt.macro(conn, SMFIC_RCPT, "i", "4CgSNs5Q9sz7SllQ", '{cert_subject}', "mail.protection.outlook.comx")
if mt.rcptto(conn, "<envelope.recipient@example.com>") ~= nil then
error "mt.rcptto() failed"
end
if mt.getreply(conn) ~= SMFIR_CONTINUE then
error "mt.rcptto() unexpected reply"
end
-- HEADER
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"
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"
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"
end
if mt.header(conn, "Authentication-Results", "my-auth-serv-id;\n exota=pass") ~= nil then
error "mt.header(Subject) 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"
end
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
error "mt.header(Subject) 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"
end
if mt.header(conn, "X-ExOTA-Authentication-Results", "my-auth-serv-id;\n exota=pass") ~= nil then
error "mt.header(Subject) failed"
end
-- EOM
if mt.eom(conn) ~= nil then
error "mt.eom() failed"
end
mt.echo("EOM: " .. mt.getreply(conn))
if mt.getreply(conn) == SMFIR_CONTINUE then
mt.echo("EOM-continue")
elseif mt.getreply(conn) == SMFIR_REPLYCODE then
mt.echo("EOM-reject")
end
if not mt.eom_check(conn, MT_HDRADD, "X-ExOTA-Authentication-Results") then
mt.echo("no header added")
else
mt.echo("X-ExOTA-Authentication-Results header added")
end
-- DISCONNECT
mt.disconnect(conn)

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
}
}