Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MPP-3932: Disable masks on complaints, round 2 #5115

Merged
merged 37 commits into from
Oct 24, 2024
Merged
Show file tree
Hide file tree
Changes from 36 commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
4f50b0a
Move dev logging inside loop
jwhitlock Oct 15, 2024
dbd2dfc
Log complaints from unknown users
jwhitlock Oct 15, 2024
4a70554
Detect complaint simulator
jwhitlock Oct 15, 2024
f809b56
Update complaint message to match live complaint
jwhitlock Oct 15, 2024
b492e46
Fix _get_address, add _get_address_if_exists
jwhitlock Oct 15, 2024
8b96430
Drop unused original_spam_email arg
jwhitlock Oct 16, 2024
6802e40
Remove google-measurement-protocol
jwhitlock Oct 16, 2024
d6d6760
Customize metrics for disabled email
jwhitlock Oct 16, 2024
946e6f1
Rewrite _handle_complaint
jwhitlock Oct 16, 2024
7951e00
Add init_waffle_flags to init once
jwhitlock Oct 16, 2024
15ddc13
Extract _get_complaint_data
jwhitlock Oct 16, 2024
31785a7
Keep developer mode, remove Jira reference
jwhitlock Oct 16, 2024
b34f2ef
Test missing complainedRecipients
jwhitlock Oct 16, 2024
1f0edce
Test, fix error cases in _get_complaint_data
jwhitlock Oct 16, 2024
e90e5b1
Remove ComplaintData structure
jwhitlock Oct 16, 2024
a3ddf5f
Convert to count
jwhitlock Oct 16, 2024
6c12303
Remove UserComplaintData
jwhitlock Oct 16, 2024
63b8b68
Extract _gather_complainers
jwhitlock Oct 16, 2024
f83e040
Track where the complainer was found
jwhitlock Oct 16, 2024
0bce33b
Fix, test error conditions in _gather_complainers
jwhitlock Oct 16, 2024
f363691
Extract _reduce_future_complaints
jwhitlock Oct 16, 2024
b53a466
Test more complaint error cases
jwhitlock Oct 16, 2024
c279650
Update docs, remove found_in from metrics
jwhitlock Oct 16, 2024
ce69d60
Allow domain addresses for complaint simulation
jwhitlock Oct 16, 2024
93e9af8
Use full URLs for links
jwhitlock Oct 17, 2024
775f2c5
Remove unused translation
jwhitlock Oct 17, 2024
b8eabdb
Update expected email for disabled mask
jwhitlock Oct 17, 2024
81bd1d0
Use '_handle_complaint: developer_mode' for logs
jwhitlock Oct 17, 2024
5293485
Add docs for developer mode
jwhitlock Oct 21, 2024
beeb796
Use mask metrics_id in simulator address
jwhitlock Oct 21, 2024
50ec4ac
Add log_group_id
jwhitlock Oct 21, 2024
ac1688e
Add GetComplaintDataTest, move missing key tests
jwhitlock Oct 21, 2024
39262e5
Change error log message
jwhitlock Oct 21, 2024
363282e
Add GatherComplainersTest, move tests
jwhitlock Oct 21, 2024
888c93f
Rewrite comment on SES simulator
jwhitlock Oct 21, 2024
a0b19f9
Add tests for _get_mask_by_metrics_id
jwhitlock Oct 21, 2024
7e1163b
Rename to standard developer_mode.md
jwhitlock Oct 24, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
178 changes: 178 additions & 0 deletions docs/developer-mode.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
# Developer mode: Collect more data and test the untestable

Developer mode helps Relay staff in two situations. It allows staff to opt-in
to extended data collection for their own account. It allows staff to test
scenarios that are hard to setup or would damage the service.

## Enabling developer mode

To enable developer mode for an existing Relay account, you can run this
Django management command in a shell:

```sh
./manage.py waffle_flag developer_mode --append --user "<[email protected]>"
```

This should only be applied to Relay staff with prior consent. This flag can
log additional data, retained for 90 days, that is not allowed under the
[Relay Privacy Policy][]. On [relay.firefox.com][], shell access is limited
to Service Reliability Engineers (SREs) and can not be done by Relay
developers. A Jira ticket should be used to track the request and the work to
enable the flag.

[Relay Privacy Policy]: https://www.mozilla.org/en-US/privacy/subscription-services/
[relay.firefox.com]: https://relay.firefox.com

## Developer mode features

To see where the code uses developer mode, search the codebase for the keyword
`developer_mode`.

### <a name="h-log-notification"></a>Extended logging of AWS SES Notifications

When the Amazon Web Service (AWS) Simple Email Service (SES) receives an email
to or from a Relay mask, it places a [Received Notification][] in the Relay
incoming mail queue. In production, this includes the email headers and other
metadata. In deprecated Relay configurations, this can include the email
content. Relay processes this notification to forward email.

When a user has developer mode active for their Relay account, and the email
mask description contains the text `DEV:`, then this received notification is
logged. The log will have the message `_handle_received: developer_mode`, and
these items:

- `mask_id`: The identifier for the email mask. For example, `R123` is the
random mask with ID 123, and `D567` is the domain mask with the ID 567.
- `dev_action`: `log`
- `notification_gza85`: The received notification, which may be split over
several log messages. See [Encoded Data][] for working with this format.
- `part`: The segment number of this log, starting at 0
- `parts`: The total number of logs needed to split the data into 1024-byte
segments.
- `log_group_id`: An identifier that is the same for all logs of the same
notification. This can be used to find all the parts of a split
notification.

Extended logging is also enabled for Complaint Notifications, with the
log message `_handle_complaint: developer_mode`. See
[Simulate a Complaint][] below.

[Encoded Data]: #h-encoded-data
[Received Notification]: https://docs.aws.amazon.com/ses/latest/dg/receiving-email-notifications-contents.html
[Simulate a Complaint]: #h-simulate-complaint

### <a name="h-simulate-complaint"></a>Simulate a Complaint

AWS SES subscribes to the [feedback loop][] for some email providers. When a
user marks a message as spam, the email provider notifies AWS via the feedback
loop. AWS SES marks this against Relay's sender reputation, and places a
[Complaint Notification][] in the Relay incoming mail queue.

When a user has developer mode active for their Relay account, and the email
mask description contains the text `DEV:simulate_complaint` (no spaces), then
emails to the mask are no longer forwarded to the user's real email, but
instead to the [mailbox simulator][] complaint email address. The address has
the format `complaint+R123@@simulator.amazonses.com`. The embedded mask
identifier (`R123` in this example) allows linking the simulated complant
back to the developer.

When handling the complaint notification, Relay performs the standard complaint
handling. It sets `auto_block_spam` on the first complaint, to block incoming
emails that AWS SES identifies as spam. If the flag `disable_mask_on_complaint`
is enabled, the mask is set to "Blocking all email" on the second complaint.
The email notifying that the mask was set to "Block all emails" is sent to the
user's real email address, not the SES complaint simulator email address.

Since `DEV:simulate_complaint` also includes `DEV:`, the incoming email
notification is also logged; see [Extended logging of AWS SES Notifications][].
In this case, the log message is `_handle_received: developer_mode` with
`"dev_action": "simulate_complaint"` instead of `"log"`.

Additionally, _any_ complaint notification is logged, not just ones to the mask
labeled `DEV:simulate_complaint`.

[Complaint Notification]: https://docs.aws.amazon.com/ses/latest/dg/notification-contents.html#complaint-object
[Extended logging of AWS SES Notifications]: #h-log-notification
[feedback loop]: https://docs.aws.amazon.com/ses/latest/dg/success-metrics.html#metrics-complaints
[mailbox simulator]: https://docs.aws.amazon.com/ses/latest/dg/send-an-email-from-console.html#send-email-simulator

### <a name="h-encoded-data"></a>Encoded Data

When logging a notification (see [Extended logging of AWS SES Notifications]),
the notification is JSON-encoded, then compressed with [zlib][], then encoded
with [Ascii85]. This is implemented by `emails.utils.encode_dict_gza85`.

For example, this Python dict:

```python
{'notificationType': 'Received'}
```

is JSON-encoded to the very similar:

```json
{ "notificationType": "Received" }
```

and then compressed with [zlib.compress][] (hexadecimal format):

```text
789cab56cacb2fc94ccb4c4e2cc9cccf0ba92c4855b252500a4a4d4ecd2c4b4d51aa0500c6600bab
```

and finally encoded with [base64.a85encode][]:

```text
Gatg8b0)H[9Zp+)/BQ,^$`P[J<O,M!$;+#fbq)L^;5sd"`aB1T
```

This would then be logged in the [MozLog format][] like:

```json
{
"Timestamp": 1729121615657684992,
"Type": "eventsinfo",
"Logger": "fx-private-relay",
"Hostname": "dd52c3c5-0674-4d33-85db-2737887750e4",
"EnvVersion": "2.0",
"Severity": 6,
"Pid": 102,
"Fields": {
"msg": "_handle_received: developer_mode",
"mask_id": "R101",
"dev_action": "log",
"log_group_id": "32285dff-288b-4b37-a247-5c1a45456136",
"part": 0,
"parts": 1,
"notification_gza85": "Gatg8b0)H[9Zp+)/BQ,^$`P[J<O,M!$;+#fbq)L^;5sd\"`aB1T"
}
}
```

In this example, the zlib-compressed version is longer than the JSON-encoded
version (40 versus 32 bytes). In general, notification JSON can be compressed
to a smaller size.

An Ascii85-encoded string is longer than the binary string by a 5:4 ratio. For
example a 120-byte binary string is encoded as 150 bytes in Ascii85. This is
more efficient than Base64, which has a 4:3 encoding ration. A 120-byte binary
string is encoded as 160 bytes in base64. A downside is that Ascii85 uses quote
characters like `"` and `'`, requiring escaping in JSON and in Python strings.

The function `emails.utils.decode_dict_gza85` can be used to decode an encoded
string back to a Python dictionary.

If the encoded string is more than 1024 bytes, it is split into 1024-byte
segments and emitted over several log messages. This ensures it does not exceed
any log backend limits. The log field `"log_group_id"` will be unique for the
encoded string, and can be used to find all the parts. The log field `"part"`
can be used to order the log messages. The log field `"parts"` can be used to
confirm that all the segments are found. The segments can be concatenated or
joined by whitespace, such as newlines, before sending it to
`decode_dict_gza85`.

[Ascii85]: https://en.wikipedia.org/wiki/Ascii85
[MozLog format]: https://wiki.mozilla.org/Firefox/Services/Logging#MozLog_application_logging_standard
[base64.a85encode]: https://docs.python.org/3.11/library/base64.html#base64.a85encode
[zlib.compress]: https://docs.python.org/3/library/zlib.html
[zlib]: https://docs.python.org/3/library/zlib.html
4 changes: 2 additions & 2 deletions emails/templates/emails/disabled_mask_for_spam.html
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
</style>
<table role="presentation" border="0" cellpadding="0" cellspacing="10px" style="padding: 30px;" align="center">
{% with mask|striptags|urlencode as mask_for_url %}
{% with "/accounts/profile/#"|add:mask_for_url as mask_url%}
{% with SITE_ORIGIN|add:"/accounts/profile/#"|add:mask_for_url as mask_url%}
<tr>
<td style="max-width:850px; padding-top: 0px; padding-bottom: 0px; text-align: center;">
<h2 style="font-family: inter medium, Arial, system-ui, sans-serif;">
Expand All @@ -44,7 +44,7 @@ <h2 style="font-family: inter medium, Arial, system-ui, sans-serif;">
<tr>
<td style="max-width:850px; padding-top: 0px; padding-bottom: 0px;">
<p style="line-height: 1.5; margin-bottom: 1.5em">
{% ftlmsg 'relay-received-spam-complaint-and-deactivated-mask-html' mask=mask %} {% ftlmsg 'relay-disabled-your-mask-detail-html' mask=mask %}
{% ftlmsg 'relay-received-spam-complaint-and-deactivated-mask-html' mask=mask %}
</p>
<p style="line-height: 1.5; margin-bottom: 1.5em">
{% ftlmsg 'reactivate-mask-detail-html' mask_url=mask_url %}
Expand Down
14 changes: 8 additions & 6 deletions emails/tests/fixtures/disabled_mask_for_spam_expected.email
Original file line number Diff line number Diff line change
Expand Up @@ -269,13 +269,14 @@ ges/email-images/warning.png" style=3D"margin: 0 5px;" alt=3D"warning icon"/>
<td style=3D"max-width:850px; padding-top: 0px; padding-bottom: 0=
px;">
<p style=3D"line-height: 1.5; margin-bottom: 1.5em">
An email from your mask address, [email protected], was mark=
ed as spam. When this happens, Firefox Relay deactivates the mask and stops f=
orwarding emails. ???
An email from your mask address, [email protected], was =
marked as spam. When this happens, Firefox Relay deactivates the mask and sto=
ps forwarding emails.
</p>
<p style=3D"line-height: 1.5; margin-bottom: 1.5em">
To reactivate your mask, <a href=3D"/accounts/profile/#w4=
1fwbt4q%40test.com">remove email blocking</a> on your Firefox Relay dashboard.
To reactivate your mask, <a href=3D"http://127.0.0.1:8000=
/accounts/profile/#w41fwbt4q%40test.com">remove email blocking</a> on your Fi=
refox Relay dashboard.
</p>
<p style=3D"line-height: 1.5; margin-bottom: 1.5em">
<a href=3D"https://support.mozilla.org/kb/disable-email-f=
Expand All @@ -287,7 +288,8 @@ email forwarding</a>
<tr>
<td style=3D"max-width:850px; padding-top: 20px; padding-bottom: =
20px; text-align: center;">
<a href=3D"/accounts/profile/#w41fwbt4q%40test.com"
<a href=3D"http://127.0.0.1:8000/accounts/profile/#w41fwbt4q%40=
test.com"
target=3D"_blank" class=3D"button" style=3D"color: #FFFFFF;=
" rel=3D"noreferrer">
Reactivate your email mask
Expand Down
Loading