Line data Source code
1 : use std::{
2 : future::Future,
3 : sync::Arc,
4 : time::{Duration, SystemTime},
5 : };
6 :
7 : use anyhow::{bail, ensure, Context};
8 : use arc_swap::ArcSwapOption;
9 : use dashmap::DashMap;
10 : use jose_jwk::crypto::KeyInfo;
11 : use serde::{Deserialize, Deserializer};
12 : use signature::Verifier;
13 : use tokio::time::Instant;
14 :
15 : use crate::{context::RequestMonitoring, http::parse_json_body_with_limit, EndpointId, RoleName};
16 :
17 : // TODO(conrad): make these configurable.
18 : const CLOCK_SKEW_LEEWAY: Duration = Duration::from_secs(30);
19 : const MIN_RENEW: Duration = Duration::from_secs(30);
20 : const AUTO_RENEW: Duration = Duration::from_secs(300);
21 : const MAX_RENEW: Duration = Duration::from_secs(3600);
22 : const MAX_JWK_BODY_SIZE: usize = 64 * 1024;
23 :
24 : /// How to get the JWT auth rules
25 : pub(crate) trait FetchAuthRules: Clone + Send + Sync + 'static {
26 : fn fetch_auth_rules(
27 : &self,
28 : role_name: RoleName,
29 : ) -> impl Future<Output = anyhow::Result<Vec<AuthRule>>> + Send;
30 : }
31 :
32 : pub(crate) struct AuthRule {
33 : pub(crate) id: String,
34 : pub(crate) jwks_url: url::Url,
35 : pub(crate) audience: Option<String>,
36 : }
37 :
38 : #[derive(Default)]
39 : pub(crate) struct JwkCache {
40 : client: reqwest::Client,
41 :
42 : map: DashMap<(EndpointId, RoleName), Arc<JwkCacheEntryLock>>,
43 : }
44 :
45 : pub(crate) struct JwkCacheEntry {
46 : /// Should refetch at least every hour to verify when old keys have been removed.
47 : /// Should refetch when new key IDs are seen only every 5 minutes or so
48 : last_retrieved: Instant,
49 :
50 : /// cplane will return multiple JWKs urls that we need to scrape.
51 : key_sets: ahash::HashMap<String, KeySet>,
52 : }
53 :
54 : impl JwkCacheEntry {
55 4 : fn find_jwk_and_audience(&self, key_id: &str) -> Option<(&jose_jwk::Jwk, Option<&str>)> {
56 6 : self.key_sets.values().find_map(|key_set| {
57 6 : key_set
58 6 : .find_key(key_id)
59 6 : .map(|jwk| (jwk, key_set.audience.as_deref()))
60 6 : })
61 4 : }
62 : }
63 :
64 : struct KeySet {
65 : jwks: jose_jwk::JwkSet,
66 : audience: Option<String>,
67 : }
68 :
69 : impl KeySet {
70 6 : fn find_key(&self, key_id: &str) -> Option<&jose_jwk::Jwk> {
71 6 : self.jwks
72 6 : .keys
73 6 : .iter()
74 10 : .find(|jwk| jwk.prm.kid.as_deref() == Some(key_id))
75 6 : }
76 : }
77 :
78 : pub(crate) struct JwkCacheEntryLock {
79 : cached: ArcSwapOption<JwkCacheEntry>,
80 : lookup: tokio::sync::Semaphore,
81 : }
82 :
83 : impl Default for JwkCacheEntryLock {
84 1 : fn default() -> Self {
85 1 : JwkCacheEntryLock {
86 1 : cached: ArcSwapOption::empty(),
87 1 : lookup: tokio::sync::Semaphore::new(1),
88 1 : }
89 1 : }
90 : }
91 :
92 : impl JwkCacheEntryLock {
93 1 : async fn acquire_permit<'a>(self: &'a Arc<Self>) -> JwkRenewalPermit<'a> {
94 1 : JwkRenewalPermit::acquire_permit(self).await
95 1 : }
96 :
97 0 : fn try_acquire_permit<'a>(self: &'a Arc<Self>) -> Option<JwkRenewalPermit<'a>> {
98 0 : JwkRenewalPermit::try_acquire_permit(self)
99 0 : }
100 :
101 1 : async fn renew_jwks<F: FetchAuthRules>(
102 1 : &self,
103 1 : _permit: JwkRenewalPermit<'_>,
104 1 : client: &reqwest::Client,
105 1 : role_name: RoleName,
106 1 : auth_rules: &F,
107 1 : ) -> anyhow::Result<Arc<JwkCacheEntry>> {
108 1 : // double check that no one beat us to updating the cache.
109 1 : let now = Instant::now();
110 1 : let guard = self.cached.load_full();
111 1 : if let Some(cached) = guard {
112 0 : let last_update = now.duration_since(cached.last_retrieved);
113 0 : if last_update < Duration::from_secs(300) {
114 0 : return Ok(cached);
115 0 : }
116 1 : }
117 :
118 1 : let rules = auth_rules.fetch_auth_rules(role_name).await?;
119 1 : let mut key_sets =
120 1 : ahash::HashMap::with_capacity_and_hasher(rules.len(), ahash::RandomState::new());
121 : // TODO(conrad): run concurrently
122 : // TODO(conrad): strip the JWKs urls (should be checked by cplane as well - cloud#16284)
123 3 : for rule in rules {
124 2 : let req = client.get(rule.jwks_url.clone());
125 2 : // TODO(conrad): eventually switch to using reqwest_middleware/`new_client_with_timeout`.
126 2 : // TODO(conrad): We need to filter out URLs that point to local resources. Public internet only.
127 4 : match req.send().await.and_then(|r| r.error_for_status()) {
128 : // todo: should we re-insert JWKs if we want to keep this JWKs URL?
129 : // I expect these failures would be quite sparse.
130 0 : Err(e) => tracing::warn!(url=?rule.jwks_url, error=?e, "could not fetch JWKs"),
131 2 : Ok(r) => {
132 2 : let resp: http::Response<reqwest::Body> = r.into();
133 2 : match parse_json_body_with_limit::<jose_jwk::JwkSet>(
134 2 : resp.into_body(),
135 2 : MAX_JWK_BODY_SIZE,
136 2 : )
137 0 : .await
138 : {
139 0 : Err(e) => {
140 0 : tracing::warn!(url=?rule.jwks_url, error=?e, "could not decode JWKs");
141 : }
142 2 : Ok(jwks) => {
143 2 : key_sets.insert(
144 2 : rule.id,
145 2 : KeySet {
146 2 : jwks,
147 2 : audience: rule.audience,
148 2 : },
149 2 : );
150 2 : }
151 : }
152 : }
153 : }
154 : }
155 :
156 1 : let entry = Arc::new(JwkCacheEntry {
157 1 : last_retrieved: now,
158 1 : key_sets,
159 1 : });
160 1 : self.cached.swap(Some(Arc::clone(&entry)));
161 1 :
162 1 : Ok(entry)
163 1 : }
164 :
165 4 : async fn get_or_update_jwk_cache<F: FetchAuthRules>(
166 4 : self: &Arc<Self>,
167 4 : ctx: &RequestMonitoring,
168 4 : client: &reqwest::Client,
169 4 : role_name: RoleName,
170 4 : fetch: &F,
171 4 : ) -> Result<Arc<JwkCacheEntry>, anyhow::Error> {
172 4 : let now = Instant::now();
173 4 : let guard = self.cached.load_full();
174 :
175 : // if we have no cached JWKs, try and get some
176 4 : let Some(cached) = guard else {
177 1 : let _paused = ctx.latency_timer_pause(crate::metrics::Waiting::Compute);
178 1 : let permit = self.acquire_permit().await;
179 4 : return self.renew_jwks(permit, client, role_name, fetch).await;
180 : };
181 :
182 3 : let last_update = now.duration_since(cached.last_retrieved);
183 3 :
184 3 : // check if the cached JWKs need updating.
185 3 : if last_update > MAX_RENEW {
186 0 : let _paused = ctx.latency_timer_pause(crate::metrics::Waiting::Compute);
187 0 : let permit = self.acquire_permit().await;
188 :
189 : // it's been too long since we checked the keys. wait for them to update.
190 0 : return self.renew_jwks(permit, client, role_name, fetch).await;
191 3 : }
192 3 :
193 3 : // every 5 minutes we should spawn a job to eagerly update the token.
194 3 : if last_update > AUTO_RENEW {
195 0 : if let Some(permit) = self.try_acquire_permit() {
196 0 : tracing::debug!("JWKs should be renewed. Renewal permit acquired");
197 0 : let permit = permit.into_owned();
198 0 : let entry = self.clone();
199 0 : let client = client.clone();
200 0 : let fetch = fetch.clone();
201 0 : tokio::spawn(async move {
202 0 : if let Err(e) = entry.renew_jwks(permit, &client, role_name, &fetch).await {
203 0 : tracing::warn!(error=?e, "could not fetch JWKs in background job");
204 0 : }
205 0 : });
206 0 : } else {
207 0 : tracing::debug!("JWKs should be renewed. Renewal permit already taken, skipping");
208 : }
209 3 : }
210 :
211 3 : Ok(cached)
212 4 : }
213 :
214 4 : async fn check_jwt<F: FetchAuthRules>(
215 4 : self: &Arc<Self>,
216 4 : ctx: &RequestMonitoring,
217 4 : jwt: &str,
218 4 : client: &reqwest::Client,
219 4 : role_name: RoleName,
220 4 : fetch: &F,
221 4 : ) -> Result<(), anyhow::Error> {
222 : // JWT compact form is defined to be
223 : // <B64(Header)> || . || <B64(Payload)> || . || <B64(Signature)>
224 : // where Signature = alg(<B64(Header)> || . || <B64(Payload)>);
225 :
226 4 : let (header_payload, signature) = jwt
227 4 : .rsplit_once('.')
228 4 : .context("Provided authentication token is not a valid JWT encoding")?;
229 4 : let (header, payload) = header_payload
230 4 : .split_once('.')
231 4 : .context("Provided authentication token is not a valid JWT encoding")?;
232 :
233 4 : let header = base64::decode_config(header, base64::URL_SAFE_NO_PAD)
234 4 : .context("Provided authentication token is not a valid JWT encoding")?;
235 4 : let header = serde_json::from_slice::<JwtHeader<'_>>(&header)
236 4 : .context("Provided authentication token is not a valid JWT encoding")?;
237 :
238 4 : let sig = base64::decode_config(signature, base64::URL_SAFE_NO_PAD)
239 4 : .context("Provided authentication token is not a valid JWT encoding")?;
240 :
241 4 : ensure!(header.typ == "JWT");
242 4 : let kid = header.key_id.context("missing key id")?;
243 :
244 4 : let mut guard = self
245 4 : .get_or_update_jwk_cache(ctx, client, role_name.clone(), fetch)
246 4 : .await?;
247 :
248 : // get the key from the JWKs if possible. If not, wait for the keys to update.
249 4 : let (jwk, expected_audience) = loop {
250 4 : match guard.find_jwk_and_audience(kid) {
251 4 : Some(jwk) => break jwk,
252 0 : None if guard.last_retrieved.elapsed() > MIN_RENEW => {
253 0 : let _paused = ctx.latency_timer_pause(crate::metrics::Waiting::Compute);
254 :
255 0 : let permit = self.acquire_permit().await;
256 0 : guard = self
257 0 : .renew_jwks(permit, client, role_name.clone(), fetch)
258 0 : .await?;
259 : }
260 : _ => {
261 0 : bail!("jwk not found");
262 : }
263 : }
264 : };
265 :
266 4 : ensure!(
267 4 : jwk.is_supported(&header.algorithm),
268 0 : "signature algorithm not supported"
269 : );
270 :
271 4 : match &jwk.key {
272 2 : jose_jwk::Key::Ec(key) => {
273 2 : verify_ec_signature(header_payload.as_bytes(), &sig, key)?;
274 : }
275 2 : jose_jwk::Key::Rsa(key) => {
276 2 : verify_rsa_signature(header_payload.as_bytes(), &sig, key, &jwk.prm.alg)?;
277 : }
278 0 : key => bail!("unsupported key type {key:?}"),
279 : };
280 :
281 4 : let payload = base64::decode_config(payload, base64::URL_SAFE_NO_PAD)
282 4 : .context("Provided authentication token is not a valid JWT encoding")?;
283 4 : let payload = serde_json::from_slice::<JwtPayload<'_>>(&payload)
284 4 : .context("Provided authentication token is not a valid JWT encoding")?;
285 :
286 4 : tracing::debug!(?payload, "JWT signature valid with claims");
287 :
288 4 : match (expected_audience, payload.audience) {
289 : // check the audience matches
290 0 : (Some(aud1), Some(aud2)) => ensure!(aud1 == aud2, "invalid JWT token audience"),
291 : // the audience is expected but is missing
292 0 : (Some(_), None) => bail!("invalid JWT token audience"),
293 : // we don't care for the audience field
294 4 : (None, _) => {}
295 : }
296 :
297 4 : let now = SystemTime::now();
298 :
299 4 : if let Some(exp) = payload.expiration {
300 4 : ensure!(now < exp + CLOCK_SKEW_LEEWAY);
301 0 : }
302 :
303 4 : if let Some(nbf) = payload.not_before {
304 0 : ensure!(nbf < now + CLOCK_SKEW_LEEWAY);
305 4 : }
306 :
307 4 : Ok(())
308 4 : }
309 : }
310 :
311 : impl JwkCache {
312 0 : pub(crate) async fn check_jwt<F: FetchAuthRules>(
313 0 : &self,
314 0 : ctx: &RequestMonitoring,
315 0 : endpoint: EndpointId,
316 0 : role_name: RoleName,
317 0 : fetch: &F,
318 0 : jwt: &str,
319 0 : ) -> Result<(), anyhow::Error> {
320 0 : // try with just a read lock first
321 0 : let key = (endpoint, role_name.clone());
322 0 : let entry = self.map.get(&key).as_deref().map(Arc::clone);
323 0 : let entry = entry.unwrap_or_else(|| {
324 0 : // acquire a write lock after to insert.
325 0 : let entry = self.map.entry(key).or_default();
326 0 : Arc::clone(&*entry)
327 0 : });
328 0 :
329 0 : entry
330 0 : .check_jwt(ctx, jwt, &self.client, role_name, fetch)
331 0 : .await
332 0 : }
333 : }
334 :
335 2 : fn verify_ec_signature(data: &[u8], sig: &[u8], key: &jose_jwk::Ec) -> anyhow::Result<()> {
336 : use ecdsa::Signature;
337 : use signature::Verifier;
338 :
339 2 : match key.crv {
340 : jose_jwk::EcCurves::P256 => {
341 2 : let pk =
342 2 : p256::PublicKey::try_from(key).map_err(|_| anyhow::anyhow!("invalid P256 key"))?;
343 2 : let key = p256::ecdsa::VerifyingKey::from(&pk);
344 2 : let sig = Signature::from_slice(sig)?;
345 2 : key.verify(data, &sig)?;
346 : }
347 0 : key => bail!("unsupported ec key type {key:?}"),
348 : }
349 :
350 2 : Ok(())
351 2 : }
352 :
353 2 : fn verify_rsa_signature(
354 2 : data: &[u8],
355 2 : sig: &[u8],
356 2 : key: &jose_jwk::Rsa,
357 2 : alg: &Option<jose_jwa::Algorithm>,
358 2 : ) -> anyhow::Result<()> {
359 : use jose_jwa::{Algorithm, Signing};
360 : use rsa::{
361 : pkcs1v15::{Signature, VerifyingKey},
362 : RsaPublicKey,
363 : };
364 :
365 2 : let key = RsaPublicKey::try_from(key).map_err(|_| anyhow::anyhow!("invalid RSA key"))?;
366 :
367 2 : match alg {
368 : Some(Algorithm::Signing(Signing::Rs256)) => {
369 2 : let key = VerifyingKey::<sha2::Sha256>::new(key);
370 2 : let sig = Signature::try_from(sig)?;
371 2 : key.verify(data, &sig)?;
372 : }
373 0 : _ => bail!("invalid RSA signing algorithm"),
374 : };
375 :
376 2 : Ok(())
377 2 : }
378 :
379 : /// <https://datatracker.ietf.org/doc/html/rfc7515#section-4.1>
380 16 : #[derive(serde::Deserialize, serde::Serialize)]
381 : struct JwtHeader<'a> {
382 : /// must be "JWT"
383 : #[serde(rename = "typ")]
384 : typ: &'a str,
385 : /// must be a supported alg
386 : #[serde(rename = "alg")]
387 : algorithm: jose_jwa::Algorithm,
388 : /// key id, must be provided for our usecase
389 : #[serde(rename = "kid")]
390 : key_id: Option<&'a str>,
391 : }
392 :
393 : /// <https://datatracker.ietf.org/doc/html/rfc7519#section-4.1>
394 12 : #[derive(serde::Deserialize, serde::Serialize, Debug)]
395 : struct JwtPayload<'a> {
396 : /// Audience - Recipient for which the JWT is intended
397 : #[serde(rename = "aud")]
398 : audience: Option<&'a str>,
399 : /// Expiration - Time after which the JWT expires
400 : #[serde(deserialize_with = "numeric_date_opt", rename = "exp", default)]
401 : expiration: Option<SystemTime>,
402 : /// Not before - Time after which the JWT expires
403 : #[serde(deserialize_with = "numeric_date_opt", rename = "nbf", default)]
404 : not_before: Option<SystemTime>,
405 :
406 : // the following entries are only extracted for the sake of debug logging.
407 : /// Issuer of the JWT
408 : #[serde(rename = "iss")]
409 : issuer: Option<&'a str>,
410 : /// Subject of the JWT (the user)
411 : #[serde(rename = "sub")]
412 : subject: Option<&'a str>,
413 : /// Unique token identifier
414 : #[serde(rename = "jti")]
415 : jwt_id: Option<&'a str>,
416 : /// Unique session identifier
417 : #[serde(rename = "sid")]
418 : session_id: Option<&'a str>,
419 : }
420 :
421 4 : fn numeric_date_opt<'de, D: Deserializer<'de>>(d: D) -> Result<Option<SystemTime>, D::Error> {
422 4 : let d = <Option<u64>>::deserialize(d)?;
423 4 : Ok(d.map(|n| SystemTime::UNIX_EPOCH + Duration::from_secs(n)))
424 4 : }
425 :
426 : struct JwkRenewalPermit<'a> {
427 : inner: Option<JwkRenewalPermitInner<'a>>,
428 : }
429 :
430 : enum JwkRenewalPermitInner<'a> {
431 : Owned(Arc<JwkCacheEntryLock>),
432 : Borrowed(&'a Arc<JwkCacheEntryLock>),
433 : }
434 :
435 : impl JwkRenewalPermit<'_> {
436 0 : fn into_owned(mut self) -> JwkRenewalPermit<'static> {
437 0 : JwkRenewalPermit {
438 0 : inner: self.inner.take().map(JwkRenewalPermitInner::into_owned),
439 0 : }
440 0 : }
441 :
442 1 : async fn acquire_permit(from: &Arc<JwkCacheEntryLock>) -> JwkRenewalPermit<'_> {
443 1 : match from.lookup.acquire().await {
444 1 : Ok(permit) => {
445 1 : permit.forget();
446 1 : JwkRenewalPermit {
447 1 : inner: Some(JwkRenewalPermitInner::Borrowed(from)),
448 1 : }
449 : }
450 0 : Err(_) => panic!("semaphore should not be closed"),
451 : }
452 1 : }
453 :
454 0 : fn try_acquire_permit(from: &Arc<JwkCacheEntryLock>) -> Option<JwkRenewalPermit<'_>> {
455 0 : match from.lookup.try_acquire() {
456 0 : Ok(permit) => {
457 0 : permit.forget();
458 0 : Some(JwkRenewalPermit {
459 0 : inner: Some(JwkRenewalPermitInner::Borrowed(from)),
460 0 : })
461 : }
462 0 : Err(tokio::sync::TryAcquireError::NoPermits) => None,
463 0 : Err(tokio::sync::TryAcquireError::Closed) => panic!("semaphore should not be closed"),
464 : }
465 0 : }
466 : }
467 :
468 : impl JwkRenewalPermitInner<'_> {
469 0 : fn into_owned(self) -> JwkRenewalPermitInner<'static> {
470 0 : match self {
471 0 : JwkRenewalPermitInner::Owned(p) => JwkRenewalPermitInner::Owned(p),
472 0 : JwkRenewalPermitInner::Borrowed(p) => JwkRenewalPermitInner::Owned(Arc::clone(p)),
473 : }
474 0 : }
475 : }
476 :
477 : impl Drop for JwkRenewalPermit<'_> {
478 1 : fn drop(&mut self) {
479 1 : let entry = match &self.inner {
480 0 : None => return,
481 0 : Some(JwkRenewalPermitInner::Owned(p)) => p,
482 1 : Some(JwkRenewalPermitInner::Borrowed(p)) => *p,
483 : };
484 1 : entry.lookup.add_permits(1);
485 1 : }
486 : }
487 :
488 : #[cfg(test)]
489 : mod tests {
490 : use crate::RoleName;
491 :
492 : use super::*;
493 :
494 : use std::{future::IntoFuture, net::SocketAddr, time::SystemTime};
495 :
496 : use base64::URL_SAFE_NO_PAD;
497 : use bytes::Bytes;
498 : use http::Response;
499 : use http_body_util::Full;
500 : use hyper1::service::service_fn;
501 : use hyper_util::rt::TokioIo;
502 : use rand::rngs::OsRng;
503 : use rsa::pkcs8::DecodePrivateKey;
504 : use signature::Signer;
505 : use tokio::net::TcpListener;
506 :
507 2 : fn new_ec_jwk(kid: String) -> (p256::SecretKey, jose_jwk::Jwk) {
508 2 : let sk = p256::SecretKey::random(&mut OsRng);
509 2 : let pk = sk.public_key().into();
510 2 : let jwk = jose_jwk::Jwk {
511 2 : key: jose_jwk::Key::Ec(pk),
512 2 : prm: jose_jwk::Parameters {
513 2 : kid: Some(kid),
514 2 : alg: Some(jose_jwa::Algorithm::Signing(jose_jwa::Signing::Es256)),
515 2 : ..Default::default()
516 2 : },
517 2 : };
518 2 : (sk, jwk)
519 2 : }
520 :
521 2 : fn new_rsa_jwk(key: &str, kid: String) -> (rsa::RsaPrivateKey, jose_jwk::Jwk) {
522 2 : let sk = rsa::RsaPrivateKey::from_pkcs8_pem(key).unwrap();
523 2 : let pk = sk.to_public_key().into();
524 2 : let jwk = jose_jwk::Jwk {
525 2 : key: jose_jwk::Key::Rsa(pk),
526 2 : prm: jose_jwk::Parameters {
527 2 : kid: Some(kid),
528 2 : alg: Some(jose_jwa::Algorithm::Signing(jose_jwa::Signing::Rs256)),
529 2 : ..Default::default()
530 2 : },
531 2 : };
532 2 : (sk, jwk)
533 2 : }
534 :
535 4 : fn build_jwt_payload(kid: String, sig: jose_jwa::Signing) -> String {
536 4 : let header = JwtHeader {
537 4 : typ: "JWT",
538 4 : algorithm: jose_jwa::Algorithm::Signing(sig),
539 4 : key_id: Some(&kid),
540 4 : };
541 4 : let body = typed_json::json! {{
542 4 : "exp": SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap().as_secs() + 3600,
543 4 : }};
544 4 :
545 4 : let header =
546 4 : base64::encode_config(serde_json::to_string(&header).unwrap(), URL_SAFE_NO_PAD);
547 4 : let body = base64::encode_config(body.to_string(), URL_SAFE_NO_PAD);
548 4 :
549 4 : format!("{header}.{body}")
550 4 : }
551 :
552 2 : fn new_ec_jwt(kid: String, key: p256::SecretKey) -> String {
553 : use p256::ecdsa::{Signature, SigningKey};
554 :
555 2 : let payload = build_jwt_payload(kid, jose_jwa::Signing::Es256);
556 2 : let sig: Signature = SigningKey::from(key).sign(payload.as_bytes());
557 2 : let sig = base64::encode_config(sig.to_bytes(), URL_SAFE_NO_PAD);
558 2 :
559 2 : format!("{payload}.{sig}")
560 2 : }
561 :
562 2 : fn new_rsa_jwt(kid: String, key: rsa::RsaPrivateKey) -> String {
563 : use rsa::pkcs1v15::SigningKey;
564 : use rsa::signature::SignatureEncoding;
565 :
566 2 : let payload = build_jwt_payload(kid, jose_jwa::Signing::Rs256);
567 2 : let sig = SigningKey::<sha2::Sha256>::new(key).sign(payload.as_bytes());
568 2 : let sig = base64::encode_config(sig.to_bytes(), URL_SAFE_NO_PAD);
569 2 :
570 2 : format!("{payload}.{sig}")
571 2 : }
572 :
573 : // RSA key gen is slow....
574 : const RS1: &str = "-----BEGIN PRIVATE KEY-----
575 : MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDNuWBIWTlo+54Y
576 : aifpGInIrpv6LlsbI/2/2CC81Arlx4RsABORklgA9XSGwaCbHTshHsfd1S916JwA
577 : SpjyPQYWfqo6iAV8a4MhjIeJIkRr74prDCSzOGZvIc6VaGeCIb9clf3HSrPHm3hA
578 : cfLMB8/p5MgoxERPDOIn3XYoS9SEEuP7l0LkmEZMerg6W6lDjQRDny0Lb50Jky9X
579 : mDqnYXBhs99ranbwL5vjy0ba6OIeCWFJme5u+rv5C/P0BOYrJfGxIcEoKa8Ukw5s
580 : PlM+qrz9ope1eOuXMNNdyFDReNBUyaM1AwBAayU5rz57crer7K/UIofaJ42T4cMM
581 : nx/SWfBNAgMBAAECggEACqdpBxYn1PoC6/zDaFzu9celKEWyTiuE/qRwvZa1ocS9
582 : ZOJ0IPvVNud/S2NHsADJiSOQ8joSJScQvSsf1Ju4bv3MTw+wSQtAVUJz2nQ92uEi
583 : 5/xPAkEPfP3hNvebNLAOuvrBk8qYmOPCTIQaMNrOt6wzeXkAmJ9wLuRXNCsJLHW+
584 : KLpf2WdgTYxqK06ZiJERFgJ2r1MsC2IgTydzjOAdEIrtMarerTLqqCpwFrk/l0cz
585 : 1O2OAb17ZxmhuzMhjNMin81c8F2fZAGMeOjn92Jl5kUsYw/pG+0S8QKlbveR/fdP
586 : We2tJsgXw2zD0q7OJpp8NXS2yddrZGyysYsof983wQKBgQD2McqNJqo+eWL5zony
587 : UbL19loYw0M15EjhzIuzW1Jk0rPj65yQyzpJ6pqicRuWr34MvzCx+ZHM2b3jSiNu
588 : GES2fnC7xLIKyeRxfqsXF71xz+6UStEGRQX27r1YWEtyQVuBhvlqB+AGWP3PYAC+
589 : HecZecnZ+vcihJ2K3+l5O3paVQKBgQDV6vKH5h2SY9vgO8obx0P7XSS+djHhmPuU
590 : f8C/Fq6AuRbIA1g04pzuLU2WS9T26eIjgM173uVNg2TuqJveWzz+CAAp6nCR6l24
591 : DBg49lMGCWrMo4FqPG46QkUqvK8uSj42GkX/e5Rut1Gyu0209emeM6h2d2K15SvY
592 : 9563tYSmGQKBgQDwcH5WTi20KA7e07TroJi8GKWzS3gneNUpGQBS4VxdtV4UuXXF
593 : /4TkzafJ/9cm2iurvUmMd6XKP9lw0mY5zp/E70WgTCBp4vUlVsU3H2tYbO+filYL
594 : 3ntNx6nKTykX4/a/UJfj0t8as+zli+gNxNx/h+734V9dKdFG4Rl+2fTLpQKBgQCE
595 : qJkTEe+Q0wCOBEYICADupwqcWqwAXWDW7IrZdfVtulqYWwqecVIkmk+dPxWosc4d
596 : ekjz4nyNH0i+gC15LVebqdaAJ/T7aD4KXuW+nXNLMRfcJCGjgipRUruWD0EMEdqW
597 : rqBuGXMpXeH6VxGPgVkJVLvKC6tZZe9VM+pnvteuMQKBgQC8GaL+Lz+al4biyZBf
598 : JE8ekWrIotq/gfUBLP7x70+PB9bNtXtlgmTvjgYg4jiu3KR/ZIYYQ8vfVgkb6tDI
599 : rWGZw86Pzuoi1ppg/pYhKk9qrmCIT4HPEXbHl7ATahu2BOCIU3hybjTh2lB6LbX9
600 : 8LMFlz1QPqSZYN/A/kOcLBfa3A==
601 : -----END PRIVATE KEY-----
602 : ";
603 : const RS2: &str = "-----BEGIN PRIVATE KEY-----
604 : MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDipm6FIKSRab3J
605 : HwmK18t7hp+pohllxIDUSPi7S5mIhN/JG2Plq2Lp746E/fuT8dcBF2R4sJlG2L0J
606 : zmxOvBU/i/sQF9s1i4CEfg05k2//gKENIEsF3pMMmrH+mcZi0TTD6rezHpdVxPHk
607 : qWxSyOCtIJV29X+wxPwAB59kQFHzy2ooPB1isZcpE8tO0KthAM+oZ3KuCwE0++cO
608 : IWLeq9aPwyKhtip/xjTMxd1kzdKh592mGSyzr9D0QSWOYFGvgJXANDdiPdhSSOLt
609 : ECWPNPlm2FQvGGvYYBafUqz7VumKHE6x8J6lKdYa2J0ZdDzCIo2IHzlxe+RZNgwy
610 : uAD2jhVxAgMBAAECggEAbsZHWBu3MzcKQiVARbLoygvnN0J5xUqAaMDtiKUPejDv
611 : K1yOu67DXnDuKEP2VL2rhuYG/hHaKE1AP227c9PrUq6424m9YvM2sgrlrdFIuQkG
612 : LeMtp8W7+zoUasp/ssZrUqICfLIj5xCl5UuFHQT/Ar7dLlIYwa3VOLKBDb9+Dnfe
613 : QH5/So4uMXG6vw34JN9jf+eAc8Yt0PeIz62ycvRwdpTJQ0MxZN9ZKpCAQp+VTuXT
614 : zlzNvDMilabEdqUvAyGyz8lBLNl0wdaVrqPqAEWM5U45QXsdFZknWammP7/tijeX
615 : 0z+Bi0J0uSEU5X502zm7GArj/NNIiWMcjmDjwUUhwQKBgQD9C2GoqxOxuVPYqwYR
616 : +Jz7f2qMjlSP8adA5Lzuh8UKXDp8JCEQC8ryweLzaOKS9C5MAw+W4W2wd4nJoQI1
617 : P1dgGvBlfvEeRHMgqWtq7FuTsjSe7e0uSEkC4ngDb4sc0QOpv15cMuEz+4+aFLPL
618 : x29EcHWAaBX+rkid3zpQHFU4eQKBgQDlTCEqRuXwwa3V+Sq+mNWzD9QIGtD87TH/
619 : FPO/Ij/cK2+GISgFDqhetiGTH4qrvPL0psPT+iH5zGFYcoFmTtwLdWQJdxhxz0bg
620 : iX/AceyX5e1Bm+ThT36sU83NrxKPkrdk6jNmr2iUF1OTzTwUKOYdHOPZqdMPfF4M
621 : 4XAaWVT2uQKBgQD4nKcNdU+7LE9Rr+4d1/o8Klp/0BMK/ayK2HE7lc8kt6qKb2DA
622 : iCWUTqPw7Fq3cQrPia5WWhNP7pJEtFkcAaiR9sW7onW5fBz0uR+dhK0QtmR2xWJj
623 : N4fsOp8ZGQ0/eae0rh1CTobucLkM9EwV6VLLlgYL67e4anlUCo8bSEr+WQKBgQCB
624 : uf6RgqcY/RqyklPCnYlZ0zyskS9nyXKd1GbK3j+u+swP4LZZlh9f5j88k33LCA2U
625 : qLzmMwAB6cWxWqcnELqhqPq9+ClWSmTZKDGk2U936NfAZMirSGRsbsVi9wfTPriP
626 : WYlXMSpDjqb0WgsBhNob4npubQxCGKTFOM5Jufy90QKBgB0Lte1jX144uaXx6dtB
627 : rjXNuWNir0Jy31wHnQuCA+XnfUgPcrKmRLm8taMbXgZwxkNvgFkpUWU8aPEK08Ne
628 : X0n5X2/pBLJzxZc62ccvZYVnctBiFs6HbSnxpuMQCfkt/BcR/ttIepBQQIW86wHL
629 : 5JiconnI5aLek0QVPoFaVXFa
630 : -----END PRIVATE KEY-----
631 : ";
632 :
633 : #[tokio::test]
634 1 : async fn renew() {
635 1 : let (rs1, jwk1) = new_rsa_jwk(RS1, "1".into());
636 1 : let (rs2, jwk2) = new_rsa_jwk(RS2, "2".into());
637 1 : let (ec1, jwk3) = new_ec_jwk("3".into());
638 1 : let (ec2, jwk4) = new_ec_jwk("4".into());
639 1 :
640 1 : let jwt1 = new_rsa_jwt("1".into(), rs1);
641 1 : let jwt2 = new_rsa_jwt("2".into(), rs2);
642 1 : let jwt3 = new_ec_jwt("3".into(), ec1);
643 1 : let jwt4 = new_ec_jwt("4".into(), ec2);
644 1 :
645 1 : let foo_jwks = jose_jwk::JwkSet {
646 1 : keys: vec![jwk1, jwk3],
647 1 : };
648 1 : let bar_jwks = jose_jwk::JwkSet {
649 1 : keys: vec![jwk2, jwk4],
650 1 : };
651 1 :
652 2 : let service = service_fn(move |req| {
653 2 : let foo_jwks = foo_jwks.clone();
654 2 : let bar_jwks = bar_jwks.clone();
655 2 : async move {
656 2 : let jwks = match req.uri().path() {
657 2 : "/foo" => &foo_jwks,
658 1 : "/bar" => &bar_jwks,
659 1 : _ => {
660 1 : return Response::builder()
661 0 : .status(404)
662 0 : .body(Full::new(Bytes::new()));
663 1 : }
664 1 : };
665 2 : let body = serde_json::to_vec(jwks).unwrap();
666 2 : Response::builder()
667 2 : .status(200)
668 2 : .body(Full::new(Bytes::from(body)))
669 2 : }
670 2 : });
671 1 :
672 1 : let listener = TcpListener::bind("0.0.0.0:0").await.unwrap();
673 1 : let server = hyper1::server::conn::http1::Builder::new();
674 1 : let addr = listener.local_addr().unwrap();
675 1 : tokio::spawn(async move {
676 1 : loop {
677 2 : let (s, _) = listener.accept().await.unwrap();
678 1 : let serve = server.serve_connection(TokioIo::new(s), service.clone());
679 1 : tokio::spawn(serve.into_future());
680 1 : }
681 1 : });
682 1 :
683 1 : let client = reqwest::Client::new();
684 1 :
685 1 : #[derive(Clone)]
686 1 : struct Fetch(SocketAddr);
687 1 :
688 1 : impl FetchAuthRules for Fetch {
689 1 : async fn fetch_auth_rules(
690 1 : &self,
691 1 : _role_name: RoleName,
692 1 : ) -> anyhow::Result<Vec<AuthRule>> {
693 1 : Ok(vec![
694 1 : AuthRule {
695 1 : id: "foo".to_owned(),
696 1 : jwks_url: format!("http://{}/foo", self.0).parse().unwrap(),
697 1 : audience: None,
698 1 : },
699 1 : AuthRule {
700 1 : id: "bar".to_owned(),
701 1 : jwks_url: format!("http://{}/bar", self.0).parse().unwrap(),
702 1 : audience: None,
703 1 : },
704 1 : ])
705 1 : }
706 1 : }
707 1 :
708 1 : let role_name = RoleName::from("user");
709 1 :
710 1 : let jwk_cache = Arc::new(JwkCacheEntryLock::default());
711 1 :
712 4 : for token in [jwt1, jwt2, jwt3, jwt4] {
713 4 : jwk_cache
714 4 : .check_jwt(
715 4 : &RequestMonitoring::test(),
716 4 : &token,
717 4 : &client,
718 4 : role_name.clone(),
719 4 : &Fetch(addr),
720 4 : )
721 4 : .await
722 4 : .unwrap();
723 1 : }
724 1 : }
725 : }
|