ドキュメント公開されていないAWS Control Tower のAPI を呼んでみた。Use awsapilib

2022.04.26

AWS事業本部の梶原@福岡オフィスです。 出オチですが、やっていることは以下となります。

awsapilibというGithubで公開されているライブラリを使用して、AWSでドキュメント公開されていないサービスのAPIをPythonから呼ぶ方法となります。

※注意

当然AWSのドキュメントにないものですので、本ブログの内容を実施したことによる影響については責任を持ちません。軽く動作することの確認はしておりますが、将来的なAPIの変更や突然APIが呼べなくなる可能性もありますので ご使用の際は十分にご検証することを推奨いたします。

ということで、最近、AWS Contorol Towerを触る機会が多いのですが、現時点(2022/04/26)ではControl Towerを操作するようなAPIのドキュメントは公開されておりません。 Control Towerを操作する上で必要なものはすべてコンソール上で可能です。 AWSからのご案内も下記のようになります。

https://aws.amazon.com/jp/controltower/faqs/

Q: Is there an API available for AWS Control Tower?
No. You can use AWS Control Tower through the AWS Management Console to perform all necessary operations.

でも、ブラウザなどをデバッグモードで追っていると、なんかあるんですよね。。。API
なんかあるんですよね。エンドポイント。。。

なんとかコードでControl Towerの情報を取得したい!!!(アカウント100とかあるんよ。200とか!切実
ということで、

思いきって、Githubの海に飛び込んだら、awsapilibに会えて、幸せ!

と、前置きはこんな雑な感じでいきます。

awsapilib とは

https://github.com/schubergphilis/awsapilib

A python library that exposes AWS services that are not covered by boto3, through the usage of undocumented APIs.

繰り返しになってしまいますが、AWSの名前は入ってしまってますが、単純にGithubに公開されているライブラリです。AWS SDK等で使用されているBoto3などではありませんので、ご使用は自己責任でお願いします。

対応しているサービス(一部のみ)

  • ControlTower
  • SSO
  • Billing
  • Account Manager

よく見ると前から欲しかったAPIが並んでますね。繰り返しになりますが、ドキュメント化されていないAWSサービスのAPIとなります。また、すべてのAPIがサポートされているわけでなく、一部のAPIになるようです。

やってみた

相変わらずですが、CloudFormation テンプレートでのご提供です。 AWSコンソールにログイン後に下記テンプレートのURLをクリックしてください。

https://ap-northeast-1.console.aws.amazon.com/cloudformation/home?region=ap-northeast-1#/stacks/quickcreate?templateUrl=https%3A%2F%2Fpub-devio-blog-qrgebosd.s3.ap-northeast-1.amazonaws.com%2Ftemplate%2Fcfn-AwsApiLibSample.yml&stackName=AwsApiLibSample

□AWS CloudFormation によって IAM リソースが作成される場合があることを承認します。 をチェックし

スタックを実行すると、GetControlTowerAccounts というLambda関数が作成されます。

Lambada実行時にスイッチロールする権限(AwsApiLibRole)はAdministrator権限を割り当てているので、実際に使用する場合は必要なRole等を割り当ててください

テンプレート全体は、本ブログの最後に記載しています。

Control Tower 配下のアカウントの一覧を出力するLambda(Python)関数となります。

Lambda

Pyhon関数の実行時に、awsapilib (現在のバージョン 2.1.1) をinstallしています。 1分以内ではありますが、実際にプロダクションで使われる場合は、Lambda Layer化するなどの方法をご検討ください

抜粋

sys.path.insert(1, '/tmp/packages')
subprocess.check_call([sys.executable, "-m", "pip", "install", '--target', '/tmp/packages', 'awsapilib==2.1.1'])

実際のawsapilibでContorl Towerのアカウントの一覧の取得処理は下記のようになります。

外部環境変数 'AWSAPILIB_CONTROL_TOWER_ROLE_ARN’ で、Administrator権限に割り当てられたAwsApiLibRoleを指定し、関数内でAssrume Roleしています。

抜粋

import awsapilib
from awsapilib import ControlTower

@exception_handling
def lambda_handler(event, context):
    tower = ControlTower(os.environ['AWSAPILIB_CONTROL_TOWER_ROLE_ARN'])
    for account in tower.accounts:
        print(account.name)

結果

実行してみると

Audit, Log Archive などのContorl Tower 配下のアカウントが取得できており正常に動いているようです。 ソースコードを参照すると、Contorol Towerr のlistManagedAccounts のAPIが呼ばれています。

CoudTrail にもListManagedAccounts のアクセスが記録されていますので、正常に呼び出せているようです。

カスタマイズ

awsapilibのドキュメントを参照して、必要な情報を取得し、整形等すればちょっと使えそうな気がしています。
ソースコード等公開されていますので、必要なAPIが見つからない場合は、ちょっと?追加実装すれば対応できそうです。
とはいえ、仕様等は手探りになりますので、結構いばらの道かと思います。そこにAPIがあるから叩くんだくらいの気合はいると思います。

まとめ

Githubの海を漂っているうちにSuperwerker という、AWSが紹介しているQuickStartでControl Towerを有効化してアカウントを作成する際に中で使われていたりしたので、大部分はこちらのControlTowerの制御部分を参考にさせてもらっています。   https://aws-quickstart.github.io/quickstart-superwerker/

ドキュメント化されていないAPIになるので、ソースコードの確認や、IAM権限の確認、副作用の確認、AWSのサポートに 聞けない、などなど超えなければ行けない壁はひたすらありますが

  • 適切権限を与える
  • 変更を伴う操作はAPIでなく、コンソールから行う
  • コンソールで設定した結果の確認、取得
  • 現在の設定値の一覧を取得する

など、ユースケースによっては有用そうです。

参考情報

awsapilib Document https://awsapilib.readthedocs.io/en/latest/

awsapilib Github https://github.com/schubergphilis/awsapilib

Superwerker https://aws-quickstart.github.io/quickstart-superwerker/

テンプレート

#
# The following template was used as a reference
# https://github.com/aws-quickstart/quickstart-superwerker/blob/main/templates/control-tower.yaml
# 
AWSTemplateFormatVersion: "2010-09-09"

Resources:
  GetControlTowerAccounts:
    Type: AWS::Lambda::Function
    Properties:
      Handler: index.lambda_handler
      Runtime: python3.9
      Timeout: 900 # give it more time since it installs awsapilib and tries to deploy control tower with retries
      Role: !GetAtt GetControlTowerAccountsRole.Arn # provide explicit role to avoid circular dependency with AwsApiLibRole
      Environment:
        Variables:
          AWSAPILIB_CONTROL_TOWER_ROLE_ARN: !GetAtt AwsApiLibRole.Arn
      FunctionName: GetControlTowerAccounts
      Code:
        ZipFile: |
          import boto3
          import os
          import sys
          import subprocess

          # load awsapilib in-process as long as we have no strategy for bundling assets
          sys.path.insert(1, '/tmp/packages')
          subprocess.check_call([sys.executable, "-m", "pip", "install", '--target', '/tmp/packages', 'awsapilib==2.1.1'])

          import awsapilib
          from awsapilib import ControlTower

          def exception_handling(function):
              def catch(event, context):
                  try:
                      function(event, context)
                  except Exception as e:
                      print(e)
                      print(event)
              return catch

          @exception_handling
          def lambda_handler(event, context):
              tower = ControlTower(os.environ['AWSAPILIB_CONTROL_TOWER_ROLE_ARN'])
              for account in tower.accounts:
                  print(account.name)


  GetControlTowerAccountsRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service: lambda.amazonaws.com
            Action: sts:AssumeRole
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole

  AwsApiLibRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              AWS: !GetAtt GetControlTowerAccountsRole.Arn
            Action: sts:AssumeRole
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/AdministratorAccess

Outputs:
  MyFunctionArn:
    Value: !GetAtt GetControlTowerAccounts.Arn