avatar
Published on

colors.js와 faker.js 사태가 준 교훈

Author
  • avatar
    Name
    yceffort

Marak Squires라는 개발자가 만든 두 개의 유명한 패키지 colors.jsfaker.js가 개발자에 의해 고의로 손상되는 사건이 일어났다.

faker.js의 경우 레포에 아무것도 남아나지 않고 멀쩡한 5.5.3 버전에서 6.6.6 버전으로 major 업데이트를 하는 귀여운(?) 수준이었지만, faker에 비해 쓰는 패키지가 더 많았던 colors.js의 경우 1.4.0에서 무한루프가 삽입되어 있는 1.4.1 버전으로 패치 업데이트를 하는 치밀함을 통해 ^.1.4.0으로 선언이 되어 있는 많은 노드 패키지를 골로 보내는 쾌거를 이뤄낼 수 있었다. 덕분에 요런 식의 수정을 추가하느라 연초부터 많은 개발자들이 바빴을 것이다.

대충 사건을 파악해보니 2020년 쯤에 아파트에 불이 나서 전재산을 날려먹은 듯한 모양 이다. 근데 불의의 사고로 불이 난 것 은 아니고, 집에서 사제폭탄을 만들다가 집을 다 태워먹은 모양이다.

어쨌건, 이 분의 의도는 돈 많이 버는 회사들이 내 오픈소스를 공짜로 쓰는데 아무런 보상이 없자 경각심을 주기 위해 한 행동이라고 하는데, 이해가 될 듯 안될 듯 공감이 되는듯 안되는 듯 하다 🤔

오픈소스를 매일매일 사용하는 사람, 또는 기업이 어떤 자세를 가져야 할지, 또 기부를 하는게 맞는지, 해야한다면 얼마를 해야하는 건지(?) 등 어려운 문제는 차치하고, 오픈소스를 매일 사용하는 node 개발자들이 이 사태를 사전에 어떻게 막아야할지 고민해보도록 하자.

먼저 패키지 개발자 관점에서 생각해보자. 잘못된 버전이 우리가 모르는 새 배포되었다. 이 패키지를 의존하는 패키지가 이로 인해 오작동 한다는 것은, 사보타주된 패키지를 npm install 등으로 설치하는 것을 시작으로 설치한 이후에도 호환성 등의 테스트가 전혀 이뤄지지 않았다는 것을 의미한다.

그리고 이 패키지를 사용하는 일반적인 개발자 입장에서 판단해보자. npm은 패키지에 나열된 요구사항에 따라 의존성 버전을 선택해서 설치한다. package.json에 나열된 의존성 뿐만 아니라, 현자 선언되어 있는 모든 의존성에서 가능한 최신 버전의 사용을 선호하게 된다. 패키지 개발자의 의존성에서는 최신버전을 사용하도록 선언되어 있었고, 그것을 조금도 의심하지 않고 설치했다. 그리고 결국에는 아래와 같은 이슈 리포팅을 남기게 된다.

npm 사용자들은 굉장히 화가 날 법한 일이지만, 적어도 무한루프를 생성하고 이상한 내용을 출력하는 수준에서 그쳤다는 것을(?) 다행으로 생각해야 한다. 해킹과 같은 더 나쁜 일이 있을 수도 있고, 이런 의도적인 파괴가 앞으로 없을 거라고 가정하더라도 이와 비슷한 결과를 만들 수 있는 버그도 발생할 수 있다. 기본적으로 모든 오픈소스 소프트웨어 라이센스 코드는 특별한 보증 (warranty) 없이 사용하기 때문이다. 따라서 패키지 관리자는 이러한 위험이 있을 수 있음을 항상 예상하고 이에 따른 피해가 최소화 될 수 있도록 설계해야 한다.

npm 패키지 관리자들은 새 패키지를 설치할 때 모든 의존성의 최신 버전을 선호하도록 하는 버저닝을 중지하는 것이 좋다. (^~) 대신 패키지가 실제로 테스트 된 버전이나, 가능한 안전한 버전을 선택한 것이 좋다.

npm은 npm shrinkwarpnpm ci 명령어를 통해 의도치 않은 종속성 업데이트를 방지한다. 그러나 이 명령어가 할 수 있는 일은 제한적이므로, 근본적으로 패키지 관리자의 고민을 덜기엔 부족하다.

이전에 포스팅에서 이야기한 것처럼 node_modules도 git으로 관리하여 패키지의 변화를 눈치채는 것도 한가지 방법이 될 수는 있다. 그러나 어디까지나 사람의 눈으로 캐치해야 하기 때문에 100% 안전한 방법이라고 할 수는 없다.

가장 중요 한 것은, 모던 프로덕션 시스템을 운영하는 사람이라면 점진적이고 단계적인 롤아웃 정책에 대해서 인지하고 적용해야 한다는 것이다. 이를 바탕으로 한 테스트를 활용하여, 한번의 실수로 모든 것이 망가지는 것을 미연에 방지해야 한다. 테스트롤 통해 변경사항이 다른 시스템에 영향을 미친다는 것을 확인한다면, 예상치 못한 문제를 사전에 발견하고 배포를 중지할 수 있는 시간을 충분히 벌 수 있을 것이다. (그러나 아쉽게도 npm은 정반대 정책으로 운영되고 있다. 최신버전의 패키지는 누구도 테스트할 기회를 얻기도 전에 모든 의존성 내부에서 널리 사용되도록 퍼져나가버렸다.)

참고: 점진적이고 단계적인 롤 아웃

시스템 관리자의 격언 중 하나는 '실행 중인 시스템을 절대로 바꾸지 말라' 라는 말이다. 어떠한 형태로든 변화는 위험을 나타내며, 시스템의 신뢰성을 위해서 위험은 최소화 되어야 한다. 어떤 작은 시스템에라도 작은 변화가 일어난다면, 구글의 고도로 복제되고 전세계로 분산되어 있는 시스템에도 엄청난 큰 위험으로 적용된다.

구글에서 전세계에가 사용할 수 있도록 특정시간에 신제품을 출시하는 '버튼' 을 누르는 경우는 없다. 시간이 지남에 따라 구글은 제품과 기능을 점진적으로 출시하여 위험을 최소화 할 수 있는 패턴을 개발했다. https://sre.google/sre-book/service-best-practices/

구글 서비스에 대한 거의 모든 업데이트는 정해진 프로세스에 따라 적절한 검증 단계를 거쳐서 점진적으로 진행된다. 하나의 데이터 센터에 있는 몇개의 시스템에 새 서버가 설치되고, 이는 사전에 정의된 기간에서 관찰할 수 있다. 모든 것이 정상으로 확인되면, 서버는 한 데이터 센터의 모든 시스템에 설치되고, 그 다음 다시 상태를 살펴본 다음 모든 시스템에 설치된다. 이러한 롤아웃의 첫단계를 'canaries'라고 부른다. 이는 탄광에 위험한 가스가 있는지 확인하기 위해 보내는 새 카나리아에서 따온 말로, 여기서 canaries는 실제 사용자 트래픽에서 새로운 소프트웨어 동작으로 인해 위험한 영향을 감지하는 것을 의미한다.

이 테스팅은 환경설정을 변경하는 시스템 뿐만 아니라 자동화된 변경을 위해 사용되는 구글의 많은 내부도구에 도입되니 개념이다. 새 소프트웨어 설치를 관리하는 도구는 일반적으로 새로 시작하나 서버를 잠시 관찰하여 서버가 충돌하거나 잘못된 동작을 하지 않도록 한다. 변경된 내용이 유효기간을 지나지 않으면 자동으로 롤백 된다.

이러한 점진적 롤아웃의 개념은 구글의 서버에서 실행되지 않는 소프트웨어에도 적용된다. 새로운 버전의 안드로이드 앱은 점진적으로 롤아웃 될 수 있으며, 업데이트 된 버전이 점차 업그레이드 되기 위해 점차 그 범위를 늘리는 방법을 채택했다. 업데이트 된 인스턴스의 비율이 100%에 되기 까지 시간이 지남에 따라 점차 증가한다. 이러한 원격 설치 유형은 새 버전으로 인해 구글 데이터 센터의 백엔드 서버에 트래픽이 추가되는 경우에도 유용하다. 이렇게 하면 새로운 버전을 점차 출시하고, 문제를 조기에 발견하면서 버서에 미치는 영향을 점진적으로 관찰할 수 있다.

https://sre.google/sre-book/reliable-product-launches/#gradual-and-staged-rollouts-yDsrIPFV