Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added built-in certificates autorenewal #35

Closed
wants to merge 3 commits into from
Closed
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
45 changes: 44 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,9 @@ and keys in shared memory, then set variable with the zone name.

## Automatic Certificate Renewal

NGINX and NJS do not yet have a mechanism for running code on a time interval, which presents a challenge for certificate renewal. One workaround to this is to set something up to periodically request `/acme/auto` from the NGINX server.
### NGINX opensource

NGINX opensource and NJS do not yet have a mechanism for running code on a time interval, which presents a challenge for certificate renewal. One workaround to this is to set something up to periodically request `/acme/auto` from the NGINX server.

If running directly on a host, you can use `cron` to schedule a periodic request. When deploying in Kubernetes you can use a [liveness-check](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/#define-a-liveness-http-request). If you are running in a `docker` context, you can use Docker's `healthcheck:` functionality to do this.

Expand All @@ -197,6 +199,47 @@ service:

This configuration will request `/acme/auto` every 90 seconds. If the certificate is nearing expiry, it will be automatically renewed.

### NGINX Plus

If using NGINX Plus, certificate renewal is automatically managed through active healthchecks to send a `GET` request to `/acme/auto` every 90 seconds. The relevant configuration section is

```nginx
# Internal upstream for automated certificates renewal - requires NGINX Plus
upstream acme_auto_renewal {
zone acme_auto_renewal 64k;
server 127.0.0.1:10080;
}

## Internal certificates renewal server - requires NGINX Plus
# GETs proxy.nginx.com:8000/acme/auto every 90 seconds to automatically renews certificates
server {
listen 127.0.0.1:10080;

location / {
internal;
health_check interval=90;
health_check uri=/internal_auto_renewal;
proxy_pass http://acme_auto_renewal;
}

location = /internal_auto_renewal {
proxy_set_header Host proxy.nginx.com;
proxy_pass http://127.0.0.1:8000/acme/auto;
}
}
```

This configuration will request `/acme/auto` every 90 seconds. If the certificate is nearing expiry, it will be automatically renewed.

In order to use NGINX Plus and automated certificate renewal you will need to use `examples/nginxplus.conf` as the main configuration file by running:

```
cp examples/nginxplus.conf examples/nginx.conf
```

and then following the installation instructions above here


## Advanced
### Serving challenges directly
If you do not wish to use `js_content acme.challengeResponse` to respond to challenge requests, then you can serve them directly with NGINX. Just be sure that the `root` directive value in your location block matches the value of `$njs_acme_challenge_dir`.
Expand Down
2 changes: 1 addition & 1 deletion examples/nginx.conf
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ http {
js_shared_dict_zone zone=acme:1m;

server {
listen 0.0.0.0:8000; # testing with 8000 should be 80 in prod, pebble usees httpPort in dev/pebble/config.json
listen 0.0.0.0:8000; # testing with 8000 should be 80 in prod, pebble uses httpPort in dev/pebble/config.json
listen 443 ssl;
server_name proxy.nginx.com;

Expand Down
104 changes: 104 additions & 0 deletions examples/nginxplus.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
daemon off;
user nginx;

load_module modules/ngx_http_js_module.so;

error_log /dev/stdout debug;

events {
}

http {
# Internal upstream for automated certificates renewal - Requires NGINX Plus
upstream acme_auto_renewal {
zone acme_auto_renewal 64k;
server 127.0.0.1:10080;
}

## Internal certificates renewal server - Requires NGINX Plus
# GETs proxy.nginx.com:8000/acme/auto every 90 seconds to automatically renews certificates
server {
listen 127.0.0.1:10080;

location / {
internal;
health_check interval=90;
health_check uri=/internal_auto_renewal;
proxy_pass http://acme_auto_renewal;
}

location = /internal_auto_renewal {
proxy_set_header Host proxy.nginx.com;
proxy_pass http://127.0.0.1:8000/acme/auto;
}
}

js_path "/usr/lib/nginx/njs_modules/";
js_fetch_trusted_certificate /etc/ssl/certs/ISRG_Root_X1.pem;

js_import acme from acme.js;

# One `resolver` directive must be defined.
resolver 127.0.0.11 ipv6=off; # docker-compose
# resolver 1.1.1.1 1.0.0.1 [2606:4700:4700::1111] [2606:4700:4700::1001] valid=300s; # Cloudflare
# resolver 8.8.8.8 8.8.4.4; # Google
# resolver 172.16.0.23; # AWS EC2 Classic
# resolver 169.254.169.253; # AWS VPC
resolver_timeout 5s;

## advanced use-case for njs-acme to optionally use shared dict to cache cert/key pairs
# then provide the same zone name in $njs_acme_shared_dict_zone_name
# zone size should be enough to store all certs and keys; 1MB should be enough to store 100 certs/keys
js_shared_dict_zone zone=acme:1m;

server {
listen 0.0.0.0:8000; # testing with 8000 should be 80 in prod, pebble uses httpPort in dev/pebble/config.json
listen 443 ssl;
server_name proxy.nginx.com;

# The full set of configuration variables. These can also be defined in
# environment variables. Use the same name as below, just UPPER_CASE.
## Mandatory Variables
js_var $njs_acme_server_names "proxy.nginx.com proxy2.nginx.com";
js_var $njs_acme_account_email "[email protected]";

## Optional Variables
# js_var $njs_acme_dir /etc/nginx/njs-acme;
# js_var $njs_acme_challenge_dir /etc/nginx/njs-acme/challenge;
# js_var $njs_acme_account_private_jwk /etc/nginx/njs-acme/account_private_key.json;
# js_var $njs_acme_directory_uri https://pebble/dir;
# js_var $njs_acme_verify_provider_https false;

## advanced use-case
# # use a `js_shared_dict_zone` to store certs/keys in memory
js_var $njs_acme_shared_dict_zone_name acme;

js_set $dynamic_ssl_cert acme.js_cert;
js_set $dynamic_ssl_key acme.js_key;



ssl_certificate data:$dynamic_ssl_cert;
ssl_certificate_key data:$dynamic_ssl_key;

location = / {
return 200 "hello server_name:$server_name\nssl_session_id:$ssl_session_id\n";
}

location ~ "^/\.well-known/acme-challenge/[-_A-Za-z0-9]{22,128}$" {
js_content acme.challengeResponse;
}

location = /acme/auto {
js_content acme.clientAutoMode;
}

location = /csr/new {
js_content acme.createCsrHandler;
}

location = /acme/new-acct {
js_content acme.acmeNewAccount;
}
}
}