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
52 changes: 21 additions & 31 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ the linker to perform LTO and dead code elimination.
## How to Use

Add the module to the nginx configuration and configure as described below.
Note that this module requires a [resolver] configuration in the `http` block.

[resolver]: https://nginx.org/en/docs/http/ngx_http_core_module.html#resolver

## Example Configuration

Expand All @@ -54,8 +57,9 @@ resolver 127.0.0.1:53;

acme_issuer example {
uri https://acme.example.com/directory;
contact mailto:[email protected];
contact [email protected];
state_path /var/lib/nginx/acme-example;
accept_terms_of_service;
}

acme_shared_zone zone=acme_shared:1M;
Expand Down Expand Up @@ -133,39 +137,11 @@ restart unless [](#state_path) is configured.
**Context:** acme_issuer

An array of URLs that the ACME server can use to contact the client for issues
related to this account.
related to this account. The `mailto:` scheme will be assumed unless specified
explicitly.

Can be specified multiple times.

### resolver

**Syntax:** resolver `address` ... [ `valid` = `time` ] [ `ipv4` = `on` | `off` ] [ `ipv6` = `on` | `off` ] [ `status_zone` = `zone` ]

**Default:** -

**Context:** acme_issuer

Configures name servers used to resolve names of upstream servers into
addresses.
See [resolver](https://nginx.org/en/docs/http/ngx_http_core_module.html#resolver)
for the parameter reference.

Required, but can be inherited from the `http` block.
### resolver_timeout

**Syntax:** resolver_timeout `time`

**Default:** 30s

**Context:** acme_issuer

Sets a timeout for name resolution, for example:

```nginx
resolver_timeout 5s;

```

### ssl_trusted_certificate

**Syntax:** ssl_trusted_certificate `file`
Expand Down Expand Up @@ -203,6 +179,20 @@ help with rate-limiting ACME servers.
The directory, if configured, will contain sensitive content:
the account key, the issued certificates and private keys.

### accept_terms_of_service

**Syntax:** accept_terms_of_service

**Default:** -

**Context:** acme_issuer

Agree to the terms under which the ACME server is to be used.

Some servers require the user to agree with the terms of service before
registering an account. The text is usually available on the ACME server's
website and the URL will be printed to the error log if necessary.

### acme_shared_zone

**Syntax:** acme_shared_zone `zone` = `name:size`
Expand Down
111 changes: 63 additions & 48 deletions src/conf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@ use core::{mem, ptr};

use nginx_sys::{
ngx_command_t, ngx_conf_parse, ngx_conf_t, ngx_http_core_srv_conf_t, ngx_str_t, ngx_uint_t,
NGX_CONF_1MORE, NGX_CONF_BLOCK, NGX_CONF_FLAG, NGX_CONF_TAKE1, NGX_HTTP_MAIN_CONF,
NGX_HTTP_MAIN_CONF_OFFSET, NGX_HTTP_SRV_CONF, NGX_HTTP_SRV_CONF_OFFSET, NGX_LOG_EMERG,
NGX_CONF_1MORE, NGX_CONF_BLOCK, NGX_CONF_FLAG, NGX_CONF_NOARGS, NGX_CONF_TAKE1,
NGX_HTTP_MAIN_CONF, NGX_HTTP_MAIN_CONF_OFFSET, NGX_HTTP_SRV_CONF, NGX_HTTP_SRV_CONF_OFFSET,
NGX_LOG_EMERG,
};
use ngx::collections::Vec;
use ngx::core::{Pool, Status, NGX_CONF_ERROR, NGX_CONF_OK};
Expand Down Expand Up @@ -71,7 +72,7 @@ pub static mut NGX_HTTP_ACME_COMMANDS: [ngx_command_t; 4] = [
ngx_command_t::empty(),
];

static mut NGX_HTTP_ACME_ISSUER_COMMANDS: [ngx_command_t; 9] = [
static mut NGX_HTTP_ACME_ISSUER_COMMANDS: [ngx_command_t; 8] = [
ngx_command_t {
name: ngx_string!("uri"),
type_: NGX_CONF_TAKE1 as ngx_uint_t,
Expand All @@ -96,22 +97,6 @@ static mut NGX_HTTP_ACME_ISSUER_COMMANDS: [ngx_command_t; 9] = [
offset: 0,
post: ptr::null_mut(),
},
ngx_command_t {
name: ngx_string!("resolver"),
type_: NGX_CONF_TAKE1 as ngx_uint_t,
set: Some(cmd_issuer_set_resolver),
conf: 0,
offset: 0,
post: ptr::null_mut(),
},
ngx_command_t {
name: ngx_string!("resolver_timeout"),
type_: NGX_CONF_TAKE1 as ngx_uint_t,
set: Some(nginx_sys::ngx_conf_set_msec_slot),
conf: 0,
offset: mem::offset_of!(Issuer, resolver_timeout),
post: ptr::null_mut(),
},
ngx_command_t {
name: ngx_string!("ssl_trusted_certificate"),
type_: NGX_CONF_TAKE1 as ngx_uint_t,
Expand All @@ -136,6 +121,14 @@ static mut NGX_HTTP_ACME_ISSUER_COMMANDS: [ngx_command_t; 9] = [
offset: mem::offset_of!(Issuer, state_path),
post: ptr::null_mut(),
},
ngx_command_t {
name: ngx_string!("accept_terms_of_service"),
type_: NGX_CONF_NOARGS as ngx_uint_t,
set: Some(cmd_issuer_set_accept_tos),
conf: 0,
offset: 0,
post: ptr::null_mut(),
},
ngx_command_t::empty(),
];

Expand Down Expand Up @@ -308,6 +301,24 @@ extern "C" fn cmd_issuer_add_contact(
_cmd: *mut ngx_command_t,
conf: *mut c_void,
) -> *mut c_char {
const MAILTO: &[u8] = b"mailto:";

fn has_scheme(val: &[u8]) -> bool {
// scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )
if !val[0].is_ascii_alphabetic() {
return false;
}

for c in val {
if c.is_ascii_alphanumeric() || matches!(c, b'+' | b'-' | b'.') {
continue;
}
return *c == b':';
}

false
}

let cf = unsafe { cf.as_mut().expect("cf") };
let issuer = unsafe { conf.cast::<Issuer>().as_mut().expect("issuer conf") };

Expand All @@ -318,11 +329,25 @@ extern "C" fn cmd_issuer_add_contact(
// NGX_CONF_TAKE1 ensures that args contains 2 elements
let args = cf.args();

if core::str::from_utf8(args[1].as_bytes()).is_err() {
return c"contains invalid UTF-8 sequence".as_ptr().cast_mut();
if args[1].is_empty() || core::str::from_utf8(args[1].as_bytes()).is_err() {
return c"invalid value".as_ptr().cast_mut();
};

issuer.contacts.push(args[1]);
if has_scheme(args[1].as_ref()) {
issuer.contacts.push(args[1]);
} else {
let mut value = ngx_str_t::empty();
value.len = MAILTO.len() + args[1].len;
value.data = cf.pool().alloc_unaligned(value.len).cast();
if value.data.is_null() {
return NGX_CONF_ERROR;
}

value.as_bytes_mut()[..MAILTO.len()].copy_from_slice(MAILTO);
value.as_bytes_mut()[MAILTO.len()..].copy_from_slice(args[1].as_ref());

issuer.contacts.push(value);
}

NGX_CONF_OK
}
Expand Down Expand Up @@ -350,32 +375,6 @@ extern "C" fn cmd_issuer_set_account_key(
NGX_CONF_OK
}

extern "C" fn cmd_issuer_set_resolver(
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") };
let issuer = unsafe { conf.cast::<Issuer>().as_mut().expect("issuer conf") };

if issuer.resolver.is_some() {
return NGX_CONF_DUPLICATE;
}

let args = unsafe { &mut *cf.args };
let value: *mut ngx_str_t = args.elts.cast();

issuer.resolver = ptr::NonNull::new(unsafe {
nginx_sys::ngx_resolver_create(cf, value.add(1), args.nelts - 1)
});

if issuer.resolver.is_none() {
return NGX_CONF_ERROR;
}

NGX_CONF_OK
}

extern "C" fn cmd_issuer_set_uri(
cf: *mut ngx_conf_t,
_cmd: *mut ngx_command_t,
Expand All @@ -400,6 +399,22 @@ extern "C" fn cmd_issuer_set_uri(
NGX_CONF_OK
}

extern "C" fn cmd_issuer_set_accept_tos(
_cf: *mut ngx_conf_t,
_cmd: *mut ngx_command_t,
conf: *mut c_void,
) -> *mut c_char {
let issuer = unsafe { conf.cast::<Issuer>().as_mut().expect("issuer conf") };

if issuer.accept_tos.is_some() {
return NGX_CONF_DUPLICATE;
}

issuer.accept_tos = Some(true);

NGX_CONF_OK
}

/* Methods and trait implementations */

impl AcmeMainConfig {
Expand Down
4 changes: 3 additions & 1 deletion src/conf/issuer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ pub struct Issuer {
pub ssl_trusted_certificate: ngx_str_t,
pub ssl_verify: ngx_flag_t,
pub state_path: *mut ngx_path_t,
pub accept_tos: Option<bool>,
// Generated fields
// ngx_ssl_t stores a pointer to itself in SSL_CTX ex_data.
pub ssl: Box<NgxSsl, Pool>,
Expand All @@ -51,7 +52,7 @@ pub enum IssuerError {
AccountKey(super::ssl::CertificateFetchError),
#[error("cannot generate account key: {0}")]
AccountKeyGen(#[from] super::pkey::PKeyGenError),
#[error("resolver is not configured")]
#[error("\"resolver\" is not configured")]
Resolver,
#[error("memory allocation failed")]
Alloc(#[from] AllocError),
Expand Down Expand Up @@ -80,6 +81,7 @@ impl Issuer {
ssl_trusted_certificate: ngx_str_t::empty(),
ssl_verify: NGX_CONF_UNSET_FLAG,
state_path: ptr::null_mut(),
accept_tos: None,
ssl,
pkey: None,
orders: RbTreeMap::try_new_in(alloc)?,
Expand Down
5 changes: 3 additions & 2 deletions t/acme_conf_certificate.t
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,10 @@ http {

acme_issuer example {
uri https://localhost:%%PORT_9000%%/dir;
resolver 127.0.0.1:%%PORT_8980_UDP%%;
ssl_verify off;
ssl_verify off;
}

resolver 127.0.0.1:%%PORT_8980_UDP%%;
}

EOF
Expand Down
Loading
Loading