【AWS】ほぼ全リソースに使える「削除保護タグ」を簡単に作れるって知ってた？

# TL;DR

`DeletionProtection`タグを付けたリソースが削除できなくなるサービスコントロールポリシー(SCP)を作ります。Actionに書いたリソースの操作が対象になります。

```json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "DeletionProtectionTag",
            "Effect": "Deny",
            "Action": [
                "logs:DeleteLogGroup",
                "lambda:DeleteFunction"
            ],
            "Resource": "*",
            "Condition": {
                "StringEquals": {
                    "aws:ResourceTag/DeletionProtection": "true"
                }
            }
        }
    ]
}
```

# 背景

エンジニアにとって最も恐ろしいのは、本番環境のリソースをうっかり削除してしまうことでしょう。

幸い、EC2インスタンスやRDSクラスターなど一部のリソースには「削除保護(終了保護)」の機能があります。

![rds.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/502255/2ab90c3e-970c-65a9-7a0a-3dc84972597d.png)

また、マネジメントコンソールで削除時にリソース名を入力させる画面が出る場合もあります。

![s3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/502255/9f4f5d00-49fc-24d6-c813-1b1907c1552d.png)

問題は、全てのリソースがそうではないということです。

例えばCloudWatchロググループやLambda関数は比較的簡単に削除できてしまいます。

ほぼ全てのリソースにはタグが付けられるので、「削除保護タグ」みたいなものがあればなぁ...

# 作ってみた

そこで自作してみました。

使うのは [AWS Organizations](https://docs.aws.amazon.com/ja_jp/organizations/latest/userguide/orgs_introduction.html) の [サービスコントロールポリシー(SCP)](https://docs.aws.amazon.com/ja_jp/organizations/latest/userguide/orgs_manage_policies_scps.html) です。

SCPを使うことで、Org内のAWSアカウントに対しアクセス許可を設定することができます。ポリシーの書き方はIAM等と同じなので、学習コストも低めです。

「削除保護タグ」は次のように実装できます。

```json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "DeletionProtectionTag",
            "Effect": "Deny",
            "Action": [
                "logs:DeleteLogGroup",
                "lambda:DeleteFunction"
            ],
            "Resource": "*",
            "Condition": {
                "StringEquals": {
                    "aws:ResourceTag/DeletionProtection": "true"
                }
            }
        }
    ]
}
```

上記は`DeletionProtection`タグがついたリソースについて、記載したActionをDenyするポリシーです。**記載していないActionは拒否されません。** 今回は例として「CloudWatchロググループの削除」と「Lambda関数の削除」を拒否しています。

また、リソースから`DeletionProtection`タグを外すことは拒否していません。今回はあくまで「うっかり削除」の防止が目的であり、本当にリソースを消したいときに面倒くさいからです。

# CloudFormationテンプレート

本記事ではOrganizationやSCPの導入方法は割愛します。CloudFormationで作る場合は下記のようになります。

```YAML
Resources:
  SCPDeletionProtectionTag:
    Type: AWS::Organizations::Policy
    Properties:
      Type: SERVICE_CONTROL_POLICY
      Name: DeletionProtectionTag
      TargetIds:
        - # 下記をアタッチするOrganizational Unit
      Content:
        Version: 2012-10-17
        Statement:
          - Sid: DeletionProtectionTag
            Effect: Deny
            Action:
              - logs:DeleteLogGroup
              - lambda:DeleteFunction
              # ここにActionを追加する
            Resource: '*'
            Condition:
              StringEquals:
                aws:ResourceTag/DeletionProtection: 'true'
```

# 削除保護タグの対応リソース

おおまかに、[AWS services that work with IAM](https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_aws-services-that-work-with-iam.html) の「Services that work with IAM」の表でABACが✅Yesになっていれば、だいたい対応しています。ABACはattribute-based access controlの略です。

![abac.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/502255/ed12747f-2838-a6f3-bfb3-26a76f87d8f3.png)

より正確には、[Actions, resources, and condition keys for AWS services](https://docs.aws.amazon.com/service-authorization/latest/reference/reference_policies_actions-resources-contextkeys.html) の子ページを確認し、Resource typesの表に`aws:ResourceTag/${TagKey}`があれば対応しています。

![cw.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/502255/b5e7190f-d5c5-81cc-4ffb-ffb71ec1787f.png)

![dynamodb.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/502255/3c30a05d-0e9b-035d-5853-88b62af52c68.png)

また、もし対応していない場合は、ポリシーを作る画面で「このサービスにこのCondition Keyは使えないよ」と警告が出ます。

![policy.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/502255/9740f560-efe2-65b1-83d9-da6db581fa43.png)

# 削除保護の挙動

作ったSCPの挙動を確認してみます。

AWS CLIの場合、エラーメッセージの末尾に`service control policy`があり、分かりやすかったです。

```text
$ aws logs delete-log-group --log-group-name example-log-group

An error occurred (AccessDeniedException) when calling the DeleteLogGroup operation: User: arn:aws:iam::XXXXXXXXXXXX:user/tippy is not authorized to perform: logs:DeleteLogGroup on resource: arn:aws:logs:ap-northeast-1:XXXXXXXXXXXX:log-group:example-log-group:log-stream: with an explicit deny in a service control policy
```

一方マネジメントコンソールの場合、リソースにもよると思いますが、CloudWatchロググループでは特にエラーメッセージが出ませんでした。

![cw_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/502255/29c2aef3-c13a-3e6c-bdcf-527ba178c3fc.png)

# 実際に使ってみて

この仕組みを実際に弊社の全アカウントに導入しました。小並感ですが、感想は次のとおりです。

良かった点

- 導入がとても簡単
- 複数のAWSアカウントに対して、全ユーザーに対して、一括で設定できる
- マネジメントコンソールだけでなく、CLIやSDKによる操作にも有効
  - SSOでAdminで入ったときにも有効

だめな点

- 一部リソースには対応していない
- Actionを1つ1つ追加するのが大変
- エラーメッセージが出ないことがある

制約は多いですが、ガードレールの1つとして十分使えると思います。

皆様の参考になれば幸いです。