Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(storage) add consul and vault storage backend
- Loading branch information
Showing
3 changed files
with
240 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Original file line | Diff line number | Diff line change |
---|---|---|---|
@@ -0,0 +1,33 @@ | |||
## resty.acme.storage | |||
|
|||
An storage can be easily plug-and-use as long as it implement the following interface: | |||
|
|||
```lua | |||
local _M = {} | |||
local mt = {__index = _M} | |||
|
|||
function _M.new(conf) | |||
local self = setmetatable({}, mt) | |||
return self, err | |||
end | |||
|
|||
function _M:set(k, v) | |||
return err | |||
end | |||
|
|||
function _M:delete(k) | |||
return err | |||
end | |||
|
|||
function _M:get(k) | |||
-- if key not exist, return nil, nil | |||
return value, err | |||
end | |||
|
|||
function _M:list(prefix) | |||
local keys = { "key1", "key2" } | |||
return keys, err | |||
end | |||
|
|||
return _M | |||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Original file line | Diff line number | Diff line change |
---|---|---|---|
@@ -0,0 +1,111 @@ | |||
local http = require "resty.http" | |||
local cjson = require "cjson.safe" | |||
|
|||
local _M = {} | |||
local mt = {__index = _M} | |||
|
|||
function _M.new(conf) | |||
conf = conf or {} | |||
local base_url = conf.https and "https://" or "http://" | |||
base_url = base_url .. (conf.host or "127.0.0.1") | |||
base_url = base_url .. ":" .. (conf.port or "8500") | |||
|
|||
local prefix = conf.kv_path | |||
if not prefix then | |||
prefix = "/acme" | |||
elseif path:sub(1, 1) ~= "/" then | |||
prefix = "/" .. prefix | |||
end | |||
base_url = base_url .. "/v1/kv" .. prefix .. "/" | |||
|
|||
local self = | |||
setmetatable( | |||
{ | |||
timeout = conf.timeout or 2000, | |||
base_url = base_url, | |||
}, | |||
mt | |||
) | |||
self.headers = { | |||
["X-Consul-Token"] = conf.token, | |||
} | |||
return self, nil | |||
end | |||
|
|||
local function api(self, method, uri, payload) | |||
local ok, err | |||
-- consul don't keepalive, we create a new instance for every request | |||
local client = http:new() | |||
client:set_timeout(self.timeout) | |||
|
|||
local res, err = client:request_uri(self.base_url .. uri, { | |||
method = method, | |||
headers = self.headers, | |||
body = payload, | |||
}) | |||
if err then | |||
return nil, err | |||
end | |||
client:close() | |||
|
|||
-- return "soft error" for not found | |||
if res.status == 404 then | |||
return nil, nil | |||
end | |||
|
|||
-- "true" "false" is also valid through cjson | |||
local decoded, err = cjson.decode(res.body) | |||
if not decoded then | |||
return nil, "unable to decode response body " .. (err or 'nil') | |||
end | |||
return decoded, err | |||
end | |||
|
|||
function _M:set(k, v) | |||
local res, err = api(self, "PUT", k, v) | |||
if not res or err then | |||
return err or "set key failed" | |||
end | |||
return nil | |||
end | |||
|
|||
function _M:delete(k) | |||
local res, err = api(self, "DELETE", k) | |||
if not res or err then | |||
return err or "delete key failed" | |||
end | |||
end | |||
|
|||
function _M:get(k) | |||
local res, err = api(self, 'GET', k) | |||
if err then | |||
return nil, err | |||
elseif not res or not res[1] or not res[1]["Value"] then | |||
return nil, nil | |||
end | |||
return ngx.decode_base64(res[1]["Value"]), err | |||
end | |||
|
|||
local empty_table = {} | |||
function _M:list(prefix) | |||
local res, err = api(self, 'GET', '?keys') | |||
if err then | |||
return nil, err | |||
elseif not res then | |||
return empty_table, nil | |||
end | |||
local ret = {} | |||
local prefix_length = #prefix | |||
for _, key in ipairs(res) do | |||
local key, err = ngx.re.match(key, [[([^/]+)$]], "jo") | |||
if key then | |||
key = key[1] | |||
if key:sub(1, prefix_length) == prefix then | |||
table.insert(ret, key) | |||
end | |||
end | |||
end | |||
return ret | |||
end | |||
|
|||
return _M |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Original file line | Diff line number | Diff line change |
---|---|---|---|
@@ -1,31 +1,121 @@ | |||
local http = require "resty.http" | |||
local cjson = require "cjson.safe" | |||
|
|||
local _M = {} | local _M = {} | ||
local mt = {__index = _M} | local mt = {__index = _M} | ||
|
|
||
function _M.new(conf) | function _M.new(conf) | ||
conf = conf or {} | |||
local base_url = conf.https and "https://" or "http://" | |||
base_url = base_url .. (conf.host or "127.0.0.1") | |||
base_url = base_url .. ":" .. (conf.port or "8200") | |||
|
|||
local prefix = conf.kv_path | |||
if not prefix then | |||
prefix = "/acme" | |||
elseif path:sub(1, 1) ~= "/" then | |||
prefix = "/" .. prefix | |||
end | |||
local metadata_url = base_url .. "/v1/secret/metadata" .. prefix .. "/" | |||
local data_url = base_url .. "/v1/secret/data" .. prefix .. "/" | |||
|
|||
local self = | local self = | ||
setmetatable( | setmetatable( | ||
{ | { | ||
|
timeout = conf.timeout or 2000, | ||
data_url = data_url, | |||
metadata_url = metadata_url, | |||
}, | }, | ||
mt | mt | ||
) | ) | ||
return self | self.headers = { | ||
["X-Vault-Token"] = conf.token, | |||
} | |||
return self, nil | |||
end | end | ||
|
|
||
function _M:set(k, v) | local function api(self, method, uri, payload) | ||
local ok, err | |||
-- vault don't keepalive, we create a new instance for every request | |||
local client = http:new() | |||
client:set_timeout(self.timeout) | |||
|
|||
local payload = payload and cjson.encode(payload) | |||
|
|||
local res, err = client:request_uri(uri, { | |||
method = method, | |||
headers = self.headers, | |||
body = payload, | |||
}) | |||
if err then | |||
return nil, err | |||
end | |||
client:close() | |||
|
|||
-- return "soft error" for not found and successful delete | |||
if res.status == 404 or res.status == 204 then | |||
return nil, nil | |||
end | |||
|
|
||
-- "true" "false" is also valid through cjson | |||
local decoded, err = cjson.decode(res.body) | |||
if not decoded then | |||
return nil, "unable to decode response body " .. (err or 'nil') | |||
end | |||
return decoded, err | |||
end | end | ||
|
|
||
function _M:delete(k) | function _M:set(k, v) | ||
local res, err = api(self, "POST", self.data_url .. k, { | |||
data = { | |||
value = v, | |||
note = "managed by lua-resty-acme", | |||
} | |||
}) | |||
if not res or err then | |||
return err or "set key failed" | |||
end | |||
return nil | |||
end | |||
|
|
||
function _M:delete(k) | |||
local res, err = api(self, "DELETE", self.metadata_url .. k) | |||
if err then | |||
return "delete key failed" | |||
end | |||
end | end | ||
|
|
||
function _M:get(k) | function _M:get(k) | ||
|
local res, err = api(self, 'GET', self.data_url .. k) | ||
if err then | |||
return nil, err | |||
elseif not res or not res["data"] or not res["data"]["data"] | |||
or not res["data"]["data"]["value"] then | |||
return nil, nil | |||
end | |||
return res["data"]["data"]["value"], err | |||
end | end | ||
|
|
||
local empty_table = {} | |||
function _M:list(prefix) | function _M:list(prefix) | ||
|
local res, err = api(self, 'LIST', self.metadata_url) | ||
if err then | |||
return nil, err | |||
elseif not res or not res['data'] or not res['data']['keys'] then | |||
return empty_table, nil | |||
end | |||
local ret = {} | |||
local prefix_length = #prefix | |||
for _, key in ipairs(res['data']['keys']) do | |||
local key, err = ngx.re.match(key, [[([^/]+)$]], "jo") | |||
if key then | |||
key = key[1] | |||
if key:sub(1, prefix_length) == prefix then | |||
table.insert(ret, key) | |||
end | |||
end | |||
end | |||
return ret | |||
end | end | ||
|
|
||
return _M | return _M |