LabHub

ブログ

SiteMinder から Keycloak へ — レガシー SSO マイグレーション戦略と実戦ロードマップ

한국어English日本語

はじめに — 先送りしてきた宿題に取り組む時

前回の記事で SiteMinder のアーキテクチャを解剖しました。今回はその次の問い、「では、どうやって抜け出すのか」を扱います。

2026年現在、この宿題をもう先送りできない理由は明確になりました。Keycloak 26.6 は FAPI 2.0 Final、ログインフローへの passkeys 統合、zero-downtime ローリングアップデート、Workflows による realm 管理自動化まで備え、エンタープライズ要件を十分に受け止められる水準になりました。標準側でも OAuth 2.1 draftRFC 9700 によってベストプラクティスが整理されています。一方 SiteMinder 側は、12.8.04 の EOS(2023年12月)が示すようにバージョンのライフサイクル管理自体が常時リスクであり、Broadcom 買収後のライセンスポリシーの変化はコスト予測を困難にしました。

本記事はコンサル資料ではなく実務ロードマップを目指します。インベントリの作り方、SiteMinder オブジェクトの Keycloak へのマッピング、共存アーキテクチャの構成、パスワードマイグレーション、ロールバック計画まで — 実際のプロジェクトでぶつかる順番に整理します。

マイグレーションの動機 — なぜ離れるのか

組織ごとに事情は異なりますが、動機はおおむね3つに収斂します。

1. EOS とサポートリスク

2. ライセンスコスト

3. 標準の不在とエコシステムの断絶

フェーズ 0: インベントリ — すべては分類から始まる

マイグレーション失敗の最も多い原因は技術ではなくインベントリの不備です。XPSExport でポリシーストアをダンプし、アプリを以下のカテゴリに分類することが出発点です。

# ポリシーストア全体のエクスポート
XPSExport sm-policy-export.xml -xb -vT

# realm/response 数などの一次統計を抽出 (例)
grep -c "<Realm" sm-policy-export.xml
grep -o 'SM_[A-Z_]*' sm-policy-export.xml | sort | uniq -c | sort -rn

アプリ分類体系

分類特徴移行難易度移行方法
A. 標準プロトコルアプリすでに SAML/OIDC で連携(フェデレーションパートナー)IdP エンドポイントを Keycloak に差し替えるだけ
B. ヘッダーベースアプリ(修正可能)SM_USER ヘッダー依存、ソース修正可能OIDC クライアントとして書き換え、またはプロキシ
C. ヘッダーベースアプリ(修正不可)ベンダー廃業、ソースなし、変更凍結プロキシパターンでヘッダー再現(無修正)
D. エージェント API 依存アプリSiteMinder SDK/Agent API を直接呼び出し最高個別分析のうえ再設計が必要

分類とあわせて、アプリごとに次のメタデータを収集します。

このインベントリは Excel ではなく、バージョン管理される構造化データ(YAML/JSON)として作ることを推奨します。移行進捗ダッシュボードのデータソースになります。

# app-inventory.yaml (例)
- appId: hr-payroll
  category: C            # ヘッダーベース、修正不可
  headers: [SM_USER, SM_USERGROUPS, X-Custom-Empno]
  authScheme: forms
  sessionIdle: 30m
  owner: hr-it-team
  criticality: high
  wave: 3                # 移行ウェーブの割り当て

ビッグバン vs 段階的移行

項目ビッグバン段階的(推奨)
移行期間短い(理論上)長い(1〜3年)
リスク全社同時障害の可能性ウェーブ単位に局所化
ロールバック事実上不可能ウェーブ単位で可能
共存インフラ不要SAML ブリッジ/プロキシが必要
適した環境アプリ30個未満の小規模金融機関/大企業の数百アプリ

アプリが数十を超える環境でのビッグバンはギャンブルです。以下の内容は段階的移行を前提とします。段階的移行の中心課題は、移行期間中に2つの IdP が共存しながらも、ユーザーは一度だけログインすればよい状態を作ることです。

共存(Coexistence)アーキテクチャ — 2つの世界の橋

SAML ブローカリング: Keycloak と SiteMinder の相互連携

両システムとも SAML 2.0 を話せることが橋になります。方向は2つあります。

方向 1: Keycloak が IdP、SiteMinder が SP(目標状態へ向かう推奨方向)

新規/移行済みのアプリは Keycloak で直接ログインし、まだ SiteMinder 配下にあるレガシーアプリにアクセスすると、SiteMinder が SAML SP として Keycloak に認証を委譲します。認証の信頼できる情報源が先に Keycloak へ移るため、passkeys のようなモダンな認証手段を全社に先行適用できます。

 ユーザー
   |
   | 1. ログイン (passkey/OTP/パスワード)
   v
+------------------+    SAML assertion    +--------------------+
|     Keycloak     | -------------------> |     SiteMinder     |
|   (新 IdP)       |   (SM が SAML SP)    |  (レガシー WebSSO)  |
+--------+---------+                      +---------+----------+
         |                                          |
         | OIDC/SAML                                | SMSESSION + SM_USER
         v                                          v
+------------------+                      +--------------------+
| 移行済みアプリ群    |                      | 未移行レガシーアプリ群 |
| (OIDC クライアント)|                      | (ヘッダーベース)      |
+------------------+                      +--------------------+

方向 2: SiteMinder が IdP、Keycloak がブローカー(移行初期に有用)

既存のログイン体験に手を付けず、新規アプリから Keycloak に接続したい場合、Keycloak の Identity Brokering 機能で SiteMinder を外部 IdP として登録します。ユーザーは引き続き SiteMinder のログイン画面を見ますが、新規アプリは標準 OIDC で開発されます。

Keycloak で SAML Identity Provider を登録する設定例は次のとおりです。

# kcadm で SiteMinder を SAML IdP として登録
/opt/keycloak/bin/kcadm.sh create identity-provider/instances -r enterprise \
  -s alias=siteminder-idp \
  -s providerId=saml \
  -s enabled=true \
  -s 'config.entityId=https://keycloak.example.com/realms/enterprise' \
  -s 'config.singleSignOnServiceUrl=https://sso.example.com/affwebservices/public/saml2sso' \
  -s 'config.nameIDPolicyFormat=urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified' \
  -s 'config.postBindingResponse=true' \
  -s 'config.wantAssertionsSigned=true'

セッションブリッジングの現実

2つの IdP のセッションは別物です。SAML ブローカリングは「再認証なしに相手側のセッションを作ってくれる」ものであり、セッションを1つに統合するものではありません。したがって、次を設計段階で決める必要があります。

ヘッダーベースアプリのためのプロキシパターン — oauth2-proxy

分類 C(修正不可のヘッダーベースアプリ)への解決策です。Web Agent がやっていたこと — 認証確認後のユーザーヘッダー注入 — を oauth2-proxy と nginx で再現します。

 ユーザー --> nginx (auth_request) --> oauth2-proxy --> Keycloak (OIDC)
              |                          |
              | 認証 OK + ヘッダー受信      |
              v                          |
        レガシーアプリ (SM_USER ヘッダーを   |
        そのまま受け取る — コード無修正) <----+

oauth2-proxy 設定の核心は、Keycloak の OIDC クレームを SiteMinder のヘッダー名へ変換する部分です。

# oauth2-proxy.yaml (alpha config)
upstreamConfig:
  upstreams:
    - id: hr-payroll
      path: /
      uri: http://hr-payroll.internal:8080
providers:
  - id: keycloak-oidc
    provider: keycloak-oidc
    clientID: legacy-bridge-proxy
    clientSecretFile: /secrets/client-secret
    oidcConfig:
      issuerURL: https://keycloak.example.com/realms/enterprise
      emailClaim: email
      audienceClaims: [aud]
injectRequestHeaders:
  - name: SM_USER                 # レガシーアプリが期待するヘッダー名そのまま
    values:
      - claim: preferred_username
  - name: SM_USERGROUPS
    values:
      - claim: groups             # Keycloak の mapper でキャレット(^)結合形式を再現
  - name: X-Custom-Empno
    values:
      - claim: employeeNumber

nginx 側は auth_request パターンで接続します。

server {
  listen 443 ssl;
  server_name hr-payroll.example.com;

  location /oauth2/ {
    proxy_pass http://oauth2-proxy:4180;
    proxy_set_header X-Forwarded-Uri $request_uri;
  }

  location / {
    auth_request /oauth2/auth;
    error_page 401 = /oauth2/start?rd=$scheme://$host$request_uri;

    # oauth2-proxy が検証後に返したヘッダーをアップストリームへ伝達
    auth_request_set $sm_user $upstream_http_sm_user;
    auth_request_set $sm_groups $upstream_http_sm_usergroups;
    proxy_set_header SM_USER $sm_user;
    proxy_set_header SM_USERGROUPS $sm_groups;

    # 外部から偽造されて入ってきた同名ヘッダーは上で無条件に上書きされる
    proxy_pass http://hr-payroll.internal:8080;
  }
}

セキュリティ注意: 前回の記事で強調した header injection の原則がそのまま適用されます。レガシーアプリは必ずこのプロキシ経由でのみアクセス可能になるようネットワークを分離し、プロキシはクライアントが送ってきた SM_USER 系ヘッダーを上書きしなければなりません。

グループクレームを SiteMinder 形式(キャレット区切り)に合わせる必要があれば、Keycloak の Script Mapper またはカスタム protocol mapper で処理します。

// Keycloak カスタム ProtocolMapper の核心ロジック (キャレット区切りグループ文字列)
@Override
protected void setClaim(IDToken token, ProtocolMapperModel model,
                        UserSessionModel userSession, KeycloakSession session,
                        ClientSessionContext ctx) {
    String joined = userSession.getUser().getGroupsStream()
            .map(GroupModel::getName)
            .collect(Collectors.joining("^"));
    token.getOtherClaims().put("smUserGroups", joined);
}

ポリシーマイグレーション — オブジェクトマッピング表

SiteMinder のオブジェクトモデルを Keycloak の概念へ移すときの基準マッピングです。

SiteMinder オブジェクトKeycloak 対応概念備考
Policy DomainRealm またはクライアントのグルーピング組織単位が大きければ realm 分離を検討
Realm (URL 領域+スキーム)Client + 要求認証フローURL 保護はアプリ/プロキシの責務へ移動
Rule (リソース+アクション)Authorization Services の resource/scopeまたはアプリレベルの認可へ移動
Policy (rule+ユーザー)Role/Group マッピング、AuthZ policyLDAP グループは group-ldap-mapper で同期
Response (ヘッダー注入)Protocol Mapper (クレーム注入)ヘッダーインベントリがマッピングの入力
Auth SchemeAuthentication Flowstep-up は ACR/LoA 条件付きフローで
Protection LevelACR (Authentication Context Class)step-up authentication を再現
User Store (AD/LDAP)User Federation (LDAP provider)初期は既存 LDAP をそのまま接続
Session (idle/max)SSO Session Idle / Maxrealm 単位での統一の必要性を検討
OnAuthAccept イベントルールEvent Listener SPI / Required Actionカスタム SPI 開発の領域

重要な観点の転換が一つあります。SiteMinder は URL 中心(この URL に誰がアクセスできるか)であり、Keycloak はクライアント/トークン中心(このアプリにどんなクレームとロールを発行するか)です。URL 単位の細かい認可がどうしても必要なら Keycloak Authorization Services またはゲートウェイレベル(例: プロキシのパス別グループ検査)へ移しますが、可能ならアプリ内部の認可に下ろす方が長期的に健全です。

LDAP User Federation は共存初期の鍵となる仕組みです。両システムが同じユーザーストアを参照すれば、アカウント同期の問題が消えます。

# 既存の AD/LDAP を Keycloak の User Federation として接続
/opt/keycloak/bin/kcadm.sh create components -r enterprise \
  -s name=corp-ldap \
  -s providerId=ldap \
  -s providerType=org.keycloak.storage.UserStorageProvider \
  -s 'config.connectionUrl=["ldaps://ad.example.com:636"]' \
  -s 'config.usersDn=["ou=people,dc=example,dc=com"]' \
  -s 'config.bindDn=["cn=svc-keycloak,ou=svc,dc=example,dc=com"]' \
  -s 'config.editMode=["READ_ONLY"]' \
  -s 'config.syncRegistrations=["false"]'

ユーザー/パスワードマイグレーション — 漸進的キャプチャ

同じ LDAP を共有するならパスワード問題はありません。しかし SiteMinder が独自のユーザーストアを使っている場合や、この機会に LDAP 自体を廃止したい場合、パスワードハッシュを移せない状況(独自ハッシュ、ポリシー上 export 禁止)が生じます。このとき使うのが漸進的パスワードキャプチャ(gradual password capture)です。

  1. ユーザープロファイル(ID、メール、属性)は SCIM(RFC 7644)またはバッチで先行移行し、パスワードは空のままにします。
  2. Keycloak にカスタム User Storage SPI を配置します。ユーザーが初めて Keycloak でログインすると、SPI が入力されたパスワードでレガシーストア(LDAP bind または SiteMinder 認証 API)に検証を委譲します。
  3. 検証に成功したら、そのパスワードを Keycloak 自身のクレデンシャル(argon2 ハッシュ)として保存し、以降はローカル検証に切り替えます。
  4. 数か月後、キャプチャ率が十分に上がったら(例: 95パーセント)、残りのユーザーにパスワード再設定を案内してレガシーストアを廃止します。
// User Storage SPI の核心: 初回ログイン時のレガシー検証 + キャプチャ
@Override
public boolean isValid(RealmModel realm, UserModel user, CredentialInput input) {
    String rawPassword = input.getChallengeResponse();
    boolean legacyOk = legacyAuthClient.validate(user.getUsername(), rawPassword);
    if (legacyOk) {
        // キャプチャ: Keycloak ローカルクレデンシャルとして保存 → 次回からローカル検証
        user.credentialManager().updateCredential(
            UserCredentialModel.password(rawPassword));
        log.infof("Password captured for user=%s", user.getUsername());
    }
    return legacyOk;
}

このパターンは、ユーザーに「パスワード再設定の大混乱」を起こさずにストアを乗り換える実証済みの方法です。ただし、キャプチャ期間中はレガシー認証経路の可用性が Keycloak ログインの可用性に影響するため、サーキットブレーカーと監視を付ける必要があります。

ウェーブ単位のロールアウトとロールバック計画

ウェーブ設計

Wave 0 (パイロット)   : 社内 IT チーム用の低リスクアプリ 2〜3個。全パターンを検証。
Wave 1 (低リスク)     : トラフィックが少なく業務影響の低いアプリ 10〜20個。
Wave 2〜N (本移行)    : 部門/ドメイン単位の束。ウェーブあたり 2〜6週間。
Wave Final (中枢部)   : 基幹業務アプリ。十分な共存運用実績の後で。
最後: SiteMinder デコミッション (ポリシーアーカイブ、ライセンス終了)

ウェーブごとに同じチェックリストを繰り返します。

  1. 事前: 対象アプリのヘッダー/セッション要件の再確認、Keycloak クライアント/プロキシの構成、ステージングでの検証
  2. 切り替え: DNS/ロードバランサーのルーティングを新経路(プロキシまたは OIDC 直接)へ変更。可能ならカナリア(トラフィックの一部のみ新経路)を適用
  3. 観察: 認証成功率、ログイン所要時間、ヘルプデスクチケット数を最低 1〜2週間モニタリング
  4. 確定またはロールバック

ロールバック計画

ロールバックが容易な構造を意図的に作るべきです。

# ルーティング切替/復旧をスクリプト化しておく (例: nginx upstream の差し替え)
./switch-route.sh hr-payroll --to keycloak-proxy   # 切り替え
./switch-route.sh hr-payroll --to siteminder       # ロールバック (1分以内)

検証シナリオ — ウェーブごとに回すテスト

最低限、次のシナリオを自動化しておくことを推奨します。

番号シナリオ確認ポイント
T1未認証ユーザーが保護 URL にアクセスログインリダイレクト、元の URL への復帰
T2ログイン後、同じウェーブの別アプリにアクセスSSO(再ログインなし)
T3レガシーアプリと新アプリの行き来ブリッジ経由の SSO、二重ログインなし
T4ヘッダー値の整合性SM_USER などがレガシーと同一形式
T5セッション idle 失効失効後の再認証要求、無限リダイレクトなし
T6ログアウト両側のセッションが終了するか
T7ヘッダー偽造の試行外部注入の SM_USER が無視/上書きされるか
T8グループベースの認可権限のないグループの 403 確認
T9step-up 認証高保護アプリ進入時の再認証/2FA 要求
T10障害注入Keycloak 1ノード停止時もログイン継続

T4(ヘッダー整合性)は特に重要です。レガシー環境と新プロキシ環境へ同じリクエストを送り、ヘッダーダンプを diff するハーネスを作っておけば、ウェーブごとに再利用できます。

# ヘッダー整合性 diff ハーネス (概念例)
curl -s -b "$LEGACY_COOKIE" https://legacy.example.com/echo-headers > legacy.txt
curl -s -b "$KC_COOKIE" https://bridge.example.com/echo-headers > new.txt
diff <(grep '^SM_' legacy.txt | sort) <(grep '^SM_' new.txt | sort)

Identity Orchestration ツールという選択肢

すべてを自前で構築する代わりに、移行期の複雑さを吸収してくれる商用レイヤーもあります。Strata Maverics のような identity orchestration 製品は、SiteMinder とモダン IdP の間に抽象化レイヤーを置き、アプリごとにどの IdP を使うかをポリシーで切り替え、セッション変換とヘッダー注入を代行します。

組織のエンジニアリング能力が十分でアプリのパターンが単純(大半がヘッダーベース)なら oauth2-proxy ベースの自前構築が、パターンが複雑でスケジュールが厳しいならオーケストレーションツールの導入が合理的です。

よくある落とし穴集

おわりに

SiteMinder から Keycloak へのマイグレーションは IdP の入れ替えプロジェクトではなく、アプリケーションポートフォリオのモダナイゼーションプログラムです。成功の8割はインベントリと分類、そしてロールバック可能なウェーブ設計で決まります。技術的には、SAML ブローカリングと oauth2-proxy のヘッダー変換という2つの橋を堅牢に架ければ、残りは反復可能な運用プロセスの問題です。

次の記事では、この移行期が数年続くという現実を受け入れ、レガシー WebSSO とモダン IAM の共存アーキテクチャパターン — プロトコルブリッジ、リバースプロキシのヘッダー注入、identity orchestration、strangler fig — を、銀行イントラネット 200 アプリの仮想事例とともに深く扱います。

参考資料

コメント

まだコメントはありません。

ログインするとコメントできます