From 82a670623414f900332d27f854e17a29abff0890 Mon Sep 17 00:00:00 2001 From: windmgc Date: Mon, 19 Feb 2024 10:31:35 +0800 Subject: [PATCH 1/5] feat: container credential provider support auth token --- .../04-RemoteCredentials_spec.lua | 38 +++++++++++++++++++ src/resty/aws/config.lua | 15 ++++++++ .../aws/credentials/RemoteCredentials.lua | 19 ++++++++++ 3 files changed, 72 insertions(+) diff --git a/spec/03-credentials/04-RemoteCredentials_spec.lua b/spec/03-credentials/04-RemoteCredentials_spec.lua index bee8625..1419c43 100644 --- a/spec/03-credentials/04-RemoteCredentials_spec.lua +++ b/spec/03-credentials/04-RemoteCredentials_spec.lua @@ -4,6 +4,7 @@ local restore = require "spec.helpers" -- Mock for HTTP client local response = {} -- override in tests +local http_records = {} -- record requests for assertions local http = { new = function() return { @@ -12,8 +13,10 @@ local http = { set_timeouts = function() return true end, request = function(self, opts) if opts.path == "/test/path" then + table.insert(http_records, opts) return { -- the response for the credentials status = (response or {}).status or 200, + headers = opts and opts.headers or {}, read_body = function() return json.encode { AccessKeyId = (response or {}).AccessKeyId or "access", SecretAccessKey = (response or {}).SecretAccessKey or "secret", @@ -30,6 +33,12 @@ local http = { end, } +local pl_utils = { + readfile = function() + return "testtokenabc123" + end +} + describe("RemoteCredentials", function() @@ -85,3 +94,32 @@ describe("RemoteCredentials with customized full URI", function () assert.equal("token", token) end) end) + +describe("RemoteCredentials with full URI and token file", function () + it("fetches credentials", function () + local RemoteCredentials + + restore() + restore.setenv("AWS_CONTAINER_CREDENTIALS_FULL_URI", "http://localhost:12345/test/path") + restore.setenv("AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE", "/var/run/secrets/pods.eks.amazonaws.com/serviceaccount/eks-pod-identity-token") + + local _ = require("resty.aws.config").global -- load config before mocking http client + package.loaded["resty.luasocket.http"] = http + package.loaded["pl.utils"] = pl_utils + + RemoteCredentials = require "resty.aws.credentials.RemoteCredentials" + finally(function() + restore() + end) + + local cred = RemoteCredentials:new() + local success, key, secret, token = cred:get() + assert.equal(true, success) + assert.equal("access", key) + assert.equal("secret", secret) + assert.equal("token", token) + + assert.not_nil(http_records[#http_records].headers) + assert.equal(http_records[#http_records].headers["Authorization"], "testtokenabc123") + end) +end) diff --git a/src/resty/aws/config.lua b/src/resty/aws/config.lua index 61e390f..cc53844 100644 --- a/src/resty/aws/config.lua +++ b/src/resty/aws/config.lua @@ -57,6 +57,8 @@ -- * `AMAZON_SESSION_TOKEN` -- * `AWS_CONTAINER_CREDENTIALS_RELATIVE_URI` -- * `AWS_CONTAINER_CREDENTIALS_FULL_URI` +-- * `AWS_CONTAINER_AUTHORIZATION_TOKEN` +-- * `AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE` local pl_path = require "pl.path" @@ -140,6 +142,19 @@ local env_vars = { -- Variables used in RemoteCredentials (and in the CredentialProviderChain) AWS_CONTAINER_CREDENTIALS_RELATIVE_URI = { name = "AWS_CONTAINER_CREDENTIALS_RELATIVE_URI", default = nil }, AWS_CONTAINER_CREDENTIALS_FULL_URI = { name = "AWS_CONTAINER_CREDENTIALS_FULL_URI", default = nil }, + -- Token related Variables used in RemoteCredentials + -- Note that AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE has higher priority than AWS_CONTAINER_AUTHORIZATION_TOKEN + -- if both are set, the value in AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE will be used + -- + -- This is also used by EKS Pod Identity authorization + AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE = { name = "AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE", default = nil }, + -- TODO: --- + -- A possible issue is that due to Nginx worker process's envvars isolation + -- the AWS_CONTAINER_AUTHORIZATION_TOKEN may not get refreshed. + -- According to the AWS documentation, the AWS_CONTAINER_AUTHORIZATION_TOKEN is only + -- used in IoT product Greengrass, which is not a common use case. + -- AWS_CONTAINER_AUTHORIZATION_TOKEN = { name = "AWS_CONTAINER_AUTHORIZATION_TOKEN", default = nil }, + -- --------- -- HTTP/HTTPs proxy settings HTTP_PROXY = { name = "http_proxy", default = nil }, diff --git a/src/resty/aws/credentials/RemoteCredentials.lua b/src/resty/aws/credentials/RemoteCredentials.lua index a3a7ac1..72b1ac9 100644 --- a/src/resty/aws/credentials/RemoteCredentials.lua +++ b/src/resty/aws/credentials/RemoteCredentials.lua @@ -13,9 +13,11 @@ local DEFAULT_SERVICE_REQUEST_TIMEOUT = 5000 local url = require "socket.url" local http = require "resty.luasocket.http" local json = require "cjson" +local readfile = require("pl.utils").readfile local FullUri +local AuthTokenFile local function initialize() @@ -76,6 +78,11 @@ local function initialize() ({ http = 80, https = 443 })[FullUri.scheme] end + -- get auth token file path + if aws_config.global.AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE then + AuthTokenFile = aws_config.global.AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE + end + initialize = nil end @@ -107,6 +114,17 @@ function RemoteCredentials:refresh() return nil, "No URI environment variables found for RemoteCredentials" end + + local headers = {} + if AuthTokenFile then + local token, err = readfile(AuthTokenFile) + if not token then + return nil, "Failed reading AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE: " .. err + end + + headers["Authorization"] = token + end + local client = http.new() client:set_timeout(DEFAULT_SERVICE_REQUEST_TIMEOUT) @@ -122,6 +140,7 @@ function RemoteCredentials:refresh() local response, err = client:request { method = "GET", path = FullUri.path, + headers = headers, } if not response then From fc1c9225f2e81db6d5083324665aadf11d127f94 Mon Sep 17 00:00:00 2001 From: windmgc Date: Mon, 19 Feb 2024 15:13:24 +0800 Subject: [PATCH 2/5] add another allowed hostname for Pod Identity --- src/resty/aws/credentials/RemoteCredentials.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/resty/aws/credentials/RemoteCredentials.lua b/src/resty/aws/credentials/RemoteCredentials.lua index 72b1ac9..d51d19a 100644 --- a/src/resty/aws/credentials/RemoteCredentials.lua +++ b/src/resty/aws/credentials/RemoteCredentials.lua @@ -33,7 +33,7 @@ local function initialize() local FULL_URI_UNRESTRICTED_PROTOCOLS = makeset { "https" } local FULL_URI_ALLOWED_PROTOCOLS = makeset { "http", "https" } - local FULL_URI_ALLOWED_HOSTNAMES = makeset { "localhost", "127.0.0.1" } + local FULL_URI_ALLOWED_HOSTNAMES = makeset { "localhost", "127.0.0.1", "169.254.170.23" } local RELATIVE_URI_HOST = '169.254.170.2' local function getFullUri() From fe547817cb992acc98818011b36f6ce9b7ce27ea Mon Sep 17 00:00:00 2001 From: windmgc Date: Mon, 26 Feb 2024 16:38:58 +0800 Subject: [PATCH 3/5] fix: add token env --- .../04-RemoteCredentials_spec.lua | 31 +++++++++++++++++++ src/resty/aws/config.lua | 8 +---- .../aws/credentials/RemoteCredentials.lua | 11 +++++++ 3 files changed, 43 insertions(+), 7 deletions(-) diff --git a/spec/03-credentials/04-RemoteCredentials_spec.lua b/spec/03-credentials/04-RemoteCredentials_spec.lua index 1419c43..1d9dfc5 100644 --- a/spec/03-credentials/04-RemoteCredentials_spec.lua +++ b/spec/03-credentials/04-RemoteCredentials_spec.lua @@ -123,3 +123,34 @@ describe("RemoteCredentials with full URI and token file", function () assert.equal(http_records[#http_records].headers["Authorization"], "testtokenabc123") end) end) + +describe("RemoteCredentials with full URI and token and token file, file takes higher precedence", function () + it("fetches credentials", function () + local RemoteCredentials + + restore() + restore.setenv("AWS_CONTAINER_CREDENTIALS_FULL_URI", "http://localhost:12345/test/path") + restore.setenv("AWS_CONTAINER_AUTHORIZATION_TOKEN", "testtoken") + restore.setenv("AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE", "/var/run/secrets/pods.eks.amazonaws.com/serviceaccount/eks-pod-identity-token") + + local _ = require("resty.aws.config").global -- load config before mocking http client + package.loaded["resty.luasocket.http"] = http + package.loaded["pl.utils"] = pl_utils + + RemoteCredentials = require "resty.aws.credentials.RemoteCredentials" + finally(function() + restore() + end) + + local cred = RemoteCredentials:new() + local success, key, secret, token = cred:get() + assert.equal(true, success) + assert.equal("access", key) + assert.equal("secret", secret) + assert.equal("token", token) + + assert.not_nil(http_records[#http_records].headers) + assert.equal(http_records[#http_records].headers["Authorization"], "testtokenabc123") + end) +end) + diff --git a/src/resty/aws/config.lua b/src/resty/aws/config.lua index cc53844..d2942d5 100644 --- a/src/resty/aws/config.lua +++ b/src/resty/aws/config.lua @@ -147,14 +147,8 @@ local env_vars = { -- if both are set, the value in AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE will be used -- -- This is also used by EKS Pod Identity authorization + AWS_CONTAINER_AUTHORIZATION_TOKEN = { name = "AWS_CONTAINER_AUTHORIZATION_TOKEN", default = nil }, AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE = { name = "AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE", default = nil }, - -- TODO: --- - -- A possible issue is that due to Nginx worker process's envvars isolation - -- the AWS_CONTAINER_AUTHORIZATION_TOKEN may not get refreshed. - -- According to the AWS documentation, the AWS_CONTAINER_AUTHORIZATION_TOKEN is only - -- used in IoT product Greengrass, which is not a common use case. - -- AWS_CONTAINER_AUTHORIZATION_TOKEN = { name = "AWS_CONTAINER_AUTHORIZATION_TOKEN", default = nil }, - -- --------- -- HTTP/HTTPs proxy settings HTTP_PROXY = { name = "http_proxy", default = nil }, diff --git a/src/resty/aws/credentials/RemoteCredentials.lua b/src/resty/aws/credentials/RemoteCredentials.lua index d51d19a..16b3418 100644 --- a/src/resty/aws/credentials/RemoteCredentials.lua +++ b/src/resty/aws/credentials/RemoteCredentials.lua @@ -17,6 +17,7 @@ local readfile = require("pl.utils").readfile local FullUri +local AuthToken local AuthTokenFile @@ -78,6 +79,11 @@ local function initialize() ({ http = 80, https = 443 })[FullUri.scheme] end + -- get auth token + if aws_config.global.AWS_CONTAINER_AUTHORIZATION_TOKEN then + AuthToken = aws_config.global.AWS_CONTAINER_AUTHORIZATION_TOKEN + end + -- get auth token file path if aws_config.global.AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE then AuthTokenFile = aws_config.global.AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE @@ -116,6 +122,11 @@ function RemoteCredentials:refresh() local headers = {} + + if AuthToken then + headers["Authorization"] = AuthToken + end + if AuthTokenFile then local token, err = readfile(AuthTokenFile) if not token then From 8ef9c77754ffea58d57d773d88d60b40e70a43ef Mon Sep 17 00:00:00 2001 From: windmgc Date: Mon, 26 Feb 2024 16:52:09 +0800 Subject: [PATCH 4/5] docs: add changelog --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 404dd90..610c42d 100644 --- a/README.md +++ b/README.md @@ -185,6 +185,9 @@ Release process: - security: remove the documentation entry that contains a sample access key from AWS SDK. This avoids false postive vulnerability report. [102](https://github.com/Kong/lua-resty-aws/pull/102) +- feat: container credential provider now supports using auth token defined in + AWS_CONTAINER_AUTHORIZATION_TOKEN and AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE. + [107](https://github.com/Kong/lua-resty-aws/pull/107) ### 1.3.6 (25-Dec-2023) From 132762f8cb0f361beb416d649b5d992dc7a4e471 Mon Sep 17 00:00:00 2001 From: windmgc Date: Wed, 28 Feb 2024 13:58:49 +0800 Subject: [PATCH 5/5] apply suggestions --- src/resty/aws/credentials/RemoteCredentials.lua | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/src/resty/aws/credentials/RemoteCredentials.lua b/src/resty/aws/credentials/RemoteCredentials.lua index 16b3418..5e7c27f 100644 --- a/src/resty/aws/credentials/RemoteCredentials.lua +++ b/src/resty/aws/credentials/RemoteCredentials.lua @@ -79,15 +79,9 @@ local function initialize() ({ http = 80, https = 443 })[FullUri.scheme] end - -- get auth token - if aws_config.global.AWS_CONTAINER_AUTHORIZATION_TOKEN then - AuthToken = aws_config.global.AWS_CONTAINER_AUTHORIZATION_TOKEN - end - - -- get auth token file path - if aws_config.global.AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE then - AuthTokenFile = aws_config.global.AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE - end + -- get auth token/file + AuthToken = aws_config.global.AWS_CONTAINER_AUTHORIZATION_TOKEN + AuthTokenFile = aws_config.global.AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE initialize = nil end