diff --git a/OCI/Dockerfile b/OCI/Dockerfile new file mode 100644 index 0000000..ea8acc0 --- /dev/null +++ b/OCI/Dockerfile @@ -0,0 +1,25 @@ +FROM alpine +LABEL maintainer="Dominik Chilla " +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" ] \ No newline at end of file diff --git a/OCI/cmd b/OCI/cmd new file mode 100644 index 0000000..d364851 --- /dev/null +++ b/OCI/cmd @@ -0,0 +1,4 @@ +#!/bin/sh + +umask 0000 +exec python3 /app/exota-milter.py \ No newline at end of file diff --git a/README.md b/README.md index 1a585c8..e407919 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,9 @@ 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 + + # 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). @@ -85,9 +88,90 @@ Finally it´s the combination of all of the above discussed aspects which may re * restriction of client IPs via ACL (MTA) * verification of Microsoft´s x509 client certificate (MTA) * matching for client certificate´s CN (ExOTA-Milter) -* verification of DKIM signatures providing *Authentication-Results* header (another milter) +* 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) -# How to start? +# How about a docker/OCI image? +## 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/exomilter-socket` (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:@0.0.0.0`. As IPv6 is supported by the `libmilter` library too, a notation like `inet6:@[::]` 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) \ No newline at end of file diff --git a/app/exota-milter.py b/app/exota-milter.py index 6c18d06..ff1c1e4 100644 --- a/app/exota-milter.py +++ b/app/exota-milter.py @@ -14,21 +14,34 @@ from policy import ( ExOTAPolicyBackendJSON, ExOTAPolicy ) -# Globals with mostly senseless defaults ;) +# Globals with defaults. Can/should be modified by ENV-variables on startup. +# ENV[MILTER_NAME] g_milter_name = 'exota-milter' +# ENV[MILTER_SOCKET] g_milter_socket = '/socket/' + g_milter_name +# ENV[MILTER_REJECT_MESSAGE] g_milter_reject_message = 'Security policy violation!' +# ENV[MILTER_TMPFAIL_MESSAGE] g_milter_tmpfail_message = 'Service temporarily not available! Please try again later.' -g_re_domain = re.compile(r'^.*@(\S+)$', re.IGNORECASE) +# 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' -g_milter_policy_file = None -g_milter_policy_backend = None +# 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) + class ExOTAMilter(Milter.Base): # Each new connection is handled in an own thread def __init__(self): @@ -177,7 +190,7 @@ class ExOTAMilter(Milter.Base): # Get policy for 5322.from_domain policy = None try: - policy = g_milter_policy_backend.get(self.hdr_from_domain) + policy = g_policy_backend.get(self.hdr_from_domain) logging.debug(self.mconn_id + "/" + self.getsymval('i') + "/EOM Policy for 5322.from_domain={0} fetched from backend".format(self.hdr_from_domain) ) @@ -292,6 +305,7 @@ if __name__ == "__main__": g_milter_name = os.environ['MILTER_NAME'] if 'MILTER_SOCKET' in os.environ: g_milter_socket = os.environ['MILTER_SOCKET'] + logging.info("ENV[MILTER_SOCKET]: {0}".format(g_milter_socket)) if 'MILTER_REJECT_MESSAGE' in os.environ: g_milter_reject_message = os.environ['MILTER_REJECT_MESSAGE'] logging.info("ENV[MILTER_REJECT_MESSAGE]: {0}".format(g_milter_reject_message)) @@ -321,7 +335,7 @@ if __name__ == "__main__": g_milter_policy_file = os.environ['MILTER_POLICY_FILE'] logging.info("ENV[MILTER_POLICY_FILE]: {0}".format(g_milter_policy_file)) try: - g_milter_policy_backend = ExOTAPolicyBackendJSON(g_milter_policy_file) + g_policy_backend = ExOTAPolicyBackendJSON(g_milter_policy_file) logging.info("JSON policy backend initialized") except ExOTAPolicyException as e: logging.error("Policy backend error: {0}".format(e.message)) diff --git a/requirements.txt b/requirements.txt index 6d7adca..62ce3d1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,2 @@ authres==1.2.0 -pkg-resources==0.0.0 pymilter==1.0.4 diff --git a/tests/README.md b/tests/README.md index 3f87602..09e6700 100644 --- a/tests/README.md +++ b/tests/README.md @@ -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 * a binary called `miltertest`. Under debian based distros it´s located in the `opendkim-tools` package. -* a lua-script for miltertest: `tests/miltertest.lue` +* a lua-script for miltertest: `tests/miltertest.lua` * 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.