Skip to content

Commit e0fee0d

Browse files
Add site configs and harden portal handling
Signed-off-by: Yoshifumi Nakamura <nakamura@riken.jp>
1 parent 576d14c commit e0fee0d

15 files changed

Lines changed: 273 additions & 54 deletions

File tree

.github/workflows/gitlab-manual-ci.yml

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,19 +44,27 @@ jobs:
4444
name: Run GitLab CI manually
4545
runs-on: ubuntu-latest
4646
steps:
47-
- name: Check out target ref
47+
- name: Check out trusted workflow ref
4848
uses: actions/checkout@v4
4949
with:
5050
fetch-depth: 0
51-
ref: ${{ inputs.target_ref }}
51+
path: trusted
5252

5353
- name: Prepare GitLab repository settings
5454
id: gitlab-repo
55-
uses: ./.github/actions/prepare-gitlab-repo
55+
uses: ./trusted/.github/actions/prepare-gitlab-repo
5656
with:
5757
gitlab-repo: ${{ secrets.GITLAB_REPO }}
5858

59+
- name: Check out target ref
60+
uses: actions/checkout@v4
61+
with:
62+
fetch-depth: 0
63+
ref: ${{ inputs.target_ref }}
64+
path: target
65+
5966
- name: Push target ref to GitLab test branch
67+
working-directory: target
6068
env:
6169
GITLAB_TOKEN: ${{ secrets.GITLAB_TOKEN }}
6270
GITLAB_REPO_HOST_PATH: ${{ steps.gitlab-repo.outputs.host-path }}
@@ -184,6 +192,7 @@ jobs:
184192
185193
- name: Delete GitLab test branch
186194
if: always()
195+
working-directory: trusted
187196
env:
188197
GITLAB_TOKEN: ${{ secrets.GITLAB_TOKEN }}
189198
GITLAB_REPO_HOST_PATH: ${{ steps.gitlab-repo.outputs.host-path }}

config/queue.csv

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
queue,submit_cmd,template
2+
SLURM_AI4SS,sbatch,"-p ${queue_group} -t ${elapse} -N ${nodes} --ntasks-per-node=${numproc_node} --cpus-per-task=${nthreads} --gpus-per-node=${numproc_node}"
23
FJ,pjsub,"-L rscunit=rscunit_ft01,rscgrp=${queue_group},elapse=${elapse},node=${nodes} --mpi max-proc-per-node=${numproc_node} -x PJM_LLIO_GFSCACHE=/vol0002:/vol0003:/vol0004:/vol0005"
34
PJM_GENKAI,pjsub,"-L rscgrp=${queue_group},elapse=${elapse},node=${nodes} --mpi proc=${proc}"
45
SLURM_RC,sbatch,"-p ${queue_group} -t ${elapse} -N ${nodes} --ntasks-per-node=${numproc_node} --cpus-per-task=${nthreads}"
@@ -7,4 +8,8 @@ PBS_Grand_C,qsub,"-q ${queue_group} -l select=${nodes}:nsockets=${cpu_per_node},
78
PBS_Grand_G,qsub,"-q ${queue_group} -l select=${nodes}:ngpus=1,walltime=${elapse} -W group_list=d30992"
89
NQSV_AOBA_VE,qsub,"-Z -v http_proxy,https_proxy,HTTP_PROXY,HTTPS_PROXY -q ${queue_group} -T necmpi --venode ${proc} -l elapstim_req=${elapse}"
910
NQSV_AOBA_B,qsub,"-Z -v http_proxy,https_proxy,HTTP_PROXY,HTTPS_PROXY -q ${queue_group} -T intmpi -b ${nodes} -l elapstim_req=${elapse}"
11+
PJM_WISTERIA_O,pjsub,"-g jh260034o -L rscgrp=${queue_group},elapse=${elapse},node=${nodes} --mpi proc=${proc} --omp thread=${nthreads}"
12+
PJM_WISTERIA_A,pjsub,"-g jh260034a -L rscgrp=${queue_group},elapse=${elapse},node=${nodes} --mpi proc=${proc} --omp thread=${nthreads}"
13+
AGE_TSUBAME4,qsub,"-l ${queue_group}=${nodes} -l h_rt=${elapse}"
14+
SLURM_CAMPHOR3,sbatch,"-p ${queue_group} -t ${elapse} --rsc p=${proc}:t=${nthreads}:c=${nthreads}:m=1G"
1015
none,none,none

config/system.csv

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
system,mode,tag_build,tag_run,queue,queue_group
2+
AI4SS,cross,ai4ss_login,ai4ss_jacamar,SLURM_AI4SS,1n1gpu
23
Fugaku,cross,fugaku_login1,fugaku_jacamar,FJ,small
34
FugakuLN,native,,fugaku_login1,none,small
45
FugakuCN,native,,fugaku_jacamar,FJ,small
@@ -16,4 +17,8 @@ Grand_G,cross,grand_login,grand_jacamar,PBS_Grand_G,eg
1617
AOBA_A,cross,aoba_ab_login,aoba_ab_jacamar,NQSV_AOBA_VE,sx
1718
AOBA_B,cross,aoba_ab_login,aoba_ab_jacamar,NQSV_AOBA_B,lx
1819
AOBA_S,cross,aoba_s_login,aoba_s_jacamar,NQSV_AOBA_VE,sxs
20+
Odyssey,cross,wisteria_login,wisteria-o_jacamar,PJM_WISTERIA_O,short-o
21+
Aquarius,cross,wisteria_login,wisteria-a_jacamar,PJM_WISTERIA_A,short-a
22+
TSUBAME4,cross,tsubame4_login,tsubame4_jacamar,AGE_TSUBAME4,node_f
23+
Camphor3,cross,camphor3_login,camphor3_jacamar,SLURM_CAMPHOR3,gr19999a
1924
FNCX,native,,fncx-curl-jq,none,small

config/system_info.csv

Lines changed: 22 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,23 @@
11
system,name,cpu_name,cpu_per_node,cpu_cores,gpu_name,gpu_per_node,memory,display_order
2-
Fugaku,Fugaku,A64FX,1,48,-,-,32GB,1
3-
FugakuCN,FugakuCN,A64FX,1,48,-,-,32GB,2
4-
FugakuLN,FugakuLN,Intel(R) Xeon(R) Gold 6242 CPU @ 2.80GHz,2,16,-,-,96GB,3
5-
MiyabiG,MiyabiG,NVIDIA Grace CPU,1,72,NVIDIA Hopper H100 GPU,1,120GB,4
6-
MiyabiC,MiyabiC,Intel Xeon Max 9480,2,56,-,-,128GB,5
7-
RC_GH200,RC_GH200,NVIDIA Grace CPU,1,72,NVIDIA Hopper H100 GPU,1,120GB,6
8-
RC_DGXSP,RC_DGXSP,ARM Cortex-X925 / Cortex-A725,1,20,NVIDIA GB10,1,128GB,7
9-
RC_GENOA,RC_GENOA,AMD EPYC 9684X,2,96,-,-,768GB,8
10-
RC_FX700,RC_FX700,A64FX,1,48,-,-,32GB,9
11-
GenkaiA,GenkaiA,Intel Xeon Platinum 8490H (Sapphire Rapids),2,60,-,-,512GiB,10
12-
GenkaiB,GenkaiB,Intel Xeon Platinum 8490H (Sapphire Rapids),2,60,NVIDIA H100 (Hopper),4,1024GiB,11
13-
GenkaiC,GenkaiC,Intel Xeon Platinum 8480+ (Sapphire Rapids),2,56,NVIDIA H100 (Hopper),8,8TiB,12
14-
Grand_C,Grand_C,Intel Xeon Gold 6548Y+ (Emerald Rapids),2,32,-,-,512GiB,13
15-
Grand_G,Grand_G,Intel Xeon Gold 6548Y+ (Emerald Rapids),2,32,NVIDIA H100 (Hopper),4,512GiB,14
16-
AOBA_A,AOBA_A,SX-Aurora TSUBASA VH,1,24,NEC SX-Aurora TSUBASA Type 20B VE,8,640GB,15
17-
AOBA_B,AOBA_B,AMD EPYC 7702,2,64,-,-,256GB,16
18-
AOBA_S,AOBA_S,SX-Aurora TSUBASA VH,1,64,NEC SX-Aurora TSUBASA Type 30A VE,8,256GB + 768GB,17
2+
AI4SS,RIKEN AI4S Supercomputer,NVIDIA Grace CPU,2,72,NVIDIA B200,4,960GiB + 692.8GiB,1
3+
Fugaku,Fugaku,A64FX,1,48,-,-,32GB,2
4+
FugakuCN,FugakuCN,A64FX,1,48,-,-,32GB,3
5+
FugakuLN,FugakuLN,Intel(R) Xeon(R) Gold 6242 CPU @ 2.80GHz,2,16,-,-,96GB,4
6+
MiyabiG,MiyabiG,NVIDIA Grace CPU,1,72,NVIDIA Hopper H100 GPU,1,120GB,5
7+
MiyabiC,MiyabiC,Intel Xeon Max 9480,2,56,-,-,128GB,6
8+
RC_GH200,RC_GH200,NVIDIA Grace CPU,1,72,NVIDIA Hopper H100 GPU,1,120GB,7
9+
RC_DGXSP,RC_DGXSP,ARM Cortex-X925 / Cortex-A725,1,20,NVIDIA GB10,1,128GB,8
10+
RC_GENOA,RC_GENOA,AMD EPYC 9684X,2,96,-,-,768GB,9
11+
RC_FX700,RC_FX700,A64FX,1,48,-,-,32GB,10
12+
GenkaiA,GenkaiA,Intel Xeon Platinum 8490H (Sapphire Rapids),2,60,-,-,512GiB,11
13+
GenkaiB,GenkaiB,Intel Xeon Platinum 8490H (Sapphire Rapids),2,60,NVIDIA H100 (Hopper),4,1024GiB,12
14+
GenkaiC,GenkaiC,Intel Xeon Platinum 8480+ (Sapphire Rapids),2,56,NVIDIA H100 (Hopper),8,8TiB,13
15+
Grand_C,Grand_C,Intel Xeon Gold 6548Y+ (Emerald Rapids),2,32,-,-,512GiB,14
16+
Grand_G,Grand_G,Intel Xeon Gold 6548Y+ (Emerald Rapids),2,32,NVIDIA H100 (Hopper),4,512GiB,15
17+
AOBA_A,AOBA_A,SX-Aurora TSUBASA VH,1,24,NEC SX-Aurora TSUBASA Type 20B VE,8,640GB,16
18+
AOBA_B,AOBA_B,AMD EPYC 7702,2,64,-,-,256GB,17
19+
AOBA_S,AOBA_S,SX-Aurora TSUBASA VH,1,64,NEC SX-Aurora TSUBASA Type 30A VE,8,256GB + 768GB,18
20+
Odyssey,Odyssey,A64FX,1,48,-,-,32GiB,19
21+
Aquarius,Aquarius,Intel Xeon Platinum 8360Y,2,36,NVIDIA A100,8,512GiB,20
22+
TSUBAME4,TSUBAME4.0,AMD EPYC 9654,2,96,NVIDIA H100 SXM5 94GB HBM2e,4,768GiB,21
23+
Camphor3,Camphor3,Intel Xeon CPU Max 9480,2,56,-,-,128GiB,22

programs/qws/build.sh

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@ case "$system" in
3636
echo "Dummy build for FNCX Docker runner test"
3737
echo aaaa > main
3838
;;
39+
AI4SS)
40+
module load nvhpc-hpcx/26.3
41+
make -j 8 fugaku_benchmark= omp=1 compiler=openmpi-gnu arch=skylake rdma= mpi=1 powerapi= CC=mpicc CXX=mpic++
42+
;;
3943
RC_GH200)
4044
module load system/qc-gh200 nvhpc-hpcx/25.9
4145
### QWSはNeoverse版やGPU版はないので汎用版としてとりあえずarch=skylakeを指定している
@@ -75,6 +79,20 @@ case "$system" in
7579
AOBA_B)
7680
make -j 8 fugaku_benchmark= omp=1 compiler=openmpi-gnu arch=skylake rdma= mpi=1 powerapi= CC=mpicc CXX=mpic++
7781
;;
82+
Odyssey)
83+
module load odyssey
84+
make -j 8 fugaku_benchmark= omp=1 compiler=fujitsu_cross rdma= mpi=1 powerapi= SYSLIBS=
85+
;;
86+
Aquarius)
87+
module load nvidia/25.9 nvmpi/25.9
88+
make -j 8 fugaku_benchmark= omp=1 compiler=openmpi-gnu arch=skylake rdma= mpi=1 powerapi= CC=mpicc CXX=mpic++
89+
;;
90+
TSUBAME4)
91+
make -j 8 fugaku_benchmark= omp=1 compiler=openmpi-gnu arch=skylake rdma= mpi=1 powerapi= CC=mpicc CXX=mpic++
92+
;;
93+
Camphor3)
94+
make -j 8 fugaku_benchmark= omp=1 compiler=intel arch=skylake rdma= mpi=1 powerapi=
95+
;;
7896
*)
7997
echo "Unknown system: $system"
8098
exit 1

programs/qws/list.csv

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
system,enable,nodes,numproc_node,nthreads,elapse
2+
AI4SS,yes,1,1,72,0:10:00
23
Fugaku,yes,1,4,12,0:10:00
34
FugakuLN,yes,1,1,1,0:10:00
45
FugakuCN,no,1,4,12,0:10:00
@@ -17,4 +18,8 @@ Grand_G,yes,1,1,64,0:10:00
1718
AOBA_A,yes,1,1,8,0:10:00
1819
AOBA_S,yes,1,1,8,0:10:00
1920
AOBA_B,yes,1,1,128,0:10:00
21+
Odyssey,yes,1,1,48,0:10:00
22+
Aquarius,yes,1,1,72,0:10:00
23+
TSUBAME4,yes,1,1,192,0:10:00
24+
Camphor3,yes,1,1,112,0:10:00
2025
FNCX,yes,1,1,1,0:10:00

programs/qws/run.sh

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,12 @@ case "$system" in
8484
echo 'dummy call for FNCX Docker runner test'
8585
bk_emit_result --fom 99.99 --fom-version dummy --exp FNCXTest --nodes "$nodes" --numproc-node "$numproc_node" --nthreads "$nthreads" >> ../results/result
8686
;;
87+
AI4SS)
88+
qws_numproc=$((nodes * numproc_node))
89+
module load nvhpc-hpcx/26.3
90+
mpirun -n ${qws_numproc} --bind-to core --map-by ppr:1:node:PE=72 ./main 32 6 4 3 1 1 1 1 -1 -1 6 50 > CASE0
91+
print_results CASE0 CASE0 ${numproc_node} >> ../results/result
92+
;;
8793
RC_GH200)
8894
module load system/qc-gh200 nvhpc-hpcx/25.9
8995
mpirun -n 1 --bind-to core --map-by ppr:1:node:PE=72 ./main 32 6 4 3 1 1 1 1 -1 -1 6 50 > CASE0
@@ -141,6 +147,22 @@ case "$system" in
141147
mpirun -np ${qws_numproc} ./main 32 6 4 3 1 1 1 1 -1 -1 6 50 > CASE0
142148
print_results CASE0 CASE0 ${numproc_node} >> ../results/result
143149
;;
150+
Odyssey)
151+
module load odyssey fj fjmpi
152+
mpiexec -n 1 ./main 32 6 4 3 1 1 1 1 -1 -1 6 50 > CASE0
153+
print_results output.${PJM_JOBID}/0/1/stdout.1.0 CASE0 1 >> ../results/result
154+
;;
155+
Aquarius)
156+
qws_numproc=$((nodes * numproc_node))
157+
module load nvidia/25.9 nvmpi/25.9
158+
mpirun -n ${qws_numproc} ./main 32 6 4 3 1 1 1 1 -1 -1 6 50 > CASE0
159+
print_results CASE0 CASE0 ${numproc_node} >> ../results/result
160+
;;
161+
TSUBAME4|Camphor3)
162+
qws_numproc=$((nodes * numproc_node))
163+
mpirun -n ${qws_numproc} ./main 32 6 4 3 1 1 1 1 -1 -1 6 50 > CASE0
164+
print_results CASE0 CASE0 ${numproc_node} >> ../results/result
165+
;;
144166
*)
145167
echo "Unknown Running system: $system"
146168
exit 1

result_server/routes/api.py

Lines changed: 60 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@
77
import uuid
88
import shutil
99
import io
10-
import sys
1110
import tarfile
11+
import tempfile
1212
from datetime import datetime
1313

1414
from utils.auth import verify_ingest_key
@@ -145,25 +145,61 @@ def _find_result_file_by_uuid(received_dir, uuid_value):
145145

146146

147147
def _safe_extract_tar_bytes(file_storage, target_dir):
148-
"""Extract uploaded tar bytes with path and member-type checks.
149-
150-
The explicit path normalization catches traversal attempts before writing
151-
anything, and Python 3.12's data filter rejects non-regular archive entries
152-
such as unsafe links or device files.
153-
"""
154-
if sys.version_info < (3, 12):
155-
raise RuntimeError("Python 3.12 or later is required for safe tar extraction.")
156-
148+
"""Extract uploaded tar bytes with path and member-type checks."""
157149
os.makedirs(target_dir, exist_ok=True)
158150
with tarfile.open(fileobj=file_storage.stream, mode="r:*") as tar:
159151
for member in tar.getmembers():
160152
normalized = os.path.normpath(member.name)
161-
if os.path.isabs(normalized) or normalized.startswith(".."):
153+
drive, _ = os.path.splitdrive(normalized)
154+
if (
155+
drive
156+
or os.path.isabs(normalized)
157+
or normalized == ".."
158+
or normalized.startswith(f"..{os.sep}")
159+
or normalized.startswith("../")
160+
or normalized.startswith("..\\")
161+
):
162162
abort(400, description="Unsafe archive entry")
163-
try:
164-
tar.extractall(target_dir, filter="data")
165-
except tarfile.FilterError:
166-
abort(400, description="Unsafe archive entry")
163+
if member.issym() or member.islnk() or member.isdev():
164+
abort(400, description="Unsafe archive entry")
165+
if not (member.isfile() or member.isdir()):
166+
abort(400, description="Unsafe archive entry")
167+
168+
destination = os.path.abspath(os.path.join(target_dir, normalized))
169+
abs_target_dir = os.path.abspath(target_dir)
170+
try:
171+
if os.path.commonpath([abs_target_dir, destination]) != abs_target_dir:
172+
abort(400, description="Unsafe archive entry")
173+
except ValueError:
174+
abort(400, description="Unsafe archive entry")
175+
176+
if member.isdir():
177+
os.makedirs(destination, exist_ok=True)
178+
continue
179+
180+
os.makedirs(os.path.dirname(destination), exist_ok=True)
181+
source = tar.extractfile(member)
182+
if source is None:
183+
abort(400, description="Unsafe archive entry")
184+
with source, open(destination, "wb") as output:
185+
shutil.copyfileobj(source, output)
186+
187+
188+
def _replace_directory_after_success(source_dir, target_dir):
189+
"""Replace target_dir only after source_dir is fully prepared."""
190+
if not os.path.isdir(target_dir):
191+
os.rename(source_dir, target_dir)
192+
return False
193+
194+
backup_dir = f"{target_dir}.bak.{uuid.uuid4().hex}"
195+
os.rename(target_dir, backup_dir)
196+
try:
197+
os.rename(source_dir, target_dir)
198+
except Exception:
199+
os.rename(backup_dir, target_dir)
200+
raise
201+
shutil.rmtree(backup_dir)
202+
return True
167203

168204

169205
# ==========================================
@@ -264,13 +300,16 @@ def ingest_estimation_inputs():
264300

265301
result_stem = os.path.splitext(result_filename)[0]
266302
inputs_root = current_app.config["RECEIVED_ESTIMATION_INPUTS_DIR"]
303+
os.makedirs(inputs_root, exist_ok=True)
267304
target_dir = os.path.join(inputs_root, result_stem)
268-
replaced = os.path.isdir(target_dir)
269-
if replaced:
270-
shutil.rmtree(target_dir)
271-
os.makedirs(target_dir, exist_ok=True)
272-
273-
_safe_extract_tar_bytes(uploaded_file, target_dir)
305+
temp_dir = tempfile.mkdtemp(prefix=f".{result_stem}.", dir=inputs_root)
306+
try:
307+
_safe_extract_tar_bytes(uploaded_file, temp_dir)
308+
replaced = _replace_directory_after_success(temp_dir, target_dir)
309+
except Exception:
310+
if os.path.isdir(temp_dir):
311+
shutil.rmtree(temp_dir)
312+
raise
274313

275314
print(f"Saved estimation inputs: {target_dir}", flush=True)
276315
return {

result_server/tests/test_api_routes.py

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -412,6 +412,34 @@ def test_ingest_estimation_inputs_rejects_parent_path_entry(self, client, tmp_di
412412
)
413413
assert resp.status_code == 400
414414

415+
def test_ingest_estimation_inputs_keeps_existing_data_on_bad_archive(self, client, tmp_dirs):
416+
received = tmp_dirs[0]
417+
estimation_inputs_dir = tmp_dirs[2]
418+
uuid_value = "12345678-1234-1234-1234-123456789abc"
419+
result_stem = self._seed_result(received, uuid_value)
420+
target_dir = os.path.join(estimation_inputs_dir, result_stem)
421+
os.makedirs(target_dir, exist_ok=True)
422+
existing_path = os.path.join(target_dir, "existing.json")
423+
with open(existing_path, "w", encoding="utf-8") as f:
424+
json.dump({"keep": True}, f)
425+
426+
archive_bytes = io.BytesIO()
427+
with tarfile.open(fileobj=archive_bytes, mode="w:gz") as tar:
428+
payload = b"bad"
429+
info = tarfile.TarInfo(name="../outside.txt")
430+
info.size = len(payload)
431+
tar.addfile(info, io.BytesIO(payload))
432+
archive_bytes.seek(0)
433+
434+
resp = client.post(
435+
"/api/ingest/estimation-inputs",
436+
data={"id": uuid_value, "file": (archive_bytes, "estimation_inputs.tgz")},
437+
headers={"X-API-Key": API_KEY},
438+
content_type="multipart/form-data",
439+
)
440+
assert resp.status_code == 400
441+
assert os.path.exists(existing_path)
442+
415443
def test_ingest_estimation_inputs_rejects_absolute_path_entry(self, client, tmp_dirs):
416444
received = tmp_dirs[0]
417445
uuid_value = "12345678-1234-1234-1234-123456789abc"
@@ -497,4 +525,3 @@ def test_query_estimation_inputs_returns_archive(self, client, tmp_dirs):
497525
names = tar.getnames()
498526
assert "compute_solver_papi.tgz" in names
499527

500-

result_server/tests/test_pagination.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -458,6 +458,16 @@ def test_compare_route_still_works(self, flask_app, tmp_dir):
458458
resp = client.get(f"/results/compare?files={f1},{f2}")
459459
assert resp.status_code == 200
460460

461+
def test_compare_route_rejects_unsafe_filename(self, flask_app, tmp_dir):
462+
"""Compare should not accept path-like filenames from query parameters."""
463+
uid = str(uuid.uuid4())
464+
f1 = f"result_20250101_000000_{uid}.json"
465+
_write_json(tmp_dir, f1, {"code": "a", "system": "s", "FOM": 1.0})
466+
467+
with flask_app.test_client() as client:
468+
resp = client.get(f"/results/compare?files={f1},../outside.json")
469+
assert resp.status_code == 404
470+
461471
def test_no_filter_reads_only_page_files(self, flask_app, tmp_dir):
462472
"""Test case."""
463473
_make_result_files(tmp_dir, 150)

0 commit comments

Comments
 (0)