HAproxy+Lua: Return requests if validation fails from Lua script
Asked Answered
I

1

5

We are trying to build an incoming request validation platform using HAProxy+Lua. Our use-case is to create a LUA scripts that will essentially make a socket call to a Validation API, and based on the response from Validation API we want to redirect the request to a backend API, and if the validation fails we would want to return the request right from the LUA script. For example, for 200 response we would want to redirect the request to backend api, and for 404 we would want to return the request. From the documentation, I understand that there are various default functions available with Lua-Haproxy integration.

core.register_action() --> I'm using this. Take TXN as input
core.register_converters() --> Essentially used for string manipulations.
core.register_fetches() --> Takes TXN as input and returns string; Mainly used for representing dynamic backend profiles in haproxy config
core.register_init() --> Used for initialization
core.register_service() --> You have to return the response mandatorily while using this function, which doesn't satisfy our requirements
core.register_task() -->  For using normal functions. No mandatory input class. TXN is required to fetch header details from request

I have tried all of the functions from above list, I understand that core.register_service is basically to return a response from the Lua script. However, what is problematic is, we must send the response from the LUA script and it will not redirect the request to BACKEND. Currently, I am using core.register_action to interrupt the requests, but I'm not able to return the request using this function. Here's what my code looks like:

local http_socket = require("socket.http")
local pretty_print = require("pl.pretty")

function add_http_request_header(txn, name, value)
    local headerName = name
    local headerValue = value
    txn.http:req_add_header(headerName, headerValue)
end

function call_validation_api()
    local request, code, header = http_socket.request {
                                   method = "GET",                    -- Validation API Method                           
                                   url = "http://www.google.com/"     -- Validation API URL
                                   }

   -- Using core.log; Print in some cases is a blocking operation http://www.arpalert.org/haproxy-lua.html#h203
   core.Info( "Validation API Response Code: " .. code )
   pretty_print.dump( header )
   return code
end

function failure_response(txn)
    local response = "Validation Failed"
    core.Info(response)
    txn.res:send(response)
--    txn:close()
end

core.register_action("validation_action", { "http-req", "http-res" }, function(txn)
   local validation_api_code = call_validation_api()
   if validation_api_code == 200 then
      core.Info("Validation Successful")
      add_http_request_header(txn, "test-header", "abcdefg")
      pretty_print.dump( txn.http:req_get_headers() )
   else
      failure_response(txn) --->>> **HERE I WANT TO RETURN THE RESPONSE**
   end
end)

Following is the configuration file entry:

frontend  http-in
    bind :8000
    mode http
    http-request    lua.validation_action

    #Capturing header of the incoming request
    capture request header test-header len 64

    #use_backend %[lua.fetch_req_params]
    default_backend app

backend         app
    balance     roundrobin
    server      app1    127.0.0.1:9999  check

Any help is much appreciated in achieving this functionality. Also, I understand that SOCKET call from Lua script is a blocking call, which is opposite to HAProxy's default nature of keep-alive connection. Please feel free to suggest any other utility to achieve this functionality, if you have already used it.

Isadoraisadore answered 20/2, 2017 at 6:21 Comment(0)
I
11

Ok I have figured out the answer to this question: I created 2 backends for success and failure of requests, and based on the response I am returning 2 different strings. In "failure_backend", I have called a different service, which essentially is a core.register_service and can return the response. I'm pasting code for both the configuration file and lua script

HAProxy conf file:

#---------------------------------------------------------------------
# Global settings
#---------------------------------------------------------------------
global
    # to have these messages end up in /var/log/haproxy.log you will
    # need to:
    #
    # 1) configure syslog to accept network log events.  This is done
    #    by adding the '-r' option to the SYSLOGD_OPTIONS in
    #    /etc/sysconfig/syslog
    #
    # 2) configure local2 events to go to the /var/log/haproxy.log
    #   file. A line like the following can be added to
    #   /etc/sysconfig/syslog
    #
    #    local2.*                       /var/log/haproxy.log
    #
    log         127.0.0.1 local2
    maxconn     4000
    user        haproxy
    group       haproxy
    daemon
    #lua file load
    lua-load    /home/aman/coding/haproxy/http_header.lua

    # turn on stats unix socket
    stats       socket      /var/lib/haproxy/stats

#---------------------------------------------------------------------
# common defaults that all the 'listen' and 'backend' sections will
# use if not designated in their block
#---------------------------------------------------------------------
defaults
    mode        http
    log         global
    option      httplog
    option      dontlognull
    retries     3
    timeout     http-request    90s
    timeout     queue           1m
    timeout     connect         10s
    timeout     client          1m
    timeout     server          1m
    timeout     http-keep-alive 10s
    timeout     check           10s
    maxconn     3000

#---------------------------------------------------------------------
# main frontend which proxys to the backends
#---------------------------------------------------------------------
frontend            http-in
    bind            :8000
    mode            http
    use_backend     %[lua.validation_fetch]
    default_backend failure_backend

#---------------------------------------------------------------------
# round robin balancing between the various backends
#---------------------------------------------------------------------
backend         success_backend
    balance     roundrobin
    server      app1    172.23.12.94:9999  check

backend             failure_backend
    http-request    use-service     lua.failure_service

# For displaying HAProxy statistics.
frontend            stats
    bind            :8888
    default_backend stats

backend     stats
    stats   enable
    stats   hide-version
    stats   realm Haproxy Statistics
    stats   uri /haproxy/stats
    stats   auth aman:rjil@123

Lua script:

local http_socket = require("socket.http")
local pretty_print = require("pl.pretty")

function add_http_request_header(txn, name, value)
    local headerName = name
    local headerValue = value
    txn.http:req_add_header(headerName, headerValue)
end

function call_validation_api()
    local request, code, header = http_socket.request {
                                   method = "GET",                          -- Validation API Method                           
                                   url = "http://www.google.com/"     -- Validation API URL
                                   }

   -- Using core.log; Print in some cases is a blocking operation http://www.arpalert.org/haproxy-lua.html#h203
   core.Info( "Validation API Response Code: " .. code )
   pretty_print.dump( header )
   return code
end

function failure_response(txn)
    local response = "Validation Failed"
    core.Info(response)
    return "failure_backend"
end

-- Decides back-end based on Success and Failure received from validation API
core.register_fetches("validation_fetch", function(txn)
   local validation_api_code = call_validation_api()
   if validation_api_code == 200 then
      core.Info("Validation Successful")
      add_http_request_header(txn, "test_header", "abcdefg")
      pretty_print.dump( txn.http:req_get_headers() )
      return "success_backend"
   else
      failure_response(txn)
   end
end)

-- Failure service
core.register_service("failure_service", "http", function(applet)
      local response = "Validation Failed"
      core.Info(response)
      applet:set_status(400)
      applet:add_header("content-length", string.len(response))
      applet:add_header("content-type", "text/plain")
      applet:start_response()
      applet:send(response)
end)
Isadoraisadore answered 21/2, 2017 at 7:33 Comment(2)
How were you able to get socket.http installed with HProxy/Lua? I tried to install but get module 'socket.http' not found. Using HA 1.7.7Quantifier
As far as I remember, I used LuaRocks to install socket.http moduleIsadoraisadore

© 2022 - 2024 — McMap. All rights reserved.