Skip to Content
문서보안

보안 모델

이 문서는 Deplite의 보안 메커니즘을 한 곳에 모았어요.
도입 검토용 보안 리뷰나 내부 감사 자료로 활용하실 수 있어요.

핵심 원칙

  1. 실행 분리: 시크릿과 실행 로그는 Agent 밖으로 나가지 않아요.
  2. 모든 명령은 서명: ED25519로 서명되고, nonce로 재생 공격을 막아요.
  3. 토큰은 해시 저장: API 토큰은 SHA256으로 저장되고, 평문은 발급 시점에만 보여요.
  4. 모든 동작은 기록: 누가·언제·무엇을 했는지 audit log로 남아요.

ED25519 키 쌍

소유자어디에 보관
Agent 개인키Agent./creds/agent.key (chmod 600)
Agent 공개키Server등록 시 전송, DB의 agents.publicKey에 저장
Server 개인키ServerKMS 또는 환경변수
Server 공개키Agent등록 응답으로 전달, ./creds/server.pub

Agent → Server 요청 서명

모든 요청 헤더에 다음이 들어가요.

X-Agent-Id: <agentId> X-Timestamp: 1717180800 X-Nonce: a1b2c3d4... (hex 16바이트) X-Signature: <base64 ed25519>

서명 대상 문자열 (canonical):

<timestamp>\n <nonce>\n <UPPERCASE-METHOD>\n <path-with-query>\n <sha256-hex-of-body>

서버 검증:

  • X-Timestamp 현재 시각 ±60초
  • X-Nonce LRU 캐시(TTL 10분)에 없는가
  • 서명이 등록된 공개키로 검증되는가

Server → Agent 명령 서명 (Deploy)

SSE deploy 이벤트의 페이로드:

{ "payload": "<base64-json>", "signature": "<base64-ed25519>" }

Agent 검증:

  • payload JSON 정규화(정렬된 키)
  • server.pub로 ED25519 검증
  • issued_at 현재 시각 ±60초
  • nonce Agent 측 LRU 캐시에 없는가

API Token

prefix:tk_ + 32바이트 hex (총 64자 본문)
  • DB에는 SHA256(token)만 저장
  • 발급 직후 1회만 평문 노출 (이후 복원 불가)
  • 회수 시 revokedAt 설정 (즉시 무효)
  • 만료 시 expiresAt 설정 가능

스코프

  • agent: 특정 Agent에 명령 (Agent ID 목록 필수)
  • trigger: 특정 Trigger의 webhook 호출 (Trigger ID 목록 필수)
  • storage: 파일 스토리지 접근. binding ID와 권한(read/write/delete)을 세분화

토큰 레이트 리밋

분/시간/일 단위로 호출 횟수를 제한할 수 있어요. 초과 시 HTTP 429.

JWT (사용자 인증)

  • Firebase Authentication 사용
  • 헤더: Authorization: Bearer <jwt>
  • 매 요청마다 검증

Slack 서명

Slack에서 들어오는 모든 콜백은 다음 헤더로 검증해요.

  • x-slack-signature (HMAC-SHA256)
  • x-slack-request-timestamp

타임스탬프 ±5분, HMAC 일치 시에만 처리해요.

시크릿 격리

워크플로우 YAML의 secrets: 필드는 환경변수 이름만 적어요.

secrets: - DATABASE_URL - SLACK_WEBHOOK
  • 값은 Agent 머신의 환경변수(/etc/deplite/agent.env 등)에서 주입
  • stdout에서 해당 값이 보이면 자동으로 ***로 마스킹
  • 시크릿 이름 목록은 메타데이터로 서버에 보고되지만, 값은 절대 전송 안 됨

Agent 회수 (Revoke)

대시보드에서 Agent를 회수하면 다음이 일어나요.

  1. DB의 Agent status: revoked, revokedAt 설정
  2. 다음 SSE 응답으로 revoke 이벤트 전송
  3. Agent는 받자마자 종료
  4. 이후 같은 agent.key로는 어떤 요청도 거부됨

Audit Log

조직 내 중요한 모든 동작이 기록돼요.

액션의미
trigger.run_manual대시보드 수동 실행
trigger.force_runforce=true 실행
job.dispatch서버가 Agent로 실행 명령 전송
job.rejected리밋/큐 초과로 거부
agent.enrollAgent 등록
agent.revokeAgent 회수
api_token.create토큰 발급
api_token.revoke토큰 회수
slack.installSlack 워크스페이스 연결

metadata 필드에 부가 정보(워크플로우, ref, reason 등)가 담겨요.

책임 분담 (Responsibility Matrix)

항목Deplite ServerAgent (당신)
워크플로우 정의메타데이터만실제 YAML
시크릿 값보관 안 함환경변수로 주입
실행 로그 원본보관 안 함./logs/jobs/
stdout 요약summary만 보관verbose는 로컬만
명령 서명Server 개인키Agent 개인키
토큰 발급/검증DB(해시)
감사 로그보관

실전 예시

요청 서명을 직접 만들어 보기 (Go)

import ( "crypto/ed25519" "crypto/rand" "crypto/sha256" "encoding/base64" "encoding/hex" "fmt" "time" ) func signRequest(priv ed25519.PrivateKey, method, path string, body []byte) map[string]string { ts := fmt.Sprintf("%d", time.Now().Unix()) nonceBytes := make([]byte, 16) rand.Read(nonceBytes) nonce := hex.EncodeToString(nonceBytes) bodyHash := sha256.Sum256(body) canonical := fmt.Sprintf("%s\n%s\n%s\n%s\n%x", ts, nonce, method, path, bodyHash) sig := ed25519.Sign(priv, []byte(canonical)) return map[string]string{ "X-Agent-Id": "ag-001", "X-Timestamp": ts, "X-Nonce": nonce, "X-Signature": base64.StdEncoding.EncodeToString(sig), } }

실제 요청 헤더 예:

POST /agent/heartbeat HTTP/1.1 Host: api.deplite.io X-Agent-Id: ag-001-2f3e-4a5b X-Timestamp: 1748700000 X-Nonce: a1b2c3d4e5f6789012345678abcdef00 X-Signature: dGhpc0lzQUR1bW15U2lnbmF0dXJlRm9yRG9jc1B1cnBvc2VPbmx5PQ== Content-Type: application/json {"agent_version":"0.2.0","running_jobs":[],"workflow_count":5}

API 토큰 로테이션

/rotate는 새 평문 토큰을 발급하고 옛 토큰을 즉시 무효화해요. CI 환경변수만 교체하면 끝.

# 1. 회전 (응답으로 새 plaintext 토큰 한 번만 노출) curl -X POST https://app.deplite.io/api/orgs/<orgId>/api-tokens/<id>/rotate \ -H "Authorization: Bearer $JWT" # 응답 { "id": "tok-001", "plainToken": "tk_NEW_64_HEX_..." }
# 2. CI 시크릿 업데이트 (GitHub 예시) gh secret set DEPLITE_TOKEN --body "tk_NEW_64_HEX_..."

Agent 키 유출 대응 절차

  1. 대시보드에서 해당 Agent Revoke → DB에 revokedAt, 다음 SSE로 revoke 이벤트
  2. Agent 머신 접근하여 ./creds/* 모두 삭제
  3. 새 Enrollment Token 발급
  4. DEPLITE_TOKEN을 새 값으로 재시작 → 새 키쌍 자동 생성·등록
  5. Audit log에서 의심스러운 job.dispatch 기록 검토

”이 요청이 정말 우리 Agent에서 왔는가” 검증 예 (서버 측 의사코드)

function verify(req) { const ts = parseInt(req.headers['x-timestamp']) if (Math.abs(Date.now()/1000 - ts) > 60) throw new Error('clock skew') if (nonceCache.has(req.headers['x-nonce'])) throw new Error('replay') nonceCache.set(req.headers['x-nonce'], true, 600) // TTL 10분 const agent = db.agents.findById(req.headers['x-agent-id']) if (agent.status === 'revoked') throw new Error('revoked') const canonical = `${ts}\n${nonce}\n${method}\n${path}\n${sha256(body)}` if (!ed25519.verify(agent.publicKey, canonical, sig)) throw new Error('bad sig') }

세 단계(시계·재생·서명) 모두 통과해야 요청이 받아들여져요.

다음으로

  • API — 토큰 발급·회수 엔드포인트
  • Agent 운영./creds 권한과 백업
최종 수정 일자: