diff --git a/README.md b/README.md index 2156165..7c6d0bb 100644 --- a/README.md +++ b/README.md @@ -57,6 +57,29 @@ The result will be located at `objs/ngx_http_acme_module.so`. Currently this method produces a slightly larger library, as we don't instruct the linker to perform LTO and remove unused code. +#### Build options + +As there is no mechanism to add third-party module configuration options to +auto/configure, all the module build-time options are set via environment +variables passed to the `cargo build` or `make` commands. +Currently accepted options are: + + - `NGX_ACME_STATE_PREFIX`: sets a default prefix for per-issuer state paths. + If unset, state paths are created relative to the NGINX prefix directory. + The prefix directory should exist and be readable to the worker processes. + +Example: + +```sh +export NGX_ACME_STATE_PREFIX=/var/cache/nginx +auto/configure \ + ... \ + --with-compat \ + --with-http_ssl_module \ + --add-dynamic-module=/path/to/nginx-acme +make +``` + ### Testing 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. ### state_path -**Syntax:** state_path `path` +**Syntax:** state_path `path` | `off` -**Default:** - +**Default:** acme_`name` or `$NGX_ACME_STATE_PREFIX`/acme_`name` **Context:** acme_issuer diff --git a/src/conf.rs b/src/conf.rs index edec702..fce646c 100644 --- a/src/conf.rs +++ b/src/conf.rs @@ -34,6 +34,7 @@ pub mod ssl; const NGX_CONF_DUPLICATE: *mut c_char = c"is duplicate".as_ptr().cast_mut(); const NGX_CONF_INVALID_VALUE: *mut c_char = c"invalid value".as_ptr().cast_mut(); +pub const NGX_CONF_UNSET_PTR: *mut core::ffi::c_void = nginx_sys::NGX_CONF_UNSET as _; /// Main (http block) level configuration. #[derive(Debug, Default)] @@ -132,7 +133,7 @@ static mut NGX_HTTP_ACME_ISSUER_COMMANDS: [ngx_command_t; 9] = [ ngx_command_t { name: ngx_string!("state_path"), type_: NGX_CONF_TAKE1 as ngx_uint_t, - set: Some(nginx_sys::ngx_conf_set_path_slot), + set: Some(cmd_issuer_set_state_path), conf: 0, offset: mem::offset_of!(Issuer, state_path), post: ptr::null_mut(), @@ -479,6 +480,28 @@ extern "C" fn cmd_issuer_set_uri( NGX_CONF_OK } +/// A wrapper over the `ngx_conf_set_path_slot` that takes the "off" value to disable persistency. +extern "C" fn cmd_issuer_set_state_path( + cf: *mut ngx_conf_t, + cmd: *mut ngx_command_t, + conf: *mut c_void, +) -> *mut c_char { + let cf = unsafe { cf.as_mut().expect("cf ptr is always valid") }; + let issuer = unsafe { conf.cast::().as_mut().expect("issuer conf") }; + + if issuer.state_path != NGX_CONF_UNSET_PTR.cast() { + return NGX_CONF_DUPLICATE; + } + + issuer.state_path = ptr::null_mut(); + + if cf.args().get(1).map(ngx_str_t::as_bytes) == Some(b"off") { + return NGX_CONF_OK; + } + + unsafe { nginx_sys::ngx_conf_set_path_slot(cf, cmd, ptr::from_mut(issuer).cast()) } +} + extern "C" fn cmd_issuer_set_accept_tos( _cf: *mut ngx_conf_t, _cmd: *mut ngx_command_t, diff --git a/src/conf/issuer.rs b/src/conf/issuer.rs index 4ed624f..21c20f3 100644 --- a/src/conf/issuer.rs +++ b/src/conf/issuer.rs @@ -100,7 +100,7 @@ impl Issuer { resolver_timeout: NGX_CONF_UNSET_MSEC, ssl_trusted_certificate: ngx_str_t::empty(), ssl_verify: NGX_CONF_UNSET_FLAG, - state_path: ptr::null_mut(), + state_path: super::NGX_CONF_UNSET_PTR.cast(), accept_tos: None, ssl, pkey: None, @@ -128,6 +128,22 @@ impl Issuer { return Err(IssuerError::Uri); } + if self.state_path == super::NGX_CONF_UNSET_PTR.cast() { + let mut init: nginx_sys::ngx_path_init_t = unsafe { core::mem::zeroed() }; + init.name = default_state_path(cf, &self.name)?; + + self.state_path = ptr::null_mut(); + + unsafe { + nginx_sys::ngx_conf_merge_path_value( + cf, + &mut self.state_path, + ptr::null_mut(), + &mut init, + ) + }; + } + if matches!(self.account_key, PrivateKey::Unset) { self.account_key = PrivateKey::default(); } @@ -306,6 +322,26 @@ impl Issuer { } } +fn default_state_path(cf: &mut ngx_conf_t, name: &ngx_str_t) -> Result { + let mut path = Vec::new_in(cf.pool()); + let reserve = "acme_".len() + name.len; + + if let Some(p) = core::option_env!("NGX_ACME_STATE_PREFIX") { + let p = p.trim_end_matches('/'); + path.try_reserve_exact(p.len() + reserve + 1) + .map_err(|_| AllocError)?; + path.extend(p.as_bytes()); + path.push(b'/'); + } + + path.try_reserve_exact(reserve).map_err(|_| AllocError)?; + path.extend(b"acme_"); + path.extend(name.as_bytes()); + + let (data, len, _) = path.into_raw_parts(); + Ok(ngx_str_t { data, len }) +} + #[derive(Debug, thiserror::Error)] enum CachedCertificateError { #[error(transparent)] diff --git a/t/acme_conf_certificate.t b/t/acme_conf_certificate.t index b96cfb5..c9b1a37 100644 --- a/t/acme_conf_certificate.t +++ b/t/acme_conf_certificate.t @@ -55,6 +55,7 @@ http { acme_issuer example { uri https://localhost:%%PORT_9000%%/dir; ssl_verify off; + state_path %%TESTDIR%%; } resolver 127.0.0.1:%%PORT_8980_UDP%%; diff --git a/t/acme_conf_issuer.t b/t/acme_conf_issuer.t index 3b2806b..64951f2 100644 --- a/t/acme_conf_issuer.t +++ b/t/acme_conf_issuer.t @@ -84,6 +84,7 @@ like(check($t, <<'EOF' ), qr/\[emerg].*"resolver" is not/, 'no resolver'); acme_issuer example { uri https://localhost:%%PORT_9000%%/dir; ssl_verify off; + state_path %%TESTDIR%%; } EOF @@ -96,6 +97,7 @@ acme_shared_zone bad-value; acme_issuer example { uri https://localhost:%%PORT_9000%%/dir; ssl_verify off; + state_path %%TESTDIR%%; } resolver 127.0.0.1:%%PORT_8980_UDP%%; @@ -110,6 +112,7 @@ acme_shared_zone zone=test:bad-size; acme_issuer example { uri https://localhost:%%PORT_9000%%/dir; ssl_verify off; + state_path %%TESTDIR%%; } resolver 127.0.0.1:%%PORT_8980_UDP%%; @@ -123,6 +126,7 @@ acme_issuer example { uri https://localhost:%%PORT_9000%%/dir; account_key no-such-file.key; ssl_verify off; + state_path %%TESTDIR%%; } resolver 127.0.0.1:%%PORT_8980_UDP%%; @@ -136,6 +140,7 @@ acme_issuer example { uri https://localhost:%%PORT_9000%%/dir; account_key ecdsa:234; ssl_verify off; + state_path %%TESTDIR%%; } resolver 127.0.0.1:%%PORT_8980_UDP%%; @@ -149,6 +154,7 @@ acme_issuer example { uri https://localhost:%%PORT_9000%%/dir; account_key rsa:1024; ssl_verify off; + state_path %%TESTDIR%%; } resolver 127.0.0.1:%%PORT_8980_UDP%%;