로테이션 확인
이 가이드는 클라우드 공급자에서 로테이션된 시크릿이 바로 다음 자동화 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 탭).
1단계 — 검증 Crew 스캐폴딩
섹션 제목: “1단계 — 검증 Crew 스캐폴딩”이 예제는 crew.py를 통해 Python 도구를 연결하므로 클래식 crew 프로젝트를 만듭니다:
crewai create crew rotation_verifier --classic --skip_providercd rotation_verifier2단계 — Credential Echo 도구 추가
섹션 제목: “2단계 — Credential Echo 도구 추가”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-identitysecrets manager at kickoff time) and returns a stable fingerprint. Neverecho 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 hashlibimport 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_checker4단계 — Crew 클래스 연결
섹션 제목: “4단계 — Crew 클래스 연결”from crewai import Agent, Crew, Process, Taskfrom crewai.project import CrewBase, agent, crew, taskfrom crewai.agents.agent_builder.base_agent import BaseAgent
from rotation_verifier.tools.credential_echo_tool import CredentialEchoTool
@CrewBaseclass 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에 있는 시크릿 이름
6단계 — 첫 번째 Kickoff 실행
섹션 제목: “6단계 — 첫 번째 Kickoff 실행”<DEPLOYMENT_AUTH_TOKEN>과 <DEPLOYMENT_HOST>를 배포의 Run 탭에 있는 값으로 교체합니다.
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:004421b993c9fingerprint를 기록합니다. 그 해시는 클라우드 공급자에 현재 있는 어떤 시크릿 값과 고유하게 연결되어 있습니다.
7단계 — 클라우드 공급자에서 시크릿 로테이션
섹션 제목: “7단계 — 클라우드 공급자에서 시크릿 로테이션”aws secretsmanager update-secret \ --region <REGION> \ --secret-id <SECRET_NAME> \ --secret-string "rotated value"새 버전을 추가합니다(Secret Manager는 항상 latest를 읽음):
echo -n "rotated value" | gcloud secrets versions add <SECRET_NAME> \ --data-file=- \ --project=<YOUR_PROJECT_ID>az keyvault secret set \ --vault-name <VAULT_NAME> \ --name <SECRET_NAME> \ --value "rotated value"8단계 — 두 번째 Kickoff 실행 및 비교
섹션 제목: “8단계 — 두 번째 Kickoff 실행 및 비교”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}에 대해 조사”) 프롬프트를 검사하는 것입니다. 그렇게 하지 마세요. 두 가지 이유:
- LLM 호출 트레이스와 공급자 측 로그에 시크릿이 유출됩니다. 트레이스 액세스가 있는 모든 사람이 읽을 수 있습니다.
- 모든 kickoff에서 작업 설명이 변경됩니다. CrewAI Platform은 설명의 MD5 해시로 작업을 식별합니다. 로테이션되는 값은 kickoff마다 해시가 변경되어 배포 시간 → 런타임 작업 매핑이 깨집니다. 증상: 작업 레코드가 무한정
pending_run으로 표시되거나 다중 작업 Crew의 일부 작업만 등록됩니다.
이 가이드의 도구 기반 패턴은 두 문제를 모두 회피합니다: 프롬프트는 정적이고, 도구가 런타임에 환경 변수를 읽으며, 값의 fingerprint만 LLM에 도달합니다.
다음 단계
섹션 제목: “다음 단계”- Secrets Manager 개요로 돌아가기
- 검증되면 검증 Crew를 삭제합니다. 실제 Crew는 동일한 패턴을 따라야 합니다: 시크릿은 도구 내부의
os.environ을 통해 액세스되며, 절대 프롬프트에 치환되지 않습니다.