AWS Configを駆使して使っていないSecurity Groupをお掃除する

この記事は[Speee Advent Calendar 2019](https://qiita.com/advent-calendar/2019/speee) 1日目の記事です。

# あらすじ

長い間AWSアカウントを運用していると、様々な理由で不要なリソースというものが生まれます。

- 長く運用されているアカウントで昔のリソースがIaCされていなくて使われなくなったリソースが残り続けていたり
- dev兼用だったりして不意に誰かの検証リソースが残っていたり
- などなど理由は様々です

使ってないリソースは消すに限ります。
本記事では、使ってないSecurityGroupをAWS Configを使って洗い出す方法をご紹介します。

# AWS Config

AWS Configには様々な機能がありますが、機能の一つにAWSリソース間の依存関係をSQLライクなクエリで抽出する機能があります。
https://docs.aws.amazon.com/ja_jp/config/latest/developerguide/querying-AWS-resources.html

使ったことがない方向けに雰囲気を共有するためにAWS Configのサンプルクエリを一つ紹介します。
下記は使っていないEBSボリュームを抽出するクエリです。

```sql
SELECT
    resourceId,
    resourceType,
    configuration.volumeType,
    configuration.size,
    resourceCreationTime,
    tags,
    configuration.encrypted,
    configuration.availabilityZone,
    configuration.state.value
WHERE
    resourceType = 'AWS::EC2::Volume'
    AND configuration.state.value <> 'in-use'
```

AWS ConfigのクエリはAWS Config用のデータのみを参照するのでFROM句は使いません。
基本的にSELECTとWHEREで欲しい情報の列と行を絞る感じになります。
JOINはサポートしていないけどGROUP BYはあります。HAVING BYはないです。

# 実際に使っていないSecurityGroupを抽出する方法

では実際に使っていないSecurityGroupを抽出していきます。

紹介するクエリはコンソールかCLIかSDKかなんらか方法で実行して下さい。
得られた結果を加工したりするので得意な言語のSDKがおすすめです。

## Step1:他のAWSリソースから参照されているSecurityGroupを出す

まずは以下のクエリを実行します。

```sql
SELECT
    relationships.resourceId
WHERE
    resourceType != 'AWS::EC2::VPC' AND
    relationships.resourceType = 'AWS::EC2::SecurityGroup'
GROUP BY
    relationships.resourceId
```

このクエリを実行するとセキュリティグループを参照しているリソース（例えばELB）が参照しているリソース（例えばELBが参照しているVPC,SecurityGroup,Subnet）のID一覧を得ることができます。
ややこしいですね。

クエリを少し解説すると、WHERE句の`relationships.resourceType = 'AWS::EC2::SecurityGroup'`はSecurityGroupを参照しているAWSリソースを抽出しています。relationshipsというカラムにそのリソースが参照している依存関係の情報があります。
VPCを除いているのは、全てのSecurityGroupはVPCに紐付けられるのでVPCにしか参照されていない（つまり他から使われていない）SecurityGroupもクエリに含められてしまうからです。

GROUP BYは参照先のリソースIDで集約しています。

このクエリを実行するとSecurityGroup以外にもVPCやSubnetのIDも含まれてしまうので、得られた結果のうちを `sg-` から始まるidだけをスクリプトか何かで抽出します。
本当はHavingが使えればそこで絞れて良いのですが...

最終的にここで抽出されたSecurityGroupのIdは、何かのawsリソースから参照されているSecurityGroupの一覧になります。

## Step2:他のAWSリソースから参照されていないSecurityGroupを出す

`全SecurityGroup - 他のAWSリソースから参照されているSecurityGroup = 他のAWSリソースから参照されていないSecurityGroup`です。

以下のクエリを実行します。

```sql
SELECT
    resourceId,
    resourceName
WHERE
    resourceType = 'AWS::EC2::SecurityGroup' AND
    resourceId NOT IN (
        'sg-xxxxxx',
        'sg-yyyyyy',
        'sg-zzzzzz',
        ...
    )
```

IN句に入れるIDはstep1で抽出したIDになります。サブクエリとか使えると良いんですがね..

これで晴れて他のAWSリソースから参照されていないSecurityGroupの一覧が出せました。

## Step3:SecurityGroupのルールで参照されているSecurityGroupを除く

もう一つ考慮するケースがあって他のSecurityGroupのsrcやdestに指定されているSecurityGroupは消すことができません。

なのでSecurityGroupのルールとして使われているSecurityGroupを除きます。

Step2:で得られたSecurityGroupIdに対して何らスクリプトでループを回して各SecurityGroupIdに対して以下のクエリを実行します。
（この辺もうちょいいい感じでクエリ書けるかもしれません）

```sql
SELECT
    *
WHERE
    resourceType = 'AWS::EC2::SecurityGroup' AND (
    	configuration.ipPermissions.userIdGroupPairs.groupId = 'sg-xxxxxx'
      OR
      configuration.ipPermissionsEgress.userIdGroupPairs.groupId = 'sg-xxxxxx'
 )
```

SELECTの結果が0ならどのSecurityGroupにも参照されていません。消せる候補です。

# 終わりに

以上のステップで使っていないSecurityGroupを洗い出すことができました。あとは一覧をチェックして消すのみです。一応消す前は人の目を挟んだ方がいいと思います。

不要なゴミは消して快適なAWS運用を実現しましょう！！