Skip to content

Commit fad696b

Browse files
authored
fix(*): patch expanduser to be more friendly to OpenResty environment (#111)
pl.path.expanduser may return with nil, err when received a path argument starting with ~ but $HOME(or other home path related) environment variable is not present. This PR adds proper error handling code when calling expanduser.
1 parent 1f3b8bb commit fad696b

File tree

5 files changed

+125
-18
lines changed

5 files changed

+125
-18
lines changed

README.md

+6
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,12 @@ Release process:
176176
1. upload using: `VERSION=x.y.z APIKEY=abc... make upload`
177177
1. test installing the rock from LuaRocks
178178

179+
180+
### Unreleased
181+
182+
- fix: patch expanduser function to be more friendly to OpenResty environment
183+
[111](https://github.com/Kong/lua-resty-aws/pull/111)
184+
179185
### 1.4.0 (20-Mar-2024)
180186

181187
- fix: aws configuration cannot be loaded due to pl.path cannot resolve the path started with ~

spec/01-generic/01-config_spec.lua

+1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ describe("config loader", function()
1919
local config
2020
before_each(function()
2121
restore()
22+
restore.setenv("HOME")
2223
pl_utils.writefile(config_filename, config_info)
2324
end)
2425

spec/03-credentials/04-RemoteCredentials_spec.lua

+27-8
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
local json = require("cjson.safe").new()
22
local restore = require "spec.helpers"
33

4+
local old_pl_utils = require("pl.utils")
45

56
-- Mock for HTTP client
67
local response = {} -- override in tests
@@ -33,18 +34,17 @@ local http = {
3334
end,
3435
}
3536

36-
local pl_utils = {
37-
readfile = function()
38-
return "testtokenabc123"
39-
end
40-
}
41-
4237

4338
describe("RemoteCredentials", function()
4439

4540
local RemoteCredentials
41+
local pl_utils_readfile = old_pl_utils.readfile
4642

4743
before_each(function()
44+
pl_utils_readfile = old_pl_utils.readfile
45+
old_pl_utils.readfile = function()
46+
return "testtokenabc123"
47+
end
4848
restore()
4949
restore.setenv("AWS_CONTAINER_CREDENTIALS_FULL_URI", "https://localhost/test/path")
5050

@@ -55,6 +55,7 @@ describe("RemoteCredentials", function()
5555
end)
5656

5757
after_each(function()
58+
old_pl_utils.readfile = pl_utils_readfile
5859
restore()
5960
end)
6061

@@ -96,6 +97,16 @@ describe("RemoteCredentials with customized full URI", function ()
9697
end)
9798

9899
describe("RemoteCredentials with full URI and token file", function ()
100+
local pl_utils_readfile
101+
before_each(function()
102+
pl_utils_readfile = old_pl_utils.readfile
103+
old_pl_utils.readfile = function()
104+
return "testtokenabc123"
105+
end
106+
end)
107+
after_each(function()
108+
old_pl_utils.readfile = pl_utils_readfile
109+
end)
99110
it("fetches credentials", function ()
100111
local RemoteCredentials
101112

@@ -105,7 +116,6 @@ describe("RemoteCredentials with full URI and token file", function ()
105116

106117
local _ = require("resty.aws.config").global -- load config before mocking http client
107118
package.loaded["resty.luasocket.http"] = http
108-
package.loaded["pl.utils"] = pl_utils
109119

110120
RemoteCredentials = require "resty.aws.credentials.RemoteCredentials"
111121
finally(function()
@@ -125,6 +135,16 @@ describe("RemoteCredentials with full URI and token file", function ()
125135
end)
126136

127137
describe("RemoteCredentials with full URI and token and token file, file takes higher precedence", function ()
138+
local pl_utils_readfile
139+
before_each(function()
140+
pl_utils_readfile = old_pl_utils.readfile
141+
old_pl_utils.readfile = function()
142+
return "testtokenabc123"
143+
end
144+
end)
145+
after_each(function()
146+
old_pl_utils.readfile = pl_utils_readfile
147+
end)
128148
it("fetches credentials", function ()
129149
local RemoteCredentials
130150

@@ -135,7 +155,6 @@ describe("RemoteCredentials with full URI and token and token file, file takes h
135155

136156
local _ = require("resty.aws.config").global -- load config before mocking http client
137157
package.loaded["resty.luasocket.http"] = http
138-
package.loaded["pl.utils"] = pl_utils
139158

140159
RemoteCredentials = require "resty.aws.credentials.RemoteCredentials"
141160
finally(function()

spec/03-credentials/08-SharedFileCredentials_spec.lua

+6-2
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,19 @@
11
local pl_path = require "pl.path"
22
local pl_config = require "pl.config"
33
local tbl_clear = require "table.clear"
4+
local restore = require "spec.helpers"
45

56
local hooked_file = {}
67

78
local origin_read = pl_config.read
89
local origin_isfile = pl_path.isfile
910

1011
pl_config.read = function(name, ...)
11-
return hooked_file[pl_path.expanduser(name)] or origin_read(name, ...)
12+
return hooked_file[name] or origin_read(name, ...)
1213
end
1314

1415
pl_path.isfile = function(name)
15-
return hooked_file[pl_path.expanduser(name)] and true or origin_isfile(name)
16+
return hooked_file[name] and true or origin_isfile(name)
1617
end
1718

1819
local function hook_config_file(name, content)
@@ -24,12 +25,15 @@ describe("SharedFileCredentials_spec", function()
2425
local SharedFileCredentials_spec
2526

2627
before_each(function()
28+
-- make ci happy
29+
restore.setenv("HOME", "/home/ci-test")
2730
local _ = require("resty.aws.config").global -- load config before anything else
2831

2932
SharedFileCredentials_spec = require "resty.aws.credentials.SharedFileCredentials"
3033
end)
3134

3235
after_each(function()
36+
restore()
3337
tbl_clear(hooked_file)
3438
end)
3539

src/resty/aws/config.lua

+85-8
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,12 @@
6363

6464
local pl_path = require "pl.path"
6565
local pl_config = require "pl.config"
66+
local pl_utils = require 'pl.utils'
6667

68+
local sub = string.sub
69+
local os_getenv = os.getenv
70+
local assert_string = pl_utils.assert_string
71+
local is_windows = pl_utils.is_windows
6772

6873

6974
-- Convention: variable values are stored in the config table by the name of
@@ -154,6 +159,14 @@ local env_vars = {
154159
HTTP_PROXY = { name = "http_proxy", default = nil },
155160
HTTPS_PROXY = { name = "https_proxy", default = nil },
156161
NO_PROXY = { name = "no_proxy", default = nil },
162+
163+
-- Environment variables for expanding user home path
164+
-- Nix specific
165+
HOME = { name = "HOME", default = nil },
166+
-- Windows specific
167+
USERPROFILE = { name = "USERPROFILE", default = nil },
168+
HOMEPATH = { name = "HOMEPATH", default = nil },
169+
HOMEDRIVE = { name = "HOMEDRIVE", default = nil },
157170
}
158171

159172
-- populate the env vars with their values, or defaults
@@ -173,18 +186,72 @@ local config = {
173186
env_vars = env_vars
174187
}
175188

189+
190+
--- Returns the environment variable value or the cached
191+
--- environment variable value in the `env_vars` table.
192+
-- @string var_name The environment variable name
193+
-- @treturn[1] string The environment variable value or the cached value in `env_vars` table
194+
-- @treturn[2] nil If the environment variable is not set and the cached value is not available
195+
local function getenv(var_name)
196+
return os_getenv(var_name) or env_vars[var_name].value
197+
end
198+
199+
200+
--- Replace a starting '~' with the user's home directory.
201+
--- This is a patched version of the original `pl.path.expanduser` function.
202+
--- In lua-resty-aws the environment variables must be fetched in `init_phase`
203+
--- So we need to cache those home path related values and fall back to them
204+
--- if expanduser function failed to fetch the environment variables.
205+
-- @string P A file path
206+
-- @treturn[1] string The file path with the `~` prefix substituted, or the input path if it had no prefix.
207+
-- @treturn[2] nil
208+
-- @treturn[2] string Error message if the environment variables were unavailable.
209+
local function expanduser(P)
210+
assert_string(1,P)
211+
if P:sub(1,1) ~= '~' then
212+
return P
213+
end
214+
215+
local home = getenv('HOME')
216+
if (not home) and (not is_windows) then
217+
-- no more options to try on Nix
218+
return nil, "failed to expand '~' (HOME not set)"
219+
end
220+
221+
if (not home) then
222+
-- try alternatives on Windows
223+
home = getenv('USERPROFILE')
224+
if not home then
225+
local hd = getenv('HOMEDRIVE')
226+
local hp = getenv('HOMEPATH')
227+
if not (hd and hp) then
228+
return nil, "failed to expand '~' (HOME, USERPROFILE, and HOMEDRIVE and/or HOMEPATH not set)"
229+
end
230+
home = hd..hp
231+
end
232+
end
233+
234+
return home..sub(P,2)
235+
end
236+
176237
do
177238
-- load a config file. If section given returns section only, otherwise full file.
178239
-- returns an empty table if the section does not exist
179240
local function load_file(filename, section)
180241
assert(type(filename) == "string", "expected filename to be a string")
181-
if not pl_path.isfile(pl_path.expanduser(filename)) then
242+
243+
local expanded_filename, err = expanduser(filename)
244+
if not expanded_filename then
245+
return nil, "failed expanding path '"..filename.."': "..tostring(err)
246+
end
247+
248+
if not pl_path.isfile(expanded_filename) then
182249
return nil, "not a file: '"..filename.."'"
183250
end
184251

185-
local contents, err = pl_config.read(filename, { variabilize = false })
252+
local contents, err = pl_config.read(expanded_filename, { variabilize = false })
186253
if not contents then
187-
return nil, "failed reading file '"..filename.."': "..tostring(err)
254+
return nil, "failed reading file '"..filename.."'(expanded: '"..expanded_filename.."'): "..tostring(err)
188255
end
189256

190257
if not section then
@@ -193,11 +260,11 @@ do
193260

194261
assert(type(section) == "string", "expected section to be a string or falsy")
195262
if not contents[section] then
196-
ngx.log(ngx.DEBUG, "section '",section,"' does not exist in file '",filename,"'")
263+
ngx.log(ngx.DEBUG, "section '",section,"' does not exist in file '",filename,"'(expanded: '"..expanded_filename.."')")
197264
return {}
198265
end
199266

200-
ngx.log(ngx.DEBUG, "loaded section '",section,"' from file '",filename,"'")
267+
ngx.log(ngx.DEBUG, "loaded section '",section,"' from file '",filename,"'(expanded: '"..expanded_filename.."')")
201268
return contents[section]
202269
end
203270

@@ -237,7 +304,9 @@ end
237304
-- table if the config file does not exist.
238305
-- @return options table as gotten from the configuration file, or nil+err.
239306
function config.load_config()
240-
if not pl_path.isfile(pl_path.expanduser(env_vars.AWS_CONFIG_FILE.value)) then
307+
local expanded_path, err = expanduser(env_vars.AWS_CONFIG_FILE.value)
308+
if not (expanded_path and pl_path.isfile(expanded_path)) then
309+
ngx.log(ngx.DEBUG, "failed to expand config file path or file does not exist: ", err)
241310
-- file doesn't exist
242311
return {}
243312
end
@@ -252,11 +321,15 @@ end
252321
-- @return credentials table as gotten from the credentials file, or a table
253322
-- with the key, id, and token from the configuration file, table can be empty.
254323
function config.load_credentials()
255-
if pl_path.isfile(pl_path.expanduser(env_vars.AWS_SHARED_CREDENTIALS_FILE.value)) then
324+
local expanded_path, err = expanduser(env_vars.AWS_SHARED_CREDENTIALS_FILE.value)
325+
if expanded_path and pl_path.isfile(expanded_path) then
256326
local creds = config.load_credentials_file(env_vars.AWS_SHARED_CREDENTIALS_FILE.value, env_vars.AWS_PROFILE.value)
257327
if creds then -- ignore error, already logged
258328
return creds
259329
end
330+
331+
else
332+
ngx.log(ngx.DEBUG, "failed to expand credential file path or file does not exist: ", err)
260333
end
261334

262335
-- fall back to config file
@@ -288,7 +361,8 @@ end
288361
function config.get_config()
289362
local cfg = config.load_config() or {} -- ignore error, already logged
290363

291-
if pl_path.isfile(pl_path.expanduser(env_vars.AWS_SHARED_CREDENTIALS_FILE.value)) then
364+
local expanded_path, err = expanduser(env_vars.AWS_SHARED_CREDENTIALS_FILE.value)
365+
if expanded_path and pl_path.isfile(expanded_path) then
292366
-- there is a creds file, so override creds with creds file
293367
local creds = config.load_credentials_file(
294368
env_vars.AWS_SHARED_CREDENTIALS_FILE.value, env_vars.AWS_PROFILE.value) -- ignore error, already logged
@@ -297,6 +371,9 @@ function config.get_config()
297371
cfg.aws_secret_access_key = creds.aws_secret_access_key
298372
cfg.aws_session_token = creds.aws_session_token
299373
end
374+
375+
else
376+
ngx.log(ngx.DEBUG, "failed to expand credential file path or file does not exist: ", err)
300377
end
301378

302379
-- add environment variables

0 commit comments

Comments
 (0)