見出し画像

Google Workspace のログイン ID をセカンダリドメインに付け替えた話

ご挨拶

こんにちは、ちゃんりと申します。
(情シス Slack には Yone という名前で参加しています)
初心者枠として、情シス Slack のアドベントカレンダーに参加しています!

へーしゃで今年おこなった、Google Workspace のドメイン変更について書こうと思います。

GWS 内のドメイン構成

今回行った作業は、ざっくり上図のとおりです。
ドメインエイリアスをセカンダリドメインとして登録後、プライマリドメインには変更せずに、そのままユーザー・グループのメインメールアドレスとして利用しました。


作業の流れ

事前準備含め、大まかな流れは以下の通りです。

  1. (ここはユーザー各自で前日までに作業)他サービスの「Google でログイン」を、一時的に ID/PW ログインに変更

  2. (ここから本番作業当日)リレーメールサーバーを稼働し、メールがリレーサーバー経由で受信できることを確認

  3. ここからいよいよ GWS テナント作業!

  4. 今回の主役・ドメインエイリアスである hoge.com を、GWS テナントから削除

  5. ソワソワしながら待つ(30分程度)

  6. 先ほど削除したドメインエイリアス hoge.com を、セカンダリドメインとして追加

  7. ユーザー・グループを、プライマイドメイン(fuga.com)からセカンダリドメイン(hoge.com)に付け替え(GAS でごにょごにょ)

  8. ユーザー・グループの予備のメールアドレスに、自動でプライマリドメイン(fuga.com)が追加されていることを確認

  9. IdP やら周辺環境の設定を変更

  10. MXレコードを Google に戻して、リレーメールサーバーを停止


なぜセカンダリドメインに付け替えたか

経緯

弊社の場合、「ユーザーの Google ログインメールアドレスを、普段メイン使いしているドメインに変更する」というのが第一目的でした。
特に毎月の新入社員の方からすると、GWS だけなんかよく知らないドメインでログインしているぞ…?と混乱があったんじゃないかなと思います。
安定性を第一にしよう!の方針のもと、対応スコープを定めていったところ、部署内で「あれ、これセカンダリのままでも要件満たすんじゃね…?」の共通認識が芽生えてきました。

ユーザーのメールアドレス変更という要件を満たし、かつプライマリドメインへ変更の合間の待ち時間(最大48時間って書いてある)を短縮でき、それに伴うリスクも回避できそうってことになり、最低限のスコープでの作業実施となりました。


各作業の詳細

実際に行った作業の中で気にした点・方法などをメモしていこうと思います。

1. 「Google でログイン」の洗い出し

GWS のログインメールアドレスの変更によって、「Google を利用したソーシャルログイン」をおこなっている他サービスでログイン ID の差異が生まれてしまいます。
管理者側でログインIDを一括変更できるものもあれば、仕様上行えないものもあります。
その場合は、ドメイン変更後のログイン不可を避けるため、事前に ID/PW ログインへの変更を依頼しました。

どんなサービスに対して Google ログインを使用しているかわからないよ!という場合は、以下の手順で確認できます。

  1.  アカウントにアクセスできるアプリ にアクセス

  2. ページをスクロールし、「Google でログイン」のアプリ一覧を表示

  3. 「付与されているアクセス権:」に「アカウントの基本情報」が記載されている場合、Google アカウントでログインしたことがあるアプリです。

2. リレーメールサーバーを稼働

なんでもできるスーパーマン・しもしゃんさん(@shimosyan)が実施してくださいました。ありがとうございます!!
今回、Amazon SES での構築となっています。
「SESでメールを受信」という機能は一部リージョンでしか使用できず、エンドポイントは us-east-1 となっているそうです(私の知ったかぶりはこれが限界)。
本番当日の 2〜3時間前から稼働を始め、メールがリレーサーバー経由で受信できていることを確認してから次の作業へ移りました。

3. ここから GWS テナント作業!

ひじょーーーにソワソワするため、お茶でも飲みながら気持ちを落ち着かせます。
事前準備したチェックリストの想定時刻を復習しながらソワソワ。ソワソワ。

4. ドメインエイリアスを削除

ドメインエイリアスである hoge.com をテナントから削除します。
ここは、難なく実施することができました。
ただ、ユーザーやグループの「予備のメールアドレス」に hoge.com が設定されたままだとエラーで削除できなくなるため、事前確認が必要です。

5. 30分ほど待つ

GMOペパボさんの記事 や、a03さんの記事 にもある通り、すぐにはセカンダリドメインとして登録できずエラーになります。
事前に知っていても、エラー表示が出るのは心臓に悪いですね。。
へーしゃの場合、20〜30分ほどで登録可能となりました。

6. セカンダリドメインとして追加

削除した hoge.com を、セカンダリドメインとして追加します。
「Gmailを有効にする」の際に MX レコードの設定を求められますが、今はリレーサーバー稼働中のため、DNS に GoogleWS の MXレコードは登録できません。
「MXレコードの設定をスキップ」を選択します。

その後、TXT レコードによるドメイン認証が求められるため、DNS の設定値を確認して終了です。

7. ユーザー・グループを付け替え

テナント内に、hoge.com をセカンダリドメインとして登録することができました。
ただし、ユーザーやグループはまだプライマリドメインに紐づいたままなので、プライマイドメイン(fuga.com)からセカンダリドメイン(hoge.com)への付け替え作業を行います。(GAS でごにょごにょ)
使用した GAS は下記の通り。スーパーマン・しもしゃんs(略(@shimosyan)に教わりつつ準備を行いました。
スプシに一度ユーザー・グループ一覧を吐き出して、そこの値を読み取りつつ変更していきます。

  1. シート初期化(initializeSheet():別シートに対象のドメインを記入する欄を設け、かつメインのシートで一覧出力のフォーマットを整えます。)

  2. ユーザー・グループを取得(getUserAndGroupList():_getUserList、_getGroupList それぞれで一覧を取得し、それら2つの配列を合体してシートに書き出します。)

  3. メールアドレスの変更を実行(runChangeMailAddress():これが実際に行いたい作業です。ユーザーの場合は primaryEmail、グループの場合は email を、xxx@hoge.com に変更します。

/**
 * 実行前に左のサイドバーの「サービス」から AdminSDK - AdminDirectory を追加すること
 */
const defaultSourceDomain = 'fuga.com';
const defaultTargetDomain = 'hoge.com';

const configSheetName = '設定';
const userAndGroupListSheetName = 'ユーザー・グループリスト';

const isDebug = false; // ← 本番実行前に false に変更すること

function onOpen(){
  const ui = SpreadsheetApp.getUi();
  const menu = ui.createMenu('スクリプト');

  menu.addItem('シート初期化', 'initializeSheet');
  menu.addItem('ユーザー・グループを取得', 'getUserAndGroupList');
  menu.addSeparator();
  menu.addItem('メールアドレスの変更を実行', 'runChangeMailAddress');
 
  menu.addToUi();
}

/**
 * 処理に必要なシートを初期化する
 */
function initializeSheet() {
  /**
   * シートを作成する。既にあれば削除した後に新規で作成する。
   */
  const ss =SpreadsheetApp.getActiveSpreadsheet();

  // "設定"シート
  let configSheet = ss.getSheetByName(configSheetName);
  if(configSheet === null){
    configSheet = ss.insertSheet(configSheetName);
  }else{
    ss.deleteSheet(configSheet);
    configSheet = ss.insertSheet(configSheetName);
  }

  // "ユーザー・グループリスト"シート
  let userAndGroupListSheet = ss.getSheetByName(userAndGroupListSheetName);
  if(userAndGroupListSheet === null){
    userAndGroupListSheet = ss.insertSheet(userAndGroupListSheetName);
  }else{
    ss.deleteSheet(userAndGroupListSheet);
    userAndGroupListSheet = ss.insertSheet(userAndGroupListSheetName);
  }

  /**
   * 書式設定する
   */

  // 不要な列を削除
  if(configSheet.getMaxColumns() > 2){
    configSheet.deleteColumns(3, configSheet.getMaxColumns() - 2);
  }

  if(userAndGroupListSheet.getMaxColumns() > 6){
    userAndGroupListSheet.deleteColumns(7, userAndGroupListSheet.getMaxColumns() - 6);
  }

  // 不要な行を削除
  if(configSheet.getMaxRows() > 2){
    configSheet.deleteRows(3, configSheet.getMaxRows() - 2);
  }
  
  // 見出し追加
  const configHeader = ['変更前ドメイン', '変更後ドメイン'];
  configSheet.getRange(1, 1, 1, configHeader.length).setValues([configHeader]).setBackground('#d9ead3');

  const userAndGroupListHeader = ['カテゴリー', '名称', 'ID', 'メールアドレス', '変更後メールアドレス', '処理日時', '成否'];
  userAndGroupListSheet.getRange(1, 1, 1, userAndGroupListHeader.length).setValues([userAndGroupListHeader]).setBackground('#d9ead3');

  // 見出し固定
  configSheet.setFrozenRows(1);
  userAndGroupListSheet.setFrozenRows(1);

  // 列サイズ調整
  configSheet.setColumnWidths(1, 2, 200);
  userAndGroupListSheet.setColumnWidths(2, 2, 200);
  userAndGroupListSheet.setColumnWidths(4, 2, 240);
  userAndGroupListSheet.setColumnWidths(6, 1, 200);

  // 既定値追加
  configSheet.getRange(2, 1, 1, 2).setValues([[defaultSourceDomain, defaultTargetDomain]]);
  userAndGroupListSheet.getRange(1, 5).setFormula(`={"変更後メールアドレス"; ARRAYFORMULA(IF(D2:D <> "", SUBSTITUTE( D2:D, '設定'!$A$2, '設定'!$B$2), ""))}`);
}

/**
 * 現在のユーザー・グループの一覧を取得する
 */
function getUserAndGroupList() {
  const ss =SpreadsheetApp.getActiveSpreadsheet();
  const configSheet = ss.getSheetByName(configSheetName);
  const userAndGroupListSheet = ss.getSheetByName(userAndGroupListSheetName);
  const sourceDomain = configSheet.getRange(2, 1).getValue();

  // ユーザー一覧を取得する
  const userList = _getUserList(sourceDomain);

  // グループ一覧を取得する
  const groupList = _getGroupList(sourceDomain);

  // 2つの配列を合体してシートに書き出す
  const userAndGroupList = userList.concat(groupList);
  userAndGroupListSheet.getRange(2, 1, userAndGroupList.length, userAndGroupList[0].length).setValues(userAndGroupList);
}

/**
 * 一覧に載っているユーザー及びグループのメールアドレスを書き換える
 */
function runChangeMailAddress() {
  const ss =SpreadsheetApp.getActiveSpreadsheet();
  const userAndGroupListSheet = ss.getSheetByName(userAndGroupListSheetName);
  const rawSheetValues = userAndGroupListSheet.getSheetValues(1, 1, userAndGroupListSheet.getLastRow(), userAndGroupListSheet.getLastColumn());

  //const sheetHeader = rawSheetValues[0];
  const sheetValues= rawSheetValues.slice(1).filter(item => item[0] !== '');

  sheetValues.forEach((value, index) => {
    const data = {
      category: value[0],
      name: value[1],
      id: value[2],
      originalEmail: value[3],
      targetEmail: value[4],
      processDate: value[5],
      isComplete: !(value[6] === '' || value[6] === null),
      rowNumber: index + 2,
    }

    if(data.isComplete){
      return;
    }

    const today = Utilities.formatDate(new Date(), 'JST', 'yyyy-MM-dd HH:mm:ss');
    userAndGroupListSheet.getRange(data.rowNumber, 6, 1, 1).setValue(today);

    if(data.category === 'ユーザー'){
      // ユーザーのメールアドレスの書き換え
      const params = {
        primaryEmail: data.targetEmail
      }
      
      if(isDebug === false){
        AdminDirectory.Users.update(params, data.id);
      }else{
        console.log(data, params);
      }
    }

    if(data.category === 'グループ'){
      // グループのメールアドレスの書き換え
      const params = {
        email: data.targetEmail
      }
      
      if(isDebug === false){
        AdminDirectory.Groups.update(params, data.id);
      }else{
        console.log(data, params);
      }
    }

    userAndGroupListSheet.getRange(data.rowNumber, 7, 1, 1).setValue('○');
  });
}

function _getUserList(domain) {
  const list = [];
  let pageToken;
  do{
    const optionalArgs = {
      domain:domain,
      maxResults:500, //デフォルトでは100件までなので、500(最大数)を指定
      pageToken: pageToken,
    };

    const datas = AdminDirectory.Users.list(optionalArgs);

    if(!datas.users) {
      return;
    }

    datas.users.forEach(user => {
      list.push(['ユーザー', user.name.fullName, user.id, user.primaryEmail]);
    });

    pageToken = datas.nextPageToken;
  }while(pageToken);

  return list;
}

function _getGroupList(domain) {
  const list = [];
  let pageToken;
  do{
    const optionalArgs = {
      domain:domain,
      maxResults:500, //デフォルトでは100件までなので、500(最大数)を指定
      pageToken: pageToken,
    };

    const datas = AdminDirectory.Groups.list(optionalArgs);

    if(!datas.groups) {
      return;
    }

    datas.groups.forEach(group => {
      list.push(['グループ', group.name, group.id, group.email]);
    });

    pageToken = datas.nextPageToken;
  }while(pageToken);

  return list;
}

8. 予備のメールアドレスの確認

(記事を見つけられなかっただけかもですが、、)明記されてない情報として、ユーザー・グループのメインのメールアドレスを変更した場合、旧メインメールアドレスは予備のメールアドレスとして自動で登録されます。
これのおかげで、旧メインメールアドレス(=プライマリドメイン(fuga.com))はダウンタイムなくメールの受信が可能です。
今回は、上記の GAS 実行が終わったタイミングで追加となるため、想定どおり値が入っているかどうかサンプルチェックして完了です。

9. IdP やら周辺環境の設定を変更

へーしゃでは IdP として Okta を利用しています。
Okta - GWS 間での SAML、SCIM では、fuga.com への変換が行われているため、その部分の修正を行いました。

  • SAML

    • Sign On > Credentials Details > Application username format

      • (変更前)Custom にし、hoge.com → fuga.com に変換

      • (変更後)Okta username にし、hoge.com のまま連携

  • SCIM

    • Okta Profile>Mappings > login を appuser.userName に変更

    • Provisioning > To Okta > Okta username format をメールアドレスに変更

10. リレーメールサーバーの停止

当日作業としてはラストです。DNS の MX レコードを GWS のものに書き換えます。

おつかれさまでした!


ユーザー影響について

ユーザー影響

軽微なもの含め、ドメイン付け替え後に起きたユーザー視点での変化は以下です。

1.カレンダーの「日」「週」表示が真っ白な状態になる

こんなかんじ(「他のカレンダー」も表示されませんでした)

これは、時間経過とともに解消されました。
また、発生する人・しない人がいます。
一応、画面右上から「月」や「年」表示に切り替えると問題なく描写されますが、「日」や「週」の描写が行われません。
公式ドキュメントに、「メール エイリアスの変更 (古いエイリアスの削除や新しいエイリアスの追加など) が反映されるまで、最大 24 時間かかる場合があります。」
の記載がありますが、
このようなカレンダー周りの挙動を含めての24時間バッファなのかなと感じました。
今回は休日作業だったため、影響を受けたのは(おそらく)我々作業者のみでしたが、平日作業の場合これらの対策も必要そうです。(土日に作業するのが得策でしょうか)

2.メール送信時にエラー メッセージが出る
「[From] アドレスが無効です。有効なアドレスを選択してください。」と表示されることがあります。
これは、一度 Google アカウントからログアウトし、新しいメールアドレスで再ログインすると解消されました。
公式ドキュメントにもこっそり書いてあります。(確認不足で、社内からお問い合わせがあった際にワタワタしました…反省点。)


3.メーリスの差出人設定が消える
設定>アカウント>名前 で設定する、差出人メールアドレスです。

本人のメールアドレスは、fuga.com から hoge.com に自動で置き換わっていましたが、メーリングリストを差出人として設定していた場合は、削除されました。
あくまで再設定が必要なのはメーリングリストのみで、本人アカウントのメールアドレスは維持されます。

4.モバイルのメール受信通知設定がオンになる
Gmail アプリを利用している場合、ログインメールアドレスの変更後、メール通知がオンとなりました。
アプリからの再度オフ設定が必要です。


その他のユーザー影響

また、事前に懸念していたものの、アクセス権などがそのまま維持されたものは以下です。
これらの動きは、検証時確認していたとおりでした。

  1. Chrome プロファイル、GWS へのログインはセッションが切れずログイン状態を保持(気がついたらログインメアドの表示が新しいものに置き換わっている)

  2. GCP、Youtube など、Google アカウントを利用してログインするサービスも、そのまま自動でログインIDが置き換わり、権限設定は維持される

  3. スプレッドシートや、外部 API などを操作する GAS の初回実行時の認証情報は保持されたまま(再認証不要)

  4. Google Drive のアクセス権設定は、自テナント、他テナントともに維持

  5. カレンダーの予定はそのまま維持


今回スコープ外となった作業

当初、「ユーザーの Google ログインメールアドレスを変更する」の他に、実施したい目標がありました。
それは、「メイン使いしている hoge.com 以外のドメインの利用を廃止する」というものです。
冒頭の図には記載してませんが、へーしゃのテナントには、プライマリドメインのドメインエイリアスとして、他にもいくつかのドメインが存在しています。
各ドメインが使われているかどうかを調査して、不要であれば GWS から削除して…まで含めたかったのですが、今回の作業からは省く形となりました。
それらも含めると、かなり長期戦になりそうであり、ログインIDを変更する!という最優先の目的を早めに達成できなさそうだったからです。
そんなことから、現時点での、へーしゃの GWS 内で利用できるメールアドレスは以下となっています。

本作業前からアカウントのあるユーザー・グループ

  • メインメールアドレス(=セカンダリドメイン hoge.com)

  • 予備のメールアドレス(プライマリドメイン fuga.com)

  • その他メールアドレス(プライマリドメインのドメインエイリアス数個)

本作業後に追加されたユーザー・グループ

  • メインメールアドレス(=セカンダリドメイン hoge.com)

作業前からアカウントのあるユーザー・グループの、 hoge.com 以外のドメイン利用を廃止することが、最終のきれいなゴールなのかなと思います。
いつかやりたいな。(優先度がどうなるかな)

ちなみに…
セカンダリドメインへのドメインエイリアス追加は、GUI での実施は不可であり、API 利用が必須だそうです。(今回はスコープ外のため未実施)


最後に

ドメイン変更のバイブル・GMOペパボさんの記事 を読んでこんなに大変なのか!と戦々恐々とし、a03さんの記事 を読んでわかる…わかる…となり。。
このような先人の方々のアウトプットや、情シス Slack でのナレッジ共有は、本当にありがたいなあと日頃感じております。
自分はまだまだ勉強中の身ではありますが、自身のスキル向上のため(や、私が所属するコミュニティの方に、多少1ミリでも何かお役に立てたらいいなあと思い)今回 note を書いてみました。
これからも、いろんな場で知識を吸収しながら、自分も定期的にアウトプット出せるようにがんばります!
ありがとうございました。


この記事が気に入ったらサポートをしてみませんか?