diff --git a/html/images/openresty-splash-page.png b/html/images/openresty-splash-page.png new file mode 100644 index 0000000..deb1443 Binary files /dev/null and b/html/images/openresty-splash-page.png differ diff --git a/posts/2025-06-22-test-nginx-conf-directives.md b/posts/2025-06-22-test-nginx-conf-directives.md new file mode 100644 index 0000000..7cdbbcc --- /dev/null +++ b/posts/2025-06-22-test-nginx-conf-directives.md @@ -0,0 +1,415 @@ +$index + +## Introduction + +We'll need nginx and luarocks. Buildpack has luarocks installed. + +docker pull openresty/openresty:bookworm-buildpack + +$ docker run --rm -it -p 80:80 openresty/openresty:bookworm-buildpack + +Visit `localhost` in browser. Should see OpenResty splash page. + +![OpenResty default nginx index page](/images/openresty-splash-page.png) + +https://openresty.org/en/getting-started.html#prepare-directory-layout + +$ mkdir -p logs/ conf/conf.d/ html/ + +https://github.com/openresty/docker-openresty?tab=readme-ov-file#nginx-config-files + +$ docker run --rm -it -w /opt -v $PWD:/opt openresty/openresty:bookworm-buildpack \ +cp /etc/nginx/conf.d/default.conf /opt/conf.d/ + +edit default.conf +change `root /usr/local/openresty/nginx/html;` to: + + root /var/www; + +`html/index.html` + +```html + + + + + + + + + hello world! + + +``` + +Start nginx: + +```console +$ docker run --rm -it -p 80:80 \ +-v $PWD/conf/conf.d:/etc/nginx/conf.d -v $PWD/html:/var/www \ +openresty/openresty:bookworm-buildpack +``` + +Then, in another console: + +```console +$ curl -v localhost +* Trying 127.0.0.1:80... +* Connected to localhost (127.0.0.1) port 80 (#0) +> GET / HTTP/1.1 +> Host: localhost +> User-Agent: curl/7.88.1 +> Accept: */* +> +< HTTP/1.1 200 OK +< Server: openresty/1.27.1.2 +< ... +< + + + ... + + hello world! + + +``` + +If we wanted to write a test for that, we need some packages from `luarocks`. + +`Dockerfile` + +```Dockerfile +FROM openresty/openresty:bookworm-buildpack + +WORKDIR /opt/app + +# needed for testing +RUN luarocks install busted +RUN luarocks install luajit-curl +RUN luarocks install luasocket # needed for testing nginx reverse proxy +``` + +```console +$ docker build -t test-nginx . +$ mkdir spec +``` + +`spec/nginx_spec.moon` + +```moonscript +http = require "luajit-curl-helper.http" + +req = (url) -> + request = http.init url + st = request\perform! + error request\lastError! if not st + request + +describe "http://localhost", -> + it "sends /index.html", -> + request = req "http://localhost" + assert.same request\statusCode!, 200 + assert.same request\statusMessage!, "OK" + assert.same request\body!\match("%s+(.-)%s+"), "hello world!" +``` + +Start the test server: + +```console +$ ct=$(docker run --rm -d \ +-v $PWD/conf/conf.d:/etc/nginx/conf.d \ +-v $PWD/html:/var/www \ +-v $PWD:/opt/app \ +test-nginx) +``` + +Run the tests. + +```console +$ docker exec -t $ct busted +● +1 success / 0 failures / 0 errors / 0 pending : 0.008246 seconds +``` + +Stop the test server. + +```console +$ docker exec -t $ct openresty -s stop +``` + +## Edit hosts + +Instead of `localhost` we'd like to use an actual domain name. We can do this +with the `--add-host` option. But before we do that, we want to make sure our +container does not have access to the internet, otherwise we might +unintentionally get a response from a domain's server on the internet rather +than from our test server. + +$ docker network create --internal no-internet + +Now we can start the test server with our host: + +```console +$ ct=$(docker run --rm -d \ +-v $PWD/conf/conf.d:/etc/nginx/conf.d \ +-v $PWD/html:/var/www \ +-v $PWD:/opt/app \ +--network no-internet \ +--add-host=domain.abc=127.0.0.1 \ +test-nginx) +``` + +Update our test + + request = req "http://localhost" + +to + + request = req "http://domain.abc" + +Run the tests. + +```console +$ docker exec -t $ct busted +● +1 success / 0 failures / 0 errors / 0 pending : 0.008246 seconds +``` + +Stop the test server. + +```console +$ docker exec -t $ct openresty -s stop +``` + +## Add a test to make sure the test server is offline + +```moonscript +describe "test environment", -> + it "can't connect to the internet", -> + assert.has_error (-> req "http://example.org"), + "Couldn't resolve host name" +``` + +## Create a Makefile + +Let's create a `Makefile` to make running all these commands easier. + +`Makefile` + +```Makefile +image = test-nginx +loopback = 127.0.0.1 + +image-build: + docker build -t $(image) . + +image-rm: + docker image rm $(image) + +test: + @ct=$(shell docker run --rm -d \ + -v $(PWD)/conf/conf.d:/etc/nginx/conf.d \ + -v $(PWD)/html:/var/www \ + -v $(PWD):/opt/app \ + --network no-internet \ + --add-host=domain.abc=$(loopback) \ + $(image)); \ + docker exec -t $$ct busted; \ + docker exec $$ct openresty -s stop +``` + +Now we can run tests by running `make test`. + +```console +$ make test +●● +2 successes / 0 failures / 0 errors / 0 pending : 0.008812 seconds +``` + +## SSL + +We want our server to redirect all `http` requests to `https`. + +Our test: + +```moonscript +describe "http://domain.abc", -> + it "redirects to https", -> + request = req "http://domain.abc" + assert.same request\statusCode!, 301 + assert.same request\statusMessage!, "Moved Permanently" + assert.same request\header!.Location, "https://domain.abc/" +``` + +```console +$ make test +●●◼ +2 successes / 1 failure / 0 errors / 0 pending : 0.010449 seconds + +Failure → .../luajit/lib/luarocks/rocks-5.1/busted/2.2.0-1/bin/busted @ 3 +http://domain.abc redirects to https +spec/nginx_spec.moon:24: Expected objects to be the same. +Passed in: +(number) 301 +Expected: +(number) 200 +``` + +Make self-signed certs in Dockerfile: + +```Dockerfile +RUN openssl req -x509 -newkey rsa:4096 -nodes \ + -keyout /etc/ssl/private/domain.abc.pem \ + -out /etc/ssl/certs/domain.abc.pem \ + -sha256 -days 365 -subj '/CN=domain.abc' \ + -addext "subjectAltName=DNS:domain.abc" +``` + +Edit `default.conf`: + +``` +server { + listen 80; + return 301 https://$host$request_uri; +} + +server { + listen 443 ssl; + server_name domain.abc; + ssl_certificate /etc/ssl/certs/domain.abc.pem; + ssl_certificate_key /etc/ssl/private/domain.abc.pem; +``` + +Rebuild the image: + +```console +$ make image-rm +$ make image-build +``` + +Run tests: + +```console +$ make test +●◼● +2 successes / 1 failure / 0 errors / 0 pending : 0.009618 seconds + +Failure → .../luajit/lib/luarocks/rocks-5.1/busted/2.2.0-1/bin/busted @ 3 +http://domain.abc sends /index.html +spec/nginx_spec.moon:17: Expected objects to be the same. +Passed in: +(number) 200 +Expected: +(number) 301 +``` + +Fix test: + +```moonscript +describe "https://domain.abc", -> + it "sends /index.html", -> + request = req "https://domain.abc" +``` + +Run tests: + +```console +$ make test +●●● +3 successes / 0 failures / 0 errors / 0 pending : 0.017065 seconds +``` + +## Reverse proxy a subdomain to a Gitea unix socket + +Add to `default.conf`: + +``` +server { + listen 443 ssl; + server_name git.domain.abc; + + location / { + client_max_body_size 1024M; + proxy_pass http://unix:/run/gitea/gitea.socket; + proxy_set_header Connection $http_connection; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } +} +``` + +Add subdomain to certs in Dockerfile: + +``` + -addext "subjectAltName=DNS:domain.abc,DNS:git.domain.abc" +``` + +Add a test socket server: + +`spec/unixstreamsrvr.moon` + +```moonscript +-- modified from +-- https://github.com/lunarmodules/luasocket/blob/4844a48fbf76b0400fd7b7e4d15d244484019df1/test/unixstreamsrvr.lua +socket = require "socket" +socket.unix = require "socket.unix" +u = assert socket.unix.stream! +assert u\bind "/run/gitea/gitea.socket" +assert u\listen! +assert u\settimeout 1 +c = assert u\accept! + +while true + m = assert c\receive! + break if m == "" + print m +``` + +Add a spec: + +```moonscript +describe "https://git.domain.abc", -> + it "reverse-proxies request to a gitea unix socket", -> + Path = require "sitegen.path" + socket_fname = "unixstreamsrvr.moon" + socket_dir = "/run/gitea" + socket_owner = "nobody" + basepath = Path.basepath debug.getinfo(1).short_src + + Path.exec "install", "-o", socket_owner, "-d", socket_dir + cmd = "su -s /bin/bash -c 'moon %s' %s" + server = io.popen cmd\format Path.join(basepath, socket_fname), socket_owner + Path.exec "sleep", "0.1" + result = Path.read_exec "find", socket_dir, "-type", "s", "-ls" + assert.truthy result\match "nobody%s+nogroup.+" .. Path.join(socket_dir, "gitea.socket") + + req "https://git.domain.abc" + + reqheader = with server\read "*a" + server\close! + + assert.truthy reqheader\match "Host: git.domain.abc" +``` + +Edit Makefile: + + --add-host=git.domain.abc=$(loopback) \ + + +```console +$ make image-rm image-build +``` + +```console +$ make test +●●●● +4 successes / 0 failures / 0 errors / 0 pending : 0.131619 seconds +``` + + +## Conclusion + +