FileMaker から S3 や EC2 などの AWS の API を利用する方法
こんにちは。株式会社フルーデンスの小巻です。
タイトルの通り、FileMaker から S3 や EC2 などの AWS の API を利用する方法についての解説記事です。
今までは、FileMaker で画像や動画などのファイルを使う場合、Dropbox を使って実装するケースが多くありました。
しかし、昨今では、AWS EC2 で FileMaker Server を利用するケースや、BIツールとして Amazon QuickSight と連携したり、Amazon API Gateway や AWS Lambda、Amazon SES を利用するケースも増えてきました。
そのため、オブジェクトフィールドの代替手段として、Dropbox に加えて S3 で実装するケースが増えてきました。
S3 は容量を気にすることなく始めることができますし、Cyberduck や Mountain Duck などを使うことで、いろいろなケースで使えるメリットもあります。
S3 に限らず、FileMaker から AWS の API を利用できるようになれば、EC2・Lambda・QuickSight など、いろいろな用途で利用できる幅が増えると思います。
また、昨今では、開発者の中でも Claris Connect や Zapier、Make(旧Integromat)などの GUI でアプリ連携をするサービスを使う人も増えてきました。
大変便利ですが、連携されていないアプリの API は利用できないということになります。
メインのサービスが連携されているので、ほとんどのケースをカバーできるので良いのかもしれませんが、個人的にはドキュメントを読み、手を動かし実装する方法についても多くの人に挑戦して頂ければと思います。
対象者
- AWS の API の署名の作成方法を理解したい方
- 様々な AWS の API を、SDKやライブラリを使わずに実行したい方
初めに
最近では、Google や Microsoft、Dropbox などの API を利用する際は、OAuth 2.0 を理解していれば、ほとんどの API が利用できる状況です。
しかし、AWS の API は、リクエスト情報をもとに署名を作成し、ヘッダーに作成した署名を設定する必要があります。
そのため、OAuth 2.0 とは少し違う方法で API を実行する必要があります。
今回は、FileMaker から S3 の API を利用する想定で、署名の作成方法や実際にリクエストする方法までの工程を解説します。
公式ドキュメント
詳細については、以下のドキュメントを見ていただければと思います。
今回はの記事では、以下のドキュメントに書いている以上のことは記載していません。
そのため、以下のドキュメントを理解されている方は、記事を読む必要はありません。
環境
私の環境は以下の通りです。
- macOS Monterey (v12.6.1)
- FileMaker Pro 19.6.1.45
curl コマンドの具体例とデモ動画
ドキュメントの通りに進めていき、最終的に署名が作成できましたら、以下のような curl コマンドをリクエストします。
今回の記事を通して、オプションの `–header “Authorization: XXXXX” ` という値を作成する方法を理解して頂ければと思います。
curl コマンド
S3の “demo-eib5t-filemaker” バケットにある “animal_chara_radio_azarashi.png” というpngファイルを取得する際のコマンドになります。
URL https://demo-eib5t-filemaker.s3.us-east-1.amazonaws.com/animal_chara_radio_azarashi.png curlオプション --request GET --header "Authorization: AWS4-HMAC-SHA256 Credential=yourAaccessKeyId/20221130/us-east-1/s3/aws4_request, SignedHeaders=host;x-amz-content-sha256;x-amz-date, Signature=d36d49c86a509378e7b75ebd0730d86e9a8dcec4b8508543bb6bf0af98b7fe66" --header "x-amz-content-sha256: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" --header "x-amz-date: 20221130T044954Z" --show-error --dump-header $dumpHeader --FM-return-container-variable --output "animal_chara_radio_azarashi.png"
デモ動画
サンプルファイルを使いデモ動画を準備しました。
学習用のサンプルファイルについて
サンプルファイルのダウンロード
以下のリポジトリよりダウンロードしてください。
https://github.com/frudens/aws-api-sig-v4-filemaker
サンプルファイルの説明
以下の2つのファイルを準備しました。
- frudens_amazon_signature_v4_learning_material.fmp12
- 学習用ファイル
- frudens_amazon_your_custom_app.fmp12
- カスタム関数用ファイル
- カスタムAppに組み込むために、カスタム関数だけで機能するサンプルファイル
frudens_amazon_signature_v4_learning_material.fmp12
frudens_amazon_your_custom_app.fmp12
事前準備
FileMaker から AWS の API をリクエストする前に、S3バケットの作成や、API用のポリシーやユーザーを作成する必要があります。
AWSにログイン
AWS にログインし、S3に移動します。
S3バケットの作成
適当な名前のS3バケットを作成します。
今回は “demo-ahx9h-filemaker” という名前のバケットを作成します。
バケットが作成できました。
続いて、ポリシーを作成するために、IAMに移動します。
ポリシーの作成
ポリシーに移動します。
ポリシーを作成します。
ビジュアルエディタで設定しても良いのですが、今回はJSONで直接編集します。
以下のJSONを参考に、S3のバケット部分をご自身のバケットに変更します。
オブジェクトの一覧の取得、オブジェクトの作成・更新、オブジェクトの削除、を許可する設定になります。
{ "Version": "2012-10-17", "Statement": [ { "Sid": "VisualEditor0", "Effect": "Allow", "Action": [ "s3:PutObject", "s3:GetObject", "s3:ListBucket", "s3:DeleteObject" ], "Resource": [ "arn:aws:s3:::demo-ahx9h-filemaker/*", "arn:aws:s3:::demo-ahx9h-filemaker" ] } ] }
適当な名前のタグを設定します。
適当な名前のポリシーを作成します。
今回は “demo-s3-fileMaker” という名前のポリシーを作成します。
ポリシーが作成できました。
ユーザーの作成
ユーザーに移動します。
ユーザーを作成します。
適当な名前のユーザーを作成します。
今回は “demo-filemaker-user” という名前のユーザーを作成します。
下図のように、チェックをし、アクセス権限へ移動します。
下図のように “既存のポリシーを直接アタッチ” をクリックし、先ほど作成したポリシーをチェックします。
タグに移動します。
適当な名前のタグを設定します。
“ユーザーの作成” をクリックします。
ユーザーが作成できました。
後ほど、アクセスキーID、シークレットアクセスキーを利用しますので、コピーするか、csvのダウンロードをします。
以上の作業で、AWS側の設定は終了となります。
ドキュメントを確認
冒頭でも紹介しましたが、改めてドキュメントを確認します。
AWS API リクエストの署名
以下の “リクエストに署名するタイミング” にも記載の通り、FileMaker は AWS SDK を使える環境ではありませんので、自分で AWS SDK と同様のことを FileMaker で実装する必要があります。
リクエストに署名するタイミング
AWS に API リクエストを送信するためのカスタムコードを記述するときは、リクエストの署名するためのコードを含める必要があります。
次の理由から、これが必要になる場合があります。
- AWS SDK がないプログラミング言語を使用しているためです。
- AWS にリクエストを送る方法を完全に管理する場合。
AWS CLI または AWS SDK の 1 つを使うときは、リクエストに署名する必要はありません。
これらのツールは、署名を計算し、接続の詳細を管理し、リクエストの再試行を処理し、エラー処理を提供します。
また多くの場合、SDK には、AWS とやり取りするアプリケーションの作成を開始するのに役立つ、サンプルコード、チュートリアルなどのリソースも含まれています。
署名バージョン 4 の署名プロセス
以下の “署名バージョン 4 の仕組み” に記載している通りのことを、FileMakerで一つずつ実装していきます。
見慣れない単語が出てきますが、具体的な計算式も記載しますので、おそらく理解して頂けると思います。
署名バージョン 4 の仕組み
- 正規リクエストを作成します。
- 正規リクエストと追加のメタデータを使用して、署名の文字列を作成します。
- AWS シークレットアクセスキーから署名キーを取得します。次に、署名キーと、前の手順で準備した文字列を使用して、署名を作成します。
- 作成した署名をヘッダーの HTTP リクエストに追加するか、クエリ文字列パラメータとして追加します。
署名バージョン 4 を使用した AWS リクエストへの署名
先ほどの “署名バージョン 4 の仕組み” を、より具体的にステップに分けて説明しているのが、以下の “署名手順の概要” になります。
署名手順の概要
署名付きリクエストを作成するには、次の操作を完了します。
- タスク 1: 署名バージョン 4 の正規リクエストを作成する
リクエストのコンテンツ (ホスト、アクション、ヘッダーなど) を標準的な形式、つまり正規形式に変換します。
正規リクエストは、署名する文字列を作成するのに使用される入力の 1 つです。- タスク 2: 署名バージョン 4 の署名文字列を作成する
正規リクエストに加えてアルゴリズム、リクエスト日、認証情報スコープ、正規リクエストのダイジェスト (ハッシュ) などの追加情報を使用して、署名する文字列を作成します。- タスク 3: AWS 署名バージョン 4 の署名を計算する
署名キーは、最初のハッシュオペレーションのキーとして AWS のシークレットアクセスキーを使用して、リクエスト日、リージョン、およびサービスに対する一連のキー付きハッシュオペレーション (HMAC オペレーション) を実行することによって抽出します。
署名キーを取得したら、署名文字列にキー付きハッシュ操作を実行することで、署名を計算します。
取得した署名キーを、この操作のハッシュキーとして使用します。- タスク 4: HTTP リクエストに署名を追加する
署名を計算したら、それをリクエストの HTTP ヘッダーまたはクエリ文字列に追加します。
時間があれば、上記に紹介したドキュメントや、その他の関連ドキュメントにも目を通しておくと良いと思います。
学習用サンプルファイルの確認
上記のドキュメントを確認し、早速 “タスク 1: 署名バージョン 4 の正規リクエストを作成する” に進みたいのですが、その前に学習用サンプルファイルを確認します。
“Task 0” タブの入力
先ほどダウンロードした、学習用のサンプルファイルを開きます。
“Task 0” タブを開き、事前に作成した AWS のユーザー情報や、S3バケットのURLを入力します。
下図は、作成した S3バケットのオブジェクトの一覧を取得するためのURLを設定しています。
タブとタスクの関係性について
サンプルファイルを開くと “Task 1” や “Task 2” というタブがあります。
例えば、ドキュメントの “タスク 1: 署名バージョン 4 の正規リクエストを作成する” を参照する場合は “Task 1” のタブを開いて頂ければと思います。
サンプルファイルの説明・確認ができましたので “タスク 1: 署名バージョン 4 の正規リクエストを作成する” に進みます。
タスク 1: 署名バージョン 4 の正規リクエストを作成する
タスク1の作業では、リクエスト情報を、ドキュメントの通りに正規形式に修正し、正規リクエスト(CanonicalRequest)を作成します。
署名プロセスを開始するには、リクエストからの情報を含む文字列を標準化された (正規) 形式で作成します。
これにより、リクエストを受け取ると、AWS はお客様が計算したのと同じ署名を計算できるようになります。正規バージョンのリクエストを作成するには以下の手順に従ってください。
そうしなければ、使用しているバージョンと AWS によって計算されたバージョンが一致せず、リクエストが拒否されます。
正規リクエストの擬似コードとして、以下のコードが記載されています。
例 正規リクエストの擬似コード ----- CanonicalRequest = HTTPRequestMethod + '\n' + CanonicalURI + '\n' + CanonicalQueryString + '\n' + CanonicalHeaders + '\n' + SignedHeaders + '\n' + HexEncode(Hash(RequestPayload))
上記の擬似コードだけだと、少し分かりづらいかもしれませんが、ドキュメントをスクロールすると、下部に具体的な正規リクエストのコードが記載されています。
要するに、リクエスト情報を、以下のような文字列にする必要があるということです。
正規リクエストのコードに、擬似コードの単語を追記しました。
例 正規リクエスト ----- GET <= HTTPRequestMethod / <= CanonicalURI Action=ListUsers&Version=2010-05-08 <= CanonicalQueryString content-type:application/x-www-form-urlencoded; charset=utf-8 <= CanonicalHeaders host:iam.amazonaws.com <= CanonicalHeaders x-amz-date:20150830T123600Z <= CanonicalHeaders content-type;host;x-amz-date <= SignedHeaders e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 <= HexEncode(Hash(RequestPayload))
S3から任意のオブジェクトを取得する場合の例
例えば、以下のURLのように、任意のバケットの任意のオブジェクトを取得する場合の正規リクエストは以下の通りです。
https://demo-ahx9h-filemaker.s3.us-east-1.amazonaws.com/animal_chara_radio_azarashi.png
例 demo-ahx9h-filemaker バケットの animal_chara_radio_azarashi.png というオブジェクトを取得したい場合の、正規リクエスト ----- GET <= HTTPRequestMethod /animal_chara_radio_azarashi.png <= CanonicalURI <= CanonicalQueryString host:demo-ahx9h-filemaker.s3.us-east-1.amazonaws.com <= CanonicalHeaders x-amz-content-sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 <= CanonicalHeaders x-amz-date:20221206T045320Z <= CanonicalHeaders host;x-amz-content-sha256;x-amz-date <= SignedHeaders e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 <= HexEncode(Hash(RequestPayload))
"HexEncode(Hash(RequestPayload))" について
上記の "正規リクエストの擬似コード" の最下部に記載されている "HexEncode(Hash(RequestPayload))" という文字列についての説明です。
POSTやPUTメソッドの際に、リクエストボディにバイナリを含めるケースがあると思いますが、そのバイナリの計算結果になります。
そのため、GETメソッドでは、以下の計算式のように共通の文字列になります。
具体的には、以下のような計算式を設定します。
FileMakerの計算式 ----- 計算式: Lower ( HexEncode ( CryptDigest ( "" ; "SHA256" ) ) ) 結果: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
署名内で利用する "日時情報" について
上記の正規リクエストの中に "x-amz-date" という文字列があり "YYYYMMDDTHHMMSSZ" などの日時情報を設定する必要があります。
日本は UTC+09:00 ですが、署名内で日時情報を設定する際は UTC で計算する必要があります。
具体的には、以下のような計算式を設定します。
FileMakerの計算式 ----- 計算式: // RequestDateTime Let ( [ ~timestamp_local = Get ( CurrentTimestamp ) ; ~timestamp = GetAsTimestamp ( Int ( Get ( 現在の時刻 UTC ミリ秒 ) / 1000 ) ) ; ~date = GetAsDate ( ~timestamp ) ; ~dateY = Year ( ~date ) ; ~dateM = Right ( "0" & Month ( ~date ) ; 2 ) ; ~dateD = Right ( "0" & Day ( ~date ) ; 2 ) ; ~time = GetAsTime ( ~timestamp ) ; ~timeH = Right ( "0" & Hour ( ~time ) ; 2 ) ; ~timeM = Right ( "0" & Minute ( ~time ) ; 2 ) ; ~timeS = Right ( "0" & Seconds ( ~time ) ; 2 ) ] ; List ( "===== UTC + 9 =====" ; ~timestamp_local ; "===== UTC =====" ; ~timestamp ; // こちらを利用します。 ~dateY & ~dateM & ~dateD ; ~dateY & ~dateM & ~dateD & "T" & ~timeH & ~timeM & ~timeS & "Z" ) ) /*let*/ 結果: ===== UTC + 9 ===== 2022/12/07 5:55:39 ===== UTC ===== 2022/12/06 20:55:39 20221206 20221206T205539Z
署名内で利用する "改行コード" について
署名内で改行が必要な場合は "Char(10)" や "TextEncode関数" を利用する必要があります。
FileMaker の改行コードではエラーになります。
タスク1のアウトプット
タスク1のドキュメントを確認すると、下図のように、最終的には "正規リクエストのダイジェスト (ハッシュ)" を作成する必要があります。
8. ペイロードをハッシュする際に使用したのと同じアルゴリズムを使用して、正規リクエストのダイジェスト (ハッシュ) を作成します。
具体的には、以下のような計算式を設定します。
FileMakerの計算式 ----- 計算式: Lower ( HexEncode ( CryptDigest ( TextEncode ( xc_task1_canonicalRequest_substitute ; "utf-8" ; 3 ) ; "SHA256" ) ) ) 結果: 9a3a63bf4008e37e9d52d601abe55ca34db9f1047aecb87d960eac99da180d94
今回は、TextEncode関数を使います。
"正規リクエストのダイジェスト (ハッシュ)" が取得できれば、タスク1は完了です。
サンプルファイルを確認
サンプルファイルの "Task 1" タブを確認します。
事前に "Task 0" を入力したため "Task 1" のタブで入力するフィールドはありません。
上記で説明した、正規リクエストや正規リクエストのハッシュがフィールドに設定されています。
詳細については、フィールドの計算式を確認して頂ければと思います。
Urlのパースについて
タスク1の作業で必要になる "CanonicalURI" や "CanonicalQueryString" や "host" などの情報は、設定したURLから取得しています。
計算式内で利用しているカスタム関数は以下になりますので、興味のある方は、確認して頂ければと思います。
バグが見つかりましたら、コメントでやSNSなどで教えて頂ければと思います。
FileMaker Custom Function: ParseUrl ( _url )
URLをパースするカスタム関数が見つからなかったので、作りました。
正しくパースできているのか不安なので、間違っている部分がありましたら、コメント頂けると嬉しいです…URLの構造については、以下を参照しました。https://t.co/Ep2k79mX1h
---
ParseUrl ( _url )https://t.co/UiOcOZ3See pic.twitter.com/kG2FjcpAOR— Teruhiro Komaki (@trhrkmk) November 24, 2022
タスク2に進みます。
タスク 2: 署名バージョン 4 の署名文字列を作成する
固定文字列の "AWS4-HMAC-SHA256(Algorithm)" や日付情報などで構成された3行の文字列に、タスク1で作成した "正規リクエストのハッシュ" を連結して4行の署名文字列を作成します。
署名文字列を作成するには、次の擬似コードに示すように、アルゴリズム、日付と時間、認証情報スコープ、および正規リクエストのダイジェストを連結します。
ドキュメントの "署名文字列の構造" に、実際の値を追加しました。
署名文字列の構造 ----- StringToSign = Algorithm + \n + <= AWS4-HMAC-SHA256 RequestDateTime + \n + <= 20150830T123600Z CredentialScope + \n + <= 20150830/us-east-1/iam/aws4_request HashedCanonicalRequest <= タスク1で作成した、正規リクエストのハッシュ
タスク2のアウトプット
タスク2のドキュメントを確認すると、下図のように、最終的には "署名対象の文字列" を作成する必要があります。
4. タスク 1: 署名バージョン 4 の正規リクエストを作成する で作成した正規リクエストのハッシュを追加します。
この値の後には、改行文字を置きません。
ハッシュされた正規リクエストは、「RFC 4648 セクション 8」で定義されているように、小文字で Base16 エンコードする必要があります。
例 署名対象の文字列 ----- AWS4-HMAC-SHA256 20150830T123600Z 20150830/us-east-1/iam/aws4_request f536975d06c0309214f805bb90ccff089219ecd68b2577efef23edd43b7e1a59
各フィールドの計算式
学習用サンプルファイル内で定義している、各フィールドの計算式を記載します。
xc_task2_stringToSign_templateフィールドの計算式 ----- "{Algorithm}¶" & "{RequestDateTime}¶" & "{CredentialScope}¶" & "{HashedCanonicalRequest}"
xc_algorithmフィールドの計算式 ----- "AWS4-HMAC-SHA256"
xc_task2_credentialScopeフィールドの計算式 ----- xc_requestDateFormat & "/" & region & "/" & service & "/aws4_request"
xc_task2_stringToSign_substituteフィールドの計算式 ----- Substitute ( xc_task2_stringToSign_template ; [ "{Algorithm}" ; xc_algorithm ] ; [ "{RequestDateTime}" ; xc_requestTimestampFormat ] ; [ "{CredentialScope}" ; xc_task2_credentialScope ] ; [ "{HashedCanonicalRequest}" ; xc_task1_hashCanonicalRequest ] ) /*substitute*/
サンプルファイルを確認
サンプルファイルの "Task 2" タブを確認します。
"Task 1" と同様で "Task 0" を入力したため "Task 2" のタブで入力するフィールドはありません。
上記で説明した、署名文字列がフィールドに設定されています。
"Task 1" と同様ですが、ドキュメントに近づけるために、文字列のテンプレートを作成し、テンプレートを置換する計算式にしています。
詳細については、フィールドの計算式を確認して頂ければと思います。
タスク3に進みます。
タスク 3: AWS 署名バージョン 4 の署名を計算する
"Task 0" タブで入力した内容や、タスク2で作成した署名文字列を利用し、署名を作成します。
署名を計算する前に、AWS のシークレットアクセスキーから署名キーを取得します。
取得した署名キーは日付、サービス、およびリージョンに固有であるため、かなりの保護が得られます。
リクエストに署名するためにはシークレットアクセスキーを使用しません。
次に、署名キーと「タスク 2: 署名バージョン 4 の署名文字列を作成する」で作成した署名文字列を、キー付きハッシュ関数の入力として使用します。
キー付きハッシュ関数から得られた 16 進エンコード値が署名です。
署名キーを取得するための擬似コード ----- kSecret = your secret access key kDate = HMAC("AWS4" + kSecret, Date) kRegion = HMAC(kDate, Region) kService = HMAC(kRegion, Service) kSigning = HMAC(kService, "aws4_request")
上記の擬似コードだけだと、少し分かりづらいかもしれませんので、サンプルファイルの計算式を記載します。
各フィールドの計算式
学習用サンプルファイル内で定義している、各フィールドの計算式を記載します。
変数で取り扱う場合は、そこまで気にする必要はないのですが、フィールドで定義する際に、型をオブジェクトにする必要があります。
xc_task3_kSecretフィールドの計算式 ----- // kSecret = your secret access key secretAccessKey // fyb0v0+g8jR2Z2BGEcrNfZppXwttzvRxUFf04Bci
xc_task3_kDateフィールドの計算式 ----- // kDate = HMAC("AWS4" + kSecret, Date) CryptAuthCode ( xc_requestDateFormat ; "SHA256" ; "AWS4" & xc_task3_kSecret )
xc_task3_kRegionフィールドの計算式 ----- // kRegion = HMAC(kDate, Region) CryptAuthCode ( region ; "SHA256" ; xc_task3_kDate )
xc_task3_kServiceフィールドの計算式 ----- // kService = HMAC(kRegion, Service) CryptAuthCode ( service ; "SHA256" ; xc_task3_kRegion )
xc_task3_kSigningフィールドの計算式 ----- // kSigning = HMAC(kService, "aws4_request") CryptAuthCode ( "aws4_request" ; "SHA256" ; xc_task3_kService )
タスク3のアウトプット
タスク3のドキュメントを確認すると、下図のように、最終的には "署名" を作成する必要があります。
署名を計算します。
そのために、キー付きハッシュ関数への入力として、取得した署名キーと署名文字列を使用します。
署名を計算したら、バイナリ値を 16 進数表現に変換します。
次の擬似コードは、署名の計算方法を示しています。signature = HexEncode(HMAC(derived signing key, string to sign))
具体的には、以下のような計算式を設定します。
xc_task3_signatureフィールドの計算式 ----- // signature = HexEncode(HMAC(derived signing key, string to sign)) Lower ( HexEncode ( CryptAuthCode ( TextEncode ( xc_task2_stringToSign_substitute ; "utf-8" ; 3 ) ; "SHA256" ; xc_task3_kSigning ) ) )
サンプルファイルを確認
サンプルファイルの "Task 3" タブを確認します。
上記で説明した、署名がフィールドに設定されています。
詳細について、フィールドの計算式を確認して頂ければと思います。
タスク4に進みます。
タスク 4: HTTP リクエストに署名を追加する
タスク3で作成した署名をリクエストのヘッダーにセットし、リクエストします。
署名を計算したら、リクエストに追加します。署名をリクエストに追加するには、次の 2 種類のうちいずれかの方法で行います。
- HTTP ヘッダー (Authorization)
- クエリ文字列
Authorization ヘッダーとクエリ文字列の両方に署名情報を渡すことはできません。
Authorization ヘッダーに署名情報を追加する
署名情報を含めるには、Authorization という名前の HTTP ヘッダーに署名情報を追加できます。
ヘッダーの内容は、前のステップに従って署名を計算した後で作成されるため、Authorization ヘッダーは、署名付きヘッダーのリストに含まれていません。
ヘッダーは Authorization という名前ですが、実際には、署名情報が認証に使用されます。
擬似コード ----- Authorization: algorithm Credential=access key ID/credential scope, SignedHeaders=SignedHeaders, Signature=signature
最終的な Authorization ヘッダー ----- Authorization: AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/iam/aws4_request, SignedHeaders=content-type;host;x-amz-date, Signature=5d672d79c15b13162d9279b0855cfba6789a8edb4c82c400e06b5924a6f2b5d7
上記の擬似コードの通り、今までのタスクで作成した文字列を設定します。
各フィールドの計算式
学習用サンプルファイル内で定義している、各フィールドの計算式を記載します。
xc_task4_authorization_templateフィールドの計算式 ----- "{algorithm} Credential={accessKeyId}/{credentialScope}, SignedHeaders={signedHeaders}, Signature={signature}"
xc_task4_authorization_substituteフィールドの計算式 ----- Substitute ( xc_task4_authorization_template ; [ "{algorithm}" ; xc_algorithm ] ; [ "{accessKeyId}" ; accessKeyId ] ; [ "{credentialScope}" ; xc_task2_credentialScope ] ; [ "{signedHeaders}" ; xc_task1_signedHeaders ] ; [ "{signature}" ; xc_task3_signature ] ) /*substitute*/
サンプルファイルを確認
サンプルファイル "Task 4" タブを確認します。
上記の擬似コードの通り、Authorizationがフィールドに設定されています。
詳細については、フィールドの計算式を確認して頂ければと思います。
"Example" タブを確認
サンプルファイの "Example" タブに、curlコマンドを記載していますので、確認します。
GET
--request GET --header "Authorization: AWS4-HMAC-SHA256 Credential=AKIAQGIBVCFDVJVICR7H/20221213/ap-northeast-1/s3/aws4_request, SignedHeaders=host;x-amz-content-sha256;x-amz-date, Signature=9f079118b1048cd80d92e1faa42da026035b696b1ca56244abb49bbce661cbb8" --header "x-amz-content-sha256: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" --header "x-amz-date: 20221213T074244Z" --show-error --dump-header $dumpHeader --FM-return-container-variable --output "response.xml"
PUT
--request PUT --header "Authorization: AWS4-HMAC-SHA256 Credential=AKIAQGIBVCFDVJVICR7H/20221213/ap-northeast-1/s3/aws4_request, SignedHeaders=host;x-amz-content-sha256;x-amz-date, Signature=9f079118b1048cd80d92e1faa42da026035b696b1ca56244abb49bbce661cbb8" --header "x-amz-content-sha256: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" --header "x-amz-date: 20221213T074244Z" --show-error --dump-header $dumpHeader --data-binary @$requestBody
DELETE
--request DELETE --header "Authorization: AWS4-HMAC-SHA256 Credential=AKIAQGIBVCFDVJVICR7H/20221213/ap-northeast-1/s3/aws4_request, SignedHeaders=host;x-amz-content-sha256;x-amz-date, Signature=9f079118b1048cd80d92e1faa42da026035b696b1ca56244abb49bbce661cbb8" --header "x-amz-content-sha256: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" --header "x-amz-date: 20221213T074244Z" --show-error --dump-header $dumpHeader
APIのテスト
上記の "Example" タブに設定されているcurlコマンドを使い、テストします。
オブジェクトの一覧を取得
ポータルで、該当のレコードを選択し "Task 0" タブを開きます。
メソッドはGETを設定します。
URLを設定します。
"Test" タブを開き "Test Request" をクリックします。
オブジェクトの一覧が取得できました。
バケットが空のため、オブジェクトの情報は設定されていません。
<?xml version="1.0" encoding="UTF-8"?> <ListBucketResult xmlns="http://s3.amazonaws.com/doc/2006-03-01/"> <Name>demo-ahx9h-filemaker</Name> <Prefix></Prefix> <Marker></Marker> <MaxKeys>1000</MaxKeys> <IsTruncated>false</IsTruncated> </ListBucketResult>
オブジェクトのアップロード
ポータルで、該当のレコードを選択し "Task 0" タブを開きます。
メソッドはPUTを設定にします。
手元のファイルをオブジェクトフィールドにドラッグします。
ファイル名を、URLの末尾に追加します。
"Test" タブを開き "Test Request" をクリックします。
オブジェクトのアップロードが成功しました。
S3を確認すると、オブジェクトがアップロードされていることを確認できます。
オブジェクトの取得
ポータルで、該当のレコードを選択し "Task 0" タブを開きます。
メソッドはGETを設定します。
URLは、取得したいオブジェクトを設定しますので、先ほどアップロードしたURLを設定します。
"Test" タブを開き "Test Request" をクリックします。
オブジェクトの取得が成功しました。
EC2のインスタンスの一覧を取得
EC2のAPIを実行する際は、S3の時と同様に、EC2用のポリシーを作成し、ユーザーにEC2用のポリシーを割り当てる必要があります。
ポータルで、該当のレコードを選択し "Task 0" タブを開きます。
適宜、フィールドを設定します。
"Test" タブを開き "Test Request" をクリックします。
EC2のインスタンスの一覧の取得が成功しました。
<?xml version="1.0" encoding="UTF-8"?> <DescribeInstancesResponse xmlns="http://ec2.amazonaws.com/doc/2005-10-05/"> <reservationSet> <item> <reservationId>r-00c7a2210c6c5dd07</reservationId> <ownerId>013425250631</ownerId> <groupSet/> <instancesSet> <item> <instanceId>i-04288207d845a3bcd</instanceId> <imageId>ami-0149b2da6ceec4bb0</imageId> <instanceState> <code>80</code> <name>stopped</name> </instanceState> <dnsName/> <reason>User initiated (2022-11-29 07:44:06 GMT)</reason> <keyName>aws_us-east-1_keypair</keyName> </item> </instancesSet> </item> </reservationSet> </DescribeInstancesResponse>
EC2を確認すると、レスポンスと同じEC2のインスタンスの一覧を確認できます。
カスタムAppに組み込む
今までの説明で、署名の作成方法や、リクエスト方法が、概ね理解できたと思います。
しかし、今まで使ってきた学習用サンプルファイルは、あくまで学習・説明目的のサンプルファイルです。
タスク1〜タスク4でやってきた作業を、より簡易的に実行したり、カスタムAppに組み込むために、2つのカスタム関数を作成しました。
カスタム関数1 "aws_v4_sign_envJson"
aws_v4_sign_envJson ( _accessKeyId ; _secretAccessKey ; _region ; _service ; _method ; _host ; _canonicalURI ; _canonicalQueryString ; _hashedPayload ; _optionJsonObject ) ----- Let ( [ ~timestamp = GetAsTimestamp ( Int ( Get ( 現在の時刻 UTC ミリ秒 ) / 1000 ) ) ; ~date = GetAsDate ( ~timestamp ) ; ~dateY = Year ( ~date ) ; ~dateM = Right ( "0" & Month ( ~date ) ; 2 ) ; ~dateD = Right ( "0" & Day ( ~date ) ; 2 ) ; ~time = GetAsTime ( ~timestamp ) ; ~timeH = Right ( "0" & Hour ( ~time ) ; 2 ) ; ~timeM = Right ( "0" & Minute ( ~time ) ; 2 ) ; ~timeS = Right ( "0" & Seconds ( ~time ) ; 2 ) ; ~timestampFormat = ~dateY & ~dateM & ~dateD & "T" & ~timeH & ~timeM & ~timeS & "Z" ; ~dateFormat = ~dateY & ~dateM & ~dateD ; ~envJson = JSONSetElement ( _optionJsonObject ; [ "accessKeyId" ; _accessKeyId ; JSONString ] ; [ "secretAccessKey" ; _secretAccessKey ; JSONString ] ; [ "region" ; _region ; JSONString ] ; [ "service" ; _service ; JSONString ] ; [ "method" ; Upper ( _method ) ; JSONString ] ; [ "host" ; _host ; JSONString ] ; [ "canonicalURI" ; _canonicalURI ; JSONString ] ; [ "canonicalQueryString" ; _canonicalQueryString ; JSONString ] ; [ "hashedPayload" ; _hashedPayload ; JSONString ] ; [ "timestamp" ; ~timestampFormat ; JSONString ] ; [ "date" ; ~dateFormat ; JSONString ] ) ] ; ~envJson ) /*let*/
カスタム関数2 "aws_v4_sign_generate_authorization"
aws_v4_sign_generate_authorization ( _envJson ) ----- Let ( [ // env ~isJson = not Exact ( Left ( JSONFormatElements ( _envJson ) ; 1 ) ; "?" ) ; ~env = If ( ~isJson ; _envJson ; "{}" ) ; ~timestamp = JSONGetElement ( ~env ; "timestamp" ) ; ~date = JSONGetElement ( ~env ; "date" ) ; ~region = JSONGetElement ( ~env ; "region" ) ; ~service = JSONGetElement ( ~env ; "service" ) ; // CanonicalRequest ~method = Upper ( JSONGetElement ( ~env ; "method" ) ) ; ~canonicalURI = JSONGetElement ( ~env ; "canonicalURI" ) ; ~canonicalQueryString = JSONGetElement ( ~env ; "canonicalQueryString" ) ; ~header1 = "host:" & JSONGetElement ( ~env ; "host" ) ; ~header2 = "x-amz-content-sha256:" & JSONGetElement ( ~env ; "hashedPayload" ) ; ~header3 = "x-amz-date:" & ~timestamp ; ~canonicalHeaders = List ( ~header1 ; ~header2 ; ~header3 ) ; ~signedHeaders = "host;x-amz-content-sha256;x-amz-date" ; ~hashedPayload = JSONGetElement ( ~env ; "hashedPayload" ) ; ~canonicalRequest = ~method & "¶" & ~canonicalURI & "¶" & ~canonicalQueryString & "¶" & ~canonicalHeaders & "¶" & "¶" & ~signedHeaders & "¶" & ~hashedPayload ; // HashCanonicalRequest ~hashCanonicalRequest = Lower ( HexEncode ( CryptDigest ( TextEncode ( ~canonicalRequest ; "utf-8" ; 3 ) ; "SHA256" ) ) ) ; // CredentialScope ~credentialScope = ~date & "/" & ~region & "/" & ~service & "/aws4_request" ; // StringToSign ~stringToSign = List ( "AWS4-HMAC-SHA256" ; ~timestamp ; ~credentialScope ; ~hashCanonicalRequest ) ; // Signature ~kSecret = JSONGetElement ( ~env ; "secretAccessKey" ) ; ~kDate = CryptAuthCode ( ~date ; "SHA256" ; "AWS4" & ~kSecret ) ; ~kRegion = CryptAuthCode ( ~region ; "SHA256" ; ~kDate ) ; ~kService = CryptAuthCode ( ~service ; "SHA256" ; ~kRegion ) ; ~kSigning = CryptAuthCode ( "aws4_request" ; "SHA256" ; ~kService ) ; ~signature = Lower ( HexEncode ( CryptAuthCode ( TextEncode ( ~stringToSign ; "utf-8" ; 3 ) ; "SHA256" ; ~kSigning ) ) ) ; // Authorization ~authorizationTemplate = "{algorithm} Credential={accessKeyId}/{credentialScope}, SignedHeaders={signedHeaders}, Signature={signature}" ; ~authorization = Substitute ( ~authorizationTemplate ; [ "{algorithm}" ; "AWS4-HMAC-SHA256" ] ; [ "{accessKeyId}" ; JSONGetElement ( ~env ; "accessKeyId" ) ] ; [ "{credentialScope}" ; ~credentialScope ] ; [ "{signedHeaders}" ; ~signedHeaders ] ; [ "{signature}" ; ~signature ] ) /*substitute*/ ] ; ~authorization ) /*let*/
カスタム関数用ファイル "frudens_amazon_your_custom_app.fmp12" について
作成した2つのカスタム関数を利用して、AWSのAPIを実行するためのサンプルファイルを準備しました。
ダウンロードしたサンプルファイルの "frudens_amazon_your_custom_app.fmp12" を開きます。
オブジェクトの一覧を取得
下図の通り、各フィールドに値を設定し "Request" をクリックします。
オブジェクトのアップロード
下図の通り、各フィールドに値を設定し "Request" をクリックします。
オブジェクトの取得
下図の通り、各フィールドに値を設定し "Request" をクリックします。
"Request" ボタンに設定しているスクリプト
上記の "Request" ボタンには、下図の "T02_request_request_api" というスクリプトを設定しています。
# T02_request_request_api in file frudens_amazon_your_custom_app # -------------------------------------------------------------------------------- # --- create envJson # -------------------------------------------------------------------------------- 変数を設定 [ $accessKeyId ; 値: "AKIAQGIBVCFDVJVICR7H" ] 変数を設定 [ $secretAccessKey ; 値: "fyb0v0+g8jR2Z2BGEcrNfZppXwttzvRxUFf04Bci" ] 変数を設定 [ $region ; 値: "ap-northeast-1" ] 変数を設定 [ $service ; 値: "s3" ] 変数を設定 [ $method ; 値: Let ( [ ~long = FindValues ( T02_request::curlOptionTemplate ; "--request" ) ; ~short = FindValues ( T02_request::curlOptionTemplate ; "-X" ) ; ~line = Case ( not IsEmpty ( ~long ) ; ~long ; not IsEmpty ( ~short ) ; ~short ; "… ] # --- url 変数を設定 [ $url ; 値: T02_request::requestUrl ] 変数を設定 [ $urlJson ; 値: ParseUrl ( $url ) ] 変数を設定 [ $host ; 値: JSONGetElement ( $urlJson ; "host" ) ] 変数を設定 [ $canonicalURI ; 値: JSONGetElement ( $urlJson ; "path" ) ] 変数を設定 [ $fileName ; 値: JSONGetElement ( $urlJson ; "fileName" ) ] 変数を設定 [ $canonicalQueryString ; 値: JSONGetElement ( $urlJson ; "query" ) ] # --- body 変数を設定 [ $requestBody ; 値: T02_request::curlOptionBody ] 変数を設定 [ $hashedPayload ; 値: Lower ( HexEncode ( CryptDigest ( $requestBody ; "SHA256" ) ) ) ] # --- envJson 変数を設定 [ $envJson ; 値: aws_v4_sign_envJson ( $accessKeyId ; $secretAccessKey ; $region ; $service ; $method ; $host ; $canonicalURI ; $canonicalQueryString ; $hashedPayload ; "" ) ] # -------------------------------------------------------------------------------- # curl # -------------------------------------------------------------------------------- # --- fileName If [ IsEmpty ( $fileName ) ] 変数を設定 [ $fileName ; 値: "yourOutputFileName.txt" ] End If # --- option 変数を設定 [ $dumpHeader ] 変数を設定 [ $optionTemplate ; 値: T02_request::curlOptionTemplate ] 変数を設定 [ $timestamp ; 値: JSONGetElement ( $envJson ; "timestamp" ) ] 変数を設定 [ $authorization ; 値: aws_v4_sign_generate_authorization ( $envJson ) ] 変数を設定 [ $option ; 値: Substitute ( $optionTemplate ; [ "{authorization}" ; $authorization ] ; [ "{hashedPayload}" ; $hashedPayload ] ; [ "{timestamp}" ; $timestamp ] ; [ "{fileName}" ; $fileName ] ) /*substitute*/ ] # --- curl 変数を設定 [ $apiResponse ] URL から挿入 [ 選択 ; ダイアログあり: オフ ; ターゲット: $apiResponse ; $url ; SSL 証明書の検証 ; cURL オプション: $option ] # --- set response フィールド設定 [ T02_request::responseHeader ; $dumpHeader ] フィールド設定 [ T02_request::responseBody ; $apiResponse ] レコード/検索条件確定 [ ダイアログあり: オフ ] 現在のスクリプト終了 [ テキスト結果: ]
カスタムAppに組み込む方法
以下のステップで、カスタムAppで利用することができるはずです。
- カスタム関数1 "aws_v4_sign_envJson" をコピペする。
- カスタム関数2 "aws_v4_sign_generate_authorization" をコピペする。
- スクリプト "T02_request_request_api" をコピペする。
うまく動かない部分や、バグがありましたら、コメントやSNSなどで教えて頂ければと思います。
是非とも、チャレンジして頂ければと思います。
クエリ文字列に署名情報を追加する
今までのリクエスト方法で、AWSのAPIは利用できるのですが、S3では、クエリ文字列に署名情報を追加することで、該当のオブジェクトをブラウザで開くことができるようになります。
つまり、Webビューアや、WebビューアのHTML内のimgタグのソースに "事前署名されたURL(presigned URLs)" を指定することで、S3のオブジェクトを表示できるようになります。
詳細については、以下のドキュメントをご確認いただければと思います。
クエリ文字列に署名情報を追加する
リクエストを作成して、署名情報を含むすべてのリクエスト値をクエリ文字列に渡すことができます。
これは、AWS に対するコールを正常に行うために必要なすべてのものが含まれた単一の URL を生成するため、事前署名された URL と呼ばれる場合があります。
これは、Amazon S3 でよく使用されます。
詳細については、Amazon Simple Storage Service API リファレンスの「Authenticating Requests: Using Query Parameters (AWS Signature Version 4)」を参照してください。
海外の開発者の方が、上記の "事前署名されたURL(presigned URLs)" を利用したサンプルファイルをブログで公開してくださっています。
S3 as a FileMaker Container Alternative
参考リンク
今回の記事の情報や、S3の活用方法は、海外の開発者の間では、かなり前から知られています。
以下のリンク先にも目を通して頂ければと思います。
- FileMakerでS3にREST APIでアクセスする (AWS Signature Version 4)
- Generate AWS Signature Version 4 in FileMaker | Goya
- quarfie/s3presignedUrl_FMFunction: FileMaker custom function to generate a presigned URL to upload/download/delete an object in S3
- Signed URLs for Amazon S3 in Filemaker
- Amazon S3 REST API with curl - Łukasz Adamczak
次回
時間がありましたら、以下のような記事を書きたいと思っています。
- Googleのサービスアカウントを活用したAPIの利用方法
- LINE WORKSのVer2.0でのAPIの利用方法
- XMLのパースをしようと思ったが途中で諦めた話
お知らせ
Claris Engage Japan 2022について
2022年10月26日〜28日に開催された、Claris Engage Japan 2022 で、2つセッションを担当させて頂きました。
登壇して頂いた今井先生はもちろん、Claris社のスタッフの皆様、関係者の皆様、ご視聴頂いた皆様、誠にありがとうございました。
先日、YouTubeでセッション動画が公開されましたので、ご興味のある方は、是非ともご覧頂ければと思います。
https://www.youtube.com/playlist?list=PLTTmwgF5akKI7jJHp0Njm_gsVjzliIE1g
歯科クリニックにおけるスケジュール管理や予約業務の効率化 〜SMSによるリマインド通知の活用やGoogleカレンダーとの連携〜
FullCalendarの基本やカスタマイズ方法を例に、Claris FileMaker とJavaScriptを連携する方法
1ヶ月間お試しの開発について
いくつかお問合せを頂いておりますが、バタバタしており、すぐに対応できないため、お待ち頂いている状況です。
対応まで少し時間がかかっても良いという場合は、引き続きフォームよりお問合せ頂けますでしょうか。
最後に
誤字脱字、間違っている点などありましたら、コメントやSNSでお知らせ頂ければと思います。
ありがとうございました!