CFnの別のスタックからの変更をブロックしたい

## やりたいこと

1. CloudFormationでStackを１つ作る。
2. 別のStackを作る。このStackの中で[1]で作成したResourceを変更する。
3. [1]で作ったスタックをアップデートする。とどうなる？
4. [2]の変更で[1]で作成したリソースが変更できないようにする。

**※[4]は、今の所、できていない。もう少し模索する（ToDo）**

## 1. CloudFormationでStackを１つ作る

```yaml
AWSTemplateFormatVersion: "2010-09-09"
Description: 
  VPC, Subnet

Metadata: 
  "AWS::CloudFormation::Interface": 
    ParameterGroups: 
      - Label: 
          default: "Project Name Prefix"
        Parameters: 
          - ClusterPrefix
      - Label: 
          default: "Network Configuration"
        Parameters: 
          - VPCCIDR
          - PublicSubnetCIDR
    ParameterLabels: 
      VPCCIDR: 
        default: "VPC CIDR"
      PublicSubnetCIDR: 
        default: "PublicSubnet CIDR"

# ------------------------------------------------------------#
# Input Parameters
# ------------------------------------------------------------# 
Parameters:
  ClusterPrefix:
    Type: String
    Default: "foobar"

  VPCCIDR:
    Type: String
    Default: "10.0.0.0/16"
  PublicSubnetCIDR:
    Type: String
    Default: "10.0.10.0/24"

Resources: 
# ------------------------------------------------------------#
#  VPC
# ------------------------------------------------------------#
# VPC
  VPC: 
    Type: "AWS::EC2::VPC"
    Properties: 
      CidrBlock: !Ref VPCCIDR
      EnableDnsSupport: "true"
      EnableDnsHostnames: "true"
      InstanceTenancy: default
      Tags: 
        - Key: Name
          Value: !Sub "${ClusterPrefix}-vpc"

# InternetGateway
  InternetGateway: 
    Type: "AWS::EC2::InternetGateway"
    Properties: 
      Tags: 
        - Key: Name
          Value: !Sub "${ClusterPrefix}-igw"

# IGW Attach
  InternetGatewayAttachment: 
    Type: "AWS::EC2::VPCGatewayAttachment"
    Properties: 
      InternetGatewayId: !Ref InternetGateway
      VpcId: !Ref VPC

# DHCPOptions
  DHCPOptions:
    Type: AWS::EC2::DHCPOptions
    Properties:
      DomainName: ap-northeast-1.compute.internal
      DomainNameServers:
        - AmazonProvidedDNS
      Tags:
        - Key: Name
          Value: !Sub "${ClusterPrefix}-dhcp-options"
  DHCPOptionsAssociation:
    Type: AWS::EC2::VPCDHCPOptionsAssociation
    Properties:
      VpcId: !Ref VPC
      DhcpOptionsId: !Ref DHCPOptions

# ------------------------------------------------------------#
#  Subnet
# ------------------------------------------------------------#          
# PublicSubnet
  PublicSubnet: 
    Type: "AWS::EC2::Subnet"
    Properties: 
      AvailabilityZone: "ap-northeast-1a"
      CidrBlock: !Ref PublicSubnetCIDR
      VpcId: !Ref VPC 
      Tags: 
        - Key: Name
          Value: !Sub "${ClusterPrefix}-public-subnet-a"

# ------------------------------------------------------------#
# RouteTable
# ------------------------------------------------------------#          
# PublicRouteTable
  PublicRouteTable: 
    Type: "AWS::EC2::RouteTable"
    Properties: 
      VpcId: !Ref VPC 
      Tags: 
        - Key: Name
          Value: !Sub "${ClusterPrefix}-public-route-a"

# ------------------------------------------------------------#
# Route
# ------------------------------------------------------------# 
# PublicRouteA
  PublicRouteA: 
    Type: "AWS::EC2::Route"
    Properties: 
      RouteTableId: !Ref PublicRouteTable
      DestinationCidrBlock: "0.0.0.0/0"
      GatewayId: !Ref InternetGateway 

# ------------------------------------------------------------#
# RouteTable Associate
# ------------------------------------------------------------# 
# PublicSubnetRouteTableAssociation
  PublicSubnetRouteTableAssociation: 
    Type: "AWS::EC2::SubnetRouteTableAssociation"
    Properties: 
      SubnetId: !Ref PublicSubnet
      RouteTableId: !Ref PublicRouteTable

# Outputs
Outputs:
  VPC:
    Value: !Ref VPC
  PublicSubnet:
    Value: !Ref PublicSubnet
  InternetGateway:
    Value: !Ref InternetGateway
```

OutputsでRouteTableとIGWとVPCのIDを出力しているので、コピーしておく。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/325101/8450e11a-a18a-3f2c-5ed5-3c263304b9c1.png)

## 別のStackを作る。このStackの中で[1]で作成したResourceを変更する。

[1]で作ったRouteTableをすげ替えるようなRouteの設定とは違う設定を突っ込む。

```yaml
AWSTemplateFormatVersion: "2010-09-09"
# ------------------------------------------------------------#
# Input Parameters
# ------------------------------------------------------------# 
Parameters:
  ClusterPrefix:
    Type: String
    Default: "foobar"
  VPC:
    Type: String
    Default: ""
  InternetGateway:
    Type: String
    Default: ""
  PublicSubnet:
    Type: String
    Default: ""

Resources: 
# ------------------------------------------------------------#
# RouteTable
# ------------------------------------------------------------#          
# PublicRouteTableMod
  PublicRouteTableMod: 
    Type: "AWS::EC2::RouteTable"
    Properties: 
      VpcId: !Ref VPC 
      Tags: 
        - Key: Name
          Value: !Sub "${ClusterPrefix}-public-route-a-mod"

# ------------------------------------------------------------#
# Route
# ------------------------------------------------------------# 
# PublicRouteMod
  PublicRouteMod: 
    Type: "AWS::EC2::Route"
    Properties: 
      RouteTableId: !Ref PublicRouteTableMod
      DestinationCidrBlock: "192.168.0.0/24"
      GatewayId: !Ref InternetGateway 

# ------------------------------------------------------------#
# RouteTable Associate
# ------------------------------------------------------------# 
# PublicSubnetARouteTableAssociationMod
  PublicSubnetARouteTableAssociationMod: 
    Type: "AWS::EC2::SubnetRouteTableAssociation"
    Properties: 
      SubnetId: !Ref PublicSubnet
      RouteTableId: !Ref PublicRouteTableMod
```

RouteTableを確認するとあとから作ったやつが、Subnetに紐付けされており、

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/325101/f8a18657-f198-e834-a79b-645312c3ff7e.png)

紐付けされているRouteもあとから作ったやつになっている。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/325101/3762cb3a-3920-6d88-5316-67c2aab2d724.png)

## 3. [1]で作ったスタックをアップデートする。とどうなる？

先ずは、何も考えずに[1]のStackを更新しちゃう。
ここではPrivateSubnetを作る感じ。

```diff
36a37,39
>   PrivateSubnetCIDR:
>     Type: String
>     Default: "10.0.20.0/24"
97a101,110
> # PrivateSubnet
>   PrivateSubnet: 
>     Type: "AWS::EC2::Subnet"
>     Properties: 
>       AvailabilityZone: "ap-northeast-1a"
>       CidrBlock: !Ref PrivateSubnetCIDR
>       VpcId: !Ref VPC 
>       Tags: 
>         - Key: Name
>           Value: !Sub "${ClusterPrefix}-private-subnet-a"
```

StackのEventを見ると以下の通り。ん～、YAMLの差分更新しかしない感じなのね。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/325101/00f15e26-da14-c277-09d0-954b5cb61e29.png)

では、今度はPublicSubnetのRouteTableをアップデートしてみる。

```diff
132c132
<       DestinationCidrBlock: "0.0.0.0/0"
---
>       DestinationCidrBlock: "172.16.0.0/12"
```

この更新の場合の変更セットは以下のようになる。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/325101/e1003cf6-853f-2753-60f4-49f934e46287.png)

むむむ、RouteTableが削除＆再作成されている。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/325101/84a0305a-a3d7-cab4-4621-190be69a3f9a.png)

結果、RouteTableは再作成されて、意図したルーティング設定にはなっているものの、肝心のRouteTableがSubnetにアタッチされていないという、グッチャグチャの構成に...。stack01で作ったRouteTableがSubnetにアタッチされるのかと思いきや、やはりYAMLの差分のみの適用を行う動きをするようで、Associationし直すということはなかった。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/325101/ec3e7b0b-933a-e17e-2174-e357632457da.png)

## 4. [2]の変更で[1]で作成したリソースが変更できないようにする。

[2]の変更で[1]の構成を崩したが、これを阻止したいときに使うのが、**stack policy**らしい。

https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/protect-stack-resources.html

> スタックを作成するとき、すべてのリソースですべての更新アクションが許可されます。デフォルトでは、スタック更新アクセス権限を持つユーザーであれば誰でもスタック内のすべてのリソースを更新できます。更新処理中、リソースの中断や完全な置き換えが必要になった場合は、新しい物理 ID やまったく新しいストレージが使用される結果になります。スタックポリシーを使用すると、スタックの更新中にスタックのリソースが意図せずに更新または削除されるのを防止できます。スタックポリシーは、指定したリソースに対して実行できる更新アクションを定義する JSON ドキュメントです。
> スタックポリシーを設定すると、デフォルトでスタック内のすべてのリソースが保護されます。特定のリソースでの更新を許可するには、スタックポリシーでこれらのリソースに対して Allow ステートメントを明示的に指定します。1 つのスタックに定義できるスタックポリシーは 1 つのみですが、1 つのポリシー内で複数のリソースを保護することができます。スタックポリシーは、スタックを更新しようとするすべての AWS CloudFormation ユーザーに適用されます。異なるスタックポリシーを異なるユーザーに関連付けることはできません。
> スタックポリシーは、スタックの更新時のみ適用されます。AWS Identity and Access Management (IAM) ポリシーのようなアクセスコントロールは提供しません。特定のスタックリソースを誤って更新しないように、スタックポリシーはフェイルセーフメカニズムとしてのみ使用してください。AWS リソースまたはアクションへのアクセスを制御するには、IAM を使用します。

[1]の実施時に、stack policyを設定する。
[2]では、新たにRouteTableを作ってアタッチするようなYAMLなので、これをブロックするポリシーにする。

```json:stackpolicy
{
  "Statement" : [
    {
      "Effect" : "Allow",
      "Action" : "Update:*",
      "Principal": "*",
      "Resource" : "*"
    },
    {
      "Effect" : "Deny",
      "Action" : "Update:*",
      "Principal": "*",
      "Resource" : "LogicalResourceId/PublicSubnetRouteTableAssociation"
    }
  ]
}
```

再度、[2]を実施すると...
失敗させたかったけど、同一スタックの更新にのみ適用できるみたい？なのかな。普通に変更できてしまった。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/325101/d3f5c486-d61e-73a7-b4c3-9f16c59bb5c3.png)