diff --git a/.github/workflows/test-nginx.yml b/.github/workflows/test-nginx.yml index 72434a2c..ee56a757 100644 --- a/.github/workflows/test-nginx.yml +++ b/.github/workflows/test-nginx.yml @@ -10,6 +10,10 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + with: + fetch-depth: 0 + fetch-tags: true + submodules: recursive - uses: actions/setup-node@v4 with: node-version: 20.17.0 diff --git a/client b/client index 6fd5f284..99b11a8c 160000 --- a/client +++ b/client @@ -1 +1 @@ -Subproject commit 6fd5f284811872f445e757a8b5c4496acbcf18a7 +Subproject commit 99b11a8cc7d30dbc03376f97ab345dc0d18c2a98 diff --git a/docker-compose.yml b/docker-compose.yml index 79cebee5..1d70fdfc 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -108,7 +108,7 @@ services: options: max-file: "30" pyxform: - image: 'ghcr.io/getodk/pyxform-http:v2.1.0' + image: 'ghcr.io/getodk/pyxform-http:v2.1.1' restart: always secrets: volumes: diff --git a/test/mock-http-server/index.js b/test/mock-http-server/index.js index e04e9f42..c39b716e 100644 --- a/test/mock-http-server/index.js +++ b/test/mock-http-server/index.js @@ -14,6 +14,8 @@ app.get('/reset', withStdLogging((req, res) => { res.json('OK'); })); +app.get('/v1/reflect-headers', withStdLogging((req, res) => res.json(req.headers))); + app.get('/*', ok('GET')); app.post('/*', ok('POST')); // TODO add more methods as required diff --git a/test/nginx-test.dockerfile b/test/nginx-test.dockerfile deleted file mode 100644 index 6fe86da0..00000000 --- a/test/nginx-test.dockerfile +++ /dev/null @@ -1,28 +0,0 @@ -# -# KEEP THIS FILE AS CLOSE AS POSSIBLE TO ../nginx.dockerfile -# -# It is ESPECIALLY IMPORTANT to keep IDENTICAL BASE IMAGES. -# -FROM jonasal/nginx-certbot:5.4.0 - -EXPOSE 80 -EXPOSE 443 - -# Persist Diffie-Hellman parameters and/or selfsign key -VOLUME [ "/etc/dh", "/etc/selfsign" ] - -RUN apt-get update && apt-get install -y netcat-openbsd - -RUN mkdir -p /usr/share/odk/nginx/ - -COPY files/nginx/setup-odk.sh /scripts/ -RUN chmod +x /scripts/setup-odk.sh - -COPY files/nginx/redirector.conf /usr/share/odk/nginx/ -COPY files/nginx/common-headers.conf /usr/share/odk/nginx/ - -COPY ./test/files/nginx-test/http_root/ /usr/share/nginx/html -COPY ./test/files/nginx-test/acme-challenge /var/www/letsencrypt/ -RUN ls /var/www/letsencrypt/ - -ENTRYPOINT [ "/scripts/setup-odk.sh" ] diff --git a/test/nginx.test.docker-compose.yml b/test/nginx.test.docker-compose.yml index 1615c8f8..f7af4ace 100644 --- a/test/nginx.test.docker-compose.yml +++ b/test/nginx.test.docker-compose.yml @@ -16,7 +16,7 @@ services: nginx: build: context: .. - dockerfile: ./test/nginx-test.dockerfile + dockerfile: nginx.dockerfile depends_on: - service - enketo diff --git a/test/run-tests.sh b/test/run-tests.sh index 3833414f..f9ee7828 100755 --- a/test/run-tests.sh +++ b/test/run-tests.sh @@ -26,11 +26,6 @@ wait_for_http_response() { fi } -log "Checking nginx dockerfiles have same base image..." -# It would be nice if Dockerfiles allowed some kind of templating which would -# allow for direct sharing between the real and test nginx dockerfiles. -diff <(grep FROM nginx-test.dockerfile) <(grep FROM ../nginx.dockerfile | grep -v AS) - log "Starting test services..." docker_compose up --build --detach diff --git a/test/test-nginx.js b/test/test-nginx.js index f317d14e..2125adc7 100644 --- a/test/test-nginx.js +++ b/test/test-nginx.js @@ -6,23 +6,6 @@ describe('nginx config', () => { resetBackendMock(), ])); - it('well-known should serve from HTTP', async () => { - // when - const res = await fetchHttp('/.well-known/acme-challenge'); - - // then - assert.equal(res.status, 301); - assert.equal(res.headers.get('location'), 'https://localhost:9000/.well-known/acme-challenge'); - }); - - it('well-known should serve from HTTPS', async () => { - // when - const res = await fetchHttps('/.well-known/acme-challenge'); - - // then - assert.equal(res.status, 404); - }); - it('HTTP should forward to HTTPS', async () => { // when const res = await fetchHttp('/'); @@ -43,22 +26,24 @@ describe('nginx config', () => { }); [ - '/index.html', - '/version.txt', - ].forEach(staticFile => { + [ '/index.html', /
<\/div>/ ], + [ '/version.txt', /^versions:/ ], + ].forEach(([ staticFile, expectedContent ]) => { it(`${staticFile} file should have no-cache header`, async () => { // when const res = await fetchHttps(staticFile); // then assert.equal(res.status, 200); - assert.equal(await res.text(), `hi:${staticFile}\n`); + assert.match(await res.text(), expectedContent); assert.equal(await res.headers.get('cache-control'), 'no-cache'); }); }); [ - '/should-be-cached.txt', + '/blank.html', + '/favicon.ico', + // there's no way to predict generated asset paths, as they have cache-busting names ].forEach(staticFile => { it(`${staticFile} file should not have no-cache header`, async () => { // when @@ -66,7 +51,6 @@ describe('nginx config', () => { // then assert.equal(res.status, 200); - assert.equal(await res.text(), `hi:${staticFile}\n`); assert.isNull(await res.headers.get('cache-control')); }); }); @@ -96,6 +80,34 @@ describe('nginx config', () => { { method:'GET', path:'/v1/some/central-backend/path' }, ); }); + + it('should set x-forwarded-proto header to "https"', async () => { + // when + const res = await fetch(`https://localhost:9001/v1/reflect-headers`); + // then + assert.equal(res.status, 200); + + // when + const body = await res.json(); + // then + assert.equal(body['x-forwarded-proto'], 'https'); + }); + + it('should override supplied x-forwarded-proto header', async () => { + // when + const res = await fetch(`https://localhost:9001/v1/reflect-headers`, { + headers: { + 'x-forwarded-proto': 'http', + }, + }); + // then + assert.equal(res.status, 200); + + // when + const body = await res.json(); + // then + assert.equal(body['x-forwarded-proto'], 'https'); + }); }); function fetchHttp(path, options) {