Skip to content

Commit 18e07a6

Browse files
author
William Strecker-Kellogg
committed
Transfer api-key in file not env for condor jobs
On a standard shared HTCondor deployment condor_schedds often allow reading by others and the Environment Classad is readable too - better to pass sensitive information (api keys) via a method that doesn't expose them to the world. This commit writes a secure temp-file under '/tmp' with the api_token in it and transfers that file to the jobs with condor's file-transfer mechanism and sources that file in the spawner prior to execution
1 parent 1decdf2 commit 18e07a6

File tree

1 file changed

+46
-4
lines changed

1 file changed

+46
-4
lines changed

batchspawner/batchspawner.py

+46-4
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@
2020
import pwd
2121
import os
2222
import re
23-
import sys
23+
import grp
24+
25+
import tempfile
2426

2527
import xml.etree.ElementTree as ET
2628

@@ -877,20 +879,26 @@ def get_env(self):
877879
class CondorSpawner(UserEnvMixin, BatchSpawnerRegexStates):
878880
batch_script = Unicode(
879881
"""
880-
Executable = /bin/sh
882+
Executable = /bin/bash
881883
RequestMemory = {memory}
882884
RequestCpus = {nprocs}
883-
Arguments = \"-c 'exec {cmd}'\"
885+
Arguments = \"-c 'source $_CONDOR_SCRATCH_DIR/{apikey_file}; exec {cmd}'\"
884886
Remote_Initialdir = {homedir}
885887
Output = {homedir}/.jupyterhub.condor.out
886888
Error = {homedir}/.jupyterhub.condor.err
887-
ShouldTransferFiles = False
888889
GetEnv = True
890+
transfer_executable = False
891+
transfer_input_files = {apikeyfile_dir}/{apikey_file}
892+
should_transfer_files = YES
889893
{options}
890894
Queue
891895
"""
892896
).tag(config=True)
893897

898+
req_apikey_file = Unicode("")
899+
900+
req_apikeyfile_dir = Unicode("/tmp")
901+
894902
# outputs job id string
895903
batch_submit_cmd = Unicode("condor_submit").tag(config=True)
896904
# outputs job data XML string
@@ -903,6 +911,40 @@ class CondorSpawner(UserEnvMixin, BatchSpawnerRegexStates):
903911
state_running_re = Unicode(r"^2,").tag(config=True)
904912
state_exechost_re = Unicode(r"^\w*, .*@([^ ]*)").tag(config=True)
905913

914+
def write_apikey_file(self):
915+
p = "bsapikey-{0}".format(self.user.name)
916+
917+
with tempfile.NamedTemporaryFile(
918+
delete=False, dir=self.req_apikeyfile_dir, prefix=p
919+
) as fp:
920+
self.log.info("Writing apikey to file: %s", fp.name)
921+
fp.write("export JUPYTERHUB_API_TOKEN={}\n".format(self.api_token).encode())
922+
self.req_apikey_file = os.path.basename(fp.name)
923+
924+
# Set file owned by user for batch-submission
925+
user = pwd.getpwnam(self.user.name)
926+
os.chown(fp.name, user.pw_uid, user.pw_gid)
927+
928+
def clean_apikey_file(self):
929+
try:
930+
os.unlink(os.path.join(self.req_apikeyfile_dir, self.req_apikey_file))
931+
except OSError:
932+
pass
933+
934+
def get_env(self):
935+
env = super().get_env()
936+
env.pop("JUPYTERHUB_API_TOKEN", None)
937+
env.pop("JPY_API_TOKEN", None)
938+
return env
939+
940+
async def submit_batch_script(self):
941+
self.write_apikey_file()
942+
return await super().submit_batch_script()
943+
944+
async def cancel_batch_job(self):
945+
self.clean_apikey_file()
946+
await super().cancel_batch_job()
947+
906948
def parse_job_id(self, output):
907949
match = re.search(r".*submitted to cluster ([0-9]+)", output)
908950
if match:

0 commit comments

Comments
 (0)