DEV Community

Cover image for How to Secure OpenResty with a Free WAF
Carrie
Carrie

Posted on

How to Secure OpenResty with a Free WAF

OpenResty is a full-featured web application server based on Nginx and LuaJIT. It provides a powerful and flexible way to build and extend web applications while maintaining the high performance and reliability of Nginx. As the foundation for gateway products such as APISIX, Kong, and Ingress Nginx, OpenResty and its derivatives are well-suited as a unified entry point for WAF protection.

Choosing a Free WAF

I use the community edition of SafeLine developed by Chaitin. You can find more information and try it out via this link.

This article will explain how to use the free community edition of SafeLine WAF and the lua-resty-t1k plugin to add security capability to OpenResty, forming a security architecture that separates forwarding and detection services.

Basic Version

We'll take the example of installing OpenResty and the community edition of SafeLine WAF on the same host.

Step 1: Install SafeLine

The community edition of SafeLine WAF offers multiple installation methods. Refer to the official documentation for detailed instructions. Ensure the version is >= 2.0.0, which you can check in the lower left corner of the management page.

Step 2: Configure OpenResty

Using the official alpine-fat image of OpenResty as an example, we'll enable SafeLine WAF:

  1. Navigate to the installation directory of the SafeLine WAF community edition, check that the resources/detector directory exists, and start OpenResty:

    docker run -d --name openresty -v $(pwd)/resources/detector:/opt/detector openresty/openresty:alpine-fat
    
  2. Enter the OpenResty container and install the lua-resty-t1k plugin with luarocks:

    docker exec -it openresty bash
    luarocks install lua-resty-t1k
    
  3. Modify the OpenResty configuration at /etc/nginx/conf.d/default.conf to add the /t path for testing WAF protection. Here is the complete configuration, which can directly replace the /etc/nginx/conf.d/default.conf file:

    server {
        listen       80;
        server_name  localhost;
    
        location /t {
            access_by_lua_block {
                local t1k = require "resty.t1k"
    
                local t = {
                    mode = "block",
                    host = "unix:/opt/detector/snserver.sock",
                }
    
                local ok, err, result = t1k.do_access(t, true)
                if not ok then
                    ngx.log(ngx.ERR, err)
                end
            }
    
            header_filter_by_lua_block {
                local t1k = require "resty.t1k"
                t1k.do_header_filter()
            }
    
            content_by_lua_block {
                ngx.say("passed")
            }
        }
    
        location / {
            root   /usr/local/openresty/nginx/html;
            index  index.html index.htm;
        }
    
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   /usr/local/openresty/nginx/html;
        }
    }
    
  4. Validate and reload the OpenResty configuration:

    openresty -t && openresty -s reload
    

Step 3: Verification

Access /t/shell.php to verify the protection effect:

curl http://127.0.0.1/t/shell.php
Enter fullscreen mode Exit fullscreen mode

Expected return:

{"code": 403, "success":false, "message": "blocked by Chaitin SafeLine Web Application Firewall", "event_id": "2005f374e2c44757a449b1071f284e3b"}
Enter fullscreen mode Exit fullscreen mode

You should also see corresponding interception logs in the community edition of SafeLine WAF.

Normal access to /t will not trigger interception:

curl http://127.0.0.1/t/
Enter fullscreen mode Exit fullscreen mode

Expected return:

passed
Enter fullscreen mode Exit fullscreen mode

Advanced Version

In a production environment, the community edition of SafeLine WAF might not be on the same host as OpenResty, or there might be multiple OpenResty instances distributed across different hosts. In this case, you need to map the detection service to a specified port on the host so that other hosts can access it via the network.

  1. Navigate to the installation directory of SafeLine WAF Community Edition,modify the compose.yaml file to add port mapping:

    ...
    detector:
        container_name: safeline-detector
        restart: always
        image: chaitin/safeline-detector:${IMAGE_TAG}
        volumes:
        - ${SAFELINE_DIR}/resources/detector:/resources/detector
        - ${SAFELINE_DIR}/logs/detector:/logs/detector
        - /etc/localtime:/etc/localtime:ro
        environment:
        - LOG_DIR=/logs/detector
        networks:
          safeline-ce:
            ipv4_address: ${SUBNET_PREFIX}.5
        cap_drop:
        - net_raw
        ports: ["8000:8000"] # Newly added line
    ...
    
  2. Modify the configuration file resources/detector/snserver.yml of detection services:

    fusion_sofile: ./config/libfusion2.so
    ip_location_db: ./GeoLite2-City.mmdb
    # bind_addr: unix:///resources/detector/snserver.sock # Commented out
    bind_addr: 0.0.0.0 # Newly added line
    listen_port: 8000 # Newly added line
    
  3. Execute the following command to apply the modification:

    docker compose up -d
    
  4. Use the nc command to verify that the detection service port is reachable:

    nc -zv ${Chaitin SafeLine WAF Community Edition Host IP} 8000
    

    Expected return:

    Connection to ${Chaitin SafeLine WAF Community Edition Host IP} 8000 port [tcp/*] succeeded!
    
  5. Modify the OpenResty configuration file to specify the host and port:

    ...
    
    location /t {
        access_by_lua_block {
            local t1k = require "resty.t1k"
    
            local t = {
                mode = "block",
                host = "${Chaitin SafeLine WAF Community Edition Host IP}",
                port = 8000,
            }
    
            local ok, err, result = t1k.do_access(t, true)
            if not ok then
                ngx.log(ngx.ERR, err)
            end
        }
    
        header_filter_by_lua_block {
            local t1k = require "resty.t1k"
            t1k.do_header_filter()
        }
    
        content_by_lua_block {
            ngx.say("passed")
        }
    }
    
    ...
    
  6. Validate and reload the OpenResty configuration:

    openresty -t && openresty -s reload
    

You can now verify the security protection capabilities.

Enjoy a more secure OpenResty!

Top comments (0)