W3 CloudFormation 2021 振り返り

# 概要

弊社が展開している物流クラウドサービス [W3](https://w3mimosa.biz) では、[AWS CloudFormation](https://aws.amazon.com/jp/cloudformation/) (以下 CFn) を使って環境構築を自動化しています。
この記事では、そんなW3のCFnテンプレートで2021年に実施したアップデートをおさらいします。

## W3のシステム構成

W3は2013年に誕生し、2015年からは現在のクラウド型サービスとして多くのお客様に使っていただいています。
システム構成はEC2, RDSを中心としたレガシーなアーキテクチャーとなっており、SaaSモデルのほか、お客様専有環境でのパッケージ形態での提供も行っています。

# 今年の W3 CFn アップデート

最近はセキュリティ関連のご要望をいただくことが増え、2021年は特に暗号化関連での対応を中心に行いました。
これまで追加で手動対応していた箇所を自動化したものが多くなっていますが、主な変更箇所を以下にまとめました。
なお、テンプレートはJSON形式となっており、いずれも変更箇所を抜粋したものになります。

## 各種暗号化

使用するAWSサービスの暗号化設定をテンプレートに反映しました。
これまでは構築後に担当者が個別に設定し直していました。

* EC2

`Ebs`の`Encrypted`を有効化し、KMSキーのARNを指定しました。
※KMSキーは事前に作成しておきます。

```json
"Ec2": {
  "Type": "AWS::EC2::Instance",
  "Properties": {
    "BlockDeviceMappings": [
      {
        "Ebs": {
          "Encrypted": "true",
          "KmsKeyId": { "Fn::Sub": "arn:aws:kms:${AWS::Region}:${AWS::AccountId}:key/***" }
        }
      }
    ]
  }
}
```

* SQS

`KmsMasterKeyId`にKMSキーのARNを追加しました。
  
```json
"Sqs": {
  "Type": "AWS::SQS::Queue",
  "Properties": {
    "KmsMasterKeyId": { "Fn::Sub": "arn:aws:kms:${AWS::Region}:${AWS::AccountId}:key/***" }
}
```

* S3

`ServerSideEncryptionConfiguration`で`BucketKeyEnabled`を有効化、`ServerSideEncryptionByDefault`はではKMSによる暗号化を指定しました。

```json
"S3bucket": {
  "Type": "AWS::S3::Bucket",
  "Properties": {
    "BucketEncryption": {
      "ServerSideEncryptionConfiguration": [
        {
          "BucketKeyEnabled": true,
          "ServerSideEncryptionByDefault": {
            "SSEAlgorithm": "aws:kms",
            "KMSMasterKeyID": { "Fn::Sub": "arn:aws:kms:${AWS::Region}:${AWS::AccountId}:key/***" }
          }
        }
      ]
    }
  }
}
```

* RDS (証明書設定)

RDSはSSL証明書を有効化しました。
アプリケーション側でも証明書(ap-northeast-1-bundle.pem)を読み込むように設定しています。

```json
"Rds": {
  "Type": "AWS::RDS::DBInstance",
  "Properties": {
    "CACertificateIdentifier": "rds-ca-2019"
  }
}
```

* RDS (SecretsManager)

これまではRDSのパスワードを個別に指定していましたが、SecretsManagerで自動生成するようにしました。
アプリケーション側にはEC2のUserDataを使って反映しています。

```json
"RdsSec": {
  "Type": "AWS::SecretsManager::Secret",
  "Properties": {
    "GenerateSecretString": {
      "SecretStringTemplate": "{\"username\": \"***\"}",
      "GenerateStringKey": "password",
      "PasswordLength": 32,
      "ExcludeCharacters": "\"$'@`|/\\"
    }
  }
},
"RdsCluster": {
  "Type": "AWS::RDS::DBCluster",
  "MasterUsername": { "Fn::Sub": "{{resolve:secretsmanager:${RdsSec}::username}}"
 },
  "MasterUserPassword": { "Fn::Sub": "{{resolve:secretsmanager:${RdsSec}:SecretSt
ring:password}}" }
},
"RdsSecAtt": {
  "Type": "AWS::SecretsManager::SecretTargetAttachment",
  "Properties": {
    "SecretId": { "Ref": "RdsSec" },
    "TargetId": { "Ref": "RdsCluster" },
    "TargetType": "AWS::RDS::DBCluster"
  }
},
"Ec2": {
  "Type": "AWS::EC2::Instance",
  "Properties": {
    "UserData": { "Fn::Base64": { "Fn::Sub": [
      "Content-Type: multipart/mixed; boundary=\"//\"\nMIME-Version: 1.0\n\n--//\nConte
nt-Type: text/cloud-config; charset=\"us-ascii\"\nMIME-Version: 1.0\nContent-Transfer-Encodi
ng: 7bit\nContent-Disposition: attachment; filename=\"cloud-config.txt\"\n\n#cloud-config\nc
loud_final_modules:\n - [scripts-user, always]\noutput: { all : '| tee -a /var/log/cloud-ini
t-output.log' }\n\n--//\nContent-Type: text/x-shellscript; charset=\"us-ascii\"\nMIME-Versio
n: 1.0\nContent-Transfer-Encoding: 7bit\nContent-Disposition: attachment; filename=\"userdat
a.txt\"\n\n#!/bin/bash\nsecret_id=\"${secret_id}\" region=\"${AWS::Region}\" /opt/cloud-init.
sh\n--/--",
        { "secret_id": { "Ref": "rdspwd" } }
    ]}}
  }
}
```

## 冗長化

* ElastiCache(Redis)

`TransitEncryptionEnabled`で通信の暗号化を有効化し、アプリケーション側も`tls`プロトコル接続に変更しました。
また、これまではシングル構成の定義のみだったため、本番環境の場合にはAWS管理コンソールから冗長化構成で追加していましたが、これをCFnのパラメータで指定できるようにし、`Type`を`AWS::ElastiCache::CacheCluster`から`AWS::ElastiCache::ReplicationGroup`に変更、シャードを有効化して冗長化構成をしやすくしました。

```json:変更前(抜粋)
"Redis": {
  "Type": "AWS::ElastiCache::CacheCluster",
  "Properties": {
    "NumCacheNodes": "1"
  }
},
```

```json:変更後(抜粋)
"Redis": {
  "Condition": "noCacheMultiAz",
  "Type": "AWS::ElastiCache::ReplicationGroup",
  "Properties": {
    "AtRestEncryptionEnabled": "true",
    "MultiAZEnabled": "false",
    "NodeGroupConfiguration": [
      {
        "PrimaryAvailabilityZone": "ap-northeast-1a",
        "ReplicaCount": "0"
      }
    ],
    "NumNodeGroups": "1",
    "TransitEncryptionEnabled": "true"
  }
},
"RedisRg": {
  "Condition": "enCacheMultiAz",
  "Type": "AWS::ElastiCache::ReplicationGroup",
  "Properties": {
    "AtRestEncryptionEnabled": "true",
    "MultiAZEnabled": "true",
    "NodeGroupConfiguration": [
      {
        "PrimaryAvailabilityZone": "ap-northeast-1a",
        "ReplicaAvailabilityZones": [ "ap-northeast-1c" ],
        "ReplicaCount": "1"
      }
    ],
    "NumNodeGroups": "1",
    "TransitEncryptionEnabled": "true"
  }
}
```

# 来年に向けて

最初にW3が誕生してから早くも8年が経過しました。
このW3 CFnテンプレートもアップデートを重ねてきましたが、容量制限(51200バイト)ぎりぎりで、そもそもJSONのメンテが疲れるので、来年には[AWS Cloud Development Kit](https://aws.amazon.com/jp/cdk/)で書き直したいと考えています。
できれば実行基盤もモダンなものにしたい...

---

### 株式会社ダイアログについて

『物流業界をテクノロジーの力でHAPPYに』
弊社は「物流xITx経営」をキーワードに、W3、Tariffeeといった自社サービスを中心に、他サービス連携やデータプラットフォーム化を通してよりHAPPYな物流を目指して活動しています。
ただいま一緒に働くエンジニアを募集中です。
物流、ロジテック、サービス連携、データプラットフォーム等に興味のある方、カジュアル面談で一度お話しませんか。
詳しい採用情報は [Wantedly](https://www.dialog-inc.com/top/recruit_top/) にて。