Add DMARC

This commit is contained in:
Son NK 2020-05-03 12:01:31 +02:00
parent a270987f70
commit 753e82d490
4 changed files with 98 additions and 29 deletions

View File

@ -9,7 +9,7 @@
{% block domain_detail_content %}
<div class="bg-white p-4" style="max-width: 60rem; margin: auto">
<h1 class="h3"> {{ custom_domain.domain }} </h1>
<h1 class="h2"> {{ custom_domain.domain }} </h1>
<div class="">Please follow the steps below to set up your domain.</div>
<div class="small-text mb-5">
@ -167,7 +167,7 @@
Domain: <em data-toggle="tooltip"
title="Click to copy"
class="clipboard"
data-clipboard-text="dkim._domainkey.">dkim._domainkey.</em>{{ custom_domain.domain }} <br>
data-clipboard-text="dkim._domainkey">dkim._domainkey</em>.{{ custom_domain.domain }} <br>
Value:
<em data-toggle="tooltip"
title="Click to copy"
@ -211,5 +211,73 @@
{% endif %}
</div>
<hr>
<div id="dmarc-form">
<div class="font-weight-bold">4. DMARC (Optional)
{% if custom_domain.dmarc_verified %}
<span class="cursor" data-toggle="tooltip" data-original-title="DMARC Verified"></span>
{% else %}
<span class="cursor" data-toggle="tooltip" data-original-title="DMARC Not Verified">🚫 </span>
{% endif %}
</div>
<div>
DMARC <a href="https://en.wikipedia.org/wiki/DMARC" target="_blank">(Wikipedia↗)</a>
is designed to protect the domain from unauthorized use, commonly known as email spoofing. <br>
Built around SPF and DKIM, a DMARC policy tells the receiving mail server what to do if
neither of those authentication methods passes.
</div>
<div class="mb-2">Add the following TXT DNS record to your domain.</div>
<div class="mb-2 p-3" style="background-color: #eee">
Record: TXT <br>
Domain: <em data-toggle="tooltip"
title="Click to copy"
class="clipboard"
data-clipboard-text="_dmarc">_dmarc</em>.{{ custom_domain.domain }} <br>
Value:
<em data-toggle="tooltip"
title="Click to copy"
class="clipboard"
data-clipboard-text="{{ dmarc_record }}">
{{ dmarc_record }}
</em>
</div>
<form method="post" action="#dmarc-form">
<input type="hidden" name="form-name" value="check-dmarc">
{% if custom_domain.dmarc_verified %}
<button type="submit" class="btn btn-outline-primary">
Re-verify
</button>
{% else %}
<button type="submit" class="btn btn-primary">
Verify
</button>
{% endif %}
</form>
{% if not dmarc_ok %}
<div class="text-danger mt-4">
Your DNS is not correctly set.
The TXT record we obtain is:
<div class="mb-3 p-3" style="background-color: #eee">
{% if not dmarc_errors %}
(Empty)
{% endif %}
{% for r in dmarc_errors %}
{{ r }} <br>
{% endfor %}
</div>
{% if custom_domain.dmarc_verified %}
Without DMARC setup, emails sent from your alias might end up in the Spam/Junk folder.
{% endif %}
</div>
{% endif %}
</div>
</div>
{% endblock %}

View File

@ -1,12 +1,11 @@
from flask import render_template, request, redirect, url_for, flash
from flask_login import login_required, current_user
from app.config import EMAIL_SERVERS_WITH_PRIORITY, DKIM_DNS_VALUE, EMAIL_DOMAIN
from app.config import EMAIL_SERVERS_WITH_PRIORITY, EMAIL_DOMAIN
from app.dashboard.base import dashboard_bp
from app.dns_utils import (
get_mx_domains,
get_spf_domain,
get_dkim_record,
get_txt_record,
get_cname_record,
)
@ -27,8 +26,10 @@ def domain_detail_dns(custom_domain_id):
# hardcode the DKIM selector here
dkim_cname = f"dkim._domainkey.{EMAIL_DOMAIN}"
mx_ok = spf_ok = dkim_ok = True
mx_errors = spf_errors = dkim_errors = []
dmarc_record = "v=DMARC1; p=quarantine; pct=100; adkim=s; aspf=s"
mx_ok = spf_ok = dkim_ok = dmarc_ok = True
mx_errors = spf_errors = dkim_errors = dmarc_errors = []
if request.method == "POST":
if request.form.get("form-name") == "check-mx":
@ -43,7 +44,7 @@ def domain_detail_dns(custom_domain_id):
]
else:
flash(
"Your domain is verified. Now it can be used to create custom alias",
"Your domain can start receiving emails. You can now use it to create alias",
"success",
)
custom_domain.verified = True
@ -58,7 +59,7 @@ def domain_detail_dns(custom_domain_id):
if EMAIL_DOMAIN in spf_domains:
custom_domain.spf_verified = True
db.session.commit()
flash("The SPF is setup correctly", "success")
flash("SPF is setup correctly", "success")
return redirect(
url_for(
"dashboard.domain_detail_dns", custom_domain_id=custom_domain.id
@ -75,7 +76,7 @@ def domain_detail_dns(custom_domain_id):
elif request.form.get("form-name") == "check-dkim":
dkim_record = get_cname_record(custom_domain.domain)
if dkim_record == dkim_cname:
flash("The DKIM is setup correctly.", "success")
flash("DKIM is setup correctly.", "success")
custom_domain.dkim_verified = True
db.session.commit()
@ -89,6 +90,24 @@ def domain_detail_dns(custom_domain_id):
dkim_ok = False
dkim_errors = [dkim_record or "[Empty]"]
elif request.form.get("form-name") == "check-dmarc":
txt_records = get_txt_record("_dmarc." + custom_domain.domain)
if dmarc_record in txt_records:
custom_domain.dmarc_verified = True
db.session.commit()
flash("DMARC is setup correctly", "success")
return redirect(
url_for(
"dashboard.domain_detail_dns", custom_domain_id=custom_domain.id
)
)
else:
flash(
f"DMARC: The TXT record is not correctly set", "warning",
)
dmarc_ok = False
dmarc_errors = txt_records
return render_template(
"dashboard/domain_detail/dns.html",
EMAIL_SERVERS_WITH_PRIORITY=EMAIL_SERVERS_WITH_PRIORITY,

View File

@ -71,6 +71,7 @@ def get_spf_domain(hostname) -> [str]:
def get_txt_record(hostname) -> [str]:
"""return all domains listed in *include:*"""
try:
answers = _get_dns_resolver().query(hostname, "TXT")
except Exception:
@ -78,24 +79,10 @@ def get_txt_record(hostname) -> [str]:
ret = []
for a in answers: # type: dns.rdtypes.ANY.TXT.TXT
ret.append(a)
return ret
def get_dkim_record(hostname) -> str:
"""query the dkim._domainkey.{hostname} record and returns its value"""
try:
answers = _get_dns_resolver().query(f"dkim._domainkey.{hostname}", "TXT")
except Exception:
return ""
ret = []
for a in answers: # type: dns.rdtypes.ANY.TXT.TXT
for record in a.strings:
record = record.decode() # record is bytes
ret.append(record)
return "".join(ret)
return ret

View File

@ -23,8 +23,3 @@ def test_get_txt_record():
r = get_txt_record(_DOMAIN)
assert len(r) > 0
def test_get_dkim_record():
r = get_dkim_record(_DOMAIN)
assert r.startswith("v=DKIM1; k=rsa;")