Google Workspaceの共有ドライブは、外部パートナーとの共同作業に不可欠なツールです。しかし、標準機能では外部ユーザーのアクセスに有効期限を設定できないため、プロジェクト終了後の権限削除漏れが情報漏洩リスクにつながることがあります。
この記事を読んだほうが良い人
- 100名規模の企業でGoogle Workspaceを管理している情シス担当者
- 外部パートナーや協力会社と共有ドライブでプロジェクトを進める機会が多い
- プロジェクト終了後の共有ドライブの権限削除漏れによる情報漏洩リスクに懸念がある
- 手動での権限管理に限界を感じ、自動化を検討したい
なぜ「時限式アクセス」が必要なのか?共有ドライブ運用の課題
多くの企業で、外部の協力会社やフリーランスとのプロジェクトが増加しています。Google Workspaceの共有ドライブは、こうした外部連携において、ファイルの共有、共同編集、一元管理を容易にする強力なツールです。しかし、その利便性の裏には、情シス担当者が対応に追われるセキュリティ上の課題が潜んでいます。
手動での権限削除の限界とリスク
共有ドライブに外部ユーザーを追加するのは簡単ですが、プロジェクトが終了した後にそのユーザーのアクセス権を削除するのは、意外と手間がかかります。
- 削除漏れによる情報漏洩リスク: プロジェクトが多数同時並行で進行している場合、どの共有ドライブにどの外部ユーザーがいて、いつアクセスを終了すべきか、すべてを正確に把握し、手動で削除し続けるのは現実的ではありません。削除漏れが発生すると、プロジェクト終了後も外部ユーザーが機密情報にアクセスし続ける状態が放置され、情報漏洩のリスクが高まります。
- 管理コストの増加: 情シス担当者は、プロジェクト終了のたびに共有ドライブの権限を確認し、手動で削除する作業に追われます。これは本来、より戦略的な業務に割かれるべき時間を奪う、非効率な作業です。
Google Workspaceの現状:標準機能では期限付きアクセスがない
Google Workspaceの共有ドライブには、残念ながら外部ユーザーに対して「この日までアクセスを許可する」といった、有効期限付きのアクセス権を標準で設定する機能は提供されていません。Google Workspace 管理者ヘルプにも、共有ドライブの共有設定に関する記述はありますが、特定のユーザーに対する時限的なアクセス設定については言及されていません。
このため、情シス担当者は手動での管理に頼るか、何らかの自動化を検討する必要があります。本記事では、この課題を解決するための「時限式外部アクセス」の自動化設計を提案します。
GASによる自動剥奪の全体像:予防的アプローチの設計
「時限式外部アクセス」とは、プロジェクト開始時に外部ユーザーのアクセス期限をあらかじめ設定し、その期限が来た際にGoogle Apps Script (GAS) が自動的に権限を剥奪する仕組みです。これにより、手動管理による削除漏れを防ぎ、情シスの負担を軽減しながらセキュリティを向上させることができます。
構成要素
この自動化を実現するために、以下の3つの要素を組み合わせます。
- 期限管理用Googleスプレッドシート: どの共有ドライブの、どの外部ユーザーのアクセスを、いつまで許可するかを管理するマスターデータとして機能します。
- Google Apps Script (GAS): スプレッドシートの情報を読み込み、Google Drive APIを介して共有ドライブの権限を操作するスクリプトです。
- Google Drive API (Advanced Service): GASから共有ドライブの権限をプログラムで操作するために利用します。
ステップ1:期限管理スプレッドシートの設計
まず、外部ユーザーのアクセス期限を管理するためのGoogleスプレッドシートを作成します。このスプレッドシートは、GASスクリプトが読み込む「指示書」となります。
必要な情報
以下の情報を列として用意します。
- A列: プロジェクト名: どのプロジェクトに関連するアクセスかを示す(例: 「〇〇社_新サービス開発」)
- B列: 共有ドライブID: 権限を操作する共有ドライブの一意なID。URLから取得できます(
drive.google.com/drive/folders/の後に続く文字列) - C列: 外部メンバーメール: アクセス権を剥奪する外部ユーザーのメールアドレス
- D列: アクセス終了日: この日付になったら権限を剥奪する(例:
YYYY/MM/DD形式) - E列: 処理ステータス: GASが処理を行った結果を記録する(例:
未処理,処理済み,エラー) - F列: 最終処理日時: GASが最後にこの行を処理した日時
スプレッドシートの例
| プロジェクト名 | 共有ドライブID | 外部メンバーメール | アクセス終了日 | 処理ステータス | 最終処理日時 |
|---|---|---|---|---|---|
| 〇〇社_新サービス開発 | xxxxxxxxxxxxxxxxxxxxx |
partner1@example.com |
2024/07/31 |
未処理 |
|
| △△プロジェクト | yyyyyyyyyyyyyyyyyyyyy |
contractor@example.jp |
2024/08/15 |
未処理 |
|
| 〇〇社_新サービス開発 | xxxxxxxxxxxxxxxxxxxxx |
partner2@example.com |
2024/07/31 |
未処理 |
ステップ2:GASスクリプトの実装
次に、このスプレッドシートを読み込み、共有ドライブの権限を自動で剥奪するGASスクリプトを作成します。
事前準備
- 新しいGoogle Apps Scriptプロジェクトの作成: Google Driveから「新規」>「その他」>「Google Apps Script」を選択して作成します。
- Google Drive API (Advanced Service) の有効化:
- GASエディタの左側のメニューから「プロジェクトの設定」(歯車アイコン)をクリックします。
- 「Google Cloud Platform プロジェクト」の項目で、表示されているプロジェクト番号をクリックしてGoogle Cloud Consoleを開きます。
- Google Cloud Consoleで、左側のナビゲーションメニューから「APIとサービス」>「ライブラリ」を選択します。
- 検索バーで「Google Drive API」と入力し、検索結果から「Google Drive API」を選択して「有効にする」ボタンをクリックします。
- GASエディタに戻り、左側のメニューから「サービス」(+アイコン)をクリックします。
- 「Drive」を選択し、「追加」をクリックします。これにより、GASからDrive APIを利用できるようになります。
スクリプトの処理フロー
- スプレッドシートからデータを読み込む: 期限管理スプレッドシートの情報をすべて取得します。
- 今日が終了日のエントリを特定: 読み込んだデータの中から、
アクセス終了日が今日の日付以前であり、かつ処理ステータスが未処理のエントリをフィルタリングします。 - 共有ドライブのメンバーリストを取得: 該当する共有ドライブの現在のメンバー(権限)リストをDrive APIを使って取得します。
- 外部メンバーかつ終了日を過ぎたユーザーを特定: 取得したメンバーリストとスプレッドシートのエントリを照合し、削除対象の外部ユーザーと、そのユーザーの
permissionIdを特定します。 - 権限を削除: Drive APIの
Permissions.deleteメソッドを使って、対象ユーザーの権限を共有ドライブから削除します。 - スプレッドシートのステータスを更新: 処理が完了したエントリの
処理ステータスを処理済みに、最終処理日時を現在時刻に更新します。エラーが発生した場合はエラーと記録します。
GASコード例
// 定数
const SPREADSHEET_ID = 'あなたの期限管理スプレッドシートID'; // スプレッドシートURLの /spreadsheets/d/{ID}/edit の {ID} 部分
const SHEET_NAME = 'Sheet1'; // シート名を適宜変更
// スプレッドシートの列インデックス(0から始まる)
const COL_PROJECT_NAME = 0;
const COL_SHARED_DRIVE_ID = 1;
const COL_EXTERNAL_EMAIL = 2;
const COL_EXPIRATION_DATE = 3;
const COL_STATUS = 4;
const COL_PROCESSED_AT = 5;
/**
* スプレッドシートにカスタムメニューを追加します。
*/
function onOpen() {
const ui = SpreadsheetApp.getUi();
ui.createMenu('自動権限剥奪')
.addItem('アクセス期限をチェックして剥奪', 'checkAndRevokeAccess')
.addToUi();
}
/**
* 共有ドライブのアクセス期限をチェックし、期限切れの外部ユーザーの権限を剥奪します。
*/
function checkAndRevokeAccess() {
const ss = SpreadsheetApp.openById(SPREADSHEET_ID);
const sheet = ss.getSheetByName(SHEET_NAME);
if (!sheet) {
Logger.log('シートが見つかりません: ' + SHEET_NAME);
SpreadsheetApp.getUi().alert('エラー', 'シート "' + SHEET_NAME + '" が見つかりません。', SpreadsheetApp.getUi().ButtonSet.OK);
return;
}
const dataRange = sheet.getDataRange();
const values = dataRange.getValues();
const today = new Date();
today.setHours(0, 0, 0, 0); // 日付のみ比較するため時間をリセット
// ヘッダー行をスキップし、データ行を処理
for (let i = 1; i < values.length; i++) {
const row = values[i];
const projectName = row[COL_PROJECT_NAME];
const sharedDriveId = row[COL_SHARED_DRIVE_ID];
const externalEmail = row[COL_EXTERNAL_EMAIL];
const expirationDate = new Date(row[COL_EXPIRATION_DATE]);
expirationDate.setHours(0, 0, 0, 0);
let status = row[COL_STATUS];
// 処理済みでない、かつ終了日が今日以前のエントリを対象とする
if (status === '未処理' && expirationDate <= today) {
Logger.log(`処理対象: プロジェクト名=${projectName}, ドライブID=${sharedDriveId}, メール=${externalEmail}, 期限=${expirationDate.toLocaleDateString()}`);
try {
const permissions = getSharedDrivePermissions(sharedDriveId);
const targetPermission = permissions.find(p => p.emailAddress === externalEmail);
if (targetPermission) {
removePermission(sharedDriveId, targetPermission.id);
sheet.getRange(i + 1, COL_STATUS + 1).setValue('処理済み');
sheet.getRange(i + 1, COL_PROCESSED_AT + 1).setValue(new Date());
Logger.log(`権限を剥奪しました: ${externalEmail} from ${sharedDriveId}`);
} else {
sheet.getRange(i + 1, COL_STATUS + 1).setValue('対象なし');
sheet.getRange(i + 1, COL_PROCESSED_AT + 1).setValue(new Date());
Logger.log(`共有ドライブに ${externalEmail} の権限が見つかりませんでした。`);
}
} catch (e) {
sheet.getRange(i + 1, COL_STATUS + 1).setValue('エラー');
sheet.getRange(i + 1, COL_PROCESSED_AT + 1).setValue(new Date());
Logger.log(`エラー発生: ${e.message} (プロジェクト名: ${projectName}, メール: ${externalEmail})`);
}
}
}
SpreadsheetApp.getUi().alert('処理完了', '共有ドライブのアクセス期限チェックが完了しました。', SpreadsheetApp.getUi().ButtonSet.OK);
}
/**
* 指定された共有ドライブの全権限を取得します。
* @param {string} driveId 共有ドライブID
* @returns {GoogleAppsScript.Drive.Schema.Permission[]} 権限の配列
*/
function getSharedDrivePermissions(driveId) {
const permissions = [];
let pageToken = null;
do {
// fieldsパラメータで必要な情報のみを取得し、パフォーマンスを向上
const response = Drive.Permissions.list(driveId, {
supportsAllDrives: true, // 共有ドライブをサポート
fields: 'nextPageToken, permissions(id, emailAddress, type, domain)',
pageToken: pageToken
});
permissions.push(...(response.permissions || []));
pageToken = response.nextPageToken;
} while (pageToken);
return permissions;
}
/**
* 指定された共有ドライブから特定の権限を削除します。
* @param {string} driveId 共有ドライブID
* @param {string} permissionId 削除する権限のID
*/
function removePermission(driveId, permissionId) {
// supportsAllDrivesをtrueに設定して共有ドライブでの操作を許可
Drive.Permissions.remove(driveId, permissionId, { supportsAllDrives: true });
}
スクリプトの解説
onOpen(): スプレッドシートを開いたときに、カスタムメニュー「自動権限剥奪」を追加します。ここから手動でスクリプトを実行できます。checkAndRevokeAccess(): メインの処理関数です。- スプレッドシートからデータを読み込み、今日が終了日の未処理エントリを特定します。
getSharedDrivePermissions()を呼び出して、対象の共有ドライブの現在の全権限を取得します。- 取得した権限の中から、スプレッドシートに記載された外部メンバーのメールアドレスと一致する権限 (
permissionId) を探します。 removePermission()を呼び出して、そのpermissionIdを持つ権限を削除します。- 処理の成否に応じてスプレッドシートのステータスと最終処理日時を更新します。
getSharedDrivePermissions(driveId): 指定された共有ドライブのすべての権限情報を取得します。supportsAllDrives: trueを設定することで、共有ドライブの権限を操作できるようにしています。fieldsパラメータで必要な情報のみを取得し、API呼び出しの効率を高めています。removePermission(driveId, permissionId): 指定された共有ドライブから、特定のpermissionIdに紐づく権限を削除します。
ステップ3:GASのトリガー設定と運用上の注意点
スクリプトが完成したら、毎日自動で実行されるようにトリガーを設定します。また、運用上の注意点も把握しておく必要があります。
トリガー設定
GASエディタの左側のメニューから「トリガー」(時計アイコン)をクリックし、「トリガーを追加」ボタンをクリックします。
- 実行する関数:
checkAndRevokeAccessを選択 - イベントのソース:
時間主導型を選択 - 時間ベースのタイプ:
日を選択 - 時刻: 任意の時間帯(例:
午前 0 時~1 時)を選択
これにより、毎日指定した時間帯にスクリプトが自動実行され、期限切れの外部ユーザーの権限が自動的に剥奪されます。
運用上の注意点
- 外部ユーザーへの事前通知の重要性: アクセス権が突然剥奪されると、外部パートナーは混乱する可能性があります。プロジェクト開始時や終了日の数日前に、アクセスが終了すること、必要なデータは事前にダウンロードしておくことなどを明確に通知するプロセスを組み込みましょう。
- グループ経由でのアクセスの場合: 外部ユーザーがGoogleグループのメンバーとして共有ドライブにアクセスしている場合、このスクリプトではグループそのものの権限を削除することになります。特定の外部ユーザーのみをグループから削除したい場合は、別途Google Admin SDK (Directory API) を使ってグループメンバーシップを管理するスクリプトが必要になります。本記事のスクリプトは、共有ドライブに直接追加された外部ユーザーの権限剥奪を想定しています。
- 内部ユーザーの権限剥奪防止: スクリプトは外部ユーザーに特化していますが、誤って内部ユーザーのメールアドレスがスプレッドシートに記載されないよう注意が必要です。スクリプトにドメインチェックを追加することも検討できますが、スプレッドシートの入力ミスを防ぐことが最も重要です。
- エラーハンドリングとログ記録: スクリプトには基本的なエラーハンドリングを加えていますが、より堅牢な運用のためには、エラー発生時に情シス担当者へメールで通知する機能や、詳細なログをスプレッドシートやSlackなどに記録する機能の追加も検討してください。
- GASの実行制限とAdmin SDKの権限: GASには日次実行時間やAPI呼び出し回数に制限があります(例: スクリプト実行時間の上限は 1 回 6 分、1 日の総実行時間は 6 時間。最新値は Google Apps Script の割り当てページを参照)。大規模な環境ではこれらの制限を考慮した設計が必要です。本スクリプトはDrive APIを使用しており、Admin SDKの権限は必要ありません。
設計判断基準:個人ドライブ共有との違いとグループ経由の場合
この自動化を設計する上で、いくつか考慮すべき点があります。
共有ドライブ vs. 個人ドライブ共有
- 共有ドライブが推奨される理由: 外部連携においては、個人のGoogleドライブではなく、共有ドライブの利用を強く推奨します。共有ドライブは、組織が所有権を持つため、担当者が異動・退職してもデータが残ります。また、詳細な権限管理や、チーム全体でのアクセス管理が容易です。本記事の自動化も共有ドライブを前提としています。
- 個人ドライブ共有の問題点: 個人ドライブで外部共有を行うと、共有設定が個人の管理に委ねられ、情シスからの統制が効きにくくなります。担当者が退職すると、その共有も失われるリスクがあります。
グループ経由の権限付与
前述の通り、外部ユーザーを直接共有ドライブに追加するのではなく、Googleグループに追加し、そのグループを共有ドライブにメンバーとして加える運用もあります。
- メリット: 外部ユーザーが複数の共有ドライブにアクセスする場合、グループメンバーシップを管理するだけで複数のドライブへのアクセスを一元的に制御できます。
- デメリット(本スクリプトとの関連): この場合、本スクリプトではグループ自体の共有ドライブへのアクセス権を剥奪することになります。特定の外部ユーザーのみアクセスを停止したい場合は、そのユーザーをグループから削除する必要があります。グループメンバーシップの管理はGoogle Admin SDK (Directory API) を使うことで自動化できますが、本記事のスコープ外です。まずは直接共有ドライブに追加するシンプルなケースから始めるのが現実的です。
まとめ:予防的セキュリティで安心な外部連携を
Google Workspaceの共有ドライブにおける外部ユーザーのアクセス管理は、手動では限界があり、情報漏洩リスクや情シスの運用負荷増大といった課題を抱えています。本記事で紹介した「時限式外部アクセス」のGASによる自動化は、これらの課題に対する強力な解決策となります。
プロジェクト開始時から外部ユーザーのアクセス期限を設計に組み込み、GASが自動で権限を剥奪することで、情シスはセキュリティリスクを未然に防ぎ、本来の業務に集中できるようになります。この予防的なアプローチは、組織全体のセキュリティガバナンスを向上させ、安心して外部パートナーとの連携を進めるための重要な一歩となります。
次のステップとして、まずは小規模なプロジェクトでこの仕組みを導入し、運用しながら改善していくことをおすすめします。
コーポレートITのご相談はお気軽に
この記事で書いたような業務改善・自動化の設計から実装まで、DRASENASではコーポレートITの現場に寄り添った支援を行っています。 「まず相談だけ」でも大歓迎です。DRASENAS 公式サイトからお気軽にどうぞ。
御社の IT 部門、ここにあります。
「ITのことはあまりわからない」── そのような状態からで、まったく問題ございません。まずはお気軽にご相談ください。