Sigstore Ruby: 소프트웨어 출처 검증을 위한 순수 Ruby 구현의 도전과 교훈

[EN] The Challenges of Building sigstore-ruby / Samuel Giddins @segiddins

3줄 요약

  • Sigstore Ruby는 소프트웨어 아티팩트의 출처를 암호학적으로 검증하는 순수 Ruby 구현체입니다.
  • 이 프로젝트는 OpenSSL gem의 한계와 여러 암호화 원시 요소의 부재로 인해 예상보다 훨씬 복잡하고 오랜 시간이 소요되었습니다.
  • 개발자는 Sigstore 클라이언트 구축의 어려움을 극복하고 커뮤니티의 발전에 기여한 경험을 공유하며, 미래의 구현을 위한 개선점을 제시합니다.

본 발표는 RubyGems, Bundler, RubyGems.org의 보안 책임자이자 Sigstore Ruby 개발자인 Samuel Giddens가 Sigstore Ruby 구축 과정에서 겪은 도전과 그를 통해 얻은 교훈을 공유합니다. 이 토크의 궁극적인 목표는 Ruby 생태계에서 이러한 종류의 보안 소프트웨어를 더 쉽게 구축할 수 있는 방안을 커뮤니티와 함께 모색하는 것입니다. Sigstore Ruby는 SIGstore 검증 및 서명의 순수 Ruby 구현체로서, 현재 RubyGems.org의 gem 페이지에서 gem의 출처(Provenance) 정보를 표시하는 핵심 기능을 담당하고 있습니다. Sigstore의 핵심 목적은 gem이 어떤 커밋에서, 어떤 저장소에서, 어떤 CI 시스템에서 언제 빌드되었는지와 같은 출처 정보를 암호학적으로 검증 가능한 방식으로 제공하는 데 있습니다. 이는 향후 gem 설치 시 특정 정책(예: 'AWS' 접두사로 시작하는 모든 gem은 AWS에서 게시되어야 한다)을 적용하는 기반이 될 것입니다. Sigstore 클라이언트의 주요 기능은 크게 두 가지로 요약됩니다: 아티팩트와 Sigstore 번들이 신뢰할 수 있는 루트와 일치하는지 확인하는 기능, 그리고 신뢰할 수 있는 루트에 대해 아티팩트에 암호화 서명을 하는 기능입니다. 이러한 단순해 보이는 두 가지 기능 뒤에는 상당한 복잡성이 숨어 있습니다.

Sigstore는 소프트웨어 안전을 위한 기술의 집합체로, 서명, 검증, 출처 확인 기능을 제공하며 개인 정보를 보호하고 대규모로 작동합니다. 이는 TUF(The Update Framework) 저장소, OIDC 토큰 기반 서명 인증서 발급 서비스, 인증서 투명성 로그, 투명성 로그 기록 서비스, 그리고 이 모든 것을 포함하는 번들 형식으로 구성된 정교한 암호화 시스템입니다. ChatGPT에 따르면, Sigstore 클라이언트를 처음부터 작성하는 것은 기본 암호화 프로토콜의 복잡성, 다양한 서비스와의 통합, 그리고 보안 문제 처리로 인해 매우 어렵습니다. 프로토콜의 복잡성, 방대한 암호화 기반, 다양한 서비스와의 인터페이스, 보안 구성, 다른 도구와의 통합, API 설계, 유용성, 테스트 및 디버깅 등 모든 측면이 난제로 작용합니다. Sigstore Ruby의 구현 목표는 서명 및 검증 흐름의 순수 Ruby 구현, RubyGems 및 Bundler 내 100% 벤더링 가능성(Ruby 배포판에 RubyGems 및 Bundler가 벤더링될 수 있어야 하므로), 그리고 새로운 암호화 코드를 직접 작성하지 않는 것이었습니다. 여기서 ‘순수 Ruby’는 MRI, JRuby, TruffleRuby의 최신 버전에서 모두 사용 가능한 표준 라이브러리, 기본 및 번들 gem을 사용하는 코드를 의미합니다. 그러나 순수 Ruby 코드 작성은 C로 작성된 산업 표준 라이브러리, 네이티브 코드로 구현된 Ruby 표준 라이브러리 원시 요소, 래퍼 계층으로 인한 임피던스 불일치, 그리고 다양한 Ruby 버전 및 구현 지원으로 인한 의존성 제한 때문에 어려움을 겪습니다. Rust 라이브러리를 래핑하는 대안도 고려되었으나, RubyGems 내부에 포함되어야 하는 특성상 네이티브 코드 컴파일 종속성은 불가능했으며, Rust가 Java와 C를 모두 처리하는 복잡성, Ruby 릴리스와 별개로 Sigstore Ruby를 업데이트해야 하는 필요성, 다양한 Ruby 플랫폼 지원의 어려움 등으로 인해 적합하지 않았습니다. 개발은 2023년 2월에 시작되어 10월에 첫 릴리스가 이루어졌고, 11월 중순에 프로덕션에서 처음 사용되었습니다. Sigstore 검증 흐름은 TUF 저장소 새로 고침, Sigstore 번들 JSON 파일 읽기 및 유효성 검사, 아티팩트 암호화 해싱, 신뢰할 수 있는 시간 원본 설정, X.509 경로 유효성 검사, 서명된 인증서 타임스탬프 유효성 검사, 투명성 로그 항목 포함 확인, 정책에 대한 인증서 확인, 아티팩트 및 서명 인증서에 대한 서명 확인, 번들 페이로드와 정책 간 일관성 확인 등 13단계에 이르는 복잡한 과정을 포함합니다. 이 모든 과정에서 필요한 원시 요소는 Google Protocol Buffers(JSON 인코딩), 다양한 암호화 서명 알고리즘(RSA, ECC DSA, ED25519), X.509 서명 인증서, RFC 3161 서명된 인증서 타임스탬프, Go Sum DB의 서명 노트 형식, Merkle 트리, 두 가지 JSON 정규화 형식 등 다양합니다. 서명을 위해서는 JWT(OIDC ID), 키 생성, X.509 작업, 서명된 타임스탬프 생성, 서명 생성, Sigstore 로그 항목 형식 처리 등이 추가로 필요하며, Recor(투명성 로그) 및 Fulcio(인증서 발급자)와 통신하기 위한 HTTP 클라이언트도 필수적입니다. OpenSSL gem은 다양한 OpenSSL 버전 지원의 복잡성, ED25519 지원 부족(이후 PR로 해결), 인증서 속성 쿼리 및 RFC 3161 유효성 검사와 같은 일부 기능 누락 등의 한계를 보였습니다. 특히 JRuby OpenSSL은 X.509 경로 유효성 검사 버그, ED25519 지원 부재 등 더 많은 제약이 있어, 필요한 모든 원시 요소를 지원하기 위해 원시 Java.security API를 직접 사용해야 하는 상황에 이르렀습니다. 이로 인해 Sigstore Ruby 내부에서는 X.509 래퍼, RFC 8785 JSON 정규화, X.509 인증서의 ASN.1 바이트 수동 이동을 통한 서명될 인증서 바이트 추출, 서명된 인증서 타임스탬프 구현, RFC 3161 지원, DESI(Dead Simple Security Envelopes) 지원, 그리고 TUF 클라이언트 전체 구현(두 번째 JSON 정규화 스키마 포함) 등 많은 기능을 직접 구현해야 했습니다. 이러한 복잡성은 Sigstore가 X.509, PKI, TUF, Merkle 트리, Signotes 등 여러 시스템의 결합체이며, Ruby 생태계에 이 모든 것을 지원하는 통합된 라이브러리가 부족한 데서 기인합니다. 또한, 다양한 구성 지점(키 유형, 서명 스키마, 번들 내용 인코딩 방식 등)이 조합 폭발을 일으켜 구현의 어려움을 가중시켰습니다. 그러나 이러한 재구현 노력은 여러 구현에서 버그가 발견될 경우의 영향을 완화하고, Sigstore 사양을 더욱 정확하고 이식성 있게 만드는 데 기여했습니다. 각 새로운 구현은 다른 엣지 케이스를 발견하고 사양에 대한 새로운 관점을 제공했습니다. 다행히 2024년에는 여러 적합성 테스트 스위트, 잘 편집된 Sigstore 클라이언트 토크, 다른 구현 간의 수렴, Moxigstore 구현 및 커뮤니티의 도움 덕분에 상황이 개선되었습니다.

Sigstore 클라이언트를 처음부터 구축하는 것은 여전히 어려운 일이지만, 이번 Sigstore Ruby 프로젝트를 통해 얻은 경험과 커뮤니티의 발전 덕분에 다음 클라이언트를 구축하는 개발자는 더 쉬운 시간을 보낼 수 있을 것입니다. Sigstore 커뮤니티는 프로토타입 단계에서 초기 채택자를 거쳐 이제는 중요하고 잘 사용되는 프로덕션 시스템으로 진화했습니다. Sigstore Ruby를 구축하는 과정은 도전적이었지만, 이 경험이 다음 Sigstore 클라이언트 개발자가 더 수월하게 작업할 수 있는 기반이 되기를 희망합니다. Sigstore Ruby에 기여하거나, Ruby에서 Sigstore 구현을 더 쉽게 만드는 데 관심이 있다면 언제든지 환영합니다.