Skip to content

Commit 274755f

Browse files
authored
Fixes for multiple hostnames (#11)
* Fixes for supporting SAN certs with multiple names * typescript fixes * Revert PROJECT_NAME in Makefile; Some small cleanups in Makefile, package.json, and examples/nginx.conf * remove errant $domain var * add nginxinc/ prefix to image name
1 parent 197b39b commit 274755f

File tree

6 files changed

+69
-60
lines changed

6 files changed

+69
-60
lines changed

Makefile

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@ GREP ?= $(shell command -v ggrep 2> /dev/null || command -v grep 2
44
AWK ?= $(shell command -v gawk 2> /dev/null || command -v awk 2> /dev/null)
55
DOCKER ?= docker
66
PROJECT_NAME ?= njs-acme
7+
DOCKER_IMAGE_NAME ?= nginxinc/nginx-$(PROJECT_NAME)
78
GITHUB_REPOSITORY ?= nginxinc/$(PROJECT_NAME)
89
SRC_REPO := https://github.com/$(GITHUB_REPOSITORY)
9-
CURRENT_DIR = $(shell pwd)
1010

1111
Q = $(if $(filter 1,$V),,@)
1212
M = $(shell printf "\033[34;1m▶\033[0m")
@@ -25,36 +25,35 @@ build: ## Run npm run build
2525

2626
.PHONY: docker-build
2727
docker-build: ## Build docker image
28-
$(DOCKER) buildx build $(DOCKER_BUILD_FLAGS) -t $(PROJECT_NAME) .
28+
$(DOCKER) buildx build $(DOCKER_BUILD_FLAGS) -t $(DOCKER_IMAGE_NAME) .
2929

3030

3131
.PHONY: docker-copy
32-
docker-copy: CONTAINER_ID=$(shell $(DOCKER) create $(PROJECT_NAME))
32+
docker-copy: CONTAINER_NAME=njs_acme_dist_source
3333
docker-copy: docker-build ## Copy the acme.js file out of the container and save in dist/
34-
echo ${CONTAINER_ID}
35-
$(DOCKER) cp ${CONTAINER_ID}:/usr/lib/nginx/njs_modules/acme.js dist/acme.js
36-
$(DOCKER) rm -v ${CONTAINER_ID}
34+
mkdir -p dist
35+
$(DOCKER) create --name $(CONTAINER_NAME) $(DOCKER_IMAGE_NAME)
36+
$(DOCKER) cp $(CONTAINER_NAME):/usr/lib/nginx/njs_modules/acme.js dist/acme.js
37+
$(DOCKER) rm -v $(CONTAINER_NAME)
3738

3839

3940
.PHONY: docker-nginx
4041
docker-nginx: docker-build ## Start nginx container
41-
$(DOCKER) run --rm -it -p 8000:8000 \
42-
-e "NJS_ACME_DIR=/etc/nginx/njs-acme" \
43-
$(PROJECT_NAME)
42+
$(DOCKER) run --rm -it -p 8000:8000 -p \
43+
$(DOCKER_IMAGE_NAME)
4444

4545

4646
.PHONY: docker-njs
4747
docker-njs: docker-build ## Start nginx container and run `njs`
48-
$(DOCKER) run --rm -it -p 8000:8000 \
49-
-e "NJS_ACME_DIR=/etc/nginx/njs-acme" \
50-
$(PROJECT_NAME) njs
48+
$(DOCKER) run --rm -it \
49+
$(DOCKER_IMAGE_NAME) njs
5150

5251

5352
.PHONY: docker-devup
54-
docker-devup: docker-build ## Start all docker compose services
53+
docker-devup: docker-build ## Start all docker compose services for development/testing
5554
$(DOCKER) compose up -d
5655

5756

5857
.PHONY: docker-reload-nginx
59-
docker-reload-nginx: ## Reload nginx
58+
docker-reload-nginx: ## Reload nginx started from `docker compose`
6059
$(DOCKER) compose up -d --force-recreate nginx && $(DOCKER) compose logs -f nginx

examples/nginx.conf

Lines changed: 6 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -14,21 +14,15 @@ http {
1414
js_fetch_trusted_certificate /etc/ssl/certs/ISRG_Root_X1.pem;
1515

1616
js_import acme from acme.js;
17-
# js_preload_object acme_account from acme_account_info.json;
18-
# Define variable to hold private key value
19-
# variable $dynamic_ssl_key;
20-
# Define variable to hold ceritificate value
21-
# variable $dynamic_ssl_cert;
2217

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

27-
map $ssl_server_name $domain {
28-
default $ssl_server_name;
29-
~(([^\.]+)\.([^\.]+))$ $1;
30-
}
31-
3226
server {
3327

3428
listen 0.0.0.0:8000; # testing with 8000 should be 80 in prod, pebble usees httpPort in integration-tests/pebble/config.json
@@ -50,13 +44,11 @@ http {
5044
js_set $dynamic_ssl_cert acme.js_cert;
5145
js_set $dynamic_ssl_key acme.js_key;
5246

53-
# /etc/letsencrypt/live/$domain/fullchain.pem;
54-
# data:$variable can be specified instead of the file (
5547
ssl_certificate $dynamic_ssl_cert;
5648
ssl_certificate_key $dynamic_ssl_key;
5749

5850
location = / {
59-
return 200 "hello server_name:$server_name\nssl_server_name:$ssl_server_name\nssl_session_id:$ssl_session_id\n";
51+
return 200 "hello server_name:$server_name\nssl_session_id:$ssl_session_id\n";
6052
}
6153

6254
location ^~ /.well-known/acme-challenge/ {
@@ -76,17 +68,4 @@ http {
7668
location = /acme/new-acct {
7769
js_content acme.acmeNewAccount;
7870
}
79-
8071
}
81-
82-
# server {
83-
# listen 4443 ssl http2;
84-
# listen [::]:4443 ssl http2;
85-
86-
# server_name example.com www.example.com;
87-
# ssl_certificate /etc/letsencrypt/live/www.domain.com/fullchain.pem;
88-
# ssl_certificate_key /etc/letsencrypt/live/www.domain.com/privkey.pem;
89-
# ssl_session_cache shared:SSL:10m;
90-
# ssl_session_timeout 10m;
91-
# }
92-
}

package.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"name": "njs-acme-experemental",
2+
"name": "njs-acme",
33
"version": "1.0.0",
44
"description": "## How do I use this template?",
55
"main": "dist/acme.js",
@@ -9,15 +9,15 @@
99
],
1010
"repository": {
1111
"type": "git",
12-
"url": "git+https://github.com/nginxinc/njs-acme-experemental.git"
12+
"url": "git+https://github.com/nginxinc/njs-acme.git"
1313
},
1414
"keywords": [],
1515
"author": "",
1616
"license": "APACHE-2.0",
1717
"bugs": {
18-
"url": "https://github.com/nginxinc/njs-acme-experemental/issues"
18+
"url": "https://github.com/nginxinc/njs-acme/issues"
1919
},
20-
"homepage": "https://github.com/nginxinc/njs-acme-experemental#readme",
20+
"homepage": "https://github.com/nginxinc/njs-acme#readme",
2121
"engines": {
2222
"node": ">= 14.15"
2323
},

src/client.ts

Lines changed: 39 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
import { HttpClient } from './api'
2-
import { formatResponseError, getPemBodyAsB64u, retry } from './utils'
2+
import {
3+
formatResponseError,
4+
getPemBodyAsB64u,
5+
readCsrDomainNames,
6+
retry,
7+
toPEM,
8+
} from './utils'
39
import OGCrypto from 'crypto'
410

511
export interface ClientExternalAccountBindingOptions {
@@ -795,7 +801,6 @@ export class AcmeClient {
795801
//}
796802

797803
/* Return default certificate chain */
798-
// FIXME: is it json() or text()
799804
return await resp.text()
800805
}
801806

@@ -941,19 +946,45 @@ async function auto(
941946
/**
942947
* Parse domains from CSR
943948
*/
944-
// FIXME implement parsing CSR to get a list of domain...
945949
ngx.log(
946950
ngx.INFO,
947951
'njs-acme: [auto] Parsing domains from Certificate Signing Request'
948952
)
949-
// const csrDomains = readCsrDomains(opts.csr);
950-
// const domains = [csrDomains.commonName].concat(csrDomains.altNames);
951-
// const uniqueDomains = Array.from(new Set(domains));
952953

953-
const uniqueDomains = ['proxy.nginx.com']
954+
if (opts.csr === null) {
955+
throw new Error('csr is required')
956+
}
957+
958+
const csrDomains = readCsrDomainNames(toPEM(opts.csr, 'CERTIFICATE REQUEST'))
959+
960+
// Work around issue in x509.get_oid_value (called from readCsrDomainNames)
961+
// where altNames comes back as an Array in an Array, e.g.:
962+
// [[ 'hostname1', 'hostname2' ]]
963+
// We just want an Array:
964+
// [ 'hostname1', 'hostname2' ]
965+
const uniqueDomains = [csrDomains.commonName]
966+
// Hacky stuff to make Typescript happy
967+
const origAltNames = csrDomains.altNames as unknown as string[][]
968+
let altNames = csrDomains.altNames
969+
if (altNames && altNames[0] && altNames[0][0]) {
970+
altNames = origAltNames[0]
971+
}
972+
973+
if (altNames) {
974+
for (const altName of altNames) {
975+
if (uniqueDomains.indexOf(altName) === -1) {
976+
uniqueDomains.push(altName)
977+
}
978+
}
979+
}
980+
954981
ngx.log(
955982
ngx.INFO,
956-
`njs-acme: [auto] Resolved ${uniqueDomains.length} unique domains from parsing the Certificate Signing Request`
983+
`njs-acme: [auto] Resolved ${
984+
uniqueDomains.length
985+
} unique domains (${uniqueDomains.join(
986+
', '
987+
)}) from parsing the Certificate Signing Request`
957988
)
958989

959990
/**

src/index.ts

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -112,11 +112,8 @@ async function clientAutoMode(r: NginxHTTPRequest): Promise<void> {
112112

113113
// Create a new CSR
114114
const params = {
115-
altNames: [commonName],
115+
altNames: serverNames.length > 1 ? serverNames.slice(1) : [],
116116
commonName: commonName,
117-
// state: "WA",
118-
// country: "US",
119-
// organizationUnit: "NGINX",
120117
emailAddress: email,
121118
}
122119

@@ -277,9 +274,10 @@ async function createCsrHandler(r: NginxHTTPRequest): Promise<void> {
277274
*/
278275
function js_cert(r: NginxHTTPRequest): string {
279276
const prefix = acmeDir(r)
277+
const serverNames = acmeServerNames(r)
280278
const { path, data } = read_cert_or_key(
281279
prefix,
282-
r.variables.ssl_server_name?.toLowerCase() || '',
280+
serverNames[0].toLowerCase(), // filename is the commonName (first serverName)
283281
CERTIFICATE_SUFFIX
284282
)
285283
// ngx.log(ngx.INFO, `njs-acme: Loaded cert for ${r.variables.ssl_server_name} from path: ${path}`);
@@ -304,9 +302,10 @@ function js_cert(r: NginxHTTPRequest): string {
304302
*/
305303
function js_key(r: NginxHTTPRequest): string {
306304
const prefix = acmeDir(r)
305+
const serverNames = acmeServerNames(r)
307306
const { path } = read_cert_or_key(
308307
prefix,
309-
r.variables.ssl_server_name?.toLowerCase() || '',
308+
serverNames[0].toLowerCase(), // filename is the commonName (first serverName)
310309
KEY_SUFFIX
311310
)
312311
// r.log(`njs-acme: loaded key for ${r.variables.ssl_server_name} from path: ${path}`);

src/utils.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ export type PemTag =
158158
* @returns The converted PEM string
159159
*/
160160
export function toPEM(
161-
buffer: ArrayBufferView | ArrayBuffer,
161+
buffer: string | Buffer | ArrayBufferView | ArrayBuffer,
162162
tag: PemTag
163163
): string {
164164
/**
@@ -749,6 +749,7 @@ export function readCsrDomainNames(csrPem: string | Buffer): {
749749
csrPem = csrPem.toString()
750750
}
751751
const csr = x509.parse_pem_cert(csrPem)
752+
752753
return {
753754
commonName: x509.get_oid_value(csr, '2.5.4.3'),
754755
altNames: x509.get_oid_value(csr, '2.5.29.17'),

0 commit comments

Comments
 (0)