콘텐츠로 이동

로테이션 확인

이 가이드는 클라우드 공급자에서 로테이션된 시크릿이 바로 다음 자동화 kickoff에서 적용됨을 검증하는 방법을 보여줍니다 — 재배포 없음, 워커 재시작 없음. Workload Identity 기반 자격 증명(AWS, GCP, Azure)을 구성한 경우에만 관련됩니다. 정적 자격 증명 배포는 로테이션 후 재배포가 필요합니다. 여기에서는 확인할 것이 없습니다.

아래 레시피는 하나의 도구, 하나의 에이전트, 하나의 작업으로 구성된 작은 자체 포함된 Crew를 사용합니다. Crew 프롬프트는 시크릿 값을 절대 참조하지 않으며 — 대신 도구가 os.environ에서 이를 읽고 본 것의 SHA-256 fingerprint를 보고합니다. 클라우드 공급자에서 시크릿을 로테이션하고, 다시 kickoff하면 fingerprint가 변경됩니다.

이 검증을 실행하기 전에:

  • WI 기반 Secret Provider Credential이 구성되어 있어야 합니다(AWS, GCP, Azure).
  • Secret = true, 키 API_KEY(또는 원하는 이름 — 아래 도구를 일치시키도록 조정)로 클라우드 공급자의 시크릿을 참조하는 배포의 환경 변수.
  • 클라우드 공급자에서 시크릿 값을 업데이트할 방법(CLI 액세스 또는 클라우드 콘솔).
  • HTTP를 통해 배포를 kickoff할 방법(curl, Postman, 또는 CrewAI Platform의 Run 탭).

이 예제는 crew.py를 통해 Python 도구를 연결하므로 클래식 crew 프로젝트를 만듭니다:

Terminal window
crewai create crew rotation_verifier --classic --skip_provider
cd rotation_verifier

src/rotation_verifier/tools/custom_tool.py를 시크릿 기반 환경 변수를 읽고 fingerprint를 반환하는 도구로 교체합니다:

"""Tool that verifies a runtime-injected secret without leaking the value.
Reads the secret-backed env var (populated by the workload-identity
secrets manager at kickoff time) and returns a stable fingerprint. Never
echo raw credential values into LLM output or logs in production code —
the fingerprint alone is sufficient to confirm rotation worked.
"""
from __future__ import annotations
import hashlib
import os
from crewai.tools import BaseTool
# Match the deployment environment variable's `key` field.
ENV_VAR_NAME = "API_KEY"
class CredentialEchoTool(BaseTool):
name: str = "credential_echo"
description: str = (
"Read the API credential from the worker's environment and return a "
"fingerprint summary. Use this exactly once when asked to verify the "
"current credential. Takes no arguments."
)
def _run(self) -> str:
value = os.environ.get(ENV_VAR_NAME)
if not value:
return (
f"ERROR: {ENV_VAR_NAME} env var is not set. The workload-"
"identity secret fetch did not run, or the deployment is "
"missing the secret-backed env var."
)
fingerprint = hashlib.sha256(value.encode()).hexdigest()[:12]
return f"Authenticated. credential.fingerprint=sha256:{fingerprint}"

3단계 — 기본 에이전트 및 작업 구성 교체

섹션 제목: “3단계 — 기본 에이전트 및 작업 구성 교체”

Crew에는 하나의 에이전트와 하나의 작업이 있습니다 — 둘 다 시크릿 값을 절대 언급하지 않는 설명을 가지므로, 작업 키가 로테이션 전반에 걸쳐 안정적으로 유지됩니다.

credential_checker:
role: >
Credential Verifier
goal: >
Confirm that the workload-identity-backed secret reached this worker
process and report a fingerprint of the current value.
backstory: >
You are a no-nonsense reliability engineer responsible for verifying
that secrets fetched at runtime via workload identity are present
and fresh. You always use the credential_echo tool exactly once and
report the result verbatim — you never make up values.
verify_credential_task:
description: >
Use the credential_echo tool to read the runtime-injected credential
and produce a one-line confirmation. The current year is {current_year}
(use it only in the timestamp; do not transform the credential output).
expected_output: >
A single line in the form:
"[{current_year}] <credential_echo tool's exact output>"
agent: credential_checker
from crewai import Agent, Crew, Process, Task
from crewai.project import CrewBase, agent, crew, task
from crewai.agents.agent_builder.base_agent import BaseAgent
from rotation_verifier.tools.credential_echo_tool import CredentialEchoTool
@CrewBase
class RotationVerifierCrew():
"""Single-task crew that verifies a workload-identity-backed secret
was successfully fetched at runtime.
Rotate the underlying secret in the cloud provider, kickoff again, and
the credential fingerprint in the agent's report changes — without any
re-deploy, worker restart, or input change. The crew prompt itself
never references the secret value.
"""
agents: list[BaseAgent]
tasks: list[Task]
@agent
def credential_checker(self) -> Agent:
return Agent(
config=self.agents_config["credential_checker"],
tools=[CredentialEchoTool()],
verbose=True,
)
@task
def verify_credential_task(self) -> Task:
return Task(config=self.tasks_config["verify_credential_task"])
@crew
def crew(self) -> Crew:
return Crew(
agents=self.agents,
tasks=self.tasks,
process=Process.sequential,
verbose=True,
)

5단계 — 배포 및 시크릿 환경 변수 구성

섹션 제목: “5단계 — 배포 및 시크릿 환경 변수 구성”

다른 Crew와 마찬가지로 이 Crew를 CrewAI Platform에 배포합니다. 그런 다음 배포의 Environment Variables 페이지에서:

  • Key: API_KEY (도구의 ENV_VAR_NAME과 일치해야 함)
  • Value Source: AWS WI 또는 GCP WI에서 설정한 WI 기반 자격 증명
  • Secret Name: 클라우드 공급자의 Secret Manager에 있는 시크릿 이름

<DEPLOYMENT_AUTH_TOKEN><DEPLOYMENT_HOST>를 배포의 Run 탭에 있는 값으로 교체합니다.

Terminal window
curl -m 60 \
-H "Authorization: Bearer <DEPLOYMENT_AUTH_TOKEN>" \
-H "Content-Type: application/json" \
-X POST https://<DEPLOYMENT_HOST>/kickoff \
-d '{"inputs":{"current_year":"2026"}}'

kickoff가 완료되면(몇 초), 에이전트의 출력을 확인합니다. 다음과 같이 표시됩니다:

[2026] Authenticated. credential.fingerprint=sha256:004421b993c9

fingerprint를 기록합니다. 그 해시는 클라우드 공급자에 현재 있는 어떤 시크릿 값과 고유하게 연결되어 있습니다.

7단계 — 클라우드 공급자에서 시크릿 로테이션

섹션 제목: “7단계 — 클라우드 공급자에서 시크릿 로테이션”
AWS
Terminal window
aws secretsmanager update-secret \
--region <REGION> \
--secret-id <SECRET_NAME> \
--secret-string "rotated value"
GCP

새 버전을 추가합니다(Secret Manager는 항상 latest를 읽음):

Terminal window
echo -n "rotated value" | gcloud secrets versions add <SECRET_NAME> \
--data-file=- \
--project=<YOUR_PROJECT_ID>
Azure
Terminal window
az keyvault secret set \
--vault-name <VAULT_NAME> \
--name <SECRET_NAME> \
--value "rotated value"

8단계 — 두 번째 Kickoff 실행 및 비교

섹션 제목: “8단계 — 두 번째 Kickoff 실행 및 비교”
Terminal window
curl -m 60 \
-H "Authorization: Bearer <DEPLOYMENT_AUTH_TOKEN>" \
-H "Content-Type: application/json" \
-X POST https://<DEPLOYMENT_HOST>/kickoff \
-d '{"inputs":{"current_year":"2026"}}'

이제 에이전트의 출력은 다른 fingerprint를 보여줍니다:

[2026] Authenticated. credential.fingerprint=sha256:e2fc89848f72

이는 재배포, 워커 재시작 또는 기타 운영자 작업 없이 실행 중인 배포에서 로테이션이 적용되었음을 증명합니다.

이것이 검증하는 것 — 그리고 검증하지 않는 것

섹션 제목: “이것이 검증하는 것 — 그리고 검증하지 않는 것”

검증하는 것:

  • CrewAI Platform에서 WI OIDC 토큰 발급이 작동합니다.
  • 클라우드 측 신뢰(AWS의 IAM OIDC 공급자, GCP의 Workload Identity Pool, Azure의 Federated Identity Credential)가 토큰을 수락합니다.
  • 클라우드 측 ID(IAM Role / GCP 서비스 계정 / Entra App Registration)가 시크릿을 읽을 수 있는 액세스 권한을 가집니다.
  • 시크릿 값이 kickoff 시점에 워커 프로세스의 os.environ에 도달합니다.
  • 후속 로테이션이 다음 kickoff에 전파됩니다.

검증하지 않는 것:

  • 실제 프로덕션 Crew가 로테이션을 우아하게 처리하는지 — 예를 들어, 시작 시 환경 변수를 한 번만 읽는 장기 실행 작업은 작업이 끝날 때까지 이전 값을 계속 사용합니다. 적절히 계획하세요: 모듈 임포트 시가 아닌 사용 시점에 시크릿을 읽으세요.

왜 프롬프트에서 직접 시크릿을 참조하지 않나요?

섹션 제목: “왜 프롬프트에서 직접 시크릿을 참조하지 않나요?”

더 간단해 보이는 데모는 시크릿 값을 작업 설명에 직접 넣고(예: “{api_key}에 대해 조사”) 프롬프트를 검사하는 것입니다. 그렇게 하지 마세요. 두 가지 이유:

  1. LLM 호출 트레이스와 공급자 측 로그에 시크릿이 유출됩니다. 트레이스 액세스가 있는 모든 사람이 읽을 수 있습니다.
  2. 모든 kickoff에서 작업 설명이 변경됩니다. CrewAI Platform은 설명의 MD5 해시로 작업을 식별합니다. 로테이션되는 값은 kickoff마다 해시가 변경되어 배포 시간 → 런타임 작업 매핑이 깨집니다. 증상: 작업 레코드가 무한정 pending_run으로 표시되거나 다중 작업 Crew의 일부 작업만 등록됩니다.

이 가이드의 도구 기반 패턴은 두 문제를 모두 회피합니다: 프롬프트는 정적이고, 도구가 런타임에 환경 변수를 읽으며, 값의 fingerprint만 LLM에 도달합니다.

  • Secrets Manager 개요로 돌아가기
  • 검증되면 검증 Crew를 삭제합니다. 실제 Crew는 동일한 패턴을 따라야 합니다: 시크릿은 도구 내부의 os.environ을 통해 액세스되며, 절대 프롬프트에 치환되지 않습니다.