認証ファイルを発行せずに、Google Compute Engineからgoogleカレンダーへアクセスする


はじめに

googleカレンダーなどのgoogleサービスにアクセスするときは、サービスアカウントのjsonやAPIキーなどの認証ファイルを準備するよう公式のリファレンスで示しています。
しかし、GCPの計算リソースとサービスアカウントとアクセススコープをうまく利用することで、認証ファイルを使うことなくgoogleカレンダーなどのgoogleサービスにアクセスできます。

今回は、認証ファイルを発行せずにGoogle Compute Engine(以下GCE)からgoogleカレンダーへアクセスする手順を示します。

手順

Google Cloud SDKのインストール

以下のリンクを参照してください。
https://cloud.google.com/sdk/install
https://cloud.google.com/sdk/docs/initializing

Google Calendar APIを有効にする

APIを有効にします。
Cloud SDKの場合は以下の通りです。

cmd
gcloud services enable calendar-json.googleapis.com

(非公開カレンダーの場合)カレンダーにアクセスするためのサービスアカウントの準備

googleカレンダーはメールアドレスでアクセス権限を制御していますが、このメールアドレスにサービスアカウントを利用します。
公開されているカレンダーを閲覧したい場合は、この作業は不要です。

サービスアカウントの発行

Cloud SDKの場合は以下の通りです。

cmd
gcloud iam service-accounts create ${SVCACCOUNT_NAME} \
  --display-name ${SVCACCOUNT_NAME}

${SVCACCOUNT_NAME}は適宜設定してください。

目的のカレンダーにアクセス権限を設定する

先に発行したサービスアカウントがアクセスできるように権限を設定します。

今回はカレンダーの情報を取得したいだけなので閲覧権限です。

サービスアカウントは${SVCACCOUNT_NAME}@${PROJECT_ID}.iam.gserviceaccount.com というメールアドレス形式で得られるため、アクセスしたいカレンダーの共有設定で登録します。

googleカレンダーの [設定と共有] -> [特定のユーザーとの共有] から [ユーザーの追加] を実行します。

GCEインスタンスの設定

インスタンスは既に存在するものとします。
インスタンスを作成する段階で設定したい場合はドキュメントを参照してください。
https://cloud.google.com/sdk/gcloud/reference/compute/instances/create

スコープの設定

今回の罠ポイント。

GCEインスタンスに対してgoogleカレンダーへのアクセススコープを付与する必要があります。
compute instances set-service-account において、 --scope オプションに「インスタンスに既に付与されているスコープ」「カレンダーに対するスコープ」をカンマ区切りで記述します。

カレンダーに対するスコープは、今回についてはカレンダーへの読み取り権限があれば良いので https://www.googleapis.com/auth/calendar.readonly になります。
必要なスコープは以下のURLを参照してください。
https://developers.google.com/calendar/auth

https://www.googleapis.com/auth/cloud-platform が元々付与されていたインスタンスの場合は以下のようになります。

cmd
# インスタンスが停止しているタイミングで実行

gcloud compute instances set-service-account \
  projects/${PROJECT_ID}/zones/${ZONE}/instances/${INSTANCE_NAME} \
  --scopes https://www.googleapis.com/auth/cloud-platform,https://www.googleapis.com/auth/calendar.readonly

現在のスコープの調べ方

インスタンスに既に付与されているスコープがわからない場合は、 compute instances describe で調べます。

cmd
gcloud compute instances describe ${INSTANCE_NAME}

インスタンスの情報が出力されるので、 scopes を探します。

output
......

serviceAccounts:
- email: [email protected]
  scopes:
  - https://www.googleapis.com/auth/devstorage.read_only
  - https://www.googleapis.com/auth/logging.write
  - https://www.googleapis.com/auth/monitoring.write
  - https://www.googleapis.com/auth/servicecontrol
  - https://www.googleapis.com/auth/service.management.readonly
  - https://www.googleapis.com/auth/trace.append
......

(非公開カレンダーの場合)サービスアカウントの設定

インスタンスにサービスアカウントを紐付けます。
公開されているカレンダーを閲覧したい場合は、この作業は不要です。

cmd
# インスタンスが停止しているタイミングで実行

gcloud compute instances set-service-account \
  projects/${PROJECT_ID}/zones/${ZONE}/instances/${INSTANCE_NAME} \
  --service-account ${SVCACCOUNT_NAME}@${PROJECT_ID}.iam.gserviceaccount.com

pythonスクリプトを実行

以下は、設定したGCEインスタンス上で実行します。

ライブラリのインストール

cmd
pip3 install google-api-python-client pprint

pythonスクリプトの作成

カレンダーを読み込むためのスクリプトを書いていきます。

fetch_calendar_events.py
import googleapiclient.discovery
import google.auth
import datetime
import sys
from pprint import pprint

def create_credentials():
    credentials, _ = google.auth.default()
    return credentials


def create_service(credentials):
    return googleapiclient.discovery.build('calendar', 'v3', credentials=credentials)


def fetch_calendar_events(service, calendar_id):
    now = datetime.datetime.utcnow().isoformat() + 'Z' # 'Z' indicates UTC time
    ret = service.events().list(calendarId=calendar_id, timeMin=now,
                                        maxResults=10, singleEvents=True,
                                        orderBy='startTime').execute()
    return ret


def main(calendar_id):
    credentials = create_credentials()
    service = create_service(credentials)
    events = fetch_calendar_events(service, calendar_id)
    pprint(events)


if __name__ == '__main__':
    args = sys.argv
    calendar_id = args[1]
    main(calendar_id)

実行

実行します。
引数にカレンダーIDを与えてあげてください。

cmd
# python3 fetch_calendar_events.py <calendar_id>

# 公開カレンダーの例
python3 fetch_calendar_events.py [email protected]

上記コマンドを実行すると、ほたてカレンダーの情報が表示されます。

output
{'accessRole': 'reader',
 'defaultReminders': [],
 'description': 'ほたてちゃん親衛隊から\n'
                'ほたてちゃんを襲いたいチェイサーから\n'
                'ほたてちゃんに喰われたい女装子から\n'
                'ほたてちゃんご本人まで\n'
                '\n'
                '※非公式カレンダーです。\n'
                '※ご利用は自己責任でお願いします。\n'
                '※バグのせいで欠勤になっても責任は負いかねます。\n'
                '※バグ報告や機能改善提案は @hkak03key まで',
 ...
}

補足

サービスアカウント

サービスアカウントとは、GCP上における仮想のユーザとみなすことができます。
AWSではIAMロールに相当するものと考えられます。
(ただし、AWSにはロールにポリシーを直接アタッチするのに対し、GCPでは「役割」に権限を定義し、サービスアカウントに対してアタッチします。)

GCPでは、実際のユーザをプロジェクトに追加し、権限を与えることができます。
一方で、何らかのシステムにおいては実際のユーザは必要ない場合がほとんどです。
このような場合に、サービスアカウントを利用します。
例えば、今回のようにサービスアカウントを紐付けることで、権限を制御することができます。

一方で、サービスアカウントは「googleサービスへのアクセスユーザ」として利用できます。
つまり、非公開のgoogleカレンダーに対して共有ユーザとしてサービスアカウントを指定すると、そのカレンダーに対してアクセスが可能になります。

スコープ

スコープについての十分な理解は無いので、今回この記事を書くにあたって理解したことを書くと、

最終的なアクセス権限 = 
  (
    "https://www.googleapis.com/auth/cloud-platform" 
    AND
    {IAMで定義されたservice accountが持つgcpに対する権限} 
  ) 
  OR 
  {gcp以外のgoogleサービスへのスコープ}

となっていました。

googleカレンダーはgcp以外のgoogleサービスへのスコープなので、別途与えなければいけないようです。

おわりに

認証ファイルを使うことなくGCEからgoogleカレンダーへアクセスする手順を示しました。

AWSのiamロールに慣れきっていると、ファイルレスで認証できたほうが幸せ感ありますよね。
ただ、ぽちぽちで設定してると見通しが悪くなるのでTerraform等でIaCをやりたくなります。

ちなみに、Google Cloud Functions(以下GCF)ではスコープという概念がないようです(2020-02現在)。
「これは詰んだ...」と思いながらダメ元でGCFで試したら動いた
GCFではscope全許可のようですね...。それはそれでいいのだろうか?と思いつつ。。

それでは、本来の目的である「ほたてbotのpython移植ミッション」を進めます。
GASはもう書きたくない!!!!!!

それでは、また次回!

余談

スコープの件でめっちゃ詰んでたんですが、GCPUGのSlackに質問投げたら1時間で解決しました。

で、回答してくださった@apstndbさんの マギレコ・オタク イケメンコメントはこちら。

というわけで、みんなもGCPUG Slackに入ろう!!!!!!!
https://gcpug.slack.com/