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

Require systemd v243+, recommend systemd v245+, test against systemd v245 #117

Merged
merged 2 commits into from
May 26, 2023
Merged
Show file tree
Hide file tree
Changes from all 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
8 changes: 6 additions & 2 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -24,26 +24,30 @@ on:

jobs:
test:
runs-on: ubuntu-22.04

strategy:
fail-fast: false
matrix:
include:
# oldest supported python and jupyterhub version
- python-version: "3.8"
pip-install-spec: "jupyterhub==2.3.0 tornado==5.1.0 sqlalchemy==1.*"
runs-on: ubuntu-20.04
- python-version: "3.9"
pip-install-spec: "jupyterhub==2.* sqlalchemy==1.*"
runs-on: ubuntu-22.04
- python-version: "3.10"
pip-install-spec: "jupyterhub==3.*"
runs-on: ubuntu-22.04
- python-version: "3.11"
pip-install-spec: "jupyterhub==4.*"
runs-on: ubuntu-22.04

# latest version of python and jupyterhub (including pre-releases)
- python-version: "3.x"
pip-install-spec: "--pre jupyterhub"
runs-on: ubuntu-latest

runs-on: ${{ matrix.runs-on }}
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
Expand Down
55 changes: 10 additions & 45 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,16 +78,18 @@ The following features are currently available:

## Requirements

### Systemd
### Systemd and Linux distributions

Systemd Spawner requires you to use a Linux Distro that ships with at least
systemd v211. The security related features require systemd v228 or v227. We recommend running
with at least systemd v228. You can check which version of systemd is running with:
SystemdSpawner 1 is recommended to be used with systemd version 245 or higher,
but _may_ work with systemd version 243-244 as well. Below are examples of Linux
distributions that use systemd and has a recommended version.

```bash
$ systemctl --version | head -1
systemd 231
```
- Ubuntu 20.04+
- Debian 11+
- Rocky 9+ / CentOS 9+

The command `systemctl --version` can be used to verify that systemd is used,
and what version is used.

### Kernel Configuration

Expand Down Expand Up @@ -115,26 +117,6 @@ are required. Systemd will automatically create dynamic users as required.
See [this blog post](http://0pointer.net/blog/dynamic-users-with-systemd.html) for
details.

### Linux Distro compatibility

#### Ubuntu 16.04 LTS

We recommend running this with systemd spawner. The default kernel has all the features
we need, and a recent enough version of systemd to give us all the features.

#### Debian Jessie

The systemd version that ships by default with Jessie doesn't provide all the features
we need, and the default kernel doesn't ship with the features we need. However, if
you [enable jessie-backports](https://backports.debian.org/Instructions/) you can
install a new enough version of systemd and linux kernel to get it to work fine.

#### Centos 7

The kernel has all the features we need, but the version of systemd (219) is too old
for the security related features of systemdspawner. However, basic spawning,
memory & cpu limiting will work.

## Installation

You can install it from PyPI with:
Expand Down Expand Up @@ -332,9 +314,6 @@ c.SystemdSpawner.isolate_tmp = True

Defaults to false.

This requires systemd version > 227. If you enable this in earlier versions, spawning will
fail.

### `isolate_devices`

Setting this to true provides a separate, private `/dev` for each user. This prevents the
Expand All @@ -348,9 +327,6 @@ c.SystemdSpawner.isolate_devices = True

Defaults to false.

This requires systemd version > 227. If you enable this in earlier versions, spawning will
fail.

### `disable_user_sudo`

Setting this to true prevents users from being able to use `sudo` (or any other means) to
Expand All @@ -364,9 +340,6 @@ c.SystemdSpawner.disable_user_sudo = True

Defaults to false.

This requires systemd version > 228. If you enable this in earlier versions, spawning will
fail.

### `readonly_paths`

List of filesystem paths that should be mounted readonly for the users' notebook server. This
Expand All @@ -384,9 +357,6 @@ appropriate values for the user being spawned.

Defaults to `None` which disables this feature.

This requires systemd version > 228. If you enable this in earlier versions, spawning will
fail. It can also contain only directories (not files) until systemd version 231.

### `readwrite_paths`

List of filesystem paths that should be mounted readwrite for the users' notebook server. This
Expand All @@ -403,9 +373,6 @@ appropriate values for the user being spawned.

Defaults to `None` which disables this feature.

This requires systemd version > 228. If you enable this in earlier versions, spawning will
fail. It can also contain only directories (not files) until systemd version 231.

### `dynamic_users`

Allocate system users dynamically for each user.
Expand All @@ -418,8 +385,6 @@ is deallocated whenever the user's server is not running.
See http://0pointer.net/blog/dynamic-users-with-systemd.html for more
information.

Requires systemd 235.

### `slice`

Run the spawned notebook in a given systemd slice. This allows aggregate configuration that
Expand Down
18 changes: 18 additions & 0 deletions systemdspawner/systemd.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import os
import re
import shlex
import subprocess
import warnings

# light validation of environment variable keys
Expand Down Expand Up @@ -198,3 +199,20 @@ async def reset_service(unit_name):
"""
proc = await asyncio.create_subprocess_exec("systemctl", "reset-failed", unit_name)
await proc.wait()


def get_systemd_version():
"""
Returns systemd's major version, or None if failing to do so.
"""
try:
version_response = subprocess.check_output(["systemctl", "--version"])
# Example response from Ubuntu 22.04:
#
# systemd 249 (249.11-0ubuntu3.9)
# +PAM +AUDIT +SELINUX +APPARMOR +IMA +SMACK +SECCOMP +GCRYPT +GNUTLS +OPENSSL +ACL +BLKID +CURL +ELFUTILS +FIDO2 +IDN2 -IDN +IPTC +KMOD +LIBCRYPTSETUP +LIBFDISK +PCRE2 -PWQUALITY -P11KIT -QRENCODE +BZIP2 +LZ4 +XZ +ZLIB +ZSTD -XKBCOMMON +UTMP +SYSVINIT default-hierarchy=unified
#
version = int(float(version_response.split()[1]))
return version
except:
return None
23 changes: 19 additions & 4 deletions systemdspawner/systemdspawner.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
import asyncio
import os
import pwd
import sys

from jupyterhub.spawner import Spawner
from jupyterhub.utils import random_port
from traitlets import Bool, Dict, List, Unicode

from systemdspawner import systemd

SYSTEMD_REQUIRED_VERSION = 243
SYSTEMD_LOWEST_RECOMMENDED_VERSION = 245


class SystemdSpawner(Spawner):
user_workingdir = Unicode(
Expand Down Expand Up @@ -60,8 +64,6 @@ class SystemdSpawner(Spawner):
""",
).tag(config=True)

# FIXME: Do not allow enabling this for systemd versions < 227,
# since that is when it was introduced.
isolate_tmp = Bool(
False,
help="""
Expand Down Expand Up @@ -131,8 +133,6 @@ class SystemdSpawner(Spawner):
See http://0pointer.net/blog/dynamic-users-with-systemd.html for more
information.
Requires systemd 235.
""",
).tag(config=True)

Expand All @@ -155,6 +155,21 @@ def __init__(self, *args, **kwargs):
"user:%s Initialized spawner with unit %s", self.user.name, self.unit_name
)

systemd_version = systemd.get_systemd_version()
if systemd_version is None:
self.log.warning(
"Failed to parse systemd version from 'systemctl --version'"
)
elif systemd_version < SYSTEMD_REQUIRED_VERSION:
self.log.critical(
f"systemd version {SYSTEMD_REQUIRED_VERSION} or higher is required, version {systemd_version} is used"
)
sys.exit(1)
elif systemd_version < SYSTEMD_LOWEST_RECOMMENDED_VERSION:
self.log.warning(
f"systemd version {SYSTEMD_LOWEST_RECOMMENDED_VERSION} or higher is recommended, version {systemd_version} is used"
)

def _expand_user_vars(self, string):
"""
Expand user related variables in a given string
Expand Down
11 changes: 11 additions & 0 deletions tests/test_systemd.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,17 @@
from systemdspawner import systemd


def test_get_systemd_version():
"""
Test getting systemd version as an integer, where the assumption for the
tests are that systemd is actually running at all.
"""
systemd_version = systemd.get_systemd_version()
assert isinstance(
systemd_version, int
), "Either systemd wasn't running, or we failed to parse the version into an integer!"


async def test_simple_start():
unit_name = "systemdspawner-unittest-" + str(time.time())
await systemd.start_transient_service(
Expand Down