コンテンツにスキップ

LDAP カスタム認証と Func 属性加工(デプロイメントプラン)

デプロイメントプランでは、LDAP 認証を顧客が管理する Func 関数に委ねることも、組み込みの LDAP 認証を維持したまま、認証成功後のユーザー属性の加工のみを Func で行うこともできます。どちらの方式も、既存の LDAP ログインエントリと SSO アカウント、ワークスペース、ロールマッピングルールを再利用します。

使用前に、Studio バックエンドのバージョンが LDAPClientSet.authMode、LDAPClientSet.external、およびネイティブ Func 関数インターフェースのレスポンス形式に対応していることを確認してください。secret の暗号化を有効にするには、ブランド非依存の v2 プロトコルに対応した Studio と Func core も必要です。ダウンロードスクリプトを更新するだけでは、旧バージョンの Studio で v2 を利用できるようにはなりません。既存のデプロイメントで authMode: builtin を維持(または未設定)し、非空の server.hosts を有効にしない場合は、従来の単一アドレスの通信経路が引き続き使用されます。アップグレードのために Func や secret を設定する必要はありません。

モードの選択

authMode パスワードを検証する主体 Func が受け取るもの Func が成功時に返すもの
builtin(デフォルト) Studio 組み込み LDAP Func は呼び出されない 該当なし
builtin_transform Studio 組み込み LDAP。失敗時は Func を呼び出さない ユーザー名と、認証済みの userinfo(今回のログインパスワードは含まない) ユーザー属性のトップレベルパッチ
external 顧客の Func ユーザー名、パスワード 完全な userinfo

builtin_transform は、既存の LDAP 接続、TLS、検索、マッピング設定を共用し、マルチドメインコントローラー も併せて設定できます。このモードは新しい認証実行チェーンを使用するため、hosts を設定していない場合でも、マルチドメインコントローラーのドキュメントに記載された新しいチェーンの要件に従って、SIMPLE ユーザーパスワード検証、SAFE_SYNC、起動環境を確認してください。

external は Studio 設定内の LDAP サーバーに接続しなくなり、実際の認証は顧客の Func が担当します。Studio は引き続き LDAPClientSet.mapping を使用して返された属性を解釈します。ユーザー情報の代わりに Studio のトークン、アカウント UUID、ロール UUID を関数から返さないでください。

2 つの外部モードは必要に応じて選択し、ログインのたびに external.url で指定された関数のみを呼び出します。external が authenticate を指す場合、transform は自動的に呼び出されません。顧客関数で認証と属性加工の両方を完了したい場合は、認証成功後に完全な userinfo を加工してから返してください。Studio 組み込みの LDAP 認証を維持し、属性のみをカスタマイズする場合は、builtin_transform を使用して URL を transform に指定してください。

Func スクリプトの準備

次のサンプルには、標準的な LDAP パスワード検証と属性加工の 2 つのエントリが含まれており、顧客スクリプトの開始点として利用できます。

  • core.py をダウンロード:LDAP 接続、プロトコルエンコード/デコード、属性検証。
  • main.py をダウンロード:Func 関数エントリ。authenticate(body="") と transform(body="") を含む。
  • ソースと検証情報をダウンロード。2 つのスクリプトは Studio 実装と同一ソースです。プロトコルをアップグレードする際は、合わせて更新してください。

  • 顧客が管理できる Func 環境でスクリプトセット(例:ldap_client_demo)を作成します。

  • サブスクリプトとして ID core を作成し core.py を貼り付け、さらに ID main のサブスクリプトを作成して main.py を貼り付けます。main 内の import __core as core は同じスクリプトセット内の core を参照します。インターネットからコードをダウンロードするように変更しないでください。
  • Func Python 環境に互換性のあるバージョンの ldap3、cryptography、requests がインストールされていることを確認してください。Func Python のバージョンと互換性のある依存関係を選択し、実行環境内の他のスクリプトの依存関係を上書きしないでください。プライベート CA ファイルは Func 実行コンテナにマウントする必要があります。
  • 後述の Func 環境変数を設定し、core を先に公開してから main を公開してください。Func の呼び出しは公開済みのスクリプトに依存するため、下書きを保存するだけでは不十分です。
  • 必要な関数に対して同期関数 API を作成します。外部認証では ldap_client_demo__main.authenticate、属性加工では ldap_client_demo__main.transform を使用します。関数のパラメータ名は body、型は文字列です。同期呼び出し URL をコピーし、非同期タスクインターフェースは使用しないでください。

環境変数、スクリプト公開、関数 API については、Func 基本概念 と 関数の作成と呼び出し を参照してください。

Func 環境変数

名前 説明
LDAP_CLIENT_DEMO_SECRET 未設定でもかまいません。デフォルトは空文字列です。リクエストボディを暗号化する場合は、Studio の external.secret と同じ非空の値を設定します。安全に生成されたランダムな共有値を使用することを推奨し、スクリプト、URL、ログには書き込まないでください。
LDAP_CLIENT_DEMO_CONFIG JSON オブジェクトまたは JSON 文字列で、内容は LDAPClientSet 内部の LDAP 設定です。LDAPClientSet でさらにラップしません。外部認証にはディレクトリ設定が必要です。属性加工のみの場合も、Studio と同じ mapping を設定してください。

標準の LDAP 認証関数を使用する場合の LDAP_CLIENT_DEMO_CONFIG の例:

{
  "server": {
    "hosts": ["ldaps://ldap-a.example.com:636", "ldaps://ldap-b.example.com:636"],
    "connect_timeout": 6,
    "use_ssl": true,
    "get_info": "SCHEMA",
    "tls": {"validate": "CERT_REQUIRED", "ca_certs_file": "/config/ldap/ca.pem"}
  },
  "connection": {"authentication": "SIMPLE", "client_strategy": "SAFE_SYNC", "receive_timeout": 5},
  "startTls": false,
  "authTimeoutSeconds": 20,
  "baseDN": "dc=example,dc=com",
  "bindDN": "cn=studio-reader,ou=service,dc=example,dc=com",
  "bindPassword": "<サービスアカウントパスワード>",
  "searchAttribute": "uid",
  "personObjectClass": "inetOrgPerson",
  "mapping": {"exterId": "uid", "username": "uid", "email": "mail", "name": "displayName"}
}

サンプルを実際のディレクトリ設定に置き換え、ID フィールドを既存のデプロイメントと一致させてください。設定に bindPassword が含まれる場合は、JSON 文字列全体を Func 環境変数の password タイプで保存します。スクリプトがこの文字列を解析するため、通常の JSON 変数リストにサービスパスワードが表示されるのを避けられます。LDAP_CLIENT_DEMO_SECRET も password タイプを使用します。この設定は Studio のマルチドメインコントローラーと同じ有界スイッチングルールを使用するため、ユーザーパスワードの送信後に自動的に別のノードへ切り替えて再試行することはありません。属性加工のみの場合は、次のように設定するだけです。

{"mapping": {"exterId": "uid", "username": "uid", "email": "mail", "name": "displayName"}}

どちらのモードも main.py の _settings() を使用するため、属性加工の場合も有効な JSON オブジェクトを提供する必要があります。サービスアカウント、LDAP アドレス、secret をリクエストパラメータから受け取らないでください。

関数 API のアクセス制御

サンプルの 2 つの @DFF.API エントリはどちらも require_api_auth=False に設定されているため、関数インターフェースの作成時に Func API 認証をバインドしなくてもかまいません。同期実行、20 秒のタイムアウト、cache_result=0 を維持してください。パスワード認証の結果をキャッシュしないでください。

Studio は以下の kwargs.body JSON リクエストを固定で送信し、カスタム認証 Header は付与しません。そのため、前述の追加 API 認証をバインドしていない Func 同期エントリに直接接続できます:

Studio → 管理された内部ネットワーク内の Func 同期関数 API

デモインターフェースは管理された内部ネットワーク内でのみ公開し、接続元とリクエストレートを制限し、認証パラメータとリクエストボディの収集を無効にしてください。実際のパスワードを送信するときは、信頼された HTTPS を使用してください。Func API 認証の強制を解除しても、関数内の LDAP パスワード検証がスキップされたり、Func のグローバル認証が無効になることはありません。secret はリクエストボディのオプション暗号化のみを制御し、LDAP 検証、TLS、ネットワークアクセス制御の代わりにはなりません。

既存の関数インターフェースに API 認証がバインドされている場合、スクリプトを更新しても自動的には解除されません。インターフェース管理者が実際の接続ポリシーに従って対応する必要があります。追加の Header を要求するインターフェース認証を引き続き使用する場合、顧客が展開する管理対象のエントリで対応してください。これはサンプルの直接接続モードでは必須の手順ではありません。オプションの設定については、Func API 認証 を参照してください。

headersFile、keyFile、その他の Studio 認証フィールドを追加する必要はありません。また、Func の MCP プログラミングアドレスや開発用 Access-Token をログインビジネスエントリとして使用しないでください。

Studio での設定

アプリケーション設定の説明 に従って、Launcher forethought-core の Core 設定を変更し、関連サービスを再起動します。

外部認証

LDAPClientSet:
  enable: true
  authMode: external
  authTimeoutSeconds: 20
  # Func が返す属性を解釈します。既存ユーザーおよび Func 設定と一致させる必要があります。
  mapping:
    exterId: uid
    username: uid
    email: mail
    name: displayName
  external:
    # authenticate の完全な同期 HTTP(S) 関数インターフェースアドレス。Studio サービスからアクセス可能である必要があります。
    # LDAP アドレス、Func コンソールページ、MCP プログラミングアドレスではありません。リダイレクトは受け付けません。
    url: "https://func.example.com/ldap/authenticate"
    # 未設定でも可。有効にする場合は Func 環境変数の値と同じ値とし、リクエストとともに送信しません。
    secret: ""
    # オプション。HTTPS プライベート CA の Studio コンテナ内のパス。空の場合はシステムのトラストストアを使用します。
    caCertsFile: ""

external モードでは、Studio は server、connection、bindDN などのローカル LDAP パラメータを使用しません。フォールバック用に元の設定を保持してもかまいません。実際のディレクトリ設定は Func 環境変数で管理されます。

組み込み認証後の属性加工

既存の server、connection、startTls、ディレクトリ検索、ID マッピング設定を保持し、モードと外部エントリのみを次のように変更します:

# 以下は LDAPClientSet 配下にマージする必要がある設定の断片です。これで LDAP 設定全体を上書きしないでください。
LDAPClientSet:
  authMode: builtin_transform
  authTimeoutSeconds: 20
  external:
    # transform 関数の同期 HTTP(S) インターフェースアドレス。
    url: "https://func.example.com/ldap/transform"
    secret: ""
    caCertsFile: ""

external.url は 2 つの外部モードの両方で必須です。従来の builtin モードではこの設定は無視されるため、デフォルトの空 URL のままで既存のデプロイメントに影響しません。URL にはアカウントパスワードやフラグメントを含めないでください。信頼された HTTPS の使用を推奨します。HTTP はトランスポート層の保護を提供しないため、実際のログインパスワードの送信には使用しないでください。

authTimeoutSeconds は、新しいチェーンの認証と属性準備にかかる全体の制限時間です。整数 1~60 秒、デフォルトは 20 秒です。各ステップごとに個別に計時されるわけではなく、後続のアカウントトランザクションをカバーするものでもありません。Func サンプルの実行タイムアウトは 20 秒です。LDAP、ゲートウェイ、Studio の全体の制限時間を考慮して設定し、無制限に待機しないようにしてください。Func が失敗、タイムアウト、またはプロトコルエラーとなった場合は、今回のログインは失敗し、他の認証モードへのフォールバックは行われません。

body リクエストとレスポンスプロトコル

Func エントリの定義は常に def authenticate(body="") または def transform(body="") です。Studio は JSON を POST で送信し、外側は次の固定形式です:

{"kwargs": {"body": "リクエストボディの文字列"}}

外部認証の論理リクエストオブジェクトは次のとおりです:

{"username": "user1", "password": "pass1"}

両方で secret を設定していない場合、body はこのオブジェクトを 1 回シリアライズした JSON 文字列です。実際の HTTP リクエストの例:

{"kwargs": {"body": "{\"username\":\"user1\",\"password\":\"pass1\"}"}}

両方で同じ非空の secret を設定している場合、body は v2:... 形式の暗号化文字列です。ダウンロードした core.py が暗号化/復号を担当するため、手動で実装する必要はありません。片方のみの設定、両方の不一致、暗号文の検証失敗はいずれもリクエストが拒否され、平文へのフォールバックはありません。スペースも secret の内容に含まれるため、両方で完全に一致させる必要があります。

プロトコルは HKDF-SHA256 で 32 バイトの鍵を導出し、AES-256-GCM で暗号化します。HKDF の salt は空で、鍵導出コンテキストは ldap-forward/v2、AAD は ldap-forward/v2/request です。転送形式は v2: に標準 Base64 を加えたもので、エンコード内容は 12 バイトのランダムな nonce、暗号文、16 バイトの認証タグの順です。リクエスト内の body のみが暗号化され、戻り値は引き続き JSON です。このプロトコルはリクエストの有効期限やリプレイの重複排除機能を提供しないため、HTTPS とエントリのアクセス制御が依然として必要です。

この AES-GCM 認証タグは暗号化されたリクエストボディの完全性を検証するためのもので、追加のデジタル署名ではありません。Studio は sign、signature、署名 Header、AK/SK を追加しません。顧客が署名キーファイルを準備する必要もありません。両方で secret を設定していない場合は、JSON 文字列をそのまま透過的に渡し、署名や暗号化認証タグは付与しません。

ブランド非依存のプロトコル: 暗号化は双方で合意した secret と前述のブランド非依存プロトコルパラメータのみを使用し、製品名、ブランド識別子、ブランドドメイン名は読み取りません。ページの文案やヘルプアドレスはブランドに応じて置き換えられますが、鍵導出や暗号文の検証には関与しません。

属性加工の論理リクエストオブジェクトは次のとおりです:

{
  "username": "user1",
  "userinfo": {"uid": ["user1"], "mail": ["user1@example.com"], "memberOf": ["cn=readers,dc=example,dc=com"]}
}

ここには今回のログインパスワードは送信されません。関数はビジネスオブジェクトを返すだけでよく、外部認証成功の例は次のとおりです:

return {"status": "success", "userinfo": {
    "uid": ["user1"],
    "mail": ["user1@example.com"],
    "displayName": ["示例用户"]
}}

パスワードが誤っている場合やログインが許可されていない場合は {"status": "denied"} を返し、ディレクトリ、設定、処理の例外がある場合は {"status": "error"} を返します。パスワード、例外スタック、内部設定を含めないでください。ネイティブ Func 同期 API はこのビジネスオブジェクトを直接返すため、関数自体で ok や data でラップする必要はありません。Studio は従来のエントリ向けに次のラッパーにも対応しています:

{"ok": true, "data": {"result": {"status": "success", "userinfo": {"uid": ["user1"], "mail": ["user1@example.com"]}}}}

Studio は HTTP 200 を要求し、ネイティブのビジネスオブジェクトまたは前述のラッパー内のビジネス結果を検証します。外側に ok が存在する場合はラッパーを厳密に検証し、外側の失敗や不正な形式の場合にネイティブの成功へフォールバックすることはありません。非同期タスク ID、リダイレクト、HTML ページ、任意のテキストは有効な認証結果ではありません。復号後のリクエストオブジェクト、ユーザー属性オブジェクト、Func のビジネス結果はそれぞれ最大 256 KiB、HTTP レスポンスボディは最大 1 MiB です。大量の無関係な属性を返さないでください。

認証または属性加工ロジックの変更

カスタム認証

main.py の authenticate は、デフォルトで同一ソースの LDAP 実行器を呼び出して実際のユーザー名とパスワードを検証します。他の ID サービスと連携する場合は、_run() の認証ブランチで実際の検証手順を置き換えてください。最初に顧客の信頼する ID サービスを呼び出してパスワードまたは認証クレデンシャルが有効であることを確認してから userinfo を構築します。認証が拒否された場合は denied、サービス例外の場合は error を返します。ユーザー名が存在するというだけで成功を返さないでください。

userinfo のフィールド名は mapping によって決まります。たとえば mapping.exterId: uid の場合、非空の uid を返す必要があり、マッピング可能なユーザー名またはメールアドレスも最低 1 つ必要です。ID フィールドは文字列または単一要素の文字列配列にできますが、複数の候補 ID を返すことはできません。他の認可に必要なグループ属性は既存の構造を踏襲してください。

複数の独立した ID ソースを統合する場合、顧客関数が ID ソースを特定して ID を検証します。exterId は統合範囲内でグローバルに安定かつ一意である必要があります。同名または同メールアドレスでも同一人物とは限らず、既存の LDAP/OIDC アカウントは自動的に統合されません。既存ユーザーを移行する前に、ID ソースと既存のバインディングを確認し、重複アカウントや ID の競合を避けてください。

属性加工

まず ldap_client_demo__main.transform 用の同期 HTTP インターフェースを作成し、Studio で authMode: builtin_transform を設定して external.url をそのインターフェースに指定します。元の LDAP 接続、検索、TLS、mapping を共用します。リクエストボディの暗号化を有効にする場合は、引き続き Func と同じ secret を使用します。

ダウンロードしたサンプルの patch = {} は、デフォルトでは属性を変更しません。顧客は main.py の transform_only ブランチで実際の加工ルールを記述し、変更後に main を再公開します。例:

# 例:実際の値は信頼できるビジネスルールまたは属性サービスで計算し、クライアントから任意に指定された値は受け取らないこと。
patch = {"displayName": ["业务显示名"]}
core.merge_transform(original, patch, config["mapping"])
return {"status": "success", "userinfo": patch}

同時に、Studio と Func の mapping で name: displayName を設定し、他の元のマッピングは維持します。空のパッチ {} は元の属性を保持することを意味します。パッチはトップレベルフィールド単位でマージされ、再帰的なマージではありません。返されなかったフィールドは保持されますが、明示的に空値または空配列を返すとフィールドがクリアされ、ロールマッピングの結果が変わる可能性があります。

属性加工では mapping.exterId に対応する ID 値を変更しないでください。他の属性を変更する場合も、事前にワークスペースとロールマッピングを検証してください。ネストされたオブジェクトを更新する場合は、そのフィールドに必要な完全な内容を返してください。パスワード、トークン、秘密鍵、共有 secret などのクレデンシャルフィールドを返さないでください。

安全検証とフォールバック

旧暗号化プロトコルはテスト環境専用です。v2 に切り替える場合は、関連するテスト認証を一時停止し、処理中のリクエストと Func キューが空になるのを待ってから、新しい core を公開し、その後に Studio を更新し、検証後に復元してください。両端のプロトコルバージョンは一致している必要があります。古い暗号文は自動的に受け入れられず、平文へのフォールバックもありません。secret を設定していない JSON の透過転送は影響を受けません。

本番稼働前に、合成データと専用テストアカウントで次の点を検証してください:

  1. 両側の secret が空の場合、Func は JSON 文字列を受け取ります。両側で同じ secret を設定している場合は暗号文を受け取り、認証が成功します。両側が不一致の場合は失敗します。
  2. 正しいパスワードは成功し、誤ったパスワードは拒否されます。Func の停止、タイムアウト、レスポンス形式のエラー時は拒否され、旧認証に切り替わりません。
  3. builtin_transform では、誤ったパスワードのリクエストは Func に入りません。空パッチでは ID とグループ属性が保持され、ID 値を変更するパッチは拒否されます。
  4. 既存ユーザーは引き続き元のアカウントに入り、アカウント情報、ワークスペース、ロールが期待どおりであること。既存のポリシーに従って後続のログインと MFA が引き続き実行されること。
  5. Studio、Func、ゲートウェイ、タスク記録、APM の収集ポリシーを確認し、認証リクエストパラメータ、パスワード、secret がログやモニタリングに入らないようにしてください。サンプルのエントリはこれらを出力しませんが、プラットフォームのパラメータおよびタスク収集を自動的にオフにするわけではありません。暗号化もこの確認の代わりにはなりません。通常のユーザー属性についても、最小限の必要な範囲のみ返してください。

失敗を調査するときは、まず Studio からゲートウェイ/Func への DNS、ネットワーク、CA を確認し、次に Func API の同期方式、アクセス認証、両側の secret、マッピングとレスポンス構造を確認してください。エラーカテゴリと trace 情報を確認し、完全なリクエストボディや設定を出力して調査しないでください。

フォールバックが必要な場合は、アップグレード前の authMode: builtin、元の単一アドレス server.host、元の LDAP 設定を復元し、server.hosts を空にするか削除して、関連サービスを再起動してください。Func スクリプトを削除する必要はありません。フォールバックでは、加工済みの属性を自動的に復元したり、生成済みのアカウントを統合したりしません。既存のアカウントと認可状態を個別に確認する必要があります。

フィードバック

このページは役に立ちましたか?