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](https://developers.google.com/identity/protocols/OAuth2ServiceAccount)


# 2. 1.からJSON Web Token (以後 JWT )を作成する
Python 2.7.5を使います。pip を使ってインストールします。

```shell-session
python get-pip.py
pip install pyjwt
pip install cryptography
```

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

```python
# -*- 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の[マニュアルページ](https://cloud.google.com/logging/docs/reference/v2/rest/v2/entries/list)調べるか、gcloud のコマンドに --log-http をつけて実行すると下のようにわかります。

```shell
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 を適宜入れてください。

```shell
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 コマンド実行時と変わりません。

```shell

{
  "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)を作ります。

```shell

header='{
  "alg": "RS256",
  "typ": "JWT",
  "kid": "0123456789abcdef01234567"
}'
payload='{
  "iss": "stackdriver-logging-read@project-name.iam.gserviceaccount.com",
  "iat": 1565869581.475549,
  "sub": "stackdriver-logging-read@project-name.iam.gserviceaccount.com",
  "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

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