Skip to content

Commit

Permalink
feat(aws-lambda): Allow IMDSv2 as protocol to retrieve EC2 instance role
Browse files Browse the repository at this point in the history
This adds optional support for the IMDSv2 protocol.  By default, the
aws-lambda plugin will use the IMDSv1 protocol to retrieve the
instance role.  Users can configure it to use IMDSv2 instead by
setting the configuration parameter `aws_imds_protocol_version` to
`v2`.  The default for the parameter is `v1` to ensure that
installations that run Kong inside of Docker on EC2 will continue to
work unchanged.

Signed-off-by: Ivan Savcic <isavcic@gmail.com>
  • Loading branch information
isavcic authored and Hans Hübner committed Dec 15, 2022
1 parent 71e5e3d commit d4585bc
Show file tree
Hide file tree
Showing 4 changed files with 87 additions and 12 deletions.
5 changes: 3 additions & 2 deletions kong/plugins/aws-lambda/handler.lua
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ local function fetch_aws_credentials(aws_conf)
end

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

if err then
return nil, err
Expand All @@ -52,7 +52,7 @@ local function fetch_aws_credentials(aws_conf)
metadata_credentials.session_token)

else
return fetch_metadata_credentials()
return fetch_metadata_credentials(aws_conf)
end
end

Expand Down Expand Up @@ -265,6 +265,7 @@ function AWSLambdaHandler:access(conf)
aws_region = conf.aws_region,
aws_assume_role_arn = conf.aws_assume_role_arn,
aws_role_session_name = conf.aws_role_session_name,
aws_imds_protocol_version = conf.aws_imds_protocol_version,
}

if not conf.aws_key then
Expand Down
58 changes: 50 additions & 8 deletions kong/plugins/aws-lambda/iam-ec2-credentials.lua
Original file line number Diff line number Diff line change
Expand Up @@ -9,29 +9,71 @@ local kong = kong
local METADATA_SERVICE_PORT = 80
local METADATA_SERVICE_REQUEST_TIMEOUT = 5000
local METADATA_SERVICE_HOST = "169.254.169.254"
local METADATA_SERVICE_URI = "http://" .. METADATA_SERVICE_HOST .. ":" .. METADATA_SERVICE_PORT ..
local METADATA_SERVICE_TOKEN_URI = "http://" .. METADATA_SERVICE_HOST .. ":" .. METADATA_SERVICE_PORT ..
"/latest/api/token"
local METADATA_SERVICE_IAM_URI = "http://" .. METADATA_SERVICE_HOST .. ":" .. METADATA_SERVICE_PORT ..
"/latest/meta-data/iam/security-credentials/"


local function fetch_ec2_credentials()
local function fetch_ec2_credentials(config)
local client = http.new()
client:set_timeout(METADATA_SERVICE_REQUEST_TIMEOUT)
local role_name_request_res, err = client:request_uri(METADATA_SERVICE_URI)

local protocol_version = config.aws_imds_protocol_version
local imds_session_headers

if protocol_version == "v1" then

-- When using IMSDv1, the role is retrieved with a simple GET
-- request requiring no special headers.
imds_session_headers = {}

elseif protocol_version == "v2" then

-- When using IMSDv1, the role is retrieved with a GET request
-- that has a valid X-aws-ec2-metadata-token header with a valid
-- token, which needs to be retrieved with a PUT request.
local token_request_res, err = client:request_uri(METADATA_SERVICE_TOKEN_URI, {
method = "PUT",
headers = {
["X-aws-ec2-metadata-token-ttl-seconds"] = "60",
},
})

if not token_request_res then
return nil, "Could not fetch IMDSv2 token from metadata service: " .. tostring(err)
end

if token_request_res.status ~= 200 then
return nil, "Fetching IMDSv2 token from metadata service returned status code " ..
token_request_res.status .. " with body " .. token_request_res.body
end
imds_session_headers = { ["X-aws-ec2-metadata-token"] = token_request_res.body }

else
return nil, "Unrecognized aws_imds_protocol_version " .. tostring(protocol_version) .. " set in configuration"
end

local role_name_request_res, err = client:request_uri(METADATA_SERVICE_IAM_URI, {
headers = imds_session_headers,
})

if not role_name_request_res then
return nil, "Could not fetch role name from metadata service: " .. tostring(err)
end

if role_name_request_res.status ~= 200 then
return nil, "Fetching role name from metadata service returned status code " ..
role_name_request_res.status .. " with body " .. role_name_request_res.body
role_name_request_res.status .. " with body " .. role_name_request_res.body
end

local iam_role_name = role_name_request_res.body

kong.log.debug("Found IAM role on instance with name: ", iam_role_name)

local iam_security_token_request, err = client:request_uri(METADATA_SERVICE_URI .. iam_role_name)
local iam_security_token_request, err = client:request_uri(METADATA_SERVICE_IAM_URI .. iam_role_name, {
headers = imds_session_headers,
})

if not iam_security_token_request then
return nil, "Failed to request IAM credentials for role " .. iam_role_name ..
" Request returned error: " .. tostring(err)
Expand Down Expand Up @@ -63,9 +105,9 @@ local function fetch_ec2_credentials()
end


local function fetchCredentialsLogged()
local function fetchCredentialsLogged(config)
-- wrapper to log any errors
local creds, err, ttl = fetch_ec2_credentials()
local creds, err, ttl = fetch_ec2_credentials(config)
if creds then
return creds, err, ttl
end
Expand Down
6 changes: 6 additions & 0 deletions kong/plugins/aws-lambda/schema.lua
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,12 @@ return {
type = "boolean",
default = true,
} },
{ aws_imds_protocol_version = {
type = "string",
required = true,
default = "v1",
one_of = { "v1", "v2" }
} },
}
},
} },
Expand Down
30 changes: 28 additions & 2 deletions spec/03-plugins/27-aws-lambda/03-iam-ec2-credentials_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ describe("[AWS Lambda] iam-ec2", function()
after_each(function()
end)

it("should fetch credentials from metadata service", function()
it("should fetch credentials from metadata service using IMDSv1", function()
http_responses = {
"EC2_role",
[[
Expand All @@ -44,7 +44,33 @@ describe("[AWS Lambda] iam-ec2", function()
]]
}

local iam_role_credentials, err = fetch_ec2()
local iam_role_credentials, err = fetch_ec2({ aws_imds_protocol_version = "v1" })

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)

it("should fetch credentials from metadata service using IMDSv2", function()
http_responses = {
"SOME-TOKEN",
"EC2_role",
[[
{
"Code" : "Success",
"LastUpdated" : "2019-03-12T14:20:45Z",
"Type" : "AWS-HMAC",
"AccessKeyId" : "the Access Key",
"SecretAccessKey" : "the Big Secret",
"Token" : "the Token of Appreciation",
"Expiration" : "2019-03-12T20:56:10Z"
}
]]
}

local iam_role_credentials, err = fetch_ec2({ aws_imds_protocol_version = "v2" })

assert.is_nil(err)
assert.equal("the Access Key", iam_role_credentials.access_key)
Expand Down

0 comments on commit d4585bc

Please sign in to comment.