diff --git a/kong.conf.default b/kong.conf.default index 201e370cb74f..48d8d66ab061 100644 --- a/kong.conf.default +++ b/kong.conf.default @@ -247,6 +247,14 @@ #cluster_encrypt_key = # base64-encoded 16-bytes key to encrypt # cluster traffic with. +#cluster_keyring_file = # Specifies a file to load keyring data from. + # Kong is able to keep encryption keys in sync + # and perform key rotations. During a key + # rotation, there may be some period of time in + # which Kong is required to maintain more than + # one encryption key until all members have + # received the new key. + #cluster_ttl_on_failure = 3600 # Time to live (in seconds) of a node in the # cluster when it stops sending healthcheck # pings, possibly caused by a node or network diff --git a/kong/cmd/cluster.lua b/kong/cmd/cluster.lua index ca04ebd14bf9..7d103001d7f6 100644 --- a/kong/cmd/cluster.lua +++ b/kong/cmd/cluster.lua @@ -1,9 +1,12 @@ local log = require "kong.cmd.utils.log" local Serf = require "kong.serf" local pl_path = require "pl.path" +local pl_table = require "pl.tablex" local DAOFactory = require "kong.dao.factory" local conf_loader = require "kong.conf_loader" +local KEYS_COMMANDS = { "list", "install", "use", "remove" } + local function execute(args) if args.command == "keygen" then local conf = assert(conf_loader(args.conf)) @@ -37,6 +40,11 @@ local function execute(args) log("force-leaving %s", node_name) assert(serf:force_leave(node_name)) log("left node %s", node_name) + elseif args.command == "keys" then + assert(pl_table.find(KEYS_COMMANDS, args[1]), "invalid command") + assert(args[1] == "list" or #args == 2, "missing key") + + print(assert(serf:keys("-"..args[1], args[2]))) end end @@ -52,6 +60,20 @@ The available commands are: reachability -p Check if the cluster is reachable. force-leave -p Forcefully remove a node from the cluster (useful if the node is in a failed state). + keys install Install a new key onto Kong's internal keyring. This + will enable the key for decryption. The key will not + be used to encrypt messages until the primary key is + changed. + keys use Change the primary key used for encrypting messages. + All nodes in the cluster must already have this key + installed if they are to continue communicating with + eachother. + keys remove Remove a key from Kong's internal keyring. The key + being removed may not be the current primary key. + keys list List all currently known keys in the cluster. This + will ask all nodes in the cluster for a list of keys + and dump a summary containing each key and the + number of members it is installed on to the console. Options: -c,--conf (optional string) configuration file @@ -65,6 +87,7 @@ return { keygen = true, members = true, reachability = true, - ["force-leave"] = true + ["force-leave"] = true, + keys = true } } diff --git a/kong/cmd/utils/serf_signals.lua b/kong/cmd/utils/serf_signals.lua index c5beefde2ac1..aac0de9d5aea 100644 --- a/kong/cmd/utils/serf_signals.lua +++ b/kong/cmd/utils/serf_signals.lua @@ -57,6 +57,7 @@ function _M.start(kong_config, dao) ["-rpc-addr"] = kong_config.cluster_listen_rpc, ["-advertise"] = kong_config.cluster_advertise, ["-encrypt"] = kong_config.cluster_encrypt_key, + ["-keyring-file"] = kong_config.cluster_keyring_file, ["-log-level"] = "err", ["-profile"] = kong_config.cluster_profile, ["-node"] = serf.node_name, diff --git a/kong/serf.lua b/kong/serf.lua index d0cc43ae9e3d..bd0875f5882d 100644 --- a/kong/serf.lua +++ b/kong/serf.lua @@ -28,7 +28,7 @@ end -- WARN: BAD, this is **blocking** IO. Legacy code from previous Serf -- implementation that needs to be upgraded. -function Serf:invoke_signal(signal, args, no_rpc) +function Serf:invoke_signal(signal, args, no_rpc, full_error) args = args or {} if type(args) == "table" then setmetatable(args, Serf.args_mt) @@ -37,8 +37,10 @@ function Serf:invoke_signal(signal, args, no_rpc) local cmd = string.format("%s %s %s %s", self.config.serf_path, signal, rpc, tostring(args)) local ok, code, stdout, stderr = pl_utils.executeex(cmd) if not ok or code ~= 0 then - -- always print the first error line of serf - local err = stdout ~= "" and pl_stringx.splitlines(stdout)[1] or stderr + local err = stderr + if stdout ~= "" then + err = full_error and stdout or pl_stringx.splitlines(stdout)[1] + end return nil, err end @@ -73,6 +75,14 @@ function Serf:force_leave(node_name) return true end +function Serf:keys(action, key) + local res, err = self:invoke_signal(string.format("keys %s %s", action, key + and key or ""), nil, false, true) + if not res then return nil, err end + + return res +end + function Serf:members() local res, err = self:invoke_signal("members", {["-format"] = "json"}) if not res then return nil, err end diff --git a/kong/templates/kong_defaults.lua b/kong/templates/kong_defaults.lua index e385aa600f5c..0aabe97238b9 100644 --- a/kong/templates/kong_defaults.lua +++ b/kong/templates/kong_defaults.lua @@ -47,6 +47,7 @@ cluster_listen = 0.0.0.0:7946 cluster_listen_rpc = 127.0.0.1:7373 cluster_advertise = NONE cluster_encrypt_key = NONE +cluster_keyring_file = NONE cluster_profile = wan cluster_ttl_on_failure = 3600 diff --git a/spec/02-integration/01-cmd/07-cluster_spec.lua b/spec/02-integration/01-cmd/07-cluster_spec.lua index 4bc42c8ab357..94c4af928f7a 100644 --- a/spec/02-integration/01-cmd/07-cluster_spec.lua +++ b/spec/02-integration/01-cmd/07-cluster_spec.lua @@ -1,4 +1,5 @@ local helpers = require "spec.helpers" +local pl_file = require "pl.file" describe("kong cluster", function() setup(function() @@ -7,9 +8,6 @@ describe("kong cluster", function() teardown(function() helpers.clean_prefix() end) - after_each(function() - helpers.kill_all() - end) it("cluster help", function() local _, stderr = helpers.kong_exec "cluster --help" @@ -25,20 +23,73 @@ describe("kong cluster", function() assert.equal("", stderr) assert.equal(26, #stdout) -- 24 + \r\n end) - it("shows members", function() - assert(helpers.kong_exec("start --conf "..helpers.test_conf_path)) - local _, _, stdout = assert(helpers.kong_exec("cluster members --prefix "..helpers.test_conf.prefix)) - assert.matches("alive", stdout) - end) - it("shows reachability", function() - assert(helpers.kong_exec("start --conf "..helpers.test_conf_path)) - local _, _, stdout = assert(helpers.kong_exec("cluster reachability --prefix "..helpers.test_conf.prefix)) - assert.matches("Successfully contacted all live nodes", stdout) + + describe("commands that require a running Serf", function() + setup(function() + assert(helpers.kong_exec("start --conf "..helpers.test_conf_path)) + end) + teardown(function() + helpers.kill_all() + end) + + it("shows members", function() + local _, _, stdout = assert(helpers.kong_exec("cluster members --prefix "..helpers.test_conf.prefix)) + assert.matches("alive", stdout) + end) + it("shows reachability", function() + local _, _, stdout = assert(helpers.kong_exec("cluster reachability --prefix "..helpers.test_conf.prefix)) + assert.matches("Successfully contacted all live nodes", stdout) + end) + it("force-leaves a node", function() + local _, _, stdout = assert(helpers.kong_exec("cluster force-leave 127.0.0.1 --prefix "..helpers.test_conf.prefix)) + assert.matches("left node 127.0.0.1", stdout, nil, true) + end) end) - it("force-leaves a node", function() - assert(helpers.kong_exec("start --conf "..helpers.test_conf_path)) - local _, _, stdout = assert(helpers.kong_exec("cluster force-leave 127.0.0.1 --prefix "..helpers.test_conf.prefix)) - assert.matches("left node 127.0.0.1", stdout, nil, true) + + describe("keys", function() + setup(function() + -- Creating a sample keyring file + local KEYRING_PATH = os.tmpname() + assert(pl_file.write(KEYRING_PATH, [[ + [ + "QHOYjmYlxSCBhdfiolhtDQ==", + "daZ2wnuw+Ql+2hCm7vQB6A==", + "keTZydopxtiTY7HVoqeWGw==" + ] + ]])) + + assert(helpers.start_kong({ + cluster_keyring_file = KEYRING_PATH + })) + end) + + teardown(function() + helpers.kill_all() + end) + + it("lists keys", function() + local ok = helpers.kong_exec("cluster keys list --prefix "..helpers.test_conf.prefix) + assert.True(ok) + end) + it("install key", function() + local ok = helpers.kong_exec("cluster keys install d7rHSZRrb2FPyJgdBAXMZQ== --prefix "..helpers.test_conf.prefix) + assert.True(ok) + end) + it("remove key", function() + local ok = helpers.kong_exec("cluster keys remove d7rHSZRrb2FPyJgdBAXMZQ== --prefix "..helpers.test_conf.prefix) + assert.True(ok) + end) + it("use key", function() + local ok = helpers.kong_exec("cluster keys use daZ2wnuw+Ql+2hCm7vQB6A== --prefix "..helpers.test_conf.prefix) + assert.True(ok) + end) + + describe("errors", function() + it("fails to install an invalid key", function() + local ok = helpers.kong_exec("cluster keys install helloworld --prefix "..helpers.test_conf.prefix) + assert.False(ok) + end) + end) end) describe("errors", function()