From 2b30a745c27733fe2b4c8eb2dee40b19b4c5d512 Mon Sep 17 00:00:00 2001 From: leaf corcoran Date: Tue, 5 Oct 2021 12:54:23 -0700 Subject: [PATCH 01/23] add bxor to bit library, misc cleanup --- pgmoon/bit.lua | 38 +++++++++++++++++++++++++------------- pgmoon/bit.moon | 49 +++++++++++++++++++++++++++++-------------------- 2 files changed, 54 insertions(+), 33 deletions(-) diff --git a/pgmoon/bit.lua b/pgmoon/bit.lua index 102f944..1b99ceb 100644 --- a/pgmoon/bit.lua +++ b/pgmoon/bit.lua @@ -1,16 +1,17 @@ -local rshift, lshift, band, ok, _ -local string_loader -string_loader = function(str) +local rshift, lshift, band, bxor +local load_code +load_code = function(str) local sent = false - return function() + return pcall(load(function() if sent then return nil end sent = true return str - end + end)) end -ok, band = pcall(load(string_loader([[ return function(a,b) +local ok +ok, band = load_code([[ return function(a,b) a = a & b if a > 0x7FFFFFFF then -- extend the sign bit @@ -18,9 +19,19 @@ ok, band = pcall(load(string_loader([[ return function(a,b) end return a end -]]))) +]]) if ok then - _, lshift = pcall(load(string_loader([[ return function(x,y) + local _ + _, bxor = load_code([[ return function(a,b) + a = a ~ b + if a > 0x7FFFFFFF then + -- extend the sign bit + a = ~0xFFFFFFFF | a + end + return a + end + ]]) + _, lshift = load_code([[ return function(x,y) -- limit to 32-bit shifts y = y % 32 x = x << y @@ -30,8 +41,8 @@ if ok then end return x end - ]]))) - _, rshift = pcall(load(string_loader([[ return function(x,y) + ]]) + _, rshift = load_code([[ return function(x,y) y = y % 32 -- truncate to 32-bit before applying shift x = x & 0xFFFFFFFF @@ -41,15 +52,16 @@ if ok then end return x end - ]]))) + ]]) else do local _obj_0 = require("bit") - rshift, lshift, band = _obj_0.rshift, _obj_0.lshift, _obj_0.band + rshift, lshift, band, bxor = _obj_0.rshift, _obj_0.lshift, _obj_0.band, _obj_0.bxor end end return { rshift = rshift, lshift = lshift, - band = band + band = band, + bxor = bxor } diff --git a/pgmoon/bit.moon b/pgmoon/bit.moon index f2a2cc6..df253fd 100644 --- a/pgmoon/bit.moon +++ b/pgmoon/bit.moon @@ -1,20 +1,16 @@ -local rshift, lshift, band, ok, _ -local string_loader - +local rshift, lshift, band, bxor -- lua5.1 has separate 'loadstring' and 'load' -- functions ('load' doesn't accept strings). -- This provides a function that 'load' can use, -- and will work on all versions of lua -string_loader = (str) -> +load_code = (str) -> sent = false - return -> - if sent then - return nil + pcall load -> + return nil if sent sent = true - return str - + str -- use load to treat as a string to prevent -- parse errors under lua < 5.3 @@ -23,7 +19,7 @@ string_loader = (str) -> -- uses 32-bit or 64-bit integers, so these wrappers will -- truncate results and/or extend the sign, as appropriate -- to match luajit's behavior. -ok, band = pcall(load(string_loader([[ +ok, band = load_code [[ return function(a,b) a = a & b if a > 0x7FFFFFFF then @@ -32,10 +28,21 @@ ok, band = pcall(load(string_loader([[ end return a end -]]))) +]] if ok then - _, lshift = pcall(load(string_loader([[ + _, bxor = load_code [[ + return function(a,b) + a = a ~ b + if a > 0x7FFFFFFF then + -- extend the sign bit + a = ~0xFFFFFFFF | a + end + return a + end + ]] + + _, lshift = load_code [[ return function(x,y) -- limit to 32-bit shifts y = y % 32 @@ -46,8 +53,9 @@ if ok then end return x end - ]]))) - _, rshift = pcall(load(string_loader([[ + ]] + + _, rshift = load_code [[ return function(x,y) y = y % 32 -- truncate to 32-bit before applying shift @@ -58,13 +66,14 @@ if ok then end return x end - ]]))) + ]] else - import rshift, lshift, band from require "bit" + import rshift, lshift, band, bxor from require "bit" -return { - rshift: rshift - lshift: lshift - band: band +{ + :rshift + :lshift + :band + :bxor } From 6eb1cc7fb6404b5ad96f944be9915f3e85e6bc99 Mon Sep 17 00:00:00 2001 From: leaf corcoran Date: Wed, 6 Oct 2021 16:08:15 -0700 Subject: [PATCH 02/23] add sha256 functions to crypto module for scram #108 --- pgmoon/crypto.lua | 52 +++++++++++++++++++++++++++++----------------- pgmoon/crypto.moon | 38 +++++++++++++++++---------------- 2 files changed, 53 insertions(+), 37 deletions(-) diff --git a/pgmoon/crypto.lua b/pgmoon/crypto.lua index e8f1572..020a0d4 100644 --- a/pgmoon/crypto.lua +++ b/pgmoon/crypto.lua @@ -1,11 +1,10 @@ -if ngx then - return { - md5 = ngx.md5 - } -end local md5 -pcall(function() - local digest = require("openssl.digest") +if ngx then + md5 = ngx.md5 +elseif pcall(function() + return require("openssl.digest") +end) then + local openssl_digest = require("openssl.digest") local hex_char hex_char = function(c) return string.format("%02x", string.byte(c)) @@ -15,20 +14,35 @@ pcall(function() return (str:gsub(".", hex_char)) end md5 = function(str) - return hex(digest.new("md5"):final(str)) + return hex(openssl_digest.new("md5"):final(str)) end -end) -if not (md5) then - pcall(function() - local crypto = require("crypto") - md5 = function(str) - return crypto.digest("md5", str) - end - end) +elseif pcall(function() + return require("crypto") +end) then + local crypto = require("crypto") + md5 = function(str) + return crypto.digest("md5", str) + end +else + md5 = function() + return error("Either luaossl (recommended) or LuaCrypto is required to calculate md5") + end +end +local hmac_sha256 +hmac_sha256 = function(key, str) + local openssl_hmac = require("openssl.hmac") + local hmac = assert(openssl_hmac.new(key, "sha256")) + hmac:update(str) + return assert(hmac:final()) end -if not (md5) then - error("Either luaossl (recommended) or LuaCrypto is required to calculate md5") +local digest_sha256 +digest_sha256 = function(str) + local digest = assert(require("openssl.digest").new("sha256")) + digest:update(str) + return assert(digest:final()) end return { - md5 = md5 + md5 = md5, + hmac_sha256 = hmac_sha256, + digest_sha256 = digest_sha256 } diff --git a/pgmoon/crypto.moon b/pgmoon/crypto.moon index a810996..269b54f 100644 --- a/pgmoon/crypto.moon +++ b/pgmoon/crypto.moon @@ -1,26 +1,28 @@ -if ngx - return { md5: ngx.md5 } - -local md5 - -pcall -> - digest = require "openssl.digest" - +md5 = if ngx + ngx.md5 +elseif pcall -> require "openssl.digest" + openssl_digest = require "openssl.digest" hex_char = (c) -> string.format "%02x", string.byte c hex = (str) -> (str\gsub ".", hex_char) - md5 = (str) -> - hex digest.new("md5")\final str + (str) -> hex openssl_digest.new("md5")\final str +elseif pcall -> require "crypto" + crypto = require "crypto" + (str) -> crypto.digest "md5", str +else + -> error "Either luaossl (recommended) or LuaCrypto is required to calculate md5" -unless md5 - pcall -> - crypto = require "crypto" +hmac_sha256 = (key, str) -> + openssl_hmac = require("openssl.hmac") + hmac = assert openssl_hmac.new(key, "sha256") - md5 = (str) -> - crypto.digest "md5", str + hmac\update str + assert hmac\final! -unless md5 - error "Either luaossl (recommended) or LuaCrypto is required to calculate md5" +digest_sha256 = (str) -> + digest = assert require("openssl.digest").new("sha256") + digest\update str + assert digest\final! -{ :md5 } +{ :md5, :hmac_sha256, :digest_sha256 } From 817c0dc217d8ec8e45efb41a3541b3671e9ccafd Mon Sep 17 00:00:00 2001 From: leaf corcoran Date: Wed, 13 Oct 2021 11:09:55 -0700 Subject: [PATCH 03/23] convert to scram sha256 to moon #108 #101 --- pgmoon/crypto.lua | 22 +++++- pgmoon/crypto.moon | 23 +++++- pgmoon/init.lua | 169 ++++++++++++++++++++++++++++++++++++++++- pgmoon/init.moon | 184 ++++++++++++++++++++++++++++++++++++++++++++- pgmoon/util.lua | 23 +++++- pgmoon/util.moon | 11 ++- 6 files changed, 425 insertions(+), 7 deletions(-) diff --git a/pgmoon/crypto.lua b/pgmoon/crypto.lua index 020a0d4..cd1a938 100644 --- a/pgmoon/crypto.lua +++ b/pgmoon/crypto.lua @@ -41,8 +41,28 @@ digest_sha256 = function(str) digest:update(str) return assert(digest:final()) end +local kdf_derive_sha256 +kdf_derive_sha256 = function(str, salt, i) + local openssl_kdf = require("openssl.kdf") + local decode_base64 + decode_base64 = require("pgmoon.util").decode_base64 + salt = decode_base64(salt) + local key, err = openssl_kdf.derive({ + type = "PBKDF2", + md = "sha256", + salt = salt, + iter = i, + pass = str, + outlen = 32 + }) + if not (key) then + return nil, "failed to derive pbkdf2 key: " .. tostring(err) + end + return key +end return { md5 = md5, hmac_sha256 = hmac_sha256, - digest_sha256 = digest_sha256 + digest_sha256 = digest_sha256, + kdf_derive_sha256 = kdf_derive_sha256 } diff --git a/pgmoon/crypto.moon b/pgmoon/crypto.moon index 269b54f..89c6836 100644 --- a/pgmoon/crypto.moon +++ b/pgmoon/crypto.moon @@ -25,4 +25,25 @@ digest_sha256 = (str) -> digest\update str assert digest\final! -{ :md5, :hmac_sha256, :digest_sha256 } + +kdf_derive_sha256 = (str, salt, i) -> + openssl_kdf = require "openssl.kdf" + import decode_base64 from require "pgmoon.util" + + salt = decode_base64 salt + + key, err = openssl_kdf.derive { + type: "PBKDF2" + md: "sha256" + salt: salt + iter: i + pass: str + outlen: 32 -- our H() produces a 32 byte hash value (SHA-256) + } + + unless key + return nil, "failed to derive pbkdf2 key: #{err}" + + key + +{ :md5, :hmac_sha256, :digest_sha256, :kdf_derive_sha256 } diff --git a/pgmoon/init.lua b/pgmoon/init.lua index c95df35..74bfd79 100644 --- a/pgmoon/init.lua +++ b/pgmoon/init.lua @@ -1,10 +1,10 @@ local socket = require("pgmoon.socket") local insert insert = table.insert -local rshift, lshift, band +local rshift, lshift, band, bxor do local _obj_0 = require("pgmoon.bit") - rshift, lshift, band = _obj_0.rshift, _obj_0.lshift, _obj_0.band + rshift, lshift, band, bxor = _obj_0.rshift, _obj_0.lshift, _obj_0.band, _obj_0.bxor end local unpack = table.unpack or unpack local VERSION = "1.12.0" @@ -252,6 +252,8 @@ do return self:cleartext_auth(msg) elseif 5 == _exp_0 then return self:md5_auth(msg) + elseif 10 == _exp_0 then + return self:scram_sha_256_auth(msg) else return error("don't know how to auth: " .. tostring(auth_type)) end @@ -264,6 +266,169 @@ do }) return self:check_auth() end, + scram_sha_256_auth = function(self, msg) + assert(self.password, "missing password, required for connect") + local openssl_rand = require("openssl.rand") + local rand_bytes = assert(openssl_rand.bytes(18)) + local encode_base64 + encode_base64 = require("pgmoon.util").encode_base64 + local c_nonce = encode_base64(rand_bytes) + local nonce = "r=" .. c_nonce + local saslname = "" + local username = "n=" .. saslname + local client_first_message_bare = username .. "," .. nonce + local plus = false + local bare = false + if msg:match("SCRAM%-SHA%-256%-PLUS") then + plus = true + elseif msg:match("SCRAM%-SHA%-256") then + bare = true + else + error("unsupported SCRAM mechanism name: " .. tostring(msg)) + end + local gs2_cbind_flag + local gs2_header + local cbind_input + local mechanism_name + if bare then + gs2_cbind_flag = "n" + gs2_header = gs2_cbind_flag .. ",," + cbind_input = gs2_header + mechanism_name = "SCRAM-SHA-256" .. NULL + elseif plus then + local cb_name = "tls-server-end-point" + gs2_cbind_flag = "p=" .. cb_name + gs2_header = gs2_cbind_flag .. ",," + mechanism_name = "SCRAM-SHA-256-PLUS" .. NULL + local cbind_data + do + local pem + local signature + if self.sock_type == "luasocket" then + local server_cert = self.sock:getpeercertificate() + pem, signature = server_cert:pem(), server_cert:getsignaturename() + else + local ssl = require("resty.openssl.ssl").from_socket(self.sock) + local server_cert = ssl:get_peer_certificate() + pem, signature = server_cert:to_PEM(), server_cert:get_signature_name() + end + signature = signature:lower() + if signature:match("md5") or signature:match("sha1") then + signature = "sha256" + end + local openssl_x509 = require("openssl.x509").new(pem, "PEM") + local openssl_x509_digest = assert(openssl_x509:digest(signature, "s")) + cbind_data = openssl_x509_digest + end + cbind_input = gs2_header .. cbind_data + end + local client_first_message = gs2_header .. client_first_message_bare + self:send_message(MSG_TYPE.password, { + mechanism_name, + self:encode_int(#client_first_message), + client_first_message + }) + local t + t, msg = self:receive_message() + if not (t) then + return nil, msg + end + local server_first_message = msg:sub(5) + local int32 = self:decode_int(msg, 4) + if int32 == nil or int32 ~= 11 then + return nil, "server_first_message error: " .. msg + end + local channel_binding = "c=" .. encode_base64(cbind_input) + nonce = server_first_message:match("([^,]+)") + if not (nonce) then + return nil, "malformed server message (nonce)" + end + local client_final_message_without_proof = channel_binding .. "," .. nonce + local xor + xor = function(a, b) + local result + do + local _accum_0 = { } + local _len_0 = 1 + for i = 1, #a do + local x = a:byte(i) + local y = b:byte(i) + if not (x and y) then + return nil + end + local _value_0 = string.char(bxor(x, y)) + _accum_0[_len_0] = _value_0 + _len_0 = _len_0 + 1 + end + result = _accum_0 + end + return table.concat(result) + end + local salt = server_first_message:match(",s=([^,]+)") + if not (salt) then + return nil, "malformed server message (salt)" + end + local i = server_first_message:match(",i=(.+)") + if not (i) then + return nil, "malformed server message (iteraton count)" + end + if tonumber(i) < 4096 then + return nil, "the iteration-count sent by the server is less than 4096" + end + local kdf_derive_sha256, hmac_sha256, digest_sha256 + do + local _obj_0 = require("pgmoon.crypto") + kdf_derive_sha256, hmac_sha256, digest_sha256 = _obj_0.kdf_derive_sha256, _obj_0.hmac_sha256, _obj_0.digest_sha256 + end + local salted_password, err = kdf_derive_sha256(self.password, salt, tonumber(i)) + if not (salted_password) then + return nil, err + end + local client_key + client_key, err = hmac_sha256(salted_password, "Client Key") + if not (client_key) then + return nil, err + end + local stored_key + stored_key, err = digest_sha256(client_key) + if not (stored_key) then + return nil, err + end + local auth_message = tostring(client_first_message_bare) .. "," .. tostring(server_first_message) .. "," .. tostring(client_final_message_without_proof) + local client_signature + client_signature, err = hmac_sha256(stored_key, auth_message) + if not (client_signature) then + return nil, err + end + local proof = xor(client_key, client_signature) + if not (proof) then + return nil, "failed to generate the client proof" + end + local client_final_message = tostring(client_final_message_without_proof) .. ",p=" .. tostring(encode_base64(proof)) + self:send_message(MSG_TYPE.password, { + client_final_message + }) + t, msg = self:receive_message() + if not (t) then + return nil, msg + end + local server_key + server_key, err = hmac_sha256(salted_password, "Server Key") + if not (server_key) then + return nil, err + end + local server_signature + server_signature, err = hmac_sha256(server_key, auth_message) + if not (server_signature) then + return nil, err + end + server_signature = encode_base64(server_signature) + local sent_server_signature = msg:match("v=([^,]+)") + if server_signature ~= sent_server_signature then + return nil, "authentication exchange unsuccessful" + end + return self:check_auth() + end, md5_auth = function(self, msg) local md5 md5 = require("pgmoon.crypto").md5 diff --git a/pgmoon/init.moon b/pgmoon/init.moon index 4f9fa61..c42f60c 100644 --- a/pgmoon/init.moon +++ b/pgmoon/init.moon @@ -1,10 +1,13 @@ socket = require "pgmoon.socket" import insert from table -import rshift, lshift, band from require "pgmoon.bit" +import rshift, lshift, band, bxor from require "pgmoon.bit" unpack = table.unpack or unpack +-- Protocol documentation: +-- https://www.postgresql.org/docs/current/protocol-message-formats.html + VERSION = "1.12.0" _len = (thing, t=type(thing)) -> @@ -232,6 +235,8 @@ class Postgres @cleartext_auth msg when 5 -- md5 password @md5_auth msg + when 10 -- AuthenticationSASL + @scram_sha_256_auth msg else error "don't know how to auth: #{auth_type}" @@ -245,6 +250,183 @@ class Postgres @check_auth! + -- https://www.postgresql.org/docs/current/sasl-authentication.html#SASL-SCRAM-SHA-256 + scram_sha_256_auth: (msg) => + assert @password, "missing password, required for connect" + + openssl_rand = require "openssl.rand" + + -- '18' is the number set by postgres on the server side + rand_bytes = assert openssl_rand.bytes 18 + + import encode_base64 from require "pgmoon.util" + + c_nonce = encode_base64 rand_bytes + nonce = "r=" .. c_nonce + saslname = "" + username = "n=" .. saslname + client_first_message_bare = username .. "," .. nonce + + plus = false + bare = false + + if msg\match "SCRAM%-SHA%-256%-PLUS" + plus = true + elseif msg\match "SCRAM%-SHA%-256" + bare = true + else + error "unsupported SCRAM mechanism name: " .. tostring(msg) + + local gs2_cbind_flag + local gs2_header + local cbind_input + local mechanism_name + + if bare + gs2_cbind_flag = "n" + gs2_header = gs2_cbind_flag .. ",," + cbind_input = gs2_header + mechanism_name = "SCRAM-SHA-256" .. NULL + elseif plus + cb_name = "tls-server-end-point" + gs2_cbind_flag = "p=" .. cb_name + gs2_header = gs2_cbind_flag .. ",," + mechanism_name = "SCRAM-SHA-256-PLUS" .. NULL + + cbind_data = do + local pem + + pem, signature = if @sock_type == "luasocket" + server_cert = @sock\getpeercertificate() + server_cert\pem!, server_cert\getsignaturename! + else + ssl = require("resty.openssl.ssl").from_socket(@sock) + server_cert = ssl\get_peer_certificate() + + server_cert\to_PEM!, server_cert\get_signature_name! + + signature = signature\lower! + + if signature\match("md5") or signature\match("sha1") then + signature = "sha256" + + openssl_x509 = require("openssl.x509").new(pem, "PEM") + openssl_x509_digest = assert openssl_x509\digest(signature, "s") + + openssl_x509_digest + + cbind_input = gs2_header .. cbind_data + + client_first_message = gs2_header .. client_first_message_bare + + @send_message MSG_TYPE.password, { + mechanism_name + self\encode_int(#client_first_message) + client_first_message + } + + t, msg = @receive_message() + + unless t + return nil, msg + + server_first_message = msg\sub 5 + int32 = @decode_int msg, 4 + + if int32 == nil or int32 != 11 + return nil, "server_first_message error: " .. msg + + channel_binding = "c=" .. encode_base64 cbind_input + nonce = server_first_message\match "([^,]+)" + + unless nonce + return nil, "malformed server message (nonce)" + + client_final_message_without_proof = channel_binding .. "," .. nonce + + xor = (a, b) -> + result = for i=1,#a + x = a\byte i + y = b\byte i + + unless x and y + return nil + + string.char bxor x, y + + table.concat result + + salt = server_first_message\match ",s=([^,]+)" + + unless salt + return nil, "malformed server message (salt)" + + i = server_first_message\match ",i=(.+)" + + unless i + return nil, "malformed server message (iteraton count)" + + if tonumber(i) < 4096 + return nil, "the iteration-count sent by the server is less than 4096" + + import kdf_derive_sha256, hmac_sha256, digest_sha256 from require "pgmoon.crypto" + salted_password, err = kdf_derive_sha256 @password, salt, tonumber i + + unless salted_password + return nil, err + + client_key, err = hmac_sha256 salted_password, "Client Key" + + unless client_key + return nil, err + + stored_key, err = digest_sha256 client_key + + unless stored_key + return nil, err + + auth_message = "#{client_first_message_bare },#{server_first_message },#{client_final_message_without_proof}" + + client_signature, err = hmac_sha256 stored_key, auth_message + + unless client_signature + return nil, err + + proof = xor client_key, client_signature + + unless proof + return nil, "failed to generate the client proof" + + client_final_message = "#{client_final_message_without_proof },p=#{encode_base64 proof}" + + @send_message MSG_TYPE.password, { + client_final_message + } + + t, msg = @receive_message() + + unless t + return nil, msg + + server_key, err = hmac_sha256 salted_password, "Server Key" + + unless server_key + return nil, err + + server_signature, err = hmac_sha256 server_key, auth_message + + unless server_signature + return nil, err + + + server_signature = encode_base64 server_signature + sent_server_signature = msg\match "v=([^,]+)" + + if server_signature != sent_server_signature then + return nil, "authentication exchange unsuccessful" + + @check_auth! + md5_auth: (msg) => import md5 from require "pgmoon.crypto" salt = msg\sub 5, 8 diff --git a/pgmoon/util.lua b/pgmoon/util.lua index 0d40e35..9752404 100644 --- a/pgmoon/util.lua +++ b/pgmoon/util.lua @@ -20,6 +20,27 @@ do return table.concat(buffer) end end +local encode_base64, decode_base64 +if ngx then + do + local _obj_0 = ngx + encode_base64, decode_base64 = _obj_0.encode_base64, _obj_0.decode_base64 + end +else + local b64, unb64 + do + local _obj_0 = require("mime") + b64, unb64 = _obj_0.b64, _obj_0.unb64 + end + encode_base64 = function(...) + return (b64(...)) + end + decode_base64 = function(...) + return (unb64(...)) + end +end return { - flatten = flatten + flatten = flatten, + encode_base64 = encode_base64, + decode_base64 = decode_base64 } diff --git a/pgmoon/util.moon b/pgmoon/util.moon index 6ac64f5..9a4d937 100644 --- a/pgmoon/util.moon +++ b/pgmoon/util.moon @@ -16,4 +16,13 @@ flatten = do __flatten t, buffer table.concat buffer -{:flatten} +local encode_base64, decode_base64 + +if ngx + {:encode_base64, :decode_base64} = ngx +else + { :b64, :unb64 } = require "mime" -- provided by luasocket + encode_base64 = (...) -> (b64 ...) + decode_base64 = (...) -> (unb64 ...) + +{:flatten, :encode_base64, :decode_base64} From db0372017490386890203eddd5feb37679bc5aa3 Mon Sep 17 00:00:00 2001 From: leaf corcoran Date: Wed, 13 Oct 2021 12:10:48 -0700 Subject: [PATCH 04/23] support cqueues socket type #101 --- pgmoon/cqueues.lua | 4 ++++ pgmoon/cqueues.moon | 5 +++++ pgmoon/init.lua | 33 ++++++++++++++++++--------------- pgmoon/init.moon | 34 +++++++++++++++++----------------- 4 files changed, 44 insertions(+), 32 deletions(-) diff --git a/pgmoon/cqueues.lua b/pgmoon/cqueues.lua index 9e5b238..a5c20bb 100644 --- a/pgmoon/cqueues.lua +++ b/pgmoon/cqueues.lua @@ -24,6 +24,10 @@ do sslhandshake = function(self) return self.sock:starttls() end, + getpeercertificate = function(self) + local ssl = assert(self.sock:checktls()) + return assert(ssl:getPeerCertificate(), "no peer certificate available") + end, send = function(self, ...) return self.sock:write(flatten(...)) end, diff --git a/pgmoon/cqueues.moon b/pgmoon/cqueues.moon index 47237dc..62dc8c3 100644 --- a/pgmoon/cqueues.moon +++ b/pgmoon/cqueues.moon @@ -24,6 +24,11 @@ class CqueuesSocket sslhandshake: => @sock\starttls! + -- returns openssl.x509 object + getpeercertificate: => + ssl = assert @sock\checktls! + assert ssl\getPeerCertificate!, "no peer certificate available" + send: (...) => @sock\write flatten ... diff --git a/pgmoon/init.lua b/pgmoon/init.lua index 74bfd79..ecc3359 100644 --- a/pgmoon/init.lua +++ b/pgmoon/init.lua @@ -302,23 +302,26 @@ do mechanism_name = "SCRAM-SHA-256-PLUS" .. NULL local cbind_data do - local pem - local signature - if self.sock_type == "luasocket" then - local server_cert = self.sock:getpeercertificate() - pem, signature = server_cert:pem(), server_cert:getsignaturename() + if self.sock_type == "cqueues" then + local openssl_x509 = self.sock:getpeercertificate() + cbind_data = openssl_x509:digest("sha256", "s") else - local ssl = require("resty.openssl.ssl").from_socket(self.sock) - local server_cert = ssl:get_peer_certificate() - pem, signature = server_cert:to_PEM(), server_cert:get_signature_name() - end - signature = signature:lower() - if signature:match("md5") or signature:match("sha1") then - signature = "sha256" + local pem, signature + if self.sock_type == "nginx" then + local ssl = require("resty.openssl.ssl").from_socket(self.sock) + local server_cert = ssl:get_peer_certificate() + pem, signature = server_cert:to_PEM(), server_cert:get_signature_name() + else + local server_cert = self.sock:getpeercertificate() + pem, signature = server_cert:pem(), server_cert:getsignaturename() + end + signature = signature:lower() + if signature:match("^md5") or signature:match("^sha1") then + signature = "sha256" + end + local openssl_x509 = require("openssl.x509").new(pem, "PEM") + cbind_data = assert(openssl_x509:digest(signature, "s")) end - local openssl_x509 = require("openssl.x509").new(pem, "PEM") - local openssl_x509_digest = assert(openssl_x509:digest(signature, "s")) - cbind_data = openssl_x509_digest end cbind_input = gs2_header .. cbind_data end diff --git a/pgmoon/init.moon b/pgmoon/init.moon index c42f60c..d3bc4d5 100644 --- a/pgmoon/init.moon +++ b/pgmoon/init.moon @@ -294,26 +294,26 @@ class Postgres mechanism_name = "SCRAM-SHA-256-PLUS" .. NULL cbind_data = do - local pem - - pem, signature = if @sock_type == "luasocket" - server_cert = @sock\getpeercertificate() - server_cert\pem!, server_cert\getsignaturename! + if @sock_type == "cqueues" + openssl_x509 = @sock\getpeercertificate! + openssl_x509\digest "sha256", "s" else - ssl = require("resty.openssl.ssl").from_socket(@sock) - server_cert = ssl\get_peer_certificate() - - server_cert\to_PEM!, server_cert\get_signature_name! - - signature = signature\lower! + pem, signature = if @sock_type == "nginx" + ssl = require("resty.openssl.ssl").from_socket(@sock) + server_cert = ssl\get_peer_certificate() + server_cert\to_PEM!, server_cert\get_signature_name! + else + server_cert = @sock\getpeercertificate() + server_cert\pem!, server_cert\getsignaturename! - if signature\match("md5") or signature\match("sha1") then - signature = "sha256" + signature = signature\lower! - openssl_x509 = require("openssl.x509").new(pem, "PEM") - openssl_x509_digest = assert openssl_x509\digest(signature, "s") + -- upgrade the signature if necessary + if signature\match("^md5") or signature\match("^sha1") + signature = "sha256" - openssl_x509_digest + openssl_x509 = require("openssl.x509").new(pem, "PEM") + assert openssl_x509\digest(signature, "s") cbind_input = gs2_header .. cbind_data @@ -321,7 +321,7 @@ class Postgres @send_message MSG_TYPE.password, { mechanism_name - self\encode_int(#client_first_message) + @encode_int #client_first_message client_first_message } From 2a609d786b94deec1de803d69f3f5e86117443ba Mon Sep 17 00:00:00 2001 From: Matt Campbell Date: Mon, 4 Oct 2021 15:11:02 -0400 Subject: [PATCH 05/23] Make application_name pg client init param configurable --- pgmoon/init.moon | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pgmoon/init.moon b/pgmoon/init.moon index d3bc4d5..e8d9faa 100644 --- a/pgmoon/init.moon +++ b/pgmoon/init.moon @@ -176,6 +176,7 @@ class Postgres ssl_version: opts.ssl_version or "any" options: { "all", "no_sslv2", "no_sslv3", "no_tlsv1" } } + @application_name = opts.application_name or "pgmoon" connect: => opts = if @sock_type == "nginx" @@ -692,7 +693,7 @@ class Postgres "database", NULL @database, NULL "application_name", NULL - "pgmoon", NULL + @application_name, NULL NULL } From 6397263b4d859e7814460d62f89a91dcfbf5010a Mon Sep 17 00:00:00 2001 From: leaf corcoran Date: Wed, 13 Oct 2021 12:17:49 -0700 Subject: [PATCH 06/23] rebuild #110 --- pgmoon/init.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pgmoon/init.lua b/pgmoon/init.lua index ecc3359..19846bb 100644 --- a/pgmoon/init.lua +++ b/pgmoon/init.lua @@ -728,7 +728,7 @@ do NULL, "application_name", NULL, - "pgmoon", + self.application_name, NULL, NULL } @@ -864,6 +864,7 @@ do "no_tlsv1" } } + self.application_name = opts.application_name or "pgmoon" end end, __base = _base_0, From a22ae0825903ace24543117f73af51c3b01d97bc Mon Sep 17 00:00:00 2001 From: leaf corcoran Date: Wed, 13 Oct 2021 12:22:29 -0700 Subject: [PATCH 07/23] minor readme updates --- README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 081a390..1340ff4 100644 --- a/README.md +++ b/README.md @@ -58,11 +58,11 @@ If you want to use SSL connections with LuaSocket then you will need LuaSec: $ luarocks install luasec ``` -If you want to use password authentication then you will need a crypto library: +Password authentication or SSL connections will require a crypto library: -* [OpenResty](https://openresty.org/en/) — Built in function will be used, no additional dependencies necessary * [luaossl](https://github.com/wahern/luaossl) — Recommended `luarocks install luaossl` -* [luacrypto](https://github.com/starius/luacrypto) — Deprecated library, not recommended +* [OpenResty](https://openresty.org/en/) — Built in function will be used if possible, otherwise `luaossl` is necessary +* [luacrypto](https://github.com/starius/luacrypto) — Deprecated library, not recommended, but will be used with LuaSocket if available ## Example @@ -107,6 +107,7 @@ of options. The table can have the following keys: * `"ssl_required"`: abort the connection if the server does not support SSL connections (default: `nil`) * `"pool"`: optional name of pool to use when using OpenResty cosocket (defaults to `"#{host}:#{port}:#{database}"`) * `"socket_type"`: optional, the type of socket to use, one of: `"nginx"`, `"luasocket"`, `cqueues` (default: `"nginx"` if in nginx, `"luasocket"` otherwise) +* `"application_name"`: set the name of the connection as displayed in `pg_stat_activity`. (default: `"pgmoon"`) Methods on the `Postgres` object returned by `new`: From 8b192853b60283aa53d76e1b6886557db3d4c9e8 Mon Sep 17 00:00:00 2001 From: jiahao Date: Fri, 30 Apr 2021 14:22:09 +0800 Subject: [PATCH 08/23] feature: added 'backlog' and 'pool_size' options while using ngx.socket. --- README.md | 7 +++++++ pgmoon/init.lua | 6 +++++- pgmoon/init.moon | 4 ++++ 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 1340ff4..4a3d62f 100644 --- a/README.md +++ b/README.md @@ -106,6 +106,13 @@ of options. The table can have the following keys: * `"ssl_verify"`: verify server certificate (default: `nil`) * `"ssl_required"`: abort the connection if the server does not support SSL connections (default: `nil`) * `"pool"`: optional name of pool to use when using OpenResty cosocket (defaults to `"#{host}:#{port}:#{database}"`) +* `"pool_size"`: optional size of pool to use when using OpenResty cosocket (default size equal to the value of the `lua_socket_pool_size` directive) +* `"backlog"`: optional size of backlog to use when using OpenResty cosocket. + If specified, this module will limit the total number of opened connections + for this pool. No more connections than `pool_size` can be opened + for this pool at any time. If the connection pool is full, subsequent + connect operations will be queued into a queue equal to this option's + value (the "backlog" queue). * `"socket_type"`: optional, the type of socket to use, one of: `"nginx"`, `"luasocket"`, `cqueues` (default: `"nginx"` if in nginx, `"luasocket"` otherwise) * `"application_name"`: set the name of the connection as displayed in `pg_stat_activity`. (default: `"pgmoon"`) diff --git a/pgmoon/init.lua b/pgmoon/init.lua index 19846bb..f143067 100644 --- a/pgmoon/init.lua +++ b/pgmoon/init.lua @@ -188,7 +188,9 @@ do local opts if self.sock_type == "nginx" then opts = { - pool = self.pool_name or tostring(self.host) .. ":" .. tostring(self.port) .. ":" .. tostring(self.database) .. ":" .. tostring(self.user) + pool = self.pool_name or tostring(self.host) .. ":" .. tostring(self.port) .. ":" .. tostring(self.database) .. ":" .. tostring(self.user), + pool_size = self.pool_size, + backlog = self.backlog } end local ok, err = self.sock:connect(self.host, self.port, opts) @@ -852,6 +854,8 @@ do self.ssl_verify = opts.ssl_verify self.ssl_required = opts.ssl_required self.pool_name = opts.pool + self.pool_size = opts.pool_size + self.backlog = opts.backlog self.luasec_opts = { key = opts.key, cert = opts.cert, diff --git a/pgmoon/init.moon b/pgmoon/init.moon index e8d9faa..a620c6e 100644 --- a/pgmoon/init.moon +++ b/pgmoon/init.moon @@ -169,6 +169,8 @@ class Postgres @ssl_verify = opts.ssl_verify @ssl_required = opts.ssl_required @pool_name = opts.pool + @pool_size = opts.pool_size + @backlog = opts.backlog @luasec_opts = { key: opts.key cert: opts.cert @@ -182,6 +184,8 @@ class Postgres opts = if @sock_type == "nginx" { pool: @pool_name or "#{@host}:#{@port}:#{@database}:#{@user}" + pool_size: @pool_size + backlog: @backlog } ok, err = @sock\connect @host, @port, opts From 2fdc6d1d695a3647e482e0521493791094c3edbc Mon Sep 17 00:00:00 2001 From: leaf corcoran Date: Wed, 13 Oct 2021 12:34:54 -0700 Subject: [PATCH 09/23] more readme tweaks --- README.md | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 4a3d62f..3e2ed31 100644 --- a/README.md +++ b/README.md @@ -105,16 +105,11 @@ of options. The table can have the following keys: * `"ssl"`: enable ssl (default: `false`) * `"ssl_verify"`: verify server certificate (default: `nil`) * `"ssl_required"`: abort the connection if the server does not support SSL connections (default: `nil`) -* `"pool"`: optional name of pool to use when using OpenResty cosocket (defaults to `"#{host}:#{port}:#{database}"`) -* `"pool_size"`: optional size of pool to use when using OpenResty cosocket (default size equal to the value of the `lua_socket_pool_size` directive) -* `"backlog"`: optional size of backlog to use when using OpenResty cosocket. - If specified, this module will limit the total number of opened connections - for this pool. No more connections than `pool_size` can be opened - for this pool at any time. If the connection pool is full, subsequent - connect operations will be queued into a queue equal to this option's - value (the "backlog" queue). * `"socket_type"`: optional, the type of socket to use, one of: `"nginx"`, `"luasocket"`, `cqueues` (default: `"nginx"` if in nginx, `"luasocket"` otherwise) * `"application_name"`: set the name of the connection as displayed in `pg_stat_activity`. (default: `"pgmoon"`) +* `"pool"`: (OpenResty only) **optional** name of pool to use when using OpenResty cosocket (default: `"#{host}:#{port}:#{database}"`) +* `"pool_size"`: (OpenResty only) **optional** Passed directly to OpenResty cosocket connect function, [see docs](https://github.com/openresty/lua-nginx-module#tcpsockconnect) +* `"backlog"`: (OpenResty only) **optional** Passed directly to OpenResty cosocket connect function, [see docs](https://github.com/openresty/lua-nginx-module#tcpsockconnect) Methods on the `Postgres` object returned by `new`: From 0580413b2439ed2461705367abfcffe8bfc8f8c1 Mon Sep 17 00:00:00 2001 From: leaf corcoran Date: Wed, 13 Oct 2021 13:16:34 -0700 Subject: [PATCH 10/23] version bump --- README.md | 31 +++++++++++++++++-------------- pgmoon/init.lua | 2 +- pgmoon/init.moon | 2 +- 3 files changed, 19 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 3e2ed31..fcbb76a 100644 --- a/README.md +++ b/README.md @@ -94,22 +94,25 @@ Functions in table returned by `require("pgmoon")`: ### `new(options={})` -Creates a new `Postgres` object. Does not connect automatically. Takes a table -of options. The table can have the following keys: +Creates a new `Postgres` object from a configuration object. All options are +optinal unless otherwise stated. The newly created object will not +automatically connect, you must call `conect` after creating the object. +Available options: + +* `"database"`: the database name to connect to **required** * `"host"`: the host to connect to (default: `"127.0.0.1"`) * `"port"`: the port to connect to (default: `"5432"`) * `"user"`: the database username to authenticate (default: `"postgres"`) -* `"database"`: the database name to connect to **required** -* `"password"`: password for authentication, optional depending on server configuration +* `"password"`: password for authentication, may be required depending on server configuration * `"ssl"`: enable ssl (default: `false`) * `"ssl_verify"`: verify server certificate (default: `nil`) * `"ssl_required"`: abort the connection if the server does not support SSL connections (default: `nil`) -* `"socket_type"`: optional, the type of socket to use, one of: `"nginx"`, `"luasocket"`, `cqueues` (default: `"nginx"` if in nginx, `"luasocket"` otherwise) +* `"socket_type"`: the type of socket to use, one of: `"nginx"`, `"luasocket"`, `cqueues` (default: `"nginx"` if in nginx, `"luasocket"` otherwise) * `"application_name"`: set the name of the connection as displayed in `pg_stat_activity`. (default: `"pgmoon"`) -* `"pool"`: (OpenResty only) **optional** name of pool to use when using OpenResty cosocket (default: `"#{host}:#{port}:#{database}"`) -* `"pool_size"`: (OpenResty only) **optional** Passed directly to OpenResty cosocket connect function, [see docs](https://github.com/openresty/lua-nginx-module#tcpsockconnect) -* `"backlog"`: (OpenResty only) **optional** Passed directly to OpenResty cosocket connect function, [see docs](https://github.com/openresty/lua-nginx-module#tcpsockconnect) +* `"pool"`: (OpenResty only) name of pool to use when using OpenResty cosocket (default: `"#{host}:#{port}:#{database}"`) +* `"pool_size"`: (OpenResty only) Passed directly to OpenResty cosocket connect function, [see docs](https://github.com/openresty/lua-nginx-module#tcpsockconnect) +* `"backlog"`: (OpenResty only) Passed directly to OpenResty cosocket connect function, [see docs](https://github.com/openresty/lua-nginx-module#tcpsockconnect) Methods on the `Postgres` object returned by `new`: @@ -122,9 +125,8 @@ message. ### postgres:settimeout(time) -Sets the timeout value (in milliseconds) for all socket operations (connect, -write, receive). This function does not have any return values. - +Sets the timeout value (in milliseconds) for all subsequent socket operations +(connect, write, receive). This function does not have any return values. ### success, err = postgres:disconnect() @@ -255,9 +257,9 @@ Returns string representation of current state of `Postgres` object. ## SSL connections pgmoon can establish an SSL connection to a Postgres server. It can also refuse -to connect to it if the server does not support SSL. -Just as pgmoon depends on LuaSocket for usage outside of OpenResty, it depends -on LuaSec for SSL connections in such contexts. +to connect to it if the server does not support SSL. Just as pgmoon depends on +LuaSocket for usage outside of OpenResty, it depends on luaossl/LuaSec for SSL +connections in such contexts. ```lua local pgmoon = require("pgmoon") @@ -400,6 +402,7 @@ Homepage: # Changelog +* 1.13.0 — 2021-10-13 - Add support for scram_sha_256_auth (@murillopaula), 'backlog' and 'pool_size' options while using ngx.socket (@xiaocang), update LuaSec ssl_protocol default options (@jeremymv2), `application_name` option (@mecampbellsoup) * 1.12.0 — 2021-01-06 - Lua pattern compatibility fix, Support for Lua 5.1 through 5.4 (@jprjr). Fix bug where SSL vesrion was not being passed. Default to TLS v1.2 when using LuaSec. Luabitop is no longer automatically installed as a dependency. New test suite. * 1.11.0 — 2020-03-26 - Allow for TLS v1.2 when using LuaSec (Miles Elam) * 1.10.0 — 2019-04-15 - Support luaossl for crypto functions, added better error when missing crypto library diff --git a/pgmoon/init.lua b/pgmoon/init.lua index f143067..5b9b44a 100644 --- a/pgmoon/init.lua +++ b/pgmoon/init.lua @@ -7,7 +7,7 @@ do rshift, lshift, band, bxor = _obj_0.rshift, _obj_0.lshift, _obj_0.band, _obj_0.bxor end local unpack = table.unpack or unpack -local VERSION = "1.12.0" +local VERSION = "1.13.0" local _len _len = function(thing, t) if t == nil then diff --git a/pgmoon/init.moon b/pgmoon/init.moon index a620c6e..86a1580 100644 --- a/pgmoon/init.moon +++ b/pgmoon/init.moon @@ -8,7 +8,7 @@ unpack = table.unpack or unpack -- Protocol documentation: -- https://www.postgresql.org/docs/current/protocol-message-formats.html -VERSION = "1.12.0" +VERSION = "1.13.0" _len = (thing, t=type(thing)) -> switch t From 31b518ed415942fef39161d9b3dab7d3cc49c4fb Mon Sep 17 00:00:00 2001 From: leaf Date: Wed, 13 Oct 2021 15:57:21 -0700 Subject: [PATCH 11/23] Update README.md --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index fcbb76a..fbd057c 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,9 @@ ![test](https://github.com/leafo/pgmoon/workflows/test/badge.svg) -> **Note!** Are you using the latest version of OpenResty? You must update to -> pgmoon 1.12 or above, due to a change in Lua pattern compatibility, any query -> that returns affected number of rows will return the expected value. +> **Note:** Have you updated from an older version of OpenResty? You must update to +> pgmoon 1.12 or above, due to a change in Lua pattern compatibility to avoid incorrect +> results from queries that return affected rows. pgmoon is a PostgreSQL client library written in pure Lua (MoonScript). From 99bf01218262510ea31c651bd3bc30324b496132 Mon Sep 17 00:00:00 2001 From: leaf corcoran Date: Thu, 14 Oct 2021 16:06:11 -0700 Subject: [PATCH 12/23] let encode array generate non-broken syntax for postgres to throw error about, fixes #37 #86 --- README.md | 16 ++++++++++++++++ pgmoon/arrays.lua | 21 +++++++++++++++------ pgmoon/arrays.moon | 18 ++++++++++++------ spec/pgmoon_spec.moon | 1 + 4 files changed, 44 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index fbd057c..2789cee 100644 --- a/README.md +++ b/README.md @@ -324,6 +324,22 @@ local my_array = {1,2,3,4,5} pg:query("insert into some_table (some_arr_col) values(" .. encode_array(my_array) .. ")") ``` +### Empty Arrays + +When trying to encode an empty array an error will be thrown. Postgres requires +a type when using an array. When there are values in the array Postgres can +infer the type, but with no values in the array no type can be inferred. This +is illustrated in the erorr provided by Postgres: + + +``` +postgres=# select ARRAY[]; +ERROR: cannot determine type of empty array +LINE 1: select ARRAY[]; + ^ +HINT: Explicitly cast to the desired type, for example ARRAY[]::integer[]. +``` + ## Handling JSON `json` and `jsonb` types are automatically decoded when they are returned from diff --git a/pgmoon/arrays.lua b/pgmoon/arrays.lua index 15df2e9..882c843 100644 --- a/pgmoon/arrays.lua +++ b/pgmoon/arrays.lua @@ -22,6 +22,11 @@ getmetatable(PostgresArray).__call = function(self, t) return setmetatable(t, self.__base) end local default_escape_literal = nil +local insert, concat +do + local _obj_0 = table + insert, concat = _obj_0.insert, _obj_0.concat +end local encode_array do local append_buffer @@ -29,13 +34,13 @@ do for _index_0 = 1, #values do local item = values[_index_0] if type(item) == "table" and not getmetatable(item) then - table.insert(buffer, "[") + insert(buffer, "[") append_buffer(escape_literal, buffer, item) buffer[#buffer] = "]" - table.insert(buffer, ",") + insert(buffer, ",") else - table.insert(buffer, escape_literal(item)) - table.insert(buffer, ",") + insert(buffer, escape_literal(item)) + insert(buffer, ",") end end return buffer @@ -53,8 +58,12 @@ do local buffer = append_buffer(escape_literal, { "ARRAY[" }, tbl) - buffer[#buffer] = "]" - return table.concat(buffer) + if buffer[#buffer] == "," then + buffer[#buffer] = "]" + else + insert(buffer, "]") + end + return concat(buffer) end end local convert_values diff --git a/pgmoon/arrays.moon b/pgmoon/arrays.moon index 7bb2d5a..100dfe0 100644 --- a/pgmoon/arrays.moon +++ b/pgmoon/arrays.moon @@ -6,18 +6,20 @@ getmetatable(PostgresArray).__call = (t) => default_escape_literal = nil +import insert, concat from table + encode_array = do append_buffer = (escape_literal, buffer, values) -> for item in *values -- plain array if type(item) == "table" and not getmetatable(item) - table.insert buffer, "[" + insert buffer, "[" append_buffer escape_literal, buffer, item buffer[#buffer] = "]" -- strips trailing comma - table.insert buffer, "," + insert buffer, "," else - table.insert buffer, escape_literal item - table.insert buffer, "," + insert buffer, escape_literal item + insert buffer, "," buffer @@ -33,8 +35,12 @@ encode_array = do buffer = append_buffer escape_literal, {"ARRAY["}, tbl - buffer[#buffer] = "]" -- strips trailing comma - table.concat buffer + + if buffer[#buffer] == "," + buffer[#buffer] = "]" + else + insert buffer, "]" + concat buffer convert_values = (array, fn) -> for idx, v in ipairs array diff --git a/spec/pgmoon_spec.moon b/spec/pgmoon_spec.moon index 8eeb748..4ce57d3 100644 --- a/spec/pgmoon_spec.moon +++ b/spec/pgmoon_spec.moon @@ -580,6 +580,7 @@ describe "pgmoon with server", -> assert PostgresArray.__base == getmetatable array it "encodes array value", -> + assert.same "ARRAY[]", encode_array {} assert.same "ARRAY[1,2,3]", encode_array {1,2,3} assert.same "ARRAY['hello','world']", encode_array {"hello", "world"} assert.same "ARRAY[[4,5],[6,7]]", encode_array {{4,5}, {6,7}} From f3e5db7fd74ae9af8a8889a51caf4369e2042c80 Mon Sep 17 00:00:00 2001 From: leaf corcoran Date: Fri, 15 Oct 2021 11:20:17 -0700 Subject: [PATCH 13/23] connect will create a socket if one isn't there fixes #44 --- pgmoon/init.lua | 3 +++ pgmoon/init.moon | 3 +++ 2 files changed, 6 insertions(+) diff --git a/pgmoon/init.lua b/pgmoon/init.lua index 5b9b44a..7684b0f 100644 --- a/pgmoon/init.lua +++ b/pgmoon/init.lua @@ -185,6 +185,9 @@ do return self:set_type_oid(tonumber(res.oid), "hstore") end, connect = function(self) + if not (self.sock) then + self.sock = socket.new(self.sock_type) + end local opts if self.sock_type == "nginx" then opts = { diff --git a/pgmoon/init.moon b/pgmoon/init.moon index 86a1580..d322103 100644 --- a/pgmoon/init.moon +++ b/pgmoon/init.moon @@ -181,6 +181,9 @@ class Postgres @application_name = opts.application_name or "pgmoon" connect: => + unless @sock + @sock = socket.new @sock_type + opts = if @sock_type == "nginx" { pool: @pool_name or "#{@host}:#{@port}:#{@database}:#{@user}" From f79bc299cae26b2ba1cfb3fe4cc131002a90d9d7 Mon Sep 17 00:00:00 2001 From: leaf corcoran Date: Fri, 15 Oct 2021 13:06:45 -0700 Subject: [PATCH 14/23] refactor how configuration is handled --- pgmoon/cqueues.lua | 4 +- pgmoon/cqueues.moon | 5 +- pgmoon/init.lua | 117 ++++++++++++++++++++++-------------------- pgmoon/init.moon | 122 ++++++++++++++++++++++++++------------------ pgmoon/socket.lua | 12 ++--- pgmoon/socket.moon | 13 ++--- 6 files changed, 151 insertions(+), 122 deletions(-) diff --git a/pgmoon/cqueues.lua b/pgmoon/cqueues.lua index a5c20bb..7f26b5a 100644 --- a/pgmoon/cqueues.lua +++ b/pgmoon/cqueues.lua @@ -21,8 +21,8 @@ do end return true end, - sslhandshake = function(self) - return self.sock:starttls() + starttls = function(self, ...) + return self.sock:starttls(...) end, getpeercertificate = function(self) local ssl = assert(self.sock:checktls()) diff --git a/pgmoon/cqueues.moon b/pgmoon/cqueues.moon index 62dc8c3..977461a 100644 --- a/pgmoon/cqueues.moon +++ b/pgmoon/cqueues.moon @@ -21,8 +21,9 @@ class CqueuesSocket true - sslhandshake: => - @sock\starttls! + -- args: [context][, timeout] + starttls: (...) => + @sock\starttls ... -- returns openssl.x509 object getpeercertificate: => diff --git a/pgmoon/init.lua b/pgmoon/init.lua index 7684b0f..4611b09 100644 --- a/pgmoon/init.lua +++ b/pgmoon/init.lua @@ -126,10 +126,13 @@ do "NULL" }, PG_TYPES = PG_TYPES, - user = "postgres", - host = "127.0.0.1", - port = "5432", - ssl = false, + default_config = { + application_name = "pgmoon", + user = "postgres", + host = "127.0.0.1", + port = "5432", + ssl = false + }, type_deserializers = { json = function(self, val, name) local decode_json @@ -184,24 +187,41 @@ do assert(res, "hstore oid not found") return self:set_type_oid(tonumber(res.oid), "hstore") end, + create_cqueues_openssl_context = function(self) + if self.config.ssl_verify == nil then + return + end + local context = require("openssl.ssl.context").new() + return context + end, + create_luasec_opts = function(self) + return { + key = self.config.key, + certificate = self.config.cert, + cafile = self.config.cafile, + protocol = self.config.ssl_version, + verify = self.config.ssl_verify and "peer" or "none" + } + end, connect = function(self) if not (self.sock) then self.sock = socket.new(self.sock_type) end - local opts - if self.sock_type == "nginx" then - opts = { - pool = self.pool_name or tostring(self.host) .. ":" .. tostring(self.port) .. ":" .. tostring(self.database) .. ":" .. tostring(self.user), - pool_size = self.pool_size, - backlog = self.backlog + local connect_opts + local _exp_0 = self.sock_type + if "nginx" == _exp_0 then + connect_opts = { + pool = self.config.pool_name or tostring(self.config.host) .. ":" .. tostring(self.config.port) .. ":" .. tostring(self.config.database) .. ":" .. tostring(self.config.user), + pool_size = self.config.pool_size, + backlog = self.config.backlog } end - local ok, err = self.sock:connect(self.host, self.port, opts) + local ok, err = self.sock:connect(self.config.host, self.config.port, connect_opts) if not (ok) then return nil, err end if self.sock:getreusedtimes() == 0 then - if self.ssl then + if self.config.ssl then local success success, err = self:send_ssl_message() if not (success) then @@ -264,15 +284,15 @@ do end end, cleartext_auth = function(self, msg) - assert(self.password, "missing password, required for connect") + assert(self.config.password, "missing password, required for connect") self:send_message(MSG_TYPE.password, { - self.password, + self.config.password, NULL }) return self:check_auth() end, scram_sha_256_auth = function(self, msg) - assert(self.password, "missing password, required for connect") + assert(self.config.password, "missing password, required for connect") local openssl_rand = require("openssl.rand") local rand_bytes = assert(openssl_rand.bytes(18)) local encode_base64 @@ -388,7 +408,7 @@ do local _obj_0 = require("pgmoon.crypto") kdf_derive_sha256, hmac_sha256, digest_sha256 = _obj_0.kdf_derive_sha256, _obj_0.hmac_sha256, _obj_0.digest_sha256 end - local salted_password, err = kdf_derive_sha256(self.password, salt, tonumber(i)) + local salted_password, err = kdf_derive_sha256(self.config.password, salt, tonumber(i)) if not (salted_password) then return nil, err end @@ -441,10 +461,10 @@ do local md5 md5 = require("pgmoon.crypto").md5 local salt = msg:sub(5, 8) - assert(self.password, "missing password, required for connect") + assert(self.config.password, "missing password, required for connect") self:send_message(MSG_TYPE.password, { "md5", - md5(md5(self.password .. self.user) .. salt), + md5(md5(self.config.password .. self.config.user) .. salt), NULL }) return self:check_auth() @@ -719,21 +739,21 @@ do return t, msg end, send_startup_message = function(self) - assert(self.user, "missing user for connect") - assert(self.database, "missing database for connect") + assert(self.config.user, "missing user for connect") + assert(self.config.database, "missing database for connect") local data = { self:encode_int(196608), "user", NULL, - self.user, + self.config.user, NULL, "database", NULL, - self.database, + self.config.database, NULL, "application_name", NULL, - self.application_name, + self.config.application_name, NULL, NULL } @@ -756,12 +776,17 @@ do return nil, err end if t == MSG_TYPE.status then - if self.sock_type == "nginx" then - return self.sock:sslhandshake(false, nil, self.ssl_verify) + local _exp_0 = self.sock_type + if "nginx" == _exp_0 then + return self.sock:sslhandshake(false, nil, self.config.ssl_verify) + elseif "luasocket" == _exp_0 then + return self.sock:sslhandshake(self.config.luasec_opts or self:create_luasec_opts()) + elseif "cqueues" == _exp_0 then + return self.sock:starttls(self.config.cqueues_openssl_context or self:create_cqueues_openssl_context()) else - return self.sock:sslhandshake(self.ssl_verify, self.luasec_opts) + return error("don't know how to do ssl handshake for socket type: " .. tostring(self.sock_type)) end - elseif t == MSG_TYPE.error or self.ssl_required then + elseif t == MSG_TYPE.error or self.config.ssl_required then self:disconnect() return nil, "the server does not support SSL connections" else @@ -845,34 +870,16 @@ do } _base_0.__index = _base_0 _class_0 = setmetatable({ - __init = function(self, opts) - self.sock, self.sock_type = socket.new(opts and opts.socket_type) - if opts then - self.user = opts.user - self.host = opts.host - self.database = opts.database - self.port = opts.port - self.password = opts.password - self.ssl = opts.ssl - self.ssl_verify = opts.ssl_verify - self.ssl_required = opts.ssl_required - self.pool_name = opts.pool - self.pool_size = opts.pool_size - self.backlog = opts.backlog - self.luasec_opts = { - key = opts.key, - cert = opts.cert, - cafile = opts.cafile, - ssl_version = opts.ssl_version or "any", - options = { - "all", - "no_sslv2", - "no_sslv3", - "no_tlsv1" - } - } - self.application_name = opts.application_name or "pgmoon" - end + __init = function(self, config) + if config == nil then + config = { } + end + self.config = config + assert(not getmetatable(self.config), "options argument must not have a metatable to allow default configuration to be inherited") + setmetatable(self.config, { + __index = self.default_config + }) + self.sock, self.sock_type = socket.new(self.config.socket_type) end, __base = _base_0, __name = "Postgres" diff --git a/pgmoon/init.moon b/pgmoon/init.moon index d322103..20d4baa 100644 --- a/pgmoon/init.moon +++ b/pgmoon/init.moon @@ -109,10 +109,13 @@ class Postgres NULL: {"NULL"} :PG_TYPES - user: "postgres" - host: "127.0.0.1" - port: "5432" - ssl: false + default_config: { + application_name: "pgmoon" + user: "postgres" + host: "127.0.0.1" + port: "5432" + ssl: false + } -- custom types supplementing PG_TYPES type_deserializers: { @@ -156,46 +159,58 @@ class Postgres assert res, "hstore oid not found" @set_type_oid tonumber(res.oid), "hstore" - new: (opts) => - @sock, @sock_type = socket.new opts and opts.socket_type - - if opts - @user = opts.user - @host = opts.host - @database = opts.database - @port = opts.port - @password = opts.password - @ssl = opts.ssl - @ssl_verify = opts.ssl_verify - @ssl_required = opts.ssl_required - @pool_name = opts.pool - @pool_size = opts.pool_size - @backlog = opts.backlog - @luasec_opts = { - key: opts.key - cert: opts.cert - cafile: opts.cafile - ssl_version: opts.ssl_version or "any" - options: { "all", "no_sslv2", "no_sslv3", "no_tlsv1" } - } - @application_name = opts.application_name or "pgmoon" + -- config={} + -- host: server hostname + -- port: server port + -- user: the username to authenticate with + -- password: the username to authenticate with + -- database: database to connect to + -- application_name: name assigned to connection to server + -- socket_type: type of socket to use (nginx, luasocket, cqueues) + -- ssl: enable ssl connections + -- ssl_verify: enable ssl connections + -- cqueues_openssl_context: manually created openssl.ssl.context for cqueues sockets + -- luasec_opts: manually created options for LuaSocket ssl connections + new: (@config={}) => + assert not getmetatable(@config), + "options argument must not have a metatable to allow default configuration to be inherited" + + setmetatable @config, __index: @default_config + + @sock, @sock_type = socket.new @config.socket_type + + create_cqueues_openssl_context: => + return if @config.ssl_verify == nil + + context = require("openssl.ssl.context").new! + context + + create_luasec_opts: => + { + key: @config.key + certificate: @config.cert + cafile: @config.cafile + protocol: @config.ssl_version + verify: @config.ssl_verify and "peer" or "none" + } connect: => unless @sock @sock = socket.new @sock_type - opts = if @sock_type == "nginx" - { - pool: @pool_name or "#{@host}:#{@port}:#{@database}:#{@user}" - pool_size: @pool_size - backlog: @backlog - } + connect_opts = switch @sock_type + when "nginx" + { + pool: @config.pool_name or "#{@config.host}:#{@config.port}:#{@config.database}:#{@config.user}" + pool_size: @config.pool_size + backlog: @config.backlog + } - ok, err = @sock\connect @host, @port, opts + ok, err = @sock\connect @config.host, @config.port, connect_opts return nil, err unless ok if @sock\getreusedtimes! == 0 - if @ssl + if @config.ssl success, err = @send_ssl_message! return nil, err unless success @@ -249,10 +264,10 @@ class Postgres error "don't know how to auth: #{auth_type}" cleartext_auth: (msg) => - assert @password, "missing password, required for connect" + assert @config.password, "missing password, required for connect" @send_message MSG_TYPE.password, { - @password + @config.password NULL } @@ -260,7 +275,7 @@ class Postgres -- https://www.postgresql.org/docs/current/sasl-authentication.html#SASL-SCRAM-SHA-256 scram_sha_256_auth: (msg) => - assert @password, "missing password, required for connect" + assert @config.password, "missing password, required for connect" openssl_rand = require "openssl.rand" @@ -378,7 +393,7 @@ class Postgres return nil, "the iteration-count sent by the server is less than 4096" import kdf_derive_sha256, hmac_sha256, digest_sha256 from require "pgmoon.crypto" - salted_password, err = kdf_derive_sha256 @password, salt, tonumber i + salted_password, err = kdf_derive_sha256 @config.password, salt, tonumber i unless salted_password return nil, err @@ -438,11 +453,11 @@ class Postgres md5_auth: (msg) => import md5 from require "pgmoon.crypto" salt = msg\sub 5, 8 - assert @password, "missing password, required for connect" + assert @config.password, "missing password, required for connect" @send_message MSG_TYPE.password, { "md5" - md5 md5(@password .. @user) .. salt + md5 md5(@config.password .. @config.user) .. salt NULL } @@ -690,17 +705,17 @@ class Postgres t, msg send_startup_message: => - assert @user, "missing user for connect" - assert @database, "missing database for connect" + assert @config.user, "missing user for connect" + assert @config.database, "missing database for connect" data = { @encode_int 196608 "user", NULL - @user, NULL + @config.user, NULL "database", NULL - @database, NULL + @config.database, NULL "application_name", NULL - @application_name, NULL + @config.application_name, NULL NULL } @@ -720,11 +735,16 @@ class Postgres return nil, err unless t if t == MSG_TYPE.status - if @sock_type == "nginx" - @sock\sslhandshake false, nil, @ssl_verify - else - @sock\sslhandshake @ssl_verify, @luasec_opts - elseif t == MSG_TYPE.error or @ssl_required + switch @sock_type + when "nginx" + @sock\sslhandshake false, nil, @config.ssl_verify + when "luasocket" + @sock\sslhandshake @config.luasec_opts or @create_luasec_opts! + when "cqueues" + @sock\starttls @config.cqueues_openssl_context or @create_cqueues_openssl_context! + else + error "don't know how to do ssl handshake for socket type: #{@sock_type}" + elseif t == MSG_TYPE.error or @config.ssl_required @disconnect! nil, "the server does not support SSL connections" else diff --git a/pgmoon/socket.lua b/pgmoon/socket.lua index d823baa..795b417 100644 --- a/pgmoon/socket.lua +++ b/pgmoon/socket.lua @@ -42,18 +42,15 @@ do end return self.sock:settimeout(t) end, - sslhandshake = function(self, verify, opts) + sslhandshake = function(self, opts) if opts == nil then opts = { } end local ssl = require("ssl") local params = { mode = "client", - protocol = opts.ssl_version or "any", - key = opts.key, - certificate = opts.cert, - cafile = opts.cafile, - verify = verify and "peer" or "none", + protocol = "any", + verify = "none", options = { "all", "no_sslv2", @@ -61,6 +58,9 @@ do "no_tlsv1" } } + for k, v in pairs(opts) do + params[k] = v + end local sec_sock, err = ssl.wrap(self.sock, params) if not (sec_sock) then return false, err diff --git a/pgmoon/socket.moon b/pgmoon/socket.moon index e206f61..2b9ca44 100644 --- a/pgmoon/socket.moon +++ b/pgmoon/socket.moon @@ -34,18 +34,19 @@ luasocket = do if t t = t/1000 @sock\settimeout t - sslhandshake: (verify, opts={}) => + + sslhandshake: (opts={}) => ssl = require "ssl" params = { mode: "client" - protocol: opts.ssl_version or "any" - key: opts.key - certificate: opts.cert - cafile: opts.cafile - verify: verify and "peer" or "none" + protocol: "any" + verify: "none" options: { "all", "no_sslv2", "no_sslv3", "no_tlsv1" } } + for k,v in pairs opts + params[k] = v + sec_sock, err = ssl.wrap @sock, params return false, err unless sec_sock From a6be346e96352f235df0d081e7f5aff284c1c1d2 Mon Sep 17 00:00:00 2001 From: leaf corcoran Date: Fri, 15 Oct 2021 13:29:43 -0700 Subject: [PATCH 15/23] misc updates to readme --- README.md | 91 +++++++++++++++++++++++++++++++------------------------ 1 file changed, 52 insertions(+), 39 deletions(-) diff --git a/README.md b/README.md index 2789cee..1342eb1 100644 --- a/README.md +++ b/README.md @@ -6,17 +6,16 @@ > pgmoon 1.12 or above, due to a change in Lua pattern compatibility to avoid incorrect > results from queries that return affected rows. -pgmoon is a PostgreSQL client library written in pure Lua (MoonScript). +**pgmoon** is a PostgreSQL client library written in pure Lua (MoonScript). -pgmoon was originally designed for use in [OpenResty][5] to take advantage of -the [cosocket api][4] to provide asynchronous queries but it also works in the -regular Lua environment as well using [LuaSocket][1] (and optionally -[luaossl](https://luarocks.org/modules/daurnimator/luaossl) or [LuaCrypto][2] for MD5 authentication and [LuaSec][6] for SSL connections). -pgmoon can also use [cqueues][]' socket when passed `"cqueues"` as the socket -type when instantiating. +**pgmoon** was originally designed for use in [OpenResty][] to take advantage +of the [cosocket +api](https://github.com/openresty/lua-nginx-module#ngxsockettcp) to provide +asynchronous queries but it also works in the regular any Lua environment where +[LuaSocket][] or [cqueues][] is available. It's a perfect candidate for running your queries both inside OpenResty's -environment and on the command line (eg. tests) in web frameworks like [Lapis][3]. +environment and on the command line (eg. tests) in web frameworks like [Lapis][]. ## Install @@ -24,19 +23,33 @@ environment and on the command line (eg. tests) in web frameworks like [Lapis][3 $ luarocks install pgmoon ``` +
+ +Using [OpenResty's OPM](https://opm.openresty.org/): + +```bash +$ opm get leafo/pgmoon +``` + +
+ + ### Dependencies pgmoon supports a wide range of environments and libraries, so it may be necessary to install additional dependencies depending on how you intend to communicate with the database: -> Tip: If you're using OpenResty then no additional dependencies are needed +> **Tip:** If you're using OpenResty then no additional dependencies are needed +> (generally, a crypto library may be necessary for some authentication +> methods) -Some socket library **is required**, depending on the environment you can chose one: +A socket implementation **is required** to use pgmoon, depending on the +environment you can chose one: -* [OpenResty](https://openresty.org/en/) — The built in socket is used, so additional dependencies necessary -* [LuaSocket](http://w3.impa.br/~diego/software/luasocket/) — Suitable for command line database access, has the highest platform compatibility `luarocks install luasocket` -* [cqueues](https://github.com/wahern/cqueues) — `luarocks install cqueues` +* [OpenResty][] — The built in socket is used, no additional dependencies necessary +* [LuaSocket][] — `luarocks install luasocket` +* [cqueues][] — `luarocks install cqueues` If you're on PUC Lua 5.1 or 5.2 then you will need a bit libray (not needed for LuaJIT): @@ -50,19 +63,21 @@ If you want to use JSON types you will need lua-cjson $ luarocks install lua-cjson ``` -If you want to use SSL connections with LuaSocket then you will need LuaSec: -(OpenResty and cqueues come with their own SSL implementations) +SSL connections may require an additional dependency: +* OpenResty — No additional dependencies required +* LuaSocket — `luarocks install luasec` +* cqueues — `luarocks install luaossl` + +Password authentication may require a crypto library, [luaossl][]. ```bash -$ luarocks install luasec +$ luarocks install luaossl ``` -Password authentication or SSL connections will require a crypto library: +> **Note:** [LuaCrypto][] can be used as a fallback, but the library is abandoned and not recommended for use -* [luaossl](https://github.com/wahern/luaossl) — Recommended `luarocks install luaossl` -* [OpenResty](https://openresty.org/en/) — Built in function will be used if possible, otherwise `luaossl` is necessary -* [luacrypto](https://github.com/starius/luacrypto) — Deprecated library, not recommended, but will be used with LuaSocket if available +> **Note:** Use within [OpenResty][] will prioritize an build in functions if possible ## Example @@ -81,8 +96,8 @@ local res = assert(pg:query("select * from users where username = " .. pg:escape_literal("leafo"))) ``` -If you are using OpenResty you should relinquish the socket after you are done -with it so it can be reused in future requests: +If you are using OpenResty you can relinquish the socket to the connection pool +after you are done with it so it can be reused in future requests: ```lua pg:keepalive() @@ -94,8 +109,8 @@ Functions in table returned by `require("pgmoon")`: ### `new(options={})` -Creates a new `Postgres` object from a configuration object. All options are -optinal unless otherwise stated. The newly created object will not +Creates a new `Postgres` object from a configuration object. All fields are +optional unless otherwise stated. The newly created object will not automatically connect, you must call `conect` after creating the object. Available options: @@ -257,7 +272,7 @@ Returns string representation of current state of `Postgres` object. ## SSL connections pgmoon can establish an SSL connection to a Postgres server. It can also refuse -to connect to it if the server does not support SSL. Just as pgmoon depends on +to connect to it if the server does not support SSL. Just as pgmoon depends on LuaSocket for usage outside of OpenResty, it depends on luaossl/LuaSec for SSL connections in such contexts. @@ -277,13 +292,13 @@ local pg = pgmoon.new({ assert(pg:connect()) ``` -> Note: In Postgres 12 and above, the minium SSL version accepted by client -> connections is 1.2. When using LuaSec to connect to an SSL server, if you -> don't specify an `ssl_version` then `tlsv1_2` is used. +> **Note:** In Postgres 12 and above, the minium SSL version accepted by client +> connections is 1.2. When using LuaSocket + LuaSec to connect to an SSL +> server, if you don't specify an `ssl_version` then `tlsv1_2` is used. -In OpenResty, make sure to configure the [lua_ssl_trusted_certificate][7] -directive if you wish to verify the server certificate, as the LuaSec-only -options become irrelevant in that case. +In OpenResty, make sure to configure the +[lua_ssl_trusted_certificate](https://github.com/openresty/lua-nginx-module#lua_ssl_trusted_certificate) +directive if you wish to verify the server certificate. ## Authentication types @@ -456,12 +471,10 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - - [1]: http://w3.impa.br/~diego/software/luasocket/ - [2]: http://mkottman.github.io/luacrypto/ - [3]: http://leafo.net/lapis - [4]: http://wiki.nginx.org/HttpLuaModule#ngx.socket.tcp - [5]: http://openresty.org/ - [6]: https://github.com/brunoos/luasec - [7]: https://github.com/openresty/lua-nginx-module#lua_ssl_trusted_certificate + [luaossl]: https://github.com/wahern/luaossl + [LuaCrypto]: https://luarocks.org/modules/starius/luacrypto + [LuaSec]: https://github.com/brunoos/luasec + [Lapis]: http://leafo.net/lapis + [OpenResty]: https://openresty.org/ + [LuaSocket]: http://w3.impa.br/~diego/software/luasocket/ [cqueues]: http://25thandclement.com/~william/projects/cqueues.html From 6fe34288783a3f81d3ed7098337e6eef81c4b45f Mon Sep 17 00:00:00 2001 From: leaf corcoran Date: Fri, 15 Oct 2021 13:31:17 -0700 Subject: [PATCH 16/23] tweak readme formatting --- README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 1342eb1..de6312a 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,11 @@ $ luarocks install pgmoon
-Using [OpenResty's OPM](https://opm.openresty.org/): + + +Using [OpenResty's OPM](https://opm.openresty.org/): + + ```bash $ opm get leafo/pgmoon From c6ac50f7ca148caa9b4e90e9715b9211147edb18 Mon Sep 17 00:00:00 2001 From: leaf corcoran Date: Fri, 15 Oct 2021 13:32:29 -0700 Subject: [PATCH 17/23] more readme tweaks --- README.md | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index de6312a..3d51b3f 100644 --- a/README.md +++ b/README.md @@ -24,12 +24,7 @@ $ luarocks install pgmoon ```
- - - -Using [OpenResty's OPM](https://opm.openresty.org/): - - +Using OpenResty's OPM ```bash $ opm get leafo/pgmoon @@ -81,7 +76,7 @@ $ luarocks install luaossl > **Note:** [LuaCrypto][] can be used as a fallback, but the library is abandoned and not recommended for use -> **Note:** Use within [OpenResty][] will prioritize an build in functions if possible +> **Note:** Use within [OpenResty][] will prioritize built in functions if possible ## Example From 899645c03aa5f99fb212f8f8e0001911e16c233b Mon Sep 17 00:00:00 2001 From: leaf corcoran Date: Fri, 15 Oct 2021 13:44:16 -0700 Subject: [PATCH 18/23] add support for configuring a cqueues openssl context, fixes #70 --- README.md | 2 ++ pgmoon/init.lua | 45 ++++++++++++++++++++++++++++---------------- pgmoon/init.moon | 49 ++++++++++++++++++++++++++++++++---------------- 3 files changed, 64 insertions(+), 32 deletions(-) diff --git a/README.md b/README.md index 3d51b3f..d1d6e4f 100644 --- a/README.md +++ b/README.md @@ -127,6 +127,8 @@ Available options: * `"pool"`: (OpenResty only) name of pool to use when using OpenResty cosocket (default: `"#{host}:#{port}:#{database}"`) * `"pool_size"`: (OpenResty only) Passed directly to OpenResty cosocket connect function, [see docs](https://github.com/openresty/lua-nginx-module#tcpsockconnect) * `"backlog"`: (OpenResty only) Passed directly to OpenResty cosocket connect function, [see docs](https://github.com/openresty/lua-nginx-module#tcpsockconnect) +* `"cqueues_openssl_context"`: Manually created `opensssl.ssl.context` to use when created cqueues SSL connections +* `"luasec_opts"`: Manually created options object to use when using LuaSec SSL connections Methods on the `Postgres` object returned by `new`: diff --git a/pgmoon/init.lua b/pgmoon/init.lua index 4611b09..7b9fab0 100644 --- a/pgmoon/init.lua +++ b/pgmoon/init.lua @@ -187,22 +187,6 @@ do assert(res, "hstore oid not found") return self:set_type_oid(tonumber(res.oid), "hstore") end, - create_cqueues_openssl_context = function(self) - if self.config.ssl_verify == nil then - return - end - local context = require("openssl.ssl.context").new() - return context - end, - create_luasec_opts = function(self) - return { - key = self.config.key, - certificate = self.config.cert, - cafile = self.config.cafile, - protocol = self.config.ssl_version, - verify = self.config.ssl_verify and "peer" or "none" - } - end, connect = function(self) if not (self.sock) then self.sock = socket.new(self.sock_type) @@ -257,6 +241,35 @@ do self.sock = nil return sock:setkeepalive(...) end, + create_cqueues_openssl_context = function(self) + if not (self.config.ssl_verify ~= nil or self.config.cert or self.config.key or self.config.ssl_version) then + return + end + local ssl_context = require("openssl.ssl.context") + local out = ssl_context.new(self.config.ssl_version) + if self.config.ssl_verify == true then + out:setVerify(ssl_context.VERIFY_PEER) + end + if self.config.ssl_verify == false then + out:setVerify(ssl_context.VERIFY_NONE) + end + if self.config.cert then + out:setCertificate(self.config.cert) + end + if self.config.key then + out:setPrivateKey(self.config.key) + end + return out + end, + create_luasec_opts = function(self) + return { + key = self.config.key, + certificate = self.config.cert, + cafile = self.config.cafile, + protocol = self.config.ssl_version, + verify = self.config.ssl_verify and "peer" or "none" + } + end, auth = function(self) local t, msg = self:receive_message() if not (t) then diff --git a/pgmoon/init.moon b/pgmoon/init.moon index 20d4baa..75ed1dd 100644 --- a/pgmoon/init.moon +++ b/pgmoon/init.moon @@ -168,7 +168,7 @@ class Postgres -- application_name: name assigned to connection to server -- socket_type: type of socket to use (nginx, luasocket, cqueues) -- ssl: enable ssl connections - -- ssl_verify: enable ssl connections + -- ssl_verify: verify the certificate -- cqueues_openssl_context: manually created openssl.ssl.context for cqueues sockets -- luasec_opts: manually created options for LuaSocket ssl connections new: (@config={}) => @@ -179,21 +179,6 @@ class Postgres @sock, @sock_type = socket.new @config.socket_type - create_cqueues_openssl_context: => - return if @config.ssl_verify == nil - - context = require("openssl.ssl.context").new! - context - - create_luasec_opts: => - { - key: @config.key - certificate: @config.cert - cafile: @config.cafile - protocol: @config.ssl_version - verify: @config.ssl_verify and "peer" or "none" - } - connect: => unless @sock @sock = socket.new @sock_type @@ -238,6 +223,38 @@ class Postgres @sock = nil sock\setkeepalive ... + -- see: http://25thandclement.com/~william/projects/luaossl.pdf + create_cqueues_openssl_context: => + return unless @config.ssl_verify != nil or @config.cert or @config.key or @config.ssl_version + + ssl_context = require("openssl.ssl.context") + + out = ssl_context.new @config.ssl_version + + if @config.ssl_verify == true + out\setVerify ssl_context.VERIFY_PEER + + if @config.ssl_verify == false + out\setVerify ssl_context.VERIFY_NONE + + if @config.cert + out\setCertificate @config.cert + + if @config.key + out\setPrivateKey @config.key + + out + + create_luasec_opts: => + { + key: @config.key + certificate: @config.cert + cafile: @config.cafile + protocol: @config.ssl_version + verify: @config.ssl_verify and "peer" or "none" + } + + auth: => t, msg = @receive_message! return nil, msg unless t From 7869f66f67ab08a64cd484573dee7b8b145c318f Mon Sep 17 00:00:00 2001 From: leaf corcoran Date: Fri, 15 Oct 2021 13:51:53 -0700 Subject: [PATCH 19/23] add spec for cqueues ssl connection with context options --- spec/pgmoon_ssl_spec.moon | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/spec/pgmoon_ssl_spec.moon b/spec/pgmoon_ssl_spec.moon index c29c463..d3ce227 100644 --- a/spec/pgmoon_ssl_spec.moon +++ b/spec/pgmoon_ssl_spec.moon @@ -79,7 +79,6 @@ describe "pgmoon with server", -> assert.same 'TLSv1.3', res[1].version pg\disconnect! - it "connects with ssl using cqueues", -> pg = Postgres { database: DB @@ -93,5 +92,27 @@ describe "pgmoon with server", -> assert pg\connect! assert pg\query "select * from information_schema.tables" + + pg\disconnect! + + + it "connects with ssl using cqueues with context options", -> + pg = Postgres { + database: DB + port: PORT + user: USER + password: PASSWORD + host: HOST + ssl: true + socket_type: "cqueues" + ssl_version: "TLSv1_2" + } + + assert pg\connect! + assert pg\query "select * from information_schema.tables" + + res = assert pg\query [[SELECT version FROM pg_stat_ssl WHERE pid=pg_backend_pid()]] + assert.same 'TLSv1.2', res[1].version + pg\disconnect! From 895b695db2b7b9b4270dd911aae8612636bc8892 Mon Sep 17 00:00:00 2001 From: leaf corcoran Date: Tue, 2 Nov 2021 14:13:35 -0700 Subject: [PATCH 20/23] remove tupfile --- Tupfile | 1 - Tuprules.tup | 1 - 2 files changed, 2 deletions(-) delete mode 100644 Tupfile delete mode 100644 Tuprules.tup diff --git a/Tupfile b/Tupfile deleted file mode 100644 index f0fe651..0000000 --- a/Tupfile +++ /dev/null @@ -1 +0,0 @@ -include_rules diff --git a/Tuprules.tup b/Tuprules.tup deleted file mode 100644 index 5fa92eb..0000000 --- a/Tuprules.tup +++ /dev/null @@ -1 +0,0 @@ -: foreach *.moon |> moonc %f |> %B.lua From d84c082426048f52f13ce047b0fcb885070d0794 Mon Sep 17 00:00:00 2001 From: leaf corcoran Date: Tue, 2 Nov 2021 14:14:13 -0700 Subject: [PATCH 21/23] change how config inheriting works --- pgmoon/init.lua | 22 ++++++++++++++-------- pgmoon/init.moon | 14 +++++++++----- 2 files changed, 23 insertions(+), 13 deletions(-) diff --git a/pgmoon/init.lua b/pgmoon/init.lua index 7b9fab0..04da2c5 100644 --- a/pgmoon/init.lua +++ b/pgmoon/init.lua @@ -883,14 +883,20 @@ do } _base_0.__index = _base_0 _class_0 = setmetatable({ - __init = function(self, config) - if config == nil then - config = { } - end - self.config = config - assert(not getmetatable(self.config), "options argument must not have a metatable to allow default configuration to be inherited") - setmetatable(self.config, { - __index = self.default_config + __init = function(self, _config) + if _config == nil then + _config = { } + end + self._config = _config + self.config = setmetatable({ }, { + __index = function(t, key) + local value = self._config[key] + if value == nil then + return self.default_config[key] + else + return value + end + end }) self.sock, self.sock_type = socket.new(self.config.socket_type) end, diff --git a/pgmoon/init.moon b/pgmoon/init.moon index 75ed1dd..cea6660 100644 --- a/pgmoon/init.moon +++ b/pgmoon/init.moon @@ -171,11 +171,15 @@ class Postgres -- ssl_verify: verify the certificate -- cqueues_openssl_context: manually created openssl.ssl.context for cqueues sockets -- luasec_opts: manually created options for LuaSocket ssl connections - new: (@config={}) => - assert not getmetatable(@config), - "options argument must not have a metatable to allow default configuration to be inherited" - - setmetatable @config, __index: @default_config + new: (@_config={}) => + @config = setmetatable {}, { + __index: (t, key) -> + value = @_config[key] + if value == nil + @default_config[key] + else + value + } @sock, @sock_type = socket.new @config.socket_type From da82db087b53450788204321d59afa44e3539a7a Mon Sep 17 00:00:00 2001 From: Kristofer Christakos Date: Mon, 1 Feb 2021 09:51:46 -0500 Subject: [PATCH 22/23] change the luasocket feature to haproxy sockets --- pgmoon/socket.lua | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/pgmoon/socket.lua b/pgmoon/socket.lua index 795b417..43db8f5 100644 --- a/pgmoon/socket.lua +++ b/pgmoon/socket.lua @@ -26,8 +26,7 @@ do } luasocket = { tcp = function(...) - local socket = require("socket") - local sock = socket.tcp(...) + local sock = core.tcp(...) local proxy = setmetatable({ sock = sock, send = function(self, ...) @@ -89,14 +88,14 @@ return { if ngx and ngx.get_phase() ~= "init" then socket_type = "nginx" else - socket_type = "luasocket" + socket_type = "haproxy" end end local socket local _exp_0 = socket_type if "nginx" == _exp_0 then socket = ngx.socket.tcp() - elseif "luasocket" == _exp_0 then + elseif "haproxy" == _exp_0 then socket = luasocket.tcp() elseif "cqueues" == _exp_0 then socket = require("pgmoon.cqueues").CqueuesSocket() From 233516c40f8da43465a70f320bb2c5e0c5767ec4 Mon Sep 17 00:00:00 2001 From: Kristofer Christakos Date: Mon, 1 Feb 2021 11:17:21 -0500 Subject: [PATCH 23/23] update rockspec source url --- pgmoon-dev-1.rockspec | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pgmoon-dev-1.rockspec b/pgmoon-dev-1.rockspec index f672764..f6a1aea 100644 --- a/pgmoon-dev-1.rockspec +++ b/pgmoon-dev-1.rockspec @@ -2,7 +2,8 @@ package = "pgmoon" version = "dev-1" source = { - url = "git://github.com/leafo/pgmoon.git" + url = "git://github.com/kriscode1/pgmoon.git", + tag = "luasocket-to-haproxy" } description = {