diff --git a/kong/plugins/aws-lambda/handler.lua b/kong/plugins/aws-lambda/handler.lua index 88141d3ab92..86b8dbb635a 100644 --- a/kong/plugins/aws-lambda/handler.lua +++ b/kong/plugins/aws-lambda/handler.lua @@ -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 @@ -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 @@ -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 diff --git a/kong/plugins/aws-lambda/iam-ec2-credentials.lua b/kong/plugins/aws-lambda/iam-ec2-credentials.lua index e57cab487a6..b212acf3c52 100644 --- a/kong/plugins/aws-lambda/iam-ec2-credentials.lua +++ b/kong/plugins/aws-lambda/iam-ec2-credentials.lua @@ -9,14 +9,54 @@ 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) @@ -24,14 +64,16 @@ local function fetch_ec2_credentials() 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) @@ -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 diff --git a/kong/plugins/aws-lambda/schema.lua b/kong/plugins/aws-lambda/schema.lua index 96829ca96d7..cf91857c62e 100644 --- a/kong/plugins/aws-lambda/schema.lua +++ b/kong/plugins/aws-lambda/schema.lua @@ -95,6 +95,12 @@ return { type = "boolean", default = true, } }, + { aws_imds_protocol_version = { + type = "string", + required = true, + default = "v1", + one_of = { "v1", "v2" } + } }, } }, } }, diff --git a/spec/03-plugins/27-aws-lambda/03-iam-ec2-credentials_spec.lua b/spec/03-plugins/27-aws-lambda/03-iam-ec2-credentials_spec.lua index 6e385347cd6..499ee165804 100644 --- a/spec/03-plugins/27-aws-lambda/03-iam-ec2-credentials_spec.lua +++ b/spec/03-plugins/27-aws-lambda/03-iam-ec2-credentials_spec.lua @@ -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", [[ @@ -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)