Skip to content

Commit 22e48b5

Browse files
ddragosdselfxp
authored andcommitted
Iam credentials refactoring (#5)
* added support for lua-resty-http lib and updated Makefile to execute integration tests * [DOC] - documents dependency to lua-rest-http * # refactored AWSIAMCredetials to use cachemanager and extracted an AwsDateConverter class * removed unused `sharedCacheDictInstance` variable
1 parent f714f1c commit 22e48b5

File tree

4 files changed

+105
-73
lines changed

4 files changed

+105
-73
lines changed

src/lua/api-gateway/aws/AWSIAMCredentials.lua

+37-65
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,21 @@
66
-- To change this template use File | Settings | File Templates.
77
--
88

9-
local cjson = require"cjson"
10-
local http = require"api-gateway.aws.httpclient.http"
11-
local url = require"api-gateway.aws.httpclient.url"
9+
local cjson = require "cjson"
10+
local http = require "api-gateway.aws.httpclient.http"
11+
local url = require "api-gateway.aws.httpclient.url"
12+
local awsDate = require "api-gateway.aws.AwsDateConverter"
13+
local cacheCls = require "api-gateway.cache.cache"
1214

1315
local DEFAULT_SECURITY_CREDENTIALS_HOST = "169.254.169.254"
1416
local DEFAULT_SECURITY_CREDENTIALS_PORT = "80"
1517
local DEFAULT_SECURITY_CREDENTIALS_URL = "/latest/meta-data/iam/security-credentials/"
1618
-- use GET /latest/meta-data/iam/security-credentials/ to auto-discover the IAM Role
1719
local DEFAULT_TOKEN_EXPIRATION = 60*60*24 -- in seconds
1820

21+
-- configur cache Manager for IAM crendentials
22+
local iamCache = cacheCls:new()
23+
1924
-- per nginx process cache to store IAM credentials
2025
local cache = {
2126
IamUser = nil,
@@ -26,8 +31,6 @@ local cache = {
2631
ExpireAtTimestamp = nil
2732
}
2833

29-
local sharedCacheDictInstance
30-
3134
local function tableToString(table_ref)
3235
local s = ""
3336
local o = table_ref or {}
@@ -37,6 +40,21 @@ local function tableToString(table_ref)
3740
return s
3841
end
3942

43+
local function initIamCache(shared_cache_dict)
44+
local localCache = require "api-gateway.cache.store.localCache":new({
45+
dict = shared_cache_dict,
46+
ttl = function (value)
47+
local value_o = cjson.decode(value)
48+
ngx.log(ngx.DEBUG, "ExpireAt=", tostring(value_o.ExpireAt))
49+
local expiryTimeUTC = value.ExpireAtTimestamp or awsDate.convertDateStringToTimestamp(value_o.ExpireAt, true)
50+
local expiryTimeInSeconds = expiryTimeUTC - os.time()
51+
return math.min(DEFAULT_TOKEN_EXPIRATION, expiryTimeInSeconds)
52+
end
53+
})
54+
55+
iamCache:addStore(localCache)
56+
end
57+
4058
local AWSIAMCredentials = {}
4159

4260
---
@@ -60,73 +78,25 @@ function AWSIAMCredentials:new(o)
6078
self.security_credentials_url = o.security_credentials_url or DEFAULT_SECURITY_CREDENTIALS_URL
6179
self.shared_cache_dict = o.shared_cache_dict
6280
if (o.shared_cache_dict ~= nil) then
63-
sharedCacheDictInstance = ngx.shared[o.shared_cache_dict]
81+
initIamCache(o.shared_cache_dict)
6482
end
6583
local s = tableToString(o)
6684
ngx.log(ngx.DEBUG, "Initializing AWSIAMCredentials with object:", s)
6785
end
6886
return o
6987
end
7088

71-
local function getTimestamp(dateString, convertToUTC)
72-
local pattern = "(%d+)%-(%d+)%-(%d+)T(%d+):(%d+):(%d+)Z"
73-
local xyear, xmonth, xday, xhour, xminute,
74-
xseconds, xoffset, xoffsethour, xoffsetmin = dateString:match(pattern)
75-
76-
-- the converted timestamp is in the local timezone
77-
local convertedTimestamp = os.time({
78-
year = xyear,
79-
month = xmonth,
80-
day = xday,
81-
hour = xhour,
82-
min = xminute,
83-
sec = xseconds
84-
})
85-
if (convertToUTC == true) then
86-
local offset = os.time() - os.time(os.date("!*t"))
87-
convertedTimestamp = convertedTimestamp + offset
88-
end
89-
return tonumber(convertedTimestamp)
90-
end
91-
92-
function AWSIAMCredentials:saveCredentialsInSharedDict()
93-
if (sharedCacheDictInstance == nil) then
94-
ngx.log(ngx.WARN, "No shared_cache_dict provided to AWSIAMCredentials. To improve performance please define one.")
95-
return
96-
end
97-
98-
local expiry_time_utc = getTimestamp(cache.ExpireAt, true)
99-
local expire_in_sec = expiry_time_utc - os.time()
100-
if ( expire_in_sec > 0 ) then
101-
-- set the values and the expiry time
102-
sharedCacheDictInstance:set("AccessKeyId", cache.AccessKeyId, expire_in_sec)
103-
sharedCacheDictInstance:set("SecretAccessKey", cache.SecretAccessKey, expire_in_sec)
104-
sharedCacheDictInstance:set("Token", cache.Token, expire_in_sec)
105-
sharedCacheDictInstance:set("ExpireAt", cache.ExpireAt, expire_in_sec)
106-
sharedCacheDictInstance:set("ExpireAtTimestamp", cache.ExpireAtTimestamp, expire_in_sec)
107-
108-
ngx.log(ngx.DEBUG, "IAM Credentials cached for ", tostring(expire_in_sec), " seconds in the shared dict=", self.shared_cache_dict)
109-
end
110-
end
111-
11289
function AWSIAMCredentials:loadCredentialsFromSharedDict()
113-
if (sharedCacheDictInstance == nil) then
114-
ngx.log(ngx.WARN, "No shared_cache_dict provided to AWSIAMCredentials. To improve performance please define one.")
115-
return
116-
end
117-
-- see if there's something in the shared dict that didn't expire yet
118-
local accessKeyId = sharedCacheDictInstance:get("AccessKeyId")
119-
if ( accessKeyId == nil ) then
120-
ngx.log(ngx.DEBUG, "nothing found in Shared Cache")
121-
return
90+
local iamCreds = iamCache:get("iam_credentials")
91+
if (iamCreds ~= nil) then
92+
iamCreds = cjson.decode(iamCreds)
93+
cache.AccessKeyId = iamCreds.AccessKeyId
94+
cache.SecretAccessKey = iamCreds.SecretAccessKey
95+
cache.Token = iamCreds.Token
96+
cache.ExpireAt = iamCreds.ExpireAt
97+
cache.ExpireAtTimestamp = iamCreds.ExpireAtTimestamp
98+
ngx.log(ngx.DEBUG, "Cache has been loaded from Shared Cache" )
12299
end
123-
124-
cache.AccessKeyId = sharedCacheDictInstance:get("AccessKeyId")
125-
cache.SecretAccessKey = sharedCacheDictInstance:get("SecretAccessKey")
126-
cache.Token = sharedCacheDictInstance:get("Token")
127-
cache.ExpireAt = sharedCacheDictInstance:get("ExpireAt")
128-
cache.ExpireAtTimestamp = sharedCacheDictInstance:get("ExpireAtTimestamp")
129-
ngx.log(ngx.DEBUG, "Cache has been loaded from Shared Cache" )
130100
end
131101

132102
---
@@ -190,8 +160,10 @@ function AWSIAMCredentials:fetchSecurityCredentialsFromAWS()
190160
--local token = url:encodeUrl(aws_response["Token"])
191161
cache.Token = aws_response["Token"]
192162
cache.ExpireAt = aws_response["Expiration"]
193-
cache.ExpireAtTimestamp = getTimestamp(cache.ExpireAt, true)
194-
self:saveCredentialsInSharedDict()
163+
cache.ExpireAtTimestamp = awsDate.convertDateStringToTimestamp(cache.ExpireAt, true)
164+
if (cache.ExpireAtTimestamp - os.time() > 0) then
165+
iamCache:put("iam_credentials", cjson.encode(cache))
166+
end
195167
return true
196168
end
197169

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
--[[
2+
Copyright 2016 Adobe Systems Incorporated. All rights reserved.
3+
4+
This file is licensed to you under the Apache License, Version 2.0 (the
5+
"License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
6+
7+
http://www.apache.org/licenses/LICENSE-2.0
8+
9+
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR RESPRESENTATIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
10+
]]
11+
12+
--
13+
-- User: ddascal
14+
-- Date: 19/03/16
15+
-- Time: 21:40
16+
-- To change this template use File | Settings | File Templates.
17+
--
18+
19+
20+
local _M = {}
21+
22+
--- Converts an AWS Date String into a timestamp number
23+
-- @param dateString AWS Date String (i.e. 2016-03-19T06:44:17Z)
24+
-- @param convertToUTC (default false). Boolean value to get the date in UTC or not
25+
--
26+
local function _convertDateStringToTimestamp(dateString, convertToUTC)
27+
local pattern = "(%d+)%-(%d+)%-(%d+)T(%d+):(%d+):(%d+)Z"
28+
local xyear, xmonth, xday, xhour, xminute,
29+
xseconds, xoffset, xoffsethour, xoffsetmin = dateString:match(pattern)
30+
31+
-- the converted timestamp is in the local timezone
32+
local convertedTimestamp = os.time({
33+
year = xyear,
34+
month = xmonth,
35+
day = xday,
36+
hour = xhour,
37+
min = xminute,
38+
sec = xseconds
39+
})
40+
if (convertToUTC == true) then
41+
local offset = os.time() - os.time(os.date("!*t"))
42+
convertedTimestamp = convertedTimestamp + offset
43+
end
44+
return tonumber(convertedTimestamp)
45+
end
46+
47+
_M.convertDateStringToTimestamp = _convertDateStringToTimestamp
48+
49+
return _M

test/docker-compose-integration-tests.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
gateway:
2-
image: adobeapiplatform/apigateway
2+
image: adobeapiplatform/apigateway:latest
33
volumes:
44
- ~/tmp/apiplatform/api-gateway-aws/src/lua/api-gateway/aws:/usr/local/api-gateway/lualib/api-gateway/aws
55
- ~/tmp/apiplatform/api-gateway-aws/test/perl:/tmp/perl

test/perl/awsIamCredentials.t

+18-7
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# vim:set ft= ts=4 sw=4 et fdm=marker:
22
use lib 'lib';
3+
use strict;
4+
use warnings;
35
use Test::Nginx::Socket::Lua;
46
use Cwd qw(cwd);
57

@@ -62,6 +64,8 @@ __DATA__
6264
=== TEST 1: test auto discovery of iam user
6365
--- http_config eval: $::HttpConfig
6466
--- config
67+
error_log ../awsIamCredentials_test1_error.log debug;
68+
6569
location = /latest/meta-data/iam/security-credentials/ {
6670
return 200 'test-iam-user';
6771
}
@@ -93,6 +97,8 @@ X-Test: test
9397
=== TEST 2: test Iam can automatically read credentials
9498
--- http_config eval: $::HttpConfig
9599
--- config
100+
error_log ../awsIamCredentials_test2_error.log debug;
101+
96102
location = /latest/meta-data/iam/security-credentials/ {
97103
return 200 'test-iam-user';
98104
}
@@ -178,6 +184,8 @@ X-Test: test
178184
=== TEST 3: test Iam can automatically read credentials with SHARED DICT
179185
--- http_config eval: $::HttpConfig
180186
--- config
187+
error_log ../awsIamCredentials_test3_error.log debug;
188+
181189
location = /latest/meta-data/iam/security-credentials/ {
182190
return 200 'test-iam-user';
183191
}
@@ -212,6 +220,7 @@ X-Test: test
212220
213221
location /test {
214222
content_by_lua '
223+
local cjson = require "cjson"
215224
local IamCredentials = require "api-gateway.aws.AWSIAMCredentials"
216225
local iam = IamCredentials:new({
217226
security_credentials_host = "127.0.0.1",
@@ -223,7 +232,7 @@ X-Test: test
223232
ngx.say("key=" .. key .. ", secret=" .. secret .. ", token=" .. token .. ", date=" .. date .. ", timestamp=" ..timestamp )
224233
225234
local shared_cache = ngx.shared["shared_cache"]
226-
assert( shared_cache:get("AccessKeyId") == nil, "Expired token should not be saved in shared cache")
235+
assert( shared_cache:get("iam_credentials") == nil, "iam_credentials should not be saved in shared cache, but found:" .. tostring(shared_cache:get("iam_credentials")))
227236
228237
-- the previous token should be expired and a new call to fetch credentials should get a new token
229238
-- changing the iam_user will cause the IamCredentials to use this one when fetching new credentials
@@ -238,12 +247,14 @@ X-Test: test
238247
if ( date ~= d ) then
239248
error("Dates should match. Got" .. date .. ", Expected: " .. d)
240249
end
241-
242-
assert( shared_cache:get("AccessKeyId") ~= nil, "AccessKeyId should be saved in shared cache")
243-
assert( shared_cache:get("SecretAccessKey") ~= nil, "SecretAccessKey should be saved in shared cache")
244-
assert( shared_cache:get("Token") ~= nil, "Token should be saved in shared cache")
245-
assert( shared_cache:get("ExpireAt") ~= nil, "ExpireAt should be saved in shared cache")
246-
assert( shared_cache:get("ExpireAtTimestamp") ~= nil, "ExpireAtTimestamp should be saved in shared cache")
250+
local cachedIam = shared_cache:get("iam_credentials")
251+
cachedIam = cjson.decode(cachedIam)
252+
assert( cachedIam ~= nil, "iam_credentials should be saved in shared cache")
253+
assert( cachedIam.AccessKeyId ~= nil, "AccessKeyId should be saved in shared cache")
254+
assert( cachedIam.SecretAccessKey ~= nil, "SecretAccessKey should be saved in shared cache")
255+
assert( cachedIam.Token ~= nil, "Token should be saved in shared cache")
256+
assert( cachedIam.ExpireAt ~= nil, "ExpireAt should be saved in shared cache")
257+
assert( cachedIam.ExpireAtTimestamp ~= nil, "ExpireAtTimestamp should be saved in shared cache")
247258
248259
ngx.sleep(3)
249260

0 commit comments

Comments
 (0)