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

feat(aws-lambda) add support for assume role based on metadata credentials #8900

Merged
merged 4 commits into from
Jun 6, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions kong-2.8.0-0.rockspec
Original file line number Diff line number Diff line change
Expand Up @@ -405,6 +405,7 @@ build = {
["kong.plugins.aws-lambda.handler"] = "kong/plugins/aws-lambda/handler.lua",
["kong.plugins.aws-lambda.iam-ec2-credentials"] = "kong/plugins/aws-lambda/iam-ec2-credentials.lua",
["kong.plugins.aws-lambda.iam-ecs-credentials"] = "kong/plugins/aws-lambda/iam-ecs-credentials.lua",
["kong.plugins.aws-lambda.iam-sts-credentials"] = "kong/plugins/aws-lambda/iam-sts-credentials.lua",
["kong.plugins.aws-lambda.schema"] = "kong/plugins/aws-lambda/schema.lua",
["kong.plugins.aws-lambda.v4"] = "kong/plugins/aws-lambda/v4.lua",
["kong.plugins.aws-lambda.request-util"] = "kong/plugins/aws-lambda/request-util.lua",
Expand Down
54 changes: 43 additions & 11 deletions kong/plugins/aws-lambda/handler.lua
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,39 @@ local AWS_REGION do
end


local fetch_credentials do
local credential_sources = {
require "kong.plugins.aws-lambda.iam-ecs-credentials",
-- The EC2 one will always return `configured == true`, so must be the last!
require "kong.plugins.aws-lambda.iam-ec2-credentials",
}
local function fetch_aws_credentials(aws_conf)
local fetch_metadata_credentials do
local metadata_credentials_source = {
require "kong.plugins.aws-lambda.iam-ecs-credentials",
-- The EC2 one will always return `configured == true`, so must be the last!
require "kong.plugins.aws-lambda.iam-ec2-credentials",
}

for _, credential_source in ipairs(metadata_credentials_source) do
if credential_source.configured then
fetch_metadata_credentials = credential_source.fetchCredentials
break
end
end
end

if aws_conf.aws_assume_role_arn then
local metadata_credentials, err = fetch_metadata_credentials()

for _, credential_source in ipairs(credential_sources) do
if credential_source.configured then
fetch_credentials = credential_source.fetchCredentials
break
if err then
return nil, err
end

local aws_sts_cred_source = require "kong.plugins.aws-lambda.iam-sts-credentials"
return aws_sts_cred_source.fetch_assume_role_credentials(aws_conf.aws_region,
aws_conf.aws_assume_role_arn,
aws_conf.aws_role_session_name,
metadata_credentials.access_key,
metadata_credentials.secret_key,
metadata_credentials.session_token)

else
return fetch_metadata_credentials()
end
end

Expand Down Expand Up @@ -235,12 +256,19 @@ function AWSLambdaHandler:access(conf)
query = conf.qualifier and "Qualifier=" .. conf.qualifier
}

local aws_conf = {
aws_region = conf.aws_region,
aws_assume_role_arn = conf.aws_assume_role_arn,
aws_role_session_name = conf.aws_role_session_name,
}

if not conf.aws_key then
-- no credentials provided, so try the IAM metadata service
local iam_role_credentials = kong.cache:get(
IAM_CREDENTIALS_CACHE_KEY,
nil,
fetch_credentials
fetch_aws_credentials,
aws_conf
)

if not iam_role_credentials then
Expand Down Expand Up @@ -292,6 +320,10 @@ function AWSLambdaHandler:access(conf)

local content = res.body

if res.status >= 400 then
return error(content)
end

-- setting the latency here is a bit tricky, but because we are not
-- actually proxying, it will not be overwritten
ctx.KONG_WAITING_TIME = get_now() - kong_wait_time_start
Expand Down
107 changes: 107 additions & 0 deletions kong/plugins/aws-lambda/iam-sts-credentials.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
local http = require "resty.http"
local json = require "cjson"
local aws_v4 = require "kong.plugins.aws-lambda.v4"
local utils = require "kong.tools.utils"
local ngx_now = ngx.now
local kong = kong

local DEFAULT_SESSION_DURATION_SECONDS = 3600
local DEFAULT_HTTP_CLINET_TIMEOUT = 60000
local DEFAULT_ROLE_SESSION_NAME = "kong"


local function get_regional_sts_endpoint(aws_region)
if aws_region then
return 'sts.' .. aws_region .. '.amazonaws.com'
else
return 'sts.amazonaws.com'
end
end


local function fetch_assume_role_credentials(aws_region, assume_role_arn,
role_session_name, access_key,
secret_key, session_token)
if not assume_role_arn then
return nil, "Missing required parameter 'assume_role_arn' for fetching STS credentials"
end

role_session_name = role_session_name or DEFAULT_ROLE_SESSION_NAME

kong.log.debug('Trying to assume role [', assume_role_arn, ']')

local sts_host = get_regional_sts_endpoint(aws_region)

-- build the url and signature to assume role
local assume_role_request_headers = {
Accept = "application/json",
["Content-Type"] = "application/x-www-form-urlencoded; charset=utf-8",
["X-Amz-Security-Token"] = session_token,
Host = sts_host
}

local assume_role_query_params = {
Action = "AssumeRole",
Version = "2011-06-15",
RoleArn = assume_role_arn,
DurationSeconds = DEFAULT_SESSION_DURATION_SECONDS,
RoleSessionName = role_session_name,
}

local assume_role_sign_params = {
region = aws_region,
service = "sts",
access_key = access_key,
secret_key = secret_key,
method = "GET",
host = sts_host,
port = 443,
headers = assume_role_request_headers,
query = utils.encode_args(assume_role_query_params)
}

local request, err
request, err = aws_v4(assume_role_sign_params)

if err then
return nil, 'Unable to build signature to assume role ['
.. assume_role_arn .. '] - error :'.. tostring(err)
end

-- Call STS to assume role
local client = http.new()
client:set_timeout(DEFAULT_HTTP_CLINET_TIMEOUT)
local res, err = client:request_uri(request.url, {
method = request.method,
headers = request.headers,
ssl_verify = false,
})

if err then
local err_s = 'Unable to assume role [' .. assume_role_arn .. ']' ..
' due to: ' .. tostring(err)
return nil, err_s
end

if res.status ~= 200 then
local err_s = 'Unable to assume role [' .. assume_role_arn .. '] due to:' ..
'status [' .. res.status .. '] - ' ..
'reason [' .. res.body .. ']'
return nil, err_s
end

local credentials = json.decode(res.body).AssumeRoleResponse.AssumeRoleResult.Credentials
local result = {
access_key = credentials.AccessKeyId,
secret_key = credentials.SecretAccessKey,
session_token = credentials.SessionToken,
expiration = credentials.Expiration
}

return result, nil, result.expiration - ngx_now()
end


return {
fetch_assume_role_credentials = fetch_assume_role_credentials,
}
9 changes: 9 additions & 0 deletions kong/plugins/aws-lambda/schema.lua
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,15 @@ return {
encrypted = true, -- Kong Enterprise-exclusive feature, does nothing in Kong CE
referenceable = true,
} },
{ aws_assume_role_arn = {
type = "string",
encrypted = true, -- Kong Enterprise-exclusive feature, does nothing in Kong CE
referenceable = true,
} },
{ aws_role_session_name = {
type = "string",
default = "kong",
} },
{ aws_region = typedefs.host },
{ function_name = {
type = "string",
Expand Down
72 changes: 72 additions & 0 deletions spec/03-plugins/27-aws-lambda/07-iam-sts-credentials_spec.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
require "spec.helpers"

describe("[AWS Lambda] iam-sts", function()

local fetch_sts_assume_role, http_responses

before_each(function()
package.loaded["kong.plugins.aws-lambda.iam-sts-credentials"] = nil
package.loaded["resty.http"] = nil
local http = require "resty.http"
-- mock the http module
http.new = function()
return {
set_timeout = function() end,
request_uri = function()
local body = http_responses[1]
table.remove(http_responses, 1)
return {
status = 200,
body = body,
}
end,
}
end
fetch_sts_assume_role = require("kong.plugins.aws-lambda.iam-sts-credentials").fetch_assume_role_credentials
end)

after_each(function()
end)

it("should fetch credentials from sts service", function()
http_responses = {
[[
{
"AssumeRoleResponse": {
"AssumeRoleResult": {
"SourceIdentity": "kong_session",
"AssumedRoleUser": {
"Arn": "arn:aws:iam::000000000001:role/temp-role",
"AssumedRoleId": "arn:aws:iam::000000000001:role/temp-role"
},
"Credentials": {
"AccessKeyId": "the Access Key",
"SecretAccessKey": "the Big Secret",
"SessionToken": "the Token of Appreciation",
"Expiration": 1552424170
},
"PackedPolicySize": 1000
},
"ResponseMetadata": {
"RequestId": "c6104cbe-af31-11e0-8154-cbc7ccf896c7"
}
}
}
]]
}

local aws_region = "ap-east-1"
local assume_role_arn = "arn:aws:iam::000000000001:role/temp-role"
local role_session_name = "kong_session"
local access_key = "test_access_key"
local secret_key = "test_secret_key"
local session_token = "test_session_token"
local iam_role_credentials, err = fetch_sts_assume_role(aws_region, assume_role_arn, role_session_name, access_key, secret_key, session_token)

assert.is_nil(err)
assert.equal("the Access Key", iam_role_credentials.access_key)
assert.equal("the Big Secret", iam_role_credentials.secret_key)
assert.equal("the Token of Appreciation", iam_role_credentials.session_token)
assert.equal(1552424170, iam_role_credentials.expiration)
end)
end)