-
Notifications
You must be signed in to change notification settings - Fork 40
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Better seed for container-based environment #19
Comments
@dorongutman I just wrote a small The only rules to respect:
Note that you can call This module comes with a lot of useful tricks:
math.lua: --- A foolproof implementation of math.randomseed() for OpenResty.
-- Once the PRNG properly seeded, this module replaces math.randomseed() with a
-- stub in order to avoid overrides of the PRNG seed by the application or by
-- third-party dependencies.
-- This implementation of math.randomseed() will guarantee a unique seed per
-- worker process, using a combination of both time and the worker's pid.
--
-- Based on:
-- https://github.com/Kong/kong/commit/06de2da2d1426fc71e03ac6dfe75f96e1a23e425
--[[
Configuration
-------------
* U_RANDOM: whether or not to read from /dev/urandom. If not, or if reading
fails, we fallback to RAND_bytes
* N_BYTES: number of bytes to read from /dev/urandom or RAND_bytes
--]]
local U_RANDOM = false
local N_BYTES = 8
-------------------------
local ffi = require "ffi"
local C = ffi.C
local new_tab
do
local pok
pok, new_tab = pcall(require, "table.new")
if not pok then
new_tab = function(narr, nrec)
return {}
end
end
end
ffi.cdef [[
typedef unsigned char u_char;
int RAND_bytes(u_char *buf, int num);
]]
if U_RANDOM then
ffi.cdef [[
int open(const char * filename, int flags, int mode);
size_t read(int fd, void *buf, size_t count);
int close(int fd);
char *strerror(int errnum);
]]
end
local O_RDONLY = 0x00 -- https://github.com/spotify/linux/blob/master/include/asm-generic/fcntl.h#L9
local bytes_buf_t = ffi.typeof "char[?]"
local function urandom_bytes(buf, size)
local fd = C.open("/dev/urandom", O_RDONLY, 0) -- mode is ignored
if fd < 0 then
ngx.log(ngx.WARN, "Error opening /dev/urandom: ",
ffi.string(C.strerror(ffi.errno())))
return false
end
local res = C.read(fd, buf, size)
if res <= 0 then
ngx.log(ngx.WARN, "Error reading from /dev/urandom: ",
ffi.str(C.strerror(ffi.errno())))
end
if C.close(fd) ~= 0 then
ngx.log(ngx.WARN, "Error closing /dev/urandom: ",
ffi.str(C.strerror(ffi.errno())))
end
return res > 0
end
local function rand_bytes(n_bytes, from_urandom)
local buf = ffi.new(bytes_buf_t, n_bytes)
ffi.fill(buf, n_bytes, 0x0)
-- only read from /dev/urandom if we were explicitly asked
if from_urandom and urandom_bytes(buf, n_bytes) then
return ffi.string(buf, n_bytes)
end
-- read from OpenSSL RAND_bytes()
if C.RAND_bytes(buf, n_bytes) == 0 then
local err_code = C.ERR_get_error()
if err_code == 0 then
return nil, "failed to get SSL error code from the queue"
end
-- get human-readable error string
C.ERR_load_crypto_strings()
local err = C.ERR_reason_error_string(err_code)
C.ERR_free_strings()
return nil, "failed to read random bytes (" .. ffi.string(err) .. ")"
end
return ffi.string(buf, n_bytes)
end
do
local _randomseed = math.randomseed
-- once called from init_by_lua*, this is the only seed in the Lua VM
local _master_seed
-- once called from init_worker_by_lua*, this will be the current worker's
-- seed
local _worker_seed
_G.math.randomseed = function() -- no arguments for a safer implementation
local seed
local phase = ngx.get_phase()
-- check current phase and previous calls
if phase ~= "init_worker" and phase ~= "init" then
ngx.log(ngx.ERR, debug.traceback("math.randomseed must only be " ..
"called from init_by_lua* or " ..
"init_worker_by_lua*", 2))
return
end
if phase == "init" and _master_seed then
ngx.log(ngx.NOTICE, debug.traceback("PRNG already seeded in " ..
"init_by_lua*", 2))
return _master_seed
end
if phase == "init_worker" and _worker_seed then
ngx.log(ngx.NOTICE, debug.traceback("PRNG already seeded in " ..
"init_worker_by_lua*", 2))
return _worker_seed
end
-- seed generation
local bytes, err = rand_bytes(N_BYTES, U_RANDOM)
if not bytes then
ngx.log(ngx.ALERT, "failed to safely seed PRNG (", err, "), ",
"falling back to time+pid (this can result in ",
"duplicated PRNG seeds)")
seed = ngx.now()*1000 + ngx.worker.pid()
else
ngx.log(ngx.DEBUG, "seeding PRNG (U_RANDOM: ", U_RANDOM, ")")
local bytes_t = new_tab(N_BYTES, 0)
for i = 1, N_BYTES do
bytes_t[i] = string.byte(bytes, i)
end
local str = table.concat(bytes_t)
if #str > 12 then
-- truncate the final number to prevent integer overflow
-- since math.randomseed() args could be casted to
-- platform-specific integers with different sizes and get
-- truncated, thus losing entropy.
--
-- double-precision floating point should be able to represent
-- numbers without rounding with up to 15/16 digits but let's
-- use 12 of them.
str = string.sub(str, 1, 12)
end
seed = tonumber(str)
end
-- preserve seed for subsequent calls
if phase == "init" then
_master_seed = seed
else
_worker_seed = seed
_master_seed = nil
end
-- seeding
_randomseed(seed)
return seed
end
end
-- vim:set ts=4 sts=4 sw=4 et: nginx.conf: master_process on;
worker_processes 2;
error_log logs/error.log debug;
events {}
http {
lua_package_path './ngx/?.lua;;';
init_by_lua_block {
require "ngx.math"
local seed = math.randomseed()
print("seed in init_by_lua*: ", seed)
-- call again (NOP + stacktrace)
--math.randomseed()
math.random()
}
init_worker_by_lua_block {
local seed = math.randomseed()
print("seed in init_worker_by_lua*: ", seed)
-- call again (NOP + stacktrace)
--math.randomseed()
math.random()
}
server {
listen 9000;
location / {
content_by_lua_block {
-- log an error with stacktrace
--math.randomseed()
ngx.say(math.random(1, 1e9))
}
}
}
} Contents of the NGINX prefix:
|
@dorongutman BTW, I would replace your implementation of
No need to require the |
@dorongutman Also, if you use OpenResty 1.13.6.2 or older, make sure that you are using lua-resty-core. Your
OpenResty 1.15.8.1 and later versions load |
@thibaultcha something's not working. I'm working on the following nginx.tmpl - https://github.com/kubernetes/ingress-nginx/blob/nginx-0.25.1/rootfs/etc/nginx/template/nginx.tmpl sed -i .bck '1,/init_by_lua_block/s/init_by_lua_block {/init_by_lua_block { \
require "ngx-math" \
local seed = math.randomseed() \
print("seed in init_by_lua*: ", seed) /' /tmp/nginx.tmpl
sed -i .bck '1,/init_worker_by_lua_block/s/init_worker_by_lua_block {/init_worker_by_lua_block { \
require "ngx-math" \
local seed = math.randomseed() \
print("seed in init_worker_by_lua*: ", seed) \
local uuid = require "jit-uuid" /' /tmp/nginx.tmpl
The generated output (taken directly from inside a running pod) of the above is: init_by_lua_block {
require "ngx-math"
local seed = math.randomseed()
print("seed in init_by_lua*: ", seed)
collectgarbage("collect")
local lua_resty_waf = require("resty.waf")
lua_resty_waf.init()
... and init_worker_by_lua_block {
require "ngx-math"
local seed = math.randomseed()
print("seed in init_worker_by_lua*: ", seed)
local uuid = require "jit-uuid"
lua_ingress.init_worker()
balancer.init_worker()
... resultsThe print from the I do get the following in the logs (from the run of
The What am I doing wrong ? |
Please remove the PRNG seeding code from |
same issue, just follow the recommendations ,but still so easy to got the same 'uuid' ! |
Continuing the discussion from #17, I've created the following version (see below) of
jit-uuid.lua
based on what @thibaultcha suggested and the link to how Kong does it, but I'm not sure if it's a solid implementation, as I'm not familiar with Lua.Our specific use case is the kubernetes nginx ingress controller. Since the seed call needs to happen in
init_worker_by_lua_block
and there can't be multiple such definitions, I'm taking nginx.tmpl from the specific nginx ingress controller version (https://raw.githubusercontent.com/kubernetes/ingress-nginx/nginx-${NGINX_VERSION}/rootfs/etc/nginx/template/nginx.tmpl), and I'm injecting the uuid init logic like so:Assuming the above is correct, the only question is whether the below will work as it should (the changes to the seed function and the 2 new function -
get_rand_bytes
andrandom_string
):Thank you.
The text was updated successfully, but these errors were encountered: