Is there an existing issue for this?
Kong version
Reproduced by code inspection against master @ 391ee48.
Current Behavior
kong.vault.get() (kong/pdk/vault.lua) is documented to postpone secret resolution to a timer during the init phase, tracking pending references in a local INIT_SECRETS table so they can be resolved later by rotate_secrets_init_worker:
local strategy, err, config, cache_key, parsed_reference = get_strategy(reference)
if not strategy then
if get_phase() == "init" then
if not INIT_SECRETS[cache_key] then
INIT_SECRETS[reference] = true
INIT_SECRETS[#INIT_SECRETS + 1] = reference
end
return ""
end
return nil, err
end
get_strategy(reference) only returns a cache_key on its success path (every failure branch inside it returns return nil, err, i.e. only 2 values). Since this whole block only executes when strategy is nil (i.e. get_strategy failed), cache_key is always nil here. The guard if not INIT_SECRETS[cache_key] then is therefore always INIT_SECRETS[nil], which is always falsy — the guard is always true, even though the entry is stored under INIT_SECRETS[reference].
As a result, every kong.vault.get() call for the same unresolved reference during the init phase (the documented, expected case when using KONG_DECLARATIVE_CONFIG before LMDB is populated) appends a duplicate entry to INIT_SECRETS, instead of deduplicating by reference. In a declarative config where the same {vault://...} reference is reused across multiple entities/fields (e.g. a shared TLS cert/key reference), INIT_SECRETS grows with one entry per lookup instead of one entry per distinct reference.
INIT_SECRETS is later passed to rotate_references(INIT_SECRETS), so this causes redundant vault resolution work proportional to the number of lookups rather than the number of distinct references, and unbounded growth of the table for configs with many repeated references.
The equivalent branch for the init_worker phase a few lines below is correct, and uses the right key (valid there since it runs on get_strategy's success path):
if get_phase() == "init_worker" then
if not INIT_WORKER_SECRETS[cache_key] then
INIT_WORKER_SECRETS[cache_key] = true
INIT_WORKER_SECRETS[#INIT_WORKER_SECRETS + 1] = cache_key
end
return ""
end
Expected Behavior
INIT_SECRETS should contain one entry per distinct reference, matching the key it's actually stored under.
Steps To Reproduce
Code-path confirmation: get_strategy's only return nil, ... paths never include a 4th cache_key return value, so at the call site, cache_key is unconditionally nil whenever strategy is nil. This can be confirmed by reading kong/pdk/vault.lua lines ~680-704 (get_strategy) against lines ~884-895 (the init-phase branch of get).
At a system level: start Kong with KONG_DECLARATIVE_CONFIG referencing the same {vault://...} secret from multiple entities/fields, before the declarative config has been loaded into LMDB — INIT_SECRETS will accumulate one entry per lookup of that reference rather than a single deduplicated entry.
Anything else?
I have a fix ready and will open a pull request referencing this issue.
Is there an existing issue for this?
Kong version
Reproduced by code inspection against
master@ 391ee48.Current Behavior
kong.vault.get()(kong/pdk/vault.lua) is documented to postpone secret resolution to a timer during theinitphase, tracking pending references in a localINIT_SECRETStable so they can be resolved later byrotate_secrets_init_worker:get_strategy(reference)only returns acache_keyon its success path (every failure branch inside it returnsreturn nil, err, i.e. only 2 values). Since this whole block only executes whenstrategyisnil(i.e.get_strategyfailed),cache_keyis alwaysnilhere. The guardif not INIT_SECRETS[cache_key] thenis therefore alwaysINIT_SECRETS[nil], which is always falsy — the guard is always true, even though the entry is stored underINIT_SECRETS[reference].As a result, every
kong.vault.get()call for the same unresolved reference during theinitphase (the documented, expected case when usingKONG_DECLARATIVE_CONFIGbefore LMDB is populated) appends a duplicate entry toINIT_SECRETS, instead of deduplicating by reference. In a declarative config where the same{vault://...}reference is reused across multiple entities/fields (e.g. a shared TLS cert/key reference),INIT_SECRETSgrows with one entry per lookup instead of one entry per distinct reference.INIT_SECRETSis later passed torotate_references(INIT_SECRETS), so this causes redundant vault resolution work proportional to the number of lookups rather than the number of distinct references, and unbounded growth of the table for configs with many repeated references.The equivalent branch for the
init_workerphase a few lines below is correct, and uses the right key (valid there since it runs onget_strategy's success path):Expected Behavior
INIT_SECRETSshould contain one entry per distinct reference, matching the key it's actually stored under.Steps To Reproduce
Code-path confirmation:
get_strategy's onlyreturn nil, ...paths never include a 4thcache_keyreturn value, so at the call site,cache_keyis unconditionallynilwheneverstrategyisnil. This can be confirmed by readingkong/pdk/vault.lualines ~680-704 (get_strategy) against lines ~884-895 (theinit-phase branch ofget).At a system level: start Kong with
KONG_DECLARATIVE_CONFIGreferencing the same{vault://...}secret from multiple entities/fields, before the declarative config has been loaded into LMDB —INIT_SECRETSwill accumulate one entry per lookup of that reference rather than a single deduplicated entry.Anything else?
I have a fix ready and will open a pull request referencing this issue.