feat(run-agent): send X-Admin-Token so backend AuthGate accepts CI calls

The backend now has a global AuthGateMiddleware: every data route
(workspaces/lookup, upload-agent, sse/...) requires auth, returning
401 {"detail":"authentication required"} otherwise. CI authenticates with
the service token via the X-Admin-Token header (proxies any user_id).

- run_agent_api.py: read ADMIN_API_TOKEN from env, attach X-Admin-Token to
  both httpx clients (all lookup/upload/sse/action/cancel calls); warn if unset
- run-agent.yml: inject ADMIN_API_TOKEN from Gitea repo secret
- Gitea repo secret ADMIN_API_TOKEN registered
- TEST_WORKFLOW.md / .env.example: document ADMIN_API_TOKEN (.env local, secret in CI)

Verified end-to-end: workspaces/lookup for user_id=jhogyu now resolves
(401 -> 200), e.g. Aug_2026_Test -> 3523fa2b-...

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-20 17:02:12 +09:00
co-authored by Claude Opus 4.8
parent 9d86372325
commit 8dfda00c12
4 changed files with 38 additions and 17 deletions
+1
View File
@@ -1 +1,2 @@
GITEA_TOKEN=
ADMIN_API_TOKEN=
+14 -10
View File
@@ -7,18 +7,20 @@
# 요구사항:
# - 저장소 설정에서 Actions 활성화 + act_runner 등록 (runs-on: ubuntu-latest 라벨)
# - 러너가 백엔드에 내부 주소로 접근 가능해야 함 (아래 api_base 참고)
# - Gitea repo secret 'ADMIN_API_TOKEN' 등록 (백엔드 AuthGate 서비스 토큰)
#
# 인증 (중요):
# 공개 URL(https://legalpoc.eroomai.com)은 Google OAuth 프록시(auth.eroomai.com)
# 뒤에 있어서 CI 에서는 302 로 로그인 페이지로 튕긴다. localhost:8800 도 job 이
# 컨테이너 안에서 돌면 컨테이너 자신을 가리켜 못 닿는다.
# → 해결: act_runner 의 job 컨테이너를 AgentBackend 와 같은 도커 네트워크에 붙이고
# 내부 컨테이너 주소로 직접 호출한다(프록시 우회). 내부 백엔드는 /api 접두사 없이
# 루트로 API 를 제공한다(/upload-agent, /sse/agent/...).
# 러너 설정 (서버, /opt/gitea-runner-scale/):
# config.yaml 의 container.network 를 백엔드 네트워크 이름으로 지정
# (docker network ls 로 확인, 보통 agentbackend_agent-network).
# 그러면 api_base = http://agent-backend:8000 (container_name, 내부 포트 8000).
# (1) 네트워크: 공개 URL(legalpoc.eroomai.com)은 OAuth 프록시 뒤라 CI 에서 302.
# localhost:8800 도 job 컨테이너 자신을 가리켜 못 닿음.
# → act_runner job 컨테이너를 AgentBackend 도커 네트워크에 붙이고 내부 컨테이너
# 주소(http://agent-backend:8000, /api 접두사 없음)로 직접 호출.
# 러너 config.yaml 의 container.network = 백엔드 네트워크(보통
# agentbackend_agent-network, docker network ls 로 확인).
# (2) 앱 인증: 백엔드에 전역 AuthGate 가 있어 모든 데이터 라우트(workspaces/lookup,
# upload-agent, sse/... 전부)가 인증을 요구한다. CI 는 서비스 토큰을
# X-Admin-Token 헤더로 보낸다(어느 user_id 로든 대리 호출 허용). 스크립트가
# ADMIN_API_TOKEN 환경변수를 읽어 자동으로 헤더에 넣는다. 값은 Gitea secret
# 'ADMIN_API_TOKEN' 에서 주입(아래 env). 로컬 실행 시에는 .env 로 제공.
#
# 실행 흐름 (SKILL.md §0.6 경로 A):
# workspaces/lookup(이름→UUID) → upload-agent 등록 → SSE start →
@@ -98,6 +100,8 @@ jobs:
- name: Run agent via API
env:
# 백엔드 전역 AuthGate 통과용 서비스 토큰 (Gitea repo secret 에 등록 필요)
ADMIN_API_TOKEN: ${{ secrets.ADMIN_API_TOKEN }}
YAML_PATH: ${{ inputs.yaml_path }}
API_BASE: ${{ inputs.api_base }}
AGENT_USER_ID: ${{ inputs.user_id }}
+13 -5
View File
@@ -19,14 +19,21 @@
프로젝트 루트에 `.env` 파일을 만들고 아래 형식으로 채운다 (값은 실제 토큰):
```dotenv
GITEA_TOKEN=<Gitea Personal Access Token>
GITEA_TOKEN=<Gitea Personal Access Token> # 워크플로우 dispatch / API 용
ADMIN_API_TOKEN=<백엔드 AuthGate 서비스 토큰> # 백엔드 데이터 라우트 인증용
```
- 토큰 발급: Gitea → Settings → Applications → Generate New Token
- 필요한 스코프: **`write:repository`**
- 사용 후에는 **폐기(revoke)** 권장. 만료를 짧게 설정할 것.
- `GITEA_TOKEN` 발급: Gitea → Settings → Applications → Generate New Token, 스코프 **`write:repository`**
- `ADMIN_API_TOKEN`: 백엔드 컨테이너 env `ADMIN_API_TOKEN` 값 (전역 AuthGate 통과용
서비스 토큰). 스크립트가 이 값을 읽어 모든 백엔드 요청에 `X-Admin-Token` 헤더로 보낸다.
없으면 `workspaces/lookup`·`upload-agent`·`sse` 전부 `401 authentication required`.
- 사용 후에는 **폐기(revoke)** 권장.
- 템플릿: [.env.example](.env.example) 참고 (빈 값, 커밋되어도 안전).
> **CI(워크플로우)** 는 `.env` 를 쓰지 않는다. `ADMIN_API_TOKEN` 을 **Gitea repo
> secret** 으로 등록해야 하며(이미 등록됨), 워크플로우가 `${{ secrets.ADMIN_API_TOKEN }}`
> 로 주입한다. `GITEA_TOKEN` 은 dispatch(방법 A)에서만 필요.
---
## 방법 A — Gitea API 로 트리거 (설계 의도: 러너가 실행)
@@ -116,7 +123,8 @@ python scripts/run_agent_api.py \
작업실 이름↔UUID 목록 확인:
```bash
curl -s "http://100.93.221.71:8800/workspaces/lookup?user_id=<user_id>" | python3 -m json.tool
curl -s -H "X-Admin-Token: $ADMIN_API_TOKEN" \
"http://100.93.221.71:8800/workspaces/lookup?user_id=<user_id>" | python3 -m json.tool
```
---
+10 -2
View File
@@ -78,9 +78,17 @@ class AgentRunner:
self.started_at = time.monotonic()
self.deadline = self.started_at + self.max_runtime
self.sse_client = httpx.Client(timeout=httpx.Timeout(30.0))
# 백엔드 전역 AuthGate 통과용 서비스 토큰 (모든 데이터 라우트에 필수).
# X-Admin-Token 은 어느 user_id 로든 대리 호출을 허용한다. 값은 환경변수로만
# 받는다(코드/로그에 노출 금지) — 로컬은 .env, CI 는 Gitea secret.
admin_token = os.environ.get("ADMIN_API_TOKEN", "")
default_headers = {"X-Admin-Token": admin_token} if admin_token else {}
if not admin_token:
log("WARNING: ADMIN_API_TOKEN 미설정 — 인증이 필요한 백엔드는 401 로 거부됩니다")
self.sse_client = httpx.Client(timeout=httpx.Timeout(30.0), headers=default_headers)
# action/cancel 등 단발 요청은 SSE 스트림과 분리된 클라이언트로 보낸다
self.api_client = httpx.Client(timeout=60.0)
self.api_client = httpx.Client(timeout=60.0, headers=default_headers)
self.events_file = (self.output_dir / "events.jsonl").open("a", encoding="utf-8")