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:
@@ -1 +1,2 @@
|
||||
GITEA_TOKEN=
|
||||
ADMIN_API_TOKEN=
|
||||
|
||||
@@ -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
@@ -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
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
@@ -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")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user