Skip to content

Commit 131a681

Browse files
committed
Set default value for "state_path".
Set default state_path to "acme_{issuer.name}" and introduce a special value "off" to revert to a stateless behavior. As usual, relative paths in configuration are resolved against NGX_PREFIX. The NGX_ACME_STATE_PREFIX environment variable can be set during build to override this behavior. The way we take the option may look a bit unusual, but unfortunately there's no infra for adding module-specific options in auto/configure. Fixes #32.
1 parent f70c0ce commit 131a681

File tree

5 files changed

+93
-4
lines changed

5 files changed

+93
-4
lines changed

README.md

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,29 @@ The result will be located at `objs/ngx_http_acme_module.so`.
5757
Currently this method produces a slightly larger library, as we don't instruct
5858
the linker to perform LTO and remove unused code.
5959

60+
#### Build options
61+
62+
As there is no mechanism to add third-party module configuration options to
63+
auto/configure, all the module build-time options are set via environment
64+
variables passed to the `cargo build` or `make` commands.
65+
Currently accepted options are:
66+
67+
- `NGX_ACME_STATE_PREFIX`: sets a default prefix for per-issuer state paths.
68+
If unset, state paths are created relative to the NGINX prefix directory.
69+
The prefix directory should exist and be readable to the worker processes.
70+
71+
Example:
72+
73+
```sh
74+
export NGX_ACME_STATE_PREFIX=/var/cache/nginx
75+
auto/configure \
76+
... \
77+
--with-compat \
78+
--with-http_ssl_module \
79+
--add-dynamic-module=/path/to/nginx-acme
80+
make
81+
```
82+
6083
### Testing
6184

6285
The repository contains an integration test suite based on the [nginx-tests].
@@ -218,9 +241,9 @@ Enables or disables verification of the ACME server certificate.
218241

219242
### state_path
220243

221-
**Syntax:** state_path `path`
244+
**Syntax:** state_path `path` | `off`
222245

223-
**Default:** -
246+
**Default:** acme_`name` or `$NGX_ACME_STATE_PREFIX`/acme_`name`
224247

225248
**Context:** acme_issuer
226249

src/conf.rs

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ pub mod ssl;
3434

3535
const NGX_CONF_DUPLICATE: *mut c_char = c"is duplicate".as_ptr().cast_mut();
3636
const NGX_CONF_INVALID_VALUE: *mut c_char = c"invalid value".as_ptr().cast_mut();
37+
pub const NGX_CONF_UNSET_PTR: *mut core::ffi::c_void = nginx_sys::NGX_CONF_UNSET as _;
3738

3839
/// Main (http block) level configuration.
3940
#[derive(Debug, Default)]
@@ -132,7 +133,7 @@ static mut NGX_HTTP_ACME_ISSUER_COMMANDS: [ngx_command_t; 9] = [
132133
ngx_command_t {
133134
name: ngx_string!("state_path"),
134135
type_: NGX_CONF_TAKE1 as ngx_uint_t,
135-
set: Some(nginx_sys::ngx_conf_set_path_slot),
136+
set: Some(cmd_issuer_set_state_path),
136137
conf: 0,
137138
offset: mem::offset_of!(Issuer, state_path),
138139
post: ptr::null_mut(),
@@ -479,6 +480,28 @@ extern "C" fn cmd_issuer_set_uri(
479480
NGX_CONF_OK
480481
}
481482

483+
/// A wrapper over the `ngx_conf_set_path_slot` that takes the "off" value to disable persistency.
484+
extern "C" fn cmd_issuer_set_state_path(
485+
cf: *mut ngx_conf_t,
486+
cmd: *mut ngx_command_t,
487+
conf: *mut c_void,
488+
) -> *mut c_char {
489+
let cf = unsafe { cf.as_mut().expect("cf ptr is always valid") };
490+
let issuer = unsafe { conf.cast::<Issuer>().as_mut().expect("issuer conf") };
491+
492+
if issuer.state_path != NGX_CONF_UNSET_PTR.cast() {
493+
return NGX_CONF_DUPLICATE;
494+
}
495+
496+
issuer.state_path = ptr::null_mut();
497+
498+
if cf.args().get(1).map(ngx_str_t::as_bytes) == Some(b"off") {
499+
return NGX_CONF_OK;
500+
}
501+
502+
unsafe { nginx_sys::ngx_conf_set_path_slot(cf, cmd, ptr::from_mut(issuer).cast()) }
503+
}
504+
482505
extern "C" fn cmd_issuer_set_accept_tos(
483506
_cf: *mut ngx_conf_t,
484507
_cmd: *mut ngx_command_t,

src/conf/issuer.rs

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ impl Issuer {
100100
resolver_timeout: NGX_CONF_UNSET_MSEC,
101101
ssl_trusted_certificate: ngx_str_t::empty(),
102102
ssl_verify: NGX_CONF_UNSET_FLAG,
103-
state_path: ptr::null_mut(),
103+
state_path: super::NGX_CONF_UNSET_PTR.cast(),
104104
accept_tos: None,
105105
ssl,
106106
pkey: None,
@@ -128,6 +128,22 @@ impl Issuer {
128128
return Err(IssuerError::Uri);
129129
}
130130

131+
if self.state_path == super::NGX_CONF_UNSET_PTR.cast() {
132+
let mut init: nginx_sys::ngx_path_init_t = unsafe { core::mem::zeroed() };
133+
init.name = default_state_path(cf, &self.name)?;
134+
135+
self.state_path = ptr::null_mut();
136+
137+
unsafe {
138+
nginx_sys::ngx_conf_merge_path_value(
139+
cf,
140+
&mut self.state_path,
141+
ptr::null_mut(),
142+
&mut init,
143+
)
144+
};
145+
}
146+
131147
if matches!(self.account_key, PrivateKey::Unset) {
132148
self.account_key = PrivateKey::default();
133149
}
@@ -306,6 +322,26 @@ impl Issuer {
306322
}
307323
}
308324

325+
fn default_state_path(cf: &mut ngx_conf_t, name: &ngx_str_t) -> Result<ngx_str_t, AllocError> {
326+
let mut path = Vec::new_in(cf.pool());
327+
let reserve = "acme_".len() + name.len;
328+
329+
if let Some(p) = core::option_env!("NGX_ACME_STATE_PREFIX") {
330+
let p = p.trim_end_matches('/');
331+
path.try_reserve_exact(p.len() + reserve + 1)
332+
.map_err(|_| AllocError)?;
333+
path.extend(p.as_bytes());
334+
path.push(b'/');
335+
}
336+
337+
path.try_reserve_exact(reserve).map_err(|_| AllocError)?;
338+
path.extend(b"acme_");
339+
path.extend(name.as_bytes());
340+
341+
let (data, len, _) = path.into_raw_parts();
342+
Ok(ngx_str_t { data, len })
343+
}
344+
309345
#[derive(Debug, thiserror::Error)]
310346
enum CachedCertificateError {
311347
#[error(transparent)]

t/acme_conf_certificate.t

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ http {
5555
acme_issuer example {
5656
uri https://localhost:%%PORT_9000%%/dir;
5757
ssl_verify off;
58+
state_path %%TESTDIR%%;
5859
}
5960
6061
resolver 127.0.0.1:%%PORT_8980_UDP%%;

t/acme_conf_issuer.t

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ like(check($t, <<'EOF' ), qr/\[emerg].*"resolver" is not/, 'no resolver');
8484
acme_issuer example {
8585
uri https://localhost:%%PORT_9000%%/dir;
8686
ssl_verify off;
87+
state_path %%TESTDIR%%;
8788
}
8889
8990
EOF
@@ -96,6 +97,7 @@ acme_shared_zone bad-value;
9697
acme_issuer example {
9798
uri https://localhost:%%PORT_9000%%/dir;
9899
ssl_verify off;
100+
state_path %%TESTDIR%%;
99101
}
100102
101103
resolver 127.0.0.1:%%PORT_8980_UDP%%;
@@ -110,6 +112,7 @@ acme_shared_zone zone=test:bad-size;
110112
acme_issuer example {
111113
uri https://localhost:%%PORT_9000%%/dir;
112114
ssl_verify off;
115+
state_path %%TESTDIR%%;
113116
}
114117
115118
resolver 127.0.0.1:%%PORT_8980_UDP%%;
@@ -123,6 +126,7 @@ acme_issuer example {
123126
uri https://localhost:%%PORT_9000%%/dir;
124127
account_key no-such-file.key;
125128
ssl_verify off;
129+
state_path %%TESTDIR%%;
126130
}
127131
128132
resolver 127.0.0.1:%%PORT_8980_UDP%%;
@@ -136,6 +140,7 @@ acme_issuer example {
136140
uri https://localhost:%%PORT_9000%%/dir;
137141
account_key ecdsa:234;
138142
ssl_verify off;
143+
state_path %%TESTDIR%%;
139144
}
140145
141146
resolver 127.0.0.1:%%PORT_8980_UDP%%;
@@ -149,6 +154,7 @@ acme_issuer example {
149154
uri https://localhost:%%PORT_9000%%/dir;
150155
account_key rsa:1024;
151156
ssl_verify off;
157+
state_path %%TESTDIR%%;
152158
}
153159
154160
resolver 127.0.0.1:%%PORT_8980_UDP%%;

0 commit comments

Comments
 (0)