본문 바로가기

Study/GW

[Spring Boot3] SSH 터널링 설정 (EC2 > RDS 데이터베이스)

2024년 초부터 RDS 인스턴스에 Public IP 주소를 무료로 사용하지 못하게 되면서, RDS 데이터베이스를 EC2에 연결하는 작업을 진행했다. 

 

Spring Boot 서버에서도 EC2 서버를 거쳐 RDS DB에 접근하도록 설정했다. Jsch 라이브러리를 사용했다.

 

 

 


 

1. build.gradle 추가

dependencies {
	...
    implementation 'org.mariadb.jdbc:mariadb-java-client:3.4.0'
    implementation 'com.jcraft:jsch:0.1.55'
}

 

 

2. application.yml 수정

spring:
  datasource:
    url: jdbc:mariadb://{DB_HOST명}/{DB명}
    driver-class-name: org.mariadb.jdbc.Driver
    username: {사용자명}
    password: {비밀번호}
   
ssh:
  remote_jump_host: {HOST명}
  ssh_port: 22
  user: ubuntu
  private_key_path: {pem키 위치}
  database_port: 3306

 

 

 

3. config/SshTunnelingInitializer.java 작성

package gods.work.backend.config;

import com.jcraft.jsch.JSch;
import com.jcraft.jsch.JSchException;
import com.jcraft.jsch.Session;
import jakarta.annotation.PreDestroy;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import org.springframework.validation.annotation.Validated;

@Slf4j
@Component
@ConfigurationProperties(prefix = "ssh")
@Validated @Setter
public class SshTunnelingInitializer {

    private String remoteJumpHost;
    private int sshPort;
    private String user;
    private String privateKeyPath;
    private int databasePort;

    private Session session;

    @PreDestroy
    public void destroy() {
        if (session.isConnected())
            session.disconnect();
    }

    public Integer buildSshConnection() {
        Integer forwardPort = null;

        try {
            log.info("Connecting to SSH with {}@{}:{} using privateKey at {}", user, remoteJumpHost, sshPort, privateKeyPath);
            JSch jsch = new JSch();

            // connection 1. application server to jump server
            jsch.addIdentity(privateKeyPath); // pem 키 추가
            session = jsch.getSession(user, remoteJumpHost, sshPort); // 세션 설정
            session.setConfig("StrictHostKeyChecking", "no");

            log.info("Starting SSH session connection...");
            session.connect(); // 연결
            log.info("SSH session connected");

            // connection 2. jump server to remote server
            forwardPort = session.setPortForwardingL(0, "localhost", databasePort);
            log.info("Port forwarding created on local port {} to remote port {}", forwardPort, databasePort);

        } catch (JSchException e) {
            log.error(e.getMessage());
            this.destroy();
            throw new RuntimeException(e);
        }

        return forwardPort;
    }
}

 

 

 

4. config/SshDataSourceConfig.java 작성

package gods.work.backend.config;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;

import javax.sql.DataSource;

@Slf4j
@Configuration
@RequiredArgsConstructor
public class SshDataSourceConfig {

    private final SshTunnelingInitializer initializer;

    @Bean("dataSource")
    @Primary
    public DataSource dataSource(DataSourceProperties properties) {
        Integer forwardedPort = initializer.buildSshConnection(); // ssh 연결 및 터널링 설정
        String url = properties.getUrl().replace("[forwardedPort]", String.valueOf(forwardedPort));
        log.info(url);
        return DataSourceBuilder.create()
                .url(url)
                .username(properties.getUsername())
                .password(properties.getPassword())
                .driverClassName(properties.getDriverClassName())
                .build();
    }
}

 

 

 

5. EC2 서버 설정

원래 4번까지 진행 후 서버를 실행하니, session.connect() 부분에서 에러가 발생했다. 역시... 한번에 잘 되는게 이상하지....!  😂

 

에러 발생 🥵

 

 

친절한 GPT에게 물어보니 몇 가지 확인 포인트를 알려주었다. 

 

1) 키 파일 권한 확인

2) 사용자 디렉터리 권한 확인

3) 키 파일 형식 확인

4) 로그 확인 (📌)

 

 

먼저 서버에 접속 후, 아래 명령어를 입력해 실시간으로 로그를 확인한다. 

# Ubuntu/Debian
sudo tail -f /var/log/auth.log

 

 

그리고 로그에서 좀더 구체적인 에러 메세지를 확인할 수 있었다. 

"JschException: Auth fail ..."

 

 

 

다시 GPT에게 물어보니, 서버측 설정을 변경해줘야 한다고 알려줬다. 기본적으로 AWS EC2 생성시 만들어진 pem키는 JSCH에서 지원되지 않는 문제 때문이라고 한다.

(JSCH에서 사용하는 ssh-rsa 방식은 AWS 아마존 리눅스에서는 기본적으로 비활성화 되어있다. - 출처: https://code-overflow.tistory.com/entry/%EC%97%90%EB%9F%ACError-JSch-comjcraftjschJSchException-Auth-fail)

 

 

 

서버측 설정을 변경하기 위해, 아래 명령어를 입력한다.

$ sudo vi /etc/ssh/sshd_config

''' 아래 내용 추가
HostKeyAlgorithms +ssh-rsa
PubkeyAcceptedAlgorithms +ssh-rsa
'''

# 서버 재시작
$ sudoo systemctl restart ssh

 

 

그리고 다시 스프링 서버를 실행하니 위의 정상적으로 SSH 연결 완료되었다. 

에러 해결!

 

 

 

 

 

하지만, 이젠 DB 관련 에러가 발생했다. 

Unable to determine Dialect without JDBC metadata (please set 'javax.persistence.jdbc.url', 'hibernate.connection.url', or 'hibernate.dialect')

 

 

아래 dialect를 추가해주니 해결 되었다. 

(dialect는, 하이버네이트가 DB와 통신하기 위해 사용하는 언어를 말한다. 하이버네이트는 어느 하나의 DBMS에 국한되지 않고, 다양한 DBMS에 사용 가능하다)

spring:
	jpa:
    	properties:
        	hibernate:
            	dialect: org.hibernate.dialect.MySQLDialect

 

 

 

 

하지만....  connect time out이 발생한다... 왜일까... 

 

찾아보니 데이터 전송 속도가 너무 느려진다고 한다. 그래서 로컬 개발 환경 설정용으로 쓰라고 한다... 이런... 

 

생각해보니, 지금 배포하는 서버 내에 RDS가 설치되어 있기 때문에 굳이 이렇게 할 필요가 없었다... 

 

로컬에서 개발용으로만..^^

 

 

 


 

 

 

EC2 서버에 배포를 하기 전에, EC2 서버에서 DB 접근할 수 있도록 설정해주자. 

 

sudo apt install mysql-server

sudo apt install mysql-server

mysql -h [DB주소] -u [아이디] -p [데이터베이스이름]

 

MySQL 접속 완료

 

 

 

 

반응형