Skip to content
Permalink
Browse files

feat(storage) add consul and vault storage backend

  • Loading branch information...
fffonion committed Oct 23, 2019
1 parent 2dd6cfa commit 028daa5bc965ab10621aa3f16d7ffabe619fd38a
Showing with 240 additions and 6 deletions.
  1. +33 −0 lib/resty/acme/storage/README.md
  2. +111 −0 lib/resty/acme/storage/consul.lua
  3. +96 −6 lib/resty/acme/storage/vault.lua
@@ -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
```
@@ -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
@@ -1,31 +1,121 @@
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 "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 =
setmetatable(
{

timeout = conf.timeout or 2000,
data_url = data_url,
metadata_url = metadata_url,
},
mt
)
return self
self.headers = {
["X-Vault-Token"] = conf.token,
}
return self, nil
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

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

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

local empty_table = {}
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

return _M

0 comments on commit 028daa5

Please sign in to comment.
You can’t perform that action at this time.