Skip to content
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

Closed
dorongutman opened this issue Aug 13, 2019 · 6 comments
Closed

Better seed for container-based environment #19

dorongutman opened this issue Aug 13, 2019 · 6 comments

Comments

@dorongutman
Copy link

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:

sed -i .bck '1,/init_worker_by_lua_block/s/init_worker_by_lua_block {/init_worker_by_lua_block { \
        math = require "math" \
	os = require "os" \
	local uuid = require "jit-uuid" \
	uuid.seed()/' /tmp/nginx.tmpl

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 and random_string):

-- vim:set ts=4 sts=4 sw=4 et:

--- jit-uuid
-- Fast and dependency-free UUID library for LuaJIT/ngx_lua.
-- @module jit-uuid
-- @author Thibault Charbonnier
-- @license MIT
-- @release 0.0.7


local bit = require 'bit'


local tohex = bit.tohex
local band = bit.band
local bor = bit.bor


local _M = {
    _VERSION = '0.0.7'
}


----------
-- seeding
----------

-- try to get n_bytes of CSPRNG data, first via /dev/urandom,
-- and then falling back to OpenSSL if necessary
function _M.get_rand_bytes(n_bytes, urandom)
  local buf = ffi_new(bytes_buf_t, n_bytes)
  ffi_fill(buf, n_bytes, 0x0)

  -- only read from urandom if we were explicitly asked
  if urandom then
    local rc = urandom_bytes(buf, n_bytes)

    -- if the read of urandom was successful, we returned true
    -- and buf is filled with our bytes, so return it as a string
    if rc then
      return ffi_str(buf, n_bytes)
    end
  end

  if C.RAND_bytes(buf, n_bytes) == 0 then
    -- get error code
    local err_code = C.ERR_get_error()
    if err_code == 0 then
      return nil, "could not 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, "could not get random bytes (" ..
                "reason:" .. ffi_str(err) .. ") "
  end

  return ffi_str(buf, n_bytes)
end

do
  local char = string.char
  local rand = math.random
  local encode_base64 = ngx.encode_base64

  -- generate a random-looking string by retrieving a chunk of bytes and
  -- replacing non-alphanumeric characters with random alphanumeric replacements
  -- (we dont care about deriving these bytes securely)
  -- this serves to attempt to maintain some backward compatibility with the
  -- previous implementation (stripping a UUID of its hyphens), while significantly
  -- expanding the size of the keyspace.
  local function random_string()
    -- get 24 bytes, which will return a 32 char string after encoding
    -- this is done in attempt to maintain backwards compatibility as
    -- much as possible while improving the strength of this function
    return encode_base64(get_rand_bytes(24, true))
           :gsub("/", char(rand(48, 57)))  -- 0 - 10
           :gsub("+", char(rand(65, 90)))  -- A - Z
           :gsub("=", char(rand(97, 122))) -- a - z
  end

  _M.random_string = random_string
end

--- Seed the random number generator.
-- Under the hood, this function calls `math.randomseed`.
-- It makes sure to use the most appropriate seeding technique for
-- the current environment, guaranteeing a unique seed.
--
-- To guarantee unique UUIDs, you must have correctly seeded
-- the Lua pseudo-random generator (with `math.randomseed`).
-- You are free to seed it any way you want, but this function
-- can do it for you if you'd like, with some added guarantees.
--
-- @param[type=number] seed (Optional) A seed to use. If none given, will
-- generate one trying to use the most appropriate technique.
-- @treturn number `seed`: the seed given to `math.randomseed`.
-- @usage
-- local uuid = require 'resty.jit-uuid'
-- uuid.seed()
--
-- -- in ngx_lua, seed in the init_worker context:
-- init_worker_by_lua {
--   local uuid = require 'resty.jit-uuid'
--   uuid.seed()
-- }
function _M.seed(seed)
    if not seed then
        if ngx then
            if ngx.worker.pid() == 1 then
                seed = _M.random_string()
            else
                seed = ngx.time() + ngx.worker.pid()
            end
        elseif package.loaded['socket'] and package.loaded['socket'].gettime then
            seed = package.loaded['socket'].gettime()*10000

        else
            seed = os.time()
        end
    end

    math.randomseed(seed)

    return seed
end


-------------
-- validation
-------------


do
    if ngx and string.find(ngx.config.nginx_configure(),'--with-pcre-jit',nil,true) then
        local type = type
        local re_find = ngx.re.find
        local regex = '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$'


        --- Validate a string as a UUID.
        -- To be considered valid, a UUID must be given in its canonical
        -- form (hexadecimal digits including the hyphen characters).
        -- This function validates UUIDs disregarding their generation algorithm,
        -- and in a case-insensitive manner, but checks the variant field.
        --
        -- Use JIT PCRE if available in OpenResty or fallbacks on Lua patterns.
        --
        -- @param[type=string] str String to verify.
        -- @treturn boolean `valid`: true if valid UUID, false otherwise.
        -- @usage
        -- local uuid = require 'resty.jit-uuid'
        --
        -- uuid.is_valid 'cbb297c0-a956-486d-ad1d-f9bZZZZZZZZZ' --> false
        -- uuid.is_valid 'cbb297c0-a956-486d-dd1d-f9b42df9465a' --> false (invalid variant)
        -- uuid.is_valid 'cbb297c0a956486dad1df9b42df9465a'     --> false (no dashes)
        -- uuid.is_valid 'cbb297c0-a956-486d-ad1d-f9b42df9465a' --> true
        function _M.is_valid(str)
            -- it has proven itself efficient to first check the length with an
            -- evenly distributed set of valid and invalid uuid lengths.
            if type(str) ~= 'string' or #str ~= 36 then
                return false
            end

            return re_find(str, regex, 'ioj') ~= nil
        end

    else
        local match = string.match
        local d = '[0-9a-fA-F]'
        local p = '^' .. table.concat({
            d:rep(8),
            d:rep(4),
            d:rep(4),
            '[89ab]' .. d:rep(3),
            d:rep(12)
        }, '%-') .. '$'


        function _M.is_valid(str)
            if type(str) ~= 'string' or #str ~= 36 then
                return false
            end

            return match(str, p) ~= nil
        end
    end
end


----------------
-- v4 generation
----------------


do
    local fmt = string.format
    local random = math.random


    --- Generate a v4 UUID.
    -- v4 UUIDs are created from randomly generated numbers.
    --
    -- @treturn string `uuid`: a v4 (randomly generated) UUID.
    -- @usage
    -- local uuid = require 'resty.jit-uuid'
    --
    -- local u1 = uuid()             ---> __call metamethod
    -- local u2 = uuid.generate_v4()
    function _M.generate_v4()
        return (fmt('%s%s%s%s-%s%s-%s%s-%s%s-%s%s%s%s%s%s',
                    tohex(random(0, 255), 2),
                    tohex(random(0, 255), 2),
                    tohex(random(0, 255), 2),
                    tohex(random(0, 255), 2),

                    tohex(random(0, 255), 2),
                    tohex(random(0, 255), 2),

                    tohex(bor(band(random(0, 255), 0x0F), 0x40), 2),
                    tohex(random(0, 255), 2),

                    tohex(bor(band(random(0, 255), 0x3F), 0x80), 2),
                    tohex(random(0, 255), 2),

                    tohex(random(0, 255), 2),
                    tohex(random(0, 255), 2),
                    tohex(random(0, 255), 2),
                    tohex(random(0, 255), 2),
                    tohex(random(0, 255), 2),
                    tohex(random(0, 255), 2)))
    end
end


----------------
-- v3/v5 generation
----------------


do
    if ngx then
        local ffi = require 'ffi'


        local tonumber = tonumber
        local assert   = assert
        local error    = error
        local concat   = table.concat
        local type     = type
        local char     = string.char
        local fmt      = string.format
        local sub      = string.sub
        local gmatch   = ngx.re.gmatch
        local sha1_bin = ngx.sha1_bin
        local md5      = ngx.md5
        local C        = ffi.C
        local ffi_new  = ffi.new
        local ffi_str  = ffi.string
        local ffi_cast = ffi.cast
        local new_tab
        do
            local ok
            ok, new_tab = pcall(require, 'table.new')
            if not ok then
                new_tab = function(narr, nrec) return {} end
            end
        end


        ffi.cdef [[
            typedef unsigned char u_char;
            typedef intptr_t ngx_int_t;

            u_char * ngx_hex_dump(u_char *dst, const u_char *src, size_t len);
            ngx_int_t ngx_hextoi(u_char *line, size_t n);
        ]]


        local str_type    = ffi.typeof('uint8_t[?]')
        local u_char_type = ffi.typeof('u_char *')


        local function bin_tohex(s)
            local slen = #s
            local blen = slen * 2
            local buf = ffi_new(str_type, blen)

            C.ngx_hex_dump(buf, s, slen)

            return ffi_str(buf, blen)
        end


        local function hex_to_i(s)
            local buf = ffi_cast(u_char_type, s)

            local n = tonumber(C.ngx_hextoi(buf, #s))
            if n == -1 then
                error("could not convert hex to number")
            end

            return n
        end


        local buf = new_tab(16, 0)


        local function factory(namespace, hash_fn)
            if not _M.is_valid(namespace) then
                return nil, 'namespace must be a valid UUID'
            end

            local i = 0
            local iter, err = gmatch(namespace, [[([\da-f][\da-f])]])
            if not iter then
                return nil, 'could not create iter: ' .. err
            end

            while true do
                local m, err = iter()
                if err then
                    return nil, err
                end

                if not m then
                    break
                end

                i = i + 1
                buf[i] = char(tonumber(m[0], 16))
            end

            assert(i == 16, "invalid binary namespace buffer length")
            local ns = concat(buf)

            return function(name)
                if type(name) ~= 'string' then
                    return nil, 'name must be a string'
                end

                local hash, ver, var = hash_fn(ns, name)

                return (fmt('%s-%s-%s%s-%s%s-%s', sub(hash, 1, 8),
                                                sub(hash, 9, 12),
                                                ver,
                                                sub(hash, 15, 16),
                                                var,
                                                sub(hash, 19, 20),
                                                sub(hash, 21, 32)))
            end
        end


        local function v3_hash(binary, name)
            local hash = md5(binary .. name)

            return hash,
            tohex(bor(band(hex_to_i(sub(hash, 13, 14)), 0x0F), 0x30), 2),
            tohex(bor(band(hex_to_i(sub(hash, 17, 18)), 0x3F), 0x80), 2)
        end


        local function v5_hash(binary, name)
            local hash = bin_tohex(sha1_bin(binary .. name))

            return hash,
            tohex(bor(band(hex_to_i(sub(hash, 13, 14)), 0x0F), 0x50), 2),
            tohex(bor(band(hex_to_i(sub(hash, 17, 18)), 0x3F), 0x80), 2)
        end


        --- Instanciate a v3 UUID factory.
        -- @function factory_v3
        -- Creates a closure generating namespaced v3 UUIDs.
        -- @param[type=string] namespace (must be a valid UUID according to `is_valid`)
        -- @treturn function `factory`: a v3 UUID generator.
        -- @treturn string `err`: a string describing an error
        -- @usage
        -- local uuid = require 'resty.jit-uuid'
        --
        -- local fact = assert(uuid.factory_v3('e6ebd542-06ae-11e6-8e82-bba81706b27d'))
        --
        -- local u1 = fact('hello')
        -- ---> 3db7a435-8c56-359d-a563-1b69e6802c78
        --
        -- local u2 = fact('foobar')
        -- ---> e8d3eeba-7723-3b72-bbc5-8f598afa6773
        function _M.factory_v3(namespace)
            return factory(namespace, v3_hash)
        end


        --- Instanciate a v5 UUID factory.
        -- @function factory_v5
        -- Creates a closure generating namespaced v5 UUIDs.
        -- @param[type=string] namespace (must be a valid UUID according to `is_valid`)
        -- @treturn function `factory`: a v5 UUID generator.
        -- @treturn string `err`: a string describing an error
        -- @usage
        -- local uuid = require 'resty.jit-uuid'
        --
        -- local fact = assert(uuid.factory_v5('e6ebd542-06ae-11e6-8e82-bba81706b27d'))
        --
        -- local u1 = fact('hello')
        -- ---> 4850816f-1658-5890-8bfd-1ed14251f1f0
        --
        -- local u2 = fact('foobar')
        -- ---> c9be99fc-326b-5066-bdba-dcd31a6d01ab
        function _M.factory_v5(namespace)
            return factory(namespace, v5_hash)
        end


        --- Generate a v3 UUID.
        -- v3 UUIDs are created from a namespace and a name (a UUID and a string).
        -- The same name and namespace result in the same UUID. The same name and
        -- different namespaces result in different UUIDs, and vice-versa.
        -- The resulting UUID is derived using MD5 hashing.
        --
        -- This is a sugar function which instanciates a short-lived v3 UUID factory.
        -- It is an expensive operation, and intensive generation using the same
        -- namespaces should prefer allocating their own long-lived factory with
        -- `factory_v3`.
        --
        -- @param[type=string] namespace (must be a valid UUID according to `is_valid`)
        -- @param[type=string] name
        -- @treturn string `uuid`: a v3 (namespaced) UUID.
        -- @treturn string `err`: a string describing an error
        -- @usage
        -- local uuid = require 'resty.jit-uuid'
        --
        -- local u = uuid.generate_v3('e6ebd542-06ae-11e6-8e82-bba81706b27d', 'hello')
        -- ---> 3db7a435-8c56-359d-a563-1b69e6802c78
        function _M.generate_v3(namespace, name)
            local fact, err = _M.factory_v3(namespace)
            if not fact then
                return nil, err
            end

            return fact(name)
        end


        --- Generate a v5 UUID.
        -- v5 UUIDs are created from a namespace and a name (a UUID and a string).
        -- The same name and namespace result in the same UUID. The same name and
        -- different namespaces result in different UUIDs, and vice-versa.
        -- The resulting UUID is derived using SHA-1 hashing.
        --
        -- This is a sugar function which instanciates a short-lived v5 UUID factory.
        -- It is an expensive operation, and intensive generation using the same
        -- namespaces should prefer allocating their own long-lived factory with
        -- `factory_v5`.
        --
        -- @param[type=string] namespace (must be a valid UUID according to `is_valid`)
        -- @param[type=string] name
        -- @treturn string `uuid`: a v5 (namespaced) UUID.
        -- @treturn string `err`: a string describing an error
        -- @usage
        -- local uuid = require 'resty.jit-uuid'
        --
        -- local u = uuid.generate_v5('e6ebd542-06ae-11e6-8e82-bba81706b27d', 'hello')
        -- ---> 4850816f-1658-5890-8bfd-1ed14251f1f0
        function _M.generate_v5(namespace, name)
            local fact, err = _M.factory_v5(namespace)
            if not fact then
                return nil, err
            end

            return fact(name)
        end

    else
        function _M.factory_v3() error('v3 UUID generation only supported in ngx_lua', 2) end
        function _M.generate_v3() error('v3 UUID generation only supported in ngx_lua', 2) end
        function _M.factory_v5() error('v5 UUID generation only supported in ngx_lua', 2) end
        function _M.generate_v5() error('v5 UUID generation only supported in ngx_lua', 2) end
    end
end


return setmetatable(_M, {
    __call = _M.generate_v4
})

Thank you.

@thibaultcha
Copy link
Owner

@dorongutman I just wrote a small ngx.math module you can use in ingress-nginx. Below is the code (math.lua), as well as an example usage (nginx.conf). It should work with all modern versions of OpenResty without any issue. You can then use the vanilla version of this UUID library without any issue.

The only rules to respect:

  1. Call require ngx.math before loading any other module (the only exception is resty.core which can require before ngx.math).
  2. Call math.randomseed (no arguments) early in init_by_lua* if you wish to use math.random in this phase.
  3. Call math.randomseed (no arguments) again early in init_worker_by_lua*. This should be the last time it is called.

Note that you can call math.randomseed or uuid.seed, up to you (the latter would call the former). Explicitness may be helpful here, I'd personally use math.randomseed.

This module comes with a lot of useful tricks:

  • It is "foolproof" and thus prevents any future code or third-party library to override the PRNG seed once it is already seeded.
  • It securely generates a unique seed for the master (init_by_lua*) and all worker processes via /dev/urandom and/or OpenSSL's RAND_bytes.
  • It returns the generated seed if you have a use for it.
  • It logs subsequent calls to math.randomseed (which safely NOP) with a stacktrace so you can identify code or third-party libraries incorrectly calling it and notify the appropriate maintainers.
  • It offers some configuration options

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:

$ tree .
.
├── client_body_temp
├── conf
│   └── nginx.conf
├── fastcgi_temp
├── logs
│   ├── access.log
│   ├── error.log
│   └── nginx.pid
├── ngx
│   └── math.lua
├── proxy_temp
├── scgi_temp
└── uwsgi_temp

8 directories, 5 files

@thibaultcha
Copy link
Owner

@dorongutman BTW, I would replace your implementation of init_worker_by_lua_block with:

sed -i .bck '1,/init_worker_by_lua_block/s/init_worker_by_lua_block {/init_worker_by_lua_block { \
        require "ngx.math" \
        math.randomseed() /tmp/nginx.tmpl

No need to require the math or os modules (which are already loaded anyway).

@thibaultcha
Copy link
Owner

@dorongutman Also, if you use OpenResty 1.13.6.2 or older, make sure that you are using lua-resty-core. Your init_by_lua_block directive should require it:

init_by_lua_block {
    require "resty.core"
}

OpenResty 1.15.8.1 and later versions load resty.core by default.

@dorongutman
Copy link
Author

@thibaultcha something's not working.
I'm using ingress-nginx version 0.25.1.
In it I had to put math.lua file you provided above in a lua folder, as the structure of the ingress-nginx is not like the one you provided.
In the lua folder I put the contents of the file you provided in a file called ngx-math.lua.

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
On it I'm running the following 2 sed replacements:

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()
...

results

The print from the init_by_lua_block (print("seed in init_by_lua*: ", seed)) isn't printing at all in the logs of the nginx-ingress pod.

I do get the following in the logs (from the run of init_worker_by_lua_block):

2019/08/22 10:41:59 [notice] 564#564: *874 [lua] init_worker_by_lua:4: seed in init_worker_by_lua*: nil, context: init_worker_by_lua*
2019/08/22 10:41:59 [notice] 564#564: *874 [lua] ngx-math.lua:143: original_randomseed(): PRNG already seeded in init_worker_by_lua*
stack traceback:
	/etc/nginx/lua/lua_ingress.lua:44: in function 'randomseed'
	/etc/nginx/lua/lua_ingress.lua:54: in function 'randomseed'
	/etc/nginx/lua/lua_ingress.lua:82: in function 'init_worker'
	init_worker_by_lua:7: in main chunk, context: init_worker_by_lua*

The lua_ingress.lua file referenced here is https://github.com/kubernetes/ingress-nginx/blob/nginx-0.25.1/rootfs/etc/nginx/lua/lua_ingress.lua


What am I doing wrong ?

@thibaultcha
Copy link
Owner

Please remove the PRNG seeding code from lua_ingress.lua and only use the math.lua module I provided above. Also, if you do not need to generate random numbers in init_by_lua*, then no need to invoke it there, you can wait until init_worker_by_lua*.

@timestee
Copy link

same issue, just follow the recommendations ,but still so easy to got the same 'uuid' !

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants