Skip to content

Commit a1a9e9d

Browse files
committed
Slurm: Validate token is a valid JWT
This avoids a situation we encountered where the filename was being used as a JWT. The SLURM-REST api server apparently assumed _any_ token being passed was valid, and so forwarded it to the backend.
1 parent c63487c commit a1a9e9d

File tree

2 files changed

+47
-0
lines changed

2 files changed

+47
-0
lines changed

src/zocalo/util/slurm/__init__.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
from __future__ import annotations
22

3+
import base64
4+
import binascii
5+
import json
36
import os
47
import pathlib
58
from typing import Any
@@ -11,6 +14,24 @@
1114
from . import models
1215

1316

17+
def validate_is_jwt(token: str) -> bool:
18+
"""Checks that a particular string is a JWT token"""
19+
if token.count(".") != 2:
20+
return False
21+
header, payload, _ = token.split(".")
22+
try:
23+
# Check both header and payload are valid base64-encoded json objects
24+
if not (
25+
isinstance(json.loads(base64.b64decode(header, validate=True)), dict)
26+
and isinstance(json.loads(base64.b64decode(payload, validate=True)), dict)
27+
):
28+
return False
29+
except (binascii.Error, json.JSONDecodeError):
30+
return False
31+
32+
return True
33+
34+
1435
class SlurmRestApi:
1536
def __init__(
1637
self,
@@ -25,8 +46,15 @@ def __init__(
2546
if user_token and os.path.isfile(user_token):
2647
with open(user_token, "r") as f:
2748
self.user_token = f.read().strip()
49+
elif isinstance(user_token, pathlib.Path):
50+
# We got passed a path, but it isn't a valid one
51+
raise RuntimeError(f"SLURM: API token file {user_token} does not exist")
2852
else:
2953
assert isinstance(user_token, str)
54+
if not validate_is_jwt(user_token):
55+
raise RuntimeError(
56+
"SLURM user_token does not appear to be a valid JWT token. Did you pass a nonexistent filename?"
57+
)
3058
self.user_token = user_token
3159
self.session = requests.Session()
3260
if self.user_name:

tests/util/test_slurm.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,25 @@ def test_get_slurm_api_user_token_external_file(tmp_path):
244244
assert api.user_token == "foobar"
245245

246246

247+
def test_get_slurm_api_invalid_external_file(tmp_path):
248+
"""Check that passing invalid file doesn't get interpreted as a token"""
249+
user_token_file = tmp_path / "bad-slurm-user-token"
250+
with pytest.raises(RuntimeError):
251+
slurm.SlurmRestApi(
252+
url="http://slurm.example.com:1234",
253+
version="v0.0.40",
254+
user_name="foo",
255+
user_token=user_token_file,
256+
)
257+
with pytest.raises(RuntimeError):
258+
slurm.SlurmRestApi(
259+
url="http://slurm.example.com:1234",
260+
version="v0.0.40",
261+
user_name="foo",
262+
user_token=str(user_token_file),
263+
)
264+
265+
247266
def test_get_jobs(requests_mock, slurm_api, jobs_response):
248267
requests_mock.get(
249268
"/slurm/v0.0.40/jobs",

0 commit comments

Comments
 (0)