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) diff --git a/spec/03-credentials/04-RemoteCredentials_spec.lua b/spec/03-credentials/04-RemoteCredentials_spec.lua index bee8625..1d9dfc5 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,63 @@ 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) + +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 61e390f..d2942d5 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,13 @@ 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 = { name = "AWS_CONTAINER_AUTHORIZATION_TOKEN", default = nil }, + AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE = { name = "AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE", 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..5e7c27f 100644 --- a/src/resty/aws/credentials/RemoteCredentials.lua +++ b/src/resty/aws/credentials/RemoteCredentials.lua @@ -13,9 +13,12 @@ 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 AuthToken +local AuthTokenFile local function initialize() @@ -31,7 +34,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() @@ -76,6 +79,10 @@ local function initialize() ({ http = 80, https = 443 })[FullUri.scheme] 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 @@ -107,6 +114,22 @@ function RemoteCredentials:refresh() return nil, "No URI environment variables found for RemoteCredentials" end + + local headers = {} + + if AuthToken then + headers["Authorization"] = AuthToken + end + + 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 +145,7 @@ function RemoteCredentials:refresh() local response, err = client:request { method = "GET", path = FullUri.path, + headers = headers, } if not response then