diff --git a/mkdocs.yml b/mkdocs.yml index b2e83fbbd..9b551b12f 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -306,6 +306,7 @@ nav: - Troubleshooting: docs/guides/troubleshooting.md - More: - Protips: docs/guides/protips.md + - Tenant isolation: docs/guides/tenant-isolation.md - Upgrade: docs/guides/upgrade.md - Migration: - Slurm: docs/guides/migration/slurm.md diff --git a/mkdocs/docs/concepts/exports.md b/mkdocs/docs/concepts/exports.md index 86748367f..39df23445 100644 --- a/mkdocs/docs/concepts/exports.md +++ b/mkdocs/docs/concepts/exports.md @@ -184,7 +184,7 @@ fleets: !!! info "Tenant isolation" - Exported fleets share the same access model as regular fleets. See [Tenant isolation](fleets.md#tenant-isolation) for details. + Exported fleets share the same access model as regular fleets. See [Tenant isolation](../guides/tenant-isolation.md) for details. !!! info "What's next?" 1. Check the [`dstack export` CLI reference](../reference/cli/dstack/export.md) diff --git a/mkdocs/docs/concepts/fleets.md b/mkdocs/docs/concepts/fleets.md index 85cb15b7e..270bee4a1 100644 --- a/mkdocs/docs/concepts/fleets.md +++ b/mkdocs/docs/concepts/fleets.md @@ -463,15 +463,6 @@ ssh_config: !!! info "Reference" The fleet configuration file supports additional options, including [`instance_types`](../reference/dstack.yml/fleet.md#instance_types), [`max_price`](../reference/dstack.yml/fleet.md#max_price), [`regions`](../reference/dstack.yml/fleet.md#max_price), among others. For the complete list, see the [reference](../reference/dstack.yml/fleet.md). -## Tenant isolation - -Users running workloads on a fleet have access to the host, including the folders that may be used as instance volumes, -and containers use host network mode unless the host has multiple [blocks](#blocks) configured and the job uses only a subset of them. - -Tighter isolation is on the roadmap, including [SSH reverse proxy](https://github.com/dstackai/dstack/issues/3644){:target="_blank"} and rootless access to the host. - -When [exporting fleets](exports.md) to other projects, the same access model applies to members of the importer projects. - ## Export fleets Fleets can be exported to other projects, allowing those projects to use the exported fleets diff --git a/mkdocs/docs/guides/tenant-isolation.md b/mkdocs/docs/guides/tenant-isolation.md new file mode 100644 index 000000000..7e2a3920c --- /dev/null +++ b/mkdocs/docs/guides/tenant-isolation.md @@ -0,0 +1,98 @@ +--- +title: Tenant isolation +description: Restricting access to hosts managed by dstack +--- + +# Tenant isolation + +`dstack` assumes mutual trust between users of the same project. While users' jobs run in Docker containers, users and their containers may have broad access to the underlying hosts. This guide explains how to restrict access to the host when stronger boundaries are required. + +!!! info "Disclaimer" + Even with all precautions, complete isolation on shared hardware is hardly achievable — container escape vulnerabilities are common. The best way to provide true isolation between users is to place them in different `dstack` projects and not share hardware between them. + +## Host SSH access + +While attached to a run, users can SSH directly into the host machine — not just the container — using: + +```shell +ssh -host +``` + +This gives unrestricted access to the underlying instance, bypassing container boundaries. + +If desired, host SSH access can be disabled server-wide by configuring the [SSH proxy](server-deployment.md#ssh-proxy) and setting the following environment variable when starting the `dstack` server: + +```shell +DSTACK_SERVER_SSHPROXY_ENFORCED=1 +``` + +With this setting, all users' SSH connections go through the SSH proxy, which only allows connections into the container and not into the host. + +## Privileged mode + +Running a container in privileged mode gives it full access to the host kernel, making container escape straightforward. `dstack` supports requesting privileged mode through several configuration properties: + +| Property | Applies to | +|---|---| +| `privileged: true` | Tasks, dev environments, services | +| `docker: true` | Tasks, dev environments, services | +| `replicas[i].privileged: true` | Services with replica groups | +| `replicas[i].docker: true` | Services with replica groups | + +To block runs that request privileged mode, write a [REST plugin](../reference/plugins/rest/index.md) or a [Python plugin](../reference/plugins/python/index.md) with an apply policy. + +
+ +```python +class NoPrivilegedPolicy(ApplyPolicy): + def on_run_apply(self, user: str, project: str, spec: RunSpec) -> RunSpec: + conf = spec.configuration + + if conf.privileged or conf.docker: + raise ValueError("Privileged mode and Docker-in-Docker are not allowed") + + if isinstance(conf, ServiceConfiguration) and isinstance(conf.replicas, list): + for group in conf.replicas: + if group.privileged or group.docker: + raise ValueError( + f"Replica group '{group.name}' requests privileged mode, which is not allowed" + ) + + return spec +``` + +
+ +## Instance volumes + +[Instance volumes](../concepts/volumes.md#instance-volumes) mount a path from the host filesystem directly into the container. A user with access to this feature can mount arbitrary host paths — including sensitive directories such as `/etc`, `/proc`, or `/var`. + +You can disallow instance volumes or restrict access to certain paths by writing a [REST plugin](../reference/plugins/rest/index.md) or a [Python plugin](../reference/plugins/python/index.md). + +## Host network access + +By default, most `dstack` jobs run in host networking mode. This allows them to listen on any host network interface and communicate with other jobs over the internal network, which facilitates workloads such as [distributed tasks](../concepts/tasks.md#distributed-tasks) or [services with routers](../concepts/services.md#pd-disaggregation). + +However, exposing the host network to the job also exposes internal `dstack` APIs used to manage containers and SSH authorized keys on the host. If this is not acceptable, bridge networking should be used, which isolates the job from the host network. Bridge networking, however, breaks workloads that do need inter-job communication. + +The `DSTACK_SERVER_JOB_NETWORK_MODE` environment variable controls which jobs get host vs. bridge networking: + +| Value | Name | Behavior | +|---|---|---| +| `1` | `HOST_FOR_MULTINODE_ONLY` | Host for distributed tasks, bridge otherwise | +| `2` | `HOST_WHEN_POSSIBLE` | Host whenever the job occupies a full instance (default) | +| `3` | `FORCED_BRIDGE` | Always bridge, including distributed tasks | + +### No distributed tasks + +If you don't need distributed tasks or other runs with inter-job communication, you can set `DSTACK_SERVER_JOB_NETWORK_MODE=3` when starting the server: + +```shell +DSTACK_SERVER_JOB_NETWORK_MODE=3 +``` + +This forces bridge networking for all jobs on the server without exception, preventing access to internal `dstack` APIs, as well as communication between jobs. + +### Allow distributed tasks in selected projects + +If you want distributed tasks or other runs with inter-job communication to be available in some projects but not others, use `DSTACK_SERVER_JOB_NETWORK_MODE=1` instead. With this mode, single-node jobs get bridge networking, while distributed tasks still run with host networking. Distributed tasks can then be selectively blocked per project or user by writing a [REST plugin](../reference/plugins/rest/index.md) or a [Python plugin](../reference/plugins/python/index.md). diff --git a/mkdocs/docs/reference/env.md b/mkdocs/docs/reference/env.md index 1b8162910..e1a86334e 100644 --- a/mkdocs/docs/reference/env.md +++ b/mkdocs/docs/reference/env.md @@ -141,6 +141,10 @@ For more details on the options below, refer to the [server deployment](../guide - `DSTACK_SERVER_DEFAULT_DOCKER_REGISTRY`{ #DSTACK_SERVER_DEFAULT_DOCKER_REGISTRY } – A default Docker registry to use for job images that do not specify an explicit registry. E.g., if set to `registry.example`, then `image: ubuntu` becomes equivalent to `image: registry.example/ubuntu`. **Note**: This setting should only be used for configuring registries that act as a pull-through cache for Docker Hub. The default `dstack` images are also pulled from the configured registry. - `DSTACK_SERVER_DEFAULT_DOCKER_REGISTRY_USERNAME`{ #DSTACK_SERVER_DEFAULT_DOCKER_REGISTRY_USERNAME } – Username for authenticating with the default Docker registry. See `DSTACK_SERVER_DEFAULT_DOCKER_REGISTRY_PASSWORD`. - `DSTACK_SERVER_DEFAULT_DOCKER_REGISTRY_PASSWORD`{ #DSTACK_SERVER_DEFAULT_DOCKER_REGISTRY_PASSWORD } – Password for authenticating with the default Docker registry. Applied only when the image has no explicit registry and the run configuration does not specify `registry_auth`. **Note**: The value may be visible to anyone who can SSH into instances managed by `dstack`, which usually includes all users of that `dstack` server. +- `DSTACK_SSHPROXY_API_TOKEN`{ #DSTACK_SSHPROXY_API_TOKEN } – Authentication token for the SSH proxy API. Required to enable SSH proxy integration; must match the token configured when deploying [`dstack-sshproxy`](https://github.com/dstackai/sshproxy). +- `DSTACK_SERVER_SSHPROXY_ADDRESS`{ #DSTACK_SERVER_SSHPROXY_ADDRESS } – Address of the SSH proxy exposed to users, in `HOSTNAME[:PORT]` form. `PORT` defaults to `22` if omitted. Required together with `DSTACK_SSHPROXY_API_TOKEN` to enable SSH proxy integration. +- `DSTACK_SERVER_SSHPROXY_ENFORCED`{ #DSTACK_SERVER_SSHPROXY_ENFORCED } – When set to any value, restricts all SSH connections to go through the SSH proxy. +- `DSTACK_SERVER_JOB_NETWORK_MODE`{ #DSTACK_SERVER_JOB_NETWORK_MODE } – Controls the network mode assigned to jobs. Accepts an integer value: `1` forces bridge networking for single-node jobs while distributed tasks still use host networking; `2` uses host networking whenever the job occupies a full instance (default); `3` forces bridge networking for all jobs including distributed tasks. ??? info "Internal environment variables" The following environment variables are intended for development purposes: