Handle DKIM key generation and storage
This commit is contained in:
@@ -20,7 +20,9 @@ default_config = {
|
|||||||
'HOSTNAME': 'mail.freeposte.io',
|
'HOSTNAME': 'mail.freeposte.io',
|
||||||
'DOMAIN': 'freeposte.io',
|
'DOMAIN': 'freeposte.io',
|
||||||
'POSTMASTER': 'postmaster',
|
'POSTMASTER': 'postmaster',
|
||||||
'DEBUG': False
|
'DEBUG': False,
|
||||||
|
'DKIM_PATH': '/dkim/{domain}.{selector}.key',
|
||||||
|
'DKIM_SELECTOR': 'dkim'
|
||||||
}
|
}
|
||||||
|
|
||||||
# Load configuration from the environment if available
|
# Load configuration from the environment if available
|
||||||
|
|||||||
21
admin/freeposte/admin/dkim.py
Normal file
21
admin/freeposte/admin/dkim.py
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
""" No crypto operation is done on keys.
|
||||||
|
They are thus represented as ASCII armored PEM.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from OpenSSL import crypto
|
||||||
|
|
||||||
|
|
||||||
|
def gen_key(key_type=crypto.TYPE_RSA, bits=1024):
|
||||||
|
""" Generate and return a new RSA key.
|
||||||
|
"""
|
||||||
|
key = crypto.PKey()
|
||||||
|
key.generate_key(key_type, bits)
|
||||||
|
return crypto.dump_privatekey(crypto.FILETYPE_PEM, key)
|
||||||
|
|
||||||
|
|
||||||
|
def strip_key(pem):
|
||||||
|
""" Return only the b64 part of the ASCII armored PEM.
|
||||||
|
"""
|
||||||
|
key = crypto.load_privatekey(crypto.FILETYPE_PEM, pem)
|
||||||
|
public_pem = crypto.dump_publickey(crypto.FILETYPE_PEM, key)
|
||||||
|
return public_pem.replace(b"\n", b"").split(b"-----")[2]
|
||||||
@@ -1,10 +1,14 @@
|
|||||||
from freeposte.admin import db
|
from freeposte.admin import db, dkim
|
||||||
|
from freeposte import app
|
||||||
|
|
||||||
from sqlalchemy.ext import declarative
|
from sqlalchemy.ext import declarative
|
||||||
from passlib import context
|
from passlib import context
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
import re
|
import re
|
||||||
|
import time
|
||||||
|
import os
|
||||||
|
import glob
|
||||||
|
|
||||||
|
|
||||||
# Many-to-many association table for domain managers
|
# Many-to-many association table for domain managers
|
||||||
@@ -34,6 +38,28 @@ class Domain(Base):
|
|||||||
max_users = db.Column(db.Integer, nullable=False, default=0)
|
max_users = db.Column(db.Integer, nullable=False, default=0)
|
||||||
max_aliases = db.Column(db.Integer, nullable=False, default=0)
|
max_aliases = db.Column(db.Integer, nullable=False, default=0)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def dkim_key(self):
|
||||||
|
file_path = app.config["DKIM_PATH"].format(
|
||||||
|
domain=self.name, selector=app.config["DKIM_SELECTOR"])
|
||||||
|
if os.path.exists(file_path):
|
||||||
|
with open(file_path, "rb") as handle:
|
||||||
|
return handle.read()
|
||||||
|
|
||||||
|
@dkim_key.setter
|
||||||
|
def dkim_key(self, value):
|
||||||
|
file_path = app.config["DKIM_PATH"].format(
|
||||||
|
domain=self.name, selector=app.config["DKIM_SELECTOR"])
|
||||||
|
with open(file_path, "wb") as handle:
|
||||||
|
handle.write(value)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def dkim_publickey(self):
|
||||||
|
return dkim.strip_key(self.dkim_key).decode("utf8")
|
||||||
|
|
||||||
|
def generate_dkim_key(self):
|
||||||
|
self.dkim_key = dkim.gen_key()
|
||||||
|
|
||||||
def has_email(self, localpart):
|
def has_email(self, localpart):
|
||||||
for email in self.users + self.aliases:
|
for email in self.users + self.aliases:
|
||||||
if email.localpart == localpart:
|
if email.localpart == localpart:
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ Domain details
|
|||||||
|
|
||||||
{% block main_action %}
|
{% block main_action %}
|
||||||
{% if current_user.global_admin %}
|
{% if current_user.global_admin %}
|
||||||
<a class="btn btn-primary" href="#">Regenerate keys</a>
|
<a class="btn btn-primary" href="{{ url_for(".domain_genkeys", domain_name=domain.name) }}">Regenerate keys</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
@@ -26,8 +26,18 @@ Domain details
|
|||||||
<td><pre>{{ domain.name }}. 600 IN MX 10 {{ config["HOSTNAME"] }}.</pre></td>
|
<td><pre>{{ domain.name }}. 600 IN MX 10 {{ config["HOSTNAME"] }}.</pre></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>DNS SPF entry</th>
|
<th>DNS SPF entries</th>
|
||||||
<td><pre>{{ domain.name }}. 600 IN TXT "v=spf1 mx a:{{ config["HOSTNAME"] }} -all"</pre></td>
|
<td><pre>
|
||||||
|
{{ domain.name }}. 600 IN TXT "v=spf1 mx a:{{ config["HOSTNAME"] }} -all"
|
||||||
|
{{ domain.name }}. 600 IN SPF "v=spf1 mx a:{{ config["HOSTNAME"] }} -all"</pre></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>DKIM public key</th>
|
||||||
|
<td><pre style="white-space: pre-wrap; word-wrap: break-word;">{{ domain.dkim_publickey }}</pre></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>DNS DKIM entry</th>
|
||||||
|
<td><pre style="white-space: pre-wrap; word-wrap: break-word;">{{ config["DKIM_SELECTOR"] }}._domainkey IN 600 TXT "v=DKIM1; k=rsa; p={{ domain.dkim_publickey }}"</pre></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>DNS DMARC entry</th>
|
<th>DNS DMARC entry</th>
|
||||||
|
|||||||
@@ -63,3 +63,11 @@ def domain_details(domain_name):
|
|||||||
domain = utils.get_domain_admin(domain_name)
|
domain = utils.get_domain_admin(domain_name)
|
||||||
return flask.render_template('domain/details.html', domain=domain,
|
return flask.render_template('domain/details.html', domain=domain,
|
||||||
config=flask_app.config)
|
config=flask_app.config)
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/domain/genkeys/<domain_name>', methods=['GET'])
|
||||||
|
def domain_genkeys(domain_name):
|
||||||
|
domain = utils.get_domain_admin(domain_name)
|
||||||
|
domain.generate_dkim_key()
|
||||||
|
return flask.redirect(
|
||||||
|
flask.url_for(".domain_details", domain_name=domain_name))
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ Flask-migrate
|
|||||||
Flask-script
|
Flask-script
|
||||||
flask_wtf
|
flask_wtf
|
||||||
WTForms-Components
|
WTForms-Components
|
||||||
|
PyOpenSSL
|
||||||
passlib
|
passlib
|
||||||
gunicorn
|
gunicorn
|
||||||
docker-py
|
docker-py
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ services:
|
|||||||
env_file: freeposte.env
|
env_file: freeposte.env
|
||||||
volumes:
|
volumes:
|
||||||
- /freeposte/filter:/data
|
- /freeposte/filter:/data
|
||||||
|
- /freeposte/dkim:/dkim
|
||||||
|
|
||||||
antispam:
|
antispam:
|
||||||
build: rspamd
|
build: rspamd
|
||||||
@@ -79,6 +80,7 @@ services:
|
|||||||
env_file: freeposte.env
|
env_file: freeposte.env
|
||||||
volumes:
|
volumes:
|
||||||
- /freeposte/freeposte:/data
|
- /freeposte/freeposte:/data
|
||||||
|
- /freeposte/dkim:/dkim
|
||||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||||
|
|
||||||
webmail:
|
webmail:
|
||||||
|
|||||||
Reference in New Issue
Block a user