在 Day 18 我們完成了雲端效能優化,今天我們要建立 AWS 雲端安全防護體系。對於健身房 SaaS 系統的雲端部署,安全性是核心要求 - 從 IAM 精細權限控制到網路隔離,從資料加密到 DDoS 防護,我們將實作全方位的 AWS 安全架構,確保多租戶環境下的資料安全與合規性。
# infrastructure/iam-security-policies.yml
AWSTemplateFormatVersion: '2010-09-09'
Description: 'IAM Security Policies for Kyo SaaS Platform'
Resources:
# 應用程式執行角色
ApplicationExecutionRole:
Type: AWS::IAM::Role
Properties:
RoleName: KyoApplicationExecutionRole
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service:
- ecs-tasks.amazonaws.com
Action: sts:AssumeRole
- Effect: Allow
Principal:
Service:
- lambda.amazonaws.com
Action: sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy
Policies:
- PolicyName: ApplicationSecurityPolicy
PolicyDocument:
Version: '2012-10-17'
Statement:
# Secrets Manager 權限 (僅限應用程式密鑰)
- Effect: Allow
Action:
- secretsmanager:GetSecretValue
Resource:
- !Sub "arn:aws:secretsmanager:${AWS::Region}:${AWS::AccountId}:secret:kyo/database/*"
- !Sub "arn:aws:secretsmanager:${AWS::Region}:${AWS::AccountId}:secret:kyo/jwt/*"
- !Sub "arn:aws:secretsmanager:${AWS::Region}:${AWS::AccountId}:secret:kyo/redis/*"
# KMS 解密權限 (僅限應用程式金鑰)
- Effect: Allow
Action:
- kms:Decrypt
- kms:DescribeKey
Resource:
- !GetAtt ApplicationKMSKey.Arn
# CloudWatch Logs 權限
- Effect: Allow
Action:
- logs:CreateLogStream
- logs:PutLogEvents
Resource:
- !Sub "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/ecs/kyo-*"
# Parameter Store 權限 (僅限應用程式參數)
- Effect: Allow
Action:
- ssm:GetParameter
- ssm:GetParameters
Resource:
- !Sub "arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/kyo/app/*"
# 部署角色 (限制性部署權限)
DeploymentRole:
Type: AWS::IAM::Role
Properties:
RoleName: KyoDeploymentRole
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
AWS: !Sub "arn:aws:iam::${AWS::AccountId}:root"
Action: sts:AssumeRole
Condition:
StringEquals:
'aws:RequestedRegion': !Ref AWS::Region
IpAddress:
'aws:SourceIp':
- '203.0.113.0/24' # 允許的部署來源 IP
Policies:
- PolicyName: LimitedDeploymentPolicy
PolicyDocument:
Version: '2012-10-17'
Statement:
# ECS 服務更新權限
- Effect: Allow
Action:
- ecs:UpdateService
- ecs:DescribeServices
- ecs:RegisterTaskDefinition
- ecs:DescribeTaskDefinition
Resource:
- !Sub "arn:aws:ecs:${AWS::Region}:${AWS::AccountId}:service/kyo-*"
- !Sub "arn:aws:ecs:${AWS::Region}:${AWS::AccountId}:task-definition/kyo-*"
# ECR 權限
- Effect: Allow
Action:
- ecr:GetAuthorizationToken
- ecr:BatchCheckLayerAvailability
- ecr:GetDownloadUrlForLayer
- ecr:BatchGetImage
- ecr:PutImage
- ecr:InitiateLayerUpload
- ecr:UploadLayerPart
- ecr:CompleteLayerUpload
Resource:
- !Sub "arn:aws:ecr:${AWS::Region}:${AWS::AccountId}:repository/kyo-*"
# 禁止刪除關鍵資源
- Effect: Deny
Action:
- rds:DeleteDBInstance
- s3:DeleteBucket
- elasticache:DeleteCacheCluster
- kms:ScheduleKeyDeletion
Resource: "*"
# 監控角色
MonitoringRole:
Type: AWS::IAM::Role
Properties:
RoleName: KyoMonitoringRole
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service:
- cloudwatch.amazonaws.com
- lambda.amazonaws.com
Action: sts:AssumeRole
Policies:
- PolicyName: MonitoringPolicy
PolicyDocument:
Version: '2012-10-17'
Statement:
# CloudWatch 權限
- Effect: Allow
Action:
- cloudwatch:PutMetricData
- cloudwatch:GetMetricStatistics
- cloudwatch:ListMetrics
- logs:CreateLogGroup
- logs:CreateLogStream
- logs:PutLogEvents
- logs:DescribeLogGroups
- logs:DescribeLogStreams
Resource: "*"
# SNS 通知權限
- Effect: Allow
Action:
- sns:Publish
Resource:
- !Ref SecurityAlarmTopic
# 備份角色
BackupRole:
Type: AWS::IAM::Role
Properties:
RoleName: KyoBackupRole
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service:
- backup.amazonaws.com
Action: sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AWSBackupServiceRolePolicyForBackup
- arn:aws:iam::aws:policy/service-role/AWSBackupServiceRolePolicyForRestores
# 跨帳戶存取角色 (用於災難復原)
CrossAccountAccessRole:
Type: AWS::IAM::Role
Properties:
RoleName: KyoCrossAccountAccessRole
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
AWS: !Sub "arn:aws:iam::${DrAccountId}:root" # 災難復原帳戶
Action: sts:AssumeRole
Condition:
StringEquals:
'sts:ExternalId': !Ref CrossAccountExternalId
Policies:
- PolicyName: DisasterRecoveryPolicy
PolicyDocument:
Version: '2012-10-17'
Statement:
# 只讀存取權限
- Effect: Allow
Action:
- rds:DescribeDBInstances
- rds:DescribeDBSnapshots
- s3:GetObject
- s3:ListBucket
Resource: "*"
# 應用程式 KMS 金鑰
ApplicationKMSKey:
Type: AWS::KMS::Key
Properties:
Description: KMS key for Kyo application encryption
KeyPolicy:
Version: '2012-10-17'
Statement:
- Sid: Enable IAM root permissions
Effect: Allow
Principal:
AWS: !Sub "arn:aws:iam::${AWS::AccountId}:root"
Action: 'kms:*'
Resource: '*'
- Sid: Allow application role to use the key
Effect: Allow
Principal:
AWS: !GetAtt ApplicationExecutionRole.Arn
Action:
- kms:Decrypt
- kms:DescribeKey
Resource: '*'
- Sid: Allow CloudWatch Logs
Effect: Allow
Principal:
Service: !Sub "logs.${AWS::Region}.amazonaws.com"
Action:
- kms:Encrypt
- kms:Decrypt
- kms:ReEncrypt*
- kms:GenerateDataKey*
- kms:DescribeKey
Resource: '*'
# 多因子驗證政策
MFAPolicy:
Type: AWS::IAM::ManagedPolicy
Properties:
ManagedPolicyName: KyoMFARequiredPolicy
PolicyDocument:
Version: '2012-10-17'
Statement:
# 強制 MFA 的政策
- Sid: DenyAllExceptUsersToManageOwnPasswordsAndAccessKeys
Effect: Deny
NotAction:
- iam:CreateVirtualMFADevice
- iam:EnableMFADevice
- iam:GetUser
- iam:ListMFADevices
- iam:ListVirtualMFADevices
- iam:ResyncMFADevice
- sts:GetSessionToken
Resource: '*'
Condition:
BoolIfExists:
'aws:MultiFactorAuthPresent': 'false'
Parameters:
DrAccountId:
Type: String
Description: AWS Account ID for disaster recovery
Default: '123456789012'
CrossAccountExternalId:
Type: String
Description: External ID for cross-account access
NoEcho: true
# infrastructure/service-control-policies.yml
# 組織層級的安全控制政策
ProductionSCP:
Type: AWS::Organizations::Policy
Properties:
Name: KyoProductionSCP
Description: Security controls for production environments
Type: SERVICE_CONTROL_POLICY
Content: |
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "DenyRootAccountUsage",
"Effect": "Deny",
"Principal": {
"AWS": "arn:aws:iam::*:root"
},
"Action": "*",
"Resource": "*",
"Condition": {
"StringNotEquals": {
"aws:PrincipalType": "AssumedRole"
}
}
},
{
"Sid": "DenyUnencryptedObjectUploads",
"Effect": "Deny",
"Action": "s3:PutObject",
"Resource": "*",
"Condition": {
"StringNotEquals": {
"s3:x-amz-server-side-encryption": [
"AES256",
"aws:kms"
]
}
}
},
{
"Sid": "DenyInsecureConnections",
"Effect": "Deny",
"Action": "s3:*",
"Resource": "*",
"Condition": {
"Bool": {
"aws:SecureTransport": "false"
}
}
},
{
"Sid": "DenyPublicS3Buckets",
"Effect": "Deny",
"Action": [
"s3:PutBucketPublicAccessBlock",
"s3:PutBucketAcl",
"s3:PutObjectAcl"
],
"Resource": "*",
"Condition": {
"StringEquals": {
"s3:x-amz-acl": [
"public-read",
"public-read-write"
]
}
}
},
{
"Sid": "DenyNonCompliantRegions",
"Effect": "Deny",
"NotAction": [
"iam:*",
"sts:*",
"cloudfront:*",
"route53:*",
"support:*"
],
"Resource": "*",
"Condition": {
"StringNotEquals": {
"aws:RequestedRegion": [
"ap-northeast-1",
"ap-southeast-1",
"us-east-1"
]
}
}
}
]
}
# infrastructure/vpc-security-architecture.yml
Resources:
# 主要 VPC
SecureVPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: 10.0.0.0/16
EnableDnsHostnames: true
EnableDnsSupport: true
Tags:
- Key: Name
Value: Kyo-Secure-VPC
# 公開子網路 (僅供 ALB 使用)
PublicSubnet1:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref SecureVPC
CidrBlock: 10.0.1.0/24
AvailabilityZone: !Select [0, !GetAZs '']
MapPublicIpOnLaunch: false
Tags:
- Key: Name
Value: Kyo-Public-Subnet-1
- Key: Type
Value: Public
PublicSubnet2:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref SecureVPC
CidrBlock: 10.0.2.0/24
AvailabilityZone: !Select [1, !GetAZs '']
MapPublicIpOnLaunch: false
Tags:
- Key: Name
Value: Kyo-Public-Subnet-2
- Key: Type
Value: Public
# 私有子網路 (應用程式層)
PrivateSubnet1:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref SecureVPC
CidrBlock: 10.0.11.0/24
AvailabilityZone: !Select [0, !GetAZs '']
Tags:
- Key: Name
Value: Kyo-Private-App-Subnet-1
- Key: Type
Value: Private-App
PrivateSubnet2:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref SecureVPC
CidrBlock: 10.0.12.0/24
AvailabilityZone: !Select [1, !GetAZs '']
Tags:
- Key: Name
Value: Kyo-Private-App-Subnet-2
- Key: Type
Value: Private-App
# 資料庫子網路 (最嚴格隔離)
DatabaseSubnet1:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref SecureVPC
CidrBlock: 10.0.21.0/24
AvailabilityZone: !Select [0, !GetAZs '']
Tags:
- Key: Name
Value: Kyo-Database-Subnet-1
- Key: Type
Value: Database
DatabaseSubnet2:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref SecureVPC
CidrBlock: 10.0.22.0/24
AvailabilityZone: !Select [1, !GetAZs '']
Tags:
- Key: Name
Value: Kyo-Database-Subnet-2
- Key: Type
Value: Database
# 管理子網路 (堡壘機)
ManagementSubnet:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref SecureVPC
CidrBlock: 10.0.31.0/24
AvailabilityZone: !Select [0, !GetAZs '']
Tags:
- Key: Name
Value: Kyo-Management-Subnet
- Key: Type
Value: Management
# 網際網路閘道
InternetGateway:
Type: AWS::EC2::InternetGateway
Properties:
Tags:
- Key: Name
Value: Kyo-IGW
AttachGateway:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
VpcId: !Ref SecureVPC
InternetGatewayId: !Ref InternetGateway
# NAT 閘道 (高可用性)
NATGateway1EIP:
Type: AWS::EC2::EIP
DependsOn: AttachGateway
Properties:
Domain: vpc
NATGateway2EIP:
Type: AWS::EC2::EIP
DependsOn: AttachGateway
Properties:
Domain: vpc
NATGateway1:
Type: AWS::EC2::NatGateway
Properties:
AllocationId: !GetAtt NATGateway1EIP.AllocationId
SubnetId: !Ref PublicSubnet1
NATGateway2:
Type: AWS::EC2::NatGateway
Properties:
AllocationId: !GetAtt NATGateway2EIP.AllocationId
SubnetId: !Ref PublicSubnet2
# 路由表配置
PublicRouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref SecureVPC
Tags:
- Key: Name
Value: Kyo-Public-Routes
DefaultPublicRoute:
Type: AWS::EC2::Route
DependsOn: AttachGateway
Properties:
RouteTableId: !Ref PublicRouteTable
DestinationCidrBlock: 0.0.0.0/0
GatewayId: !Ref InternetGateway
PublicSubnet1RouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref PublicRouteTable
SubnetId: !Ref PublicSubnet1
PublicSubnet2RouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref PublicRouteTable
SubnetId: !Ref PublicSubnet2
# 私有路由表
PrivateRouteTable1:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref SecureVPC
Tags:
- Key: Name
Value: Kyo-Private-Routes-AZ1
DefaultPrivateRoute1:
Type: AWS::EC2::Route
Properties:
RouteTableId: !Ref PrivateRouteTable1
DestinationCidrBlock: 0.0.0.0/0
NatGatewayId: !Ref NATGateway1
PrivateSubnet1RouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref PrivateRouteTable1
SubnetId: !Ref PrivateSubnet1
PrivateRouteTable2:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref SecureVPC
Tags:
- Key: Name
Value: Kyo-Private-Routes-AZ2
DefaultPrivateRoute2:
Type: AWS::EC2::Route
Properties:
RouteTableId: !Ref PrivateRouteTable2
DestinationCidrBlock: 0.0.0.0/0
NatGatewayId: !Ref NATGateway2
PrivateSubnet2RouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref PrivateRouteTable2
SubnetId: !Ref PrivateSubnet2
# 資料庫路由表 (無網際網路存取)
DatabaseRouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref SecureVPC
Tags:
- Key: Name
Value: Kyo-Database-Routes
DatabaseSubnet1RouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref DatabaseRouteTable
SubnetId: !Ref DatabaseSubnet1
DatabaseSubnet2RouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref DatabaseRouteTable
SubnetId: !Ref DatabaseSubnet2
# Network ACLs 配置
Resources:
# 公開子網路 NACL
PublicNetworkACL:
Type: AWS::EC2::NetworkAcl
Properties:
VpcId: !Ref SecureVPC
Tags:
- Key: Name
Value: Kyo-Public-NACL
# 入站規則 - HTTPS/HTTP 流量
PublicInboundHTTPSRule:
Type: AWS::EC2::NetworkAclEntry
Properties:
NetworkAclId: !Ref PublicNetworkACL
RuleNumber: 100
Protocol: 6
RuleAction: allow
CidrBlock: 0.0.0.0/0
PortRange:
From: 443
To: 443
PublicInboundHTTPRule:
Type: AWS::EC2::NetworkAclEntry
Properties:
NetworkAclId: !Ref PublicNetworkACL
RuleNumber: 110
Protocol: 6
RuleAction: allow
CidrBlock: 0.0.0.0/0
PortRange:
From: 80
To: 80
# 臨時埠範圍 (用於回應流量)
PublicInboundEphemeralRule:
Type: AWS::EC2::NetworkAclEntry
Properties:
NetworkAclId: !Ref PublicNetworkACL
RuleNumber: 120
Protocol: 6
RuleAction: allow
CidrBlock: 0.0.0.0/0
PortRange:
From: 1024
To: 65535
# 出站規則
PublicOutboundRule:
Type: AWS::EC2::NetworkAclEntry
Properties:
NetworkAclId: !Ref PublicNetworkACL
RuleNumber: 100
Protocol: 6
Egress: true
RuleAction: allow
CidrBlock: 0.0.0.0/0
PortRange:
From: 0
To: 65535
# 私有子網路 NACL
PrivateNetworkACL:
Type: AWS::EC2::NetworkAcl
Properties:
VpcId: !Ref SecureVPC
Tags:
- Key: Name
Value: Kyo-Private-NACL
# 僅允許來自公開子網路的流量
PrivateInboundFromPublicRule:
Type: AWS::EC2::NetworkAclEntry
Properties:
NetworkAclId: !Ref PrivateNetworkACL
RuleNumber: 100
Protocol: 6
RuleAction: allow
CidrBlock: 10.0.0.0/16 # VPC 內部流量
PortRange:
From: 0
To: 65535
# 允許 HTTPS 出站 (用於 API 呼叫)
PrivateOutboundHTTPSRule:
Type: AWS::EC2::NetworkAclEntry
Properties:
NetworkAclId: !Ref PrivateNetworkACL
RuleNumber: 100
Protocol: 6
Egress: true
RuleAction: allow
CidrBlock: 0.0.0.0/0
PortRange:
From: 443
To: 443
# 允許 VPC 內部出站流量
PrivateOutboundVPCRule:
Type: AWS::EC2::NetworkAclEntry
Properties:
NetworkAclId: !Ref PrivateNetworkACL
RuleNumber: 110
Protocol: 6
Egress: true
RuleAction: allow
CidrBlock: 10.0.0.0/16
PortRange:
From: 0
To: 65535
# 資料庫 NACL (最嚴格)
DatabaseNetworkACL:
Type: AWS::EC2::NetworkAcl
Properties:
VpcId: !Ref SecureVPC
Tags:
- Key: Name
Value: Kyo-Database-NACL
# 僅允許來自應用程式子網路的資料庫流量
DatabaseInboundPostgreSQLRule:
Type: AWS::EC2::NetworkAclEntry
Properties:
NetworkAclId: !Ref DatabaseNetworkACL
RuleNumber: 100
Protocol: 6
RuleAction: allow
CidrBlock: 10.0.11.0/24 # 私有子網路 1
PortRange:
From: 5432
To: 5432
DatabaseInboundPostgreSQLRule2:
Type: AWS::EC2::NetworkAclEntry
Properties:
NetworkAclId: !Ref DatabaseNetworkACL
RuleNumber: 110
Protocol: 6
RuleAction: allow
CidrBlock: 10.0.12.0/24 # 私有子網路 2
PortRange:
From: 5432
To: 5432
# Redis 流量
DatabaseInboundRedisRule:
Type: AWS::EC2::NetworkAclEntry
Properties:
NetworkAclId: !Ref DatabaseNetworkACL
RuleNumber: 120
Protocol: 6
RuleAction: allow
CidrBlock: 10.0.11.0/24
PortRange:
From: 6379
To: 6379
DatabaseInboundRedisRule2:
Type: AWS::EC2::NetworkAclEntry
Properties:
NetworkAclId: !Ref DatabaseNetworkACL
RuleNumber: 130
Protocol: 6
RuleAction: allow
CidrBlock: 10.0.12.0/24
PortRange:
From: 6379
To: 6379
# 資料庫出站規則 (僅允許回應)
DatabaseOutboundRule:
Type: AWS::EC2::NetworkAclEntry
Properties:
NetworkAclId: !Ref DatabaseNetworkACL
RuleNumber: 100
Protocol: 6
Egress: true
RuleAction: allow
CidrBlock: 10.0.0.0/16
PortRange:
From: 1024
To: 65535
# infrastructure/security-groups.yml
Resources:
# ALB 安全群組
ALBSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupName: Kyo-ALB-SecurityGroup
GroupDescription: Security group for Application Load Balancer
VpcId: !Ref SecureVPC
SecurityGroupIngress:
# HTTPS 流量
- IpProtocol: tcp
FromPort: 443
ToPort: 443
CidrIp: 0.0.0.0/0
Description: "HTTPS traffic from internet"
# HTTP 流量 (重定向到 HTTPS)
- IpProtocol: tcp
FromPort: 80
ToPort: 80
CidrIp: 0.0.0.0/0
Description: "HTTP traffic (redirect to HTTPS)"
SecurityGroupEgress:
# 僅允許到應用程式的流量
- IpProtocol: tcp
FromPort: 3000
ToPort: 3001
DestinationSecurityGroupId: !Ref ApplicationSecurityGroup
Description: "Traffic to application servers"
Tags:
- Key: Name
Value: Kyo-ALB-SG
# 應用程式安全群組
ApplicationSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupName: Kyo-Application-SecurityGroup
GroupDescription: Security group for application servers
VpcId: !Ref SecureVPC
SecurityGroupIngress:
# 來自 ALB 的流量
- IpProtocol: tcp
FromPort: 3000
ToPort: 3001
SourceSecurityGroupId: !Ref ALBSecurityGroup
Description: "Traffic from ALB"
# 來自同一安全群組的流量 (微服務間通訊)
- IpProtocol: tcp
FromPort: 3000
ToPort: 3010
SourceSecurityGroupId: !Ref ApplicationSecurityGroup
Description: "Inter-service communication"
# 來自堡壘機的 SSH (僅用於緊急情況)
- IpProtocol: tcp
FromPort: 22
ToPort: 22
SourceSecurityGroupId: !Ref BastionSecurityGroup
Description: "SSH from bastion host"
SecurityGroupEgress:
# HTTPS 出站 (API 呼叫、套件下載等)
- IpProtocol: tcp
FromPort: 443
ToPort: 443
CidrIp: 0.0.0.0/0
Description: "HTTPS outbound"
# DNS 查詢
- IpProtocol: udp
FromPort: 53
ToPort: 53
CidrIp: 0.0.0.0/0
Description: "DNS queries"
# 到資料庫的流量
- IpProtocol: tcp
FromPort: 5432
ToPort: 5432
DestinationSecurityGroupId: !Ref DatabaseSecurityGroup
Description: "PostgreSQL connection"
# 到 Redis 的流量
- IpProtocol: tcp
FromPort: 6379
ToPort: 6379
DestinationSecurityGroupId: !Ref CacheSecurityGroup
Description: "Redis connection"
Tags:
- Key: Name
Value: Kyo-Application-SG
# 資料庫安全群組
DatabaseSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupName: Kyo-Database-SecurityGroup
GroupDescription: Security group for database servers
VpcId: !Ref SecureVPC
SecurityGroupIngress:
# 僅允許來自應用程式的 PostgreSQL 連線
- IpProtocol: tcp
FromPort: 5432
ToPort: 5432
SourceSecurityGroupId: !Ref ApplicationSecurityGroup
Description: "PostgreSQL from application"
# 來自堡壘機的連線 (管理用途)
- IpProtocol: tcp
FromPort: 5432
ToPort: 5432
SourceSecurityGroupId: !Ref BastionSecurityGroup
Description: "PostgreSQL from bastion for management"
# 無出站規則 (使用預設拒絕)
Tags:
- Key: Name
Value: Kyo-Database-SG
# 快取安全群組
CacheSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupName: Kyo-Cache-SecurityGroup
GroupDescription: Security group for Redis cache
VpcId: !Ref SecureVPC
SecurityGroupIngress:
# 僅允許來自應用程式的 Redis 連線
- IpProtocol: tcp
FromPort: 6379
ToPort: 6379
SourceSecurityGroupId: !Ref ApplicationSecurityGroup
Description: "Redis from application"
Tags:
- Key: Name
Value: Kyo-Cache-SG
# 堡壘機安全群組
BastionSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupName: Kyo-Bastion-SecurityGroup
GroupDescription: Security group for bastion host
VpcId: !Ref SecureVPC
SecurityGroupIngress:
# SSH 存取 (限制來源 IP)
- IpProtocol: tcp
FromPort: 22
ToPort: 22
CidrIp: 203.0.113.0/24 # 公司 IP 範圍
Description: "SSH from corporate network"
SecurityGroupEgress:
# SSH 到私有資源
- IpProtocol: tcp
FromPort: 22
ToPort: 22
DestinationSecurityGroupId: !Ref ApplicationSecurityGroup
Description: "SSH to application servers"
# 資料庫連線
- IpProtocol: tcp
FromPort: 5432
ToPort: 5432
DestinationSecurityGroupId: !Ref DatabaseSecurityGroup
Description: "Database access for management"
# HTTPS 出站
- IpProtocol: tcp
FromPort: 443
ToPort: 443
CidrIp: 0.0.0.0/0
Description: "HTTPS outbound"
Tags:
- Key: Name
Value: Kyo-Bastion-SG
# VPC 端點安全群組
VPCEndpointSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupName: Kyo-VPCEndpoint-SecurityGroup
GroupDescription: Security group for VPC endpoints
VpcId: !Ref SecureVPC
SecurityGroupIngress:
# HTTPS 流量來自私有子網路
- IpProtocol: tcp
FromPort: 443
ToPort: 443
CidrIp: 10.0.0.0/16
Description: "HTTPS from VPC"
Tags:
- Key: Name
Value: Kyo-VPCEndpoint-SG
# infrastructure/waf-protection.yml
Resources:
# WAF Web ACL
KyoWebACL:
Type: AWS::WAFv2::WebACL
Properties:
Name: KyoSaaS-WebACL
Scope: CLOUDFRONT
DefaultAction:
Allow: {}
Description: WAF rules for Kyo SaaS platform
Rules:
# 1. IP 白名單規則
- Name: IPWhitelistRule
Priority: 10
Statement:
IPSetReferenceStatement:
Arn: !GetAtt TrustedIPSet.Arn
Action:
Allow: {}
VisibilityConfig:
SampledRequestsEnabled: true
CloudWatchMetricsEnabled: true
MetricName: IPWhitelistRule
# 2. IP 黑名單規則
- Name: IPBlacklistRule
Priority: 20
Statement:
IPSetReferenceStatement:
Arn: !GetAtt BlockedIPSet.Arn
Action:
Block: {}
VisibilityConfig:
SampledRequestsEnabled: true
CloudWatchMetricsEnabled: true
MetricName: IPBlacklistRule
# 3. 地理位置限制
- Name: GeoBlockRule
Priority: 30
Statement:
GeoMatchStatement:
CountryCodes:
- CN # 根據需求調整
- RU
- KP
Action:
Block: {}
VisibilityConfig:
SampledRequestsEnabled: true
CloudWatchMetricsEnabled: true
MetricName: GeoBlockRule
# 4. SQL 注入保護
- Name: SQLInjectionRule
Priority: 40
Statement:
ManagedRuleGroupStatement:
VendorName: AWS
Name: AWSManagedRulesSQLiRuleSet
OverrideAction:
None: {}
VisibilityConfig:
SampledRequestsEnabled: true
CloudWatchMetricsEnabled: true
MetricName: SQLInjectionRule
# 5. XSS 保護
- Name: XSSRule
Priority: 50
Statement:
ManagedRuleGroupStatement:
VendorName: AWS
Name: AWSManagedRulesCommonRuleSet
ExcludedRules:
- Name: GenericRFI_BODY
- Name: GenericRFI_QUERYARGUMENTS
OverrideAction:
None: {}
VisibilityConfig:
SampledRequestsEnabled: true
CloudWatchMetricsEnabled: true
MetricName: XSSRule
# 6. 已知攻擊模式
- Name: KnownBadInputsRule
Priority: 60
Statement:
ManagedRuleGroupStatement:
VendorName: AWS
Name: AWSManagedRulesKnownBadInputsRuleSet
OverrideAction:
None: {}
VisibilityConfig:
SampledRequestsEnabled: true
CloudWatchMetricsEnabled: true
MetricName: KnownBadInputsRule
# 7. 限制速率規則
- Name: RateLimitRule
Priority: 70
Statement:
RateBasedStatement:
Limit: 2000
AggregateKeyType: IP
ScopeDownStatement:
NotStatement:
Statement:
IPSetReferenceStatement:
Arn: !GetAtt TrustedIPSet.Arn
Action:
Block: {}
VisibilityConfig:
SampledRequestsEnabled: true
CloudWatchMetricsEnabled: true
MetricName: RateLimitRule
# 8. API 特定保護
- Name: APIProtectionRule
Priority: 80
Statement:
AndStatement:
Statements:
- ByteMatchStatement:
SearchString: "/api/"
FieldToMatch:
UriPath: {}
TextTransformations:
- Priority: 0
Type: LOWERCASE
PositionalConstraint: CONTAINS
- RateBasedStatement:
Limit: 1000
AggregateKeyType: IP
Action:
Block: {}
VisibilityConfig:
SampledRequestsEnabled: true
CloudWatchMetricsEnabled: true
MetricName: APIProtectionRule
VisibilityConfig:
SampledRequestsEnabled: true
CloudWatchMetricsEnabled: true
MetricName: KyoWebACL
# 信任的 IP 集合
TrustedIPSet:
Type: AWS::WAFv2::IPSet
Properties:
Name: KyoTrustedIPs
Scope: CLOUDFRONT
IPAddressVersion: IPV4
Addresses:
- 203.0.113.0/24 # 公司辦公室
- 198.51.100.0/24 # 備用辦公室
- 192.0.2.0/24 # VPN 出口
# 封鎖的 IP 集合
BlockedIPSet:
Type: AWS::WAFv2::IPSet
Properties:
Name: KyoBlockedIPs
Scope: CLOUDFRONT
IPAddressVersion: IPV4
Addresses:
- 192.0.2.44/32 # 已知攻擊者 IP
- 203.0.113.12/32 # 惡意爬蟲
# WAF 記錄配置
WAFLogGroup:
Type: AWS::Logs::LogGroup
Properties:
LogGroupName: /aws/wafv2/kyosaas
RetentionInDays: 30
WAFLoggingConfiguration:
Type: AWS::WAFv2::LoggingConfiguration
Properties:
ResourceArn: !GetAtt KyoWebACL.Arn
LogDestinationConfigs:
- !Sub "${WAFLogGroup.Arn}:*"
LoggingFilter:
DefaultBehavior: KEEP
Filters:
- Behavior: KEEP
Conditions:
- ActionCondition:
Action: BLOCK
Requirement: MEETS_ANY
# CloudWatch 警報
WAFBlockedRequestsAlarm:
Type: AWS::CloudWatch::Alarm
Properties:
AlarmName: WAF-HighBlockedRequests
AlarmDescription: High number of blocked requests detected
MetricName: BlockedRequests
Namespace: AWS/WAFV2
Statistic: Sum
Period: 300
EvaluationPeriods: 2
Threshold: 100
ComparisonOperator: GreaterThanThreshold
Dimensions:
- Name: WebACL
Value: !Ref KyoWebACL
- Name: Region
Value: CloudFront
AlarmActions:
- !Ref SecurityAlarmTopic
# DDoS 防護
DDoSProtection:
Type: AWS::Shield::Protection
Properties:
Name: KyoSaaS-DDoS-Protection
ResourceArn: !Sub "arn:aws:cloudfront::${AWS::AccountId}:distribution/${CloudFrontDistribution}"
# Advanced DDoS 回應團隊存取
DRTAccess:
Type: AWS::Shield::DRTAccess
Properties:
EmergencyContactList:
- ContactNotes: "Primary security contact"
EmailAddress: "security@kyosaas.com"
PhoneNumber: "+1-555-123-4567"
- ContactNotes: "Backup security contact"
EmailAddress: "backup-security@kyosaas.com"
PhoneNumber: "+1-555-765-4321"
RoleArn: !GetAtt DRTAccessRole.Arn
# DRT 存取角色
DRTAccessRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service: drt.shield.amazonaws.com
Action: sts:AssumeRole
Policies:
- PolicyName: DRTAccessPolicy
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- cloudfront:List*
- cloudfront:Get*
- route53:List*
- route53:Get*
- elasticloadbalancing:Describe*
Resource: "*"
# infrastructure/secrets-encryption.yml
Resources:
# 主要應用程式 KMS 金鑰
ApplicationKMSKey:
Type: AWS::KMS::Key
Properties:
Description: Primary encryption key for Kyo SaaS application
KeyUsage: ENCRYPT_DECRYPT
KeySpec: SYMMETRIC_DEFAULT
MultiRegion: true
KeyPolicy:
Version: '2012-10-17'
Statement:
- Sid: Enable IAM root permissions
Effect: Allow
Principal:
AWS: !Sub "arn:aws:iam::${AWS::AccountId}:root"
Action: 'kms:*'
Resource: '*'
- Sid: Allow application services
Effect: Allow
Principal:
AWS:
- !GetAtt ApplicationExecutionRole.Arn
- !GetAtt MonitoringRole.Arn
Action:
- kms:Decrypt
- kms:DescribeKey
- kms:GenerateDataKey
Resource: '*'
- Sid: Allow CloudWatch Logs
Effect: Allow
Principal:
Service: !Sub "logs.${AWS::Region}.amazonaws.com"
Action:
- kms:Encrypt
- kms:Decrypt
- kms:ReEncrypt*
- kms:GenerateDataKey*
- kms:DescribeKey
Resource: '*'
Condition:
ArnEquals:
'kms:EncryptionContext:aws:logs:arn': !Sub "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:*"
- Sid: Allow AWS services
Effect: Allow
Principal:
Service:
- secretsmanager.amazonaws.com
- rds.amazonaws.com
- elasticache.amazonaws.com
Action:
- kms:Decrypt
- kms:GenerateDataKey
Resource: '*'
ApplicationKMSAlias:
Type: AWS::KMS::Alias
Properties:
AliasName: alias/kyo-application-key
TargetKeyId: !Ref ApplicationKMSKey
# 資料庫專用 KMS 金鑰
DatabaseKMSKey:
Type: AWS::KMS::Key
Properties:
Description: Encryption key for database and sensitive data
KeyUsage: ENCRYPT_DECRYPT
KeySpec: SYMMETRIC_DEFAULT
KeyPolicy:
Version: '2012-10-17'
Statement:
- Sid: Enable IAM root permissions
Effect: Allow
Principal:
AWS: !Sub "arn:aws:iam::${AWS::AccountId}:root"
Action: 'kms:*'
Resource: '*'
- Sid: Allow RDS service
Effect: Allow
Principal:
Service: rds.amazonaws.com
Action:
- kms:Decrypt
- kms:GenerateDataKey*
- kms:DescribeKey
Resource: '*'
- Sid: Allow application for database access
Effect: Allow
Principal:
AWS: !GetAtt ApplicationExecutionRole.Arn
Action:
- kms:Decrypt
- kms:DescribeKey
Resource: '*'
DatabaseKMSAlias:
Type: AWS::KMS::Alias
Properties:
AliasName: alias/kyo-database-key
TargetKeyId: !Ref DatabaseKMSKey
# Secrets Manager 密鑰儲存
DatabaseSecret:
Type: AWS::SecretsManager::Secret
Properties:
Name: kyo/database/credentials
Description: Database credentials for Kyo SaaS
KmsKeyId: !Ref DatabaseKMSKey
GenerateSecretString:
SecretStringTemplate: '{"username": "kyoadmin"}'
GenerateStringKey: 'password'
PasswordLength: 32
ExcludeCharacters: '"@/\'
RequireEachIncludedType: true
JWTSecret:
Type: AWS::SecretsManager::Secret
Properties:
Name: kyo/jwt/secret
Description: JWT signing secret for Kyo SaaS
KmsKeyId: !Ref ApplicationKMSKey
GenerateSecretString:
SecretStringTemplate: '{"issuer": "kyosaas.com"}'
GenerateStringKey: 'secret'
PasswordLength: 64
ExcludeCharacters: '"@/\'
RedisAuthSecret:
Type: AWS::SecretsManager::Secret
Properties:
Name: kyo/redis/auth
Description: Redis authentication token
KmsKeyId: !Ref ApplicationKMSKey
GenerateSecretString:
SecretStringTemplate: '{}'
GenerateStringKey: 'token'
PasswordLength: 128
ExcludeCharacters: '"@/\'
# API 金鑰管理
ExternalAPIKeys:
Type: AWS::SecretsManager::Secret
Properties:
Name: kyo/external-apis/keys
Description: External API keys and credentials
KmsKeyId: !Ref ApplicationKMSKey
SecretString: !Sub |
{
"mitake_username": "${MitakeUsername}",
"mitake_password": "${MitakePassword}",
"stripe_secret_key": "${StripeSecretKey}",
"sendgrid_api_key": "${SendGridApiKey}"
}
# 密鑰輪換配置
DatabaseSecretRotation:
Type: AWS::SecretsManager::RotationSchedule
Properties:
SecretId: !Ref DatabaseSecret
RotationLambdaArn: !GetAtt DatabaseRotationLambda.Arn
RotationRules:
AutomaticallyAfterDays: 30
# 密鑰輪換 Lambda
DatabaseRotationLambda:
Type: AWS::Lambda::Function
Properties:
FunctionName: kyo-database-secret-rotation
Runtime: python3.9
Handler: lambda_function.lambda_handler
Role: !GetAtt RotationLambdaRole.Arn
Environment:
Variables:
SECRETS_MANAGER_ENDPOINT: !Sub "https://secretsmanager.${AWS::Region}.amazonaws.com"
Code:
ZipFile: |
import boto3
import json
import logging
logger = logging.getLogger()
logger.setLevel(logging.INFO)
def lambda_handler(event, context):
"""密鑰輪換處理函數"""
secret_arn = event['SecretId']
token = event['Token']
step = event['Step']
# 初始化 Secrets Manager 客戶端
client = boto3.client('secretsmanager')
try:
if step == "createSecret":
create_secret(client, secret_arn, token)
elif step == "setSecret":
set_secret(client, secret_arn, token)
elif step == "testSecret":
test_secret(client, secret_arn, token)
elif step == "finishSecret":
finish_secret(client, secret_arn, token)
else:
logger.error(f"Invalid step parameter: {step}")
raise ValueError(f"Invalid step parameter: {step}")
except Exception as e:
logger.error(f"Rotation failed: {str(e)}")
raise
return {"statusCode": 200}
def create_secret(client, secret_arn, token):
"""建立新的密鑰版本"""
# 實作密鑰建立邏輯
pass
def set_secret(client, secret_arn, token):
"""在資料庫中設定新密鑰"""
# 實作密鑰設定邏輯
pass
def test_secret(client, secret_arn, token):
"""測試新密鑰是否可用"""
# 實作密鑰測試邏輯
pass
def finish_secret(client, secret_arn, token):
"""完成密鑰輪換"""
# 實作密鑰完成邏輯
pass
# 輪換 Lambda 角色
RotationLambdaRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service: lambda.amazonaws.com
Action: sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole
Policies:
- PolicyName: RotationPolicy
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- secretsmanager:DescribeSecret
- secretsmanager:GetSecretValue
- secretsmanager:PutSecretValue
- secretsmanager:UpdateSecretVersionStage
Resource: !Ref DatabaseSecret
- Effect: Allow
Action:
- kms:Decrypt
- kms:GenerateDataKey
Resource: !GetAtt DatabaseKMSKey.Arn
# 密鑰使用監控
KMSKeyUsageAlarm:
Type: AWS::CloudWatch::Alarm
Properties:
AlarmName: KMS-UnauthorizedUsage
AlarmDescription: Detect unauthorized KMS key usage
MetricName: NumberOfRequestsExceeded
Namespace: AWS/KMS
Statistic: Sum
Period: 300
EvaluationPeriods: 1
Threshold: 0
ComparisonOperator: GreaterThanThreshold
Dimensions:
- Name: KeyId
Value: !Ref ApplicationKMSKey
AlarmActions:
- !Ref SecurityAlarmTopic
Parameters:
MitakeUsername:
Type: String
NoEcho: true
Description: Mitake SMS service username
MitakePassword:
Type: String
NoEcho: true
Description: Mitake SMS service password
StripeSecretKey:
Type: String
NoEcho: true
Description: Stripe payment processing secret key
SendGridApiKey:
Type: String
NoEcho: true
Description: SendGrid email service API key
# infrastructure/security-monitoring.yml
Resources:
# 安全事件 SNS 主題
SecurityAlarmTopic:
Type: AWS::SNS::Topic
Properties:
TopicName: KyoSecurityAlarms
DisplayName: Kyo SaaS Security Alerts
KmsMasterKeyId: alias/aws/sns
# 安全團隊訂閱
SecurityTeamSubscription:
Type: AWS::SNS::Subscription
Properties:
TopicArn: !Ref SecurityAlarmTopic
Protocol: email
Endpoint: security@kyosaas.com
# Slack 整合訂閱
SlackSubscription:
Type: AWS::SNS::Subscription
Properties:
TopicArn: !Ref SecurityAlarmTopic
Protocol: https
Endpoint: !Sub "https://hooks.slack.com/services/${SlackWebhookPath}"
# CloudTrail 安全監控
SecurityCloudTrail:
Type: AWS::CloudTrail::Trail
Properties:
TrailName: KyoSecurityTrail
S3BucketName: !Ref SecurityLogsBucket
S3KeyPrefix: cloudtrail-logs/
IncludeGlobalServiceEvents: true
IsMultiRegionTrail: true
EnableLogFileValidation: true
KMSKeyId: !GetAtt SecurityKMSKey.Arn
EventSelectors:
- ReadWriteType: All
IncludeManagementEvents: true
DataResources:
- Type: "AWS::S3::Object"
Values:
- "arn:aws:s3:::kyo-*/*"
- Type: "AWS::SecretsManager::Secret"
Values:
- "arn:aws:secretsmanager:*:*:secret:kyo/*"
# 安全日誌 S3 儲存桶
SecurityLogsBucket:
Type: AWS::S3::Bucket
Properties:
BucketName: !Sub "kyo-security-logs-${AWS::AccountId}-${AWS::Region}"
BucketEncryption:
ServerSideEncryptionConfiguration:
- ServerSideEncryptionByDefault:
SSEAlgorithm: aws:kms
KMSMasterKeyID: !GetAtt SecurityKMSKey.Arn
VersioningConfiguration:
Status: Enabled
LifecycleConfiguration:
Rules:
- Id: SecurityLogRetention
Status: Enabled
ExpirationInDays: 2555 # 7 年保存
NoncurrentVersionExpirationInDays: 30
PublicAccessBlockConfiguration:
BlockPublicAcls: true
BlockPublicPolicy: true
IgnorePublicAcls: true
RestrictPublicBuckets: true
# 安全專用 KMS 金鑰
SecurityKMSKey:
Type: AWS::KMS::Key
Properties:
Description: KMS key for security logging and monitoring
KeyPolicy:
Version: '2012-10-17'
Statement:
- Sid: Enable IAM root permissions
Effect: Allow
Principal:
AWS: !Sub "arn:aws:iam::${AWS::AccountId}:root"
Action: 'kms:*'
Resource: '*'
- Sid: Allow CloudTrail service
Effect: Allow
Principal:
Service: cloudtrail.amazonaws.com
Action:
- kms:GenerateDataKey*
- kms:DescribeKey
Resource: '*'
# 可疑活動檢測規則
SuspiciousLoginRule:
Type: AWS::Events::Rule
Properties:
Name: KyoSuspiciousLogin
Description: Detect suspicious login patterns
EventPattern:
source: ["aws.signin"]
detail-type: ["AWS Console Sign In via CloudTrail"]
detail:
responseElements:
ConsoleLogin: ["Success"]
sourceIPAddress:
- anything-but:
prefix: "203.0.113." # 信任的 IP 範圍
Targets:
- Arn: !GetAtt SecurityAnalysisLambda.Arn
Id: "SuspiciousLoginTarget"
# 權限升級檢測
PrivilegeEscalationRule:
Type: AWS::Events::Rule
Properties:
Name: KyoPrivilegeEscalation
Description: Detect potential privilege escalation
EventPattern:
source: ["aws.iam"]
detail-type: ["AWS API Call via CloudTrail"]
detail:
eventName:
- "AttachUserPolicy"
- "AttachRolePolicy"
- "PutUserPolicy"
- "PutRolePolicy"
- "CreateRole"
- "CreateUser"
Targets:
- Arn: !Ref SecurityAlarmTopic
Id: "PrivilegeEscalationAlert"
# 未授權 API 呼叫檢測
UnauthorizedAPIRule:
Type: AWS::Events::Rule
Properties:
Name: KyoUnauthorizedAPI
Description: Detect unauthorized API calls
EventPattern:
source: ["aws.s3", "aws.rds", "aws.kms"]
detail-type: ["AWS API Call via CloudTrail"]
detail:
errorCode: ["AccessDenied", "UnauthorizedOperation"]
Targets:
- Arn: !GetAtt SecurityAnalysisLambda.Arn
Id: "UnauthorizedAPITarget"
# 安全分析 Lambda
SecurityAnalysisLambda:
Type: AWS::Lambda::Function
Properties:
FunctionName: KyoSecurityAnalysis
Runtime: python3.9
Handler: index.lambda_handler
Role: !GetAtt SecurityAnalysisRole.Arn
Environment:
Variables:
SNS_TOPIC_ARN: !Ref SecurityAlarmTopic
SLACK_WEBHOOK: !Sub "https://hooks.slack.com/services/${SlackWebhookPath}"
Code:
ZipFile: |
import boto3
import json
import urllib3
import os
sns = boto3.client('sns')
http = urllib3.PoolManager()
def lambda_handler(event, context):
"""安全事件分析處理器"""
try:
# 解析事件
detail = event['detail']
event_name = detail.get('eventName', '')
source_ip = detail.get('sourceIPAddress', '')
user_identity = detail.get('userIdentity', {})
# 風險評估
risk_score = calculate_risk_score(detail)
# 建立警報訊息
message = {
'event_name': event_name,
'source_ip': source_ip,
'user': user_identity.get('principalId', 'Unknown'),
'risk_score': risk_score,
'timestamp': detail.get('eventTime', ''),
'region': detail.get('awsRegion', ''),
'details': detail
}
# 發送警報
if risk_score >= 7: # 高風險
send_high_priority_alert(message)
elif risk_score >= 4: # 中風險
send_medium_priority_alert(message)
return {'statusCode': 200}
except Exception as e:
print(f"Error processing security event: {str(e)}")
return {'statusCode': 500}
def calculate_risk_score(detail):
"""計算風險分數 (1-10)"""
score = 1
# IP 位址風險
source_ip = detail.get('sourceIPAddress', '')
if not source_ip.startswith('203.0.113.'): # 非信任 IP
score += 2
# 時間風險 (非營業時間)
import datetime
event_time = detail.get('eventTime', '')
if event_time:
hour = datetime.datetime.fromisoformat(event_time.replace('Z', '+00:00')).hour
if hour < 8 or hour > 18: # 非營業時間
score += 2
# 操作風險
high_risk_actions = [
'DeleteDBInstance', 'DeleteBucket', 'DeleteUser',
'AttachUserPolicy', 'CreateUser', 'CreateRole'
]
if detail.get('eventName') in high_risk_actions:
score += 3
# 錯誤碼風險
if detail.get('errorCode') in ['AccessDenied', 'UnauthorizedOperation']:
score += 2
return min(score, 10)
def send_high_priority_alert(message):
"""發送高優先級警報"""
# SNS 通知
sns.publish(
TopicArn=os.environ['SNS_TOPIC_ARN'],
Subject='[CRITICAL] Security Alert - Kyo SaaS',
Message=json.dumps(message, indent=2)
)
# Slack 通知
slack_message = {
'text': f"🚨 *CRITICAL SECURITY ALERT* 🚨",
'attachments': [{
'color': 'danger',
'fields': [
{'title': 'Event', 'value': message['event_name'], 'short': True},
{'title': 'Risk Score', 'value': f"{message['risk_score']}/10", 'short': True},
{'title': 'Source IP', 'value': message['source_ip'], 'short': True},
{'title': 'User', 'value': message['user'], 'short': True}
]
}]
}
http.request(
'POST',
os.environ['SLACK_WEBHOOK'],
body=json.dumps(slack_message),
headers={'Content-Type': 'application/json'}
)
def send_medium_priority_alert(message):
"""發送中優先級警報"""
sns.publish(
TopicArn=os.environ['SNS_TOPIC_ARN'],
Subject='[WARNING] Security Alert - Kyo SaaS',
Message=json.dumps(message, indent=2)
)
# 安全分析角色
SecurityAnalysisRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service: lambda.amazonaws.com
Action: sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
Policies:
- PolicyName: SecurityAnalysisPolicy
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- sns:Publish
Resource: !Ref SecurityAlarmTopic
Parameters:
SlackWebhookPath:
Type: String
NoEcho: true
Description: Slack webhook path for security notifications
今天我們建立了全面的 AWS 雲端安全架構: