Skip to content

完全無欠な ReadOnlyAccess を目指して… 3/3

全てのアクションが網羅されているわけではない ReadOnlyAccess を完全無欠なものとすべく計画したシリーズ第三弾の投稿です。 一旦、目処がついたのでこのシリーズとしては完結編とできそうです。

また作成したプログラムは GitHub にも公開しているので、適宜ご利用いただけると幸いです。

前回の記事はこちら

AWS 側のアップデートによる方針の変更

前回までは、網羅的な情報がないことから、AWS 公式ドキュメントからスクレイピングするような形で解析を試みていました。

その後、完全無欠な ReadOnlyAccess を目指す個人的な企みをサボっている間に、AWS 側にアップデートがあり、2024年10月10日に service reference という情報の公開がされるようになりました。

Streamline automation of policy management workflows with service reference information

service reference について

詳細は、上記の What's New AWS の記事や 公式ドキュメント から確認できますが、大元の service reference information にアクセスすることで、サービスプレフィックスとそのサービスプレフィックスの AWS API リストへの URL が返されるようになっています。

そして各サービスプレフィックスごとの URL へアクセスすることで、該当のサービスプレフィックスで用意されている API アクション名が JSON 形式で入手できるようになり、今後メンテナンスがされていくものと考えらえます。

service reference の仕組みを利用して完全無欠な ReadOnlyAccess を目指してみる

せっかく網羅的な資料が公開されるようになったので、service reference を活用して、AWS 管理ポリシー ReadOnlyAccess で網羅されていない AWS サービスや AWS API アクションを網羅してみるように方針変更をしました。

当初、単純に大元の URL から各サービス向けの URL を取得してループを回しながら全ての読み取り系アクションを一覧にしてみる方針でスクリプトを作ってみました。
その際、GetListDescribe で始まる AWS API アクションに限定したのですが、抽出結果から組み立てた IAM ポリシーは 11,000 バイトを超える分量になってしまい、単一のカスタマーマネージドポリシーとして成立しない状況でした。

そのため、おとなしくデフォルトの ReadOnlyAccess と突合し、すでに ReadOnlyAccess 側で ${service-prefix}:{action-verb}* のような形式で記載されているサービスプレフィックスは除外しつつ、アクションもワイルドカードでまとめるような形とすることで何とか単一のポリシーに収まるようになりました。

ただ、一旦は Get*List*Describe*View* で始まるアクションのみに限定していること、前回確認した API Gateway でみられるキャメルケースではない形式は無視しているなど最低限の内容となっている点が改良ポイントかと考えています。

また、作成されるポリシーは AWS 管理ポリシー ReadOnlyAccess に含まれていないポリシーとなっているため、運用する場合は ReadOnlyAccess とこの仕組みで作成されるポリシーを併用することで完全無欠(に近づく)こととなり、ReadOnlyAccess のアタッチ自体が不要になるわけではない点にご留意いただく必要があります。

作成用のサンプルプログラム

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
#!/bin/bash
set -eu
# Set work directory
readonly work_dir=$(pwd)

# Set output, temporary file full path
readonly default_read_only_access_policy="${work_dir}"/ReadOnlyAccess.json
readonly complement_read_only_access="${work_dir}"/ComplementReadOnlyAccess.json
readonly action_list_file="${work_dir}"/action_list_file.txt
readonly read_only_list="${work_dir}"/read_only_list.txt

# Determine and set python commands
if command -v python3 &>/dev/null; then
  python_cmd="python3"
elif command -v python &>/dev/null; then
  python_cmd="python"
else
  echo "Error: Python is not installed." >&2
  exit 1
fi

execute(){
  # Get ReadOnlyAccess policy arn
  read_only_access_policy_arn=$(aws iam list-policies --scope AWS --query 'Policies[?PolicyName==`ReadOnlyAccess`].Arn' --output text)
  # Get default ReadOnlyAccess version
  read_only_access_policy_version=$(aws iam list-policies --scope AWS --query 'Policies[?PolicyName==`ReadOnlyAccess`].DefaultVersionId' --output text)
  # Get default ReadOnlyAccess document
  aws iam get-policy-version --policy-arn ${read_only_access_policy_arn} --version-id ${read_only_access_policy_version} --query 'PolicyVersion.Document' > ${default_read_only_access_policy}
  # Get each aws service servicereference url from https://servicereference.us-east-1.amazonaws.com/
  service_reference_list=$(curl https://servicereference.us-east-1.amazonaws.com/ | jq '.[].url' | sed 's/"//g')

  # Get service prefix, aws api action name from each aws service servicereference url 
  for service_reference in $(echo $service_reference_list) ; do
    service_prefix=$(curl ${service_reference} | jq '.Name' | sed 's/"//g')
    actions=$(curl ${service_reference} | jq '.Actions[].Name' | sed 's/"//g')

    # Match the obtained AWS API action name with the default ReadOnlyAccess policy, and write the missing actions to a temporary file.
    for action in $(echo $actions) ; do
      action_first_verb=$(echo "${action}" | sed 's/\([A-Z][a-z]*\).*/\1/')

      if ! grep "${service_prefix}:${action_first_verb}*" ${default_read_only_access_policy} 1>/dev/null 2>&1 ; then
          echo "${service_prefix}:${action_first_verb}" >> ${action_list_file}
      fi
    done
  done

  # Extract only the read actions beginning with "Get", "List", "Describe" and "View" from the temporary file you created.
  grep -e "Get" -e "List" -e "Describe" -e "View" action_list_file.txt | uniq | sed 's/$/*",/g' | sed 's/^/"/g' > ${read_only_list}
  read_only_actions=$(cat ${read_only_list})

# Format the extracted AWS API actions into an IAM policy document and save it locally.
cat << EOF | ${python_cmd} -m json.tool > ${complement_read_only_access}
{
  "Version" : "2012-10-17",
  "Statement" : [
    {
      "Sid" : "ComplementReadOnlyAccess",
      "Effect" : "Allow",
      "Action" : [
$(echo ${read_only_actions/%?/})
      ],
      "Resource" : "*"
    }
  ]
}
EOF

  # Clean up by deleting temporary files
  rm -rf  ${action_list_file} ${read_only_list} ${default_read_only_access_policy}
  exit 0
}

usage() {
  cat <<_EOT_

Usage:
  ./$(basename ${0}) <options>

Description:
  en)
  Create a customer-managed policy to cover read-only actions of AWS APIs 
  that are not covered by the default version of ReadOnlyAccess.

  ja)
  デフォルトバージョンの ReadOnlyAccess で網羅されていない
  AWS API の読み取り系アクションを補うカスタマーマネージドポリシーを作成します。

Require:
  - AWS CLI (It doesn't matter if it's version 1 or 2)
  - curl, jq, sed
  - list-policies get-policy-version

Options:
  -v  print $(basename ${0}) version
  -h  print this
  -e  Create complement "ReadOnlyAccess" policy

_EOT_
  exit 1
}

version() {
  echo "$(basename ${0}) version 0.0.1"
  exit 1
}  

while getopts :hve opt
do
  case $opt in
    h)
      usage
    ;;
    v)
      version
    ;;
    e)
      execute
    ;;
    *)
      echo "[ERROR] Invalid option '${1}'"
      usage
      exit 1
    ;;
  esac      
done

上記のサンプルプログラムで作成したカスタマーマネージドポリシー(20204-12-26 時点)

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "ComplementReadOnlyAccess",
            "Effect": "Allow",
            "Action": [
                "a2c:Get*",
                "activate:Get*",
                "airflow:Get*",
                "amplifybackend:Get*",
                "amplifybackend:List*",
                "amplifyuibuilder:Get*",
                "amplifyuibuilder:List*",
                "app-integrations:Get*",
                "app-integrations:List*",
                "application-autoscaling:Get*",
                "application-cost-profiler:Get*",
                "application-cost-profiler:List*",
                "application-transformation:Get*",
                "appmesh:Get*",
                "appmesh-preview:Describe*",
                "appmesh-preview:Get*",
                "appmesh-preview:List*",
                "aws-marketplace:Describe*",
                "aws-marketplace:Get*",
                "aws-marketplace:List*",
                "aws-marketplace:View*",
                "aws-marketplace-management:Get*",
                "aws-portal:Get*",
                "awsconnector:Get*",
                "b2bi:Get*",
                "b2bi:List*",
                "backup-search:Get*",
                "backup-search:List*",
                "backup-storage:Describe*",
                "backup-storage:Get*",
                "backup-storage:List*",
                "batch:Get*",
                "bcm-data-exports:Get*",
                "bcm-data-exports:List*",
                "bcm-pricing-calculator:Get*",
                "bcm-pricing-calculator:List*",
                "braket:List*",
                "bugbust:Get*",
                "bugbust:List*",
                "cases:Get*",
                "cases:List*",
                "chime:Describe*",
                "cloud9:Get*",
                "cloudhsm:Get*",
                "cloudshell:Describe*",
                "cloudshell:Get*",
                "codebuild:Get*",
                "codeconnections:Get*",
                "codeconnections:List*",
                "codedeploy-commands-secure:Get*",
                "codeguru:Get*",
                "codeguru-security:Get*",
                "codeguru-security:List*",
                "codestar-notifications:Describe*",
                "codewhisperer:Get*",
                "codewhisperer:List*",
                "comprehendmedical:Describe*",
                "comprehendmedical:List*",
                "connect-campaigns:Describe*",
                "connect-campaigns:Get*",
                "connect-campaigns:List*",
                "controlcatalog:Get*",
                "controltower:Describe*",
                "controltower:Get*",
                "controltower:List*",
                "cur:Describe*",
                "cur:List*",
                "dbqms:Describe*",
                "dbqms:Get*",
                "deeplens:Get*",
                "deeplens:List*",
                "deepracer:Get*",
                "deepracer:List*",
                "detective:Describe*",
                "directconnect:List*",
                "dlm:List*",
                "dms:Get*",
                "docdb-elastic:Get*",
                "docdb-elastic:List*",
                "ds-data:Describe*",
                "ds-data:List*",
                "ebs:Get*",
                "ebs:List*",
                "ecs:Get*",
                "elasticloadbalancing:Get*",
                "elemental-activations:Get*",
                "elemental-support-cases:Get*",
                "elemental-support-cases:List*",
                "emr-containers:Get*",
                "entityresolution:Get*",
                "entityresolution:List*",
                "finspace:Get*",
                "finspace:List*",
                "finspace-api:Get*",
                "freertos:Get*",
                "fsx:Get*",
                "geo:Describe*",
                "geo:Get*",
                "geo:List*",
                "geo-maps:Get*",
                "geo-places:Get*",
                "glue:Describe*",
                "groundtruthlabeling:Describe*",
                "groundtruthlabeling:Get*",
                "groundtruthlabeling:List*",
                "honeycode:Describe*",
                "honeycode:Get*",
                "honeycode:List*",
                "iotdeviceadvisor:Get*",
                "iotdeviceadvisor:List*",
                "iotevents:Get*",
                "iotjobsdata:Describe*",
                "iotjobsdata:Get*",
                "iottwinmaker:Get*",
                "iottwinmaker:List*",
                "iq:Get*",
                "iq:List*",
                "iq-permission:Get*",
                "iq-permission:List*",
                "kafka-cluster:Describe*",
                "kendra-ranking:Describe*",
                "kendra-ranking:List*",
                "license-manager-linux-subscriptions:Get*",
                "license-manager-linux-subscriptions:List*",
                "license-manager-user-subscriptions:List*",
                "lookoutequipment:Describelabel*",
                "managedblockchain-query:Get*",
                "managedblockchain-query:List*",
                "mapcredits:List*",
                "mechanicalturk:Get*",
                "mechanicalturk:List*",
                "mediatailor:Describe*",
                "mediatailor:Get*",
                "mediatailor:List*",
                "medical-imaging:Get*",
                "medical-imaging:List*",
                "migrationhub-orchestrator:Get*",
                "migrationhub-orchestrator:List*",
                "migrationhub-strategy:Get*",
                "migrationhub-strategy:List*",
                "neptune-db:Get*",
                "neptune-db:List*",
                "neptune-graph:Get*",
                "neptune-graph:List*",
                "networkflowmonitor:Get*",
                "networkflowmonitor:List*",
                "networkmanager-chat:List*",
                "networkmonitor:Get*",
                "networkmonitor:List*",
                "opensearch:Get*",
                "opsworks:List*",
                "panorama:Describe*",
                "panorama:Get*",
                "panorama:List*",
                "partnercentral:Get*",
                "partnercentral:List*",
                "pcs:Get*",
                "pcs:List*",
                "private-networks:Get*",
                "private-networks:List*",
                "profile:Get*",
                "profile:List*",
                "q:Get*",
                "qapps:Describe*",
                "qapps:Get*",
                "qapps:List*",
                "quicksight:Describe*",
                "quicksight:Get*",
                "quicksight:List*",
                "redshift-data:Describe*",
                "redshift-data:Get*",
                "redshift-data:List*",
                "redshift-serverless:Describe*",
                "repostspace:Get*",
                "repostspace:List*",
                "resource-explorer:List*",
                "rhelkb:Get*",
                "rolesanywhere:Get*",
                "rolesanywhere:List*",
                "s3express:Get*",
                "s3express:List*",
                "s3tables:Get*",
                "s3tables:List*",
                "sagemaker-geospatial:Get*",
                "sagemaker-geospatial:List*",
                "sagemaker-mlflow:Get*",
                "sagemaker-mlflow:List*",
                "scn:Describe*",
                "scn:Get*",
                "scn:List*",
                "security-ir:Get*",
                "security-ir:List*",
                "serviceextract:Get*",
                "simspaceweaver:Describe*",
                "simspaceweaver:List*",
                "sms:Get*",
                "sms:List*",
                "sms-voice:Get*",
                "snow-device-management:Describe*",
                "snow-device-management:List*",
                "social-messaging:Get*",
                "social-messaging:List*",
                "sqlworkbench:Get*",
                "sqlworkbench:List*",
                "ssm-guiconnect:Get*",
                "ssm-guiconnect:List*",
                "ssm-quicksetup:Get*",
                "ssm-quicksetup:List*",
                "sso-directory:Get*",
                "supportapp:Describe*",
                "supportapp:Get*",
                "supportapp:List*",
                "supportrecommendations:Get*",
                "textract:Get*",
                "textract:List*",
                "thinclient:Get*",
                "thinclient:List*",
                "timestream:Get*",
                "timestream-influxdb:Get*",
                "timestream-influxdb:List*",
                "tiros:Get*",
                "transcribe:Describe*",
                "vendor-insights:Get*",
                "vendor-insights:List*",
                "voiceid:Describe*",
                "voiceid:List*",
                "wickr:List*",
                "wisdom:Get*",
                "wisdom:List*",
                "worklink:Describe*",
                "worklink:List*",
                "workmailmessageflow:Get*",
                "workspaces:Get*",
                "workspaces:List*",
                "xray:List*"
            ],
            "Resource": "*"
        }
    ]
}

おわりに

当初は AWS 側で網羅的な資料がないためにスクレイピングで無理やり情報を収集・集約するアプローチにしていましたが、放置している間に AWS 側から網羅的な資料が公開されたので、一気に形にできた感じで、いい感じに年を越せそうです。

GitHub にも公開しましたのでフォークいただきご要件に合わせた調整をした上で、ご活用いただけると幸いです。

本記事がどなたかの参考になれば幸いです。

ではまた。

Comments