mirror of
https://github.com/chillout2k/ExOTA-Milter.git
synced 2025-12-14 18:30:19 +00:00
Compare commits
No commits in common. "fa8458bb646797769bd5328af25c9b325b9475bb" and "04c440624b3b0eb84ed042f96eb55f0bdc3f6b62" have entirely different histories.
fa8458bb64
...
04c440624b
@ -1,25 +0,0 @@
|
|||||||
FROM alpine
|
|
||||||
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 \
|
|
||||||
&& pip3 install -r requirements.txt \
|
|
||||||
&& apk del gcc libc-dev libmilter-dev python3-dev py3-pip \
|
|
||||||
&& apk add libmilter \
|
|
||||||
&& adduser -D exota-milter \
|
|
||||||
&& install -d -o exota-milter /socket /data \
|
|
||||||
&& rm -rf /var/cache/apk/* /requirements.txt
|
|
||||||
|
|
||||||
ADD ./app/ /app/
|
|
||||||
ADD ./OCI/cmd /cmd
|
|
||||||
RUN chown -R exota-milter /app /cmd \
|
|
||||||
&& chmod -R +x /app /cmd
|
|
||||||
|
|
||||||
# Default file policy path: /data/policy.json
|
|
||||||
VOLUME [ "/data" ]
|
|
||||||
|
|
||||||
USER exota-milter
|
|
||||||
CMD [ "/cmd" ]
|
|
||||||
105
README.md
105
README.md
@ -2,12 +2,6 @@
|
|||||||
|
|
||||||
The **ExOTA-[Milter](https://en.wikipedia.org/wiki/Milter)** application is written in python3 and derives from **[sdgathman´s pymilter](https://github.com/sdgathman/pymilter)**.
|
The **ExOTA-[Milter](https://en.wikipedia.org/wiki/Milter)** application is written in python3 and derives from **[sdgathman´s pymilter](https://github.com/sdgathman/pymilter)**.
|
||||||
|
|
||||||
# Synopsis
|
|
||||||
TODO
|
|
||||||
|
|
||||||
# Table of contents
|
|
||||||
TODO
|
|
||||||
|
|
||||||
# Abstract/problem/motivation
|
# Abstract/problem/motivation
|
||||||
Fact is that more and more companies are migrating their Outlook/Exchange environments to the [Microsoft cloud](https://www.microsoft.com/microsoft-365).
|
Fact is that more and more companies are migrating their Outlook/Exchange environments to the [Microsoft cloud](https://www.microsoft.com/microsoft-365).
|
||||||
|
|
||||||
@ -76,23 +70,13 @@ Authentication-Results: trusted.dkim.validating.relay; dkim=pass header.d=tenan
|
|||||||
[...]
|
[...]
|
||||||
```
|
```
|
||||||
|
|
||||||
## X-MS-Exchange-CrossTenant-Id header (policy binding)
|
## 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** extracts the tenant-ID from the *X-MS-Exchange-CrossTenant-Id* email header and uses it as a *mandatory* authentication factor.
|
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>
|
X-MS-Exchange-CrossTenant-Id: <UUID-of-tenant>
|
||||||
[...]
|
[...]
|
||||||
```
|
```
|
||||||
At last the **ExOTA-Milter** needs an additional policy (JSON file), that provides a mapping of *sender-domain <-> tenant-id* and if DKIM-signatures must be taken under consideration or not. The JSON policy file itself looks like this:
|
|
||||||
```
|
|
||||||
{
|
|
||||||
"lalalulu.onmicrosoft.com": {
|
|
||||||
"tenant_id": "1234abcd-18c5-45e8-88de-123456789abc",
|
|
||||||
"dkim_enabled": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
Actually I´m also working on a LDAP-based version as policy backend.
|
|
||||||
|
|
||||||
# The solution
|
# The solution
|
||||||
So, *how can an Exchange-Online user/tenant be identified by a third party smarthost?*
|
So, *how can an Exchange-Online user/tenant be identified by a third party smarthost?*
|
||||||
@ -101,90 +85,9 @@ Finally it´s the combination of all of the above discussed aspects which may re
|
|||||||
* restriction of client IPs via ACL (MTA)
|
* restriction of client IPs via ACL (MTA)
|
||||||
* verification of Microsoft´s x509 client certificate (MTA)
|
* verification of Microsoft´s x509 client certificate (MTA)
|
||||||
* matching for client certificate´s CN (ExOTA-Milter)
|
* matching for client certificate´s CN (ExOTA-Milter)
|
||||||
* verification of DKIM signatures providing *Authentication-Results* header (another milter, e.g. OpenDKIM)
|
* verification of DKIM signatures providing *Authentication-Results* header (another milter)
|
||||||
* consideration of DKIM verification results per sender domain (ExOTA-Milter)
|
* consideration of DKIM verification results per sender domain (ExOTA-Milter)
|
||||||
* matching for tenant-id provided in *X-MS-Exchange-CrossTenant-Id* header (ExOTA-Milter)
|
* matching for tenant-id provided in *X-MS-Exchange-CrossTenant-Id* header (ExOTA-Milter)
|
||||||
|
|
||||||
# How about a docker/OCI image?
|
# How to start?
|
||||||
## Using prebuilt images from dockerhub.com
|
|
||||||
**WIP ;-)**
|
|
||||||
|
|
||||||
## Build your own image
|
|
||||||
Actually I´m going with docker-ce to build the container image, but same results should come out with e.g. [img](https://github.com/genuinetools/img) etc.
|
|
||||||
|
|
||||||
Run following command in the root directory of this repo:
|
|
||||||
```
|
|
||||||
docker build -t exota-milter:local -f OCI/Dockerfile .
|
|
||||||
[...]
|
|
||||||
Successfully built 9cceb121f604
|
|
||||||
Successfully tagged exota-milter:local
|
|
||||||
```
|
|
||||||
|
|
||||||
## Deploy the OCI image with `docker-compose`
|
|
||||||
Prerequisites: `docker-compose` installed
|
|
||||||
* Create a deployment directory and jump into it. In my case it´s `/docker/containers/exota-milter`
|
|
||||||
* `install -d /docker/containers/exota-milter`
|
|
||||||
* `cd /docker/containers/exota-milter`
|
|
||||||
* Create further directories in the deployment directory:
|
|
||||||
* `install -d -m 777 data`. The application expects the policy file in `/data/policy.json` (path inside the container!).
|
|
||||||
* `install -d -m 777 socket`. The application places the milter socket file under `/socket/exota-milter` (path inside the container!)
|
|
||||||
* Create the policy file `data/policy.json` with following content:
|
|
||||||
```
|
|
||||||
{
|
|
||||||
"lalalulu.onmicrosoft.com": {
|
|
||||||
"tenant_id": "1234abcd-18c5-45e8-88de-123456789abc",
|
|
||||||
"dkim_enabled": true
|
|
||||||
},
|
|
||||||
"asdf2.onmicrosoft.com": {
|
|
||||||
"tenant_id": "asdftasdfa",
|
|
||||||
"dkim_enabled": false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
* Create a file named `docker-compose.yml` in the deployment directory with following content:
|
|
||||||
```
|
|
||||||
version: '2.4'
|
|
||||||
|
|
||||||
services:
|
|
||||||
exota-milter:
|
|
||||||
image: exota-milter:local
|
|
||||||
environment:
|
|
||||||
LOG_LEVEL: 'debug'
|
|
||||||
MILTER_SOCKET: '/socket/exota-milter'
|
|
||||||
#MILTER_SOCKET: 'inet:123456@0.0.0.0'
|
|
||||||
MILTER_POLICY_FILE: '/data/policy.json'
|
|
||||||
MILTER_DKIM_ENABLED: 'some_value'
|
|
||||||
MILTER_TRUSTED_AUTHSERVID: 'my-auth-serv-id'
|
|
||||||
MILTER_X509_ENABLED: 'some_value'
|
|
||||||
MILTER_X509_TRUSTED_CN: 'mail.protection.outlook.com'
|
|
||||||
volumes:
|
|
||||||
- "./data/:/data/:ro"
|
|
||||||
- "./socket/:/socket/:rw"
|
|
||||||
```
|
|
||||||
If the milter should listen on a TCP-socket instead, just change the value of the `MILTER_SOCKET` ENV-variable to something like `inet:<port>@0.0.0.0`. As IPv6 is supported by the `libmilter` library too, a notation like `inet6:<port>@[::]` is also possible.
|
|
||||||
|
|
||||||
* Deploy
|
|
||||||
|
|
||||||
Execute `docker-compose up` and if nothing went wrong you shold see following output:
|
|
||||||
```
|
|
||||||
Creating network "exota-milter_default" with the default driver
|
|
||||||
Creating exota-milter_exota-milter_1 ... done
|
|
||||||
Attaching to exota-milter_exota-milter_1
|
|
||||||
exota-milter_1 | 2020-11-30 12:38:51,164: INFO ENV[MILTER_SOCKET]: /socket/exota-milter
|
|
||||||
exota-milter_1 | 2020-11-30 12:38:51,164: INFO ENV[MILTER_REJECT_MESSAGE]: Security policy violation!
|
|
||||||
exota-milter_1 | 2020-11-30 12:38:51,164: INFO ENV[MILTER_TMPFAIL_MESSAGE]: Service temporarily not available! Please try again later.
|
|
||||||
exota-milter_1 | 2020-11-30 12:38:51,164: INFO ENV[MILTER_TRUSTED_AUTHSERVID]: my-auth-serv-id
|
|
||||||
exota-milter_1 | 2020-11-30 12:38:51,165: INFO ENV[MILTER_DKIM_ENABLED]: True
|
|
||||||
exota-milter_1 | 2020-11-30 12:38:51,165: INFO ENV[MILTER_X509_TRUSTED_CN]: mail.protection.outlook.com
|
|
||||||
exota-milter_1 | 2020-11-30 12:38:51,165: INFO ENV[MILTER_X509_ENABLED]: True
|
|
||||||
exota-milter_1 | 2020-11-30 12:38:51,165: INFO ENV[MILTER_POLICY_SOURCE]: file
|
|
||||||
exota-milter_1 | 2020-11-30 12:38:51,165: INFO ENV[MILTER_POLICY_FILE]: /data/policy.json
|
|
||||||
exota-milter_1 | 2020-11-30 12:38:51,166: INFO JSON policy backend initialized
|
|
||||||
exota-milter_1 | 2020-11-30 12:38:51,166: INFO Startup exota-milter@socket: /socket/exota-milter
|
|
||||||
```
|
|
||||||
|
|
||||||
Voila! The milter socket can be accessed on the host filesystem (in my case) under `/docker/containers/exota-milter/socket/exota-milter`.
|
|
||||||
|
|
||||||
|
|
||||||
# How to test?
|
|
||||||
First of all please take a look at how to set up the testing environment, which is described [here](tests/README.md)
|
First of all please take a look at how to set up the testing environment, which is described [here](tests/README.md)
|
||||||
@ -14,33 +14,20 @@ from policy import (
|
|||||||
ExOTAPolicyBackendJSON, ExOTAPolicy
|
ExOTAPolicyBackendJSON, ExOTAPolicy
|
||||||
)
|
)
|
||||||
|
|
||||||
# Globals with defaults. Can/should be modified by ENV-variables on startup.
|
# Globals with mostly senseless defaults ;)
|
||||||
# ENV[MILTER_NAME]
|
|
||||||
g_milter_name = 'exota-milter'
|
g_milter_name = 'exota-milter'
|
||||||
# ENV[MILTER_SOCKET]
|
|
||||||
g_milter_socket = '/socket/' + g_milter_name
|
g_milter_socket = '/socket/' + g_milter_name
|
||||||
# ENV[MILTER_REJECT_MESSAGE]
|
|
||||||
g_milter_reject_message = 'Security policy violation!'
|
g_milter_reject_message = 'Security policy violation!'
|
||||||
# ENV[MILTER_TMPFAIL_MESSAGE]
|
|
||||||
g_milter_tmpfail_message = 'Service temporarily not available! Please try again later.'
|
g_milter_tmpfail_message = 'Service temporarily not available! Please try again later.'
|
||||||
# ENV[LOG_LEVEL]
|
|
||||||
g_loglevel = logging.INFO
|
|
||||||
# ENV[MILTER_DKIM_ENABLED]
|
|
||||||
g_milter_dkim_enabled = False
|
|
||||||
# ENV[MILTER_TRUSTED_AUTHSERVID]
|
|
||||||
g_milter_trusted_authservid = 'invalid'
|
|
||||||
# ENV[MILTER_POLICY_SOURCE]
|
|
||||||
g_milter_policy_source = 'file'
|
|
||||||
# ENV[MILTER_POLICY_FILE]
|
|
||||||
g_milter_policy_file = '/data/policy.json'
|
|
||||||
# ENV[MILTER_X509_ENABLED]
|
|
||||||
g_milter_x509_enabled = False
|
|
||||||
# ENV[MILTER_X509_TRUSTED_CN]
|
|
||||||
g_milter_x509_trusted_cn = 'mail.protection.outlook.com'
|
|
||||||
|
|
||||||
# Another globals
|
|
||||||
g_policy_backend = None
|
|
||||||
g_re_domain = re.compile(r'^.*@(\S+)$', re.IGNORECASE)
|
g_re_domain = re.compile(r'^.*@(\S+)$', re.IGNORECASE)
|
||||||
|
g_loglevel = logging.INFO
|
||||||
|
g_milter_dkim_enabled = False
|
||||||
|
g_milter_trusted_authservid = 'invalid'
|
||||||
|
g_milter_policy_source = 'file'
|
||||||
|
g_milter_policy_file = None
|
||||||
|
g_milter_policy_backend = None
|
||||||
|
g_milter_x509_enabled = False
|
||||||
|
g_milter_x509_trusted_cn = 'mail.protection.outlook.com'
|
||||||
|
|
||||||
class ExOTAMilter(Milter.Base):
|
class ExOTAMilter(Milter.Base):
|
||||||
# Each new connection is handled in an own thread
|
# Each new connection is handled in an own thread
|
||||||
@ -190,7 +177,7 @@ class ExOTAMilter(Milter.Base):
|
|||||||
# Get policy for 5322.from_domain
|
# Get policy for 5322.from_domain
|
||||||
policy = None
|
policy = None
|
||||||
try:
|
try:
|
||||||
policy = g_policy_backend.get(self.hdr_from_domain)
|
policy = g_milter_policy_backend.get(self.hdr_from_domain)
|
||||||
logging.debug(self.mconn_id + "/" + self.getsymval('i') +
|
logging.debug(self.mconn_id + "/" + self.getsymval('i') +
|
||||||
"/EOM Policy for 5322.from_domain={0} fetched from backend".format(self.hdr_from_domain)
|
"/EOM Policy for 5322.from_domain={0} fetched from backend".format(self.hdr_from_domain)
|
||||||
)
|
)
|
||||||
@ -305,7 +292,6 @@ if __name__ == "__main__":
|
|||||||
g_milter_name = os.environ['MILTER_NAME']
|
g_milter_name = os.environ['MILTER_NAME']
|
||||||
if 'MILTER_SOCKET' in os.environ:
|
if 'MILTER_SOCKET' in os.environ:
|
||||||
g_milter_socket = os.environ['MILTER_SOCKET']
|
g_milter_socket = os.environ['MILTER_SOCKET']
|
||||||
logging.info("ENV[MILTER_SOCKET]: {0}".format(g_milter_socket))
|
|
||||||
if 'MILTER_REJECT_MESSAGE' in os.environ:
|
if 'MILTER_REJECT_MESSAGE' in os.environ:
|
||||||
g_milter_reject_message = os.environ['MILTER_REJECT_MESSAGE']
|
g_milter_reject_message = os.environ['MILTER_REJECT_MESSAGE']
|
||||||
logging.info("ENV[MILTER_REJECT_MESSAGE]: {0}".format(g_milter_reject_message))
|
logging.info("ENV[MILTER_REJECT_MESSAGE]: {0}".format(g_milter_reject_message))
|
||||||
@ -335,7 +321,7 @@ if __name__ == "__main__":
|
|||||||
g_milter_policy_file = os.environ['MILTER_POLICY_FILE']
|
g_milter_policy_file = os.environ['MILTER_POLICY_FILE']
|
||||||
logging.info("ENV[MILTER_POLICY_FILE]: {0}".format(g_milter_policy_file))
|
logging.info("ENV[MILTER_POLICY_FILE]: {0}".format(g_milter_policy_file))
|
||||||
try:
|
try:
|
||||||
g_policy_backend = ExOTAPolicyBackendJSON(g_milter_policy_file)
|
g_milter_policy_backend = ExOTAPolicyBackendJSON(g_milter_policy_file)
|
||||||
logging.info("JSON policy backend initialized")
|
logging.info("JSON policy backend initialized")
|
||||||
except ExOTAPolicyException as e:
|
except ExOTAPolicyException as e:
|
||||||
logging.error("Policy backend error: {0}".format(e.message))
|
logging.error("Policy backend error: {0}".format(e.message))
|
||||||
|
|||||||
@ -1,2 +1,3 @@
|
|||||||
authres==1.2.0
|
authres==1.2.0
|
||||||
|
pkg-resources==0.0.0
|
||||||
pymilter==1.0.4
|
pymilter==1.0.4
|
||||||
|
|||||||
@ -6,7 +6,7 @@ First of all, please configure a python virtual environment and install all nece
|
|||||||
|
|
||||||
It´s not realy neccessary to configure a fully functional milter-aware MTA to see **ExOTA-Milter** in action. All you need is
|
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 binary called `miltertest`. Under debian based distros it´s located in the `opendkim-tools` package.
|
||||||
* a lua-script for miltertest: `tests/miltertest.lua`
|
* a lua-script for miltertest: `tests/miltertest.lue`
|
||||||
* an **ExOTA-Milter** policy JSON-file: `tests/policy.json`
|
* 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.
|
Except for the `miltertest` binary you´ll find all mandatory resources to run a test in this repo.
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user