Skip to content

Send SSH key expiration notification emails for all expired keys

Max Woolf requested to merge 331423-ssh-expiration-past-expiry into master

What does this MR do?

In %13.11 we introduced "SSH key expiration notification" emails. However, these emails were only sent to the holders of keys that expired "today". That meant holders of keys that expired in the past would never receive an email.

This MR will cause SshKeys::ExpiredNotificationWorker to send, on its next run, an email for every expired SSH key for which no email has already been sent. (The "today" logic has been available for a few weeks, so it's possible that an email has already been sent for some keys.) Because only one email is ever sent per key, so this change will only cause one abnormally large batch of emails. In SaaS it will be approximately 37,361 extra emails.

Specifically, this MR:

  • Renames the Key.expired_today_and_not_notified scope to Key.expired_and_not_notified.
  • Alters the scope to return keys that have expired anytime in the past including today.
  • Updates associated specs.

Database review

Query 1

SELECT
  "keys"."id"
FROM
  "keys"
WHERE
  (
    date(expires_at AT TIME ZONE 'UTC') BETWEEN '2000-01-01'
    AND CURRENT_DATE
    AND expiry_notification_delivered_at IS NULL
  )
  AND "keys"."id" >= 14007
ORDER BY
  "keys"."id" ASC
LIMIT
  1 OFFSET 1000

Query Plan

 Limit  (cost=34566.97..34566.97 rows=1 width=4) (actual time=62.436..62.439 rows=1 loops=1)
   Buffers: shared hit=37640
   I/O Timings: read=0.000 write=0.000
   ->  Sort  (cost=34564.47..34620.83 rows=22543 width=4) (actual time=62.241..62.383 rows=1001 loops=1)
         Sort Key: keys.id
         Sort Method: top-N heapsort  Memory: 111kB
         Buffers: shared hit=37640
         I/O Timings: read=0.000 write=0.000
         ->  Index Scan using index_keys_on_expires_at_and_expiry_notification_undelivered on public.keys  (cost=0.43..33328.30 rows=22543 width=4) (actual time=0.031..55.237 rows=37644 loops=1)
               Index Cond: ((date(timezone('UTC'::text, keys.expires_at)) >= '2000-01-01'::date) AND (date(timezone('UTC'::text, keys.expires_at)) <= CURRENT_DATE))
               Filter: (keys.id >= 14007)
               Rows Removed by Filter: 0
               Buffers: shared hit=37637
               I/O Timings: read=0.000 write=0.000
Time: 62.663 ms  
  - planning: 0.193 ms  
  - execution: 62.470 ms  
    - I/O read: 0.000 ms  
    - I/O write: 0.000 ms  
  
Shared buffers:  
  - hits: 37640 (~294.10 MiB) from the buffer pool  
  - reads: 0 from the OS file cache, including disk I/O  
  - dirtied: 0  
  - writes: 0  

Query 2

SELECT
  "users".*
FROM
  "users"
WHERE
  "users"."id" IN (
    SELECT
      "keys"."user_id"
    FROM
      "keys"
    WHERE
      (
        date(expires_at AT TIME ZONE 'UTC') BETWEEN '2000-01-01'
        AND CURRENT_DATE
        AND expiry_notification_delivered_at IS NULL
      )
      AND "keys"."id" >= 14007
      AND "keys"."id" < 15007
  )

Query Plan

 Nested Loop  (cost=338.51..344.99 rows=2 width=1316) (actual time=32.461..32.467 rows=0 loops=1)
   Buffers: shared hit=203 read=2
   I/O Timings: read=13.979 write=0.000
   ->  Unique  (cost=338.08..338.09 rows=2 width=4) (actual time=32.460..32.465 rows=0 loops=1)
         Buffers: shared hit=203 read=2
         I/O Timings: read=13.979 write=0.000
         ->  Sort  (cost=338.08..338.08 rows=2 width=4) (actual time=32.459..32.464 rows=0 loops=1)
               Sort Key: keys.user_id
               Sort Method: quicksort  Memory: 25kB
               Buffers: shared hit=203 read=2
               I/O Timings: read=13.979 write=0.000
               ->  Bitmap Heap Scan on public.keys  (cost=334.99..338.07 rows=2 width=4) (actual time=32.434..32.438 rows=0 loops=1)
                     Buffers: shared hit=200 read=2
                     I/O Timings: read=13.979 write=0.000
                     ->  BitmapAnd  (cost=334.99..334.99 rows=2 width=0) (actual time=32.426..32.429 rows=0 loops=1)
                           Buffers: shared hit=200 read=2
                           I/O Timings: read=13.979 write=0.000
                           ->  Bitmap Index Scan using keys_pkey  (cost=0.00..8.03 rows=460 width=0) (actual time=14.183..14.183 rows=479 loops=1)
                                 Index Cond: ((keys.id >= 14007) AND (keys.id < 15007))
                                 Buffers: shared hit=3 read=2
                                 I/O Timings: read=13.979 write=0.000
                           ->  Bitmap Index Scan using index_keys_on_expires_at_and_expiry_notification_undelivered  (cost=0.00..326.71 rows=22577 width=0) (actual time=16.411..16.411 rows=37644 loops=1)
                                 Index Cond: ((date(timezone('UTC'::text, keys.expires_at)) >= '2000-01-01'::date) AND (date(timezone('UTC'::text, keys.expires_at)) <= CURRENT_DATE))
                                 Buffers: shared hit=197
                                 I/O Timings: read=0.000 write=0.000
   ->  Index Scan using users_pkey on public.users  (cost=0.43..3.45 rows=1 width=1316) (actual time=0.000..0.000 rows=0 loops=0)
         Index Cond: (users.id = keys.user_id)
         I/O Timings: read=0.000 write=0.000
Time: 47.208 ms  
  - planning: 14.576 ms  
  - execution: 32.632 ms  
    - I/O read: 13.979 ms  
    - I/O write: 0.000 ms  
  
Shared buffers:  
  - hits: 203 (~1.60 MiB) from the buffer pool  
  - reads: 2 (~16.00 KiB) from the OS file cache, including disk I/O  
  - dirtied: 0  
  - writes: 0  

Screenshots (strongly suggested)

Does this MR meet the acceptance criteria?

Conformity

Availability and Testing

Security

Does this MR contain changes to processing or storing of credentials or tokens, authorization and authentication methods or other items described in the security review guidelines? If not, then delete this Security section.

  • Label as security and @ mention @gitlab-com/gl-security/appsec
  • The MR includes necessary changes to maintain consistency between UI, API, email, or other methods
  • Security reports checked/validated by a reviewer from the AppSec team

Related to #331423 (closed)

Edited by Max Woolf

Merge request reports