Skip to content

Commit c4efb7b

Browse files
cpujsha
authored andcommitted
Support -grace parameter for contact-exporter, update docs (letsencrypt#2044)
Presently the `contact-exporter` command only exports registration IDs that have existing certificates that [have not yet expired](https://github.com/letsencrypt/boulder/blob/217b79b8c265da2ae9177791a3ab136e6d656e2f/cmd/contact-exporter/main.go#L39). The threshold is [the current time](https://github.com/letsencrypt/boulder/blob/217b79b8c265da2ae9177791a3ab136e6d656e2f/cmd/contact-exporter/main.go#L42) when run. This PR adds a user configurable grace period that we can use to offset this expiration check. E.g. export all registration IDs that have unexpired certificates, or certificates that expired in the last N days. This resolves letsencrypt#1959 This PR also catches up the documentation strings for both the `expiration-mailer` and the `contact-exporter` to include the changes landed in letsencrypt#1958. Both have been updated to describe operating on registration IDs in place of bare emails.
1 parent cfd37bd commit c4efb7b

File tree

3 files changed

+83
-40
lines changed

3 files changed

+83
-40
lines changed

cmd/contact-exporter/main.go

Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"fmt"
77
"io/ioutil"
88
"os"
9+
"time"
910

1011
"gopkg.in/gorp.v1"
1112

@@ -19,6 +20,7 @@ type contactExporter struct {
1920
log blog.Logger
2021
dbMap *gorp.DbMap
2122
clk clock.Clock
23+
grace time.Duration
2224
}
2325

2426
type contact struct {
@@ -39,7 +41,7 @@ func (c contactExporter) findContacts() ([]contact, error) {
3941
WHERE expires >= :expireCutoff
4042
);`,
4143
map[string]interface{}{
42-
"expireCutoff": c.clk.Now(),
44+
"expireCutoff": c.clk.Now().Add(-c.grace),
4345
})
4446
if err != nil {
4547
c.log.AuditErr(fmt.Sprintf("Error finding contacts: %s", err))
@@ -69,26 +71,46 @@ func writeContacts(contactsList []contact, outfile string) error {
6971
const usageIntro = `
7072
Introduction:
7173
72-
The contact exporter exists to retrieve the email addresses of all registered
73-
users with currently unexpired certificates. This list of email addresses can
74+
The contact exporter exists to retrieve the IDs of all registered
75+
users with currently unexpired certificates. This list of registration IDs can
7476
then be given as input to the notification mailer to send bulk notifications.
7577
76-
The email addresses are deduplicated and sorted prior to being writen to the
77-
outfile. E.g. if two registrations exist with the contact email
78-
"example@example.com", this address will only appear once. Registration contacts
79-
that are *not* email addresses are discarded (e.g. tel:999-999-9999)
78+
The -grace parameter can be used to allow registrations with certificates that
79+
have already expired to be included in the export. The argument is a Go duration
80+
obeying the usual suffix rules (e.g. 24h).
81+
82+
Registration IDs are favoured over email addresses as the intermediate format in
83+
order to ensure the most up to date contact information is used at the time of
84+
notification. The notification mailer will resolve the ID to email(s) when the
85+
mailing is underway, ensuring we use the correct address if a user has updated
86+
their contact information between the time of export and the time of
87+
notification.
88+
89+
The contact exporter's registration ID output will be JSON of the form:
90+
[
91+
{ "id": 1 },
92+
...
93+
{ "id": n }
94+
]
8095
8196
Examples:
82-
Export all email addresses to "emails.txt":
97+
Export all registration IDs with unexpired certificates to "regs.json":
98+
99+
contact-exporter -config test/config/contact-exporter.json -outfile regs.json
100+
101+
Export all registration IDs with certificates that are unexpired or expired
102+
within the last two days to "regs.json":
83103
84-
contact-exporter -config test/config/contact-exporter.json -outfile emails.txt
104+
contact-exporter -config test/config/contact-exporter.json -grace 48h -outfile
105+
"regs.json"
85106
86107
Required arguments:
87108
- config
88109
- outfile`
89110

90111
func main() {
91112
outFile := flag.String("outfile", "", "File to write contacts to (defaults to stdout).")
113+
grace := flag.Duration("grace", 2*24*time.Hour, "Include contacts with certificates that expired in < grace ago")
92114
type config struct {
93115
ContactExporter struct {
94116
cmd.DBConfig
@@ -126,6 +148,7 @@ func main() {
126148
log: log,
127149
dbMap: dbMap,
128150
clk: cmd.Clock(),
151+
grace: *grace,
129152
}
130153

131154
contacts, err := exporter.findContacts()

cmd/contact-exporter/main_test.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,18 @@ func TestFindContacts(t *testing.T) {
6767
test.AssertEquals(t, contacts[0].ID, regA.ID)
6868
test.AssertEquals(t, contacts[1].ID, regC.ID)
6969
test.AssertEquals(t, contacts[2].ID, regD.ID)
70+
71+
// Allow a 1 year grace period
72+
testCtx.c.grace = 360 * 24 * time.Hour
73+
contacts, err = testCtx.c.findContacts()
74+
test.AssertNotError(t, err, "findContacts() produced error")
75+
// Now all four registration should be returned, including RegB since its
76+
// certificate expired within the grace period
77+
test.AssertEquals(t, len(contacts), 4)
78+
test.AssertEquals(t, contacts[0].ID, regA.ID)
79+
test.AssertEquals(t, contacts[1].ID, regB.ID)
80+
test.AssertEquals(t, contacts[2].ID, regC.ID)
81+
test.AssertEquals(t, contacts[3].ID, regD.ID)
7082
}
7183

7284
func exampleContacts() []contact {

cmd/notify-mailer/main.go

Lines changed: 39 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -175,57 +175,65 @@ func emailsForReg(id int, dbMap dbSelector) ([]string, error) {
175175
const usageIntro = `
176176
Introduction:
177177
178-
The notification mailer exists to send a fixed message to a list of email
179-
addresses. The attributes of the message (from address, subject, and message
180-
content) are provided by the command line arguments. The message content is used
181-
verbatim and must be provided as a path to a plaintext file via the -body
182-
argument. The list of recipient emails should be provided via the -toFile
183-
argument as a path to a plaintext file containing one email per line.
178+
The notification mailer exists to send a fixed message to the contact associated
179+
with a list of registration IDs. The attributes of the message (from address,
180+
subject, and message content) are provided by the command line arguments. The
181+
message content is used verbatim and must be provided as a path to a plaintext
182+
file via the -body argument. A list of registration IDs should be provided via
183+
the -toFile argument as a path to a plaintext file containing JSON of the form:
184+
185+
[
186+
{ "id": 1 },
187+
...
188+
{ "id": n }
189+
]
184190
185191
To help the operator gain confidence in the mailing run before committing fully
186192
three safety features are supported: dry runs, checkpointing and a sleep
187193
interval.
188194
189-
The -dryRun flag will use a mock mailer that prints message content to stdout
190-
instead of performing an SMTP transaction with a real mailserver. This can be
191-
used when the initial parameters are being tweaked to ensure no real emails are
192-
sent.
195+
The -dryRun=true flag will use a mock mailer that prints message content to
196+
stdout instead of performing an SMTP transaction with a real mailserver. This
197+
can be used when the initial parameters are being tweaked to ensure no real
198+
emails are sent. Using -dryRun=false will send real email.
193199
194200
Checkpointing is supported via the -start and -end arguments. The -start flag
195-
specifies which line of the -toFile to start processing at. Similarly, the -end
196-
flag specifies which line of the -toFile to end processing at. In combination
197-
these can be used to process only a fixed number of recipients at a time, and
198-
to resume mailing after early termination.
201+
specifies which registration ID of the -toFile to start processing at.
202+
Similarly, the -end flag specifies which registration ID of the -toFile to end
203+
processing at. In combination these can be used to process only a fixed number
204+
of recipients at a time, and to resume mailing after early termination.
199205
200206
During mailing the -sleep argument is used to space out individual messages.
201207
This can be used to ensure that the mailing happens at a steady pace with ample
202208
opportunity for the operator to terminate early in the event of error. The
203209
-sleep flag honours durations with a unit suffix (e.g. 1m for 1 minute, 10s for
204-
10 seconds, etc).
210+
10 seconds, etc). Using -sleep=0 will disable the sleep and send at full speed.
205211
206212
Examples:
207213
Send an email with subject "Hello!" from the email "hello@goodbye.com" with
208-
the contents read from "test_msg_body.txt" to every email listed in
209-
"test_msg_recipients.txt", sleeping 10 seconds between each message:
214+
the contents read from "test_msg_body.txt" to every email associated with the
215+
registration IDs listed in "test_reg_recipients.json", sleeping 10 seconds
216+
between each message:
210217
211-
notify-mailer -config test/config/notify-mailer.json
212-
-body cmd/notify-mailer/testdata/test_msg_body.txt -from hello@goodbye.com
213-
-toFile cmd/notify-mailer/testdata/test_msg_recipients.txt -subject "Hello!"
214-
-sleep 10s
218+
notify-mailer -config test/config/notify-mailer.json -body
219+
cmd/notify-mailer/testdata/test_msg_body.txt -from hello@goodbye.com
220+
-toFile cmd/notify-mailer/testdata/test_msg_recipients.json -subject "Hello!"
221+
-sleep 10s -dryRun=false
215222
216-
Do the same, but only to the first 100 recipients:
223+
Do the same, but only to the first 100 recipient IDs:
217224
218-
notify-mailer -config test/config/notify-mailer.json
219-
-body cmd/notify-mailer/testdata/test_msg_body.txt -from hello@goodbye.com
220-
-toFile cmd/notify-mailer/testdata/test_msg_recipients.txt -subject "Hello!"
221-
-sleep 10s -end 100
225+
notify-mailer -config test/config/notify-mailer.json
226+
-body cmd/notify-mailer/testdata/test_msg_body.txt -from hello@goodbye.com
227+
-toFile cmd/notify-mailer/testdata/test_msg_recipients.json -subject "Hello!"
228+
-sleep 10s -end 100 -dryRun=false
229+
230+
Send the message, but start at the 200th ID of the recipients file, ending after
231+
100 registration IDs, and as a dry-run:
222232
223-
Send the message, but start at line 200 of the recipients file, ending after
224-
100 recipients, and as a dry-run:
225233
notify-mailer -config test/config/notify-mailer.json
226234
-body cmd/notify-mailer/testdata/test_msg_body.txt -from hello@goodbye.com
227-
-toFile cmd/notify-mailer/testdata/test_msg_recipients.txt -subject "Hello!"
228-
-sleep 10s -start 200 -end 300 -dryRun
235+
-toFile cmd/notify-mailer/testdata/test_msg_recipients.json -subject "Hello!"
236+
-sleep 10s -start 200 -end 300 -dryRun=true
229237
230238
Required arguments:
231239
- body
@@ -237,7 +245,7 @@ Required arguments:
237245
func main() {
238246
from := flag.String("from", "", "From header for emails. Must be a bare email address.")
239247
subject := flag.String("subject", "", "Subject of emails")
240-
toFile := flag.String("toFile", "", "File containing a list of email addresses to send to, one per file.")
248+
toFile := flag.String("toFile", "", "File containing a JSON array of registration IDs to send to.")
241249
bodyFile := flag.String("body", "", "File containing the email body in plain text format.")
242250
dryRun := flag.Bool("dryRun", true, "Whether to do a dry run.")
243251
sleep := flag.Duration("sleep", 60*time.Second, "How long to sleep between emails.")

0 commit comments

Comments
 (0)