ISMS 준비를 하면서 네트워크 쪽으로 가장 사이즈가 컸던 작업 중 하나가 바로 내부망에서 필요한 GCP 서비스와 연결하는 부분이었다. 이번에는 ISMS를 준비하면서 특정 서비스의 시스템을 구축하는 과정 중에 네트워크에 관한 전체적인 흐름을 설명한다. 네트워크도 사실 여러가지 부분이 있다고 생각하는데 이번 포스팅에서는 어플리케이션과 관련된 네트워크 이야기라기보다는 내부 통제, 내부적인 사용을 위해서 필요한 네트워크 설정과 관련된 이야기를 할 것이다.
On-premise 내부망
보안적으로 중요한 시스템은 내부망에서 관리되고 있었고 물리적인 방화벽을 통해 접근 제어를 하고 있었다. 그리고 필요한 부분에 대해서는 물리적으로 보안이 갖추어진 장소에서 권한이 있는 사람만 해당 시스템에 접근할 수 있는 네트워크를 별도로 갖추었다.
그림 1에서 Office-1은 물리적인 방화벽을 통해서 접근 제어를 하고 있으며 보통 운영자들이 해당 PC에서 업무를 진행한다. Office-2는 특정 권한을 가진 인원들만이 보안 시스템을 거쳐서 접근할 수 있다.

이제 여기서 운영자들은 시스템을 운영하기 위해 필요한 웹 대시보드나 API 서버가 필요할 것이다. 이러한 것들은 PM과 개발자가 일련의 과정을 거쳐 개발을 하고 배포가 된 후에야 사용할 수 있을 것이다. 그래서 서비스를 어디에 어떻게 배포하여 관리할지를 결정해야 했다.
이 부분에 대해서는 이미 개발자와 데브옵스 엔지니어가 GKE에 배포하는 과정에 매우 익숙했고 숙련되어 있었기 때문에 이번에도 마찬가지로 빠른 시간에 개발/배포 하기 위해서 GKE에서 서비스를 관리하기로 결정했다.
그렇기 때문에 이제 클라우드에서 배포된 서비스를 내부망에서 접근하여 쓸 수 있도록 하기 위해 VPN을 도입했다. 그런데 그림 2에서 볼 수 있다시피 이제 내부망으로 접근 가능한 통로가 내부망 내부 PC에서 GCP 쪽에서 들어오는 경로도 추가된 것을 알 수 있다. 그래서 이제 GCP에 대한 접근 통제도 필요해졌는데 이 문제에 대해서는 어떻게 해결했는지 아래에서 정리하겠다.




GCP VPN
VPN의 기본적인 내용에 대해서 궁금하다면 아래의 글을 참고해도 좋다.
시스템이 간단했고 온프레미스 환경과 연결해야했기 때문에 GCP에서 제공해주는 Cloud VPN 중 경로 기반의 정적 라우팅 VPN으로 설정했다. 경로 기반의 정적 라우팅 VPN은 어려운 것은 아니고 명시적으로 해당 라우터가 관리하는 네트워크의 서브넷 중 어떤 서브넷과 연결할 것인지 설정하여 만드는 VPN이다.
VPN 특성상 온프레미스 환경 네트워크의 IP 주소 대역과 클라우드 서비스의 IP 주소 대역은 겹치면 안된다. 또한 가용성을 고려하여 두 개의 VPN 연결을 만들고 연결 하나가 끊어지면 다른 연결을 통해서 클라우드와 통신이 가능하도록 설계하였다.




위의 그림에서 알 수 있듯이 온프레미스 환경에서는 VPN을 통해서 특정 서브넷과 통신가능하도록 IP 주소 대역을 열어주고, 일단은 간단하게 VPN 게이트웨이를 두 개 두고 기본으로는 A 게이트웨이를 통해서 통신하지만 A에 문제가 생기면 우선순위에 따라 B로 통신이 계속된다.
이제 온프레미스 환경과 클라우드 환경 사이에 다리는 놓은 셈이다. 이제 시선을 GCP 환경으로 돌려보자.
GCP GKE
이전에 이야기했듯이 모든 서비스는 컨테이너 형태로 Kubernetes 환경에서 관리된다. 그리고 Kubernetes 클러스터는 GKE가 관리해주는데 GKE의 네트워킹 측면을 살펴보자.
우선 Kubernetes에는 워커 노드 (이하 노드)들이 존재하고 Kubernetes의 기능들이 올바르게 동작할 수 있도록 제어하는 제어 영역 (control plane)이 존재한다. 개발자들이 개발하고 만들어진 이미지들은 모두 노드들에 컨테이너 형태로 배포된다.
그래서 우선은 두 개의 네트워크로 나눠진다: 제어 영역을 위한 네트워크, 노드를 위한 네트워크. 이 때 제어 영역 네트워크에 내부 IP 영역 (RFC1918)이 아닌 영역으로 IP 주소가 할당될 수도 있고 내부 IP 영역으로 할당될 수도 있다. 둘 간의 차이가 어떤 것이겠는가? 내부 IP 영역으로 할당될 경우 제어 영역 네트워크 밖에서 Kubernetes에 접근할 수 없다. 더 쉽게 말해서 해당 클러스터에 대해 kubectl 명령어를 쓸 수 없다.
그렇다면 내부 IP로 할당된 제어 영역에 접근하기 위해서는 어떻게 해야하는가? 핵심은 제어 영역과 연결된 네트워크에서 접근 하는 것이다. 이를 가능하게 하기 위한 방법은 여러 가지가 있을 텐데 첫 번째 방법은 VPC를 만들고 안에 배스천 서버를 만든 뒤 해당 VPC와 제어 영역 네트워크를 VPN, VPC Peering 등을 이용하여 연결한 후 배스천 서버 내부에서 접근하는 방법이 있다. 다른 하나는 지금과 같이 온프레미스 환경과 VPN을 통해 연결된 경우 온프레미스 쪽에서는 VPC 제어 영역 네트워크를 연결할 수 있도록 열어주고 클라우드 환경에서는 VPC Peering을 통해 제어 영역 네트워크로 라우팅 될 수 있도록 경로를 열어주는 방법이 있다.
이번에 시스템을 구축할 때는 첫 번째 방법과 두 번째 방법을 놓고 고민했으며 최종적으로는 두 번째 방법으로 진행하기로 결정했다. 둘 간에 비교했을 때 가장 큰 비중을 차지하였던 요인은 두 가지 방법 중 어떤 것이 ‘접근 제어가 더 용이한가’ 였다. 위에서 잠깐 이야기했지만 GCP에 대한 접근 제어를 결국 고민해야했다. 첫 번째 방법으로 진행했을 경우 이제 배스천 서버에 대한 접근 제어를 어떻게 할 것인지를 또 생각해야하고 GCP 접근 제어를 관리하기 위해서 고려해야할 사항이 하나 더 늘어나는 격이다. 또한 배스천 서버를 여러 사용자가 사용하는 방식에 대해서 심도 깊게 고민해야했으며 (여기에 대한 얘기도 나중에 더 자세히하면 좋을듯), 이는 현재 한 달 남짓한 시간 안에 마무리를 하지 못할 가능성이 높았다. 반면에 두 번째 방식은 이제 GKE 접근 제어를 온프레미스 환경에 넘기는 방식이고 이는 그 때 당시 온프레미스 환경 접근 제어가 아주 잘 구성되어 있었기 때문에 보안이 좋을 뿐더러 고민해야할 사항을 하나 줄여 시간을 단축할 수 있었다. 그렇기 때문에 두 번째 방식으로 결정되었다.
이제 GKE에 접근할 수 있는 경로는 아래 굵은 화살표와 같다. 접근 엔트리포인트가 클라우드가 아닌 온프레미스 환경에 있기 때문에 GKE 접근 제어 중 대부분은 온프레미스 환경에서 신경쓰게 된다.
몇 가지 추상화된 과정과 컴포넌트들이 있지만 이 포스팅에서는 우선 큰 그림에 대해서만 살펴보고 다른 포스팅에서 좀 더 구체적인 이야기를 정리해보겠다.




GCP CloudSQL
디비에 대한 네트워크 접근 통제도 GKE와 동일하다. VPC Peering을 통해서 CloudSQL에 접근할 수 있는 내부 IP 영역을 온프레미스 환경에 알려준다. 또한 공개 IP는 존재하지 않도록 구성하므로 CloudSQL에 접근할 수 있는 네트워크는 같은 VPC에 존재하거나, Peering 등으로 연결되었거나 VPN으로 연결된 곳에서만 접근할 수 있다.
지금 상황에서는 VPN으로 연결된 온프레미스 환경 혹은 VPC Peering 되어 있는 GKE 클러스터가 올라간 네트워크 영역에서 CloudSQL에 접근할 수 있다.




Google Service
사실 위의 과정들을 거쳤을 때 이제 온프레미스 환경에서 필요한 모든 클라우드 서비스를 연결한 것 같지만 아직 가장 중요한 부분이 빠져있다 (그리고 이해하고 적용하기 가장 어려웠던 과정이다). 바로 해당 클라우드 서비스를 사용할 권한을 획득하기 위한 Google 인증 서비스이다.
GCP에서는 인터넷이 차단된 내부망에서 GCP 서비스를 사용할 수단을 제공해준다. VPC 내에서 접근할 수 있는 Google API 네트워크 영역이 존재한다. 이를 통해 인터넷에 연결되어 있지 않은 VPC에서도 해당 내부 Google API 네트워크와 연결할 수 있다면 인터넷을 타지 않고도 Google 에서 제공해주는 여러 서비스들을 이용할 수 있다. 우리는 그중에서 Google 인증 서비스를 사용할 것이고 이를 통해 해당 사용자가 권한을 획득할 것이다.
해당 문서를 통해 기본적인 내용을 알 수 있을 것이다. GCP에서는 199.36.153.8/30
영역에서 Google API에 접근할 수 있도록 네트워크를 열어두었다. 그리고 해당 영역에 접근하기 위해서는 private.googleapis.com 이라는 A Record를 이용해서 Cloud DNS를 구성하라고 안내하고 있다. 이 부분은 자세히 설명된 문서가 없으며 GCP 내부 구현 방식에 따른 가이드라고 생각하고 있다.
DNS 설정
그래서 가장 먼저 해야할 일은 private.googleapis.com DNS 쿼리가 들어올 경우 199.36.153.8/30
의 IP들을 반환할 수 있도록 Cloud DNS 구성을 설정하는 것이다.
동시에 고려해야할 것이 몇 가지가 있는데 그 중 첫 번째는 해당 DNS를 외부 인터넷에 노출시킬지 여부에 관한 것인데 당연히 아니다. 우리의 목표는 인터넷이 차단된 온프레미스 환경에서 VPN을 통해 Google 서비스를 이용하는 것인데 DNS 데이터를 인터넷에 노출시킬 필요는 없다. 이를 위해 GCP에서 제공해주는 ‘비공개 라우팅’ 방식을 통해 Cloud DNS를 구성할 것이다. 비공개 라우팅 방식의 Cloud DNS는 승인된 (혹은 연결된) VPC에서만 해당 DNS에 쿼리를 할 수 있다.
그러면 문제가 생긴다. 온프레미스 환경의 네트워크는 위에서 만든 비공개 라우팅 방식의 Cloud DNS와 연결되어 있지 않다. 그렇다면 해당 DNS에 쿼리를 할 수 없는데 이를 어떻게 해결해야할까?
Google에서는 이러한 문제를 해결하기 위해 Cloud DNS에 inbound server policy (인바운드 서버 정책)을 설정하도록 서비스를 제공해준다. 조금 더 구체적으로 이야기하면 비공개 라우팅 Cloud DNS와 연결된 VPC가 있을 것이고 해당 VPC 내부에는 여러 개의 서브넷이 있을 것인데, inbound server policy는 연결된 서브넷으로부터 IP를 하나씩 할당받아 해당 IP를 통해서 비공개 라우팅 Cloud DNS에 쿼리를 할 수 있는 엔트리포인트를 제공해준다. 그렇다면 온프레미스 환경에서는 해당 IP를 통해 DNS 쿼리를 수행하면 올바른 응답값을 받아볼 수 있을 것이다.




이렇게 클라우드 쪽 설정을 신나게하고 온프레미스 환경에서 gcloud auth login --no-launch-browser
와 같이 GCP 로그인을 시도해보면 타임아웃이 난다. 왜 그럴까?
On-premise DNS (BIND)
지금 생각하면 바보같지만 그 때 당시에는 잘 몰랐기 때문에 엄청난 좌절감을 맛보았다 (지금도 잘 모른다). ‘하라는 대로 했는데 왜 안되는거야?’
문제는 여러가지가 있었는데 가장 크리티컬한 것은 온프레미스 환경에서 DNS 쿼리를 날렸을 때 클라우드쪽 DNS Inbound forwarder로 쿼리를 전달하는 서비스가 없었기 때문이다. 두 번째 문제는 온프레미스 방화벽에서 DNS 쿼리를 전달할 수 있도록 53번 포트를 열어줘야한다. 사소한 것이지만 놓치기 쉽고 중요하다.
첫 번째 문제로 다시 돌아가면 이 문제를 해결할 방법은 온프레미스 환경에서 DNS를 올려놓고 해당 서버가 쿼리 요청을 받았을 때 Cloud DNS Inbound forwarder로 쿼리를 전달하는 것이다. 유명한 DNS 중에 BIND 를 이용하여 온프레미스 환경에 DNS를 구축하였다.
BIND에 대해서는 따로 설명을 하지는 않겠다. DNS 관련 서적에 대해 아래에서 적어놓는 것으로 설명을 대체하겠다. DNS와 BIND에 대해서 깊게 알 수 있는 아주 좋은 책이다.
include "/path/to/bind/etc/named.conf.options"
include "/path/to/bind/etc/named.conf.local"
options {
forwarders {
// Cloud DNS Inbound forwarder IP addresses
}
response-policy { zone "googleapis.zone"; };
...
}
include "/path/to/bind/etc/named.conf.default-zones";
zone "googleapis.zone" {
type master;
file "/path/to/bind/etc/db.googleapis.zone";
allow-query { none; };
}
$TTL 1H
@ SOA LOCALHOST. noreply.localhost(1 1h 15m 30d 2h)
NS LOCALHOST.
*.googleapis.com IN CNAME private.googleapis.com.
private.googleapis.com IN CNAME rpz-passthru.
위는 BIND 설정의 일부이다. named.conf
가 BIND 설정의 엔트리포인트이다. 눈여겨봐야할 부분은 두 군데인데 첫 번째는 named.conf.options 파일이다. 살펴보면 forwarders
블럭이 존재하는데 해당 블럭에 어디로 DNS 쿼리를 포워딩할지 명시해줄 수 있고 여기에 Cloud DNS Inbound forwarder의 IP를 명시해주면 된다.
두 번째는 db.googleapis.zone 파일이다. Google API로 요청을 보내는 클라이언트는 private.googleapis.com
가 아닌 compute.googleapis.com
와 같은 곳으로 요청을 보낸다. 온프레미스 환경의 DNS는 이 요청을 받아서 private.googleapis.com
에 대한 쿼리로 바꾸고 최종적으로는 Cloud DNS로부터 해당 값을 찾도록 만든다. 자기 자신은 private.googleapis.com
요청에 대해 rpz-passthru.
함으로써 아무런 동작을 하지 않고 다른 곳에서 찾도록 만든다.
혹시 DNS나 BIND에 대해서 더 궁금하다면 O’Reilly의 <DNS and BIND> 라는 책을 읽어봐도 좋을 것 같다.
이처럼 온프레미스 환경에 DNS 서버를 구축하게 되고 이를 Cloud DNS로 포워딩되게 만든다면 정상적으로 Google API에 온프레미스 환경의 클라이언트가 내부망을 통해 요청을 보낼 수 있게 된다. 이제 온프레미스 환경에서 권한을 획득하고 배포 관리자가 GKE에 서비스를 배포하고 관리를 할 수 있고 운영자들은 배포된 서비스들을 온프레미스 환경에서 사용할 수 있게 된다.




GCP Service Perimeter, Access Context Manager
이제 사용에 큰 문제는 없다. 그런데 포스팅 초반부에 얘기했던 GCP에 대한 접근 통제에 대해서도 고민이 필요했다. 우리가 이렇게 힘들게 내부망을 통해 클라우드 환경을 이용할 수 있게 만들었다해도, 사실 아무곳에서나 클라우드 환경을 통제할 수 있는 웹콘솔이나 CLI을 통해 클라우드 서비스들을 제어할 수 있으면 앞선 노력이 말짱 꽝이다. 적어도 IP 접근 통제 정도는 할 수 있어야 했다.
이러한 문제를 해결하기 위해 Service Perimeter (서비스 경계)와 Access Context Manager를 이용했다. 이 둘에 대해서는 사실 깊게 설명할 것은 없다. 이 둘의 관계는 Service Perimeter가 Access Context Manager에서 생성할 수 있는 Access Policy (접근 정책)을 이용하게 되어 있다. Access Policy를 통해 ‘특정 IP를 통해서 접근할 수 있다.’ 라는 정책을 만들 수 있고 Service Perimeter에서는 어떤 GCP 프로젝트에서 어떤 GCP 서비스에 해당 Access Policy를 바인딩할지를 결정한다. 이 개념과 관련된 문서 링크만 남기고 이 섹션은 간단하게 마무리하겠다.




마무리하며
사실 네트워크 쪽 문제만 다뤄서 그렇지 이로 인해 변경이 생기는 기존 리소스들도 많았고 이슈들도 많았다. 권한, 로깅과 모니터링, 소스 코드 관리 등등 다루지 못한 부분들이 많다. 짧은 시간에 많은 것을 만들어서 그런지 지식의 수준이 언제든 무너질 수 있는 모래성 같이 느껴진다. 늦기 전에 큰 얼개에 대해서 정리했다. 네트워크 쪽에서도 이번 포스팅에서는 내부 시스템과 관련된 부분들에 대해서 다루었다. 아직 어플리케이션 측면에서는 다루지 못했다. 그리고 좀 더 디테일하게 다룰 부분들이 있는데 이것에 대해서 이 글에서 모두 다루었다가는 글이 복잡해지고 시간이 많이 걸릴 것 같아 일단은 전체적인 그림과 흐름을 설명하는데 초점을 맞추었다.
아래의 주제는 이번 포스팅을 쓰면서 좀 더 구체적으로 정리해보면 좋겠다 싶었던 주제들이다.
- Private GKE의 네트워킹
- VPC Peering을 통한 GKE 제어 영역과 CloudSQL 라우팅 노출