Conversation
Packer 기반 artifact release pipeline과 dev 배포 구조 추가
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 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".
| instance_refresh { | ||
| strategy = "Rolling" |
There was a problem hiding this comment.
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 👍 / 👎.
| resource "aws_ecr_repository" "ctfd" { | ||
| name = local.ctfd_repository_name | ||
| image_tag_mutability = "IMMUTABLE" | ||
| force_delete = false |
There was a problem hiding this comment.
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 👍 / 👎.
아티팩트 빌드 시간을 단축한다
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.
SQL 문제 출제 가이드 추가
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 응답 수정
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로 복사하지 않음
SQLSTATE 21과 MySQL 오류 1096·1111을 SQL 오류로 분류한다. 학생 Test·Submit의 오류 안내와 횟수 처리, 모범답안 오류 및 실제 장애의 횟수 보존을 검증한다.
학생 SQL 오류 허용 목록을 제거하고 MySQL 오류 응답은 원문을 전달한다. 명시적인 통신·자원 장애와 연결·세션 준비 실패는 횟수를 보존하며 초기화·모범답안 오류는 실행 단계에 따라 구분한다.
SQL Playzone의 학생 로그인, SQL 채점, 시험 운영, 배포 방식을 정비합니다. 학생은 학교 계정으로 처음 등록한 뒤 ID·비밀번호로 접속하고, 문제에 공개된 기준에 따라 MySQL 8.4에서 채점받습니다. 운영자는 관리자 화면에서 시험 참여 규칙을 관리하고, 시험 전에 서버를 예약 확장할 수 있습니다.
학생 계정과 로그인
가입부터 시험 접속까지
기존 비밀번호 계정은 중복되지 않는 이전 사용자 이름을 로그인 ID로 이전합니다. 첫 계정 설정을 마치기 전인 Google 계정은 학생이 직접 ID를 정해야 합니다. API 토큰 발급·인증은 관리자 계정에 제공하며, 학생은 로그인 세션을 사용합니다. 교내 공유 IP에서의 동시 접속을 고려해 로그인 요청 한도와 화면 안내도 조정했습니다.
Google 계정 허용 범위 설정
위치: AWS Secrets Manager의 애플리케이션 설정에 있는
GOOGLE_HOSTED_DOMAIN. 로컬 실행에서는config.ini또는 환경변수로 설정할 수 있습니다.*— 기본값hanyang.ac.krhanyang.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, 문자열'NULL', 빈 문자열도 각각 구별합니다.읽기 전용 권한과 실행 시간·결과 크기·동시 처리 제한으로 쿼리를 격리합니다. 제한 함수 검사는 일반 식별자와 문자열을 구분해 정상 SQL의 오탐을 줄였습니다. 초기화 SQL과 정답 SQL 조회는 관리자에게만 허용합니다.
출제자가 설정하는 정렬·표시 형식
위치: 관리자 Challenges → SQL 문제 생성·수정 화면. 지문을 확정한 뒤 아래 두 필드를 설정해 저장해 주세요.
1 asc, 2 desc3, 4예를 들어 “평균을 소수 둘째 자리로 반올림한 값”을 요구하려면 지문에 기준을 적고 정답 SQL에
ROUND를 사용할 수 있습니다. “항상 소수 두 자리로 표시”까지 요구한다면 해당 열을 표시 형식 평가에 추가하고 정답 출력도 그 형식으로 맞춰야 합니다.기존 문제도 사용 전에 기준을 저장해야 합니다. 저장 전에는 “판정 불가”로 안내하며 제출 횟수를 보존합니다. 두 입력을 비운 채 저장하면 “행 순서 무관·숫자 값 비교” 기준이 적용됩니다.
저장 후에는 학생 화면의 공개 기준을 확인하고, 관리자 Test로 정답·다른 풀이의 정답·오답을 실행해 보는 것이 좋습니다.
LIMIT으로 일부 행을 선택하는 문제는 경계의 동률 때문에 답이 달라질 수 있으므로 지문에 행 선택 기준을 명확히 적어야 합니다.제출 마감과 시도 횟수
학생의 Test는 실행 결과 확인에, Submit은 성적 제출에 사용합니다. 두 경로에 문제 공개 상태, 선수 문제, 팀 소속, 시작 시각, 일시 중지 검사를 일관되게 적용합니다.
0은 무제한입니다. 횟수에는 저장된 오답을 반영하고 Test는 실행 확인용으로 처리합니다.채점 장애와 학생 문의 대응
학생 SQL 오류·실행 한도 위반·결과 불일치는 오답으로 처리합니다. 문제 초기화·정답 오류나 채점 서버·통신 장애는 판정 불가로 안내하고 오답 횟수를 보존합니다. 화면에서는 저장 실패, 세션 만료, 접근 제한, 채점 오류를 구분하며 SQL 결과의 안전한 표시도 보완했습니다.
판정 불가가 반복되면 해당 문제의 기준·초기화 SQL·정답을 관리자 Test로 확인하고 채점 서버(judge)의 상태와 로그를 점검해야 합니다. 복구 후에는 학생에게 다시 제출하도록 안내해 주세요. 재제출은 새로운 접수 시각으로 마감을 판단하므로, 마감이 지난 학생의 처리는 제출·장애 기록을 대조해 결정해야 합니다.
시험 참여와 접속 규칙
명단·시험 브라우저·단일 세션 설정
위치: 관리자 Plugins → Exam Mode (
/admin/exam_mode/). 세 스위치는 각각 관리하며 기본값은 모두 off입니다. 화면에서 저장하면 적용됩니다.Student ID Number사용자 필드를 준비하고, 허용 학번을 한 줄에 하나씩 입력한 뒤 Save & ApplyTrustlockbrowser이며, 브라우저가 보내는 식별 문자열에 이 값이 포함되는지 대소문자 구분 없이 검사합니다.관리자는 일반 브라우저와 여러 세션으로 운영할 수 있습니다. 활성 규칙은 관리자 배너에 표시됩니다. 명단은 기존 로그인에도 적용하며, 단일 학번과 앞자리 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대를 가정한 예시입니다.YYYY-MM-DDTHH:mm:ss형식으로 입력해야 합니다. 시간대 변환은 Terraform이 수행하므로 예시처럼 시간대 접미사를 생략해 주세요.asg_min_size10입니다. 문제 데이터·쿼리와 동시 수강생 수를 기준으로 사전 부하 검사를 거쳐 결정하는 것이 좋습니다.asg_max_size범위의 정수이며 기본 범위는 1등록과 결과 확인
terraform -chdir=IaC plan -var-file=<IaC 기준 변수 파일 경로>로 변경 예정 내역(plan)을 만듭니다. 해당 서버 그룹에 시작·종료 예약이 생기는지, 시각과 대수가 맞는지 검토해야 합니다.min_size)와 목표 대수(desired_capacity)를 capacity로 설정합니다. 시험 시작 전에는 필요한 서버가 InService, 로드 밸런서(ALB)의 대상 상태가 healthy인지 확인해야 합니다.일정을 변경하거나 등록 시각을 놓친 경우
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에서 다음 값을 확인해 주세요.
dev또는main. 배포 버전을 가져올 경로를 선택합니다.새 버전 배포와 이전 버전 복원
배포할 때는 다음 순서로 진행해 주세요.
scripts/build-release --channel <dev 또는 main> --commit <전체 병합 commit SHA>로 release를 빌드합니다.이전 버전으로 되돌리려면 먼저 이전 앱과 현재 DB 구조의 호환성을 확인해야 합니다. DB 구조 변경은 migration으로 적용되며, 앱 버전을 되돌려도 현재 DB 구조와 데이터는 유지됩니다. DB 복구가 필요한 경우에는 백업과 복구 절차도 함께 준비해야 합니다.
scripts/set-channel-release --channel <dev 또는 main> --release-id <이전 release ID>로 검증된 버전을 선택하고,artifact_release_id의 고정 여부를 확인한 뒤 plan 검토·승인·apply·기능 확인 순서로 진행합니다.첨부 파일과 배포 파일 정리
scripts/prune-artifacts로 삭제 대상을 확인해 주세요. 실제 삭제는--apply로 수행하며, 현재·이전 release와 실행 중 자원이 사용하는 파일은 보호합니다.deployment_mode로 구분합니다. 운영용persistent는 DB 삭제 보호와 첨부 버킷 보존을 적용합니다. 임시 개발용ephemeral은 환경 삭제 시 DB·첨부를 함께 폐기할 수 있으므로 필요한 자료를 먼저 보관해야 합니다.운영 안정성 수정
/healthcheck로 통일해 시험 브라우저 제한 때문에 정상 인스턴스가 교체되는 문제를 해결했습니다.출제 전 문제 세트 일괄 검사
여러 문제의 실행 가능 여부와 채점 설정을 한 번에 확인하는 도구를 제공합니다. 문제 정의를 내보내 임시 MySQL에서 실행하고, 문제별 오류·채점 기준·결과 크기·소요 시간을 보고서로 만듭니다.
CTFD_URL), 관리자 토큰(CTFD_TOKEN), 문제·보고서를 저장할 비공개 경로를 준비합니다.scripts/export-challenges --out <문제 JSON 경로>로 문제 정의를 내보냅니다.scripts/review-challenges <문제 JSON 경로> --report <보고서 경로>를 실행합니다. Docker가 검사에 필요한 MySQL과 현재 소스의 judge를 띄웁니다.policy_required가 표시되면 관리자 화면에서 채점 기준을 저장합니다. 수정한 문제를 다시 내보내 검사합니다.문제·정답과 관리자 토큰은 접근이 제한된 위치에 보관해야 합니다. 자세한 실행 환경과 한도는 출제 가이드, 도구 사용법과 판정별 조치는 문제 검토 가이드를 참고해 주세요.
검증 결과와 운영 반영
시험 운영 전에는 사용할 문제의 정렬·표시 형식과 정답을 검토하고, 시험 일정·서버 대수·학번 명단·브라우저 규칙을 설정해야 합니다. 약관·개인정보처리방침의 문의처와 보관 기간도 확정해야 합니다.
상세 절차는 제출·운영 검증 문서와 공개 약관 관리를 참고해 주세요.