Skip to content

Commit

Permalink
feat(aws-lambda) add support for assume role based on metadata creden…
Browse files Browse the repository at this point in the history
…tials (#8900)

This PR adds support for assuming role based on EC2/ECS metadata credentials.

Currently, the aws-lambda plugin does not support cross-account invoking. To achieve that we need to add support for assuming roles based on EC2/ECS metadata credentials. After fetching the metadata credentials,  it'll make an additional request to AWS's STS service to ask to assume role.

Codes in the `iam-sts-credentials.lua` originated from an [old PR](Kong/kong-plugin-aws-lambda#42), I re-arranged it and picked up part of the function that I need.

Note that this is a short term plan to support FTI-3291 as it does not modify much of the code. Hoping to catch the last train of Version 3.0. In the long term perspective, I think all the codes related to fetching AWS credentials should be rebuilt based on https://github.com/Kong/lua-resty-aws, the library has complete support for AWS environment credential functions(ENV vars, configs, etc.)
  • Loading branch information
windmgc authored Jun 6, 2022
1 parent b367611 commit 819d316
Show file tree
Hide file tree
Showing 5 changed files with 232 additions and 11 deletions.
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)

0 comments on commit 819d316

Please sign in to comment.