Skip to content

Commit

Permalink
feat: support multiple FxA JWKs to ease key rotation (#1339)
Browse files Browse the repository at this point in the history
  • Loading branch information
ethowitz authored Jun 22, 2022
1 parent f76b5fc commit eba3566
Show file tree
Hide file tree
Showing 5 changed files with 59 additions and 37 deletions.
14 changes: 7 additions & 7 deletions docker-compose.e2e.mysql.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,13 @@ services:
SYNC_TOKENSERVER__FXA_EMAIL_DOMAIN: api-accounts.stage.mozaws.net
SYNC_TOKENSERVER__FXA_METRICS_HASH_SECRET: secret0
SYNC_TOKENSERVER__RUN_MIGRATIONS: "true"
SYNC_TOKENSERVER__FXA_OAUTH_JWK__KTY: "RSA"
SYNC_TOKENSERVER__FXA_OAUTH_JWK__ALG: "RS256"
SYNC_TOKENSERVER__FXA_OAUTH_JWK__KID: "20190730-15e473fd"
SYNC_TOKENSERVER__FXA_OAUTH_JWK__FXA_CREATED_AT: "1564502400"
SYNC_TOKENSERVER__FXA_OAUTH_JWK__USE: "sig"
SYNC_TOKENSERVER__FXA_OAUTH_JWK__N: "15OpVGC7ws_SlU0gRbRh1Iwo8_gR8ElX2CDnbN5blKyXLg-ll0ogktoDXc-tDvTabRTxi7AXU0wWQ247odhHT47y5uz0GASYXdfPponynQ_xR9CpNn1eEL1gvDhQN9rfPIzfncl8FUi9V4WMd5f600QC81yDw9dX-Z8gdkru0aDaoEKF9-wU2TqrCNcQdiJCX9BISotjz_9cmGwKXFEekQNJWBeRQxH2bUmgwUK0HaqwW9WbYOs-zstNXXWFsgK9fbDQqQeGehXLZM4Cy5Mgl_iuSvnT3rLzPo2BmlxMLUvRqBx3_v8BTtwmNGA0v9O0FJS_mnDq0Iue0Dz8BssQCQ"
SYNC_TOKENSERVER__FXA_OAUTH_JWK__E: "AQAB"
SYNC_TOKENSERVER__FXA_OAUTH_PRIMARY_JWK__KTY: "RSA"
SYNC_TOKENSERVER__FXA_OAUTH_PRIMARY_JWK__ALG: "RS256"
SYNC_TOKENSERVER__FXA_OAUTH_PRIMARY_JWK__KID: "20190730-15e473fd"
SYNC_TOKENSERVER__FXA_OAUTH_PRIMARY_JWK__FXA_CREATED_AT: "1564502400"
SYNC_TOKENSERVER__FXA_OAUTH_PRIMARY_JWK__USE: "sig"
SYNC_TOKENSERVER__FXA_OAUTH_PRIMARY_JWK__N: "15OpVGC7ws_SlU0gRbRh1Iwo8_gR8ElX2CDnbN5blKyXLg-ll0ogktoDXc-tDvTabRTxi7AXU0wWQ247odhHT47y5uz0GASYXdfPponynQ_xR9CpNn1eEL1gvDhQN9rfPIzfncl8FUi9V4WMd5f600QC81yDw9dX-Z8gdkru0aDaoEKF9-wU2TqrCNcQdiJCX9BISotjz_9cmGwKXFEekQNJWBeRQxH2bUmgwUK0HaqwW9WbYOs-zstNXXWFsgK9fbDQqQeGehXLZM4Cy5Mgl_iuSvnT3rLzPo2BmlxMLUvRqBx3_v8BTtwmNGA0v9O0FJS_mnDq0Iue0Dz8BssQCQ"
SYNC_TOKENSERVER__FXA_OAUTH_PRIMARY_JWK__E: "AQAB"
TOKENSERVER_HOST: http://localhost:8000
entrypoint: >
/bin/sh -c "
Expand Down
14 changes: 7 additions & 7 deletions docker-compose.e2e.spanner.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,13 @@ services:
SYNC_TOKENSERVER__FXA_EMAIL_DOMAIN: api-accounts.stage.mozaws.net
SYNC_TOKENSERVER__FXA_METRICS_HASH_SECRET: secret0
SYNC_TOKENSERVER__RUN_MIGRATIONS: "true"
SYNC_TOKENSERVER__FXA_OAUTH_JWK__KTY: "RSA"
SYNC_TOKENSERVER__FXA_OAUTH_JWK__ALG: "RS256"
SYNC_TOKENSERVER__FXA_OAUTH_JWK__KID: "20190730-15e473fd"
SYNC_TOKENSERVER__FXA_OAUTH_JWK__FXA_CREATED_AT: "1564502400"
SYNC_TOKENSERVER__FXA_OAUTH_JWK__USE: "sig"
SYNC_TOKENSERVER__FXA_OAUTH_JWK__N: "15OpVGC7ws_SlU0gRbRh1Iwo8_gR8ElX2CDnbN5blKyXLg-ll0ogktoDXc-tDvTabRTxi7AXU0wWQ247odhHT47y5uz0GASYXdfPponynQ_xR9CpNn1eEL1gvDhQN9rfPIzfncl8FUi9V4WMd5f600QC81yDw9dX-Z8gdkru0aDaoEKF9-wU2TqrCNcQdiJCX9BISotjz_9cmGwKXFEekQNJWBeRQxH2bUmgwUK0HaqwW9WbYOs-zstNXXWFsgK9fbDQqQeGehXLZM4Cy5Mgl_iuSvnT3rLzPo2BmlxMLUvRqBx3_v8BTtwmNGA0v9O0FJS_mnDq0Iue0Dz8BssQCQ"
SYNC_TOKENSERVER__FXA_OAUTH_JWK__E: "AQAB"
SYNC_TOKENSERVER__FXA_OAUTH_PRIMARY_JWK__KTY: "RSA"
SYNC_TOKENSERVER__FXA_OAUTH_PRIMARY_JWK__ALG: "RS256"
SYNC_TOKENSERVER__FXA_OAUTH_PRIMARY_JWK__KID: "20190730-15e473fd"
SYNC_TOKENSERVER__FXA_OAUTH_PRIMARY_JWK__FXA_CREATED_AT: "1564502400"
SYNC_TOKENSERVER__FXA_OAUTH_PRIMARY_JWK__USE: "sig"
SYNC_TOKENSERVER__FXA_OAUTH_PRIMARY_JWK__N: "15OpVGC7ws_SlU0gRbRh1Iwo8_gR8ElX2CDnbN5blKyXLg-ll0ogktoDXc-tDvTabRTxi7AXU0wWQ247odhHT47y5uz0GASYXdfPponynQ_xR9CpNn1eEL1gvDhQN9rfPIzfncl8FUi9V4WMd5f600QC81yDw9dX-Z8gdkru0aDaoEKF9-wU2TqrCNcQdiJCX9BISotjz_9cmGwKXFEekQNJWBeRQxH2bUmgwUK0HaqwW9WbYOs-zstNXXWFsgK9fbDQqQeGehXLZM4Cy5Mgl_iuSvnT3rLzPo2BmlxMLUvRqBx3_v8BTtwmNGA0v9O0FJS_mnDq0Iue0Dz8BssQCQ"
SYNC_TOKENSERVER__FXA_OAUTH_PRIMARY_JWK__E: "AQAB"
TOKENSERVER_HOST: http://localhost:8000
entrypoint: >
/bin/sh -c "
Expand Down
11 changes: 11 additions & 0 deletions syncstorage/src/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,17 @@ impl Settings {
"https://oauth.stage.mozaws.net",
)?;
s.set_default("tokenserver.fxa_oauth_request_timeout", 10)?;

// The type parameter for None::<bool> below would more appropriately be `Jwk`, but due
// to constraints imposed by version 0.11 of the config crate, it is not possible to
// implement `ValueKind: From<Jwk>`. The next best thing would be to use `ValueKind`,
// but `ValueKind` is private in this version of config. We use `bool` as a placeholder,
// since `ValueKind: From<bool>` is implemented, and None::<T> for all T is simply
// converted to ValueKind::Nil (see below link).
// https://github.com/mehcode/config-rs/blob/0.11.0/src/value.rs#L35
s.set_default("tokenserver.fxa_oauth_primary_jwk", None::<bool>)?;
s.set_default("tokenserver.fxa_oauth_secondary_jwk", None::<bool>)?;

s.set_default("tokenserver.node_type", "spanner")?;
s.set_default("tokenserver.statsd_label", "syncstorage.tokenserver")?;
s.set_default("tokenserver.run_migrations", cfg!(test))?;
Expand Down
49 changes: 28 additions & 21 deletions syncstorage/src/tokenserver/auth/oauth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,14 @@ use futures::TryFutureExt;
use pyo3::{
prelude::{Py, PyAny, PyErr, PyModule, Python},
types::{IntoPyDict, PyString},
IntoPy,
};
use serde::{Deserialize, Serialize};
use serde_json;
use tokenserver_common::error::TokenserverError;
use tokio::{task, time};

use super::VerifyToken;
use crate::tokenserver::settings::Settings;
use crate::tokenserver::settings::{Jwk, Settings};

use core::time::Duration;
use std::convert::TryFrom;
Expand Down Expand Up @@ -47,24 +46,31 @@ impl TryFrom<&Settings> for Verifier {
let module = PyModule::from_code(py, code, Self::FILENAME, Self::FILENAME)?;
let kwargs = {
let dict = [("server_url", &settings.fxa_oauth_server_url)].into_py_dict(py);
let jwks = settings
.fxa_oauth_jwk
.as_ref()
.map(|jwk| {
let dict = [
("kty", &jwk.kty),
("alg", &jwk.alg),
("kid", &jwk.kid),
("use", &jwk.use_of_key),
("n", &jwk.n),
("e", &jwk.e),
]
.into_py_dict(py);
dict.set_item("fxa-createdAt", jwk.fxa_created_at).unwrap();

[dict]
})
.into_py(py);
let parse_jwk = |jwk: &Jwk| {
let dict = [
("kty", &jwk.kty),
("alg", &jwk.alg),
("kid", &jwk.kid),
("use", &jwk.use_of_key),
("n", &jwk.n),
("e", &jwk.e),
]
.into_py_dict(py);
dict.set_item("fxa-createdAt", jwk.fxa_created_at).unwrap();

dict
};

let jwks = match (
&settings.fxa_oauth_primary_jwk,
&settings.fxa_oauth_secondary_jwk,
) {
(Some(primary_jwk), Some(secondary_jwk)) => {
Some(vec![parse_jwk(primary_jwk), parse_jwk(secondary_jwk)])
}
(Some(jwk), None) | (None, Some(jwk)) => Some(vec![parse_jwk(jwk)]),
(None, None) => None,
};
dict.set_item("jwks", jwks).unwrap();
dict
};
Expand All @@ -84,7 +90,8 @@ impl TryFrom<&Settings> for Verifier {
Ok(Self {
inner,
timeout: settings.fxa_oauth_request_timeout,
jwk_is_cached: settings.fxa_oauth_jwk.is_some(),
jwk_is_cached: settings.fxa_oauth_primary_jwk.is_some()
|| settings.fxa_oauth_secondary_jwk.is_some(),
})
}
}
Expand Down
8 changes: 6 additions & 2 deletions syncstorage/src/tokenserver/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,10 @@ pub struct Settings {
/// The JWK to be used to verify OAuth tokens. Passing a JWK to the PyFxA Python library
/// prevents it from making an external API call to FxA to get the JWK, yielding substantial
/// performance benefits.
pub fxa_oauth_jwk: Option<Jwk>,
pub fxa_oauth_primary_jwk: Option<Jwk>,
/// A secondary JWK to be used to verify OAuth tokens. This is intended to be used to enable
/// seamless key rotations on FxA.
pub fxa_oauth_secondary_jwk: Option<Jwk>,
/// The issuer expected in the BrowserID verification response.
pub fxa_browserid_issuer: String,
/// The audience to be sent to the FxA BrowserID verification server.
Expand Down Expand Up @@ -80,7 +83,8 @@ impl Default for Settings {
fxa_metrics_hash_secret: "secret".to_owned(),
fxa_oauth_server_url: "https://oauth.stage.mozaws.net".to_owned(),
fxa_oauth_request_timeout: 10,
fxa_oauth_jwk: None,
fxa_oauth_primary_jwk: None,
fxa_oauth_secondary_jwk: None,
fxa_browserid_audience: "https://token.stage.mozaws.net".to_owned(),
fxa_browserid_issuer: "api-accounts.stage.mozaws.net".to_owned(),
fxa_browserid_server_url: "https://verifier.stage.mozaws.net/v2".to_owned(),
Expand Down

0 comments on commit eba3566

Please sign in to comment.