GitHub CLI 업데이트가 갑자기 막힌 이유: 서명 키 교체의 두 단계

9월 15일 나온 GitHub CLI 2.101.0은 기능보다 패키지 서명 키 공지가 더 중요합니다. 공식 APT·RPM 저장소와 개별 RPM 패키지는 이제 새 PGP 키 하나로만 서명됩니다. 2026년 4월 8일 전에 공식 Linux 저장소로 gh를 설치했고 키링을 갱신하지 않았다면, 프로그램이 고장 난 것이 아니라 패키지 관리자가 새 서명을 신뢰하지 못해 업데이트를 거부할 수 있습니다.

이 문제는 모든 gh 사용자에게 생기지 않습니다. macOS와 Windows, Homebrew, GitHub Releases의 바이너리, 소스 빌드는 해당 PGP 키를 쓰지 않습니다. Linux에서도 4월 8일 이후 공식 설치 절차를 다시 실행했다면 키링에 새 키가 이미 들어 있을 가능성이 큽니다. 중요한 것은 운영체제 이름보다 "어떤 배포 경로를 썼고, 그 경로의 신뢰 저장소가 언제 갱신됐는가"입니다.

무엇이 바뀌었나

기존 키의 지문은 2C6106201985B60E6C7AC87323F3D4EA75716059이고, 만료 시각은 2026년 9월 5일 12:44:10 UTC였습니다. 새 키의 지문은 7F38BBB59D064DBCB3D84D725612B36462313325입니다. GitHub는 4월 8일 두 키가 함께 든 키링을 배포하기 시작했고, 8월에는 APT 저장소를 두 키로 서명하는 전환 단계를 거쳤습니다. 9월 8일 병합된 PR #14197은 저장소 설정과 배포 워크플로를 새 키 전용으로 바꿨습니다.

실제 2.101.0 태그의 script/distributions에는 지원 배포판마다 새 지문만 SignWith로 지정돼 있습니다. script/rpmmacros%_gpg_name도 새 지문을 가리키고, 배포 워크플로의 repomd.xml 서명 명령도 같은 키를 명시합니다. 릴리스 노트만 바뀐 것이 아닙니다. 서명을 만드는 실행 경로가 함께 바뀌었습니다.

GitHub CLI 저장소 서명 키가 키링 선배포, 구키와 신키의 겹치는 서명, 신키 단독 서명 순서로 전환된 흐름

그림 1. 서명 키 교체는 신키를 먼저 배포하고 겹치는 검증 기간을 둔 뒤 단독 서명으로 넘어간다. 출처: github.com/cli/cli/issues/13118.

왜 "새 키를 서버에 올렸다"로 끝나지 않나

PGP 저장소 서명은 발행자와 클라이언트가 같은 전환 상태를 공유해야 작동합니다. 서버가 새 키로 서명해도 클라이언트 키링에 새 공개키가 없으면 검증은 실패합니다. 반대로 클라이언트가 새 키를 미리 신뢰해도 서버가 여전히 예전 키로만 서명하는 동안에는 기존 서명을 계속 검증할 수 있어야 합니다.

그래서 이번 교체는 겹치는 기간을 뒀습니다. 먼저 공개 키링에 구키와 신키를 함께 넣고, APT 메타데이터를 두 키로 서명했습니다. 클라이언트가 새 키를 받아갈 시간을 준 뒤 구키가 만료된 다음 신키 단독 서명으로 바꿨습니다. 이 순서를 뒤집으면 최신 클라이언트만 업데이트되고 오래된 이미지나 서버는 한꺼번에 멈출 수 있습니다.

GitHub 저장소의 신키 서명을 패키지 관리자가 로컬 키링으로 확인한 뒤에만 업데이트를 허용하는 신뢰 체인

그림 2. 서버 서명과 로컬 키링이 연결되지 않으면 패키지 관리자는 업데이트를 거부한다. 출처: github.com/cli/cli/blob/v2.101.0/docs/install_linux.md.

저는 공식 키링을 다시 내려받아 확인했습니다. SHA-256은 공식 설치 문서의 값 6084d5d7bd8e288441e0e94fc6275570895da18e6751f70f057485dc2d1a811b와 일치했고, 키링에는 구키와 신키의 기본 지문이 모두 있었습니다. 현재 APT Release.gpg와 RPM repomd.xml.asc의 issuer fingerprint는 신키 하나였습니다. 이 재계산은 2.101.0 공지가 설명한 최종 상태와 맞습니다.

영향 범위부터 좁혀라

업데이트 실패가 보인다고 곧바로 키 파일을 덮어쓰면 원인을 놓칠 수 있습니다. 먼저 설치 경로를 구분해야 합니다.

  • 공식 APT 또는 RPM 저장소를 2026년 4월 8일 전에 등록했고 이후 키링을 갱신하지 않았다면 영향을 받을 수 있습니다.
  • 4월 8일 이후 공식 설치 절차를 사용했다면 현재 키링에 두 키가 들어 있어야 합니다.
  • Homebrew, Windows 패키지, 릴리스 바이너리, 소스 빌드는 이 저장소 키 교체의 대상이 아닙니다.
  • 오래된 Docker 베이스 이미지도 별도 점검 대상입니다. 이미지 안에 예전 저장소 설정과 키링이 고정돼 있으면, 나중 레이어의 apt update가 갑자기 실패합니다.
오래된 공식 APT와 RPM 저장소 및 Docker 이미지는 점검 대상으로, macOS Windows Homebrew 릴리스 바이너리는 직접 영향이 아닌 것으로 나눈 표

그림 3. 이번 교체의 직접 영향은 운영체제 전체가 아니라 공식 Linux 저장소의 오래된 키링에 한정된다. 출처: github.com/cli/cli/issues/13118.

Debian·Ubuntu에서는 signed-by=가 가리키는 실제 키링 경로를 먼저 찾고 gpg --show-keys로 두 기본 지문을 확인하는 편이 안전합니다. 경로가 /etc/apt/keyrings/일 수도 있고, 예전 설치라면 /usr/share/keyrings/일 수도 있습니다. RPM 계열은 설치 당시 가져온 키가 로컬 RPM 키링에 남기 때문에, 배포판별 공식 절차로 저장소 설정을 다시 받고 새 지문을 확인해야 합니다.

자동화에서 놓치기 쉬운 지점

CI와 Docker에서는 "키링 다운로드"와 "패키지 목록 갱신"을 서로 다른 레이어로 나누는 경우가 많습니다. 오래 캐시된 앞 레이어가 예전 키링을 들고 있으면, 뒤 레이어만 다시 실행될 때 검증이 실패합니다. 해결은 검증을 끄는 것이 아니라 apt update보다 앞에서 공식 키링을 갱신하고, 내려받은 파일의 SHA-256과 지문을 확인하는 것입니다.

키링 URL이 HTTPS라고 해서 내용 검증이 끝난 것도 아닙니다. 공식 문서에 고정된 SHA-256과 실제 파일 해시를 비교하고, gpg --show-keys로 예상 지문을 확인해야 합니다. 저장소 설정의 signed-by가 다른 파일을 가리키면 올바른 키링을 내려받고도 실패합니다. RPM 계열도 패키지 관리자가 보여 주는 가져오기 지문을 공식 값과 대조해야 합니다.

2024년에도 같은 구키의 만료 때문에 설치와 업데이트가 중단됐습니다. 당시에는 키 만료일을 연장하는 응급 조치를 썼고, GitHub CLI 팀은 담당자가 바뀌며 이 시한에 대한 조직 지식이 사라졌다고 적었습니다. 이번에는 신키를 미리 배포하고 겹치는 서명 기간을 둔 뒤 단독 서명으로 넘어갔습니다. 운영 교훈은 단순합니다. 만료일 알림만 두지 말고, 새 키 배포와 이중 서명, 클라이언트 갱신률, 단독 서명 전환을 각각 검증해야 합니다.

실무 체크리스트

  1. gh가 어떤 경로로 설치됐는지 확인합니다.
  2. 공식 APT·RPM 저장소라면 실제 키링 또는 RPM 키 저장소에서 신키 지문을 찾습니다.
  3. 키링을 갱신할 때 공식 URL과 SHA-256을 함께 검증합니다.
  4. APT의 signed-by 경로가 갱신한 파일과 같은지 확인합니다.
  5. Dockerfile에서는 패키지 목록 갱신보다 앞에서 키링을 교체하고 캐시가 오래된 레이어를 다시 빌드합니다.
  6. 검증을 끄거나 --nogpgcheck로 우회하지 않습니다. 실패는 공급망 경계가 작동하고 있다는 신호입니다.

이번 변경의 핵심은 새 지문 자체보다 전환 순서입니다. 발행자는 두 키가 공존하는 기간을 만들고, 소비자는 실제 신뢰 저장소가 새 키를 받았는지 확인해야 합니다. 둘 중 한쪽만 끝나면 릴리스는 정상인데 업데이트만 실패하는 이상한 상태가 남습니다.

참고 자료

댓글

이 블로그의 인기 게시물

체크아웃은 분리됐는데 Git 상태는 공유된다: Codex 0.154 worktree의 세 경계

잠근 작업이 커밋 뒤 다시 나온다: PostgreSQL 에이전트 큐의 소유권 설계

코딩 에이전트는 API 문서보다 AGENTS.md를 먼저 읽었다