This commit is contained in:
parent
34e1ae34db
commit
fb47979787
3 changed files with 22 additions and 25 deletions
17
Dockerfile
17
Dockerfile
|
|
@ -55,22 +55,25 @@ COPY --from=ghcr.io/amacneil/dbmate:2 /usr/local/bin/dbmate /usr/local/bin/dbmat
|
||||||
COPY db/migrations/ /app/db/migrations/
|
COPY db/migrations/ /app/db/migrations/
|
||||||
|
|
||||||
# debian:*-slim ships without /etc/nsswitch.conf, and glibc then cannot turn a
|
# debian:*-slim ships without /etc/nsswitch.conf, and glibc then cannot turn a
|
||||||
# uid back into a user record. libvirt does exactly that lookup before connecting
|
# uid back into a user record. Restore the plain file-based lookup.
|
||||||
# and gives up with "Failed to find user record for uid", whatever the socket
|
|
||||||
# permissions are. Restore the plain file-based lookup.
|
|
||||||
RUN printf 'passwd: files\ngroup: files\nshadow: files\nhosts: files dns\n' \
|
RUN printf 'passwd: files\ngroup: files\nshadow: files\nhosts: files dns\n' \
|
||||||
> /etc/nsswitch.conf
|
> /etc/nsswitch.conf
|
||||||
|
|
||||||
# Unprivileged, but it must land in the host's libvirt group to reach the
|
# Runs as root, on purpose. libvirt resolves the calling uid to a user record
|
||||||
# socket: `group_add` in the compose file does that at run time.
|
# before it even opens the socket, and refused to do so for a custom uid here
|
||||||
RUN useradd --system --create-home --uid 10001 app
|
# ("Failed to find user record for uid") even with /etc/passwd and the libvirt
|
||||||
|
# group both in place. Root always resolves.
|
||||||
|
#
|
||||||
|
# The privilege actually gained is small: any process that can reach the libvirt
|
||||||
|
# socket already starts and destroys the VMs of this host, which is the whole
|
||||||
|
# point of this container. It holds no other mount and no secret beyond the
|
||||||
|
# database password.
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
COPY --from=backend /build/target/release/app-template /usr/local/bin/vm-selector
|
COPY --from=backend /build/target/release/app-template /usr/local/bin/vm-selector
|
||||||
COPY --from=frontend /build/dist/ /app/frontend/
|
COPY --from=frontend /build/dist/ /app/frontend/
|
||||||
COPY docker/config.yml /etc/app-template/config.yml
|
COPY docker/config.yml /etc/app-template/config.yml
|
||||||
|
|
||||||
USER app
|
|
||||||
EXPOSE 3000
|
EXPOSE 3000
|
||||||
|
|
||||||
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
|
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
|
||||||
|
|
|
||||||
22
README.md
22
README.md
|
|
@ -196,24 +196,22 @@ cargo sqlx prepare # dev database must be up and migrated
|
||||||
|
|
||||||
### Portainer stack
|
### Portainer stack
|
||||||
|
|
||||||
Deploy `docker-compose.yml` and set the four stack variables it documents at the top
|
Deploy `docker-compose.yml` and set the stack variables it documents at the top
|
||||||
(`IMAGE`, `POSTGRES_PASSWORD`, `LIBVIRT_GID`, `APP_PORT`). Get the group id from the
|
(`IMAGE`, `POSTGRES_PASSWORD`, `APP_PORT`).
|
||||||
host:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
getent group libvirt | cut -d: -f3
|
|
||||||
```
|
|
||||||
|
|
||||||
Then create a webhook on the stack, **tick "Re-pull image"**, and put its url in the
|
Then create a webhook on the stack, **tick "Re-pull image"**, and put its url in the
|
||||||
`PORTAINER_WEBHOOK` secret of the repository. Without the re-pull, the webhook
|
`PORTAINER_WEBHOOK` secret of the repository. Without the re-pull, the webhook
|
||||||
recreates the containers from the layer already on disk and nothing changes.
|
recreates the containers from the layer already on disk and nothing changes.
|
||||||
|
|
||||||
The two things that make libvirt reachable from inside the container:
|
What makes libvirt reachable from inside the container: `/var/run/libvirt` is
|
||||||
|
bind-mounted from the host — the whole directory, since the socket is `libvirt-sock`
|
||||||
|
or `virtqemud-sock` depending on the host's setup — and the container runs as root.
|
||||||
|
|
||||||
- `/var/run/libvirt` bind-mounted from the host — the whole directory, since the
|
Running as root is not laziness about the group: with a custom uid, libvirt resolves
|
||||||
socket is `libvirt-sock` or `virtqemud-sock` depending on the host's setup
|
the caller to a user record before opening the socket and refused to do so here even
|
||||||
- the container in the host's `libvirt` group (`group_add`) — without it the socket
|
with `/etc/passwd` correct and the `libvirt` group added through `group_add`
|
||||||
is visible and every call is denied
|
(`Failed to find user record for uid '10001'`). What root actually adds is small —
|
||||||
|
anything that reaches this socket already controls every VM on the host.
|
||||||
|
|
||||||
### Domain name
|
### Domain name
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,6 @@
|
||||||
# Environment variables to set in the Portainer stack:
|
# Environment variables to set in the Portainer stack:
|
||||||
# IMAGE registry.tibiscuit.ch/vm-selector:latest
|
# IMAGE registry.tibiscuit.ch/vm-selector:latest
|
||||||
# POSTGRES_PASSWORD anything, it never leaves the internal network
|
# POSTGRES_PASSWORD anything, it never leaves the internal network
|
||||||
# LIBVIRT_GID the host's libvirt group id, from:
|
|
||||||
# getent group libvirt | cut -d: -f3
|
|
||||||
# APP_PORT host port for the reverse proxy to point at (default 3010)
|
# APP_PORT host port for the reverse proxy to point at (default 3010)
|
||||||
|
|
||||||
services:
|
services:
|
||||||
|
|
@ -59,10 +57,8 @@ services:
|
||||||
# runs the monolithic libvirtd or the split daemons, the app needs
|
# runs the monolithic libvirtd or the split daemons, the app needs
|
||||||
# libvirt-sock or virtqemud-sock.
|
# libvirt-sock or virtqemud-sock.
|
||||||
- /var/run/libvirt:/var/run/libvirt
|
- /var/run/libvirt:/var/run/libvirt
|
||||||
# Reaching that socket requires being in the host's libvirt group. Without
|
# No `group_add`: the container runs as root (see the Dockerfile for why), so
|
||||||
# this the socket is visible and every call is denied.
|
# it needs no group membership to reach the socket.
|
||||||
group_add:
|
|
||||||
- '${LIBVIRT_GID:?see the header of this file}'
|
|
||||||
depends_on:
|
depends_on:
|
||||||
db:
|
db:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue