エヌデーデー関口です。

Google Cloud のサービスから Google Workspace(GWS) の Google ドライブにファイルを作りたい場合、サービス アカウントを利用するというパターンがあります。

また、サービス アカウントを利用する場合にも、サービスカウントに対してファイル作成先フォルダを直接共有する方法と、ドメイン全体の委任を利用してアクセスさせる方法が考えられます。

しかし、前者は GWS 組織が外部共有を許可していない場合には利用ができません。そこで、今回は後者の方法で Python プログラムから操作する方法をご紹介します。Webを検索して良くでてくる例ではサービス アカウントのキーファイルを作成してアクセスする方法が紹介されていますが、今回の方法ではキーファイルは作成しないので、キーファイル漏洩のリスクを軽減することができます。

Google Cloud で IAM と API を設定する

IAM 設定

  1. Google Cloud でサービス アカウントを作成し、サービス アカウントの OAuth 2 クライアント ID を控えておきます。クライアント ID はすべて数字で構成されたものです(下図)
  2. Google Cloud のプロジェクトで、今回作成するプログラムを実行するユーザー(または上記のものとのは別のサービス アカウント)に「サービス アカウント トークン作成者(roles/iam.serviceAccountTokenCreator)」ロールを付与します
  3. (オプション) Cloud Shell やローカルから、ログインユーザーとしてプログラムを実行する場合は「Service Usage ユーザー(roles/serviceusage.serviceUsageConsumer)」ロールを付与してください

Google Drive API の有効化

Google Cloud 管理コンソールの検索から Google Drive API を探して有効化してください。

GWS にドメイン全体の委任を設定する

GWS でサービス アカウントに対してドメイン全体の委任を設定していきます。

  1. GWS の Google 管理コンソールに特権管理者としてログインして [セキュリティ] > [アクセスとデータ管理] > [API の制御] > [ドメイン全体の委任を管理] に移動します
  2. [新しく追加] をクリックします
  3. サービス アカウントの OAuth2 クライアント ID を入力します
  4. [OAuth のスコープ] で、アプリケーションがアクセスできるスコープを追加します。今回は Google Drive API を利用するので、https://www.googleapis.com/auth/drive とします。スコープは運用に合わせて細かく制限してください。

プログラム解説と実行方法

サンプル プログラム

ここまでサービス アカウントは作成していません。ここから Python プログラムを解説します。

import os
import google.auth
from google.auth import impersonated_credentials
from googleapiclient.discovery import build
from googleapiclient.http import MediaFileUpload
from googleapiclient.errors import HttpError


# ドメイン全体の委任を管理に設定したサービス アカウントのメールアドレス
# 実行ユーザーによってなりすまされる側
IMPERSONATED_SA = "<サービス アカウント>@<プロジェクトID>.iam.gserviceaccount.com"

# GWS上でサービス アカウントが委任されるユーザーのメールアドレス
DELEGATED_USER_EMAIL = "delegeted_user@exapmle.com"

# Google Drive API にアクセスするためのスコープ(固定)
SCOPES = ["https://www.googleapis.com/auth/drive"]

# GWS上のフォルダID(URLの folders/ より後ろの文字)
FOLDER_ID = "1Ue2Vxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

# 実行しているユーザー(または実行中サービス アカウント)の認証情報を取得する
creds, _ = google.auth.default()

# 実行ユーザーとして、サービス アカウントになりすました認証情報を取得する
# 認証情報には、サービス アカウントがGWS上のユーザーに委任されている情報も付与する
delegated_creds = impersonated_credentials.Credentials(
    source_credentials=creds,
    target_principal=IMPERSONATED_SA,
    subject=DELEGATED_USER_EMAIL,
    target_scopes=SCOPES,
)

# Google Drive API サービスを生成する
service = build("drive", "v3", credentials=delegated_creds)


# ファイルアップロード関数
def upload_file(service, file_path, file_name, mime_type, parent_folder_id=None):
    """
    ローカルファイルからGWSにファイルをアップロードする

    Args:
        service (Any): GWSサービス
        file_path (string): ローカルファイルのパス
        file_name (string): GWS上でのファイル名
        mime_type (string): Mimeタイプ
        parent_folder_id (string): GWS上のアップロード先のフォルダID

    Returns:
        string: 作成したファイルのID

    """
    file_metadata = {'name': file_name}
    if parent_folder_id:
        file_metadata['parents'] = [parent_folder_id]

    media = MediaFileUpload(file_path, mimetype=mime_type, resumable=True)
    try:
        file = service.files().create(
            body=file_metadata, media_body=media, fields='id').execute()
        print(f"File ID: {file.get('id')}")
        return file.get('id')
    except HttpError as error:
        print(f"An error occurred: {error}")
        return None


# ローカルにファイルを作成して、アップロード関数を呼び出す
with open("sample.txt", "w") as f:
    f.write("This is a sample text file.")
upload_file(service, 'sample.txt', 'Sample Document.txt', 'text/plain', FOLDER_ID)
os.remove("sample.txt")

登場人物が複雑になっているので、図で説明します。

  1. プログラムの23行目で下図の実行ユーザーにあたる USER A の認証情報を取得します。
  2. 既に IAM ロールでサービス アカウントのトークンを取得することは許可されているので、図の中心のサービスアカウントとして振る舞うことができます
  3. 27行目がこのプログラムの肝となる部分です。USER A はサービスアカウントとしての認証を取得しているのですが、その際サービス アカウントには GWS のユーザーである USER B の権限をつかってよいということを示しています
  4. その後、取得した認証で Google ドライブ上にファイルを作成する処理をします(60行目)

繰り返しになりますが、27行目で使用している google.auth.impersonated_credentials.Credentials クラスを利用するときに、subject 引数を指定すると OAuth 2.0 認証時の JWT ペイロードの sub フィールドになりすまし先のユーザーを設定することができるというのが重要なポイントです。

なお、Web検索でさまざまな情報を探したのですが、これを実現しているサンプルソースを見つけることができませんでした。Gemini にも質問して、あるタイミングで Gemini が誤ったコードを出力したときに、偶然 subject 引数に出会うことができました!

※権限借用もなりすましも英語で impersonate (なりすまし)なのですが、なりすますタイミングを区別するために言葉をかえて使っています。

実行してみる

上記のプログラムが main.py に保存されている想定で、ローカルまたは、Cloud Shell で実行する場合は次のように行ってください。

# プログラム実行時にユーザー認証を与える設定(ブラウザでのユーザー認証を行います)
# これを実施しないと、サンプル プログラムの23行目で認証情報が取得できません
gcloud auth application-default login

# 仮に権限借用とは別の実行用のサービス アカウントで実行したい場合は次のようにコマンドを実行します
#gcloud auth application-default login --impersonate-service-account=<サービス アカウント>

# 実行するときのプロジェクトを指定しておきます(特にローカル実行の場合)
gcloud auth application-default set-quota-project <プロジェクトID>

# プログラムを実行します
python main.py

まとめ

上記のソースや図で示したとおり、GWS のドメイン全体の委任は、ユーザーの同意を回避してしまう強力な権限を設定次第で与えてしまうので、設定する場合は慎重に検討してください。

その上で、ドメイン全体の委任を利用する場合は、公式ドキュメントの「サービス アカウント キーを管理するためのベスト プラクティス」にもあるとおり、サービス アカウント キーを作成せず、今回紹介した処理で回避しましょう。

Visited 1 times, 93 visit(s) today