【趣味の工作】AWS CLIに自前Web APIへのアクセス機能を追加する

ども、大瀧です。
AWS CLIは、AWSをオペレーションするために欠かせないコマンドラインツールです。AWSのAPIを呼び出す公式かつオープンソースのツールであることはもちろん、リクエストのフィルタ/クエリ抽出、ページングやウェイターなどいろいろ補助的な仕組みがあるので、CLIフレームワークという側面もあるかなと最近思っています。

そこで今回は自前のWeb APIの呼び出し機能をノンプログラミングでAWS CLIに追加する方法をご紹介したいと思います。

本ブログで紹介する内容は独自に調査・検証したものであり、予告なく仕様が変更されることがあります。趣味としての利用に留め、商用利用はお控えください。

AWS CLIを利用するメリット

  • サーバーサイド(Web API)としてAmazon API Gatewayを利用すれば、AWS IAMと簡単に認証統合できる
  • クライアントはプログラミングなしでAWS CLIの豊富な機能(フィルタ/クエリ抽出、ページング、ウェイターなど)を利用できる(定義ファイルなどの準備は必要)

一方でデメリットとしてはドキュメントがほとんど公開・整備されていないため、定義ファイルやサーバーサイドの作り込みにはAWS CLIのソースや他のAWSサービスの定義ファイルの読み込みが必要です。動作品質やサポートを求められると辛いので、趣味の工作というわけです。楽しんでいきましょう。

実装上のポイント

AWS CLIはOSSですので、AWS CLI自体に手を入れて改変することはもちろん可能です。しかし、プラグイン機構を利用することでCLI本体に手を入れなくとも、今回の独自Web APIの追加を実現することが出来ます。ポイントは以下です。

  • APIのエンドポイント設定はAWS CLIプラグインであるwbingli/awscli-plugin-endpointを利用する
  • APIへのリクエスト/レスポンス定義はモデルaws configure add-modelコマンドで行う

検証環境

検証で使用した環境は以下です。

  • OS : macOS High Sierra
  • ソフトウェアバージョン : aws-cli/1.14.37 Python/2.7.13 Darwin/17.4.0 botocore/1.8.41

1. API Gatewayの構成とエンドポイントの設定

まずはサーバーサイドを用意しましょう。API Gatewayを利用するとAWS CLIの持つAWS V4署名機能をAWS IAM認証と対応付けて簡単に認証統合ができます。今回はAPI GatewayのサンプルであるPetStoreのWeb APIを流用し、GET /petsを実装してみます。ドキュメントの手順通りにAPIを作成後、以下を変更します。

マッピングテンプレートの設定

これはAWS CLIのレスポンスは最上位階層がオブジェクトになんらかの要素を持つ構造のため、PetStoreのGET /petsのレスポンスである配列ではこれに適合しないためです。Pets要素の値として前述の配列を返すよう、統合レスポンスの本文マッピングテンプレートを以下に設定します。

#set($inputRoot = $input.path('$'))
{
  "Pets" : $input.json('$')
}

これでOKです。

AWS IAM認証の有効化

ここでは、API GatewayのAPIを呼び出すためのアクセス制御機能を利用して、認証統合を試してみましょう。メソッドリクエストの[設定] - [認証]で「AWS_IAM」を選択します。これで許可されたIAMユーザー/ロールによるexecute-api:Invokeアクションを含むAWS V4署名を含むリクエストのみが許可されるようになります。

以上の変更をAPIに反映させるためにAPIのデプロイを実行します。ステージはAWS APIでよく利用されるv1ステージを作成し、デプロイします。

デプロイされたらエンドポイントのホスト名をメモして、このあとのAWS CLIのエンドポイント設定で指定します。

AWS CLIのプラグイン導入とエンドポイント設定

AWS CLIでは決められたAWS APIのエンドポイントを扱いますが、ローカルでのデバッグ用途などで独自のエンドポイントを設定するためのプラグインが公開されています(非公式のツールなので、自己責任の元ご利用ください)。まずはプラグインをインストールします。AWS CLIと同様、pip installで一発です。

$ pip install awscli-plugin-endpoint

インストール後は、以下のコマンドでプラグインを有効化します。

$ aws configure set plugins.endpoint awscli_plugin_endpoint

これでOKです。プロファイルおよびサービス毎に独自エンドポイントが定義できます。今回はデフォルトのプロファイル、サービス名をpetstoreとするので、以下のコマンドラインで実行します。

$ aws configure set petstore.endpoint_url https://XXXXXXXXXX.execute-api.ap-northeast-1.amazonaws.com

これでOKです。

2. モデルの定義ファイル作成とAWS CLIへの追加

続いて、APIのリクエストとレスポンスを定義するモデルファイル(service.json)を作成します。今回はAWSの音声読み上げサービスであるAmazon PollyのAPIがRESTfulなJSON APIでAPI Gatewayに近い感じがしたので、モデルファイルを習作してみました。ポイントは以下です。

  • 5行目 : endpointPrefixは接続先エンドポイントではなく、前述の署名に含まれるアクションに利用されるためAPI Gatewayにはexecute-apiで固定になります
  • 16,17行目 : リクエストするURLはAPI Gatewayのリソースに対応させます。今回はlist-petsサブコマンドにGET /petsを対応させる定義にしました。
{
  "version":"2.0",
  "metadata":{
    "apiVersion":"2018-02-14",
    "endpointPrefix":"execute-api",
    "serviceFullName":"PetStore",
    "protocol":"rest-json",
    "serviceId":"PetStore",
    "signatureVersion":"v4",
    "uid":"petstore-2018-02-14"
  },
  "operations":{
    "ListPets":{
      "name":"ListPets",
      "http":{
        "method":"GET",
        "requestUri":"/v1/pets",
        "responseCode":200
      },
      "input":{"shape":"ListPetsInput"},
      "output":{"shape":"ListPetsOutput"},
      "errors":[
        {"shape":"ServiceFailureException"}
      ],
      "documentation":"<p>test</p>"
    }
  },
  "shapes":{
    "ListPetsInput":{
      "type":"structure",
      "members":{
      }
    },
    "ListPetsOutput":{
      "type":"structure",
      "members":{
        "Pets":{
          "shape":"PetDescriptionList"
        }
      }
    },
    "PetDescriptionList":{
      "type":"list",
      "member":{"shape":"PetDescription"}
    },
      "PetDescription":{
      "type":"structure",
      "members":{
        "id":   { "shape":"PetId" },
        "type": { "shape":"PetType" },
        "price":{ "shape":"PetPrice" }
      }
    },
    "PetId":   {"type":"integer"},
    "PetType": {"type":"string"},
    "PetPrice":{"type":"string"},
    "ErrorMessage":{"type":"string"},
    "ServiceFailureException":{
      "type":"structure",
      "members":{
        "message":{"shape":"ErrorMessage"}
      },
      "documentation":"<p>An unknown condition has caused a service failure.</p>",
      "error":{"httpStatusCode":500},
      "exception":true,
      "fault":true
    }
  },
  "documentation":"<p>test</p>"
}

では作成したモデルファイルをAWS CLIに追加します。

$ aws configure add-model --service-model file://service.json --service-name petstore

これでOKです。

動作確認

では、試してみましょう。aws <サービス名> <サブコマンド>の形式で実行してみます。

$ aws petstore list-pets
{
    "Pets": [
        {
            "price": 249.99,
            "type": "dog",
            "id": 1
        },
        {
            "price": 124.99,
            "type": "cat",
            "id": 2
        },
        {
            "price": 0.99,
            "type": "fish",
            "id": 3
        }
    ]
}
$

レスポンスが返ってきました!出力フォーマット変更もできます。

$ aws petstore list-pets --output text
PETS	1	249.99	dog
PETS	2	124.99	cat
PETS	3	0.99	fish
$ 

あとは、AWS IAM認証が設定されているので、API Gatewayに署名なしでリクエストを送るとエラーになることを確認します。

$ curl https://XXXXXXXXXX.execute-api.ap-northeast-1.amazonaws.com/v1/pets
{"message":"Missing Authentication Token"}$

期待した結果になりました。

後片付け

API GatewayはAPI定義を削除すれば掃除完了です。AWS CLIへの変更はCLIの構成として以下のファイル/ディレクトリが変更されているので、それぞれ当該箇所を削除すればOKです。

  • ~/.aws/config
    • [default]petstore行とその後ろのendpoint_url
    • [plugins]endpoint
  • ~/.aws/models/petstore/ディレクトリ一式

まとめ

Web APIをAWS CLIに統合して遊ぶ様子をご紹介しました。仕組みとしてこんなことも出来るかな?と思いついて悪ノリでやってしまったのでした、すみません。

参考URL