이전에 별도의 서버에 도커 컨테이너로 실행시킨 MySQL 서버에 접속해 우리의 네임서버에서 사용하려 합니다.
프라이빗 서버인 DB 서버
와 퍼블릭 서버인 네임 서버
는 서로 직접적으로 통신할 수 있지만,
데이터베이스 서버가 사용하는 3306 포트를 외부에 아예 개방하지 않으며 보안을 강화해보자는 의도에서 SSH-Tunneling을 활용해 3306 포트는 DB 서버의 로컬에서만 사용하게 했습니다.
멘토님께서 흔치 않은 방식이라고 하시길래 좀 더 찾아보았는데
보안 그룹에서 신경써주는 정도가 흔한 접근이지만, 그래도 보안을 더 신경썼다는 강점을 내세우면 좋을 것 같았습니다.
성능에 대한 문제만 없다면 괜찮지 않을까..
import { createTunnel } from 'tunnel-ssh';
import type { Pool } from 'mysql2/promise';
import mysql from 'mysql2/promise';
import { forwardConfig, poolConfig, sshConfig, tunnelConfig, serverConfig } from './config';
import type { Server } from 'net';
class DatabasePool {
private static instance: DatabasePool;
private pool: Pool | null = null;
private server: Server | null = null;
// ...(생략)...
private async setupTunnel(): Promise<void> {
try {
this.server = (
await createTunnel(tunnelConfig, serverConfig, sshConfig, forwardConfig)
)[0];
console.log('SSH 터널링 성공!');
this.pool = mysql.createPool(poolConfig);
} catch (error) {
console.error('Failed to setup tunnel or create pool:', error);
await this.cleanup();
throw error;
}
}
public async getPool(): Promise<Pool> {
if (!this.pool) {
await this.setupTunnel();
}
return this.pool as Pool;
}
// ...(생략)...
}
데이터베이스 설정 변수를 넣어 커넥션 풀을 생성하고 다루는 코드는 다들 아시는 mysql2를 사용한 것과 다를 바가 없습니다.
차이는 createTunnel()
메소드 하나에 있습니다. 매개변수를 확인하면 createTunnel()의 역할에 대한 감이 잡힐 것 같습니다. 네개나 되는 config 매개변수들에 대한 설명은 아래에👇 붙이겠습니다.
위에서 createTunnel()
에 넘어가는 config 매개변수들을 살펴보면 이해가 조금 더 쉬울 것 같습니다.
export const sshConfig: SshOptions = {
host: process.env.SSH_HOST,
port: Number(process.env.SSH_PORT),
username: process.env.SSH_USERNAME,
agent: process.env.SSH_AUTH_SOCK,
};
export const tunnelConfig = {
autoClose: true,
};
export const forwardConfig: ForwardOptions = {
srcAddr: process.env.LOCAL_HOST,
srcPort: Number(process.env.LOCAL_PORT),
dstAddr: process.env.SSH_HOST,
dstPort: Number(process.env.DB_PORT),
};
export const serverConfig = {
host: process.env.LOCAL_HOST,
port: Number(process.env.LOCAL_PORT),
};
export const poolConfig: PoolOptions = {
host: process.env.LOCAL_HOST,
port: Number(process.env.LOCAL_PORT),
user: process.env.DB_USERNAME,
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME,
};
sshConfig
라이브러리에서 사용되는 ssh 클라이언트에 db 서버에 접속하기 위한 정보를 담습니다.
ssh-add <pem키 위치>
이후 ssh watchducks-ns
로 로컬에서 점프 서버로 들어갈 때 키 값이 포워딩 됩니다.
이후, 네임서버의 환경변수에 해당 키 값이 들어가 process.env.SSH_AUTH_SOCK
와 같이 서버에서 사용할 수 있습니다.
serverConfig
로컬(여기서는 네임서버 자체) 서버의 tcp 연결을 설정합니다.
해당 라이브러리 README에 null
을 넣어주면 OS가 알아서 설정해준다 되어있었는데, 타입 오류 피하기 위해 빈 객체 {}
넣었더니 연결 자체가 안되더라고요…. 내 마음대로 해석하지 말기..
poolConfig
다들 아시는 mysql.createPool()
을 위한 config 변수입니다.
tunnel-ssh를 활용해 LOCAL_HOST:LOCAL_PORT
에서 SSH_HOST:SSH_PORT
으로 포워딩될 예정이므로 LOCAL_HOST
와 LOCAL_PORT
로 설정합니다.