In other way, HAProxy require Lua-5.3. In many cases, this requirement implies usage of a Lua library specifically packaged because current Linux distributions doesn't embed Lua-5.3. Sometimes, usefull Lua libraries are not available with your distro, and you don't want to use heavy solutions like luarocks. This article explain also compile some modules:
- base64
- crypto
- cjson
Lua compilation
The first step is compiling Lua. This library is very easy to compile, it doesn't have a lot of dependencies nor a ./configure which add compilation difficulties on manny Linux distributions.The Lua dependencies are "libreadline". Note that this dependency is required for the command line Lua interpretor. If you not have this library, the Lua library will be compiled but not the command line interpretor. This condition doesn't impact the embedding with HAProxy.
Just download le last Lua version (here 5.3.4), extract data from the tarball, enter the directory and type "make":
- wget https://www.lua.org/ftp/lua-5.3.4.tar.gz
- tar xf lua-5.3.4.tar.gz
- make -C lua-5.3.4 linux
That's all ! The installation is not required. We considers that the Lua directory is store in the variable "$LUA_DIR".
base64 compilation
This extension is very useful, and very easy to compile. We download the archive and compile the library with right path. The command are:- wget http://webserver2.tecgraf.puc-rio.br/~lhf/ftp/lua/5.3/lbase64.tar.gz
- tar xf lbase64.tar.gz
- make -C base64 LUA=$LUA_DIR G=-fPIC
That's done ! Le library is available in the source directory and it is called base64.so.
Note that the compilation embed Lua test of this new library. On my screen, it displays:
$LUA_DIR/src/lua test.lua
base64 library for Lua 5.3 / Aug 2012
0 true
1 true TA== L
2 true THU= Lu
3 true THVh Lua
4 true THVhLQ== Lua-
5 true THVhLXM= Lua-s
6 true THVhLXNj Lua-sc
7 true THVhLXNjcg== Lua-scr
8 true THVhLXNjcmk= Lua-scri
9 true THVhLXNjcmlw Lua-scrip
testing prefix 0
testing prefix 1
testing prefix 2
testing prefix 3
THVhLXNjcmlwdGluZy1sYW5ndWFnZQ== Lua-scripting-language 22
???h???jcmlwd?lu?y1s??5nd??n??== nil
THV?LXN??????G??Z?1?YW5??WF?ZQ== nil
===h===jcmlwd=lu=y1s==5nd==n==== 0
THV=LXN======G==Z=1=YW5==WF=ZQ== Lu 2
Note that, the website whoch provides the base64 library provides also a lot of useful libraries: http://webserver2.tecgraf.puc-rio.br/~lhf/ftp/lua/index.html
lua-cjson compilation
This library is also easy to compile. It provides very useful JSON converters functions. There is the way to compile:- wget https://github.com/mpx/lua-cjson/archive/2.1.0.zip -O lua-cjson-2.1.0.zip
- unzip lua-cjson-2.1.0.zip
- make -C lua-cjson-2.1.0 LUA_INCLUDE_DIR=$LUA_DIR/src
Some warning will be display, but you can ignore it. The Lua library is available in the source directory with the name "cjson.so".
This library doesn't provides easy-to-use tests. You can test with this code written in the file "test.lua" stored in the source directory
local cjson = require("cjson")
local struct = {
a = "a",
b = {
33, 34
},
c = "string"
}
enc = cjson.encode(struct)
print(enc)
dec = cjson.decode(enc)
print(dec['c'])
And executes the following command line:
user@host:~/build/lua-cjson-2.1.0$ $LUA_DIR/src/lua test.lua
{"b":[33,34],"c":"string","a":"a"}
string
crypto compilation
This library is very annoying to compile because it use all the compilation assistants: automake, pkg-config, autoconf, libtool, luarocks, and it very difficult to find the right option to compile with a Lua source installed in a non-standard directory.In other way, look in the directory "src". You will find only one file ! All this crap is used for compiling only one file... :-(
One point for the library: It seems to be deprecated, But it provides required functions.
Default Lua build doesn't provide "*pkg*" file, the option LUA_CFLAGS seems to be ignored, ... After many time of research and try, I decide to use a non conventional way to compile this library. There is my solution:
First, we need to apply a little patch because this library is written for Lua version < 5.3.x. It easy: edit the file src/lcrypto.c and line 91, replace "luaL_checkint" by "luaL_checkinteger".
- wget https://github.com/mkottman/luacrypto/archive/0.3.2.zip -O luacrypto-0.3.2.zip
- unzip luacrypto-0.3.2.zip
- cd luacrypto-0.3.2
- gcc -I$LUA_DIR/src -fPIC -g -O2 -DLUA_COMPAT_APIINTCASTS -c -o src/lcrypto.o src/lcrypto.c
- gcc -fPIC -shared -o crypto.so src/lcrypto.o -lcrypto
It works. About 25 various build files for an effective work of two fucking compilation lines. Great !
We will test the library, but only the load. We considers if the library is loaded, it will works perfectly.
local crypto = require("crypto")
And executes the Lua code:
user@host:~/build/luacrypto-0.3.2$ $LUA_DIR/src/lua test.lua
table: 0x1823160
It works !Now we have 3 .so files which contains basic functions for out main goal. Just copy these files in the same directory that the haproxy configuration and the Lua file. These library will be move in a conventional directory later.
Crypting cookies with HAProxy and Lua
The following Lua program shows how encoding Lua data and/or HAProxy variables in a signed cookie. The values will be serialised using json and them base64 encoded. The JSON string is signed with an SHA-256 HMACWe are just 4 functions:
- cookie_encode: Get a Lua struct, encode it and generate signature, return cookie compatible string.
- cookie_decode: decode the cookie and verify the signature, and return a Lua struct.
- one HAProxy action for retrieving the cookie
- one HAProxy action for generating the cookie
This is the code:
cjson = require("cjson")
base64 = require("base64")
crypto = require("crypto")
-- serialize and sign this data
function cookie_encode(data, secret)
local cookie_json = cjson.encode(cookie_data)
local cookie_base64 = base64.encode(cookie_json)
local sign_bin = crypto.hmac.digest("sha256", cookie_json, secret)
local sign_base64 = base64.encode(sign_bin)
return cookie_base64 .. "@" .. sign_base64
end
-- deserialize and check cookie
function cookie_decode(data, secret)
local index = string.find(data, "@")
if index == nil then return false, "bad-format" end
local cookie_base64 = string.sub(data, 1, index - 1)
local cookie_json = base64.decode(cookie_base64)
if cookie_json == nil then return false, "bad-format" end
local sign_base64 = string.sub(data, index + 1)
local sign_bin = base64.decode(sign_base64)
if cookie_json == nil then return false, "bad-format" end
local sign_cmp = crypto.hmac.digest("sha256",cookie_json, secret)
if sign_cmp ~= sign_bin then return false, "bad-sign" end
local st, cookie_data = pcall(cjson.decode, cookie_json)
if st == false then return false, "bad-format" end
return true, cookie_data
end
-- Secret key
secret = "s3cr3t"
-- Create cookie and set it
core.register_action("set-cookie", {"http-res"}, function (txn)
local cookie_data = {
date = os.date("%Y-%m-%d %H:%M:%S"),
var1 = txn:get_var("txn.var1"),
var2 = txn:get_var("txn.var2")
}
-- Generate cookie
local cookie = cookie_encode(cookie_data, secret)
txn.http:req_add_header("Set-Cookie", "MYDATA="..cookie)
end)
-- Decode cookie ans restore vars
core.register_action("get-cookie", {"http-req"}, function(txn)
local cookie = txn.sf:req_cook_val("MYDATA")
local status, cookie_dec = cookie_decode(cookie, secret)
if status == false then return end
txn:set_var("txn.var1", cookie_dec.var1)
txn:set_var("txn.var2", cookie_dec.var2)
end)
You must add tow lines in the HAProxy configuration:
The HAProxy transaction variables txn.var1 and txn.var2 will be encoded, signed and sent as cookie.
Do not hesitate to send feedback
- http-request lua.get-cookie
- http-response lua.set-cookie
The HAProxy transaction variables txn.var1 and txn.var2 will be encoded, signed and sent as cookie.
Do not hesitate to send feedback