Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions cmd/localstack/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,8 @@ func main() {
}
}

EnsureHome()

// file watcher for hot-reloading
fileWatcherContext, cancelFileWatcher := context.WithCancel(context.Background())

Expand Down
18 changes: 17 additions & 1 deletion cmd/localstack/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@ package main

import (
"fmt"
log "github.com/sirupsen/logrus"
"os"
"os/user"
"strconv"
"strings"
"syscall"

log "github.com/sirupsen/logrus"
)

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

// EnsureHome sets HOME=/tmp if the current process has no /etc/passwd entry.
// UnsetLsEnvs strips HOME for AWS parity, which is fine in the normal
// root-start flow where AddUser has written a passwd entry. But when the
// container is launched with --user=1000:1000, AddUser is never called and
// Node's os.homedir() / AWS SDK config loading fail with ENOENT.
func EnsureHome() {
if _, err := user.Current(); err != nil {
if setErr := os.Setenv("HOME", "/tmp"); setErr != nil {
log.Warnln("Could not set HOME=/tmp for non-passwd user:", setErr)
} else {
log.Debugln("No /etc/passwd entry for current UID; HOME set to /tmp")
}
}
}

// DropPrivileges switches to another UNIX user by dropping root privileges
// Initially based on https://stackoverflow.com/a/75545491/6875981
func DropPrivileges(userToSwitchTo string) error {
Expand Down
85 changes: 85 additions & 0 deletions test-homedir.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# Test: Home Directory Fix (Node.js 22, --user=1000:1000)

## Step 1 — Build the binary

```bash
make compile-with-docker
# produces bin/aws-lambda-rie-x86_64
```

## Step 2 — Create the function file

```bash
mkdir -p /tmp/fn-homedir
cat > /tmp/fn-homedir/index.js << 'EOF'
exports.handler = async () => {
const os = require("os");
const homeEnv = process.env.HOME;
const debug = {
HOME_env: homeEnv === undefined ? null : homeEnv,
has_HOME_key: Object.prototype.hasOwnProperty.call(process.env, "HOME"),
};
try {
const h = os.homedir();
return { statusCode: 200, body: JSON.stringify({ ok: true, ...debug, homedir: h }) };
} catch (e) {
return {
statusCode: 500,
body: JSON.stringify({
ok: false, ...debug,
name: e.name, code: e.code, message: e.message,
syscall: e.syscall, errno: e.errno, info: e.info,
}),
};
}
};
EOF
```

## Step 3 — Start LocalStack with your binary and --user=1000:1000

The binary path must be mounted into the LocalStack container via `DOCKER_FLAGS`, then
referenced via `LAMBDA_INIT_BIN_PATH` using the in-container path.

```bash
DOCKER_FLAGS="-v $(pwd)/bin:/lambda-bin" \
LAMBDA_DOCKER_FLAGS="--user=1000:1000" \
LAMBDA_INIT_BIN_PATH=/lambda-bin/aws-lambda-rie-x86_64 \
localstack start -d

localstack wait -t 60
```

> **Note:** LocalStack warns to use `LOCALSTACK_`-prefixed env vars — both forms work.

## Step 4 — Deploy and invoke

```bash
# Zip the function
cd /tmp/fn-homedir && zip function.zip index.js && cd -

# Create the function
awslocal lambda create-function \
--function-name homedir-test \
--runtime nodejs22.x \
--handler index.handler \
--role arn:aws:iam::000000000000:role/lambda-role \
--zip-file fileb:///tmp/fn-homedir/function.zip

# Wait for it to be active
awslocal lambda wait function-active --function-name homedir-test

# Invoke and pretty-print the response
awslocal lambda invoke \
--function-name homedir-test \
--payload '{}' \
/tmp/fn-homedir/response.json && cat /tmp/fn-homedir/response.json
```

## Expected output (with the EnsureHome() fix)

```json
{"statusCode":200,"body":"{\"ok\":true,\"HOME_env\":\"/tmp\",\"has_HOME_key\":true,\"homedir\":\"/tmp\"}"}
```

Without the fix you'd get `statusCode: 500` with `"code":"ENOENT"`.