GCP のAPIをcurlコマンドで実行するときのメモ


概要

GCPのAPIを実行するにはgcloud コマンドやSDKを使うやり方もありますがcurlコマンドを使ったやり方について調べたのでそのメモです。

GCPで作成したサービスアカウントを使います。方法は2つあります。
1) 事前にOAuth 2.0の認証を行い、トークンをもらってからAPIにアクセスする方法
2) 事前の認証無しに、認証情報を署名してトークンとして使うことでAPIのリクエストする方法
後者の方が事前の認証の手間がなく今回はこちらを利用します。

OSはLinuxの CentOS7を使いました。
この記事ではサービスアカウントの作成については省きます。また後に登場するJWT ですが詳細については https://jwt.io/introduction/ を参照してください。

(参考:HTTP/RESTを使った呼び出し
https://developers.google.com/identity/protocols/OAuth2ServiceAccount#authorizingrequests :
直接APIを呼び出しする方法については
https://developers.google.com/identity/protocols/OAuth2ServiceAccount#jwt-auth)

Google APIs GitHub repositoryに利用したいAPIの定義ファイルがない場合は利用できないようです。
この記事ではloggingのAPIを呼び出します。loggingのAPIについては
https://github.com/googleapis/googleapis/blob/master/google/logging/logging.yaml
で確認することができました。

  1. 認証に必要な情報をまとめる
  2. 1.からJSON Web Token (以後 JWT )を作成する
  3. JWTを使ったGCP APIの呼び出し

のようにGCPのAPIをcurlにて呼び出ししたいと思います。

1. 認証情報(JWT)の作成

項目 入力値
iss service accountの認証ファイル(JSON)内のclient_emailの値
sub service accountの認証ファイル(JSON)内のclient_emailの値
aud https://SERVICE_NAME/API_NAME」 のように作成します。今回の場合、https://github.com/googleapis/googleapis/blob/master/google/logging/logging.yaml 内にSERVICE_NAME とAPI_NAME があります。
iat アサーションが発行された時刻。1970年1月1日00:00:00 UTCからの秒数で指定されます。
exp アサーションの有効期限。1970年1月1日00:00:00 UTCからの秒数として指定されます。この値は、発行された時刻の最大1時間後です。 作成したトークンには有効期限があります。期限は最大1時間。
kid サービスアカウントの認証情報JSON 内のprivate_key_id

参考:https://developers.google.com/identity/protocols/OAuth2ServiceAccount

2. 1.からJSON Web Token (以後 JWT )を作成する

Python 2.7.5を使います。pip を使ってインストールします。

python get-pip.py
pip install pyjwt
pip install cryptography

pythonのCode

https://developers.google.com/identity/protocols/OAuth2ServiceAccount#jwt-auth のコードを元にpythonのコードを完成させます。signed_jwtが出力されるのでそれをコピーします。

# -*- coding: utf-8 -*-
import jwt
import time
iat = time.time()
exp = iat + 3600
payload = {'iss': 'stackdriver-logging-read@xxxxx-xxxxx-xxxx..iam.gserviceaccount.com',
     'sub': 'stackdriver-logging-read@xxxxx-xxxxx-xxxx.iam.gserviceaccount.com',
     'aud': 'https://logging.googleapis.com/google.logging.v2.LoggingServiceV2',
     'iat': iat,
     'exp': exp}
additional_headers = {'kid': 'XXXXXXXXXXXXX'}
signed_jwt = jwt.encode(payload, '-----BEGIN PRIVATE KEY-----\nXXXXXXXXXXXXXXXXXXXXXXXXXXX\n-----END PRIVATE KEY-----\n', headers=additional_headers,algorithm='RS256')
print signed_jwt

3. JWTを使ったGCP APIの呼び出し

curlに指定するURIはGCPのマニュアルページ調べるか、gcloud のコマンドに --log-http をつけて実行すると下のようにわかります。

gcloud logging read 'timestamp<="2019-07-30T23:59:59Z"' --format="json" --log-http 
...
==== request start ====
uri: https://logging.googleapis.com/v2/entries:list?alt=json
method: POST
...

curlコマンドは以下です。 python で作ったsigned_jwt とGCPのproject-name を適宜入れてください。

curl -X POST \
  'https://logging.googleapis.com/v2/entries:list?alt=json' \
  -H 'Authorization: Bearer <signed_jwt>' \
  -H 'Content-Type: application/json' \
  -H 'Host: logging.googleapis.com' \
  -d '{ "filter": '\''timestamp<="2019-07-30T23:59:59Z"'\'', "orderBy": "timestamp desc", "pageSize": 1000, "resourceNames": ["projects/<project-name>"]}'

1. 認証情報(JWT)の作成のexp の項にも書きましたがトークンには有効期限があります。失効した場合はもう一度signed_jwt を作成する必要があります。

出力は以下のような感じです。gcloud コマンド実行時と変わりません。


{
  "entries": [
    {
      "insertId": "..........................Aj_..",
      "jsonPayload": {
        "client.received.end.timestamp": "1564465946852",
        "request.verb": "GET",
...

補足

JWTをpythonのツールで生成しましたが、Linuxのコマンドで生成する方法も調べたのでメモ

JWTは
- Header
- Payload
- Signature
で構成されていて、

base64UrlEncode(header) + "." + base64UrlEncode(payload) + "." + Signature

Signature は
base64UrlEncode(header) + "." + base64UrlEncode(payload)
に秘密鍵で署名をしてbase64UrlEncodeしたものになります。

秘密鍵はGCPのサービスアカウントの認証情報(json)にあります。
-----BEGIN PRIVATE KEY-----から始まる文字列です。これをコピーしてテキストファイル(secret_key)を作ります。


header='{
  "alg": "RS256",
  "typ": "JWT",
  "kid": "0123456789abcdef01234567"
}'
payload='{
  "iss": "[email protected]",
  "iat": 1565869581.475549,
  "sub": "[email protected]",
  "exp": 1565873181.475549,
  "aud": "https://logging.googleapis.com/google.logging.v2.LoggingServiceV2"
}'
base64UrlEncHeader=`echo -n ${header}|tr -d " \n"|openssl base64  |tr -- '+/=' '-_ '|tr -d " \n"`
base64UrlEncPayload=`echo -n ${payload}|tr -d " \n"|openssl base64  |tr -- '+/=' '-_ '|tr -d " \n"`
signature=`echo -n "${base64UrlEncHeader}.${base64UrlEncPayload}" | openssl dgst -sha256 -sign secret_key -binary |openssl base64  |tr -- '+/=' '-_ '|tr -d " \n"`
echo "${base64UrlEncHeader}.${base64UrlEncPayload}.${signature}"

[参考]
https://jwt.io/introduction/
https://qiita.com/kunichiko/items/3c0b1a2915e9dacbd4c1
https://crypto.stackexchange.com/posts/68400/revisions

以上でした。
間違いのご指摘や質問大歓迎です。