Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Setting File Access Lists #4369

Merged
merged 8 commits into from
Mar 5, 2021
Merged
Show file tree
Hide file tree
Changes from 3 commits
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: 1 addition & 1 deletion docs/containers/debug-python.md
Original file line number Diff line number Diff line change
Expand Up @@ -232,4 +232,4 @@ When you select **Docker: Add Docker Files to Workspace** for Django or Flask, w

Learn more about:

- [Configuring a non-root user in your container](/docs/containers/python-user-rights.md)
- [Configuring a non-root user in your container](/docs/containers/python-configure-containers.md.md)
ucheNkadiCode marked this conversation as resolved.
Show resolved Hide resolved
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
---
Area: containers
TOCTitle: User Privileges in Python Containers
ContentId: 1ebbceb6-ae61-4b98-953d-0b18323becc4
PageTitle: User Privileges in Python Containers
PageTitle: Configure your Python containers
DateApproved: 04/20/2020
MetaDescription: How to setup a non-root user for VS Code Docker Extension
---

# User privileges in Python containers
# Configure your Python containers

When containerizing an application for production, your goal should be to port existing code into a separate runtime environment without introducing unforeseen security concerns. For this reason, you should select the default port for **Python: Django** (8000) and **Python: Flask** (5000) during execution of the **Add Dockerfiles to Workspace** command, or opt for a port **greater than** 1023. This will allow VS Code to configure the Dockerfile with non-root access and prevent a malicious user from elevating permissions in the container, ultimately [obtaining host machine root access](https://nvd.nist.gov/vuln/detail/CVE-2019-5736). When you choose **Python: General**, the Docker extension configures non-root access by default, since no port is chosen. However, in all cases, to use a non-root user within a container, you must ensure each resource your application needs to read or modify [can be accessed](#invalid-file-permissions).
When containerizing an application for production, your goal should be to port existing code into a separate runtime environment without introducing unforeseen security concerns. For this reason, we recommend selecting the default port for **Python: Django** (8000) or **Python: Flask** (5000) when executing the **Add Dockerfiles to Workspace** command, or opting for a port **greater than** 1023. This will allow VS Code to configure the Dockerfile with non-root access and prevent a malicious user from elevating permissions in the container, ultimately [obtaining host machine root access](https://nvd.nist.gov/vuln/detail/CVE-2019-5736). When you choose **Python: General**, there is no port selection, so the Docker extension configures non-root access by default. In all cases, you must ensure each resource (such as ports and files) modified or used by your application [can be accessed](#invalid-file-permissions) by a non-root user in your container.

If a user selects ports less than 1024 when adding Dockerfiles to workspace, by default, **we cannot** scaffold a Dockerfile that will run the container as a non-root user. This is because ports in this range are called **well-known** or **system** ports and must execute with root privileges in order to bind a network socket to an IP address.

Expand All @@ -31,7 +30,7 @@ If you chose **Python: General**, non-root privileges will be set up by default,

### Docker file changes

Within the Dockerfile, you must expose a non-system port, create a working directory for your app code, and then add a non-root user with access to the app directory. Lastly, ensure your exposed port **matches** the port binding of the Gunicorn command. The `CMD` command below configures Gunicorn for a Django container. For more information on configuring Gunicorn, refer to the documentation on [Gunicorn configuration for Django/Flask apps](/docs/containers/quickstart-python.md#gunicorn-modifications-for-djangoflask-apps).
Within the Dockerfile, you must expose a **non-system port**, create a working directory for your app code, and then add a non-root user with access to the app directory. Lastly, ensure your exposed port **matches** the port binding of the Gunicorn command. The `CMD` command below configures Gunicorn for a Django container. For more information on configuring Gunicorn, refer to the documentation on [Gunicorn configuration for Django/Flask apps](/docs/containers/quickstart-python.md#gunicorn-modifications-for-djangoflask-apps).

``` dockerfile
# 1024 or higher
Expand All @@ -44,7 +43,7 @@ EXPOSE 1024
WORKDIR /app
ADD . /app

# Switches to a non-root user and changes the ownership of the /app folder"
# Creates a non-root user and adds permission to access the /app folder
RUN useradd appuser && chown -R appuser /app
USER appuser

Expand All @@ -67,7 +66,7 @@ After choosing a non-system port and setting up the container to run as a non-ro
"python": {
"args": [
"runserver",
"0.0.0.0:1024", //Change the number after the colon
"0.0.0.0:1024", //<- Change the number after the colon
"--nothreading",
"--noreload"
],
Expand Down Expand Up @@ -96,7 +95,7 @@ After choosing a non-system port and setting up the container to run as a non-ro
"--no-debugger",
"--no-reload",
"--host", "0.0.0.0",
"--port", "1024" //Change this port number
"--port", "1024" //<- Change this port number
],
"module": "flask"
}
Expand All @@ -105,11 +104,11 @@ After choosing a non-system port and setting up the container to run as a non-ro

## Potential errors when running as a non-root user

Following the guide up to this point should eliminate most configuration issues caused by running as a non-root user. However, we have compiled a list (non-exhaustive) of common errors you may run into.
Following the guide up to this point should eliminate most configuration issues caused by running as a non-root user. However, we have compiled a non-exhaustive list of common errors you may run into.

If you encounter any other problems due to running as a non-root user, **please** report the issue in the [Docker Extension repository](https://github.com/microsoft/vscode-docker/issues/new). We love your feedback!

### Invalid file permissions
### Invalid file permissions in the container

If you are reading, writing, or creating a file within your container, a non-root user might not have access to folders or files in specific directories unless directly given.

Expand Down Expand Up @@ -137,41 +136,69 @@ Exception has occurred: PermissionError
To solve this issue, we need to correctly add permissions to the non-root user to gain access to this specific file or directory in the container. Within your Dockerfile, add:

```dockerfile
# Creates a non-root user and adds permission to access the /app folder
RUN useradd appuser && chown -R appuser /app

# Adds permission to appuser (non-root) for access to the /extra folder
# Adds permission for appuser (non-root) to access the /extra folder
RUN chown -R appuser /extra
```

> **Note**: This is just one example of how to add permissions. There are many ways to do so and it is your responsibility give the least permission possible to specific files and folders.
> **Note**: This is just one example of how to add permissions in a container. There are many ways to do so and it is your responsibility give the least permission possible to specific files and folders.
ucheNkadiCode marked this conversation as resolved.
Show resolved Hide resolved

### Binding to an inaccessible port
### Invalid file permission on the host (Linux)

If your container starts and stops immediately after `kb(workbench.action.debug.start)` without producing logs in the **Debug Console**, we can try to diagnose the issue through these steps:
In the previous example, we showed you how to add permissions to a file or folder on the container as a non-root user. However, if you are trying to access a folder **on the host machine** from within the container as a non-root user, the user ID or group ID in the container must have access to the files on the host. To solve this issue in Linux, you may need to set file access control lists (setfacl).
ucheNkadiCode marked this conversation as resolved.
Show resolved Hide resolved

If you have a folder named `/share` on your host machine and try to access this folder before the access control list are properly set, you will likely receive this error:

```python
PermissionError: [Errno 13] Permission denied: '/share/logs/log.txt'
```

In order to give access to a non-root user `appuser` from within the container, follow these steps:

1. From the container command line, run one of these commands:
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe it's worth mentioning how to enter a container's commandline

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bwateratmsft or @TheYarin Would it be sufficient to say

  1. From the Containers pane in the Docker View, right click on your running container, select Attach Shell, and run one of these commands:

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm, I think the user will be doing all this permissions configuration in a production environment, so docker actions from within the developer's VSCode might not be relevant.

(Unless managing your remote docker host from within VSCode is really easy, of which I might not be familiar with)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@TheYarin, so you're expecting this scenario will only/mostly occur as a remote container and not a local container on the user's PC? If the user is ever in the situation when it's local, then the steps above work. Otherwise, we have a remote containers extension that the user could use for SSH

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I'm expecting this scenario will mostly occur as a remote container. This might happen in the development environment if it's a linux environment (when mounting a volume on my Windows dev environment it didn't cause any permissions errors), but honestly, I got a feeling that a lot of developers don't properly test their application in a containerized setup on their dev machine, because the developer probably has all the dependencies set up already so it's easier for him to just run the app outside the container.

But considering what @karolz-ms said below about the best practice regarding UID, I think mentioning how to enter a container's commandline is kind of irrelevant.

Copy link

@TheYarin TheYarin Mar 5, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you sure the user/group ID will always be the same if the container is removed and re-created? That's a common scenario when operating with docker-compose up & docker-compose down

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@karolz-ms or @bwateratmsft, do you know about this?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know for sure, but I would think that the uid/gid would stay the same, since it comes from the image, not the container. I think?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch @TheYarin Best practice is to use and explicit UID in this case: https://docs.docker.com/develop/develop-images/dockerfile_best-practices/#user


```powershell
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why powershell and not shell/bash?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same for line 173

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's just telling the docs site how to syntax highlight this chunk of code. Those commands are actually shell/bash commands.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I know, this might seem a little petty but why not mark the block specifically as bash/sh? I'm pretty sure whichever markdown engine is used, it supports this language.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

... or shell I agree with @TheYarin

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @TheYarin! I'll add this change to the docs

# To find a specific user's UID
id -u appuser

# Or to find a specific user's GID
id -g username
```

1. Copy the output from the container command line.
1. From the **host machine's** command line, run one of these commands:

```powershell
# Example of giving a User ID with the value of 5678 access to the /share folder on the host machine

setfacl -m u:5678:rwx /share

# Example of giving a Group ID with the value of 6789 access to the /share folder on the host machine

setfacl -m g:6789:rwx /share
```

### Binding to a low-range port

If you hit `kb(workbench.action.debug.start)` to start your container and it immediately stops without producing any logs in the **Debug Console**, this error could mean you are exposing a system-port (ports less than 1024) while attempting to run as a non-root user. This may be hard to catch because, by default, containers are removed after debugging is stopped. To diagnose this port error, follow these steps:

1. Open and modify your `launch.json` file:

```json
{
"name": "Docker: Python - Django",
"name": "Docker: {Configuration Name}",
"type": "docker",
"request": "launch",
"preLaunchTask": "docker-run: debug",
"removeContainerAfterDebug": false, //add this line
"python": {
"pathMappings": [
{
"localRoot": "${workspaceFolder}",
"remoteRoot": "/app"
}
],
"projectType": "django"
}
"removeContainerAfterDebug": false, //<- add this line
// ... the rest of the launch configuration
}
```

1. Hit `kb(workbench.action.debug.start)` to run your container again.
1. After the container exits once more, navigate to the Docker Extension, right-click the container, and select **View Logs**.
2. Hit `kb(workbench.action.debug.start)` to run your container again.
3. After the container exits once more, navigate to the Docker Extension, right-click the container, and select **View Logs**.

![User clicking view logs on their container](images/quickstarts/python-user-rights-view-logs.png)

Expand All @@ -184,6 +211,6 @@ In a Flask app, you may see the error:
> self.socket.bind(self.server_address)
> PermissionError: [Errno 13] Permission denied

This likely means you are exposing a system-port (ports less than 1024) while attempting to run as a non-root user. This incompatible configuration is demonstrated in the image above.
The image above is a problematic configuration because a port **less than** 1024 was selected.

To solve this issue, modify your Dockerfile and `tasks.json` file in the manner shown [above](#running-your-containerized-app-as-a-nonroot-user).
To solve this issue, modify your Dockerfile and `tasks.json` file in the manner shown [here](#running-your-containerized-app-as-a-nonroot-user).
2 changes: 1 addition & 1 deletion docs/containers/quickstart-python.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ After verifying your app runs properly, you can now Dockerize your application.

>**Tip**: You may also enter the path to a folder name as long as this folder includes a `__main__.py` file.

1. If **Python: Django** or **Python: Flask** was selected, specify app port for local development. Django defaults to port 8000 while Flask defaults to port 5000; however, any unused port will work. We recommend selecting port 1024 or above to mitigate security concerns from [running as a root user](/docs/containers/python-user-rights.md).
1. If **Python: Django** or **Python: Flask** was selected, specify app port for local development. Django defaults to port 8000 while Flask defaults to port 5000; however, any unused port will work. We recommend selecting port 1024 or above to mitigate security concerns from [running as a root user](/docs/containers/python-configure-containers.md).
ucheNkadiCode marked this conversation as resolved.
Show resolved Hide resolved

1. With all of this information, the Docker extension creates the following files:

Expand Down