Skip to content

Commit 8b0a838

Browse files
authored
fix(aws-lambda): aws lambda service cache by service related fields (#11805)
Cache the aws lambda service by composing a cache key using the service related fields, so that service object can be reused between plugins and vault refresh can take effect when key/secret is rotated * fix(aws-lambda): aws lambda service cache by service related fields * tests(aws-lambda): add test for checking service cache refresh when vault rotates * style(*): lint Fix KAG-2832
1 parent bab36ea commit 8b0a838

File tree

5 files changed

+159
-4
lines changed

5 files changed

+159
-4
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
message: Cache the AWS lambda service by those lambda service related fields
2+
type: bugfix
3+
scope: Plugin

kong/plugins/aws-lambda/handler.lua

+31-4
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
-- Copyright (C) Kong Inc.
22

3-
local fmt = string.format
43
local ngx_var = ngx.var
54
local ngx_now = ngx.now
65
local ngx_update_time = ngx.update_time
6+
local md5_bin = ngx.md5_bin
7+
local fmt = string.format
8+
local buffer = require "string.buffer"
9+
local lrucache = require "resty.lrucache"
710

811
local kong = kong
912
local meta = require "kong.meta"
@@ -22,7 +25,7 @@ local AWS_REGION do
2225
AWS_REGION = os.getenv("AWS_REGION") or os.getenv("AWS_DEFAULT_REGION")
2326
end
2427
local AWS
25-
local LAMBDA_SERVICE_CACHE = setmetatable({}, { __mode = "k" })
28+
local LAMBDA_SERVICE_CACHE
2629

2730

2831
local function get_now()
@@ -32,11 +35,34 @@ end
3235

3336

3437
local function initialize()
38+
LAMBDA_SERVICE_CACHE = lrucache.new(1000)
3539
AWS_GLOBAL_CONFIG = aws_config.global
3640
AWS = aws()
3741
initialize = nil
3842
end
3943

44+
local build_cache_key do
45+
-- Use AWS Service related config fields to build cache key
46+
-- so that service object can be reused between plugins and
47+
-- vault refresh can take effect when key/secret is rotated
48+
local SERVICE_RELATED_FIELD = { "timeout", "keepalive", "aws_key", "aws_secret",
49+
"aws_assume_role_arn", "aws_role_session_name",
50+
"aws_region", "host", "port", "disable_https",
51+
"proxy_url", "aws_imds_protocol_version" }
52+
53+
build_cache_key = function (conf)
54+
local cache_key_buffer = buffer.new(100):reset()
55+
for _, field in ipairs(SERVICE_RELATED_FIELD) do
56+
local v = conf[field]
57+
if v then
58+
cache_key_buffer:putf("%s=%s;", field, v)
59+
end
60+
end
61+
62+
return md5_bin(cache_key_buffer:get())
63+
end
64+
end
65+
4066

4167
local AWSLambdaHandler = {
4268
PRIORITY = 750,
@@ -62,7 +88,8 @@ function AWSLambdaHandler:access(conf)
6288
local scheme = conf.disable_https and "http" or "https"
6389
local endpoint = fmt("%s://%s", scheme, host)
6490

65-
local lambda_service = LAMBDA_SERVICE_CACHE[conf]
91+
local cache_key = build_cache_key(conf)
92+
local lambda_service = LAMBDA_SERVICE_CACHE:get(cache_key)
6693
if not lambda_service then
6794
local credentials = AWS.config.credentials
6895
-- Override credential config according to plugin config
@@ -132,7 +159,7 @@ function AWSLambdaHandler:access(conf)
132159
http_proxy = conf.proxy_url,
133160
https_proxy = conf.proxy_url,
134161
})
135-
LAMBDA_SERVICE_CACHE[conf] = lambda_service
162+
LAMBDA_SERVICE_CACHE:set(cache_key, lambda_service)
136163
end
137164

138165
local upstream_body_json = build_request_payload(conf)

spec/03-plugins/27-aws-lambda/99-access_spec.lua

+93
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ local fixtures = require "spec.fixtures.aws-lambda"
77
local TEST_CONF = helpers.test_conf
88
local server_tokens = meta._SERVER_TOKENS
99
local null = ngx.null
10+
local fmt = string.format
1011

1112

1213

@@ -1182,4 +1183,96 @@ for _, strategy in helpers.each_strategy() do
11821183
end)
11831184
end)
11841185
end)
1186+
1187+
describe("Plugin: AWS Lambda with #vault [#" .. strategy .. "]", function ()
1188+
local proxy_client
1189+
local admin_client
1190+
1191+
local ttl_time = 1
1192+
1193+
lazy_setup(function ()
1194+
helpers.setenv("KONG_VAULT_ROTATION_INTERVAL", "1")
1195+
1196+
local bp = helpers.get_db_utils(strategy, {
1197+
"routes",
1198+
"services",
1199+
"plugins",
1200+
"vaults",
1201+
}, { "aws-lambda" }, { "random" })
1202+
1203+
local route1 = bp.routes:insert {
1204+
hosts = { "lambda-vault.com" },
1205+
}
1206+
1207+
bp.plugins:insert {
1208+
name = "aws-lambda",
1209+
route = { id = route1.id },
1210+
config = {
1211+
port = 10001,
1212+
aws_key = fmt("{vault://random/aws_key?ttl=%s&resurrect_ttl=0}", ttl_time),
1213+
aws_secret = "aws_secret",
1214+
aws_region = "us-east-1",
1215+
function_name = "functionEcho",
1216+
},
1217+
}
1218+
1219+
assert(helpers.start_kong({
1220+
database = strategy,
1221+
prefix = helpers.test_conf.prefix,
1222+
nginx_conf = "spec/fixtures/custom_nginx.template",
1223+
vaults = "random",
1224+
plugins = "bundled",
1225+
log_level = "error",
1226+
}, nil, nil, fixtures))
1227+
end)
1228+
1229+
lazy_teardown(function()
1230+
helpers.unsetenv("KONG_VAULT_ROTATION_INTERVAL")
1231+
1232+
helpers.stop_kong()
1233+
end)
1234+
1235+
before_each(function()
1236+
proxy_client = helpers.proxy_client()
1237+
admin_client = helpers.admin_client()
1238+
end)
1239+
1240+
after_each(function ()
1241+
proxy_client:close()
1242+
admin_client:close()
1243+
end)
1244+
1245+
it("lambda service should use latest reference value after Vault ttl", function ()
1246+
local res = assert(proxy_client:send {
1247+
method = "GET",
1248+
path = "/get?key1=some_value1&key2=some_value2&key3=some_value3",
1249+
headers = {
1250+
["Host"] = "lambda-vault.com"
1251+
}
1252+
})
1253+
assert.res_status(200, res)
1254+
local body = assert.response(res).has.jsonbody()
1255+
local authorization_header = body.headers.authorization
1256+
local first_aws_key = string.match(authorization_header, "Credential=(.+)/")
1257+
1258+
assert.eventually(function()
1259+
proxy_client:close()
1260+
proxy_client = helpers.proxy_client()
1261+
1262+
local res = assert(proxy_client:send {
1263+
method = "GET",
1264+
path = "/get?key1=some_value1&key2=some_value2&key3=some_value3",
1265+
headers = {
1266+
["Host"] = "lambda-vault.com"
1267+
}
1268+
})
1269+
assert.res_status(200, res)
1270+
local body = assert.response(res).has.jsonbody()
1271+
local authorization_header = body.headers.authorization
1272+
local second_aws_key = string.match(authorization_header, "Credential=(.+)/")
1273+
1274+
return first_aws_key ~= second_aws_key
1275+
end).ignore_exceptions(true).with_timeout(ttl_time * 2).is_truthy()
1276+
end)
1277+
end)
11851278
end
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
local utils = require "kong.tools.utils"
2+
3+
local function get(conf, resource, version)
4+
-- Return a random string every time
5+
kong.log.err("get() called")
6+
return utils.random_string()
7+
end
8+
9+
10+
return {
11+
VERSION = "1.0.0",
12+
get = get,
13+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
local typedefs = require "kong.db.schema.typedefs"
2+
3+
return {
4+
name = "random",
5+
fields = {
6+
{
7+
config = {
8+
type = "record",
9+
fields = {
10+
{ prefix = { type = "string" } },
11+
{ suffix = { type = "string" } },
12+
{ ttl = typedefs.ttl },
13+
{ neg_ttl = typedefs.ttl },
14+
{ resurrect_ttl = typedefs.ttl },
15+
},
16+
},
17+
},
18+
},
19+
}

0 commit comments

Comments
 (0)