Skip to content

Commit 4939a7e

Browse files
committed
ACME: define and allocate shared data structures.
1 parent ad7310d commit 4939a7e

File tree

9 files changed

+525
-10
lines changed

9 files changed

+525
-10
lines changed

src/conf.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ use self::issuer::Issuer;
1616
use self::order::CertificateOrder;
1717
use self::pkey::PrivateKey;
1818
use self::shared_zone::{SharedZone, ACME_ZONE_NAME, ACME_ZONE_SIZE};
19+
use crate::state::AcmeSharedData;
1920

2021
pub mod ext;
2122
pub mod identifier;
@@ -31,6 +32,7 @@ const NGX_CONF_DUPLICATE: *mut c_char = c"is duplicate".as_ptr().cast_mut();
3132
#[derive(Debug, Default)]
3233
pub struct AcmeMainConfig {
3334
pub issuers: Vec<Issuer>,
35+
pub data: Option<&'static AcmeSharedData>,
3436
pub shm_zone: shared_zone::SharedZone,
3537
}
3638

@@ -500,7 +502,10 @@ impl AcmeMainConfig {
500502
self.shm_zone = SharedZone::Configured(ACME_ZONE_NAME, ACME_ZONE_SIZE);
501503
}
502504

505+
let amcfp = ptr::from_mut(self).cast();
503506
let shm_zone = self.shm_zone.request(cf)?;
507+
shm_zone.init = Some(crate::state::ngx_acme_shared_zone_init);
508+
shm_zone.data = amcfp;
504509
shm_zone.noreuse = 1;
505510

506511
Ok(())

src/conf/issuer.rs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,16 @@ use ngx::collections::{RbTreeMap, Vec};
1313
use ngx::core::{Pool, Status};
1414
use ngx::http::{HttpModuleLocationConf, NgxHttpCoreModule};
1515
use ngx::ngx_log_debug;
16+
use ngx::sync::RwLock;
1617
use openssl::pkey::{PKey, Private};
1718
use thiserror::Error;
1819

1920
use super::order::CertificateOrder;
2021
use super::pkey::PrivateKey;
2122
use super::ssl::NgxSsl;
2223
use super::AcmeMainConfig;
24+
use crate::state::certificate::CertificateContext;
25+
use crate::state::issuer::IssuerContext;
2326

2427
const ACCOUNT_KEY_FILE: &str = "account.key";
2528
const NGX_ACME_DEFAULT_RESOLVER_TIMEOUT: ngx_msec_t = 30000;
@@ -41,8 +44,9 @@ pub struct Issuer {
4144
// Generated fields
4245
// ngx_ssl_t stores a pointer to itself in SSL_CTX ex_data.
4346
pub ssl: Box<NgxSsl, Pool>,
44-
pub orders: RbTreeMap<CertificateOrder<ngx_str_t, Pool>, (), Pool>,
47+
pub orders: RbTreeMap<CertificateOrder<ngx_str_t, Pool>, CertificateContext, Pool>,
4548
pub pkey: Option<PKey<Private>>,
49+
pub data: Option<&'static RwLock<IssuerContext>>,
4650
}
4751

4852
#[derive(Debug, Error)]
@@ -83,6 +87,7 @@ impl Issuer {
8387
ssl,
8488
pkey: None,
8589
orders: RbTreeMap::try_new_in(alloc)?,
90+
data: None,
8691
})
8792
}
8893

@@ -158,7 +163,9 @@ impl Issuer {
158163
self.name
159164
);
160165

161-
if self.orders.try_insert(order.clone(), ()).is_err() {
166+
let cert = CertificateContext::Empty;
167+
168+
if self.orders.try_insert(order.clone(), cert).is_err() {
162169
return Err(Status::NGX_ERROR);
163170
}
164171
} else {

src/conf/shared_zone.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use core::ffi::c_void;
22
use core::ptr::{self, NonNull};
33

44
use nginx_sys::{ngx_conf_t, ngx_int_t, ngx_shm_zone_t, ngx_str_t, NGX_ERROR};
5-
use ngx::core::Status;
5+
use ngx::core::{SlabPool, Status};
66
use ngx::http::HttpModule;
77
use ngx::log::ngx_cycle_log;
88
use ngx::{ngx_log_debug, ngx_string};
@@ -32,6 +32,13 @@ pub enum SharedZoneError {
3232
}
3333

3434
impl SharedZone {
35+
pub fn allocator(&self) -> Option<SlabPool> {
36+
match self {
37+
Self::Ready(zone) => unsafe { SlabPool::from_shm_zone(zone.as_ref()) },
38+
_ => None,
39+
}
40+
}
41+
3542
pub fn is_configured(&self) -> bool {
3643
!matches!(self, Self::Unset)
3744
}

src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ use crate::conf::{AcmeMainConfig, AcmeServerConfig, NGX_HTTP_ACME_COMMANDS};
1414
use crate::variables::NGX_HTTP_ACME_VARS;
1515

1616
mod conf;
17+
mod state;
18+
mod time;
1719
mod variables;
1820

1921
#[derive(Debug)]

src/state.rs

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
//! Shared runtime state of the module.
2+
use core::ffi::c_void;
3+
use core::ptr;
4+
5+
use nginx_sys::{ngx_int_t, ngx_shm_zone_t, NGX_LOG_EMERG};
6+
use ngx::allocator::{AllocError, Allocator, Box, TryCloneIn};
7+
use ngx::collections::Queue;
8+
use ngx::core::{SlabPool, Status};
9+
use ngx::log::ngx_cycle_log;
10+
use ngx::sync::RwLock;
11+
use ngx::{ngx_log_debug, ngx_log_error};
12+
13+
use crate::conf::shared_zone::SharedZone;
14+
use crate::conf::AcmeMainConfig;
15+
16+
pub use self::certificate::CertificateContext;
17+
pub use self::issuer::IssuerContext;
18+
19+
pub mod certificate;
20+
pub mod issuer;
21+
22+
#[derive(Debug)]
23+
pub struct AcmeSharedData<A = SlabPool>
24+
where
25+
A: Allocator + Clone,
26+
{
27+
pub issuers: Queue<RwLock<IssuerContext>, A>,
28+
}
29+
30+
impl<A> AcmeSharedData<A>
31+
where
32+
A: Allocator + Clone,
33+
{
34+
pub fn try_new_in(alloc: A) -> Result<Self, AllocError> {
35+
Ok(Self {
36+
issuers: Queue::try_new_in(alloc)?,
37+
})
38+
}
39+
}
40+
41+
pub extern "C" fn ngx_acme_shared_zone_init(
42+
shm_zone: *mut ngx_shm_zone_t,
43+
data: *mut c_void,
44+
) -> ngx_int_t {
45+
// SAFETY: shm_zone is always valid in this callback
46+
let shm_zone = unsafe { &mut *shm_zone };
47+
let log = ngx_cycle_log().as_ptr();
48+
49+
ngx_log_debug!(
50+
log,
51+
"acme: init shared zone \"{}:{}\"",
52+
shm_zone.shm.name,
53+
shm_zone.shm.size,
54+
);
55+
56+
let oamcf = unsafe { data.cast::<AcmeMainConfig>().as_ref() };
57+
let amcf = unsafe { shm_zone.data.cast::<AcmeMainConfig>().as_mut().unwrap() };
58+
let zone = SharedZone::Ready(shm_zone.into());
59+
60+
let mut alloc = zone.allocator().expect("shared zone allocator");
61+
62+
// Our shared zone is `noreuse`, meaning that we get an empty zone every time unless we are
63+
// running on Windows.
64+
65+
let Ok(mut data) =
66+
AcmeSharedData::try_new_in(alloc.clone()).and_then(|x| Box::try_new_in(x, alloc.clone()))
67+
else {
68+
ngx_log_error!(NGX_LOG_EMERG, log, "cannot allocate acme shared data");
69+
return Status::NGX_ERROR.into();
70+
};
71+
72+
for issuer in &mut amcf.issuers[..] {
73+
// Create new shared data.
74+
let Ok(ctx) = IssuerContext::try_new_in(issuer, alloc.clone()) else {
75+
ngx_log_error!(
76+
NGX_LOG_EMERG,
77+
log,
78+
"cannot allocate acme issuer \"{}\"",
79+
issuer.name,
80+
);
81+
return Status::NGX_ERROR.into();
82+
};
83+
84+
// Copy data from the previous cycle.
85+
if let Some(oissuer) = oamcf.and_then(|x| x.issuer(&issuer.name)) {
86+
ngx_log_debug!(log, "acme: copy old data for issuer \"{}\"", issuer.name);
87+
88+
for (order, ctx) in issuer.orders.iter_mut() {
89+
// Should not fail as we just allocated all the certificate contexts.
90+
let CertificateContext::Shared(ctx) = ctx else {
91+
continue;
92+
};
93+
94+
let Some(CertificateContext::Shared(octx)) = oissuer.orders.get(order) else {
95+
continue;
96+
};
97+
98+
// The old shared zone is going away as soon as we're done, so we have to copy the
99+
// data to the new slab pool.
100+
let Ok(cloned) = octx.read().try_clone_in(alloc.clone()) else {
101+
return Status::NGX_ERROR.into();
102+
};
103+
104+
*ctx.write() = cloned;
105+
}
106+
}
107+
108+
if let Ok(ctx) = data.issuers.push_back(RwLock::new(ctx)) {
109+
// SAFETY: we ensured that the chosen data structure will not move the IssuerContext,
110+
// thus the pointer will remain valid beyond this scope.
111+
//
112+
// The assigned lifetime is a bit misleading though; shared zone will be unmapped
113+
// while the main config is still present, right before calling the cycle pool cleanup.
114+
// A proper ownership-tracking pointer could attempt to unref the data from the config
115+
// destructor _after_ the zone is unmapped and thus trip on an invalid address.
116+
//
117+
// Of all the ways to handle that, we are picking the most obviously unsafe to make
118+
// sure this detail is not missed while reading.
119+
issuer.data = Some(unsafe { &*ptr::from_ref(ctx) });
120+
} else {
121+
ngx_log_error!(
122+
NGX_LOG_EMERG,
123+
log,
124+
"cannot allocate acme issuer \"{}\"",
125+
issuer.name,
126+
);
127+
return Status::NGX_ERROR.into();
128+
}
129+
}
130+
131+
// Will be freed when the zone is unmapped.
132+
let data = Box::leak(data);
133+
134+
alloc.as_mut().data = ptr::from_mut(data).cast();
135+
136+
amcf.data = Some(data);
137+
amcf.shm_zone = zone;
138+
139+
Status::NGX_OK.into()
140+
}

0 commit comments

Comments
 (0)