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 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. // DomainLocalparts returns the encoded localparts and accounts configured in domain.
func (Admin) DomainLocalparts(ctx context.Context, domain string) (localpartAccounts map[string]string) { func (Admin) DomainLocalparts(ctx context.Context, domain string) (localpartAccounts map[string]string) {
d, err := dns.ParseDomain(domain) d, err := dns.ParseDomain(domain)
@ -1559,7 +1566,7 @@ type TLSRPTSummary struct {
PolicyDomain dns.Domain PolicyDomain dns.Domain
Success int64 Success int64
Failure int64 Failure int64
ResultTypeCounts map[tlsrpt.ResultType]int ResultTypeCounts map[tlsrpt.ResultType]int64
} }
// TLSRPTSummaries returns a summary of received TLS reports overlapping with // 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 sum.Failure += result.Summary.TotalFailureSessionCount
for _, details := range result.FailureDetails { for _, details := range result.FailureDetails {
if sum.ResultTypeCounts == nil { 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 summaries[dom] = sum

View File

@ -1465,7 +1465,7 @@ const tlsrptIndex = async () => {
dom._kids(page, dom._kids(page,
crumbs( crumbs(
crumblink('Mox Admin', '#'), crumblink('Mox Admin', '#'),
'TLSRPT reports and connectivity results', 'TLSRPT',
), ),
dom.ul( dom.ul(
dom.li( dom.li(
@ -1620,7 +1620,8 @@ const tlsrptReports = async () => {
dom._kids(page, dom._kids(page,
crumbs( crumbs(
crumblink('Mox Admin', '#'), 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.'), 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) renderTLSRPTSummaries(summaries)
@ -1643,7 +1644,7 @@ const renderTLSRPTSummaries = (summaries) => {
dom.tbody( dom.tbody(
summaries.map(r => summaries.map(r =>
dom.tr( 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.Success),
dom.td(style({textAlign: 'right'}), '' + r.Failure), dom.td(style({textAlign: 'right'}), '' + r.Failure),
dom.td(!r.ResultTypeCounts ? [] : Object.entries(r.ResultTypeCounts).map(kv => kv[0] + ': ' + kv[1]).join('; ')), 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 start = new Date(new Date().getTime() - 30*24*3600*1000).toISOString()
const [records, dnsdomain] = await Promise.all([ const [records, dnsdomain] = await Promise.all([
api.TLSReports(start, end, d), api.TLSReports(start, end, d),
api.Domain(d), api.ParseDomain(d),
]) ])
const policyType = (policy) => { const policyType = (policy) => {
@ -1677,8 +1678,9 @@ const domainTLSRPT = async (d) => {
dom._kids(page, dom._kids(page,
crumbs( crumbs(
crumblink('Mox Admin', '#'), crumblink('Mox Admin', '#'),
crumblink('Domain ' + domainString(dnsdomain), '#domains/'+d), crumblink('TLSRPT', '#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('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.'), dom.p('Below the TLS reports for the past 30 days.'),
@ -1724,7 +1726,7 @@ const domainTLSRPT = async (d) => {
const addRow = (d, di) => { const addRow = (d, di) => {
const row = dom.tr( const row = dom.tr(
index > 0 || rows.length > 0 ? [] : [ 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, 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']))), 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 domainTLSRPTID = async (d, reportID) => {
const [report, dnsdomain] = await Promise.all([ const [report, dnsdomain] = await Promise.all([
api.TLSReportID(d, reportID), api.TLSReportID(d, reportID),
api.Domain(d), api.ParseDomain(d),
]) ])
const page = document.getElementById('page') const page = document.getElementById('page')
dom._kids(page, dom._kids(page,
crumbs( crumbs(
crumblink('Mox Admin', '#'), crumblink('Mox Admin', '#'),
crumblink('Domain ' + domainString(dnsdomain), '#domains/'+d), crumblink('TLSRPT', '#tlsrpt'),
crumblink('TLS report', '#domains/' + d + '/tlsrpt'), crumblink('Reports', '#tlsrpt/reports'),
crumblink('Domain '+domainString(dnsdomain), '#tlsrpt/reports/' + d + ''),
'Report ' + reportID 'Report ' + reportID
), ),
dom.p('Below is the raw report as received from the remote mail server.'), 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]) await domainDMARC(t[1])
} else if (t[0] === 'domains' && t.length === 4 && t[2] === 'dmarc' && parseInt(t[3])) { } else if (t[0] === 'domains' && t.length === 4 && t[2] === 'dmarc' && parseInt(t[3])) {
await domainDMARCReport(t[1], 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') { } else if (t[0] === 'domains' && t.length === 3 && t[2] === 'dnscheck') {
await domainDNSCheck(t[1]) await domainDNSCheck(t[1])
} else if (t[0] === 'domains' && t.length === 3 && t[2] === 'dnsrecords') { } else if (t[0] === 'domains' && t.length === 3 && t[2] === 'dnsrecords') {
@ -2602,6 +2601,10 @@ const init = async () => {
await tlsrptIndex() await tlsrptIndex()
} else if (h === 'tlsrpt/reports') { } else if (h === 'tlsrpt/reports') {
await tlsrptReports() 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') { } else if (h === 'tlsrpt/results') {
await tlsrptResults() await tlsrptResults()
} else if (t[0] == 'tlsrpt' && t[1] == 'results' && t.length === 3) { } 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", "Name": "DomainLocalparts",
"Docs": "DomainLocalparts returns the encoded localparts and accounts configured in domain.", "Docs": "DomainLocalparts returns the encoded localparts and accounts configured in domain.",
@ -2545,7 +2565,7 @@
"Docs": "", "Docs": "",
"Typewords": [ "Typewords": [
"{}", "{}",
"int32" "int64"
] ]
} }
] ]