Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New Consul storage adapter for lua-resty-auto-ssl #203

Draft
wants to merge 6 commits into
base: master
Choose a base branch
from

Conversation

fititnt
Copy link

@fititnt fititnt commented Nov 29, 2019


What is Consul?

Consul is a service mesh solution providing a full featured control plane with service discovery, configuration, and segmentation functionality.
(...)
KV Store: Applications can make use of Consul's hierarchical key/value store for any number of purposes, including dynamic configuration, feature flagging, coordination, leader election, and more. The simple HTTP API makes it easy to use.
(...)
Multi Datacenter: Consul supports multiple datacenters out of the box. This means users of Consul do not have to worry about building additional layers of abstraction to grow to multiple regions.
From https://www.consul.io/intro/index.html


The initial MVP is based on the on reverse engineering of existing adapters, resty/auto-ssl/storage_adapters/redis.lua and resty/auto-ssl/storage_adapters/file.lua. The end result of this PR is likely to:

  1. Implement at least the same functionalities from Redis that Consul support
  2. Improve the TODO at the readme marked as "Document and formalize the API for other storage adapters."
    1. Even if, be able to merge, just means documenting pretty well the new storage_adapters/consul.lua
    2. And make the API just a bit more flexible (both Redis and File use : in some parts, but for Consul this would be /)
  3. Document the additional dependency if used (opm get hamishforbes/lua-resty-consul) and maybe other pertinent issues

I think I will try to keep this Pull Request simple and I'm specially open to discussion on how to implement the interfaces, but I will try not touch other core files for now.


Some information about me:

Actually, I literally started to code in Lua 2 days ago. (started with Lean lua in an Hour https://www.youtube.com/watch?v=S4eNl1rA1Ns, but already had interest on this language before, just does not had a reason to use in production). And oh, god, this language is beautiful and very powerful (at least compared with the time debugging and implementing things direct with NGinx/Apache/At application level.)

This maybe explain why I would avoid touch other files and to have some reference, is implementing some documenting patterns (one that seems to be more used is ldoc, https://stevedonovan.github.io/ldoc/manual/doc.md.html). I'm also even looking for VSCode extensions that could allow same level of IDE I do have with other languages.

I have strong proficiency with NGinx/Docker and on last months with Ansible automation. So it means that, even if not for this Pull Request, I later could try to already change one or more Dockerfiles to be able to do the full testing with Consul. And also that if have some interest in discuss things that could be drafted to formalize other storage adapters we could do.

Ah, and other pertinent aspect, I'm specially interested in clustering, so maybe even some Ansible demo with Redis and/or Consul is not even far of what I would do anyway.

@fititnt
Copy link
Author

fititnt commented Nov 30, 2019

I'm back. I will take another round today to improve this PR today.

The warnings from the CI I'm likely to solve today, but before the merge I remove the dump functions.

Some things I will ask later, but already looking upfront:

I'm already looking the file redis_spec.lua (https://github.com/GUI/lua-resty-auto-ssl/blob/master/spec/redis_spec.lua). I guess I could also do a draft of something like this, but at this exact moment did not know how to use the Lua part of these tests.

All Dockerfile's also do install redis or redis-server without download newer versions, so I guess for Consul the tests (if have option) should not require complex or updated versions. Alpine have this https://pkgs.alpinelinux.org/package/edge/testing/x86/consul, so Centos and Ubuntu I could spin a full VM.

At this moment I not sure if, at least for Consul (and maybe other drivers) the maintainers would prefer install the full tools on all actual distributions or create one or two Dockerfiles for each new driver.

…gx.timer.at (as file.lua) instead of Consul native TTL since resty.consul does not explicitly expose this functionality (could be improved on future)
@fititnt
Copy link
Author

fititnt commented Dec 1, 2019

Fedback of the actual state of this Pull Request

  • Maybe not because of this plugin, but because of my infrastructure (I'm initially testing behind HAProxy, and with IPv6 is not fine tunned) that needs to be fixed:
    • the "the new certificate is saved, cached, and returned to the client (without dropping the original request)" does not work smootly on first request. I in investigate further. The connection is dropped, sometimes need press F5 (but on Chrome, it does automatically)
    • I'm having some warnings related to IPv6
  • Still need some code clean up (can do in an hour if need when we're ok to consider merge)

All this issues will be solved.

About the need or not to integrate full rests with each new adapter

With this PR, we will have Consul adapter. In theory this would means add tests to

  • Dockerfile-test
  • Dockerfile-test-alpine
  • Dockerfile-test-lua51
  • Dockerfile-test-openresty1.13
  • Dockerfile-test-ubuntu

These tests in fact, use 3 linux distributions

  • alpine
  • centos
  • bionic

Another point of discussion we're should have, not because of me, but to incentive other drivers, is the bare minimum ideal tests addition to have an PR Accepted.

Lets give an example

Rocha is adding Consul. Etcd (https://etcd.io/) is also pretty popular (to a point even me will submit a new PR eventually, or documenting so much that someone else will do it.

@arturmkrtchyan here #187 even was thinking about an S3 storage.

So, How do we scale testing for new drivers?

About documenting the Storage Driver API

I think I will submit a different pull request only to draft example.lua storage, and try to make it very friendly for someone new draft a new driver. I will remove some of my comments from the consul.lua

…ved some comments to different Pull Request, example.lua
@GUI
Copy link
Collaborator

GUI commented Feb 20, 2020

@fititnt: Sorry for the long delay and lack of response! I'll try to review this soon, but many thanks for your contributions!

@fititnt
Copy link
Author

fititnt commented Feb 20, 2020

@GUI actually no problem. The only very, very important thing that we should do before you release a version is, at least, do this:

  • Delete the local function dumpvar(data)
  • Delete dump calls, like this dump({'started', os.date("!%Y-%m-%dT%TZ")})
  • Delete some ngx.log that are not called just on errors, like this one ngx.log(ngx.ERR, "\n\n\n\n\n\n\n\n\n\n---")

I just already doesn't' deleted these lines to help you or who review this PR to test or if I add new unitary tests with Consul.

About how to scale integrate automated tests

Like I said before, If you want discuss this before merge (even if you want to implement without asking help) you can do it.

But if want merge more faster, we can do this without adding the tests.

@maxramqvist
Copy link

Hi,

We are using this code in production and it works great - except for renewal of expired (or to be expired) certificates.

The renewal doesn't work with this code as the function _M.keys_with_suffix isn't complete.
This code works though:

function _M.keys_with_suffix(self, suffix)
  local connection, connection_err = self:get_connection()
  if connection_err then
    ngx.log(ngx.EMERG, '_M.keys_with_suffix: ', connection_err)
    return false, connection_err
  end

  local result = {}
  local res, err = connection:list_keys(self.options["prefix"])
  if res and self.options["prefix"] then

    local keys = {}
    if res.status == 200 then
      keys = res.body
    end

    local unprefixed_keys = {}
    -- First character past the prefix and a colon
    local offset = string.len(self.options["prefix"]) + 2

    for _, key in ipairs(keys) do

      local unprefixed = string.sub(key, offset)

      table.insert(unprefixed_keys, unprefixed)

    end
    result = unprefixed_keys

  end

  return result, err
end

@fititnt if you could update your PR?

@fititnt
Copy link
Author

fititnt commented Mar 16, 2021

@maxramqvist I also had problem with renewal of expired (or to be expired) certificates! And this type of thing is hard to test (can take like months until come the issue!).

On next days I will merge your suggestion here. And is good that at least someone else is testing this.

This proposed change by @maxramqvist is expected to solve issues
with renewal of expired (or to be expired) certificates
@maxramqvist
Copy link

maxramqvist commented Mar 17, 2021

@fititnt Realized I made a mistake in the code for the keys_with_suffix function. It didn't actually return only the keys with the suffix. Here is the proper code:

function _M.keys_with_suffix(self, suffix)
  local connection, connection_err = self:get_connection()
  if connection_err then
    ngx.log(ngx.EMERG, '_M.keys_with_suffix: ', connection_err)
    return false, connection_err
  end

  local result = {}

  -- Get all keys under prefix/*
  local res, err = connection:list_keys(self.options["prefix"])
  if res and self.options["prefix"] then

    local keys = {}
    if res.status == 200 then
      keys = res.body
    end

    -- Match keys with requested suffix.
    local keys_with_suffix = {}
    for _, key in ipairs(keys) do

      local match = string.match(key, '.*' ..suffix .. '$')
      if match == nil then
        goto continue
      end

      table.insert(keys_with_suffix, key)

      ::continue::
    end


    local unprefixed_keys = {}
    -- First character past the prefix and a colon
    local offset = string.len(self.options["prefix"]) + 2
    
    -- Remove prefix from key so that only the actual key remains.
    for _, key in ipairs(keys_with_suffix) do
      local unprefixed = string.sub(key, offset)
      table.insert(unprefixed_keys, unprefixed)
    end
    result = keys_with_suffix

  end

  return result, err
end

Fixed 'It didn't actually return only the keys with the suffix.'
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants