CI 의 장기 시크릿을 OIDC 로 모두 교체하고 6개월 후

47 개의 장기 AWS access key 를 OIDC workload identity 로 갈아끼웠다. 6개월 후 감사 trail 이 한 배포당 47줄에서 1줄로 줄었다. 마이그레이션 6단계와 그 사이 함정.

백재민
백재민
CollabOps 창업자
CI 의 장기 시크릿을 OIDC 로 모두 교체하고 6개월 후

작년 봄, 우리 고객 중 한 곳에서 기밀 사고 가 있었다. CI 에 박혀 있던 AWS IAM 사용자의 access key 가 어디선가 유출됐다. 어디서 유출됐는지를 알아내는 데 11일이 걸렸다 — 그 키는 3년 동안 회전한 적이 없었고, 사용한 사람·시스템·잡 ID 가 모든 로그에 같은 형태로 찍혀 있어서 분간이 불가능했다.

그 사고 이후 우리는 장기 시크릿 을 CI 에서 전부 추방하는 계획을 세웠다. 6개월 걸렸고, 47개의 access key 가 0개 가 됐다. 감사 trail 도 한 배포당 47줄에서 1줄로 줄었다. 이 글은 그 6단계의 이주와, 사이에 부서졌던 가정들이다.

OIDC workload identity 가 정확히 무엇인가

CI 잡이 그 잡임을 증명하는 단기 토큰 을 들고 가서, 클라우드 측이 그 토큰의 서명자(=ID provider)클레임 을 검증하면 짧게 사용 가능한 자격증명 을 발급해 주는 흐름.

이 한 줄에 모든 게 있다. 시크릿이 미리 박혀 있지 않고, 잡 시작 시 토큰을 들고 가면 클라우드 측이 그 잡이 들어간 시점에만 유효한 자격 을 내준다.

CI 워커               IdP (예: GitHub OIDC)            클라우드 IAM
────────              ──────────────────              ──────────────
잡 시작                                                

  요청 (audience: aws)                                
        ─────────────────▶                          
                          서명된 JWT 발급             
        ◀─────────────────                           
  토큰 들고 클라우드에                                
        ────────────────────────────────────────────▶
                                                  서명자 검증
                                                  클레임 매칭
                                                  (repo, branch,
                                                   workflow 등)
                                                  IF OK:
                                                    임시 자격 발급
                                                    (보통 1시간)
        ◀────────────────────────────────────────────
  자격으로 클라우드 호출                              
  잡 종료 시 자격 만료                               

장기 시크릿이 어디에도 저장되지 않는다. 이게 핵심이다.

6단계 마이그레이션

1단계 — 인벤토리

CI 안에 어떤 시크릿이 어떤 잡에서 어떤 서비스에 쓰이는가. 이게 안 되면 시작이 불가능하다. 우리는 secret store grep + 빌드 로그 분석으로 47개를 찾았다 (정확하게는 49개 — 2개는 6개월간 한 번도 안 쓰인 죽은 키였다).

2단계 — Identity Provider 등록

각 클라우드 (AWS / GCP / Azure) 에 우리 CI 의 OIDC issuer 를 trusted IdP 로 등록. 우리는 GitHub Actions OIDC + 자체 CI 의 OIDC 둘 다 등록. 클라우드별로 audiencethumbprint 이 다른 게 함정.

3단계 — Trust Policy 작성

이게 핵심이다. 어떤 클레임의 토큰어떤 IAM Role 을 assume 할 수 있는지를 명시.

{
  "Effect": "Allow",
  "Principal": { "Federated": "arn:aws:iam::123456789012:oidc-provider/token.actions.githubusercontent.com" },
  "Action": "sts:AssumeRoleWithWebIdentity",
  "Condition": {
    "StringEquals": {
      "token.actions.githubusercontent.com:aud": "sts.amazonaws.com",
      "token.actions.githubusercontent.com:sub": "repo:collabops/web:ref:refs/heads/main"
    }
  }
}

sub 클레임을 정확히 박는 게 결정적이다. 너무 느슨하면 다른 repo 가 같은 role 을 assume 할 수 있고, 너무 빡빡하면 합법적인 잡이 거부된다.

4단계 — 한 잡씩 이주

처음에 한 잡 을 골라 OIDC 로 옮긴다. 보통 덜 critical 한 빌드 잡 부터. 첫 잡이 안정화되는 데 보통 1~2주.

5단계 — 장기 시크릿 회전 후 폐기

OIDC 잡이 안정 후, 원래 access key 를 회전 시키고, 그 회전된 키도 폐기 한다. 이 단계가 가장 많이 빠진다 — OIDC 가 작동하면 안심 해서 옛 키를 그냥 두는 사례. 옛 키는 반드시 폐기.

6단계 — 모든 잡 이주 + 옛 IAM 사용자 삭제

마지막. 모든 잡이 OIDC 로 동작하면, 원래의 IAM 사용자 자체를 삭제. 이 시점에서 클라우드 콘솔에 사람도 자동화도 access key 가 없는 상태 가 된다.

사이에 부서진 가정 4가지

1. "토큰은 알아서 갱신되겠지." 안 된다. 우리 CI 는 잡 시작 시 한 번 토큰을 받는다. 잡이 1시간 이상 걸리면 token TTL 보다 길어져 자격이 만료. 해결: 긴 잡은 step 단위로 자격 재요청.

2. "claim 매칭은 정확히 가는 줄 알았다." OIDC sub 클레임 형식이 플랫폼마다 다르다. GitHub Actions 는 repo:<org>/<repo>:ref:refs/heads/<branch>. GitLab 은 다른 포맷. CollabOps 는 또 다른 포맷. 플랫폼 이주 가 일어나면 모든 trust policy 를 다시 써야 한다.

3. "rotation 이 안 필요한 줄 알았다." OIDC 자체는 회전 필요 없지만, IdP 의 서명 키주기적으로 회전 한다. 클라우드의 등록된 thumbprint 가 오래되면 자동 거부됨. 분기 1회 점검 필요.

4. "단기 토큰이라 감사가 자동인 줄 알았다." 클라우드는 AssumeRoleWithWebIdentity 호출을 CloudTrail 에 기록하지만, 어떤 sub 클레임으로 들어왔는지 의 가시성이 도구마다 다르다. 우리는 자체 감사 layer 에 모든 OIDC token 발급 + assume role 호출 을 별도로 기록하기로 했다. 그 layer 가 진짜 감사 가치.

6개월 후 감사 trail

이전:
  배포 한 번 = 47 라인의 access key 사용 로그
              + 어떤 잡에서 썼는지 grep
              + 시간순 정렬
              + 사람과 자동화 분리 (불가능에 가까움)

이후:
  배포 한 번 = 1 라인 (OIDC token sub: <정확한 워크플로우 URL>)
              자동화 vs 사람 분리 자동
              어떤 repo, 어떤 branch, 어떤 workflow 인지 명시
              token TTL 안에서만 유효 (자동 만료)

이 한 표가 6개월 작업의 진짜 결과다. 비용이 아닌 감사 가능성 의 변화.

누가 이 글을 읽으면 좋은가

CI 에 장기 시크릿 이 5개 이상 박혀 있는 모든 팀. 5개를 넘기면 인벤토리 자체가 무너지기 시작한다. 50개를 넘기면 사고는 시간 문제다. 작은 팀일수록 OIDC 의 이주 비용 대비 감사 가치 가 크다.

태그#oidc#cicd#security#secrets#audit#devsecops