From 5c63eb1795b7824257f90a5f5fc86004e57cbda5 Mon Sep 17 00:00:00 2001 From: Chris Smith Date: Tue, 25 Jun 2024 08:41:46 -0700 Subject: [PATCH] fix: bypass rate limits --- src/attestation_store/cf_kv.rs | 58 ++++++++++++++++------------------ src/http_server/mod.rs | 11 ++++--- src/main.rs | 14 ++++---- terraform/ecs/cluster.tf | 4 +-- terraform/ecs/variables.tf | 14 ++------ terraform/res_ecs.tf | 4 +-- terraform/variables.tf | 15 ++------- 7 files changed, 47 insertions(+), 73 deletions(-) diff --git a/src/attestation_store/cf_kv.rs b/src/attestation_store/cf_kv.rs index ca78aa0..884f6e3 100644 --- a/src/attestation_store/cf_kv.rs +++ b/src/attestation_store/cf_kv.rs @@ -1,54 +1,54 @@ use { super::{AttestationStore, Result}, + crate::http_server::{CsrfToken, TokenManager}, async_trait::async_trait, hyper::StatusCode, + reqwest::Url, serde::Serialize, std::time::Duration, }; -const ATTESTATION_TTL_SECS: usize = 300; - +#[derive(Clone)] pub struct CloudflareKv { - pub account_id: String, - pub namespace_id: String, - pub bearer_token: String, + pub endpoint: Url, + pub token_manager: TokenManager, pub http_client: reqwest::Client, } impl CloudflareKv { - pub fn new(account_id: String, namespace_id: String, bearer_token: String) -> Self { + pub fn new(endpoint: Url, token_manager: TokenManager) -> Self { Self { - account_id, - namespace_id, - bearer_token, + endpoint, + token_manager, http_client: reqwest::Client::new(), } } } #[derive(Serialize)] -struct SetBulkBody<'a> { - expiration_ttl: usize, - key: &'a str, - value: &'a str, +#[serde(rename_all = "camelCase")] +struct SetAttestationCompatBody<'a> { + attestation_id: &'a str, + origin: &'a str, } #[async_trait] impl AttestationStore for CloudflareKv { async fn set_attestation(&self, id: &str, origin: &str) -> Result<()> { - let url = format!( - "https://api.cloudflare.com/client/v4/accounts/{account_id}/storage/kv/namespaces/{namespace_id}/bulk", - account_id = self.account_id, namespace_id = self.namespace_id - ); + let url = self.endpoint.join("/attestation")?; let res = self .http_client - .put(&url) - .bearer_auth(&self.bearer_token) - .json(&vec![SetBulkBody { - expiration_ttl: ATTESTATION_TTL_SECS, - key: id, - value: origin, - }]) + .post(url) + .header( + CsrfToken::header_name(), + self.token_manager + .generate_csrf_token() + .map_err(|e| anyhow::anyhow!("{e:?}"))?, + ) + .json(&SetAttestationCompatBody { + attestation_id: id, + origin, + }) .timeout(Duration::from_secs(1)) .send() .await?; @@ -64,14 +64,12 @@ impl AttestationStore for CloudflareKv { } async fn get_attestation(&self, id: &str) -> Result> { - let url = format!( - "https://api.cloudflare.com/client/v4/accounts/{account_id}/storage/kv/namespaces/{namespace_id}/values/{id}", - account_id = self.account_id, namespace_id = self.namespace_id - ); + let url = self + .endpoint + .join(&format!("/v1/compat-attestation/{id}"))?; let response = self .http_client - .get(&url) - .bearer_auth(&self.bearer_token) + .get(url) .timeout(Duration::from_secs(1)) .send() .await?; diff --git a/src/http_server/mod.rs b/src/http_server/mod.rs index 1c58d8b..1e847b9 100644 --- a/src/http_server/mod.rs +++ b/src/http_server/mod.rs @@ -84,13 +84,14 @@ impl Server { } } -struct TokenManager { +#[derive(Clone)] +pub struct TokenManager { encoding_key: jsonwebtoken::EncodingKey, decoding_key: jsonwebtoken::DecodingKey, } impl TokenManager { - fn new(secret: &[u8]) -> Self { + pub fn new(secret: &[u8]) -> Self { Self { encoding_key: jsonwebtoken::EncodingKey::from_secret(secret), decoding_key: jsonwebtoken::DecodingKey::from_secret(secret), @@ -262,14 +263,14 @@ where } #[derive(Serialize, Deserialize)] -struct CsrfToken { +pub struct CsrfToken { exp: usize, } impl CsrfToken { // Using const value instead of a fn produces this warning: // https://rust-lang.github.io/rust-clippy/master/index.html#declare_interior_mutable_const - const fn header_name() -> HeaderName { + pub const fn header_name() -> HeaderName { HeaderName::from_static("x-csrf-token") } @@ -282,7 +283,7 @@ impl CsrfToken { } impl TokenManager { - fn generate_csrf_token(&self) -> Result { + pub fn generate_csrf_token(&self) -> Result { use jsonwebtoken::{encode, get_current_timestamp, Header}; const TTL_SECS: usize = 60 * 60; // 1 hour diff --git a/src/main.rs b/src/main.rs index e89e20a..d4c56ce 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,7 +10,7 @@ use { bouncer::{ attestation_store::{cf_kv::CloudflareKv, migration::MigrationStore}, event_sink, - http_server::{RequestInfo, ServerConfig}, + http_server::{RequestInfo, ServerConfig, TokenManager}, project_registry::{self, CachedExt as _}, scam_guard, util::redis, @@ -56,9 +56,7 @@ pub struct Configuration { pub data_api_auth_token: String, pub scam_guard_cache_url: String, - pub cf_kv_account_id: String, - pub cf_kv_namespace_id: String, - pub cf_kv_bearer_token: String, + pub cf_kv_endpoint: String, pub secret: String, @@ -109,9 +107,11 @@ async fn main() -> Result<(), anyhow::Error> { redis::new("attestation_store", config.attestation_cache_url.clone()) .context("Failed to initialize AttestationStore")?; let cf_kv_attestation_store = CloudflareKv::new( - config.cf_kv_account_id, - config.cf_kv_namespace_id, - config.cf_kv_bearer_token, + config + .cf_kv_endpoint + .parse() + .context("Failed to parse cf_kv_endpoint")?, + TokenManager::new(config.secret.as_bytes()), ); MigrationStore::new(redis_attestation_store, cf_kv_attestation_store) }; diff --git a/terraform/ecs/cluster.tf b/terraform/ecs/cluster.tf index 302cb50..ae4d2d2 100644 --- a/terraform/ecs/cluster.tf +++ b/terraform/ecs/cluster.tf @@ -86,9 +86,7 @@ resource "aws_ecs_task_definition" "app_task" { { name = "PROJECT_REGISTRY_CACHE_URL", value = var.project_registry_cache_url }, { name = "SCAM_GUARD_CACHE_URL", value = var.scam_guard_cache_url }, - { name = "CF_KV_ACCOUNT_ID", value = var.cf_kv_account_id }, - { name = "CF_KV_NAMESPACE_ID", value = var.cf_kv_namespace_id }, - { name = "CF_KV_BEARER_TOKEN", value = var.cf_kv_bearer_token }, + { name = "CF_KV_ENDPOINT", value = var.cf_kv_endpoint }, { name = "DATA_LAKE_BUCKET", value = var.analytics_datalake_bucket_name }, diff --git a/terraform/ecs/variables.tf b/terraform/ecs/variables.tf index 764ff95..3c8d53a 100644 --- a/terraform/ecs/variables.tf +++ b/terraform/ecs/variables.tf @@ -152,18 +152,8 @@ variable "scam_guard_cache_url" { type = string } -variable "cf_kv_account_id" { - description = "The account ID of the Cloudflare KV store" - type = string -} - -variable "cf_kv_namespace_id" { - description = "The namespace ID of the Cloudflare KV store" - type = string -} - -variable "cf_kv_bearer_token" { - description = "The Cloudflare API bearer token" +variable "cf_kv_endpoint" { + description = "The endpoint of the Cloudflare KV worker" type = string } diff --git a/terraform/res_ecs.tf b/terraform/res_ecs.tf index 9e5d833..a37a361 100644 --- a/terraform/res_ecs.tf +++ b/terraform/res_ecs.tf @@ -65,9 +65,7 @@ module "ecs" { project_registry_cache_url = "redis://${module.redis.endpoint}/1" scam_guard_cache_url = "redis://${module.redis.endpoint}/2" - cf_kv_account_id = var.cf_kv_account_id - cf_kv_namespace_id = var.cf_kv_namespace_id - cf_kv_bearer_token = var.cf_kv_bearer_token + cf_kv_endpoint = var.cf_kv_endpoint ofac_blocked_countries = var.ofac_blocked_countries diff --git a/terraform/variables.tf b/terraform/variables.tf index b8c3320..a2aea4a 100644 --- a/terraform/variables.tf +++ b/terraform/variables.tf @@ -78,22 +78,11 @@ variable "ofac_blocked_countries" { #------------------------------------------------------------------------------- # Cloudflare KV for V2 migration -variable "cf_kv_account_id" { - description = "The account ID of the Cloudflare KV store" +variable "cf_kv_endpoint" { + description = "The endpoint of the Cloudflare KV worker" type = string } -variable "cf_kv_namespace_id" { - description = "The namespace ID of the Cloudflare KV store" - type = string -} - -variable "cf_kv_bearer_token" { - description = "The Cloudflare API bearer token" - type = string -} - - #------------------------------------------------------------------------------- # Project Registry