AWS CLIでCloudFrontのレスポンスヘッダーポリシー更新時にハマったこと

# やりたかったこと

- CloudFrontに設定しているレスポンスヘッダーの値を変更する
    - SPAコンテンツをデプロイ時に更新チェック用にと・・・

## 試したこと（NGケース）

1. AWS CLIで現在の設定値のJSONを取得
1. 変更したいレスポンスヘッダーの値を更新
1. JSONで更新に必要な部分の抜粋（ResponseHeadersPolicyConfigの中身だけ使うので）
1. ETAG取得
1. AWS CLIで更新

### 用意したコマンド

```bash
# 現在の設定を取得
aws cloudfront get-response-headers-policy --id $TATGET_POLICY_ID > config.json

# 書き替え
jq '(.ResponseHeadersPolicy.ResponseHeadersPolicyConfig.CustomHeadersConfig.Items[] | select(.Header == "x-xxxxxxx-id").Value) |= "★変更後の値★"' config.json > update_config_1.json

# 比較
diff -w config.json update_config_1.json

# 更新ファイル準備
jq '.ResponseHeadersPolicy.ResponseHeadersPolicyConfig' update_config_1.json > update_config_2.json

# 更新
ETAG=$(jq -r '.ETag' response_config.json)
aws cloudfront update-response-headers-policy --id $TATGET_POLICY_ID --if-match $ETAG --response-headers-policy-config file://update_response_config_excerpt.json
```

### コマンドの結果

マネジメントコンソールで指定していないセキュリティヘッダーも指定が必要とエラーが発生！

```bash
Parameter validation failed:
Missing required parameter in ResponseHeadersPolicyConfig.SecurityHeadersConfig.XSSProtection: "Override"
Missing required parameter in ResponseHeadersPolicyConfig.SecurityHeadersConfig.XSSProtection: "Protection"
Missing required parameter in ResponseHeadersPolicyConfig.SecurityHeadersConfig.FrameOptions: "Override"
Missing required parameter in ResponseHeadersPolicyConfig.SecurityHeadersConfig.FrameOptions: "FrameOption"
Missing required parameter in ResponseHeadersPolicyConfig.SecurityHeadersConfig.ReferrerPolicy: "Override"
Missing required parameter in ResponseHeadersPolicyConfig.SecurityHeadersConfig.ReferrerPolicy: "ReferrerPolicy"
Missing required parameter in ResponseHeadersPolicyConfig.SecurityHeadersConfig.ContentSecurityPolicy: "Override"
Missing required parameter in ResponseHeadersPolicyConfig.SecurityHeadersConfig.ContentSecurityPolicy: "ContentSecurityPolicy"
Missing required parameter in ResponseHeadersPolicyConfig.SecurityHeadersConfig.ContentTypeOptions: "Override"
Missing required parameter in ResponseHeadersPolicyConfig.SecurityHeadersConfig.StrictTransportSecurity: "Override"
Missing required parameter in ResponseHeadersPolicyConfig.SecurityHeadersConfig.StrictTransportSecurity: "AccessControlMaxAgeSec"
```

### エラーの原因と対策

今回は、以下のような更新するJSONを用意していましたが、SecurityHeadersConfigの中の各項目（`←ここ`の箇所）は空なら、項目を削除しなければいけないみたいです。

```json
{
  "Comment": "xxxxxx",
  "Name": "sample-custom-header",
  "SecurityHeadersConfig": {
    "XSSProtection": {},　←ここ
    "FrameOptions": {},　←ここ
    "ReferrerPolicy": {},　←ここ
    "ContentSecurityPolicy": {},　←ここ
    "ContentTypeOptions": {}　←ここ
  },
  "CustomHeadersConfig": {
    "Quantity": 1,
    "Items": [
      {
        "Header": "x-xxxxxx-id",
        "Value": "X123456Y",
        "Override": false
      }
    ]
  }
}
```

つまり、用意しないとダメなのは、こんな感じです。（SecurityHeadersConfigは非必須なので消して良いかも）

```json
{
  "Comment": "xxxxxx",
  "Name": "sample-custom-header",
  "SecurityHeadersConfig": {
  },
  "CustomHeadersConfig": {
    "Quantity": 1,
    "Items": [
      {
        "Header": "x-xxxxxx-id",
        "Value": "X123456Y",
        "Override": false
      }
    ]
  }
}
```

# 試したこと（OKケース）

1. AWS CLIで現在の設定値のJSONを取得
1. 変更したいレスポンスヘッダーの値を更新
1. JSONで更新に必要な部分の抜粋（ResponseHeadersPolicyConfigの中身だけ使うので）
1. セキュリティヘッダーの中身が空の場合は項目を削除
1. ETAG取得
1. AWS CLIで更新

### 修正したコマンド

```bash
# 現在の設定を取得
aws cloudfront get-response-headers-policy --id $TATGET_POLICY_ID > response_config.json

# 書き替え
jq '(.ResponseHeadersPolicy.ResponseHeadersPolicyConfig.CustomHeadersConfig.Items[] | select(.Header == "x-contents-id").Value) |= "X123456X"' response_config.json > update_response_config_1.json

# 比較
diff -w response_config.json update_response_config_1.json

# 更新ファイル準備
jq '.ResponseHeadersPolicy.ResponseHeadersPolicyConfig' update_response_config_1.json > update_response_config_2.json

# セキュリティ設定で未設定項目は削除
jq '(.SecurityHeadersConfig.XSSProtection | length == 0) as $xss | 
    (.SecurityHeadersConfig.FrameOptions | length == 0) as $frame |
    (.SecurityHeadersConfig.ReferrerPolicy | length == 0) as $referrer |
    (.SecurityHeadersConfig.ContentSecurityPolicy | length == 0) as $csp |
    (.SecurityHeadersConfig.ContentTypeOptions | length == 0) as $content |
    (.SecurityHeadersConfig.StrictTransportSecurity | length == 0) as $sts |
    del(
        if $xss then .SecurityHeadersConfig.XSSProtection else empty end,
        if $frame then .SecurityHeadersConfig.FrameOptions else empty end,
        if $referrer then .SecurityHeadersConfig.ReferrerPolicy else empty end,
        if $csp then .SecurityHeadersConfig.ContentSecurityPolicy else empty end,
        if $content then .SecurityHeadersConfig.ContentTypeOptions else empty end,
        if $sts then .SecurityHeadersConfig.StrictTransportSecurity else empty end
    )' update_response_config_2.json > update_response_config_3.json

# 比較
diff -w update_response_config_2.json update_response_config_3.json

# 更新
ETAG=$(jq -r '.ETag' response_config.json)
aws cloudfront update-response-headers-policy --id $TATGET_POLICY_ID --if-match $ETAG --response-headers-policy-config file://update_response_config_3.json
```