-
Notifications
You must be signed in to change notification settings - Fork 4.8k
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
feat(utils) generate random strings via CSPRNG #2536
Conversation
@p0pr0ck5 this contains changes to |
@Tieske nope, it's noted in the PR summary, we remove duplicate and conflicting cdefs from this handler. Otherwise we attempt to redefine an already loaded symbol |
BTW the Travis failures here are due to a hiccup with using the wrong build cache and the existing lua_system_constants shared object not being used in the test as part of the luarocks install. It should be ignored, or we can purge the caches to see the build through. |
Since OpenSSL's I would go a little bit further, and would also like to ask why LuaJIT's |
tl;dr: https://sockpuppet.org/blog/2014/02/25/safely-generate-random-numbers/ Another good thread: https://twitter.com/ircmaxell/status/653950793830297603 Userspace CSPRNGs aren't the best solution. We have access to the raw kernel entropy device, so let's use it while we can. For use cases that don't need/care about the best possible solution (or don't want to block on the read), this is why we provide a flag to ignore urandom. And, if we want a buffered solution for reading urandom bytes, we can look at something like lua-resty-urandom, which is getting a much-needed update now that lua_system_constants is updated.
Tausworthe is not a CSPRNG, and thus is certainly not an appropriate source of randomness for generating what we consider secret values. At a higher level, the small search space provided by using a UUID is a weakness IMO; we should absolutely expand the character set use to build these values. |
Another excellent resource I forgot to mention earlier: https://www.2uo.de/myths-about-urandom/ |
Yeah, I knew those articles and all the fuss about random/urandom. I would have thought OpenSSL's
That I wasn't sure of, but couldn't find any source to assert that. Do you have one?
Oh yeah for sure, using UUIDs for secret values was a flawed approach since day 1, definitely. |
The kernel source is a good one :) |
@bungle yes the method by which OpenSSL seeds itself is not the issue, the problem is that userspace CSPRNGs are inherently flawed. |
What do you think about this (it is a syscall but not file io): The thing with userspace (like OpenSSL) is that they will certainly be faster. |
@bungle yeah this is the ideal approach. But Yes, userspace would be "faster". In our use case (infrequent, asynchronous uses) we are not concerned with pure speed. We are concerned with providing the most secure solution. Trading off to a userspace CSPRNG purely for speed gains is not a valid argument here IMO. Having said that, I'm not ready to die on a urandom hill. If we're all truly opposed to a kernel-based solution, we can circle back to the argument, but at bare minimum we need to make |
It looks like the new |
@bungle yep im a dummy and made a typo after doing some live testing. thanks! |
kong/tools/utils.lua
Outdated
fallback = true | ||
end | ||
|
||
local res = ffi.C.read(fd, buf, n_bytes) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we fail opening /dev/urandom
, we still try to read from it, and later on, to close the file descriptor? Wouldn't that leave use with 3 WARN logs instead of one, potentially?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, refactored approach to avoid this. let me know your thoughts.
spec/01-unit/04-utils_spec.lua
Outdated
@@ -135,9 +135,15 @@ describe("Utils", function() | |||
describe("random_string()", function() | |||
it("should return a random string", function() | |||
local first = utils.random_string() | |||
assert.truthy(first) | |||
assert.falsy(first:find("-")) | |||
assert.equals("string", type(first)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
assert.is_string(first)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done, thanks
spec/01-unit/04-utils_spec.lua
Outdated
assert.equals(32, #first) | ||
|
||
-- ensure we don't find anything that isnt alphanumeric | ||
assert.is_nil(first:find("^[^%a%d]+$")) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Something like assert.not_matches("^[^%a%d]+$", first)
maybe
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done, thanks
(same issue with test failures here, this requires cache purging in order to pass because it tries to use the old version of the lua_system_constants object while running the tests as part of the local install). |
The current implementation of utils.random_string() leverages the LuaJIT math.random() under the hood to generate bytes; this is unsuitable for cases where cryptographically secure random data is needed, such as fallback values for authentication plugin secret values. To correct this, we introduce a wrapper around the kernel CSPRNG (via /dev/urandom) to read random bytes, and wrap utils.random_string around this. We also return these bytes in a modified Base64 encoding (replacing non-alphanumeric characters with random alphanumeric replacements); this serves to increase the size of the keyspace significantly while attempting to maintain some backwards compatibility with previous generated string parameters (e.g. by generating a string of the same size and a somewhat matching pattern). The underlying get_rand_bytes implementation is modified to read from /dev/urandom when explicitly requested, and falling back to OpenSSL's RAND_bytes when reading from urandom fails. The blocking read from urandom is acceptable when explicitly requested, as this is typically done in asynchronous environments (e.g. admin API requests), where the need for strong psuedorandomness outweighs the overhead of I/O and talking to the kernel.
(rebased following 0.10.3 release) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM 👍 Was lua_system_constants released yet? It seems CI is failing due to it currently.
@thibaultcha yep lua_system_constants was deployed before this PR was pushed: http://luarocks.org:8080/modules/mashape/lua_system_constants/0.1.2-0 The CI is failing because, as a result of the travis cache, luarocks is using the 0.1.1 version of the shared object when trying to run the tests as part of the dep update process. With a clean cache this passes. |
Summary
The current implementation of utils.random_string() leverages the LuaJIT math.random() under the hood to generate bytes; this is unsuitable for cases where cryptographically secure random data is needed, such as fallback values for authentication plugin secret values. To correct this, we introduce a wrapper around the kernel CSPRNG (via /dev/urandom) to read random bytes, and wrap utils.random_string around this. We also return these bytes in a modified Base64 encoding (replacing non-alphanumeric characters with random alphanumeric replacements); this serves to increase the size of the keyspace significantly while attempting to maintain some backwards compatibility with previous generated string
parameters (e.g. by generating a string of the same size and a somewhat matching pattern).
The underlying get_rand_bytes implementation is modified to read from /dev/urandom when explicitly requested, and falling back to OpenSSL's RAND_bytes when reading from urandom fails. The blocking read from urandom is acceptable when explicitly requested, as this is typically done in asynchronous environments (e.g. admin API requests), where the need for strong psuedorandomness outweighs the overhead of I/O and talking to the kernel.
Full changelog