Skip to content

Commit aa62e0d

Browse files
committed
fix: HOME fallback for custom container user
1 parent d930866 commit aa62e0d

File tree

3 files changed

+104
-1
lines changed

3 files changed

+104
-1
lines changed

cmd/localstack/main.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,8 @@ func main() {
170170
}
171171
}
172172

173+
EnsureHome()
174+
173175
// file watcher for hot-reloading
174176
fileWatcherContext, cancelFileWatcher := context.WithCancel(context.Background())
175177

cmd/localstack/user.go

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,13 @@ package main
33

44
import (
55
"fmt"
6-
log "github.com/sirupsen/logrus"
76
"os"
87
"os/user"
98
"strconv"
109
"strings"
1110
"syscall"
11+
12+
log "github.com/sirupsen/logrus"
1213
)
1314

1415
// AddUser adds a UNIX user (e.g., sbx_user1051) to the passwd and shadow files if not already present
@@ -82,6 +83,21 @@ func UserLogger() *log.Entry {
8283
})
8384
}
8485

86+
// EnsureHome sets HOME=/tmp if the current process has no /etc/passwd entry.
87+
// UnsetLsEnvs strips HOME for AWS parity, which is fine in the normal
88+
// root-start flow where AddUser has written a passwd entry. But when the
89+
// container is launched with --user=1000:1000, AddUser is never called and
90+
// Node's os.homedir() / AWS SDK config loading fail with ENOENT.
91+
func EnsureHome() {
92+
if _, err := user.Current(); err != nil {
93+
if setErr := os.Setenv("HOME", "/tmp"); setErr != nil {
94+
log.Warnln("Could not set HOME=/tmp for non-passwd user:", setErr)
95+
} else {
96+
log.Debugln("No /etc/passwd entry for current UID; HOME set to /tmp")
97+
}
98+
}
99+
}
100+
85101
// DropPrivileges switches to another UNIX user by dropping root privileges
86102
// Initially based on https://stackoverflow.com/a/75545491/6875981
87103
func DropPrivileges(userToSwitchTo string) error {

test-homedir.md

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
# Test: Home Directory Fix (Node.js 22, --user=1000:1000)
2+
3+
## Step 1 — Build the binary
4+
5+
```bash
6+
make compile-with-docker
7+
# produces bin/aws-lambda-rie-x86_64
8+
```
9+
10+
## Step 2 — Create the function file
11+
12+
```bash
13+
mkdir -p /tmp/fn-homedir
14+
cat > /tmp/fn-homedir/index.js << 'EOF'
15+
exports.handler = async () => {
16+
const os = require("os");
17+
const homeEnv = process.env.HOME;
18+
const debug = {
19+
HOME_env: homeEnv === undefined ? null : homeEnv,
20+
has_HOME_key: Object.prototype.hasOwnProperty.call(process.env, "HOME"),
21+
};
22+
try {
23+
const h = os.homedir();
24+
return { statusCode: 200, body: JSON.stringify({ ok: true, ...debug, homedir: h }) };
25+
} catch (e) {
26+
return {
27+
statusCode: 500,
28+
body: JSON.stringify({
29+
ok: false, ...debug,
30+
name: e.name, code: e.code, message: e.message,
31+
syscall: e.syscall, errno: e.errno, info: e.info,
32+
}),
33+
};
34+
}
35+
};
36+
EOF
37+
```
38+
39+
## Step 3 — Start LocalStack with your binary and --user=1000:1000
40+
41+
The binary path must be mounted into the LocalStack container via `DOCKER_FLAGS`, then
42+
referenced via `LAMBDA_INIT_BIN_PATH` using the in-container path.
43+
44+
```bash
45+
DOCKER_FLAGS="-v $(pwd)/bin:/lambda-bin" \
46+
LAMBDA_DOCKER_FLAGS="--user=1000:1000" \
47+
LAMBDA_INIT_BIN_PATH=/lambda-bin/aws-lambda-rie-x86_64 \
48+
localstack start -d
49+
50+
localstack wait -t 60
51+
```
52+
53+
> **Note:** LocalStack warns to use `LOCALSTACK_`-prefixed env vars — both forms work.
54+
55+
## Step 4 — Deploy and invoke
56+
57+
```bash
58+
# Zip the function
59+
cd /tmp/fn-homedir && zip function.zip index.js && cd -
60+
61+
# Create the function
62+
awslocal lambda create-function \
63+
--function-name homedir-test \
64+
--runtime nodejs22.x \
65+
--handler index.handler \
66+
--role arn:aws:iam::000000000000:role/lambda-role \
67+
--zip-file fileb:///tmp/fn-homedir/function.zip
68+
69+
# Wait for it to be active
70+
awslocal lambda wait function-active --function-name homedir-test
71+
72+
# Invoke and pretty-print the response
73+
awslocal lambda invoke \
74+
--function-name homedir-test \
75+
--payload '{}' \
76+
/tmp/fn-homedir/response.json && cat /tmp/fn-homedir/response.json
77+
```
78+
79+
## Expected output (with the EnsureHome() fix)
80+
81+
```json
82+
{"statusCode":200,"body":"{\"ok\":true,\"HOME_env\":\"/tmp\",\"has_HOME_key\":true,\"homedir\":\"/tmp\"}"}
83+
```
84+
85+
Without the fix you'd get `statusCode: 500` with `"code":"ENOENT"`.

0 commit comments

Comments
 (0)