Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 25 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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].
Expand Down Expand Up @@ -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

Expand Down
25 changes: 24 additions & 1 deletion src/conf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down Expand Up @@ -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(),
Expand Down Expand Up @@ -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::<Issuer>().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,
Expand Down
38 changes: 37 additions & 1 deletion src/conf/issuer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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();
}
Expand Down Expand Up @@ -306,6 +322,26 @@ impl Issuer {
}
}

fn default_state_path(cf: &mut ngx_conf_t, name: &ngx_str_t) -> Result<ngx_str_t, AllocError> {
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)]
Expand Down
1 change: 1 addition & 0 deletions t/acme_conf_certificate.t
Original file line number Diff line number Diff line change
Expand Up @@ -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%%;
Expand Down
6 changes: 6 additions & 0 deletions t/acme_conf_issuer.t
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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%%;
Expand All @@ -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%%;
Expand All @@ -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%%;
Expand All @@ -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%%;
Expand All @@ -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%%;
Expand Down
Loading