webadmin: allow accessing tls reports for mail host policy domain (tlsa)

instead of requiring policy domains to be configured recipient domains.
when accessing TLS reports, always do it under path #tlsrpt/reports, not under #domain/.../tlsrpt.
This commit is contained in:
Mechiel Lukkien 2023-11-12 14:58:46 +01:00
parent 6e6f716e91
commit 2265769b8e
No known key found for this signature in database
3 changed files with 48 additions and 18 deletions

View file

@ -1463,6 +1463,13 @@ func (Admin) Domain(ctx context.Context, domain string) dns.Domain {
return d
}
// ParseDomain parses a domain, possibly an IDNA domain.
func (Admin) ParseDomain(ctx context.Context, domain string) dns.Domain {
d, err := dns.ParseDomain(domain)
xcheckuserf(ctx, err, "parse domain")
return d
}
// DomainLocalparts returns the encoded localparts and accounts configured in domain.
func (Admin) DomainLocalparts(ctx context.Context, domain string) (localpartAccounts map[string]string) {
d, err := dns.ParseDomain(domain)
@ -1559,7 +1566,7 @@ type TLSRPTSummary struct {
PolicyDomain dns.Domain
Success int64
Failure int64
ResultTypeCounts map[tlsrpt.ResultType]int
ResultTypeCounts map[tlsrpt.ResultType]int64
}
// TLSRPTSummaries returns a summary of received TLS reports overlapping with
@ -1587,9 +1594,9 @@ func (Admin) TLSRPTSummaries(ctx context.Context, start, end time.Time, policyDo
sum.Failure += result.Summary.TotalFailureSessionCount
for _, details := range result.FailureDetails {
if sum.ResultTypeCounts == nil {
sum.ResultTypeCounts = map[tlsrpt.ResultType]int{}
sum.ResultTypeCounts = map[tlsrpt.ResultType]int64{}
}
sum.ResultTypeCounts[details.ResultType]++
sum.ResultTypeCounts[details.ResultType] += details.FailedSessionCount
}
}
summaries[dom] = sum

View file

@ -1465,7 +1465,7 @@ const tlsrptIndex = async () => {
dom._kids(page,
crumbs(
crumblink('Mox Admin', '#'),
'TLSRPT reports and connectivity results',
'TLSRPT',
),
dom.ul(
dom.li(
@ -1620,7 +1620,8 @@ const tlsrptReports = async () => {
dom._kids(page,
crumbs(
crumblink('Mox Admin', '#'),
'TLS reports (TLSRPT)',
crumblink('TLSRPT', '#tlsrpt'),
'Reports'
),
dom.p('TLSRPT (TLS reporting) is a mechanism to request feedback from other mail servers about TLS connections to your mail server. If is typically used along with MTA-STS and/or DANE to enforce that SMTP connections are protected with TLS. Mail servers implementing TLSRPT will typically send a daily report with both successful and failed connection counts, including details about failures.'),
renderTLSRPTSummaries(summaries)
@ -1643,7 +1644,7 @@ const renderTLSRPTSummaries = (summaries) => {
dom.tbody(
summaries.map(r =>
dom.tr(
dom.td(dom.a(attr({href: '#domains/' + domainName(r.PolicyDomain) + '/tlsrpt', title: 'See report details.'}), domainName(r.PolicyDomain))),
dom.td(dom.a(attr({href: '#tlsrpt/reports/' + domainName(r.PolicyDomain), title: 'See report details.'}), domainName(r.PolicyDomain))),
dom.td(style({textAlign: 'right'}), '' + r.Success),
dom.td(style({textAlign: 'right'}), '' + r.Failure),
dom.td(!r.ResultTypeCounts ? [] : Object.entries(r.ResultTypeCounts).map(kv => kv[0] + ': ' + kv[1]).join('; ')),
@ -1659,7 +1660,7 @@ const domainTLSRPT = async (d) => {
const start = new Date(new Date().getTime() - 30*24*3600*1000).toISOString()
const [records, dnsdomain] = await Promise.all([
api.TLSReports(start, end, d),
api.Domain(d),
api.ParseDomain(d),
])
const policyType = (policy) => {
@ -1677,8 +1678,9 @@ const domainTLSRPT = async (d) => {
dom._kids(page,
crumbs(
crumblink('Mox Admin', '#'),
crumblink('Domain ' + domainString(dnsdomain), '#domains/'+d),
'TLSRPT',
crumblink('TLSRPT', '#tlsrpt'),
crumblink('Reports', '#tlsrpt/reports'),
'Domain '+domainString(dnsdomain),
),
dom.p('TLSRPT (TLS reporting) is a mechanism to request feedback from other mail servers about TLS connections to your mail server. If is typically used along with MTA-STS and/or DANE to enforce that SMTP connections are protected with TLS. Mail servers implementing TLSRPT will typically send a daily report with both successful and failed connection counts, including details about failures.'),
dom.p('Below the TLS reports for the past 30 days.'),
@ -1724,7 +1726,7 @@ const domainTLSRPT = async (d) => {
const addRow = (d, di) => {
const row = dom.tr(
index > 0 || rows.length > 0 ? [] : [
dom.td(reportRowSpan, valignTop, dom.a(''+record.ID, attr({href: '#domains/' + record.Domain + '/tlsrpt/'+record.ID}))),
dom.td(reportRowSpan, valignTop, dom.a(''+record.ID, attr({href: '#tlsrpt/reports/' + record.Domain + '/' + record.ID}))),
dom.td(reportRowSpan, valignTop, r['organization-name'] || r['contact-info'] || record.MailFrom || '', attr({title: 'Organization: ' +r['organization-name'] + '; \nContact info: ' + r['contact-info'] + '; \nReport ID: ' + r['report-id'] + '; \nMail from: ' + record.MailFrom, })),
dom.td(reportRowSpan, valignTop, period(new Date(r['date-range']['start-datetime']), new Date(r['date-range']['end-datetime']))),
],
@ -1766,15 +1768,16 @@ const domainTLSRPT = async (d) => {
const domainTLSRPTID = async (d, reportID) => {
const [report, dnsdomain] = await Promise.all([
api.TLSReportID(d, reportID),
api.Domain(d),
api.ParseDomain(d),
])
const page = document.getElementById('page')
dom._kids(page,
crumbs(
crumblink('Mox Admin', '#'),
crumblink('Domain ' + domainString(dnsdomain), '#domains/'+d),
crumblink('TLS report', '#domains/' + d + '/tlsrpt'),
crumblink('TLSRPT', '#tlsrpt'),
crumblink('Reports', '#tlsrpt/reports'),
crumblink('Domain '+domainString(dnsdomain), '#tlsrpt/reports/' + d + ''),
'Report ' + reportID
),
dom.p('Below is the raw report as received from the remote mail server.'),
@ -2588,10 +2591,6 @@ const init = async () => {
await domainDMARC(t[1])
} else if (t[0] === 'domains' && t.length === 4 && t[2] === 'dmarc' && parseInt(t[3])) {
await domainDMARCReport(t[1], parseInt(t[3]))
} else if (t[0] === 'domains' && t.length === 3 && t[2] === 'tlsrpt') {
await domainTLSRPT(t[1])
} else if (t[0] === 'domains' && t.length === 4 && t[2] === 'tlsrpt' && parseInt(t[3])) {
await domainTLSRPTID(t[1], parseInt(t[3]))
} else if (t[0] === 'domains' && t.length === 3 && t[2] === 'dnscheck') {
await domainDNSCheck(t[1])
} else if (t[0] === 'domains' && t.length === 3 && t[2] === 'dnsrecords') {
@ -2602,6 +2601,10 @@ const init = async () => {
await tlsrptIndex()
} else if (h === 'tlsrpt/reports') {
await tlsrptReports()
} else if (t[0] === 'tlsrpt' && t[1] === 'reports' && t.length === 3) {
await domainTLSRPT(t[2])
} else if (t[0] === 'tlsrpt' && t[1] === 'reports' && t.length === 4 && parseInt(t[3])) {
await domainTLSRPTID(t[2], parseInt(t[3]))
} else if (h === 'tlsrpt/results') {
await tlsrptResults()
} else if (t[0] == 'tlsrpt' && t[1] == 'results' && t.length === 3) {

View file

@ -56,6 +56,26 @@
}
]
},
{
"Name": "ParseDomain",
"Docs": "ParseDomain parses a domain, possibly an IDNA domain.",
"Params": [
{
"Name": "domain",
"Typewords": [
"string"
]
}
],
"Returns": [
{
"Name": "r0",
"Typewords": [
"Domain"
]
}
]
},
{
"Name": "DomainLocalparts",
"Docs": "DomainLocalparts returns the encoded localparts and accounts configured in domain.",
@ -2545,7 +2565,7 @@
"Docs": "",
"Typewords": [
"{}",
"int32"
"int64"
]
}
]