Repository: pallets/werkzeug

Issue:
Debugger pin calls getuser(), which can fail inside Docker
When launching an application with the Werkzeug debugger in Docker with a custom UID, the debugger fails while trying to construct a PIN.

```pytb
Traceback (most recent call last):
  File "/usr/local/bin/flask", line 10, in <module>
    sys.exit(main())
  File "/usr/local/lib/python3.7/site-packages/flask/cli.py", line 894, in main
    cli.main(args=args, prog_name=name)
  File "/usr/local/lib/python3.7/site-packages/flask/cli.py", line 557, in main
    return super(FlaskGroup, self).main(*args, **kwargs)
  File "/usr/local/lib/python3.7/site-packages/click/core.py", line 717, in main
    rv = self.invoke(ctx)
  File "/usr/local/lib/python3.7/site-packages/click/core.py", line 1137, in invoke
    return _process_result(sub_ctx.command.invoke(sub_ctx))
  File "/usr/local/lib/python3.7/site-packages/click/core.py", line 956, in invoke
    return ctx.invoke(self.callback, **ctx.params)
  File "/usr/local/lib/python3.7/site-packages/click/core.py", line 555, in invoke
    return callback(*args, **kwargs)
  File "/usr/local/lib/python3.7/site-packages/click/decorators.py", line 64, in new_func
    return ctx.invoke(f, obj, *args, **kwargs)
  File "/usr/local/lib/python3.7/site-packages/click/core.py", line 555, in invoke
    return callback(*args, **kwargs)
  File "/usr/local/lib/python3.7/site-packages/flask/cli.py", line 771, in run_command
    threaded=with_threads, ssl_context=cert)
  File "/usr/local/lib/python3.7/site-packages/werkzeug/serving.py", line 751, in run_simple
    application = DebuggedApplication(application, use_evalex)
  File "/usr/local/lib/python3.7/site-packages/werkzeug/debug/__init__.py", line 258, in __init__
    if self.pin is None:
  File "/usr/local/lib/python3.7/site-packages/werkzeug/debug/__init__.py", line 268, in _get_pin
    self._pin, self._pin_cookie = get_pin_and_cookie_name(self.app)
  File "/usr/local/lib/python3.7/site-packages/werkzeug/debug/__init__.py", line 148, in get_pin_and_cookie_name
    username = getpass.getuser()
  File "/usr/local/lib/python3.7/getpass.py", line 169, in getuser
    return pwd.getpwuid(os.getuid())[0]
KeyError: 'getpwuid(): uid not found: 1001'
```

https://github.com/pallets/werkzeug/blob/6fc07fec6dfd21690c9909f8c7c8e744d55afba7/werkzeug/debug/__init__.py#L148

It seems that Werkzeug fails on this line when trying to get the user with `$UID` specified in `docker-compose.yml`.

Maybe a workaround could be create the user group and the user inside the docker instance, but it's not scalable.

`docker-compose.yml`:

```yml
image: local/imageTest
build:
    context: .
    dockerfile: dockerfiles/Dockerfile
    args:
      BUILD_ENV: dev
user: $UID
volumes:
    - ./:/code
env_file:
    - .env.sample
    - .env
environment:
    HOME: /code
    FLASK_APP: "test/web.py"
    FLASK_DEBUG: "1"
depends_on:
    - postgres
ports:
    - 5050:5000
command: ["flask", "run", "--host=0.0.0.0"]
```

`Dockerfile`:

```dockerfile
FROM library/python:3.7.0

WORKDIR /code/

ARG BUILD_ENV=prod

RUN apt-get update \
 && apt-get install -y python3-dev python3-psycopg2 python3-gdal libgdal-dev \
 && rm -rf /var/lib/apt/lists/*

ADD requirements*.txt /code/
RUN pip3 install -U pip setuptools
RUN pip3 install -r requirements-${BUILD_ENV}.txt
ADD . /code/

ENV PYTHONPATH "/code"
```


Discussion:
That's because your container provides a broken environment where your `/etc/passwd` has no entry for the UID. Run `useradd` with a fixed UID in your Dockerfile to create the user properly.

However, since this is just in the debugger, and we already skip the username e.g. on GAE, I think we could just catch KeyError here as well: https://github.com/pallets/werkzeug/blob/master/src/werkzeug/debug/__init__.py#L165
I just tried this with the `python:3.7-alpine` official image and did not have an issue calling `getpass.getuser()`, it returned `'root'`. It sounds like an issue with the image you're using.
The problem is that he is using it for development so he's setting the container UID to his regular UID to avoid issues with files created from inside the container.
> I just tried this with the `python:3.7-alpine` official image and did not have an issue calling `getpass.getuser()`, it returned `'root'`. It sounds like an issue with the image you're using.

As ThiefMaster points here

> The problem is that he is using it for development so he's setting the container UID to his regular UID to avoid issues with files created from inside the container.

The problem comes from `docker-compose.yml` in `user: $UID`. With root it works by default.

And finally:

> That's because your container provides a broken environment where your `/etc/passwd` has no entry for the UID. Run `useradd` with a fixed UID in your Dockerfile to create the user properly.
> 
> However, since this is just in the debugger, and we already skip the username e.g. on GAE, I think we could just catch KeyError here as well: https://github.com/pallets/werkzeug/blob/master/src/werkzeug/debug/__init__.py#L165

That's true, it's something like I mention here:
> Maybe a workaround could be create the user group and the user inside the docker instance, but it's not scalable.

If I add the following to the image it would work, but it's a pity because I cannot manage N users.
```Dockerfile
RUN groupadd -g 1000 developer1000 && useradd -u 1000 -g 1000 developer1000 && \
    groupadd -g 1001 developer1001 && useradd -u 1001 -g 1001 developer1001
```

Thanks for your quick responses anyway ;)

Resolving patch:
diff --git a/src/werkzeug/debug/__init__.py b/src/werkzeug/debug/__init__.py
--- a/src/werkzeug/debug/__init__.py
+++ b/src/werkzeug/debug/__init__.py
@@ -159,10 +159,11 @@ def get_pin_and_cookie_name(app):
     modname = getattr(app, "__module__", getattr(app.__class__, "__module__"))
 
     try:
-        # `getpass.getuser()` imports the `pwd` module,
-        # which does not exist in the Google App Engine sandbox.
+        # getuser imports the pwd module, which does not exist in Google
+        # App Engine. It may also raise a KeyError if the UID does not
+        # have a username, such as in Docker.
         username = getpass.getuser()
-    except ImportError:
+    except (ImportError, KeyError):
         username = None
 
     mod = sys.modules.get(modname)


Test patch: