In some cases it is convenient to store data in cookie. This data must be signed by server for preventing modification by an attacker or something like that.
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:
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":
That's all ! The installation is not required. We considers that the Lua directory is store in the variable "".
This extension is very useful, and very easy to compile. We download the archive and compile the library with right path. The command are:
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:
/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
This library is also easy to compile. It provides very useful JSON converters functions. There is the way to compile:
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$ /src/lua test.lua
{"b":[33,34],"c":"string","a":"a"}
string
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".
It works. I count 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$ /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.
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 HMAC
We are just 4 functions:
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