mirror of
https://github.com/mjl-/mox.git
synced 2025-07-12 17:04:39 +03:00
add suppression list for outgoing dmarc and tls reports
for reporting addresses that cause DSNs to be returned. that just adds noise. the admin can add/remove/extend addresses through the webadmin. in the future, we could send reports with a smtp mail from of "postmaster+<signed-encoded-recipient>@...", and add the reporting recipient on the suppression list automatically when a DSN comes in on that address, but for now this will probably do.
This commit is contained in:
@ -2031,6 +2031,36 @@ func (Admin) DMARCRemoveEvaluations(ctx context.Context, domain string) {
|
||||
xcheckf(ctx, err, "removing evaluations for domain")
|
||||
}
|
||||
|
||||
// DMARCSuppressAdd adds a reporting address to the suppress list. Outgoing
|
||||
// reports will be suppressed for a period.
|
||||
func (Admin) DMARCSuppressAdd(ctx context.Context, reportingAddress string, until time.Time, comment string) {
|
||||
addr, err := smtp.ParseAddress(reportingAddress)
|
||||
xcheckuserf(ctx, err, "parsing reporting address")
|
||||
|
||||
ba := dmarcdb.SuppressAddress{ReportingAddress: addr.String(), Until: until, Comment: comment}
|
||||
err = dmarcdb.SuppressAdd(ctx, &ba)
|
||||
xcheckf(ctx, err, "adding address to suppresslist")
|
||||
}
|
||||
|
||||
// DMARCSuppressList returns all reporting addresses on the suppress list.
|
||||
func (Admin) DMARCSuppressList(ctx context.Context) []dmarcdb.SuppressAddress {
|
||||
l, err := dmarcdb.SuppressList(ctx)
|
||||
xcheckf(ctx, err, "listing reporting addresses in suppresslist")
|
||||
return l
|
||||
}
|
||||
|
||||
// DMARCSuppressRemove removes a reporting address record from the suppress list.
|
||||
func (Admin) DMARCSuppressRemove(ctx context.Context, id int64) {
|
||||
err := dmarcdb.SuppressRemove(ctx, id)
|
||||
xcheckf(ctx, err, "removing reporting address from suppresslist")
|
||||
}
|
||||
|
||||
// DMARCSuppressExtend updates the until field of a suppressed reporting address record.
|
||||
func (Admin) DMARCSuppressExtend(ctx context.Context, id int64, until time.Time) {
|
||||
err := dmarcdb.SuppressUpdate(ctx, id, until)
|
||||
xcheckf(ctx, err, "updating reporting address in suppresslist")
|
||||
}
|
||||
|
||||
// TLSRPTResults returns all TLSRPT results in the database.
|
||||
func (Admin) TLSRPTResults(ctx context.Context) []tlsrptdb.TLSResult {
|
||||
results, err := tlsrptdb.Results(ctx)
|
||||
@ -2078,3 +2108,33 @@ func (Admin) TLSRPTRemoveResults(ctx context.Context, domain string, day string)
|
||||
err = tlsrptdb.RemoveResultsPolicyDomain(ctx, dom, day)
|
||||
xcheckf(ctx, err, "removing tls results")
|
||||
}
|
||||
|
||||
// TLSRPTSuppressAdd adds a reporting address to the suppress list. Outgoing
|
||||
// reports will be suppressed for a period.
|
||||
func (Admin) TLSRPTSuppressAdd(ctx context.Context, reportingAddress string, until time.Time, comment string) {
|
||||
addr, err := smtp.ParseAddress(reportingAddress)
|
||||
xcheckuserf(ctx, err, "parsing reporting address")
|
||||
|
||||
ba := tlsrptdb.TLSRPTSuppressAddress{ReportingAddress: addr.String(), Until: until, Comment: comment}
|
||||
err = tlsrptdb.SuppressAdd(ctx, &ba)
|
||||
xcheckf(ctx, err, "adding address to suppresslist")
|
||||
}
|
||||
|
||||
// TLSRPTSuppressList returns all reporting addresses on the suppress list.
|
||||
func (Admin) TLSRPTSuppressList(ctx context.Context) []tlsrptdb.TLSRPTSuppressAddress {
|
||||
l, err := tlsrptdb.SuppressList(ctx)
|
||||
xcheckf(ctx, err, "listing reporting addresses in suppresslist")
|
||||
return l
|
||||
}
|
||||
|
||||
// TLSRPTSuppressRemove removes a reporting address record from the suppress list.
|
||||
func (Admin) TLSRPTSuppressRemove(ctx context.Context, id int64) {
|
||||
err := tlsrptdb.SuppressRemove(ctx, id)
|
||||
xcheckf(ctx, err, "removing reporting address from suppresslist")
|
||||
}
|
||||
|
||||
// TLSRPTSuppressExtend updates the until field of a suppressed reporting address record.
|
||||
func (Admin) TLSRPTSuppressExtend(ctx context.Context, id int64, until time.Time) {
|
||||
err := tlsrptdb.SuppressUpdate(ctx, id, until)
|
||||
xcheckf(ctx, err, "updating reporting address in suppresslist")
|
||||
}
|
||||
|
@ -1023,7 +1023,7 @@ const dmarcIndex = async () => {
|
||||
dom._kids(page,
|
||||
crumbs(
|
||||
crumblink('Mox Admin', '#'),
|
||||
'DMARC reports and evaluations',
|
||||
'DMARC',
|
||||
),
|
||||
dom.ul(
|
||||
dom.li(
|
||||
@ -1085,7 +1085,10 @@ const renderDMARCSummaries = (summaries) => {
|
||||
}
|
||||
|
||||
const dmarcEvaluations = async () => {
|
||||
const evalStats = await api.DMARCEvaluationStats()
|
||||
const [evalStats, suppressAddresses] = await Promise.all([
|
||||
api.DMARCEvaluationStats(),
|
||||
api.DMARCSuppressList(),
|
||||
])
|
||||
|
||||
const isEmpty = (o) => {
|
||||
for (const e in o) {
|
||||
@ -1094,6 +1097,9 @@ const dmarcEvaluations = async () => {
|
||||
return true
|
||||
}
|
||||
|
||||
let fieldset, reportingAddress, until, comment
|
||||
const nextmonth = new Date(new Date().getTime()+31*24*3600*1000)
|
||||
|
||||
const page = document.getElementById('page')
|
||||
dom._kids(page,
|
||||
crumbs(
|
||||
@ -1121,6 +1127,101 @@ const dmarcEvaluations = async () => {
|
||||
isEmpty(evalStats) ? dom.tr(dom.td(attr({colspan: '3'}), 'No evaluations.')) : [],
|
||||
),
|
||||
),
|
||||
dom.br(),
|
||||
dom.br(),
|
||||
dom.h2('Suppressed reporting addresses'),
|
||||
dom.p('In practice, sending a DMARC report to a reporting address can cause DSN to be sent back. Such addresses can be added to a supression list for a period, to reduce noise in the postmaster mailbox.'),
|
||||
dom.form(
|
||||
async function submit(e) {
|
||||
e.stopPropagation()
|
||||
e.preventDefault()
|
||||
try {
|
||||
fieldset.disabled = true
|
||||
await api.DMARCSuppressAdd(reportingAddress.value, new Date(until.value), comment.value)
|
||||
} catch (err) {
|
||||
console.log({err})
|
||||
window.alert('Error: ' + err.message)
|
||||
return
|
||||
} finally {
|
||||
fieldset.disabled = false
|
||||
}
|
||||
window.location.reload() // todo: add the address to the list, or only reload the list
|
||||
},
|
||||
fieldset=dom.fieldset(
|
||||
dom.label(
|
||||
style({display: 'inline-block'}),
|
||||
'Reporting address',
|
||||
dom.br(),
|
||||
reportingAddress=dom.input(attr({required: ''})),
|
||||
),
|
||||
' ',
|
||||
dom.label(
|
||||
style({display: 'inline-block'}),
|
||||
'Until',
|
||||
dom.br(),
|
||||
until=dom.input(attr({type: 'date', required: '', value: nextmonth.getFullYear()+'-'+(1+nextmonth.getMonth())+'-'+nextmonth.getDate()})),
|
||||
),
|
||||
' ',
|
||||
dom.label(
|
||||
style({display: 'inline-block'}),
|
||||
dom.span('Comment (optional)'),
|
||||
dom.br(),
|
||||
comment=dom.input(),
|
||||
),
|
||||
' ',
|
||||
dom.button('Add', attr({title: 'Outgoing reports to this reporting address will be suppressed until the end time.'})),
|
||||
),
|
||||
),
|
||||
dom.br(),
|
||||
dom('table.hover',
|
||||
dom.thead(
|
||||
dom.tr(
|
||||
dom.th('Reporting address'),
|
||||
dom.th('Until'),
|
||||
dom.th('Comment'),
|
||||
dom.th('Action'),
|
||||
),
|
||||
),
|
||||
dom.tbody(
|
||||
(suppressAddresses || []).length === 0 ? dom.tr(dom.td(attr({colspan: '4'}), 'No suppressed reporting addresses.')) : [],
|
||||
(suppressAddresses || []).map(ba =>
|
||||
dom.tr(
|
||||
dom.td(ba.ReportingAddress),
|
||||
dom.td(ba.Until),
|
||||
dom.td(ba.Comment),
|
||||
dom.td(
|
||||
dom.button('Remove', attr({type: 'button'}), async function click(e) {
|
||||
try {
|
||||
e.target.disabled = true
|
||||
await api.DMARCSuppressRemove(ba.ID)
|
||||
} catch (err) {
|
||||
console.log({err})
|
||||
window.alert('Error: ' + err.message)
|
||||
return
|
||||
} finally {
|
||||
e.target.disabled = false
|
||||
}
|
||||
window.location.reload() // todo: only reload the list
|
||||
}),
|
||||
' ',
|
||||
dom.button('Extend for 1 month', attr({type: 'button'}), async function click(e) {
|
||||
try {
|
||||
e.target.disabled = true
|
||||
await api.DMARCSuppressExtend(ba.ID, new Date(new Date().getTime() + 31*24*3600*1000))
|
||||
} catch (err) {
|
||||
console.log({err})
|
||||
window.alert('Error: ' + err.message)
|
||||
return
|
||||
} finally {
|
||||
e.target.disabled = false
|
||||
}
|
||||
window.location.reload() // todo: only reload the list
|
||||
}),
|
||||
),
|
||||
)
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@ -1479,10 +1580,16 @@ const tlsrptIndex = async () => {
|
||||
}
|
||||
|
||||
const tlsrptResults = async () => {
|
||||
const results = await api.TLSRPTResults()
|
||||
const [results, suppressAddresses] = await Promise.all([
|
||||
api.TLSRPTResults(),
|
||||
api.TLSRPTSuppressList(),
|
||||
])
|
||||
|
||||
// todo: add a view where results are grouped by policy domain+dayutc. now each recipient domain gets a row.
|
||||
|
||||
let fieldset, reportingAddress, until, comment
|
||||
const nextmonth = new Date(new Date().getTime()+31*24*3600*1000)
|
||||
|
||||
const page = document.getElementById('page')
|
||||
dom._kids(page,
|
||||
crumbs(
|
||||
@ -1545,6 +1652,101 @@ const tlsrptResults = async () => {
|
||||
results.length === 0 ? dom.tr(dom.td(attr({colspan: '9'}), 'No results.')) : [],
|
||||
),
|
||||
),
|
||||
dom.br(),
|
||||
dom.br(),
|
||||
dom.h2('Suppressed reporting addresses'),
|
||||
dom.p('In practice, sending a TLS report to a reporting address can cause DSN to be sent back. Such addresses can be added to a suppress list for a period, to reduce noise in the postmaster mailbox.'),
|
||||
dom.form(
|
||||
async function submit(e) {
|
||||
e.stopPropagation()
|
||||
e.preventDefault()
|
||||
try {
|
||||
fieldset.disabled = true
|
||||
await api.TLSRPTSuppressAdd(reportingAddress.value, new Date(until.value), comment.value)
|
||||
} catch (err) {
|
||||
console.log({err})
|
||||
window.alert('Error: ' + err.message)
|
||||
return
|
||||
} finally {
|
||||
fieldset.disabled = false
|
||||
}
|
||||
window.location.reload() // todo: add the address to the list, or only reload the list
|
||||
},
|
||||
fieldset=dom.fieldset(
|
||||
dom.label(
|
||||
style({display: 'inline-block'}),
|
||||
'Reporting address',
|
||||
dom.br(),
|
||||
reportingAddress=dom.input(attr({required: ''})),
|
||||
),
|
||||
' ',
|
||||
dom.label(
|
||||
style({display: 'inline-block'}),
|
||||
'Until',
|
||||
dom.br(),
|
||||
until=dom.input(attr({type: 'date', required: '', value: nextmonth.getFullYear()+'-'+(1+nextmonth.getMonth())+'-'+nextmonth.getDate()})),
|
||||
),
|
||||
' ',
|
||||
dom.label(
|
||||
style({display: 'inline-block'}),
|
||||
dom.span('Comment (optional)'),
|
||||
dom.br(),
|
||||
comment=dom.input(),
|
||||
),
|
||||
' ',
|
||||
dom.button('Add', attr({title: 'Outgoing reports to this reporting address will be suppressed until the end time.'})),
|
||||
),
|
||||
),
|
||||
dom.br(),
|
||||
dom('table.hover',
|
||||
dom.thead(
|
||||
dom.tr(
|
||||
dom.th('Reporting address'),
|
||||
dom.th('Until'),
|
||||
dom.th('Comment'),
|
||||
dom.th('Action'),
|
||||
),
|
||||
),
|
||||
dom.tbody(
|
||||
(suppressAddresses || []).length === 0 ? dom.tr(dom.td(attr({colspan: '4'}), 'No suppressed reporting addresses.')) : [],
|
||||
(suppressAddresses || []).map(ba =>
|
||||
dom.tr(
|
||||
dom.td(ba.ReportingAddress),
|
||||
dom.td(ba.Until),
|
||||
dom.td(ba.Comment),
|
||||
dom.td(
|
||||
dom.button('Remove', attr({type: 'button'}), async function click(e) {
|
||||
try {
|
||||
e.target.disabled = true
|
||||
await api.TLSRPTSuppressRemove(ba.ID)
|
||||
} catch (err) {
|
||||
console.log({err})
|
||||
window.alert('Error: ' + err.message)
|
||||
return
|
||||
} finally {
|
||||
e.target.disabled = false
|
||||
}
|
||||
window.location.reload() // todo: only reload the list
|
||||
}),
|
||||
' ',
|
||||
dom.button('Extend for 1 month', attr({type: 'button'}), async function click(e) {
|
||||
try {
|
||||
e.target.disabled = true
|
||||
await api.TLSRPTSuppressExtend(ba.ID, new Date(new Date().getTime() + 31*24*3600*1000))
|
||||
} catch (err) {
|
||||
console.log({err})
|
||||
window.alert('Error: ' + err.message)
|
||||
return
|
||||
} finally {
|
||||
e.target.disabled = false
|
||||
}
|
||||
window.location.reload() // todo: only reload the list
|
||||
}),
|
||||
),
|
||||
)
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -828,6 +828,77 @@
|
||||
],
|
||||
"Returns": []
|
||||
},
|
||||
{
|
||||
"Name": "DMARCSuppressAdd",
|
||||
"Docs": "DMARCSuppressAdd adds a reporting address to the suppress list. Outgoing\nreports will be suppressed for a period.",
|
||||
"Params": [
|
||||
{
|
||||
"Name": "reportingAddress",
|
||||
"Typewords": [
|
||||
"string"
|
||||
]
|
||||
},
|
||||
{
|
||||
"Name": "until",
|
||||
"Typewords": [
|
||||
"timestamp"
|
||||
]
|
||||
},
|
||||
{
|
||||
"Name": "comment",
|
||||
"Typewords": [
|
||||
"string"
|
||||
]
|
||||
}
|
||||
],
|
||||
"Returns": []
|
||||
},
|
||||
{
|
||||
"Name": "DMARCSuppressList",
|
||||
"Docs": "DMARCSuppressList returns all reporting addresses on the suppress list.",
|
||||
"Params": [],
|
||||
"Returns": [
|
||||
{
|
||||
"Name": "r0",
|
||||
"Typewords": [
|
||||
"[]",
|
||||
"SuppressAddress"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"Name": "DMARCSuppressRemove",
|
||||
"Docs": "DMARCSuppressRemove removes a reporting address record from the suppress list.",
|
||||
"Params": [
|
||||
{
|
||||
"Name": "id",
|
||||
"Typewords": [
|
||||
"int64"
|
||||
]
|
||||
}
|
||||
],
|
||||
"Returns": []
|
||||
},
|
||||
{
|
||||
"Name": "DMARCSuppressExtend",
|
||||
"Docs": "DMARCSuppressExtend updates the until field of a suppressed reporting address record.",
|
||||
"Params": [
|
||||
{
|
||||
"Name": "id",
|
||||
"Typewords": [
|
||||
"int64"
|
||||
]
|
||||
},
|
||||
{
|
||||
"Name": "until",
|
||||
"Typewords": [
|
||||
"timestamp"
|
||||
]
|
||||
}
|
||||
],
|
||||
"Returns": []
|
||||
},
|
||||
{
|
||||
"Name": "TLSRPTResults",
|
||||
"Docs": "TLSRPTResults returns all TLSRPT results in the database.",
|
||||
@ -920,6 +991,77 @@
|
||||
}
|
||||
],
|
||||
"Returns": []
|
||||
},
|
||||
{
|
||||
"Name": "TLSRPTSuppressAdd",
|
||||
"Docs": "TLSRPTSuppressAdd adds a reporting address to the suppress list. Outgoing\nreports will be suppressed for a period.",
|
||||
"Params": [
|
||||
{
|
||||
"Name": "reportingAddress",
|
||||
"Typewords": [
|
||||
"string"
|
||||
]
|
||||
},
|
||||
{
|
||||
"Name": "until",
|
||||
"Typewords": [
|
||||
"timestamp"
|
||||
]
|
||||
},
|
||||
{
|
||||
"Name": "comment",
|
||||
"Typewords": [
|
||||
"string"
|
||||
]
|
||||
}
|
||||
],
|
||||
"Returns": []
|
||||
},
|
||||
{
|
||||
"Name": "TLSRPTSuppressList",
|
||||
"Docs": "TLSRPTSuppressList returns all reporting addresses on the suppress list.",
|
||||
"Params": [],
|
||||
"Returns": [
|
||||
{
|
||||
"Name": "r0",
|
||||
"Typewords": [
|
||||
"[]",
|
||||
"TLSRPTSuppressAddress"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"Name": "TLSRPTSuppressRemove",
|
||||
"Docs": "TLSRPTSuppressRemove removes a reporting address record from the suppress list.",
|
||||
"Params": [
|
||||
{
|
||||
"Name": "id",
|
||||
"Typewords": [
|
||||
"int64"
|
||||
]
|
||||
}
|
||||
],
|
||||
"Returns": []
|
||||
},
|
||||
{
|
||||
"Name": "TLSRPTSuppressExtend",
|
||||
"Docs": "TLSRPTSuppressExtend updates the until field of a suppressed reporting address record.",
|
||||
"Params": [
|
||||
{
|
||||
"Name": "id",
|
||||
"Typewords": [
|
||||
"int64"
|
||||
]
|
||||
},
|
||||
{
|
||||
"Name": "until",
|
||||
"Typewords": [
|
||||
"timestamp"
|
||||
]
|
||||
}
|
||||
],
|
||||
"Returns": []
|
||||
}
|
||||
],
|
||||
"Sections": [],
|
||||
@ -3808,6 +3950,47 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"Name": "SuppressAddress",
|
||||
"Docs": "SuppressAddress is a reporting address for which outgoing DMARC reports\nwill be suppressed for a period.",
|
||||
"Fields": [
|
||||
{
|
||||
"Name": "ID",
|
||||
"Docs": "",
|
||||
"Typewords": [
|
||||
"int64"
|
||||
]
|
||||
},
|
||||
{
|
||||
"Name": "Inserted",
|
||||
"Docs": "",
|
||||
"Typewords": [
|
||||
"timestamp"
|
||||
]
|
||||
},
|
||||
{
|
||||
"Name": "ReportingAddress",
|
||||
"Docs": "",
|
||||
"Typewords": [
|
||||
"string"
|
||||
]
|
||||
},
|
||||
{
|
||||
"Name": "Until",
|
||||
"Docs": "",
|
||||
"Typewords": [
|
||||
"timestamp"
|
||||
]
|
||||
},
|
||||
{
|
||||
"Name": "Comment",
|
||||
"Docs": "",
|
||||
"Typewords": [
|
||||
"string"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"Name": "TLSResult",
|
||||
"Docs": "TLSResult is stored in the database to track TLS results per policy domain, day\nand recipient domain. These records will be included in TLS reports.",
|
||||
@ -3877,6 +4060,47 @@
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"Name": "TLSRPTSuppressAddress",
|
||||
"Docs": "TLSRPTSuppressAddress is a reporting address for which outgoing TLS reports\nwill be suppressed for a period.",
|
||||
"Fields": [
|
||||
{
|
||||
"Name": "ID",
|
||||
"Docs": "",
|
||||
"Typewords": [
|
||||
"int64"
|
||||
]
|
||||
},
|
||||
{
|
||||
"Name": "Inserted",
|
||||
"Docs": "",
|
||||
"Typewords": [
|
||||
"timestamp"
|
||||
]
|
||||
},
|
||||
{
|
||||
"Name": "ReportingAddress",
|
||||
"Docs": "",
|
||||
"Typewords": [
|
||||
"string"
|
||||
]
|
||||
},
|
||||
{
|
||||
"Name": "Until",
|
||||
"Docs": "",
|
||||
"Typewords": [
|
||||
"timestamp"
|
||||
]
|
||||
},
|
||||
{
|
||||
"Name": "Comment",
|
||||
"Docs": "",
|
||||
"Typewords": [
|
||||
"string"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"Ints": [],
|
||||
|
Reference in New Issue
Block a user