Edit post
This commit is contained in:
parent
5d7896e9e5
commit
f9ef223cf1
@ -8,12 +8,12 @@ $index
|
|||||||
## Introduction
|
## Introduction
|
||||||
|
|
||||||
[`nginx`](https://docs.nginx.com/nginx/admin-guide/web-server/web-server/#rewrite-uris-in-requests)
|
[`nginx`](https://docs.nginx.com/nginx/admin-guide/web-server/web-server/#rewrite-uris-in-requests)
|
||||||
config file `nginx.conf` can contain any number of important directives
|
configuration can contain any number of important directives (redirects and
|
||||||
(redirects and rewrites, for example) that need to be verified for correctness.
|
rewrites, for example) that need to be verified for correctness. We can write
|
||||||
We can write `specs` for directives and run them against a running test server
|
tests for directives and run them against a test server to ensure they are
|
||||||
to ensure they are correct.
|
correct.
|
||||||
|
|
||||||
We'll use...
|
To do this, we'll use...
|
||||||
|
|
||||||
- [MoonScript](https://moonscript.org) and (by extension)
|
- [MoonScript](https://moonscript.org) and (by extension)
|
||||||
[Lua](https://www.lua.org/) programming languages
|
[Lua](https://www.lua.org/) programming languages
|
||||||
@ -42,7 +42,8 @@ Start a server on `localhost`:
|
|||||||
$ docker run --rm -it -p 80:80 openresty/openresty:bookworm-buildpack
|
$ docker run --rm -it -p 80:80 openresty/openresty:bookworm-buildpack
|
||||||
```
|
```
|
||||||
|
|
||||||
Visit `localhost` in browser. We should see the OpenResty splash page.
|
We can visit `localhost` in our browser and we should see the OpenResty splash
|
||||||
|
page.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
@ -61,8 +62,7 @@ $ docker run --rm -it -w /opt -v $PWD:/opt openresty/openresty:bookworm-buildpac
|
|||||||
cp /etc/nginx/conf.d/default.conf /opt/conf.d/
|
cp /etc/nginx/conf.d/default.conf /opt/conf.d/
|
||||||
```
|
```
|
||||||
|
|
||||||
Then, we edit `default.conf` to change `root /usr/local/openresty/nginx/html;`
|
Then, we update the root directive in `default.conf`:
|
||||||
to `root /var/www;`:
|
|
||||||
|
|
||||||
::: filename-for-code-block
|
::: filename-for-code-block
|
||||||
`conf/conf.d/default.conf`
|
`conf/conf.d/default.conf`
|
||||||
@ -103,9 +103,7 @@ $ docker run --rm -it -p 80:80 \
|
|||||||
openresty/openresty:bookworm-buildpack
|
openresty/openresty:bookworm-buildpack
|
||||||
```
|
```
|
||||||
|
|
||||||
## Test an HTTP request
|
Then, in another console, this should output our index file.
|
||||||
|
|
||||||
Then, in another console:
|
|
||||||
|
|
||||||
```console
|
```console
|
||||||
$ curl -v localhost
|
$ curl -v localhost
|
||||||
@ -129,8 +127,11 @@ $ curl -v localhost
|
|||||||
</html>
|
</html>
|
||||||
```
|
```
|
||||||
|
|
||||||
If we want to write a test for that, we need some packages from LuaRocks. Let's
|
## Test an HTTP request
|
||||||
add a Dockerfile.
|
|
||||||
|
If we want to write a test for that request, we need some packages from
|
||||||
|
LuaRocks. Let's add a Dockerfile to build an image with those packages
|
||||||
|
installed.
|
||||||
|
|
||||||
### Add a `Dockerfile`
|
### Add a `Dockerfile`
|
||||||
|
|
||||||
@ -139,6 +140,7 @@ FROM openresty/openresty:bookworm-buildpack
|
|||||||
|
|
||||||
WORKDIR /opt/app
|
WORKDIR /opt/app
|
||||||
|
|
||||||
|
RUN luarocks install moonscript
|
||||||
RUN luarocks install busted
|
RUN luarocks install busted
|
||||||
RUN luarocks install luajit-curl
|
RUN luarocks install luajit-curl
|
||||||
RUN luarocks install luasocket
|
RUN luarocks install luasocket
|
||||||
@ -152,7 +154,7 @@ $ docker build -t test-nginx .
|
|||||||
|
|
||||||
### Write the test
|
### Write the test
|
||||||
|
|
||||||
Let's first make a new directory for our 'specs'.
|
Let's first make a new directory where our tests will live.
|
||||||
|
|
||||||
```console
|
```console
|
||||||
$ mkdir spec
|
$ mkdir spec
|
||||||
@ -183,7 +185,8 @@ describe "http://localhost", ->
|
|||||||
|
|
||||||
### Run the test suite
|
### Run the test suite
|
||||||
|
|
||||||
Start the test server:
|
Start the test server. We're going to use `text-nginx`, the image we just
|
||||||
|
built.
|
||||||
|
|
||||||
```console
|
```console
|
||||||
$ ct=$(docker run --rm -d \
|
$ ct=$(docker run --rm -d \
|
||||||
@ -212,37 +215,30 @@ $ docker exec $ct openresty -s stop
|
|||||||
Ok, we now have a number of long `docker` commands, let's create a `Makefile`
|
Ok, we now have a number of long `docker` commands, let's create a `Makefile`
|
||||||
to make running them easier.
|
to make running them easier.
|
||||||
|
|
||||||
|
::: filename-for-code-block
|
||||||
`Makefile`
|
`Makefile`
|
||||||
|
:::
|
||||||
|
|
||||||
```Makefile
|
```Makefile
|
||||||
image = test-nginx
|
image = test-nginx
|
||||||
loopback = 127.0.0.1
|
|
||||||
|
|
||||||
image-build:
|
image-build:
|
||||||
docker build -t $(image) .
|
docker build -t $(image) .
|
||||||
|
|
||||||
image-rm:
|
image-rm:
|
||||||
docker image rm $(image)
|
docker image rm $(image)
|
||||||
|
|
||||||
test:
|
test:
|
||||||
@ct=$(shell docker run --rm -d \
|
@ct=$(shell docker run --rm -d \
|
||||||
-v $(PWD)/conf/conf.d:/etc/nginx/conf.d \
|
-v $(PWD)/conf/conf.d:/etc/nginx/conf.d \
|
||||||
-v $(PWD)/html:/var/www \
|
-v $(PWD)/html:/var/www \
|
||||||
-v $(PWD):/opt/app \
|
-v $(PWD):/opt/app \
|
||||||
--network no-internet \
|
$(image)); \
|
||||||
--add-host=domain.abc=$(loopback) \
|
docker exec -t $$ct busted; \
|
||||||
$(image)); \
|
docker exec $$ct openresty -s stop
|
||||||
docker exec -t $$ct busted; \
|
|
||||||
docker exec $$ct openresty -s stop
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Now we can run tests by running `make test`.
|
Now we can run the test suite with the command `make test`.
|
||||||
|
|
||||||
```console
|
|
||||||
$ make test
|
|
||||||
●●
|
|
||||||
2 successes / 0 failures / 0 errors / 0 pending : 0.008812 seconds
|
|
||||||
```
|
|
||||||
|
|
||||||
## Configure the domain name
|
## Configure the domain name
|
||||||
|
|
||||||
@ -252,23 +248,62 @@ container does not have access to the internet, otherwise we might
|
|||||||
unintentionally get a response from a domain's server on the internet rather
|
unintentionally get a response from a domain's server on the internet rather
|
||||||
than from our test server.
|
than from our test server.
|
||||||
|
|
||||||
|
### Ensure the test container is offline
|
||||||
|
|
||||||
|
We need to create a network that has no external access.
|
||||||
|
|
||||||
```console
|
```console
|
||||||
$ docker network create --internal no-internet
|
$ docker network create --internal no-internet
|
||||||
```
|
```
|
||||||
|
|
||||||
Now we can start the test server with our host:
|
Now we need to update our `Makefile` to add the test container to our
|
||||||
|
internal-only network:
|
||||||
|
|
||||||
```console
|
```diff
|
||||||
$ ct=$(docker run --rm -d \
|
test:
|
||||||
-v $PWD/conf/conf.d:/etc/nginx/conf.d \
|
@ct=$(shell docker run --rm -d \
|
||||||
-v $PWD/html:/var/www \
|
-v $(PWD)/conf/conf.d:/etc/nginx/conf.d \
|
||||||
-v $PWD:/opt/app \
|
-v $(PWD)/html:/var/www \
|
||||||
--network no-internet \
|
-v $(PWD):/opt/app \
|
||||||
--add-host=domain.abc=127.0.0.1 \
|
+ --network no-internet \
|
||||||
test-nginx)
|
$(image)); \
|
||||||
```
|
```
|
||||||
|
|
||||||
Update our test:
|
And now let's add a test in `spec/nginx_spec.moon` to make sure our test
|
||||||
|
environment 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"
|
||||||
|
```
|
||||||
|
|
||||||
|
Let's run our tests:
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ make test
|
||||||
|
●●
|
||||||
|
2 successes / 0 failures / 0 errors / 0 pending : 0.020207 seconds
|
||||||
|
```
|
||||||
|
|
||||||
|
### Replace `localhost` with a custom domain
|
||||||
|
|
||||||
|
To use a custom domain name instead of `localhost`, we will need to use the
|
||||||
|
`--add-host` option for the `docker run` command. Again, we edit `Makefile`:
|
||||||
|
|
||||||
|
```diff
|
||||||
|
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=127.0.0.1 \
|
||||||
|
$(image)); \
|
||||||
|
```
|
||||||
|
|
||||||
|
Let's update our test to use the custom domain name:
|
||||||
|
|
||||||
::: filename-for-code-block
|
::: filename-for-code-block
|
||||||
`spec/nginx_spec.moon`
|
`spec/nginx_spec.moon`
|
||||||
@ -283,27 +318,12 @@ Update our test:
|
|||||||
assert.same request\statusCode!, 200
|
assert.same request\statusCode!, 200
|
||||||
```
|
```
|
||||||
|
|
||||||
Run the tests.
|
Verify our tests still pass.
|
||||||
|
|
||||||
```console
|
```console
|
||||||
$ docker exec -t $ct busted
|
$ make test
|
||||||
●
|
●●
|
||||||
1 success / 0 failures / 0 errors / 0 pending : 0.008246 seconds
|
2 successes / 0 failures / 0 errors / 0 pending : 0.0224 seconds
|
||||||
```
|
|
||||||
|
|
||||||
Stop the test server.
|
|
||||||
|
|
||||||
```console
|
|
||||||
$ docker exec -t $ct openresty -s stop
|
|
||||||
```
|
|
||||||
|
|
||||||
### Ensure the test container 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"
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Test an HTTP redirect
|
## Test an HTTP redirect
|
||||||
@ -312,7 +332,7 @@ We want our server to redirect all `http` requests to `https`.
|
|||||||
|
|
||||||
### Write the test
|
### Write the test
|
||||||
|
|
||||||
Our test:
|
Let's practice a bit of test-driven development and write our test first.
|
||||||
|
|
||||||
```moonscript
|
```moonscript
|
||||||
describe "http://domain.abc", ->
|
describe "http://domain.abc", ->
|
||||||
@ -323,6 +343,8 @@ describe "http://domain.abc", ->
|
|||||||
assert.same request\header!.Location, "https://domain.abc/"
|
assert.same request\header!.Location, "https://domain.abc/"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
We should now have one failing test.
|
||||||
|
|
||||||
```console
|
```console
|
||||||
$ make test
|
$ make test
|
||||||
●●◼
|
●●◼
|
||||||
@ -339,22 +361,31 @@ Expected:
|
|||||||
|
|
||||||
### Configure `nginx`
|
### Configure `nginx`
|
||||||
|
|
||||||
```
|
We're going to add the redirect directives, as well as a server name for our
|
||||||
server {
|
domain and the directives for the SSL certificates we will generate.
|
||||||
listen 80;
|
|
||||||
return 301 https://$host$request_uri;
|
|
||||||
}
|
|
||||||
|
|
||||||
server {
|
```diff
|
||||||
listen 443 ssl;
|
+server {
|
||||||
server_name domain.abc;
|
+ listen 80;
|
||||||
ssl_certificate /etc/ssl/certs/domain.abc.pem;
|
+ return 301 https://$host$request_uri;
|
||||||
ssl_certificate_key /etc/ssl/private/domain.abc.pem;
|
+}
|
||||||
|
|
||||||
|
server {
|
||||||
|
- listen 80;
|
||||||
|
+ listen 443 ssl;
|
||||||
|
+ server_name domain.abc;
|
||||||
|
+ ssl_certificate /etc/ssl/certs/domain.abc.pem;
|
||||||
|
+ ssl_certificate_key /etc/ssl/private/domain.abc.pem;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
root /var/www;
|
||||||
|
index index.html index.htm;
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Generate self-signed SSL/TLS certs for testing
|
### Generate self-signed SSL/TLS certs for testing
|
||||||
|
|
||||||
Make self-signed certs in Dockerfile:
|
Add a command to our Dockerfile to generate self-signed certificates:
|
||||||
|
|
||||||
```Dockerfile
|
```Dockerfile
|
||||||
RUN openssl req -x509 -newkey rsa:4096 -nodes \
|
RUN openssl req -x509 -newkey rsa:4096 -nodes \
|
||||||
@ -370,28 +401,18 @@ Rebuild the image:
|
|||||||
$ make image-rm image-build
|
$ make image-rm image-build
|
||||||
```
|
```
|
||||||
|
|
||||||
Run tests:
|
We need to update our previous test to use HTTPS instead of HTTP.
|
||||||
|
|
||||||
```console
|
::: filename-for-code-block
|
||||||
$ make test
|
`spec/nginx_spec.moon`
|
||||||
●◼●
|
:::
|
||||||
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
|
```diff
|
||||||
http://domain.abc sends /index.html
|
-describe "http://domain.abc", ->
|
||||||
spec/nginx_spec.moon:17: Expected objects to be the same.
|
+describe "https://domain.abc", ->
|
||||||
Passed in:
|
it "sends /index.html", ->
|
||||||
(number) 200
|
- request = req "http://domain.abc"
|
||||||
Expected:
|
+ request = req "https://domain.abc"
|
||||||
(number) 301
|
|
||||||
```
|
|
||||||
|
|
||||||
It's our other test breaking, now. Fix spec:
|
|
||||||
|
|
||||||
```moonscript
|
|
||||||
describe "https://domain.abc", ->
|
|
||||||
it "sends /index.html", ->
|
|
||||||
request = req "https://domain.abc"
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Run tests:
|
Run tests:
|
||||||
@ -402,9 +423,7 @@ $ make test
|
|||||||
3 successes / 0 failures / 0 errors / 0 pending : 0.017065 seconds
|
3 successes / 0 failures / 0 errors / 0 pending : 0.017065 seconds
|
||||||
```
|
```
|
||||||
|
|
||||||
👍
|
## Test reverse proxy a subdomain request to a unix socket
|
||||||
|
|
||||||
## Test subdomain reverse proxy to a unix socket
|
|
||||||
|
|
||||||
Let's say we have a running service that connects to a unix socket. We want to
|
Let's say we have a running service that connects to a unix socket. We want to
|
||||||
proxy the requests through `nginx` so that our service can respond to `https`
|
proxy the requests through `nginx` so that our service can respond to `https`
|
||||||
@ -412,9 +431,10 @@ requests but can leave handling SSL/TLS to `nginx`.
|
|||||||
|
|
||||||
### Configure `nginx`
|
### Configure `nginx`
|
||||||
|
|
||||||
Our `nginx` config file might look something like this:
|
We'll add another server block to `conf/conf.d/default.conf` for our subdomain,
|
||||||
|
`git.domain.abc`, with the proxy directives:
|
||||||
|
|
||||||
```
|
```nginx
|
||||||
server {
|
server {
|
||||||
listen 443 ssl;
|
listen 443 ssl;
|
||||||
server_name git.domain.abc;
|
server_name git.domain.abc;
|
||||||
@ -434,13 +454,46 @@ server {
|
|||||||
|
|
||||||
### Add subdomain to SSL/TLS certs
|
### Add subdomain to SSL/TLS certs
|
||||||
|
|
||||||
```Dockerfile
|
Next, we need to add our subdomain to the generated SSL certs in the
|
||||||
-addext "subjectAltName=DNS:domain.abc,DNS:git.domain.abc"
|
`Dockerfile`:
|
||||||
|
|
||||||
|
```diff
|
||||||
|
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"
|
||||||
|
+ -addext "subjectAltName=DNS:domain.abc,DNS:git.domain.abc"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Add subdomain as a host
|
||||||
|
|
||||||
|
Let's assign the loopback address to a variable and then add our subdomain as a
|
||||||
|
host in our `Makefile`:
|
||||||
|
|
||||||
|
```diff
|
||||||
|
+loopback = 127.0.0.1
|
||||||
|
|
||||||
|
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=127.0.0.1 \
|
||||||
|
+ --add-host=domain.abc=$(loopback) \
|
||||||
|
+ --add-host=git.domain.abc=$(loopback) \
|
||||||
|
$(image)); \
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
### Add a test socket server
|
### Add a test socket server
|
||||||
|
|
||||||
Copied and modified from [here](https://github.com/lunarmodules/luasocket/blob/4844a48fbf76b0400fd7b7e4d15d244484019df1/test/unixstreamsrvr.lua)):
|
We need to start up a mock socket server for our test to ensure our request is
|
||||||
|
being proxied correctly. This is why we needed the LuaSocket library.
|
||||||
|
|
||||||
|
Copied and modified from [here](https://github.com/lunarmodules/luasocket/blob/4844a48fbf76b0400fd7b7e4d15d244484019df1/test/unixstreamsrvr.lua),
|
||||||
|
this should suit our purposes:
|
||||||
|
|
||||||
::: filename-for-code-block
|
::: filename-for-code-block
|
||||||
`spec/unixstreamsrvr.moon`
|
`spec/unixstreamsrvr.moon`
|
||||||
@ -463,6 +516,8 @@ while true
|
|||||||
|
|
||||||
### Write the test
|
### Write the test
|
||||||
|
|
||||||
|
And now we can add our test:
|
||||||
|
|
||||||
::: filename-for-code-block
|
::: filename-for-code-block
|
||||||
`spec/nginx_spec.moon`
|
`spec/nginx_spec.moon`
|
||||||
:::
|
:::
|
||||||
@ -491,21 +546,13 @@ describe "https://git.domain.abc", ->
|
|||||||
assert.truthy reqheader\match "Host: git.domain.abc"
|
assert.truthy reqheader\match "Host: git.domain.abc"
|
||||||
```
|
```
|
||||||
|
|
||||||
Edit Makefile:
|
Because we modified the `Dockerfile`, we need to rebuild our image:
|
||||||
|
|
||||||
::: filename-for-code-block
|
|
||||||
`Makefile`
|
|
||||||
:::
|
|
||||||
|
|
||||||
--add-host=git.domain.abc=$(loopback) \
|
|
||||||
|
|
||||||
Rebuild image:
|
|
||||||
|
|
||||||
```console
|
```console
|
||||||
$ make image-rm image-build
|
$ make image-rm image-build
|
||||||
```
|
```
|
||||||
|
|
||||||
Run tests:
|
And if all went well, our test should pass.
|
||||||
|
|
||||||
```console
|
```console
|
||||||
$ make test
|
$ make test
|
||||||
@ -515,8 +562,9 @@ $ make test
|
|||||||
|
|
||||||
## Conclusion
|
## Conclusion
|
||||||
|
|
||||||
Using these tools, we can verify that our `nginx` configuration is working the
|
These are just a few examples of how to test `nginx` directives. Using these
|
||||||
way we intend.
|
tools, we can verify that changes to our server configuration are working the
|
||||||
|
way we intended.
|
||||||
|
|
||||||
## Bonus!: Issues Ran Into Just Making This Post
|
## Bonus!: Issues Ran Into Just Making This Post
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user