跟我一起做:借助VPC打造專屬的私密Internet
虛擬專用云(Virtual Private Cloud ,VPC)在云計算技術中扮演了重要角色。VPC能讓兩個云實例之間進行私有通信,將網(wǎng)絡流量與其他互聯(lián)網(wǎng)用戶隔離開,從而提高安全性。換句話說,作為一種虛擬網(wǎng)絡環(huán)境,VPC能在公有云基礎設施中創(chuàng)建一個隔離且私密的網(wǎng)絡空間,為用戶提供一個安全、靈活、可定制的網(wǎng)絡環(huán)境,幫助他們構建和管理各種類型的應用和服務。
延伸閱讀,點擊鏈接了解 Akamai Cloud Computing
也就是說,就像是“套娃”一樣,我們可以借助這種技術,在公共互聯(lián)網(wǎng)的基礎上構建一個規(guī)模更小,更私密的專用“Internet”來。想想是不是還挺激動?
請收下這份教程,祝你玩得愉快!
背景
下文我們將通過一個儀表板為例進行介紹。為此我們將部署兩個應用程序,每個應用程序由一個應用服務器和一個數(shù)據(jù)庫服務器組成(共四個服務器)。第一個應用程序和對應的數(shù)據(jù)庫服務器采用正常方式來部署,第二組則配置為在VPC中運行。
每個應用的前端都使用Qwik構建,并使用Tailwind進行樣式設計。服務器端由Qwik City(Qwik的官方元框架)提供支持,并在Node.js上運行,托管在共享的Linode VPS上。這些應用還使用PM2進行進程管理,并使用Caddy作為反向代理和SSL供應商。數(shù)據(jù)存儲在PostgreSQL數(shù)據(jù)庫中,該數(shù)據(jù)庫也在共享的Linode VPS上運行。應用程序使用Drizzle與數(shù)據(jù)庫進行交互,Drizzle是JavaScript的對象關系映射器(ORM)。
兩個應用程序的整個基礎架構都由Terraform管理,具體來說,使用了Terraform的Linode提供程序進行管理。初次接觸Linode的童鞋對此可能感覺有些陌生,但只要學會了操作方法,就能快速輕松地進行基礎架構的配置和銷毀。
如果感興趣,大家也可以在這里找到下文涉及的所有代碼。
Demo
正如上文所述,本例將部署兩個相同的應用程序。這方面并沒有什么特別需要注意的,相應的內容如下圖所示。
整個技術棧沒有什么特別之處。之所以選擇這些工具也沒什么特別的原因,大家完全可以結合自己的需求和習慣選擇自己覺得最適合的工具。
真正有趣的部分在于基礎架構。
首先看看一號應用程序,它基本上由兩個托管在Akamai云中的服務器組成,一個用于應用程序,另一個用于數(shù)據(jù)庫。當用戶加載應用程序時,應用程序服務器將從數(shù)據(jù)庫中提取數(shù)據(jù),構建HTML,并將結果返回給用戶。
這里的問題在于數(shù)據(jù)庫連接的配置。某些情況下,我們可能會部署一個數(shù)據(jù)庫服務器,但并不能事先知道需要允許哪些IP地址的設備(如應用服務器)訪問。此時很多人慣用的做法是簡單粗暴地直接允許具備正確憑據(jù)的任何計算機連接。但這會帶來安全隱患,因為攻擊者有可能借此連接到數(shù)據(jù)庫并竊取敏感數(shù)據(jù)。
雖然攻擊者仍然需要知道數(shù)據(jù)庫主機、端口、用戶名和密碼才能獲取訪問權限,因此看起來這個隱患其實也并不是很大。畢竟剛才也說了,這種做法在業(yè)內其實很常見。但其實還有更好的做法!
如果已經(jīng)知道每臺需要訪問的計算機的IP地址,那么一種比較好的解決方案是設置防火墻或VLAN。但如果基礎架構更加動態(tài),服務器會頻繁啟動和關閉,IP地址列表的維護工作可能會顯得非常麻煩。這就到了VPC的用武之地。我們可以將服務器配置到VPC中,并允只允許位于同一個VPC網(wǎng)絡中的計算機相互自由通信。
這就是二號應用程序的設置方式。用戶可以連接到應用服務器,該服務器位于VPC中,但允許處理來自公共互聯(lián)網(wǎng)的流量。該應用服務器可以連接到數(shù)據(jù)庫,數(shù)據(jù)庫也位于同一個VPC中,并且只允許來自同一網(wǎng)絡的訪問。然后,應用服務器就可以照常從數(shù)據(jù)庫獲取數(shù)據(jù),構建HTML,并將頁面返回給用戶。
對于普通用戶,兩個應用的訪問體驗沒有任何差異。瀏覽器可以很好地加載任何一個應用的界面。對普通用戶來說,VPC使用與否都不會對自己產生一絲一毫的影響。
然而對于攻擊者來說,體驗就截然不同了。即便攻擊者設法獲取了數(shù)據(jù)庫訪問憑據(jù),他們也無法連接,因為VPC的網(wǎng)絡是被隔離的。此時,VPC充當了虛擬防火墻的角色,確保只有位于同一個網(wǎng)絡中的設備才能訪問數(shù)據(jù)庫。(這個概念有時也被稱為“分割[segmentation]”)
Talk is cheap,show you the證據(jù)
通過加入emoji圖標的示意圖來展示技術概念,這固然是一種可愛的做法,但為了更有說服力,最好還是能給出實證。因此我們將嘗試著使用數(shù)據(jù)庫客戶端DBeaver直接連接兩個數(shù)據(jù)庫,然后看看會發(fā)生什么。
對于一號數(shù)據(jù)庫,我們設置了一個Postgres連接:使用從Akamai儀表板獲取的主機IP地址以及在Terraform腳本中設置的端口、用戶名和密碼。發(fā)現(xiàn)連接可以按預期工作。
對于二號數(shù)據(jù)庫,我們只需要更改IP地址,因為所有數(shù)據(jù)庫配置都是由相同Terraform腳本處理的,唯一的區(qū)別是:二號數(shù)據(jù)庫服務器被放在與二號應用服務器相同的VPC中,并且配置為只允許來自同一網(wǎng)絡內的計算機訪問。
毫不意外,當我們嘗試連接時,盡管提供了所有正確信息,但依然收到了一個錯誤:
錯誤信息中完全沒有提到任何關于VPC的內容。它只是說我們的IP地址不在配置文件的允許訪問列表中。這是合理的。如果需要,我們可以明確地添加自己的IP地址并獲得對數(shù)據(jù)庫的訪問權限,但這不是重點。
這里的重點是:我們并沒有明確將任何IP地址添加到Postgres的允許列表中。然而應用服務器能夠正常連接,但其他所有人都被VPC阻止了。
接下來,上代碼
為了便于大家自行實驗和練習,我們提供了部署上述應用程序的Terraform代碼。相關文件請訪問這里。
需要注意的是,我們已經(jīng)盡最大努力讓這個Terraform文件對其他人可重用。這需要更多的變量和基于tfvars文件的配置設置。
接下來一起看看其中的關鍵部分。
1.配置Terraform提供程序
首先,由于使用了Linode Terraform提供程序,因此有必要了解這是如何設置的:
terraform {
required_providers {
linode = {
source = "linode/linode"
version = "2.13.0"
}
}
}
variable "LINODE_TOKEN" {}
provider "linode" {
token = var.LINODE_TOKEN
}
這部分設置了提供程序以及Terraform要求我們提供的變量,或者大家也可以使用tfvars文件提供。
2.設置VPC和VPC子網(wǎng)
接下來需要設置實際的VPC資源以及子網(wǎng)資源。
resource "linode_vpc" "vpc" {
label = "${local.app_name}-vpc"
region = var.REGION
}
resource "linode_vpc_subnet" "vpc_subnet" {
vpc_id = linode_vpc.vpc.id
label = "${local.app_name}-vpc-subnet"
ipv4 = "${var.VPC_SUBNET_IP}"
}
服務器只能添加到同一地區(qū)的VPC中。在撰寫本文時,Akamai云平臺支持VPC的地區(qū)有13個。最新信息請參閱Akamai官網(wǎng)。
有三個IPv4地址范圍是專門保留可用于私有網(wǎng)絡(如VPC)的:
- 10.0.0.0 – 10.255.255.255
- 172.16.0.0 – 172.31.255.255
- 192.168.0.0 – 192.168.255.255
因此在配置子網(wǎng)時,我們必須從這三個選項中選擇一個,并且必須使用CIDR格式來指定想使用的IP范圍。這里我們使用了10.0.0.0/24。
私有網(wǎng)絡中的每臺服務器都將擁有該范圍內的一個IPv4地址。
3. 設置應用服務器
為了讓Terraform部署我們的應用服務器,我們使用了linode_instance資源,此外還使用了stackscript資源來創(chuàng)建一個可重復使用的部署腳本,用于安裝和配置軟件。這就像一個位于Akamai云儀表板中的Bash腳本,我們可以直接將其用于新的服務器。
相關代碼就不放在這里了,但需要通過NVM安裝Node.js 20,安裝PM2,克隆項目代碼庫,運行應用,并設置Caddy。StackScript的內容請自行參閱源代碼,下文想簡單說說Terraform的部分。
resource "linode_instance" "application1" {
depends_on = [
linode_instance.database1
]
image = "linode/ubuntu20.04"
type = "g6-nanode-1"
label = "${local.app_name}-application1"
group = "${local.app_name}-group"
region = var.REGION
authorized_keys = [ linode_sshkey.ssh_key.ssh_key ]
stackscript_id = linode_stackscript.configure_app_server.id
stackscript_data = {
"GIT_REPO" = var.GIT_REPO,
"START_COMMAND" = var.START_COMMAND,
"DOMAIN" = var.DOMAIN1,
"NODE_PORT" = var.NODE_PORT,
"DB_HOST" = linode_instance.database1.ip_address,
"DB_PORT" = var.DB_PORT,
"DB_NAME" = var.DB_NAME,
"DB_USER" = var.DB_USER,
"DB_PASS" = var.DB_PASS,
}
}
resource "linode_instance" "application2" {
depends_on = [
linode_instance.database2
]
image = "linode/ubuntu20.04"
type = "g6-nanode-1"
label = "${local.app_name}-application2"
group = "${local.app_name}-group"
region = var.REGION
authorized_keys = [ linode_sshkey.ssh_key.ssh_key ]
stackscript_id = linode_stackscript.configure_app_server.id
stackscript_data = {
"GIT_REPO" = var.GIT_REPO,
"START_COMMAND" = var.START_COMMAND,
"DOMAIN" = var.DOMAIN2,
"NODE_PORT" = var.NODE_PORT,
"DB_HOST" = var.DB_PRIVATE_IP,
"DB_PORT" = var.DB_PORT,
"DB_NAME" = var.DB_NAME,
"DB_USER" = var.DB_USER,
"DB_PASS" = var.DB_PASS,
}
interface {
purpose = "public"
}
interface {
purpose = "vpc"
subnet_id = linode_vpc_subnet.vpc_subnet.id
}
}
這兩個資源的配置過程幾乎相同,只有一些重要的事情需要注意:
- 二號應用程序包含了將應用添加到VPC的相關配置。
- StackScript 需要數(shù)據(jù)庫的IP地址。一號應用程序使用一號數(shù)據(jù)庫的公共IP地址(linode_instance.database1.ip_address),二號應用程序則使用了一個變量(var.DB_PRIVATE_IP)。這個變量稍后會用到,它是分配給VPC中運行的二號數(shù)據(jù)庫的私有IP地址。該地址也可以手動分配,所以我們將其設置為10.0.0.3。
另外還要注意,應用服務器要部署到與VPC相同的地區(qū),原因如上文所述。
4. 設置數(shù)據(jù)庫服務器
數(shù)據(jù)庫也使用linode_instance和linode_stackscript資源進行設置。同樣,StackScript的內容就直接略過了,感興趣的同學請直接參閱代碼。接下來我們需要安裝Postgres,設置數(shù)據(jù)庫和憑據(jù),并提供一些配置選項。
resource "linode_instance" "database1" {
image = "linode/ubuntu20.04"
type = "g6-nanode-1"
label = "${local.app_name}-db1"
group = "${local.app_name}-group"
region = var.REGION
authorized_keys = [ linode_sshkey.ssh_key.ssh_key ]
stackscript_id = linode_stackscript.configure_db_server.id
stackscript_data = {
"DB_NAME" = var.DB_NAME,
"DB_USER" = var.DB_USER,
"DB_PASS" = var.DB_PASS,
"PG_HBA_ENTRY" = "host all all all md5"
}
}
resource "linode_instance" "database2" {
image = "linode/ubuntu20.04"
type = "g6-nanode-1"
label = "${local.app_name}-db2"
group = "${local.app_name}-group"
region = var.REGION
authorized_keys = [ linode_sshkey.ssh_key.ssh_key ]
stackscript_id = linode_stackscript.configure_db_server.id
stackscript_data = {
"DB_NAME" = var.DB_NAME,
"DB_USER" = var.DB_USER,
"DB_PASS" = var.DB_PASS,
"PG_HBA_ENTRY" = "host all all samenet md5"
}
interface {
purpose = "public"
}
interface {
purpose = "vpc"
subnet_id = linode_vpc_subnet.vpc_subnet.id
ipv4 {
vpc = var.DB_PRIVATE_IP
}
}
}
與應用程序服務器一樣,這兩個數(shù)據(jù)庫服務器大致相同,但有一些關鍵區(qū)別:
第二個數(shù)據(jù)庫包含將自己添加到VPC的配置。
客戶端身份驗證文件(pg_hba.conf)中會被寫入不同的設置。一號數(shù)據(jù)庫允許所有的互聯(lián)網(wǎng)連接("host all all all md5"),而二號數(shù)據(jù)庫只允許來自相同網(wǎng)絡的訪問("host all all samenet md5")。
另外需要注意,在配置VPC設置時,我們明確分配了服務器的私有IP地址(var.DB_PRIVATE_IP)。這與應用程序服務器所獲得的靜態(tài)值相同,因此可以從VPC內部連接到數(shù)據(jù)庫。
總結
希望本文能讓大家了解到VPC是什么,有什么用,以及何時應該考慮使用。這就像擁有了自己的小型私有互聯(lián)網(wǎng)。嚴格意義上來說,VPC并不是為了替代VLAN或防火墻,但它是現(xiàn)有安全實踐很好的補充。
—————————————————————————————————————————————————
如您所在的企業(yè)也在考慮采購云服務或進行云遷移,
點擊鏈接了解Akamai Linode的解決方案