Skip to content

학생 계정·SQL 채점·시험 운영 개선 및 아티팩트 배포 체계 도입 - #50

Merged
pwh9882 merged 134 commits into
mainfrom
dev
Sep 8, 2026
Merged

pwh9882 merged 134 commits into
mainfrom
dev

Conversation

@pwh9882

@pwh9882 pwh9882 commented Sep 1, 2026 •

Copy link
Copy Markdown

SQL Playzone의 학생 로그인, SQL 채점, 시험 운영, 배포 방식을 정비합니다. 학생은 학교 계정으로 처음 등록한 뒤 ID·비밀번호로 접속하고, 문제에 공개된 기준에 따라 MySQL 8.4에서 채점받습니다. 운영자는 관리자 화면에서 시험 참여 규칙을 관리하고, 시험 전에 서버를 예약 확장할 수 있습니다.

학생 계정과 로그인

가입부터 시험 접속까지

  1. 처음 가입하려면 대학교 Google Workspace 계정으로 인증해야 합니다. Google Workspace는 학교나 조직이 구성원에게 제공하는 Google 계정 서비스입니다. 개인 Google 계정은 가입 단계에서 거부됩니다. Workspace를 사용하지 않는 학교의 학생은 관리자에게 계정 생성을 요청해야 합니다.
  2. 첫 계정 설정 화면(온보딩)에서 로그인 ID, Nickname(본명), 비밀번호, 학번을 입력하고 약관에 동의해야 서비스를 이용할 수 있습니다. 필수 항목, 비밀번호 규칙, 비밀번호 확인 결과를 입력 중에 안내합니다.
  3. 이후에는 ID 또는 이메일 + 비밀번호로 로그인할 수 있습니다. 비밀번호를 잊었을 때는 Google로 다시 인증해 새 비밀번호를 설정하면 됩니다. 시험 전에 계정 설정과 로그인을 미리 마쳐 보도록 학생에게 안내하는 것이 좋습니다.
계정 항목 용도와 관리 방법
로그인 ID 계정을 식별하는 고유한 값입니다. 학생 설정 화면에서는 읽기 전용으로 표시하고, 관리자는 ID로 계정을 조회·검색할 수 있습니다.
Nickname 화면에 표시하는 본명입니다. 동명이인을 허용하므로 성적을 대조할 때는 학번·이메일을 함께 확인해야 합니다.
이메일 Google 계정 연결과 로그인에 사용합니다. 학생의 직접 변경을 제한합니다.
학번 시험 참여 명단과 성적을 대조하는 기준입니다. 다른 계정과 중복될 수 없으며, 앞자리 0은 유지합니다.

기존 비밀번호 계정은 중복되지 않는 이전 사용자 이름을 로그인 ID로 이전합니다. 첫 계정 설정을 마치기 전인 Google 계정은 학생이 직접 ID를 정해야 합니다. API 토큰 발급·인증은 관리자 계정에 제공하며, 학생은 로그인 세션을 사용합니다. 교내 공유 IP에서의 동시 접속을 고려해 로그인 요청 한도와 화면 안내도 조정했습니다.

Google 계정 허용 범위 설정

위치: AWS Secrets Manager의 애플리케이션 설정에 있는 GOOGLE_HOSTED_DOMAIN. 로컬 실행에서는 config.ini 또는 환경변수로 설정할 수 있습니다.

값 가입 허용 범위
빈 값 또는 * — 기본값 모든 Google Workspace 조직의 검증된 계정
hanyang.ac.kr 지정한 조직의 검증된 계정
hanyang.ac.kr,partner.edu 쉼표로 나열한 조직의 검증된 계정

서버는 Google이 확인한 이메일과 조직 소속을 검사합니다. 기본 설정에는 기업 Workspace도 포함되므로 수강생만 시험에 참여하도록 하려면 허용 학번 명단을 설정해야 합니다.

AWS 배포는 서버 기동 때 이 설정을 읽습니다. 변경한 값을 반영하려면 승인된 배포 절차로 인스턴스를 교체해야 합니다. 반영 후에는 허용할 계정과 거부할 계정으로 로그인 결과를 확인해 주세요.

공개 약관·개인정보처리방침과 동의 관리

메인 화면 하단에서 서비스 이용약관(/tos)과 개인정보처리방침(/privacy)을 열 수 있습니다. 로그인 전, 가입 절차 중, 시험 제한 중에도 두 문서를 읽을 수 있습니다. 개인정보처리방침은 Config → Legal → Privacy Policy에서 수정합니다. 운영자는 공개 문의처·보유 기간과 외부 서비스 이용 조건을 실제 운영에 맞게 확정해야 합니다.

학번 입력란에는 잘못 입력할 경우 성적 처리에 문제가 생길 수 있다는 안내를 표시합니다. 가입·설정·관리자/API에서 중복 학번을 거절하며, 동시 등록에도 하나만 저장합니다. 본인의 학번이 이미 등록되어 있다고 나오면 담당 조교가 계정을 확인해야 합니다.

약관 본문(tos_text)은 관리자 Config → Legal → Terms of Service에서 수정·저장할 수 있습니다. 수정 후에는 학생에게 보이는 /tos 페이지와 첫 계정 설정 화면을 확인해 주세요. 외부 약관 URL을 사용하는 환경이라면 연결된 페이지도 함께 확인하는 것이 좋습니다.

새 설치에는 기본 약관이 들어가고, 기존 서비스는 관리자가 저장한 본문을 유지합니다. 학생의 동의 여부는 사용자 필드에 기록됩니다. 본문을 수정해도 기존 동의 기록은 유지되므로, 재동의가 필요한 개정은 대상 학생과 재동의 절차를 함께 정해야 합니다.

SQL 실행과 채점

MySQL 8.4와 공개 채점 기준

SQL 실행 엔진을 MySQL 8.4로 교체합니다. 채점 요청마다 임시 데이터베이스에 문제 데이터를 준비하고, 정답과 학생 SQL을 같은 데이터에 대한 각각의 읽기 전용 연결에서 실행합니다. 실행을 마치면 임시 데이터베이스와 계정을 정리합니다. MySQL Workbench·mysqldump 형식의 일반적인 초기화 스크립트를 지원합니다.

출제자가 저장한 정렬·표시 형식 기준은 학생 문제 화면에도 공개합니다.

비교 대상 채점 규칙
숫자 정확한 값으로 비교합니다. 1과 1.00은 같고, 1.234와 1.23은 다릅니다. 반올림을 요구하려면 지문과 정답 SQL에 반올림 기준을 명시해야 합니다.
자료형·NULL 숫자와 문자열을 구별하며, SQL NULL, 문자열 'NULL', 빈 문자열도 각각 구별합니다.
행 순서·중복 기본은 행 순서 무관이며 중복 개수는 일치해야 합니다. 정렬을 설정한 문제는 지정한 열 순서를 평가하고, MySQL에서 정렬 값이 같은 행끼리는 순서를 자유롭게 허용합니다.
표시 형식 소수점 끝자리 0 등 표시 형식까지 요구한 열은 정답의 표시 문자열과 비교합니다.
문자열·날짜·열 문자열·날짜의 출력 값은 공백·대소문자까지 비교합니다. 결과 열은 위치로 대응하며 별칭은 자유롭게 사용할 수 있습니다.

읽기 전용 권한과 실행 시간·결과 크기·동시 처리 제한으로 쿼리를 격리합니다. 제한 함수 검사는 일반 식별자와 문자열을 구분해 정상 SQL의 오탐을 줄였습니다. 초기화 SQL과 정답 SQL 조회는 관리자에게만 허용합니다.

출제자가 설정하는 정렬·표시 형식

위치: 관리자 Challenges → SQL 문제 생성·수정 화면. 지문을 확정한 뒤 아래 두 필드를 설정해 저장해 주세요.

필드 입력 예시 설정 방법
정렬 기준 1 asc, 2 desc 결과의 1번째 열 오름차순, 같은 값이면 2번째 열 내림차순입니다. 열 번호는 1부터 시작합니다. 순서 무관 문제라면 비워 두고 저장하면 됩니다.
표시 형식까지 평가하는 열 번호 3, 4 3·4번째 열은 표시 문자열까지 평가합니다. 지문에서 표시 형식을 요구한 열만 지정해야 합니다. 숫자 값만 평가하려면 이 목록에서 제외하면 됩니다.

예를 들어 “평균을 소수 둘째 자리로 반올림한 값”을 요구하려면 지문에 기준을 적고 정답 SQL에 ROUND를 사용할 수 있습니다. “항상 소수 두 자리로 표시”까지 요구한다면 해당 열을 표시 형식 평가에 추가하고 정답 출력도 그 형식으로 맞춰야 합니다.

기존 문제도 사용 전에 기준을 저장해야 합니다. 저장 전에는 “판정 불가”로 안내하며 제출 횟수를 보존합니다. 두 입력을 비운 채 저장하면 “행 순서 무관·숫자 값 비교” 기준이 적용됩니다.

저장 후에는 학생 화면의 공개 기준을 확인하고, 관리자 Test로 정답·다른 풀이의 정답·오답을 실행해 보는 것이 좋습니다. LIMIT으로 일부 행을 선택하는 문제는 경계의 동률 때문에 답이 달라질 수 있으므로 지문에 행 선택 기준을 명확히 적어야 합니다.

제출 마감과 시도 횟수

학생의 Test는 실행 결과 확인에, Submit은 성적 제출에 사용합니다. 두 경로에 문제 공개 상태, 선수 문제, 팀 소속, 시작 시각, 일시 중지 검사를 일관되게 적용합니다.

  • 마감은 서버 접수 시각으로 판단합니다. 마감 전에 접수한 답은 채점이 늦게 끝나도 접수 시각으로 저장합니다. 제출 기간을 정할 때는 전체 시험 시간과 SQL 문제의 **Deadline (KST)**을 함께 확인해야 합니다.
  • 문제별 Max Attempts는 관리자 SQL 문제 화면에서 설정할 수 있으며 0은 무제한입니다. 횟수에는 저장된 오답을 반영하고 Test는 실행 확인용으로 처리합니다.
  • 서버 보호를 위해 Test+Submit 합계로 계정당 동시 1개, UTC 기준 매 분 구간마다 최대 60개 실행 요청을 허용합니다. 팀 모드에서는 팀 기준으로 집계합니다.
  • 기본 시간 한도는 초기화+정답 검증 2.5초, 학생 SQL 2.5초, 요청 전체 8초입니다. 결과는 최대 1,000행 또는 4MiB입니다. 출제 전에 관리자 Test로 정답 실행 시간과 결과 크기를 확인해 주세요.

채점 장애와 학생 문의 대응

학생 SQL 오류·실행 한도 위반·결과 불일치는 오답으로 처리합니다. 문제 초기화·정답 오류나 채점 서버·통신 장애는 판정 불가로 안내하고 오답 횟수를 보존합니다. 화면에서는 저장 실패, 세션 만료, 접근 제한, 채점 오류를 구분하며 SQL 결과의 안전한 표시도 보완했습니다.

판정 불가가 반복되면 해당 문제의 기준·초기화 SQL·정답을 관리자 Test로 확인하고 채점 서버(judge)의 상태와 로그를 점검해야 합니다. 복구 후에는 학생에게 다시 제출하도록 안내해 주세요. 재제출은 새로운 접수 시각으로 마감을 판단하므로, 마감이 지난 학생의 처리는 제출·장애 기록을 대조해 결정해야 합니다.

시험 참여와 접속 규칙

명단·시험 브라우저·단일 세션 설정

위치: 관리자 Plugins → Exam Mode (/admin/exam_mode/). 세 스위치는 각각 관리하며 기본값은 모두 off입니다. 화면에서 저장하면 적용됩니다.

설정 입력·저장 켰을 때의 동작
Enable Exam Mode + Allowed Student IDs Student ID Number 사용자 필드를 준비하고, 허용 학번을 한 줄에 하나씩 입력한 뒤 Save & Apply 명단에 있는 학생의 접근을 허용합니다. 명단에는 최소 한 개의 학번이 필요합니다.
Allow the exam browser only User-Agent marker 확인 후 아래쪽 Save 학생에게 지정 브라우저 사용을 요구하고 Google 로그인 버튼을 숨깁니다. 기본 marker는 Trustlockbrowser이며, 브라우저가 보내는 식별 문자열에 이 값이 포함되는지 대소문자 구분 없이 검사합니다.
Allow only one session per student 아래쪽 Save 새 학생 로그인이 이전 로그인 세션을 종료합니다.

관리자는 일반 브라우저와 여러 세션으로 운영할 수 있습니다. 활성 규칙은 관리자 배너에 표시됩니다. 명단은 기존 로그인에도 적용하며, 단일 학번과 앞자리 0도 보존합니다.

시험 전: 학생이 계정 설정과 비밀번호 준비를 마친 뒤 규칙을 켜야 합니다. 테스트용 학생 계정으로 명단 포함·제외, 실제 시험 브라우저, 두 번째 로그인에 따른 세션 종료를 미리 확인하는 것이 좋습니다.

시험 후: 명단·브라우저·단일 세션 스위치를 각각 원하는 상태로 바꾸고, 위쪽 명단 폼과 아래쪽 세션 규칙 폼을 각각 저장해 주세요. 시험 시작·종료 시각에 맞춘 규칙 전환은 이 화면에서 운영자가 직접 해야 합니다.

성적 내보내기와 운영 기록

성적 화면과 제출 CSV에 이용 정지(Banned)·숨김(Hidden) 계정도 포함하고 상태 열을 표시합니다. 최종 성적을 처리할 때는 학번 명단과 대조해 수강생을 선별해야 합니다. 동명이인은 학번·이메일로 구분할 수 있습니다. 시험 명단을 변경해도 계정의 기존 이용 정지 상태는 유지됩니다.

Test로 실행한 SQL·판정은 서버에서 기록하고, 로그인에는 직전 로그인 정보를 함께 남깁니다. 브라우저 행동 로그는 허용 필드·크기·건수를 검사하고 작은 배치로 전송·재시도합니다. 사용자·문제 식별자는 서버가 확정합니다.

부정행위가 의심될 때는 제출 SQL, 서버 기록, 브라우저 이벤트를 함께 대조해 검토해야 합니다. 브라우저 식별 문자열과 이벤트는 사용자가 조작할 수 있다는 점을 판단에 반영해야 합니다.

시험 전 서버 예약 확장 — exam_windows

시험 시작에 접속·채점 요청이 몰려도 처리할 수 있도록 필요한 서버 수를 미리 확보하는 설정입니다. 서버 한 대를 새로 준비하는 데 수 분이 걸리므로 시험 30분 전부터 용량을 확보하고 시험 종료 30분 뒤까지 유지하는 일정을 권장합니다.

시험을 준비할 때는 서버 용량을 exam_windows에서, 학생 접근 규칙을 Exam Mode에서, 제출 기간을 전체 시험 시간과 문제별 Deadline에서 각각 설정해야 합니다.

설정 위치와 입력 예시

인프라 설정은 저장소의 IaC 디렉터리에서 Terraform으로 관리합니다. 환경별 설정값은 변수 파일(tfvars)에 작성합니다. dev는 IaC/environments/dev.tfvars를 사용하고, 운영은 운영 환경에 연결된 별도 tfvars를 사용합니다.

exam_windows의 기본값은 빈 목록입니다. 예약 확장을 사용하려면 시험 일정이 정해진 뒤 대상 환경의 tfvars에 다음과 같이 추가하면 됩니다. 아래는 2026-10-20 09:00~11:00 KST 시험, 서버 8대를 가정한 예시입니다.

exam_windows = [
  {
    name     = "midterm"
    start    = "2026-10-20T08:30:00"
    end      = "2026-10-20T11:30:00"
    capacity = 8
  }
]
필드 의미와 입력 조건
name 예약 이름. 영문 소문자·숫자로 시작하는 1~31자의 영문 소문자·숫자·하이픈을 사용하며 목록 안에서 고유해야 합니다.
start 서버 확보 시작 시각. KST를 YYYY-MM-DDTHH:mm:ss 형식으로 입력해야 합니다. 시간대 변환은 Terraform이 수행하므로 예시처럼 시간대 접미사를 생략해 주세요.
end 확보한 최소 대수를 해제할 시각. start보다 늦게 지정해야 합니다.
capacity 확보할 서버 수. asg_min_sizeasg_max_size 범위의 정수이며 기본 범위는 110입니다. 문제 데이터·쿼리와 동시 수강생 수를 기준으로 사전 부하 검사를 거쳐 결정하는 것이 좋습니다.

등록과 결과 확인

  1. 먼저 대상 환경의 Terraform 연결 설정을 확인해 주세요. backend는 인프라 상태를 저장하는 위치이고, TF_DATA_DIR은 그 연결 정보를 보관하는 로컬 디렉터리입니다. 대상 환경에 맞는 backend·TF_DATA_DIR·tfvars를 함께 사용해야 합니다.
  2. 저장소 루트에서 terraform -chdir=IaC plan -var-file=<IaC 기준 변수 파일 경로>로 변경 예정 내역(plan)을 만듭니다. 해당 서버 그룹에 시작·종료 예약이 생기는지, 시각과 대수가 맞는지 검토해야 합니다.
  3. 승인 후 Terraform apply로 예약을 AWS에 등록합니다. AWS 콘솔 Auto Scaling group → Automatic scaling → Scheduled actions에서 두 예약이 등록됐는지 확인해 주세요. Auto Scaling Group(ASG)은 함께 확장·축소하는 서버 그룹입니다.
  4. 시작 예약은 그룹의 최소 대수(min_size)와 목표 대수(desired_capacity)를 capacity로 설정합니다. 시험 시작 전에는 필요한 서버가 InService, 로드 밸런서(ALB)의 대상 상태가 healthy인지 확인해야 합니다.
  5. 종료 예약은 최소 대수를 기본값으로 복원합니다. 이후 요청량에 따른 자동 조절(target tracking)이 실제 서버 수를 점진적으로 줄입니다. 종료 후 대수와 상태를 확인해 주세요. 지난 일정은 tfvars에서 제거해 다음 plan/apply에 반영하는 것이 좋습니다.

일정을 변경하거나 등록 시각을 놓친 경우

  • 각 예약은 지정 날짜에 한 번 실행됩니다. 겹치거나 맞닿는 시험은 하나의 창으로 합쳐야 합니다.
  • 등록은 start 전에 완료해야 합니다. plan 시점에 이미 지난 시작 시각은 예약에서 제외됩니다. 등록을 놓쳤다면 현재 ASG 대수를 확인하고 승인된 수동 확장으로 필요한 서버를 확보해야 합니다. 종료 시각이 미래라면 종료 예약은 등록할 수 있습니다.
  • 진행 중인 창을 취소할 때는 현재 최소·목표 대수와 종료 예약을 함께 확인해야 합니다. 시작 후 일정을 삭제하면 확장된 최소 대수를 복원할 종료 예약도 삭제될 수 있습니다.
  • 생성 이후의 최소·목표 대수는 예약과 자동 조절이 관리합니다. 긴급 조정이 필요하면 승인 후 AWS 콘솔·CLI에서 변경할 수 있습니다. 10대보다 큰 용량이 필요하면 상한 설정인 asg_max_size도 조정해야 합니다.
  • 추가 서버는 기본 온디맨드(on_demand_percentage_above_base = 100)입니다. 값을 0으로 바꾸면 추가분에 Spot을 사용합니다. Spot은 할인된 용량을 사용하지만 AWS가 회수할 수 있으므로 시험 일정과 비용을 고려해 선택해야 합니다.

배포·업데이트·되돌리기

배포할 버전 선택

release는 특정 소스 commit에서 빌드한 웹 애플리케이션(CTFd), SQL 채점 서버(judge), 서버 기동 이미지(AMI)를 묶은 배포 버전입니다. 빌드 도구 Packer가 이미지를 만들고, 구성 목록(manifest)에 이미지 식별값을 기록합니다. Terraform은 이 목록을 사용해 같은 버전의 서비스를 배포합니다. 이미지 병렬 빌드와 빌드 캐시 재사용으로 준비 시간을 줄였습니다.

dev와 운영은 인프라 상태·자원과 버전 선택 경로(channel)를 각각 관리합니다. 배포할 버전을 선택하려면 대상 tfvars에서 다음 값을 확인해 주세요.

설정 동작
artifact_channel dev 또는 main. 배포 버전을 가져올 경로를 선택합니다.
artifact_release_id = null 선택한 channel의 현재 release를 배포합니다.
artifact_release_id에 특정 ID 지정 지정한 release에 버전을 고정합니다. 이후 배포 버전을 바꾸려면 이 값도 갱신하거나 null로 전환해야 합니다.

새 버전 배포와 이전 버전 복원

배포할 때는 다음 순서로 진행해 주세요.

  1. 검토·승인된 코드를 대상 브랜치에 병합합니다.
  2. scripts/build-release --channel <dev 또는 main> --commit <전체 병합 commit SHA>로 release를 빌드합니다.
  3. 대상 환경에서 새 Terraform plan을 만들고 배포 버전·변경 자원을 검토합니다.
  4. 승인 후 apply하고 순차 인스턴스 교체(instance refresh)가 끝날 때까지 확인합니다.
  5. 서비스 상태, 로그인 유지, SQL 실행·제출, 첨부 다운로드를 확인합니다.

이전 버전으로 되돌리려면 먼저 이전 앱과 현재 DB 구조의 호환성을 확인해야 합니다. DB 구조 변경은 migration으로 적용되며, 앱 버전을 되돌려도 현재 DB 구조와 데이터는 유지됩니다. DB 복구가 필요한 경우에는 백업과 복구 절차도 함께 준비해야 합니다.

scripts/set-channel-release --channel <dev 또는 main> --release-id <이전 release ID>로 검증된 버전을 선택하고, artifact_release_id의 고정 여부를 확인한 뒤 plan 검토·승인·apply·기능 확인 순서로 진행합니다.

첨부 파일과 배포 파일 정리

  • 첨부 파일은 접근을 제한하고 암호화한 S3 저장소에 보관해 서버 교체 후에도 유지합니다. 다운로드 주소의 서명 오류도 해결했습니다. 기존 서버에 로컬 첨부가 있다면 S3로 옮겨야 합니다. 이관 후에는 관리자·학생 계정으로 다운로드를 확인해 주세요.
  • 빌드한 이미지 등 배포 파일(artifact)을 정리하려면 먼저 scripts/prune-artifacts로 삭제 대상을 확인해 주세요. 실제 삭제는 --apply로 수행하며, 현재·이전 release와 실행 중 자원이 사용하는 파일은 보호합니다.
  • 학기 종료 시에는 실행 환경 종료 → channel 사용 종료(retire) → 배포 파일 정리 순서로 진행합니다. 배포 문서에 명령과 자원 이관 절차를 정리했습니다.
  • 환경의 데이터 보존 방식은 deployment_mode로 구분합니다. 운영용 persistent는 DB 삭제 보호와 첨부 버킷 보존을 적용합니다. 임시 개발용 ephemeral은 환경 삭제 시 DB·첨부를 함께 폐기할 수 있으므로 필요한 자료를 먼저 보관해야 합니다.

운영 안정성 수정

  • 초기 기동·교체 유예 시간을 확보하고 ALB 상태 검사 경로를 /healthcheck로 통일해 시험 브라우저 제한 때문에 정상 인스턴스가 교체되는 문제를 해결했습니다.
  • DB 연결을 새로 만들 때 Secrets Manager의 현재 비밀번호를 조회하고, 인증 실패 시 갱신·재연결을 한 번 수행해 비밀번호 변경에 대응합니다.
  • 캐시·로그인 상태 저장소인 Valkey Serverless의 여러 키 조회와 캐시 초기화를 보완했습니다. 기존 세션을 유지하면서 클러스터의 cross-slot 오류를 해결합니다.
  • 새 MySQL 설치와 기존 환경 업그레이드에 필요한 DB 구조 변경을 migration으로 적용합니다.

출제 전 문제 세트 일괄 검사

여러 문제의 실행 가능 여부와 채점 설정을 한 번에 확인하는 도구를 제공합니다. 문제 정의를 내보내 임시 MySQL에서 실행하고, 문제별 오류·채점 기준·결과 크기·소요 시간을 보고서로 만듭니다.

  1. 검사할 서비스 주소(CTFD_URL), 관리자 토큰(CTFD_TOKEN), 문제·보고서를 저장할 비공개 경로를 준비합니다.
  2. scripts/export-challenges --out <문제 JSON 경로>로 문제 정의를 내보냅니다.
  3. scripts/review-challenges <문제 JSON 경로> --report <보고서 경로>를 실행합니다. Docker가 검사에 필요한 MySQL과 현재 소스의 judge를 띄웁니다.
  4. 실행 오류가 있으면 초기화 SQL·정답을 수정하고, policy_required가 표시되면 관리자 화면에서 채점 기준을 저장합니다. 수정한 문제를 다시 내보내 검사합니다.
  5. 출제자가 지문과 정답의 의미, 다른 풀이의 정답, 정렬·LIMIT 조건을 검토한 뒤 학생에게 공개합니다.

문제·정답과 관리자 토큰은 접근이 제한된 위치에 보관해야 합니다. 자세한 실행 환경과 한도는 출제 가이드, 도구 사용법과 판정별 조치는 문제 검토 가이드를 참고해 주세요.

검증 결과와 운영 반영

  • dev 배포에서 기본 운영 검사 50/50, 채점·접근·마감·명단·로그 검사 39/39, 실제 학생 브라우저의 SQL 오류·횟수·정상 제출 검사 25/25를 통과했습니다.
  • 새 DB migration과 초기 설정, S3 첨부 업로드·다운로드, 실행 이미지 digest, CloudWatch 수집을 확인했습니다. 서버 교체 중 healthcheck 245회와 기존 세션 확인 41회가 모두 성공했습니다.
  • 공개 문서·가입·시험 제한 관련 테스트와 PC·모바일 브라우저 표시를 확인했습니다. 학번 중복 차단은 MySQL 8.4의 동시 등록 테스트를 포함한 7개 검사, 관련 계정 회귀 및 실제 설정 화면 검사를 통과했습니다.
  • 사용자 승인에 따라 main 병합 commit으로 운영 release를 빌드하고, Terraform plan 검토 후 dev 종료와 운영 배포를 진행합니다. 운영 배포 후 공개 페이지, 로그인, Test/Submit, 학번 중복 안내, 첨부와 로그를 확인합니다.

시험 운영 전에는 사용할 문제의 정렬·표시 형식과 정답을 검토하고, 시험 일정·서버 대수·학번 명단·브라우저 규칙을 설정해야 합니다. 약관·개인정보처리방침의 문의처와 보관 기간도 확정해야 합니다.

상세 절차는 제출·운영 검증 문서와 공개 약관 관리를 참고해 주세요.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 1, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-09-01T11:36:37.071143Z 939e481 PR opened
🔒 Security Review ✅ Completed 2026-09-01T11:38:41.978753Z 939e481 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 939e481465

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread IaC/ec2/ec2.tf
Comment on lines +335 to +336
instance_refresh {
strategy = "Rolling"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Persist uploads before triggering instance refreshes

When a production release changes the launch template or ArtifactRelease tag, this rolling refresh terminates the existing instances. However, platform/docker-compose.production.yml bind-mounts uploads from .data/CTFd/uploads, and the launch template stores that directory on a root EBS volume configured with delete_on_termination = true; consequently, every artifact deployment can permanently delete uploaded challenge/user files. Move uploads to shared durable storage or preserve/migrate them before enabling automatic refreshes.

Useful? React with 👍 / 👎.

Comment thread IaC/foundation/main.tf
Comment on lines +6 to +9
resource "aws_ecr_repository" "ctfd" {
name = local.ctfd_repository_name
image_tag_mutability = "IMMUTABLE"
force_delete = false

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Migrate existing ECR state before creating foundation repositories

For an environment deployed from the parent configuration, the removed module.ami already owns repositories named ${var.prefix}-ctfd and ${var.prefix}-sql-judge; with the default values, these are the exact names this new foundation state attempts to create. Following the documented foundation-first upgrade therefore fails with RepositoryAlreadyExists, while applying the old runtime state removal first schedules the force-deletable repositories and their images for destruction. Add an explicit cross-state move/import migration or use non-conflicting repository names so existing deployments can adopt the pipeline safely.

Useful? React with 👍 / 👎.

pwh9882 and others added 19 commits September 1, 2026 12:19
아티팩트 빌드 시간을 단축한다
Independent review against MySQL 8.4.11 and the built judge container
reproduced three student-reachable problems, all fixed here:

- Event scheduler threads outlived DB and account cleanup (2 of 40
  requests) and the sweeper could not stop them: start MySQL with
  --event-scheduler=DISABLED and make the sweeper kill ct_% sessions by
  name pattern instead of by mysql.user membership.
- The MAX_EXECUTION_TIME optimizer hint overrode the session limit:
  reject the hint in validateSQLQuery.
- DML is not bounded by max_execution_time and its rollback doubled the
  cost of a killed statement: run the graded statement under a separate
  SELECT-only account while a full-privilege init account loads the
  challenge SQL. The graded connection is pinned so the session limit
  applies to that statement.

Also align collation with the MySQL 8 default (utf8mb4_0900_ai_ci) so
ordering matches a student's local MySQL, give sql-judge a /health
healthcheck, and make CTFd wait for it so a fresh instance never accepts
submissions it cannot grade.
scripts/regrade-challenges sends every challenge definition to a judge with
the solution as both solution and submission, records status, latency, row
count and result size, and optionally compares result rows against a
baseline judge (for example the previous go-mysql-server build). Numeric
formatting differences are classified separately from value differences.
The input is a JSON array of challenge definitions only; no user or
submission data is involved.
SQL Judge 실행 엔진을 실제 MySQL 8.4로 교체
Re-grading last semester's 61 challenges on the MySQL judge failed 58 of
them at init: the scripts are MySQL Workbench and mysqldump exports that
manage their own schema and session.

- Skip CREATE/DROP DATABASE|SCHEMA and USE statements and map that
  schema name onto the execution's temporary database, including
  mysqldump's /*!... */ wrapped forms and schema-qualified names such
  as kbo.PLAYER in init and graded statements.
- Drop leading comment lines from each statement; MySQL rejects
  "-----" separators and empty comment-only chunks.
- Carry the init session's sql_mode (for example TRADITIONAL) into the
  graded statement's session, as one local MySQL session would.
- Start MySQL with lower_case_table_names=1 so Salaries and salaries
  match, as on Windows and macOS where the challenges were written.

The re-grade tool now treats float-precision differences as formatting.
judge가 로컬 MySQL 템플릿 형식의 init SQL을 받아들이도록 수정
AUTHORING.md explains how the judge executes init and graded SQL, what a
local MySQL export may contain, how results are compared (unique ORDER BY,
numeric text formats), and which words the filter blocks. The admin create
and update forms link to it, as does the plugin README.
scripts/export-challenges pulls SQL challenge definitions from a CTFd
through the admin API (token from the environment), and
scripts/review-challenges starts a disposable MySQL judge from the checkout,
grades every definition with scripts/regrade-challenges --review, prints
authoring findings, and removes the containers.

The review findings cover execution failures, missing ORDER BY, repeated
sort-key values, ORDER BY expressions the tool cannot check, unrounded
AVG/division results, empty or near-limit results, and slow requests.
REVIEW.md documents the procedure and how to act on each finding so a
future TA or an agent can run it unchanged.
문제 검토 파이프라인 추가 (export, review, 판정)
The plugin's read() always included init_query and solution_query, and
CTFd returns that dict from GET /api/v1/challenges/<id> to every logged-in
user, so any student could fetch the answer key. Only admins receive the
two fields now; the student view never used them. A plugin test in the
CTFd harness locks this in.
학생에게 SQL 문제 정답이 노출되던 API 응답 수정
pwh9882 and others added 14 commits September 5, 2026 06:26
SQL 채점 공정성과 시험 운영 안정성 개선
…onal accounts

Exchange students have no hanyang.ac.kr account. Blocking only gmail.com
would still admit personal Google accounts registered with other addresses,
so the callback now requires a Google Workspace account (the hd claim) and
accepts any Workspace domain by default. GOOGLE_HOSTED_DOMAIN takes a
comma-separated domain list or "*" (the new default). The account picker
hint follows the setting. Login, settings, onboarding and the terms seed no
longer say "HYU Google"; the login page explains that personal Gmail is not
accepted and that students of a university without Google should ask for
an account.
새 설치 SQL 스키마·클러스터 캐시·단일 학번 처리 수정
Google 로그인을 모든 대학교 Workspace 계정에 허용하고 개인 계정은 계속 거부
"Sign up or reset password with a university Google account" wrapped
wherever the button width happened to fall, leaving "account" alone on
the second line. The label now breaks deliberately: "Sign up or reset
password" above, "with a university Google account" below.
CTFd's single "User Name" was both the login handle and the display
name, and it had to be unique, so two students with the same real name
could not both use it. Accounts now have a unique login ID (users.login_id)
used together with the email address to log in, while the display name
(users.name) becomes a nickname that students fill with their real name
and that may repeat; students are told apart by student number and email.

The migration copies each unique existing name into the login ID so
current accounts keep logging in as before. Onboarding asks for the ID,
the nickname, the student number and the terms, all marked as required
with a red asterisk. Settings shows the ID read-only; admins set it in
the user forms and can search by it. Google sign-in keeps the profile
name as the nickname without numbered suffixes. The name-uniqueness
checks in the user schema and the corresponding upstream tests are
replaced by login-ID uniqueness checks.
Google 버튼 문구의 줄바꿈 위치 고정
로그인 ID와 Nickname(본명) 분리, 온보딩 필수 항목 표시
The users API validated the search field against a fixed list, so the
new login ID could not be queried even though the admin page's search
form offers it. Login IDs are private like email addresses, so the API
accepts the field for admins only.
사용자 API 검색에 login_id 필드 허용(관리자 전용)
The login-ID migration copied every unique name into the new ID column so
existing accounts keep logging in. Google-created accounts that had not
finished onboarding have no password and never logged in by name, yet they
received their profile name as the ID, which the onboarding page then
suggested instead of the email's local part. The copy now skips accounts
without a password, and a follow-up migration clears the copies already
made on migrated databases.
비밀번호 없는 계정에는 이름을 ID로 복사하지 않음
@pwh9882 pwh9882 changed the title artifact 배포 파이프라인, MySQL 8.4 judge, 문제 검토 도구, 정답 노출 수정, 시험 대비 ASG 설정을 main에 반영 학생 계정·SQL 채점·시험 운영 개선 및 아티팩트 배포 체계 도입 Sep 8, 2026
pwh9882 and others added 14 commits September 8, 2026 03:16
SQLSTATE 21과 MySQL 오류 1096·1111을 SQL 오류로 분류한다. 학생 Test·Submit의 오류 안내와 횟수 처리, 모범답안 오류 및 실제 장애의 횟수 보존을 검증한다.
학생 SQL 오류 허용 목록을 제거하고 MySQL 오류 응답은 원문을 전달한다. 명시적인 통신·자원 장애와 연결·세션 준비 실패는 횟수를 보존하며 초기화·모범답안 오류는 실행 단계에 따라 구분한다.
@pwh9882
pwh9882 merged commit 4ab70f1 into main Sep 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant