Skip to content

Partial KMS session encryption#6315

Merged
mitchellhenke merged 14 commits intomainfrom
mitchellhenke/kms-but-just-a-bit
May 16, 2022
Merged

Partial KMS session encryption#6315
mitchellhenke merged 14 commits intomainfrom
mitchellhenke/kms-but-just-a-bit

Conversation

@mitchellhenke
Copy link
Contributor

@mitchellhenke mitchellhenke commented May 5, 2022

Currently, we double encrypt the entire web session for a user. When saving a session, first it is converted to JSON, then encrypted with AES, then with KMS, and then put into Redis. The reverse process happens when the session is loaded.

This safe-by-default method is straightforward conceptually and in implementation, but imposes costs and friction in other ways. We have added more and more to session storage, and KMS can only encrypt 4kB per API call, which leads to us chunking the string and having to make multiple calls to KMS. Certain values we store can be quite large, which inflates the session size significantly in some contexts. The KMS API calls now take up a sizeable portion of our network and server time.

This method also places KMS in the critical path since it is used on most requests, including the home page.

From a security perspective, it means we decrypt sensitive data even when it won't be used. The most relevant part in this regard is the PII bundle, which we only need to KMS decrypt when we are displaying it to the user or passing it to the service provider.

To meet our security requirements and ensure that sensitive data continues to be KMS encrypted while vastly reducing our KMS usage otherwise, this PR attempts to split the session into sensitive and less sensitive data. The entire session is still AES encrypted, but the sensitive parts are KMS encrypted still. These changes aim to handle PII in two ways:

  1. For the PII bundle, it is decrypted with the user's password when we receive it and then it is stored in the session. Subsequent requests encrypt it with KMS inside the AES encrypted session. It is now KMS decrypted on-demand via the Pii::Cacher interface that we've moved our PII bundle handling towards using (Create common interface for accessing PII bundle in session #6054, Do not save additional PII to session when not needed #6072, Move more PII bundle usage to the common interface in Pii::Cacher #6273, Rename reactivate account session :personal_key key to be more descriptive #6283).
  2. For personal keys and IDV-related PII where the usage is a bit more dynamic and harder to standardize, the KMS encryption/decryption happens transparently similar to how it is today.

Alerting has been added if we detect a potentially sensitive key not being KMS encrypted as it should be, and sends the session structure without values to help troubleshoot if it does. In tests and development, it will raise exceptions if a sensitive key is not encrypted properly.

This change is not backwards compatible on its own, and will require deploying the code that contains the ability to parse both formats (without writing the new format immediately). Writing the new format to Redis is currently behind a configuration flag that we can switch on if and when we feel comfortable. If the new format is problematic after being enabled, the switch can be turned off with full backwards compatibility.

Document
Discussion Thread

@mitchellhenke mitchellhenke changed the title KMS KMS session encryption (WIP) May 5, 2022
@mitchellhenke mitchellhenke force-pushed the mitchellhenke/kms-but-just-a-bit branch from 8b6c0f7 to 0b88575 Compare May 5, 2022 21:56
@mitchellhenke mitchellhenke marked this pull request as ready for review May 5, 2022 23:16
@mitchellhenke mitchellhenke requested a review from jmhooper May 5, 2022 23:16
@mitchellhenke mitchellhenke force-pushed the mitchellhenke/kms-but-just-a-bit branch from 0b88575 to 2440bcb Compare May 6, 2022 13:35
@mitchellhenke mitchellhenke changed the title KMS session encryption (WIP) KMS session encryption May 6, 2022
@mitchellhenke mitchellhenke changed the title KMS session encryption Partial KMS session encryption May 6, 2022
end

def fetch_string
user_session[:decrypted_pii]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you help me understanding why the logic below is necessary? It looks like SessionEncryptor#kms_encrypt_pii! should prevent user_session[:encrypted_pii] from being an issue.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Slacked with Mitchell and see where I got hung up. The session encryptor encrypts the user_session[:encrypted_pii] value, but never actually decrypts it. It leaves this to the session encryptor so we don't need to run KMS on every request for a proofed user.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Attempted to clarify this better in cfea392

@mitchellhenke mitchellhenke force-pushed the mitchellhenke/kms-but-just-a-bit branch 2 times, most recently from 7b62800 to edc6e4a Compare May 6, 2022 20:32
@mitchellhenke mitchellhenke force-pushed the mitchellhenke/kms-but-just-a-bit branch from d71abf0 to 897da44 Compare May 6, 2022 21:14
Copy link
Contributor

@orenyk orenyk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I got a little lost in the weeds in the actual encryption stuff (hoping to take a closer look later) but in the meantime here are some minor style comments/suggestions. Great work!

@mitchellhenke mitchellhenke force-pushed the mitchellhenke/kms-but-just-a-bit branch from fd55bf8 to dfcb52c Compare May 9, 2022 14:11
@mitchellhenke mitchellhenke force-pushed the mitchellhenke/kms-but-just-a-bit branch 4 times, most recently from 7da78b8 to 94421de Compare May 13, 2022 17:16
Comment on lines 26 to 28
# rubocop:disable Layout/LineLength
SENSITIVE_REGEX = %r{FAKEY|MIDDLEFAKER|MCFAKERSON|1111111111111|GREAT FALLS|1938-10-06|2099-12-31|314-555-1212}
# rubocop:enable Layout/LineLength
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it worth doing something similar to what was done in FakeAnalytics, referencing the constant from which these values are generated?

DocAuth::Mock::ResultResponseBuilder::DEFAULT_PII_FROM_DOC.slice(
:first_name,
:last_name,
:address1,
:zipcode,
:dob,
:state_id_number,
).each do |key, default_pii_value|
if string_payload.match?(Regexp.new('\b' + Regexp.quote(default_pii_value) + '\b', 'i'))

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you that's a great catch, I had forgotten we had them in a constant.

SessionEncryptor being in lib/ means it can't reference things outside of lib/ so I moved it into our constants file in e11e91946, but I'm not sure if that's the best place

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When I was using it for spec/factories/profiles.rb in #6229, I had felt it was kinda weird to reference the constant where it was at, so I like the idea of having a single constant of fake PII in a common location like that.

@mitchellhenke mitchellhenke force-pushed the mitchellhenke/kms-but-just-a-bit branch 3 times, most recently from 23c5c6d to 7e8361f Compare May 16, 2022 13:13
Mitchell Henke and others added 2 commits May 16, 2022 08:13
…ndle when needed

changelog: Internal, Security, Only decrypt PII bundle when needed and limit usage of KMS encryption to needed use cases

Co-authored-by: Jonathan Hooper <jonathan.hooper@gsa.gov>
Mitchell Henke and others added 11 commits May 16, 2022 08:13
Co-authored-by: Zach Margolis <zachmargolis@users.noreply.github.com>
Co-authored-by: Zach Margolis <zachmargolis@users.noreply.github.com>
Co-authored-by: Oren Kanner <oren.kanner@gsa.gov>
@mitchellhenke mitchellhenke force-pushed the mitchellhenke/kms-but-just-a-bit branch from 7e8361f to 9cda15d Compare May 16, 2022 13:13
@mitchellhenke mitchellhenke force-pushed the mitchellhenke/kms-but-just-a-bit branch from 9cda15d to bba056d Compare May 16, 2022 15:02
@mitchellhenke mitchellhenke merged commit 48d8568 into main May 16, 2022
@mitchellhenke mitchellhenke deleted the mitchellhenke/kms-but-just-a-bit branch May 16, 2022 15:37
jmhooper added a commit that referenced this pull request Dec 14, 2023
In the past the IDP used the `Encryption::Encryptors::SessionEncryptor` to encrypt sessions as a whole. This tool was used by the unfortunately named `SessionEncryptor` which acts as a serializer for the session store. The `Encryption::Encryptors::SessionEncryptor` was also used for encrypting PII temporarily while it was queued for letter sending.

Two changes led to `Encryption::Encryptors::SessionEncryptor` being unused:

- #6315 enabled partial session encryption which made the session encryptor sophisticated enough that it justified its own logic for encrypting elements instead of depending on `Encryption::Encryptors::SessionEncryptor`
- #6211 replaced the `Encryption::Encryptors::SessionEncryptor` that was used for encrypting letter PII with a new encryptor built specifically for encrypting background arguments

With these changes the `Encryption::Encryptors::SessionEncryptor` no longer has a caller. This commit removes it.

[skip changelog]
jmhooper added a commit that referenced this pull request Dec 14, 2023
In the past the IDP used the `Encryption::Encryptors::SessionEncryptor` to encrypt sessions as a whole. This tool was used by the unfortunately named `SessionEncryptor` which acts as a serializer for the session store. The `Encryption::Encryptors::SessionEncryptor` was also used for encrypting PII temporarily while it was queued for letter sending.

Two changes led to `Encryption::Encryptors::SessionEncryptor` being unused:

- #6315 enabled partial session encryption which made the session encryptor sophisticated enough that it justified its own logic for encrypting elements instead of depending on `Encryption::Encryptors::SessionEncryptor`
- #6211 replaced the `Encryption::Encryptors::SessionEncryptor` that was used for encrypting letter PII with a new encryptor built specifically for encrypting background arguments

With these changes the `Encryption::Encryptors::SessionEncryptor` no longer has a caller. This commit removes it.

[skip changelog]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants