當從本機 DB 用戶端連線到位於私有子網路的 RDS/Aurora 時,過去多半是「在公有子網路放一台跳板 EC2,開放 22 埠,透過 SSH 通道運作」,但現在看起來已經以使用 Session Manager 的做法為主流,因此這次就把連線步驟整理如下。
使用 AWS Systems Manager(SSM)的 Session Manager,可以完全不開入站埠,也不用管理 SSH 金鑰,就能連到 EC2,並直接做 RDS 的埠轉送。
一般的架構大概會像下面這樣。
[本機電腦] --SSH(22)--> [跳板 EC2(公有子網路)] --3306/5432--> [RDS(私有子網路)]
只要在跳板機的安全群組開放 22 埠,就會一直成為掃描與暴力破解的目標。即使把來源 IP 限縮,仍然會有以下問題。
0.0.0.0/0,或是允許來源 IP 清單的增刪變成日常工作authorized_keys.pem)常常透過 Slack 或 Email 共享SSH 的日誌只會留在作業系統內(例如 /var/log/secure)。
AWS 的存取權限已經用 IAM(SSO)管理,但跳板機還是要再用 OS 使用者與 SSH 金鑰這套不同機制。權限盤點會分散在兩個地方,甚至可能出現 IAM 已經移除,但 SSH 金鑰還有效的情況。
Session Manager 是 AWS Systems Manager 的一項功能,可讓你透過瀏覽器或 AWS CLI,對受管節點(EC2 或內部部署伺服器)建立 Shell 連線。除了 Shell 連線之外,也支援埠轉送。
[本機電腦 / 管理主控台]
│ HTTPS (443)
▼
[AWS Systems Manager 端點]
▲
│ HTTPS (443) ※僅從 EC2 端發起對外連線
[EC2(SSM Agent)]
重點是,EC2 上的 SSM Agent 會主動對 AWS 端發起對外連線。
因此,EC2 的安全群組不需要任何入站規則。
項目 SSH + 跳板機 SSM Session Manager 入站埠 22 埠開放 不需要 來源 IP 管理 需要 不需要 金鑰管理 需要配發與輪替 SSH 金鑰 不需要(由 IAM 權限控管) 存取控制 安全群組 / authorized_keys IAM 政策(與 SSO 整合) 稽核 只有作業系統內的日誌 CloudTrail + 工作階段日誌(CloudWatch Logs / S3) 轉接用 EC2 需要 轉接到 RDS 時需要 (後述) 我一開始誤以為「有了 SSM 就不需要跳板機」,但更精確地說,是不需要專門公開給 SSH 用的跳板機。因為 RDS/Aurora 本身不會安裝 SSM Agent,所以若要對 RDS 做埠轉送,仍然需要一台充當中繼的 EC2(受管節點)。
不過這台 EC2 不需要開任何入站,也不持有金鑰,因此性質與傳統跳板機完全不同。
要用 SSM 連線,必須符合以下 3 個條件:
a. EC2 已安裝並啟動 SSM Agent
b. EC2 已掛載 IAM 角色(Instance Profile)
c. EC2 可透過 HTTPS(443) 連到 SSM 端點
Amazon Linux 2 / Amazon Linux 2023 / Ubuntu Server(官方 AMI)/ Windows Server(官方 AMI)等通常都已預先安裝。
確認 EC2 上的「SSM Agent」是否正在運作的指令(在 EC2 內執行)
# Amazon Linux 2 / 2023
sudo systemctl status amazon-ssm-agent
# Ubuntu(若是透過 snap 安裝)
sudo snap services amazon-ssm-agent
替 EC2 掛載一個已套用 AWS 管理型政策 AmazonSSMManagedInstanceCore 的 IAM 角色。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": { "Service": "ec2.amazonaws.com" },
"Action": "sts:AssumeRole"
}
]
}
如果在帳戶層級啟用 Default Host Management Configuration(DHMC),即使不替每台執行個體另外掛 IAM 角色,也能納入 SSM 管理對象(需要 IMDSv2)
EC2 必須能夠透過 443 埠與以下端點通訊:
ssm.<region>.amazonaws.comssmmessages.<region>.amazonaws.comec2messages.<region>.amazonaws.com大致有 3 種路徑:
路徑組成 成本概念(東京區域參考) 公有子網路 + IGW 在 EC2 上配置公有 IP,僅用於對外連線 公有 IPv4 $0.005/小時(僅在執行時計費,若全天運作約 $3.7/月) 私有子網路 + NAT Gateway 透過 NAT 對外連線 $0.062/小時 + $0.062/GB(約 $45/月起) 私有子網路 + VPC 端點 建立上述 3 個 Interface 型端點 $0.014/小時 × 3 個 × AZ 數(2AZ 約 $61/月) 若使用 VPC 端點,需要在端點綁定的安全群組中允許來自 EC2 的 443 埠入站。另外,也需要開啟 VPC 的「DNS 主機名稱」與「DNS 解決」。
「放在公有子網路」聽起來可能會讓人緊張,但只要安全群組沒有開入站,外部就沒有可達路徑。若用途只是放一台轉接用 EC2,這通常是最便宜的做法。
接下來介紹實際上正在使用的架構。
範例:使用 SSM 專用跳板連線到 Aurora PostgreSQL
[本機電腦]
│ localhost:15432(埠轉送)
│ aws ssm start-session(HTTPS 443 / IAM 授權)
▼
[SSM 端點]
▲ HTTPS 443(僅對外連線)
│
[跳板 EC2] ← 公有子網路 / SG 入站:無 / 金鑰:無
│ 5432(僅允許跳板 SG → 叢集 SG)
▼
[Aurora PostgreSQL Serverless v2] ← 私有子網路(無網際網路路徑)
※ SG:安全群組
設計重點如下:
公有子網路 + IGW,僅用來對外連到 SSM 端點,入站保持空白跳板 SG → 叢集 SG 的 5432 埠ssm:StartSession)管理應用程式(Lambda)對 DB 的存取與 migration 是透過 RDS Data API(HTTPS + IAM)進行,因此不使用跳板。跳板只用於「人工透過 GUI 用戶端查看 DB」的情境。將用途限定後,平常就可以維持停止狀態來運作。
也推薦閱讀:〈AWS)試用 RDS Data API〉
使用 AWS CDK 的 BastionHostLinux 時,系統會自動替 SSM 連線所需的執行個體角色加上 AmazonSSMManagedInstanceCore。
import {
AmazonLinuxCpuType, BastionHostLinux, BlockDeviceVolume, InstanceClass,
InstanceSize, InstanceType, IpAddresses, MachineImage, SubnetType, Vpc,
} from 'aws-cdk-lib/aws-ec2'
const vpc = new Vpc(this, 'DbVpc', {
ipAddresses: IpAddresses.cidr('10.0.0.0/16'),
maxAzs: 2,
natGateways: 0, // 不建立 NAT Gateway
subnetConfiguration: [
// 給 Aurora 用。沒有網際網路路徑
{ name: 'db-isolated', subnetType: SubnetType.PRIVATE_ISOLATED, cidrMask: 24 },
// 給跳板用。只用於對 SSM 端點的對外連線
{ name: 'bastion-public', subnetType: SubnetType.PUBLIC, cidrMask: 24 },
],
})
// const cluster = new DatabaseCluster(this, 'DbCluster', { vpc, vpcSubnets: { subnetType: SubnetType.PRIVATE_ISOLATED }, ... })
const bastion = new BastionHostLinux(this, 'Bastion', {
vpc,
subnetSelection: { subnetType: SubnetType.PUBLIC },
instanceName: 'myapp-dev-bastion',
instanceType: InstanceType.of(InstanceClass.T4G, InstanceSize.NANO),
machineImage: MachineImage.latestAmazonLinux2023({
cpuType: AmazonLinuxCpuType.ARM_64,
// 將 AMI 固定到 cdk.context.json(原因後述)
cachedInContext: true,
}),
requireImdsv2: true,
blockDevices: [
{ deviceName: '/dev/xvda', volume: BlockDeviceVolume.ebs(8, { encrypted: true }) },
],
})
// 只允許跳板 SG → 叢集 SG 的 5432 埠
cluster.connections.allowDefaultPortFrom(bastion, 'SSM port forwarding from bastion')
BastionHostLinux 會在不設定金鑰組、且安全群組入站維持空白的情況下建立。
AMI 會用 cachedInContext: true 固定。latestAmazonLinux2023() 預設會透過 SSM 參數解析最新 AMI,因此只要有新 AMI 出現,cdk deploy 時執行個體就可能被替換,而停止中的跳板也會在重新建立時自動被啟動。
如果要更新 AMI,可以從 cdk.context.json 刪除對應的 key 後重新 synth。
前提:
# Session Manager 外掛程式(macOS)
brew install --cask session-manager-plugin
session-manager-plugin --version
aws sso login --profile <profile>)若跳板已停止,先啟動它(可能要 1~2 分鐘才會註冊到 SSM)
aws ec2 start-instances --instance-ids i-0123456789abcdef0
# 當 PingStatus 變成 Online 即可連線
aws ssm describe-instance-information \
--query "InstanceInformationList[].[InstanceId,PingStatus]" --output table
開始埠轉送(此終端機視窗要保持開啟)
aws ssm start-session \
--target i-0123456789abcdef0 \
--document-name AWS-StartPortForwardingSessionToRemoteHost \
--parameters '{"host":["<叢集 writer endpoint>"],"portNumber":["5432"],"localPortNumber":["15432"]}'
從 DB 用戶端連到 localhost:15432(若你設定了其他數字,後續步驟也要改成相同數值)
psql "host=localhost port=15432 dbname=<DB 名稱> user=<使用者名稱> sslmode=require"
※ 密碼請從儲存在 Secrets Manager 的 DB 驗證資訊取得。若使用 DBeaver 之類的 GUI 用戶端,請參考下方「使用 DBeaver 連線」。
用完後按 Ctrl+C 停止埠轉送,再把跳板停止(節省成本)
aws ec2 stop-instances --instance-ids i-0123456789abcdef0
GUI 用戶端 DBeaver 也可以直接在保持步驟 3 的埠轉送開啟狀態下,連到 localhost:15432 即可。
不使用 DBeaver 內建的 SSH Tunnel 設定(因為 tunnel 由 Session Manager 負責)。
從 Secrets Manager 取得 DB 驗證資訊
aws secretsmanager get-secret-value \
--secret-id <秘密名稱或 ARN> \
--query SecretString --output text \
--profile <profile>
# => {"username":"...","password":"...", ...}

在「主要(Main)」分頁輸入連線資訊
項目 設定值 連線方式(Connect by) Host 主機(Host) localhost 埠(Port) 15432(步驟 3 的 localPortNumber,預設通常是 5432) 資料庫(Database) <DB 名稱> 驗證(Authentication) Database Native 使用者名稱 / 密碼 從步驟 1 取得的 username / password

Ctrl+C 停止,DBeaver 的連線也會中斷Session Manager 本身對 EC2 不另外收費。也就是說,「SSH 或 SSM」在費用上幾乎沒有差異。
「誰可以連到哪一台執行個體、用哪種方式連線」是透過 IAM 政策控管。以下範例是「只允許對有 Env=dev 標籤的跳板使用 RDS 用的埠轉送」時的設定範例。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "ssm:StartSession",
"Resource": "arn:aws:ec2:ap-northeast-1:123456789012:instance/*",
"Condition": {
"StringEquals": { "ssm:resourceTag/Env": "dev" }
}
},
{
"Effect": "Allow",
"Action": "ssm:StartSession",
"Resource": "arn:aws:ssm:ap-northeast-1::document/AWS-StartPortForwardingSessionToRemoteHost"
},
{
"Effect": "Allow",
"Action": ["ssm:TerminateSession", "ssm:ResumeSession"],
"Resource": "arn:aws:ssm:*:*:session/${aws:userid}-*"
}
]
}
AWS-StartPortForwardingSessionToRemoteHost,就無法開啟 shell,只能使用埠轉送權限TerminateSession / ResumeSession 的 Resource 限定為 ${aws:userid}-*,就能只操作自己建立的工作階段對於埠轉送的工作階段,不會記錄實際送往 DB 的 SQL 內容。
若需要以查詢為單位的稽核,則應併用 DB 端的稽核日誌(例如 PostgreSQL 的 pgaudit)。
這次介紹了如何使用 Session Manager,從本機 DB 用戶端連線到私有子網路中的 RDS/Aurora。
實際導入後,最方便的地方是不用再管理 SSH 金鑰與來源 IP。
在這個架構下,雖然仍然需要一台中繼用 EC2,但只要將用途限定為本機端的 DB 連線,就能採用平常關機、需要時才開機的運作方式(節省成本)
感謝你讀到最後!
原文出處:https://qiita.com/j-yasukawa/items/701554f14fe8b02ff34e