Add DMARC
This commit is contained in:
parent
a270987f70
commit
753e82d490
|
@ -9,7 +9,7 @@
|
||||||
|
|
||||||
{% block domain_detail_content %}
|
{% block domain_detail_content %}
|
||||||
<div class="bg-white p-4" style="max-width: 60rem; margin: auto">
|
<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="">Please follow the steps below to set up your domain.</div>
|
||||||
|
|
||||||
<div class="small-text mb-5">
|
<div class="small-text mb-5">
|
||||||
|
@ -167,7 +167,7 @@
|
||||||
Domain: <em data-toggle="tooltip"
|
Domain: <em data-toggle="tooltip"
|
||||||
title="Click to copy"
|
title="Click to copy"
|
||||||
class="clipboard"
|
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:
|
Value:
|
||||||
<em data-toggle="tooltip"
|
<em data-toggle="tooltip"
|
||||||
title="Click to copy"
|
title="Click to copy"
|
||||||
|
@ -211,5 +211,73 @@
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</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>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -1,12 +1,11 @@
|
||||||
from flask import render_template, request, redirect, url_for, flash
|
from flask import render_template, request, redirect, url_for, flash
|
||||||
from flask_login import login_required, current_user
|
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.dashboard.base import dashboard_bp
|
||||||
from app.dns_utils import (
|
from app.dns_utils import (
|
||||||
get_mx_domains,
|
get_mx_domains,
|
||||||
get_spf_domain,
|
get_spf_domain,
|
||||||
get_dkim_record,
|
|
||||||
get_txt_record,
|
get_txt_record,
|
||||||
get_cname_record,
|
get_cname_record,
|
||||||
)
|
)
|
||||||
|
@ -27,8 +26,10 @@ def domain_detail_dns(custom_domain_id):
|
||||||
# hardcode the DKIM selector here
|
# hardcode the DKIM selector here
|
||||||
dkim_cname = f"dkim._domainkey.{EMAIL_DOMAIN}"
|
dkim_cname = f"dkim._domainkey.{EMAIL_DOMAIN}"
|
||||||
|
|
||||||
mx_ok = spf_ok = dkim_ok = True
|
dmarc_record = "v=DMARC1; p=quarantine; pct=100; adkim=s; aspf=s"
|
||||||
mx_errors = spf_errors = dkim_errors = []
|
|
||||||
|
mx_ok = spf_ok = dkim_ok = dmarc_ok = True
|
||||||
|
mx_errors = spf_errors = dkim_errors = dmarc_errors = []
|
||||||
|
|
||||||
if request.method == "POST":
|
if request.method == "POST":
|
||||||
if request.form.get("form-name") == "check-mx":
|
if request.form.get("form-name") == "check-mx":
|
||||||
|
@ -43,7 +44,7 @@ def domain_detail_dns(custom_domain_id):
|
||||||
]
|
]
|
||||||
else:
|
else:
|
||||||
flash(
|
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",
|
"success",
|
||||||
)
|
)
|
||||||
custom_domain.verified = True
|
custom_domain.verified = True
|
||||||
|
@ -58,7 +59,7 @@ def domain_detail_dns(custom_domain_id):
|
||||||
if EMAIL_DOMAIN in spf_domains:
|
if EMAIL_DOMAIN in spf_domains:
|
||||||
custom_domain.spf_verified = True
|
custom_domain.spf_verified = True
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
flash("The SPF is setup correctly", "success")
|
flash("SPF is setup correctly", "success")
|
||||||
return redirect(
|
return redirect(
|
||||||
url_for(
|
url_for(
|
||||||
"dashboard.domain_detail_dns", custom_domain_id=custom_domain.id
|
"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":
|
elif request.form.get("form-name") == "check-dkim":
|
||||||
dkim_record = get_cname_record(custom_domain.domain)
|
dkim_record = get_cname_record(custom_domain.domain)
|
||||||
if dkim_record == dkim_cname:
|
if dkim_record == dkim_cname:
|
||||||
flash("The DKIM is setup correctly.", "success")
|
flash("DKIM is setup correctly.", "success")
|
||||||
custom_domain.dkim_verified = True
|
custom_domain.dkim_verified = True
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
|
@ -89,6 +90,24 @@ def domain_detail_dns(custom_domain_id):
|
||||||
dkim_ok = False
|
dkim_ok = False
|
||||||
dkim_errors = [dkim_record or "[Empty]"]
|
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(
|
return render_template(
|
||||||
"dashboard/domain_detail/dns.html",
|
"dashboard/domain_detail/dns.html",
|
||||||
EMAIL_SERVERS_WITH_PRIORITY=EMAIL_SERVERS_WITH_PRIORITY,
|
EMAIL_SERVERS_WITH_PRIORITY=EMAIL_SERVERS_WITH_PRIORITY,
|
||||||
|
|
|
@ -71,6 +71,7 @@ def get_spf_domain(hostname) -> [str]:
|
||||||
|
|
||||||
|
|
||||||
def get_txt_record(hostname) -> [str]:
|
def get_txt_record(hostname) -> [str]:
|
||||||
|
"""return all domains listed in *include:*"""
|
||||||
try:
|
try:
|
||||||
answers = _get_dns_resolver().query(hostname, "TXT")
|
answers = _get_dns_resolver().query(hostname, "TXT")
|
||||||
except Exception:
|
except Exception:
|
||||||
|
@ -78,24 +79,10 @@ def get_txt_record(hostname) -> [str]:
|
||||||
|
|
||||||
ret = []
|
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 a in answers: # type: dns.rdtypes.ANY.TXT.TXT
|
||||||
for record in a.strings:
|
for record in a.strings:
|
||||||
record = record.decode() # record is bytes
|
record = record.decode() # record is bytes
|
||||||
|
|
||||||
ret.append(record)
|
ret.append(record)
|
||||||
|
|
||||||
return "".join(ret)
|
return ret
|
||||||
|
|
|
@ -23,8 +23,3 @@ def test_get_txt_record():
|
||||||
|
|
||||||
r = get_txt_record(_DOMAIN)
|
r = get_txt_record(_DOMAIN)
|
||||||
assert len(r) > 0
|
assert len(r) > 0
|
||||||
|
|
||||||
|
|
||||||
def test_get_dkim_record():
|
|
||||||
r = get_dkim_record(_DOMAIN)
|
|
||||||
assert r.startswith("v=DKIM1; k=rsa;")
|
|
||||||
|
|
Loading…
Reference in a new issue