Skip to content

Commit 8696cee

Browse files
committed
Merge remote-tracking branch 'origin/main' into fix/fix-certificate-provisioning-failures-caused-by-acme-account-serialization-issue-and-localstack-auth-changes
2 parents 7e9e12d + 9f768ab commit 8696cee

8 files changed

Lines changed: 92 additions & 25 deletions

File tree

.env.template

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,12 @@ APP_DATABASE__URL=postgres://postgres:postgres@db:5432/status-list
2323
# Redis configuration
2424
APP_REDIS__URI=redis://redis:6379
2525
APP_REDIS__REQUIRE_CLIENT_AUTH=false
26+
# TTL for the certificate cache in Redis.
27+
# Set to 0 to disable and always fetch from S3.
28+
APP_REDIS__CERT_CACHE_TTL=3600
29+
# TTL for the Secrets Manager cache.
30+
# Set to 0 to disable and always fetch from AWS API directly.
31+
APP_REDIS__SECRETS_CACHE_TTL=300
2632

2733
# AWS SDK
2834
APP_AWS__REGION=us-east-1
@@ -31,6 +37,8 @@ AWS_ACCESS_KEY_ID=test
3137
AWS_SECRET_ACCESS_KEY=test
3238

3339
# Cache configuration
40+
# TTL for the status list record cache.
41+
# Set to 0 to disable and always fetch from the database.
3442
APP_CACHE__TTL=300
3543
APP_CACHE__MAX_CAPACITY=1000
3644

src/config.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ pub struct CertConfig {
4242
pub struct RedisConfig {
4343
pub uri: SecretString,
4444
pub require_client_auth: bool,
45+
pub cert_cache_ttl: u64,
4546
}
4647

4748
#[derive(Debug, Clone, Deserialize)]
@@ -52,6 +53,7 @@ pub struct DatabaseConfig {
5253
#[derive(Debug, Clone, Deserialize)]
5354
pub struct AwsConfig {
5455
pub region: String,
56+
pub secrets_cache_ttl: u64,
5557
}
5658

5759
#[derive(Debug, Clone, Deserialize)]
@@ -136,6 +138,8 @@ impl Config {
136138
)?
137139
.set_default("redis.uri", "redis://localhost:6379")?
138140
.set_default("redis.require_client_auth", false)?
141+
.set_default("redis.cert_cache_ttl", 3600)? // Default 1 hour
142+
.set_default("aws.secrets_cache_ttl", 300)? // Default 5 minutes
139143
.set_default("server.cert.email", "admin@example.com")?
140144
.set_default("server.cert.eku", vec![1, 3, 6, 1, 5, 5, 7, 3, 30])?
141145
.set_default("server.cert.organization", "adorsys GmbH & CO KG")?
@@ -225,6 +229,7 @@ mod tests {
225229
("APP_SERVER__CERT__ORGANIZATION", "Test Org"),
226230
("APP_SERVER__CERT__EKU", "1,3,6,1,5,5,7,3,30"),
227231
("APP_AWS__REGION", "us-west-2"),
232+
("APP_AWS__SECRETS_CACHE_TTL", "600"),
228233
("APP_CACHE__TTL", "600"),
229234
("APP_CACHE__MAX_CAPACITY", "2000"),
230235
])]

src/utils/cache.rs

Lines changed: 57 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,66 @@ use crate::models::StatusListRecord;
55

66
#[derive(Clone)]
77
pub struct Cache {
8-
pub status_list_record_cache: MokaCache<String, StatusListRecord>,
8+
inner: MokaCache<String, StatusListRecord>,
99
}
1010

1111
impl Cache {
12-
pub fn new(ttl: u64, max_capacity: u64) -> Self {
13-
Self {
14-
status_list_record_cache: MokaCache::builder()
15-
.time_to_live(Duration::from_secs(ttl))
16-
.max_capacity(max_capacity)
17-
.build(),
12+
/// Creates a cache; TTL=0 disables it naturally.
13+
pub fn new(ttl_secs: u64, max_capacity: u64) -> Self {
14+
if ttl_secs == 0 {
15+
tracing::info!("Cache disabled (TTL=0)");
1816
}
17+
let inner = MokaCache::builder()
18+
.time_to_live(Duration::from_secs(ttl_secs))
19+
.max_capacity(max_capacity)
20+
.build();
21+
Self { inner }
22+
}
23+
24+
pub async fn get(&self, key: &str) -> Option<StatusListRecord> {
25+
self.inner.get(key).await
26+
}
27+
28+
pub async fn insert(&self, key: String, value: StatusListRecord) {
29+
self.inner.insert(key, value).await;
30+
}
31+
32+
pub async fn invalidate(&self, key: &str) {
33+
self.inner.invalidate(key).await;
34+
}
35+
}
36+
37+
#[cfg(test)]
38+
mod tests {
39+
use super::*;
40+
use crate::models::{StatusList, StatusListRecord};
41+
42+
fn sample_record(id: &str) -> StatusListRecord {
43+
StatusListRecord {
44+
list_id: id.to_string(),
45+
issuer: "issuer".to_string(),
46+
sub: "sub".to_string(),
47+
status_list: StatusList {
48+
bits: 1,
49+
lst: "test".to_string(),
50+
},
51+
}
52+
}
53+
54+
#[tokio::test]
55+
async fn cache_enabled_works() {
56+
let cache = Cache::new(60, 10);
57+
let record = sample_record("list-1");
58+
cache.insert("list-1".to_string(), record.clone()).await;
59+
assert!(cache.get("list-1").await.is_some());
60+
}
61+
62+
#[tokio::test]
63+
async fn cache_disabled_returns_none() {
64+
let cache = Cache::new(0, 10);
65+
cache
66+
.insert("list-1".to_string(), sample_record("list-1"))
67+
.await;
68+
assert!(cache.get("list-1").await.is_none());
1969
}
2070
}

src/utils/cert_manager/storage/aws.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,17 @@ pub struct AwsSecretsManager {
2626

2727
impl AwsSecretsManager {
2828
/// Create a new instance of [AwsSecretsManager] with the given AWS SDK config
29-
pub async fn new(config: &SdkConfig) -> Result<Self, StorageError> {
29+
pub async fn new(
30+
config: &SdkConfig,
31+
secrets_cache_ttl: Duration,
32+
) -> Result<Self, StorageError> {
3033
let client = SecretsClient::new(config);
3134
let asm_builder = SecretsConfig::from(config).to_builder();
32-
// Cache size: 100 and a TTL of 5 minutes
35+
3336
let cache = SecretsCacheClient::from_builder(
3437
asm_builder,
3538
NonZeroUsize::new(100).unwrap(),
36-
Duration::from_secs(300),
39+
secrets_cache_ttl,
3740
true,
3841
)
3942
.await

src/utils/cert_manager/storage/redis.rs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,15 +30,17 @@ impl Redis {
3030
impl Storage for Redis {
3131
async fn store(&self, key: &str, value: &str) -> Result<(), StorageError> {
3232
let mut conn = self.conn.clone();
33-
if let Some(ttl) = self.ttl {
34-
let _: () = conn.set_ex(key, value, ttl).await?;
35-
} else {
36-
let _: () = conn.set(key, value).await?;
33+
match self.ttl {
34+
Some(0) => Ok(()), // Cache disabled
35+
Some(v) => Ok(conn.set_ex(key, value, v).await?),
36+
None => Ok(conn.set(key, value).await?),
3737
}
38-
Ok(())
3938
}
4039

4140
async fn load(&self, key: &str) -> Result<Option<String>, StorageError> {
41+
if matches!(self.ttl, Some(0)) {
42+
return Ok(None);
43+
}
4244
let mut conn = self.conn.clone();
4345
Ok(conn.get(key).await?)
4446
}

src/utils/state.rs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use color_eyre::eyre::{Context, Result as EyeResult};
1313
use sea_orm::Database;
1414
use sea_orm_migration::MigratorTrait;
1515
use secrecy::ExposeSecret;
16-
use std::sync::Arc;
16+
use std::{sync::Arc, time::Duration};
1717

1818
use super::{
1919
cache::Cache,
@@ -67,10 +67,14 @@ pub async fn build_state(config: &AppConfig) -> EyeResult<AppState> {
6767
};
6868

6969
// Initialize the storage backends for the certificate manager
70-
let cache = Redis::new(redis_conn.clone());
70+
let cache = Redis::new(redis_conn.clone()).with_ttl(config.redis.cert_cache_ttl);
7171
let cert_storage =
7272
AwsS3::new(&aws_config, BUCKET_NAME, config.aws.region.clone()).with_cache(cache);
73-
let secrets_storage = AwsSecretsManager::new(&aws_config).await?;
73+
let secrets_storage = AwsSecretsManager::new(
74+
&aws_config,
75+
Duration::from_secs(config.aws.secrets_cache_ttl),
76+
)
77+
.await?;
7478

7579
let mut certificate_manager = CertManager::new(
7680
[&config.server.domain],

src/web/handlers/status_list/get_status_list.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ async fn build_status_list_token(
6161
state: &AppState,
6262
) -> Result<impl IntoResponse + Debug, StatusListError> {
6363
// Check cache for status list record
64-
if let Some(cached_record) = state.cache.status_list_record_cache.get(list_id).await {
64+
if let Some(cached_record) = state.cache.get(list_id).await {
6565
tracing::info!("Cache hit for status list record: {list_id}");
6666
// Record is in cache, proceed with building the response
6767
return build_response_from_record(accept, &cached_record, state).await;
@@ -82,7 +82,6 @@ async fn build_status_list_token(
8282
// Store the token in the cache for future requests
8383
state
8484
.cache
85-
.status_list_record_cache
8685
.insert(list_id.to_string(), status_record.clone())
8786
.await;
8887

src/web/handlers/status_list/update_status.rs

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -77,11 +77,7 @@ pub async fn update_status(
7777
})?;
7878

7979
// Invalidate cache entry to ensure next read fetches the updated record
80-
appstate
81-
.cache
82-
.status_list_record_cache
83-
.invalidate(&exact_status_list.list_id)
84-
.await;
80+
appstate.cache.invalidate(&exact_status_list.list_id).await;
8581
tracing::info!(
8682
"Invalidated cache for status list: {}",
8783
exact_status_list.list_id

0 commit comments

Comments
 (0)