본문 바로가기
Old Posts/Linux

[Linux] ssh 터널링(ssh port forwarding) - Local / Remote / Dynamic Tunneling

by A6K 2022. 6. 7.

sh는 Secure SHell의 줄임말로 원격 호스트에 접속하기 위해 사용되는 보안 프로토콜이다. 당연하게도 ssh는 원격 호스트로 접속하기 위해 가장 많이 사용된다.

그런데 ssh는 원격 호스트로의 접속과 더불어 ‘SSH Tunneling’ 혹은 ‘ssh port forwarding’이라는 재미있는 기능을 제공한다.

SSH Tunneling(SSH Port Forwarding)

편의상 이후에는 그냥 SSH 터널링이라고 부르겠다. SSH 터널링은 프록시와 비슷한 역할을 한다. SSH 터널링을 사용할 수 있는 상황에 대한 예를 들어보겠다.

Host A에서 Host B로 접근할 일을 생각해보자. Host A에 있는 어떤 앱의 클라이언트는 Host B에 있는 서버에 접속해야한다.

하지만 언제나 그렇듯 보안상 문제로 앱 서버에서 리슨하고 있는 포트가 방화벽에 의해서 막혀있다. 이 경우 Host A에 있는 클라이언트는 Host B에 구동되어 있는 서버에 직접 접근할 수 없다.

물론 방화벽에서 막아둔 포트 대신 다른 포트를 사용하도록 서버 구성을 변경하면 되겠지만 번거로운 일이고, 운영에 필요한 ssh 접속포트인 22번 포트를 제외하고 나머지는 다 막은 경우도 있다. 이 경우에는 포트 변경으로는 해결이 안된다.

이럴 떄 쓰는게 SSH 터널링이다.

앱의 클라이언트는 SSH 연결을 통해서 방화벽을 우회, 앱 서버에 정보를 전달하고 데이터를 받을 수 있다. 이런 형태때문에 SSH 터널링이 일종의 프록시 역할을 한다고 설명했던 것이다.

SSH 터널링에는 3가지 종류가 있다.

  • Local Port Forwarding
  • Remote Port Forwarding
  • Dynamic Port Forwarding

SSH 터널링을 이용하면 기존에 서비스 중이던 앱 서버를 재구동하지 않고도 방화벽을 우회 접근할 수 있다는 장점이 있다.

또, SSH 연결은 안전하게 암호화되기 때문에 앱 클라이언트와 서버가 암호화된 통신을 지원하지 않는 경우 SSH 터널링을 이용해서 암호화된 통신을 사용할 수 있다.

물론… ssh 연결을 이용한 방화벽 우회이기 때문에 방화벽 사용의 원천적인 목적을 훼손하는 방법이다. 따라서 SSH 터널링을 악용하면 각종 보안 홀을 만들어 낼 수 있다.

SSH 터널링 서버 구성

보안상 문제가 있기 때문에 ssh 서버쪽에서는 일단 허용되어 있지 않다. 따라서 SSH 터널링을 사용하기 위해 SSH 서버의 구성을 약간 바꿔줘야한다.

/etc/ssh/sshd_config 파일을 열어 설정을 수정한다. 이 파일은 수정하기 위해 root 권한이 필요하다.

$ sudo vi /etc/ssh/sshd_config
...
#AllowTcpForwarding yes
#GatewayPorts no
...

설정 중에 AllowTcpForwarding, GatewayPorts 옵션이 있는데 주석을 해제하고 yes로 값을 바꿔 준다.

이제 바뀐 설정이 적용되도록 sshd 서비스를 재시작한다.

$ sudo systemctl restart sshd

또는

$ sudo service sshd restart

명령을 수행해서 재시작 해준다.

Local Tunneling (Local Port Forwarding)

우선 SSH 터널링에서 가장 많이 사용되는 방법은 Local Port Forwarding을 이용한 Local 터널링이다.

앱 클라이언트가 있는 Host A에서 서버가 구동되어 있는 Host B로 ssh 연결을 맺는다. 이 때 사용되는 명령은 다음과 같다.

$ ssh -L PORT1:HOSTNAME:PORT2 user@hostB

ssh 클라이언트는 PORT1에 리슨한다. PORT1으로 들어오는 요청이 있으면 ssh 클라이언트가 ssh 서버쪽으로 요청을 보내고, 서버로 전송된 요청은 HOSTNAME:PORT2로 다시 전해진다.

예를 들어 8080포트를 방화벽이 막고 있어서, HostA가 웹브라우저로 HostB:8080에 접속한 경우 정보를 가져올 수 없다.

이를 우회하기 위해서 SSH 터널링을 이용해 터널링을 열어 두었다면,

$ ssh -L 1234:HostB:8080 user@hostB

웹 브라우저에서 localhost:1234 를 접속하면 HostB:8080 에 접속한 화면이 나오게 된다. 참고로 App Server와 ssh server는 같은 장비에 있을 필요는 없고 접속 가능한 네트워크에만 있으면 된다.

이런 접속을 위해서

$ ssh -L 1234:HostC:8080 user@hostB

이렇게 구성 터널링을 할 수도 있다.

Remote Tunneling (Remote Port Forwarding)

로컬 터널링이 제일 간단하다. 그러면 리모트 터널링은 무엇일까?

가끔 외부에서 서버로 들어오는 연결을 모두 막아두는 경우가 있다. 이 경우에는 HostB로의 ssh 연결조차 안된다.

대신 HostB에서는 외부로 접속이 가능한 경우가 많다. 예를 들어 서비스망 외부에서 접속은 안되지만 서비스 망에서는 외부에 있는 각종 저장소에 접근해야하기 때문에 서비스 장비에서 외부로 나가는 것은 막지 않은 경우가 많다.

이 경우 로컬 터널링은 사용할 수 없다.

대신 앱 서버 호스트 쪽에서 클라이언트 쪽으로 SSH 연결을 수립할 수는 있다.

즉, Host A에 sshd 서버가 떠있고, Host B에서 Host A로 ssh 연결을 만들 수 있다면 역시 터널링을 통한 우회를 할 수 있다. 이게 리모트 터널링이다.

$ ssh -R PORT1:HOSTNAME:PORT2 user@hostA

서버에서 클라이언 호스트로 ssh 연결을 한다. 그러면서 PORT1을 리슨하라고 정보를 주는 것이다. 그러면 SSH 서버 쪽에서는 PORT1으로 들어오는 요청들을 ssh client로 전달해준다. 그러면 ssh client는 HOSTNAME:PORT2로 요청을 전송하게 된다.

리모트 터널링은 로컬 터널링에서 ssh client와 ssh server의 역할만 바뀐 것이다. (ssh 클라이언트가 리슨하고 있는지, ssh 서버가 리슨하고 있는지 차이)

Dynamic port forwarding

Local과 Remote 터널링은 로컬과 리모트 호스트의 포트를 1:1로 연결해준다. 만약 실행하기 전까지 어떤 포트로 전달해야하는지 모르거나 여러개의 포트 중에 임의의 포트로 트래픽을 전달해야하는 경우라면 SSH 터널링을 시작할 때 로컬 포트와 리모트 포트를 알고 있어야하는 Local/Remote 터널링은 사용하기 어렵다.

터널링 시작시 미리 포트를 알고 있어야 하는 Local/Remote 터널링에 비해 터널링이 생성된 이후 자유롭게 포트 포워딩을 할 수 있는 Dynamic Port Forwarding 혹은 SOCKS5 Proxy 터널링이란 것도 존재한다. (Local/Remote는 static port forwarding이라고도 한다)

예를 들어보자. 보안망에 운영중인 빅데이터 클러스터의 관리 Web UI 페이지에 접속하고 싶은 경우를 생각해보자. 하둡 클러스터를 운영하면 네임노드, 데이터노드, 주키퍼 등 다양한 데몬들의 정보를 볼 수 있는 웹 페이지에 접속해야한다. 하지만 클러스터를 생성할 때마다 HOST:PORT 정보를 ACL에 등록해 외부에서 접근할 수 있도록 할 수는 없다. 클러스터가 커질 수록 클러스터에 장비가 추가되거나 교체되는 일이 빈번해지기 때문이다.

게다가  하둡 클러스터의 경우 데이터노드들도 웹 페이지를 제공하는데 모든 데이터 노드의 주소와 관리 포트를 ACL로 등록하는 것도 힘들다. 동일한 이유로 클러스터에 있는 모든 장비에 대해서 Remote/Local 터널을 열어두는 것도 힘들다.

이 때, Dynamic 터널링을 열어둔다면 하나의 SSH 연결을 이용해서 마치 보안망 안쪽에서 연결을 시도하는 것처럼 SOCKS Proxy를 사용할 수 있다.

Dynamic 터널링을 위해서는 다음 명령을 이용해야한다.

$ ssh -N -D PORT USER@hostB

이러면 SSH Client가 hostB에 USER 계정으로 SSH 연결을 맺으면서 SOCKS5 Client - SOCKS5 Server 연결이 맺어진다. 이제 브라우저에서 생성된 SOCKS5 Client를 사용하도록 설정해주면 된다.

파이어 폭스의 경우 Settings 에서 Network Settings 창을 열면 프록시 설정을 할 수 있는 창이 뜬다. 여기에 생성해 둔 SOCKS5 Proxy 정보를 넣어주면 된다.

만약 보안 망에 있는 서버들의 호스트 주소에 특정한 규칙이 있고, 그 호스트들에 접근할 때만 Dynamic 터널링을 사용하고 싶은 경우가 있다. 예를 들어 사내망에 있는 클러스터 정보를 보고 싶은 경우가 이에 해당한다.

이 경우 Automatic proxy configuration URL 항목을 클릭하고 다음 JS 파일을 연결해주면 된다.

function FindProxyForURL(url, host) {

    // Host Domain 기준
    if (shExpMatch(host, "*.abc.com"))
        return "SOCKS5 localhost:8123";

    // IP 주소 기준
    if (isInNet(dnsResolve(host), "10.0.0.0", "255.0.0.0"))
        return "SOCKS5 localhost:8123";

    // Proxy를 사용하지 않고 직접 접근
    return "DIRECT";
}

 

SSH 터널링 장단점

장점은 방화벽을 우회할 수 있다는 점이다. 앱 서버의 설정을 바꾸는 작업은 생각보다 복잡할 수 있다. 서버가 클러스터로 구성되어 있는 경우 롤링 리스타트를 해야하는 등의 작업이 있을 수 있기 때문에 간단한 작업이라면 터널링을 이용해서 우회하는게 좋을 수 있다.

또, 앱 클라이언트와 앱 서버 사이에 암호화된 통신이 지원되지 않을 경우 SSH 터널링을 통해 암호화된 통신을 추가할 수 있다.

다만 SSH 터널링은 방화벽을 우회한다는 점에서 보안상 구멍을 만들 가능성이 높다. 애초에 방화벽을 왜 쓰는지를 생각해보자.

게다가 SSH 터널링을 이용해서 대규모 트래픽을 처리하려는 경우에는 바람직하지 않다.

댓글