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

feat: Removes pyo3 and derives tokens directly in Rust #1513

Merged
merged 9 commits into from
Feb 12, 2024

Conversation

tarikeshaq
Copy link
Contributor

@tarikeshaq tarikeshaq commented Jan 17, 2024

Description

We were using pyo3 in two places to call into Python code:

  1. When verifying an FxA oauth access token, calling into PyFxA's verify_token function - to verify an oauth token, which would first try to validate the token directly using a JWK stored in our environment variables, or otherwise it would trigger a /v1/verify/ call on FxA's oauth server, asking FxA to verify for us
  2. When generating a new Token to be used against storage server, we called into tokenlib's make_token and get_derived_secret

This PR replaces both usages of pyo3 with Rust code implemented directly in the tokenserver.

Testing

I've tested a local firefox connected to FxA's stage, and my local environment with tokenserver and syncstorage and verified that:

  • Tokenserver can verify tokens properly if given the JWK's in its env variables
  • Tokenserver can send the network request properly to FxA when it cannot verify the token locally (think FxA rotated their JWKs)
  • Tokenserver can issue tokens, and those tokens can be used correctly for sync against syncstorage

My local firefox syncs successfully with the changes in this PR, however, Out of abundance of caution we should run end-to-end tests and deploy this first on a canary if we plan to take it

TODOs

  • Add unit tests for both verify and get_token_and_derived_secret
    • verify
    • token generation
  • Document the hidden policies here so they're not hidden, eg: how the tokenserver tokens are derived, and how it validates the FxA access token, this was documented in the code for now as doc strings on a public API

Issue(s)

Closes SYNC-3528.

Dockerfile Outdated Show resolved Hide resolved
Comment on lines +21 to +53
impl Crypto for CryptoImpl {
type Error = TokenserverError;
fn hkdf(&self, secret: &str, salt: Option<&[u8]>, info: &[u8]) -> Result<Vec<u8>, Self::Error> {
let hk = Hkdf::<Sha256>::new(salt, secret.as_bytes());
let mut okm = [0u8; SHA256_OUTPUT_LEN];
hk.expand(info, &mut okm)
.map_err(|_| TokenserverError::internal_error())?;
Ok(okm.to_vec())
}

fn hmac_sign(&self, key: &[u8], payload: &[u8]) -> Result<Vec<u8>, Self::Error> {
let mut mac: Hmac<Sha256> =
Hmac::new_from_slice(key).map_err(|_| TokenserverError::internal_error())?;
mac.update(payload);
Ok(mac.finalize().into_bytes().to_vec())
}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This will require a careful review since they're trying to do crypto!

tokenserver-auth/src/lib.rs Outdated Show resolved Hide resolved
tokenserver-auth/src/oauth.rs Outdated Show resolved Hide resolved
Cargo.toml Outdated Show resolved Hide resolved
Cargo.lock Outdated Show resolved Hide resolved
@tarikeshaq tarikeshaq force-pushed the remove-pyo3 branch 3 times, most recently from e1ed400 to bc2c3cd Compare January 18, 2024 02:44
@tarikeshaq tarikeshaq force-pushed the remove-pyo3 branch 4 times, most recently from 60d6409 to f48c0e1 Compare January 18, 2024 18:12
@tarikeshaq
Copy link
Contributor Author

Alrighty this should be in a good stage for a first-round review

@jrconlin @pjenvey I know there is a lot going in the autopush world, so no rush getting to this - it is not urgent and I wouldn't want to interrupt your flow over there but I'll keep my eye on the patch for when ya'll get the time

tokenserver-auth/src/lib.rs Outdated Show resolved Hide resolved
tokenserver-auth/src/oauth.rs Outdated Show resolved Hide resolved
tokenserver-auth/src/lib.rs Outdated Show resolved Hide resolved
tokenserver-auth/src/lib.rs Outdated Show resolved Hide resolved
tokenserver-auth/src/crypto.rs Show resolved Hide resolved
@tarikeshaq tarikeshaq force-pushed the remove-pyo3 branch 2 times, most recently from 3e5f129 to 025c2f2 Compare January 25, 2024 03:15
tokenserver-auth/src/oauth.rs Outdated Show resolved Hide resolved
tokenserver-auth/src/crypto.rs Show resolved Hide resolved
Cargo.lock Outdated Show resolved Hide resolved
Dockerfile Outdated Show resolved Hide resolved
tokenserver-auth/src/crypto.rs Show resolved Hide resolved
tokenserver-auth/src/lib.rs Outdated Show resolved Hide resolved
tokenserver-auth/src/lib.rs Outdated Show resolved Hide resolved
tokenserver-auth/src/lib.rs Outdated Show resolved Hide resolved
tokenserver-auth/src/oauth.rs Outdated Show resolved Hide resolved
tokenserver-auth/src/oauth.rs Outdated Show resolved Hide resolved
tokenserver-auth/src/oauth.rs Outdated Show resolved Hide resolved
tokenserver-auth/src/oauth.rs Outdated Show resolved Hide resolved
Copy link
Member

@jrconlin jrconlin left a comment

Choose a reason for hiding this comment

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

Still going through the tests for oauth.rs Done. Nothing looks out of sorts.

@ethowitz
Copy link
Contributor

Just stopping by to say this is awesome and I'm glad it's happening 🎉

@tarikeshaq
Copy link
Contributor Author

This is why I love working in the open - Great to see you @ethowitz!!

tokenserver-auth/src/crypto.rs Show resolved Hide resolved

impl Crypto for CryptoImpl {
type Error = TokenserverError;
fn hkdf(&self, secret: &str, salt: Option<&[u8]>, info: &[u8]) -> Result<Vec<u8>, Self::Error> {
Copy link
Member

Choose a reason for hiding this comment

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

Equivalent function for hawk crate takes only a single key vec, but specifies the hash algo (e.g. Key::new(vec![99u8; 32], SHA256) (where the vec! would contain a real key and not a dummy value like that.) It's worth noting that we are using a salt where the hawk crate appears not to?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah the difference is the hawk function we're looking at is generating the Hmac key, which happens (in hmac_sign for us, and unlike hawk we hard-code the algorithm to be sha256, although we could expand the API if the use case comes!)

We end up using a salt here in the HKDF for the per-token secret, using the salt that is randomly generated and inserted into the token. That all said, a lot of this is out of my comfort zone and I tried to keep it true to what the python was doing and not re-invent anything

tokenserver-auth/src/lib.rs Outdated Show resolved Hide resolved
tokenserver-auth/src/oauth.rs Outdated Show resolved Hide resolved
@jrconlin
Copy link
Member

jrconlin commented Feb 7, 2024

Part of me would love to put the python replacement stuff behind a feature flag, maybe break the functions out of tokenserver-auth/lib.rs and into either lib-py.rs or lib-rs.rs (same with oauth.rs) and bring them in via a flag? That would let us land this code but keep it out of production use while it gets reviewed.

Fortunately a LOT of this PR is general cleanup.

@tarikeshaq tarikeshaq force-pushed the remove-pyo3 branch 2 times, most recently from d41ece5 to 36eea7f Compare February 8, 2024 18:50
@tarikeshaq
Copy link
Contributor Author

Thank you @jrconlin!! I like the suggestion of using rust features for this so it's safer (also easier to rollback and canary, etc etc)

I went ahead and moved the python implementation behind a rust feature, and removing that feature will have the Rust implementation

@tarikeshaq
Copy link
Contributor Author

(the ci failures are unrelated and should be fixed in #1518 )

jrconlin
jrconlin previously approved these changes Feb 9, 2024
Copy link
Member

@jrconlin jrconlin left a comment

Choose a reason for hiding this comment

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

Oh, awesome! You were able to break the python bit into it's own feature.

@tarikeshaq tarikeshaq merged commit 1b11684 into mozilla-services:master Feb 12, 2024
8 checks passed
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.

3 participants