Skip to content

1. Development

Gary Archer edited this page Nov 18, 2024 · 26 revisions

Prerequisites

A C compiler must be installed that meets the ISO C Standard (C99), such as gcc.
On macOS, the C compiler installed by XCode works fine.
Also ensure that a Docker engine and the jq tool are installed.

1. Install an IDE

This project can be developed in a basic manner with Visual Studio Code and the C/C++ Extension Pack.
Alternatively, a more specialist compiler such as CLion can be used, with better debugging features.

2. Configure (basics)

First run the base configure script, and accept the default NGINX version, which defaults to that of the latest NGINX Plus release (1.25.5). If you prefer, enter an nginx open source version instead:

./configure

Select the following options when getting started. The first enables debugging of the C code, while the second enables Perl tests to run:

Do you want to enable debug featured? --> Yes
Do you want to create a dynamic module? --> No

3. Configure (details)

The configure script will download the NGINX source code if it is not already local. If it is, the location may be provided when prompted. By default, version 1.25.5 will be downloaded; a different version can be fetched by setting NGINX_VERSION before running the configure script. Any additional parameters (e.g., --prefix) that NGINX's configure script supports can also be provided. When this module's configure script is run, it will pass along --with-compat to NGINX's script. It asks if a dynamic module should be created (thus passing along --add-dynamic-module) or if the module should be compiled into the NGINX binary (thus passing --add-module); by default, it created a dynamically-linked module. It will also ask if debug flags should be enabled; if so, --with-debug and certain GCC flags will be passed on to NGINX's configure script to make debugging easier.

WARNING If --without-pcre, --without-http_gzip_module and potentially other flags are provided to the configure script and a module is created, it will not be compatible with NGINX Plus or the pre-compiled open source NGINX binaries; if you include such flags (when building the module), you will only be able to load it into a custom build of NGINX that also excludes the same functionality. If the configure script exits with an error about a missing dependency, like PCRE and zlib, install those instead of excluding them if compatibility with pre-build NGINX binaries is desired.

The code can be imported into CLion 2020.2 or newer as a Makefile project. To do this though, you need to run the configure script without running make or make all. Also, CLion will not work if the clean target it set since this target deletes the Makefile, which is generated by the configure script. If this process is followed, the project will import easily and all the smart IDE features will work.

4. Make

Whenever the code in the module changes, run this command to rebuild NGINX, which will delegate to NGINX's Makefile:

make

5. Make Install

Pre-creating the nginx folder for development is recommended.
This enables nginx to be run as your own user account, which works better later when debugging:

sudo mkdir /usr/local/nginx
sudo chown yourusername /usr/local/nginx

Whenever you want to update the local system after building code, do a make install.
This deploys an entire NGINX system under the /usr/local/nginx folder:

make install

6. Run NGINX Locally

Finally deploy the nginx.conf development configuration and start NGINX locally:

cp ./testing/localhost/nginx.conf /usr/local/nginx/conf/nginx.conf
/usr/local/nginx/sbin/nginx

7. Act as a Client

You can run curl requests against the nginx system in the same manner as a web or mobile client:

ACCESS_TOKEN='xxx'
curl -i -X GET http://localhost:8080/api \
-H "Authorization: Bearer $ACCESS_TOKEN"

This will result in a 500 error, since the plugin cannot connect to an introspection endpoint yet:

{
    "code": "server_error",
    "message": "Problem encountered processing the request"
}

8. Run the Curity Identity Server

Run a local instance in Docker using the following command.
Then login to http://localhost:6749/admin with credentials admin / Password1.
Then complete the initial setup wizard and accept all defaults:

docker run -it -e PASSWORD=Password1 -p 6749:6749 -p 8443:8443 curity.azurecr.io/curity/idsvr

Then save this XML to a clients.xml file, and upload / merge it via the Changes / Upload option:

<config>
  <profiles xmlns="https://curity.se/ns/conf/base">
    <profile>
      <id>token-service</id>
      <type xmlns:as="https://curity.se/ns/conf/profile/oauth">as:oauth-service</type>
      <expose-detailed-error-messages/>
      <settings>
        <authorization-server xmlns="https://curity.se/ns/conf/profile/oauth">
          <client-store>
            <config-backed>
              <client>
                <id>test-client</id>
                <secret>secret1</secret>
                <capabilities>
                  <client-credentials/>
                </capabilities>
              </client>
              <client>
                <id>test-nginx</id>
                <secret>secret2</secret>
                <capabilities>
                  <introspection/>
                </capabilities>
              </client>
            </config-backed>
          </client-store>
        </authorization-server>
      </settings>
    </profile>
  </profiles>
</config>

9. Test the Plugin Locally

Next run this command to get an opaque access token:

OPAQUE_ACCESS_TOKEN=$(curl -s -X POST http://localhost:8443/oauth/v2/oauth-token \
    -H "Content-Type: application/x-www-form-urlencoded" \
    -d "client_id=test-client" \
    -d "client_secret=secret1" \
    -d "grant_type=client_credentials" | jq -r .access_token)
echo $OPAQUE_ACCESS_TOKEN

Send the opaque token to the stub API with this command, which will result in the plugin introspecting it:

curl -s -X GET 'http://localhost:8080/api' -H "Authorization: Bearer $OPAQUE_ACCESS_TOKEN"

The introspection request made from nginx can, if required, be executed locally, for debugging purposes:

JWT_ACCESS_TOKEN=$(curl -s -X POST http://localhost:8443/oauth/v2/oauth-introspect \
    -H "Accept: application/jwt" \
    -d "client_id=test-nginx" \
    -d "client_secret=secret2" \
    -d "token=$OPAQUE_ACCESS_TOKEN")
echo $JWT_ACCESS_TOKEN

The stub API simply echoes back the JWT access token:

{
    "message": "API was called successfully with eyJraWQiOiI5MTc5OTY5NTAiLCJ4NXQiOiJqeU1UMUgwb3B6MWdYWTZPY3JsaUg0dTU0amciLCJhbGciOiJSUzI1NiJ9.eyJqdGkiOiJjNWNjN2M5NS02MjczLTQwYjAtYThkNy0zNzZjZjA5ZTU0MWIiLCJkZWxlZ2F0aW9uSWQiOiIwOTNjYTZiYi1hYTkyLTRhZTMtOTY5NS04MGI2NzE5M2M5ZjAiLCJleHAiOjE2Njc0MTkwMjIsIm5iZiI6MTY2NzQxODcyMiwic2NvcGUiOiIiLCJpc3MiOiJodHRwOi8vMTQ3ZTRiNjhiMmQ0Ojg0NDMvb2F1dGgvdjIvb2F1dGgtYW5vbnltb3VzIiwic3ViIjoidGVzdC1jbGllbnQiLCJhdWQiOiJ0ZXN0LWNsaWVudCIsImlhdCI6MTY2NzQxODcyMiwicHVycG9zZSI6ImFjY2Vzc190b2tlbiJ9.j5qS61HbBuyT-igAhuSxjvYVQc4PWEX5fO5ugOTdaE5Y41oZ8HrywzaTYY-cj-bOZoXsKEbtMXftc-jW5qehiBZd0fvebICpEMUrZ81pVhwoPFmZb3tO5gBaGbzsV1M_s8s_N0n9rd26X3fMmiiIDkwBIdwZhd1fb2LeXtpglYOPlV-nBSCjXsIK5nrlpbbQkTv0NIUQx4ZZG4R9dckkzhYimxT7lwOYDwJIH-GSoPUiCT9N0l8BZiSQ6kl-JP2QcxRisiEfglkZfsmxhMZl3vQIM2HaVsJbuf6A3mKcnkvSiDAA90fGQIL3WnEAjlSaE-EkswMfzOHS4pHnUx1SWQ"
}

10. Debug Code

To perform printf debugging you can add ngx_log_error statements to the C code and then look at NGINX output.
Once nginx is running, select Run / Attach to Process in CLion, then choose the nginx worker process.
Then set breakpoints, after which you can step through code to check variable state carefully.

11. Use Development Resources

You can consult the following sources of information for further details on NGINX module development: