
하루만에 오픈소스에 기여하기 (nest)
처음으로 오픈소스에 기여해보았다 (feat. 오픈소스 멘토링)저는 처음 오픈소스에 기여하겠다!!! 라는 생각을 실천하는데 1년이나 걸렸습니다.부끄럽지만 너무 다가가기 어렵고 힘들었습니다. 하
mag1c.tistory.com
작년, 오픈소스 기여에 첫 발을 떼게 해주신 인제님의 오픈소스 멘토링 이후, 기여할 수 있는 부분들에 꾸준히 기여를 해왔다.
이번에도 nest, nestjs/swagger에 기여를 했고, 아직 머지되지 않은 기여도 있고 바로 반영된 머지들도 있다. 어떤 문제가 있었고 어떻게 해결했는지 따로 정리도 할 겸 포스팅을 남긴다.
파일 타입 검증 강화에 따른 추가 기여
최근 포스팅중에 Nest의 파일 타입 검증에 대한 보안 취약점에 기여할 뻔한 이야기라는 제목으로 글을 썻다. 위 포스팅의 기여 내용의 후속으로 개발 편의를 위한 PR을 만들게 되었으며, 기여 내용을 이해하기 위해 이전 포스팅을 간략하게나마 읽어보는 것을 추천한다.
파일 검증에 대한 보안 취약점을 개선하기 위해 Nest의 FileTypeValidator에 파일의 Magic Number(바이너리 시그니처)를 기반으로 파일 타입을 검사하도록 변경되었다. 이 변경은 보안적으로 더 개선되었지만, 실제 애플리케이션에서는 다음과 같은 부작용을 낳았다.
- .txt, .csv, .json 등 Magic Number가 없거나 너무 짧은 파일들은 파일 타입을 판단하지 못해 업로드 실패로 이어진다. 이로 인해 기존 애플리케이션에서 잘 작동하던 업로드 로직이 갑자기 실패하게 되었다.
- 에러메세지가 기존과 같이 file type에 대한 에러 메세지가 나오게 되어 왜 실패했는지 알 수 없다.
위 문제를 바로 피부로 겪었는데, 현재 조직에서도 새로 구축하고 있던 애플리케이션에서 잘 동작하던 파일 업로드가 갑자기 되지 않았고, 문제를 직접 해결하기 위해 나는 유연한 검증을 위한 옵션과, 에러 메세지의 적절한 분기 처리를 PR로 제출했다.
feat(common): Add fallbackToMimetype support in FileTypeValidator by mag123c · Pull Request #14995 · nestjs/nest
Introduce fallbackToMimetype option to allow fallback to mimetype validation when magic number detection fails (e.g., for small or undetectable buffers like text or CSV files). Also enhanced buildE...
github.com
OpenAPI 커스텀 헤더 추가
Swagger은 OpenAPI의 표준이 아닌 확장(사용자 정의) 속성을 지원하고 있는데, SecuritySchema는 이 확장 속성들 중 API 요청을 인증할 때 어떤 방식으로 인증해야하는 지 정의하는 설정이다. Nest에서는 이 옵션을 래핑하는 DocumentBuilder을 메서드 체이닝을 이용하여 보통 아래처럼 스웨거 명세를 만든다.
return new DocumentBuilder()
.setTitle('API')
.addBearerAuth(
{
type: 'http',
scheme: 'bearer',
name: 'JWT',
in: 'header',
},
'access-token',
)
.addBasicAuth(
{
type: 'apiKey',
scheme: 'apiKey',
name: 'apiKey',
in: 'header',
},
'X-Nonce',
)
.build();
하지만, OpenAPI Extensions 문서의 예시처럼, 이 API Key에 추가적인 필수 옵션들이 들어가야하는 상황이라면? 예를 들어 AWS API Gateway처럼 특정 플랫폼에 맞춘 커스텀 인증을 구현해야하는 상황에서, 스웨거를 통해서는 헤더에 원하는 커스텀 옵션을 넣을 수 없게 되어있다.
장황한 설명을 했지만, 이에 기여하기 위해 작성한 코드는 그저 옵션 추가 단 한 줄이다. (테스트 코드도 없다.)
/**
* SecuritySchemes Additional extension properties
* @issue https://github.com/nestjs/swagger/issues/3179
* @see https://swagger.io/docs/specification/v3_0/openapi-extensions/
*/
[extension: `x-${string}`]: any;
feat(swagger): add extension in SecuritySchemeObject by mag123c · Pull Request #3248 · nestjs/swagger
PR Checklist Please check if your PR fulfills the following requirements: The commit message follows our guidelines: https://github.com/nestjs/nest/blob/master/CONTRIBUTING.md Tests for the chan...
github.com
Swagger의 컴파일 타임 옵션 추가
@ApiProperty({
enum: SubjectEnum,
enumName: 'Subject',
})
readonly __caslSubjectType__: SubjectEnum = this.constructor.name as SubjectEnum;
// Swagger Schemes
"__caslSubjectType__": {
"default": "Function",
"allOf": [
{
"$ref": "#/components/schemas/Subject"
}
]
}
Nest의 Swagger Plugin은 컴파일 타임에 ts의 Transformer을 사용해서 Swagger 명세를 추상 구문 트리로 만들어낸다. JavaScript 엔진을 공부할 때 나오는 그 추상 구문 트리(Abstract Syntax Tree) 이다. 이렇기에 위 예제처럼 런타임 시에 정해지는 값은 추론되지 않아 JavaScript 함수의 name값인 Function을 넣어버리게 된다.
컴파일 타임에 제어하기 위해, Swagger Plugin Option을 추가하고, 옵션에 따라 아예 옵션을 활성화하면, Default로 추론할 수 없는 값들은 Swagger 명세에 Default로 추가되지 않는다.
// 1. 초기값이 없음
@ApiProperty()
readonly type: string;
// 2. 추론이 어려운 표현식
@ApiProperty()
readonly subjectType: SubjectEnum = this.constructor.name as SubjectEnum;
feat(swagger-plugin): add skipDefaultValues option to omit unspecified default fields and corresponding test by mag123c · Pull
PR Checklist Please check if your PR fulfills the following requirements: The commit message follows our guidelines: https://github.com/nestjs/nest/blob/master/CONTRIBUTING.md Tests for the chan...
github.com
마무리
(위 내용보다 더 간단하거나 공식 문서에 대한 기여 등은 작성하지 않았다.)
기여를 계속 하다보니 자연스레 깨닫게 된 점이 있다. 바로 조직 내에서 사용하는 기술 스택에 익숙해져감에 따라, 그것이 나의 개발 판단 기준을 점점 좁히고 있었다는 점이다. 예를 들어, 런타임 타입 검사를 위한 Zod를 typia로 변경하여 훨씬 빠르고 간결한 코드 작성이 가능한 오픈소스도 있고, 항상 tsc로 트랜스파일하던 것을 swc로 바꿔본다던가 하는 등의 퍼포먼스 개선이 가능하다. 당장 적용해도 충분히 기술 선택의 근거가 명확하고, 임팩트가 있는 선택지들이 많다는 걸 느낀다.
오픈 소스 기여는 단지 코드 변경에 머무르지 않는다. 생태계의 흐름을 눈으로 확인하고, 직접 만지며 왜 이런 기능이 추가됐는지를 맥락과 함께 이해하는 경험이라고 생각한다. 이런 과정을 겪으며 자연스럽게 내 기술의 선택 기준도 명확해지고 조직 내 기술뿐 아니라 개인적으로 사용할 기술에도 더 많은 관심을 기울이게 된다.
내가 사용하는 기술을 불편 없이 쓰는 것에서 멈추는 것이 아니라, 더 나은 방향으로 기여하거나 기존에 없는 기능을 직접 만들어내는 것.
결국 불편함을 느끼고 나만의 방식으로 개선해보는 경험을 하나씩 쌓아나가는 것이 가장 이상적인 형태가 아닐까 싶다.
장황하게 나열했는데, 여튼 다음에는 조금 더 다양한 기여 경험을 가지고 돌아오도록 하겠다.
diehreo@gmail.com
포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!