'ERR_SSL_UNSAFE_LEGACY_RENEGOTIATION_DISABLED' 발생시 처리 방법

진행중인 사이드 프로젝트 백엔드 api에서 특정 url을 fetch시 fetch failed 에러가 발생하여 500 Internel Server Error로 error response가 발생하는 이슈가 있었다.  에러 로그를 확인해보면 TypeError: fetch failed와 함께 발생 원인으로 'unsafe legacy renogotiaion disabled' 가 적혀있었다.

 

 

해당 에러는 요청 들어온 url을 fetch할 때 해당 url의 보안 관련 SSL이 안전하지않은 레거시 버전을 사용하고 있어 fetch에 실패시 발생하는 것이었다. 기존에 사용하던 node 버전 16에서는 이 이슈가 한번도 발생한 적이 없었는데 이번에 프로젝트 node 버전을 16.x에서 18 LTS로 메이저 버전 업데이트를 진행하면서 간헐적으로 발생하였다.

 

간단한 예제를 통해 해당 상황을 재구성해보았다.

// fetchTest.ts
export const fetchTest = async (mapShareAddress: string) => {
  const fetchResult = await fetch(mapShareAddress).catch((err) => {
    console.log(err);
    throw err;
  });

  return fetchResult;
};

 

fetchTest는 간단하게 전달받은 mapShareAddress를 fetch하고 해당 결과를 리턴하는 로직으로 구성되어있다.

 

// fetchTest.spec.ts
import { fetchTest } from '../fetchTest';

describe('fetchTest', () => {
  it('정상적으로 진행된다면 fetch가 완료되고 해당 결과가 리턴되어야함', async () => {
    // Given
    const mapShareAddress = 'https://google.com';

    // When
    const result = await fetchTest(mapShareAddress);

    // Then
    expect(result.status).toEqual(200);
  });
  it('SSL 에러가 발생', async () => {
    // Given
    const mapShareAddress = 'https://kko.to/2kwn5Ap-5r';

    try {
      // When
      const result = await fetchTest(mapShareAddress);

      // Then
      expect(result).toBeInstanceOf(Response);
    } catch (err) {
      // Then
      console.log(err);
      expect(err).toContain('[TypeError: fetch failed]');
    }
  });
});

 

테스트 코드로 유사한 상황을 검증해보기 위해 정상적으로 fetch가 진행되는 케이스에 google.com을, SSL 에러가 발생하는 케이스에는 카카오맵 지도 공유하기 주소를 fetchTest 로직에 입력하였다.

 

google.com의 경우, 정상적으로 테스트가 통과되었지만, 카카오맵 공유하기 주소의 경우 SSL 에러가 발생하여 테스트가 중단되었다.

 

fetch failed 이슈가 발생한 원인은 Node 버전을 16에서 18버전으로 버전업한 것이 주요 원인이었다.

 

Node.js 공식 릴리즈 포스트를 확인해보면 OpenSSL 프로젝트에서 OpenSSL 3버전을 2022년 11월에 발표함에 따라 Node 17버전부터 OpenSSL v3을 탑재하였는데 OpenSSL 3버전은 fetch 요청이 들어온 url이 OpenSSL 구버전(1, 2)을 사용하고 있을 경우, fetch를 하지않고 fetch failed 에러와 함께 'ERR_SSL_UNSAFE_LEGACY_RENEGOTIATION_DISABLED' 코드를 발생시키게 된다.

 

Node.js — OpenSSL November Security Release

Node.js® is a JavaScript runtime built on Chrome's V8 JavaScript engine.

nodejs.org

 

해당 이슈를 해결하려면 fetch 시 httpsAgent 옵션의 secureOptions 속성에 crypto 내장 모듈의SSL_OP_LEGACY_SERVER_CONNECT를 추가하여 SSL 레거시도 fetch할 수 있게 httpClient를 구성하여 해당 axios 인스턴스를 통해 fetch 가능하도록 수정하였다.

// http-client.ts
import axios from 'axios';
import * as https from 'node:https';
import * as crypto from 'node:crypto';

const httpClient = {
  request: axios.create({
    httpsAgent: new https.Agent({
      rejectUnauthorized: false,

      // allow legacy server
      secureOptions: crypto.constants.SSL_OP_LEGACY_SERVER_CONNECT,
    }),
  }),
};

export default httpClient;

 

최종적으로 http-client 코드를 반영한 fetchTest 로직은 다음과 같다.

// 수정된 fetchTest.ts
import httpClient from '../common/client/http-client';

export const fetchTest = async (mapShareAddress: string) => {
  const fetchResult = await httpClient.request
    .get(mapShareAddress)
    .catch((err) => {
      console.log(err);
    });

  return fetchResult;
};

 

동일하게 기존 테스트 코드를 재실행한 결과가 두 가지 케이스 모두 성공함을 확인할 수 있다.