VPCプレフィックスリストとCloudFormationで、セキュリティグループのIP管理をグッと楽にする

AWSのEC2インスタンスやALBをパブリックサブネットに置く場合、セキュリティグループで特定のIPからのみアクセスを許可することがあります。

例えば、社内システムなので事業所ネットワークの出口IPからのみHTTPSアクセス可能にしたいといったケースです。

```
セキュリティグループ設定例
TCP 443 本社ビルネットワークの出口IP/32
TCP 443 大阪事業所ネットワークの出口IP/32
```

## IP管理の問題点
セキュリティグループでの設定は簡単ではありますが、同じようなセキュリティグループが増えてしまうと、IPの追加や変更があった場合に修正が面倒で修正漏れも起きやすくなります。

IP変更であれば、以前のIPは他者が取得する可能性もあるので、**セキュリティリスク** になる可能性があります。
また、IPの説明を詳しく書いていなければ、時間が経つにつれて **謎のIPと化す** というのもよくある話です（経験談）。

```
NGなセキュリティグループの例
TCP 443 新本社ビルネットワークの出口IP/32 ※これはOK
TCP 443 新大阪事業所ネットワークの出口IP/32 ※これもOK
TCP 443 古い出口IP/32 ※削除漏れ
TCP 443 なんか不明なIP/32 ※削除漏れ且つ謎IP化
```

## VPCプレフィックスリストとは

VPCプレフィックスリストとは、**よく使うIPグループを事前に定義しておき、複数のセキュリティグループで使い回すことができる機能**です。2020年6月にリリースされた、比較的新しい機能になります。

https://dev.classmethod.jp/articles/amazon-vpc-prefix-list/

https://docs.aws.amazon.com/ja_jp/vpc/latest/userguide/managed-prefix-lists.html

これがあれば、例えば「社内ネットワークの出口IPリスト」を1つ作っておき、セキュリティグループではプレフィックスリストのIDを指定するだけでよくなります。

さらに、IPの追加や変更も、その共通のプレフィックスリストを修正するだけで完了するので、管理がとても楽になります。
※ちなみに、セキュリティグループ以外にもルートテーブルでも使えますが、この記事では割愛します。

### 設定例

AWSコンソールから、VPC → マネージドプレフィックスリスト → プレフィックスリストを作成 を選択します。CIDRブロックのところに、IPアドレスと説明を入れます。
![prefix-lists-example.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/858685/135bc5ff-d1fc-e045-cb22-ead85a84640b.png)

セキュリティグループでは、カスタムソースとして `pl-` から始まるプレフィックスリストを指定できます。固定されるのはあくまでIPだけなので、ポートはルール毎に自由に設定できます。
![prefix-lists-in-sg.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/858685/ffd1b716-b16f-0d0b-f091-29468919b37d.png)



### プレフィックスリスト設定のポイント

1. IPには**必ず説明を入れる**
説明が空のIPは、謎IP化する確率が高くなります。**プレフィックスリストなら一度入れるだけでいい**ので、面倒くさがらず必ず記載しておきましょう。

2. ポート設定する単位でグループ化する
とりあえず使いそうなIPを全部一つのプレフィックスリストに入れてしまうと、**不要なIPにまでポートを開ける**ことになってしまい本末転倒です。「オフィスの出口IPリスト」、「外部サービスの出口IPリスト」など、使い回ししやすい単位で作成しましょう。


## CloudFormationを使う場合

AWSリソースはCloudFormation（やCDK）で管理している場合も多いと思います。
プレフィックスリストももちろんCloudFormationで定義できます。
以下の例では、将来的なIP変更をコンソールからすることを想定し、パラメータとしてCIDRを入力させています。

```yml
AWSTemplateFormatVersion: 2010-09-09
Parameters:
  MainExitIP:
    Type: String
    Description: "本社ビルネットワークの出口IP"
    Default: x.x.x.x/32
    AllowedPattern: (\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})/(\d{1,2})
  OsakaExitIP:
    Type: String
    Description: "大阪事業所ネットワークの出口IP"
    Default: x.x.x.x/32
    AllowedPattern: (\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})/(\d{1,2})
    ConstraintDescription: "CIDRは x.x.x.x/x の形で指定してください"
Resources:
  OfficeNetworkExitIps:
    Type: AWS::EC2::PrefixList
    Properties:
      AddressFamily: IPv4
      Entries:
        - Cidr: !Ref MainExitIP
          Description: "本社ビルネットワークの出口IP（CIDR）"
        - Cidr: !Ref OsakaExitIP
          Description: "大阪事業所ネットワークの出口IP（CIDR）"
      MaxEntries: 20
      PrefixListName: office-network-exit-ips
      Tags:
        - Key: Name
          Value: office-network-exit-ips
```

## プレフィックスリストを複数作る場合のひと工夫

複数のプレフィックスリストを作る場合には、1つのCloudFormation stackに入れておいて他stackからはImportValueで参照すると、プレフィックスリストのIDを書く必要がないので便利です。

#### プレフィックスリスト用 stack
```yml
AWSTemplateFormatVersion: 2010-09-09
Resources:
  OfficeNetworkExitIps:
    Type: AWS::EC2::PrefixList
    Properties:
      AddressFamily: IPv4
      Entries:
        - Cidr: x.x.x.x/32
          Description: "本社ビルネットワークの出口IP（CIDR）"
        - Cidr: x.x.x.x/32
          Description: "大阪事業所ネットワークの出口IP（CIDR）"
      MaxEntries: 20
      PrefixListName: office-network-exit-ips
      Tags:
        - Key: Name
          Value: office-network-exit-ips
  ExternalServiceExitIps:
    Type: AWS::EC2::PrefixList
    Properties:
      AddressFamily: IPv4
      Entries:
        - Cidr: x.x.x.x/24
          Description: サービスA
        - Cidr: x.x.x.x/32
          Description: サービスB
      MaxEntries: 20
      PrefixListName: external-service-exit-ips
      Tags:
        - Key: Name
          Value: external-service-exit-ips
Outputs:
  ExternalServiceExitIpsOutput:
    Description: ""
    Value: !Ref OfficeNetworkExitIps
    Export:
      Name: !Sub "${AWS::StackName}-OfficeNetworkExitIpsPrefixList"
  ExternalServiceExitIpsOutput:
    Description: ""
    Value: !Ref ExternalServiceExitIps
    Export:
      Name: !Sub "${AWS::StackName}-ExternalServiceExitIpsPrefixList"
```

#### プレフィックスリストを利用するstack

パラメータとしてstack名を指定させています。Security Groupでは、`SourcePrefixListId: !ImportValue` を使ってプレフィックスリストを読み込んでいます（[ImportValueのAWSドキュメント](https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-importvalue.html)）。

```yml
AWSTemplateFormatVersion: 2010-09-09
Parameters:
  PrefixListStackName:
    Type: String
    Default: test-vpc-prefix-list
    Description: VPCプレフィックスリストのstack名
Resources:
  TestVpc:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: 10.100.0.0/24
      EnableDnsHostnames: true
      Tags:
        - Key: Name
          Value: TestVpc
  TestSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: 社内と外部システムからのアクセス許可
      GroupName: test
      SecurityGroupIngress:
        - SourcePrefixListId: !ImportValue
            'Fn::Sub': '${PrefixListStackName}-OfficeNetworkExitIpsPrefixList'
          FromPort: 443
          ToPort: 443
          IpProtocol: tcp
        - SourcePrefixListId: !ImportValue
            'Fn::Sub': '${PrefixListStackName}-ExternalServiceExitIpsPrefixList'
          FromPort: 443
          ToPort: 443
          IpProtocol: tcp
      VpcId: !Ref TestVpc
```



## 最後に

VPCプレフィックスリストとCloudFormationを使えば、セキュリティグループの作成もその後のIP管理もグッと楽になります。

IP管理で悩んでいる方はぜひ使ってみてください。