
IAM 設好只是門鎖裝好了。有人在撬門(DDoS)、有人在找窗戶(SQL injection)、有人已經進來了(帳號被盜)——你怎麼知道?更常見的情況是:你以為門鎖好了,其實後門一直開著。
結論:安全不是三個服務,是四層邊界
| 層 | 問的問題 | 架構圖上看得到嗎 | 稽核先問嗎 |
|---|---|---|---|
| 1. 網路 | 什麼能連到什麼——含出去的 | ✅ | 後問 |
| 2. 工作負載身分 | 程式能動哪些 AWS 資源、秘密放哪 | 🔶 只看到 role 名字 | 後問 |
| 3. 人的身分 | 誰能登入、能做什麼、離職怎麼收 | ❌ | 先問 |
| 4. 合規範圍 | 哪些元件在稽核範圍內 | ❌ | 先問 |
我的經驗是,稽核第一個問的是第三、四層,也就是人和合規。多數人做完第一層就以為做完了。這篇每層講一件最容易漏的事,工具放在它該在的層。
第一層・網路:入站大家都會鎖,出站沒人管
入站的做法在 第 2 篇(Security Group 用引用不用 IP)和 第 4 篇(CloudFront 前的 ALB 要用自訂 header 和 prefix list 鎖)講過了。這裡講另一半。
SG 的出站規則預設全開。 入站鎖得再緊,機器被入侵之後,資料照樣從出站流出去——連到攻擊者的伺服器、把資料庫整包傳走,SG 一條都不會擋。NAT 讓 private subnet 的所有東西都出得去,這正是問題。
最少做到:出站只允許到 NAT 的 SG、VPC endpoint 的 SG、和明確列出的外部目的地(金流商的 IP)。其他全擋。金流案子的 PCI 稽核會直接問這一題,「出站有沒有管控」是必答。
# 應用 SG:出站只到資料庫 SG 和 Redis SG,不寫 0.0.0.0/0
aws ec2 authorize-security-group-egress --group-id sg-app \
--protocol tcp --port 5432 --source-group sg-db
aws ec2 authorize-security-group-egress --group-id sg-app \
--protocol tcp --port 6379 --source-group sg-redis
# 對外 HTTPS 只到金流商的 IP 段
aws ec2 authorize-security-group-egress --group-id sg-app \
--protocol tcp --port 443 --cidr 203.0.113.0/24出站還有一個早就被決定的部分:VPC 怎麼接到外面。 接法選下去,就決定了流量能不能走 NAT,也就是對外的流量能不能統一從固定的 IP 出去。三種接法:
- VPC Peering——兩個 VPC 點對點。不能傳遞:A–B、A–C 都接了,B 還是不能經 A 到 C。B 也不能借 A 的 NAT、internet gateway(VPC 直接連網際網路的出口)、VPN 出去。
- Site-to-Site VPN / Direct Connect 經虛擬私有閘道——接地端,也就是公司自己機房的網路。Site-to-Site VPN 是走網際網路的加密連線,Direct Connect 是拉到 AWS 的實體專線。虛擬私有閘道(virtual private gateway)是 VPC 這端接這兩種連線的接口。官方文件寫明,這條路的流量導不進 NAT gateway。所以地端想借雲端的固定 IP 對外,這條走不通。
- Transit Gateway——AWS 的中央轉接站,多個 VPC 和地端都接到它身上,形成星狀拓撲。同一頁文件寫,VPN / Direct Connect 改接 Transit Gateway,就能把流量導進 NAT。
防火牆有三個位置,各管一段:
- Security Group 綁在網卡上(task、EC2、RDS 各自綁):只有 allow 沒有 deny。它是 stateful,會記得連線,放出去的連線,回來的封包自動放行。用 SG 互相引用代替 IP。
- NACL 綁在 subnet 上:有 deny。它是 stateless,不記得連線,所以進和出要各寫一條。
- WAF 掛在 ALB 或 CloudFront 前面:擋 SQL injection、爬蟲、特定 IP、速率限制(設法見下面)。
NAT gateway 是例外:它不能綁 SG。要管控就用後面 instance 自己的 SG,或在 NAT 所在的 subnet 用 NACL。文件提醒 NAT 用的是 1024–65535 這段 port,寫 NACL 時要算進去。NACL 不記得連線,這段沒放行的話,從 NAT 出去的連線就收不到回應。
WAF 是這層最前面的一道。AWS 的 managed rule group 開兩組就夠起步:AWSManagedRulesCommonRuleSet(OWASP 那些)和 AWSManagedRulesKnownBadInputsRuleSet(Log4j 那類已知 payload)。再加一條 rate-based rule 擋單一 IP 的暴力請求。
aws wafv2 create-web-acl --name prod-waf --scope REGIONAL --default-action Allow={} \
--rules '[{"Name":"Common","Priority":1,
"Statement":{"ManagedRuleGroupStatement":{"VendorName":"AWS","Name":"AWSManagedRulesCommonRuleSet"}},
"OverrideAction":{"None":{}},
"VisibilityConfig":{"SampledRequestsEnabled":true,"CloudWatchMetricsEnabled":true,"MetricName":"Common"}}]' \
--visibility-config SampledRequestsEnabled=true,CloudWatchMetricsEnabled=true,MetricName=prod-wafWAF 有規則會誤擋(Common Rule Set 對某些正常的 POST body 會 false positive),先用 Count 模式跑一週看 log,確定沒誤擋再切 Block。直接上 Block 的話,客服會先知道。
Shield Standard 免費、自動開、擋第三四層的 DDoS。Shield Advanced 是付費訂閱,多數公司不需要——Shield Standard 加 WAF 的 rate limit 就是一般業務該有的水位。
第二層・工作負載身分:秘密不落地,但輪替後要重啟
兩個 role 分清楚:Task Role 是你的程式呼叫 AWS 用的(讀 S3、發 SQS),Execution Role 是 ECS 自己用的(拉 image、寫 log、把 Secrets Manager 的值注入容器)。權限加錯 role 是「程式拿不到」最常見的原因。
秘密進 Secrets Manager,task definition 用 secrets 欄位指向 ARN,不用 environment 放明文:
"secrets": [
{"name": "DB_PASSWORD", "valueFrom": "arn:aws:secretsmanager:ap-northeast-1:123456789012:secret:prod/db-xxxxx"}
]先講一個前提:自己存進 Secrets Manager 的秘密,不會自己輪替。 官方文件列了三種輪替方式:
- 託管的秘密用 managed rotation。託管的秘密是由其他 AWS 服務代管的秘密,例如交給 RDS 管的資料庫主密碼。輪替由那個服務幫你設定和管理。
- 合作夥伴系統上的秘密用 managed external secrets rotation,由 Secrets Manager 去對方系統更新。
- 其他秘密要配一支 Lambda 輪替函式,自己設定自動輪替。
然後是我整理架構卡時查文件才注意到的一條,官方文件用「Important」標出來:秘密變更後,必須強制重新部署或啟動新 task 才拿得到新值。 也就是說——你開了 Secrets Manager 的自動輪替,密碼換了,跑著的 task 還在用舊密碼。下一次重啟才炸,時間差可能是幾天,那時候沒人會想到是輪替造成的。
輪替要配重啟策略:輪替完成的事件 → EventBridge → 觸發 update-service --force-new-deployment。或者程式改成執行時從 Secrets Manager 拉,不靠環境變數。
同一頁文件還講了另一件事:用環境變數注入的秘密,docker inspect 和 log 都看得到。AWS 自己的建議是用 sidecar 把秘密寫進 task 內的 volume,應用容器再從 volume 讀。合規要求高的走這條。
加密:RDS、S3、EBS 的靜態加密要在建立時開,之後補不了(見 第 6 篇)。ALB 到 target 之間要不要再加密,多數人跳過,金流不能跳。
第三層・人的身分:架構圖完全看不到,稽核第一個問
這層跟架構無關,跟公司有關。四件事:
登入——不要用 IAM user 給人登入,用 IAM Identity Center(原 AWS SSO)接公司的身分來源,強制 MFA。root 帳號鎖起來,只在必要時用,用了要有通知。
權限——用權限集分角色。權限集(permission set)是 IAM Identity Center 裡一包可以指派給人的權限範本,按角色分成唯讀、開發、維運、管理幾包。每季列一次「誰有什麼」對照,不是設完就算。跨帳號用 assume role,不發長期 access key;已經發出去的 key 訂輪替週期,用 IAM 的金鑰年齡報表盯——AWS 不會讓 access key 過期,它永遠不會提醒你。
指派權限集時,IAM Identity Center 會在每個目標帳號建一個對應的 IAM role,掛上權限集裡的政策。
SCP(Service Control Policy)是 AWS Organizations 從上面套在帳號上的政策。它本身不給任何權限,只設上限,管的是成員帳號裡的 IAM user 和 role(含 root)。權限集和 SCP 是交集,不是誰蓋過誰:實際能做的是兩邊都允許的那一塊。所以權限集給 AdministratorAccess,碰到 SCP 擋住的動作一樣做不了。
兩個例外要記住。第一,SCP 不影響管理帳號,也就是建立這個 Organization 的那個帳號。從管理帳號登入就不受 SCP 約束——這是把日常工作移出管理帳號的理由之一。第二,SCP 也不影響 service-linked role,那是 AWS 服務自己建、自己用的 role。
收回——離職那一天要收的:Identity Center 帳號、SSH key、VPN、共用密碼、API token、SaaS 帳號。少收一項就是一個後門。
證據——CloudTrail 全帳號、全區域開,這是稽核的第一手證據。而且 log 要送到另一個帳號的 S3 bucket:自己的 log 自己能刪,等於沒有 log。保留多久是合規要求不是技術選擇——PCI 要求至少一年、近三個月要能立即取用。
aws cloudtrail create-trail --name org-trail --s3-bucket-name audit-logs-in-security-account \
--is-multi-region-trail --enable-log-file-validation
aws cloudtrail start-logging --name org-trailCloudTrail 記的是「誰呼叫了哪個 API」,另外兩樣把證據補齊:AWS Config 記資源設定的變化歷史,VPC Flow Logs 記網路層誰連了誰。
GuardDuty 放這層:它分析 CloudTrail、VPC Flow Logs、DNS log 抓異常——從沒見過的地點登入、EC2 連到已知的 C2 伺服器(攻擊者用來遙控被入侵機器的指揮伺服器)、有人在挖礦。一行開:
aws guardduty create-detector --enable高嚴重度的發現接 EventBridge 觸發自動回應:隔離 EC2(換一個全擋的 SG)、停用可疑的 access key、丟 Slack。每週有人看一次 GuardDuty,不然開了等於沒開。
第四層・合規範圍:策略是縮小,不是全部合規
金流案子的核心策略是讓越少元件碰到卡號越好。 每一個碰到卡號的元件都在 PCI 稽核範圍內。
最有效的一招:卡號直接從瀏覽器送到金流商(tokenization),你的後端只拿 token——這樣 ECS、RDS、Redis 全部脫離範圍,稽核範圍只剩前端那一段和金流商的整合。做不到的話,把碰卡號的服務隔離成獨立帳號,範圍就只有那一個帳號。
帳號本身是 AWS 最強的隔離邊界。一環境一帳號,用 AWS Organizations 的 SCP 從上面鎖住「不管你是誰都不能做的事」:關 CloudTrail、開沒加密的 S3、刪 GuardDuty detector。
個資也是這層的事,有兩題要答得出來。第一題是資料落地:存在哪個區域、有沒有法規要求(台灣個資法、GDPR)。第二題是存取紀錄:誰讀過個資,要答得出來。
Security Hub 放這層:它把 GuardDuty、Inspector、Config 的發現集中成一個看板,並對照 CIS Benchmark、PCI DSS 這些標準給你打分。Inspector 掃 ECR image 和 EC2 的已知 CVE。這兩個是「知道自己哪裡不合規」的工具,不是「變得合規」的工具——分數低了要有人去修。
這四層的驗證標準
設定存在不等於生效。每層一個實際的驗法:
| 層 | ❌ 不算驗證 | ✅ 算驗證 |
|---|---|---|
| 網路 | SG 出站規則列表 | 從 task 內 curl 一個不在白名單的外部網址,要連不上 |
| 工作負載(秘密) | task definition 用了 secrets | docker inspect 和 CloudWatch log 裡搜密碼字串,零命中 |
| 工作負載(加密) | 「加密有開」 | RDS / S3 / EBS 逐一列,沒有未加密的 |
| 人(帳號) | Identity Center 設好了 | 列 IAM user,只剩服務帳號;拿上一位離職者的名字在所有系統搜一次,零殘留 |
| 人(證據) | 「CloudTrail 有開」 | 每個區域都有 trail,且送到另一個帳號的 S3 bucket |
| 合規 | Security Hub 開了 | 分數有人看、有 owner、上月的 CRITICAL 這個月已關 |
實戰 Checklist
- SG 出站不是
0.0.0.0/0;WAF 先 Count 一週再 Block - 秘密走
secrets欄位;輪替接 force-new-deployment - 人用 Identity Center + MFA,沒有長期 access key;離職清單存在且有人執行
- CloudTrail 多區域開,送另一個帳號的 bucket;GuardDuty 開且每週有人看
- 碰卡號的元件列得出來,且越少越好
從 K8s 過來的話
四層對回去:網路層是 NetworkPolicy(但 K8s 那邊 egress 也是預設全開,同樣的坑);工作負載身分是 ServiceAccount + IRSA;人的身分是 RBAC 對 kubeconfig,一樣不該發長期憑證;合規掃描是 kube-bench 對 Security Hub、Falco 對 GuardDuty、Trivy 對 Inspector。
差最多的是帳號這個邊界——K8s 用 namespace 隔離,AWS 用帳號隔離,後者重得多但也硬得多。
相關文章
- 上一篇 成本管理,下一篇 EventBridge,回 AWS 系列目錄
- 入站那半邊:VPC 網路、負載平衡
- 人的身分從第一天開始: IAM
- 概念層:Application Security、Security Scanning
安全不是一次性的設定,是持續的習慣。每週看一次 GuardDuty、每季對一次「誰有什麼」——比出事後花三天處理划算得多。
