iT邦幫忙

2025 iThome 鐵人賽

DAY 19
0
Build on AWS

30 天將工作室 SaaS 產品部署起來系列 第 19

Day 19: 30天部署SaaS產品到AWS-雲端安全架構設計

  • 分享至 

  • xImage
  •  

前情提要

在 Day 18 我們完成了雲端效能優化,今天我們要建立 AWS 雲端安全防護體系。對於健身房 SaaS 系統的雲端部署,安全性是核心要求 - 從 IAM 精細權限控制到網路隔離,從資料加密到 DDoS 防護,我們將實作全方位的 AWS 安全架構,確保多租戶環境下的資料安全與合規性。

IAM 精細權限控制策略

角色與政策設計

# 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

Service Control Policies (SCPs)

# 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"
                ]
              }
            }
          }
        ]
      }

VPC 網路安全設計

分層網路架構

# 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 安全配置

# 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

Security Groups 精細配置

分層安全群組設計

# 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

WAF 與 DDoS 防護

AWS WAF 配置

# 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: "*"

Secrets Manager 與 KMS 加密

密鑰管理策略

# 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 雲端安全架構:

  1. IAM 精細控制:多層角色設計、最小權限原則、MFA 強制政策
  2. 網路安全隔離:分層 VPC 架構、NACL 與 Security Groups 雙重防護
  3. WAF 與 DDoS 防護:多層 WAF 規則、地理封鎖、進階 DDoS 保護
  4. 加密與密鑰管理:KMS 金鑰分層、Secrets Manager 自動輪換
  5. 安全監控與回應:即時威脅檢測、自動警報系統、事件回應機制

參考資源


上一篇
Day 18: 30天部署SaaS產品到AWS-雲端效能優化策略
系列文
30 天將工作室 SaaS 產品部署起來19
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言