ローカルモックサーバー

keenauth mock serve は、ネットワークにアクセスできない CI ジョブとローカル開発のために、OpenID Connect、SAML 2.0、SCIM 2.0 の小さなサーバーと、/v1 管理 API のメモリ内版を手元のマシンで起動します。KeenAuth の環境、API キー、インターネット接続は必要ありません。すべてのデータはメモリに保持され、プロセスが終了すると削除されます。

モックサーバーは KeenAuth そのものではありません。以下に示すフローのみを実装し、すべてのリクエストを自動的に承認し、実際のサービスよりはるかに少ない検査しか行いません。実際の動作の基準は、ホスト型の development 環境だけです。リリースする前に、その環境に対してエンドツーエンドテストを実行してください。

サーバーの起動

keenauth mock serve

既定では、サーバーは 127.0.0.1:4010 で待ち受けます。これはループバックアドレスで、他のマシンからは到達できません。--port でポートを変更できます(0 を指定すると空いているポートが選ばれます)。--host でアドレスを変更できますが、ループバック以外のアドレスを指定するとモックサーバーがネットワークに公開され、CLI は警告を表示します。Ctrl+C で停止します。

このコマンドは認証情報を読み込まず、KeenAuth へ何も送信しません。

エンドポイント

パス 用途
/.well-known/openid-configuration OIDC ディスカバリードキュメント(/.well-known/oauth-authorization-server でも提供)
/.well-known/jwks.json 公開テスト鍵セット(鍵 ID keenauth-test-1)
/oauth2/authorize 認可エンドポイント:PKCE(S256)を使った認可コードフロー
/oauth2/token トークンエンドポイント:authorization_code と refresh_token の grant
/oauth2/userinfo UserInfo エンドポイント(スコープ openid を持つ Bearer アクセストークン)
/saml-idp/<application_id>/metadata SAML 2.0 アイデンティティプロバイダーのメタデータ
/saml-idp/<application_id>/sso SAML シングルサインオンエンドポイント(HTTP-Redirect および HTTP-POST バインディング)
/scim/v2/<directory_id>/… SCIM 2.0 エンドポイント:Users、Groups、ServiceProviderConfig、ResourceTypes、Schemas
/v1/… メモリ内の管理 API:管理 API の OpenAPI ドキュメントの全操作

ディスカバリードキュメントと SAML メタデータのエンドポイント URL は、サーバーが待ち受けているローカルアドレスを指します。<application_id> と <directory_id> は任意の名前です(A-Z、a-z、0-9、-、_ からなる 1〜64 文字)。事前に登録する必要はありません。

動作

トークンと検証器

トークンはオフラインテストキットの公開テスト鍵で署名され、ローカルアドレスではなく発行者 https://test.keenauth.invalid を持ちます。トップレベルドメイン .invalid は予約されているため、本番の検証器がこのトークンを受け付けることは決してありません。SDK の検証器は、テストコードで明示的に有効にしたテストモードでのみこれらを受け付けます。

// Go: accept tokens from the mock server in a test.
v, err := keenauthtest.NewVerifier(verify.Config{Audience: "https://test.keenauth.invalid/oauth2/userinfo"})
if err != nil {
	t.Fatal(err)
}
claims, err := v.VerifyAccessToken(ctx, accessToken, nil)

TypeScript(createTestVerifier)と Python(keenauth.testing.verifier)でも同じテストモードを使用します。ディスカバリードキュメントの issuer が取得元 URL と一致することを要求する汎用の OIDC クライアントライブラリは、モックサーバーのディスカバリードキュメントを拒否します。その場合はエンドポイント URL を直接設定するか、development 環境に対してテストしてください。

例:curl による完全なフロー

この例には curl、openssl、jq が必要です。先に別のターミナルでサーバーを起動してください。

BASE=http://127.0.0.1:4010
REDIRECT=http://127.0.0.1:8080/callback
VERIFIER=$(openssl rand -hex 32)
CHALLENGE=$(printf '%s' "$VERIFIER" | openssl dgst -sha256 -binary | openssl base64 -A | tr '+/' '-_' | tr -d '=')

# 1. Authorize: the server answers with a redirect that carries the code.
LOCATION=$(curl -s -o /dev/null -w '%{redirect_url}' --get "$BASE/oauth2/authorize" \
  --data-urlencode response_type=code --data-urlencode client_id=app_local \
  --data-urlencode redirect_uri="$REDIRECT" --data-urlencode 'scope=openid email offline_access' \
  --data-urlencode state=xyz --data-urlencode code_challenge="$CHALLENGE" \
  --data-urlencode code_challenge_method=S256)
CODE=$(printf '%s' "$LOCATION" | sed -E 's/.*[?&]code=([^&]+).*/\1/')

# 2. Exchange the code for tokens.
TOKENS=$(curl -s "$BASE/oauth2/token" -d grant_type=authorization_code -d client_id=app_local \
  --data-urlencode code="$CODE" --data-urlencode redirect_uri="$REDIRECT" -d code_verifier="$VERIFIER")

# 3. Call UserInfo with the access token.
curl -s "$BASE/oauth2/userinfo" -H "Authorization: Bearer $(printf '%s' "$TOKENS" | jq -r .access_token)"

# 4. Refresh.
curl -s "$BASE/oauth2/token" -d grant_type=refresh_token -d client_id=app_local \
  --data-urlencode refresh_token="$(printf '%s' "$TOKENS" | jq -r .refresh_token)"

手順 3 はテストユーザーを表示します:

{"email":"test.user@example.test","email_verified":true,"sub":"usr_01m3vnbwg0e8mbe7h7xxp6rmkc"}

SAML アイデンティティプロバイダー

開発中の SAML サービスプロバイダーのアイデンティティプロバイダーとして、モックサーバーを使用できます。サービスプロバイダーにメタデータ URL を指定してサインインすると、モックサーバーがテストユーザー向けの署名付きレスポンスを返します。パスは、KeenAuth が後のリリースで予定しているホスト型 SAML アイデンティティプロバイダーに準拠しています。

署名鍵は公開されています。 公開されたラベルから導出されているため、誰でもこの証明書で検証されるレスポンスに署名できます。SHA-256 フィンガープリントは fcc2fc37b9ddb800721283156f9eb26ff1b80f099d996a65650f8cb469a9be81 で、サブジェクトには TEST ONLY と書かれています。サービスプロバイダーの開発環境およびテスト環境の設定にのみ登録してください。本番環境には決して登録しないでください。KeenAuth 自身の SAML 接続では、この制限を強制しています。ステージングおよび本番環境では、メタデータのインポートや証明書の追加時、さらにサインイン時にも、この鍵と .invalid 配下のエンティティ ID を拒否します。開発環境では受け付けます。

例:curl による SAML サインイン

この例には curl と openssl が必要です。先に別のターミナルでサーバーを起動してください。

BASE=http://127.0.0.1:4010
APP=app_local
ACS=http://127.0.0.1:8080/saml/acs

# 1. IdP metadata: entity ID, signing certificate and SSO URL for your service provider.
curl -s "$BASE/saml-idp/$APP/metadata" -o idp-metadata.xml

# 2. Send an AuthnRequest with the HTTP-POST binding (base64, no compression).
NOW=$(date -u +%Y-%m-%dT%H:%M:%SZ)
REQUEST=$(printf '<samlp:AuthnRequest xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="_req1" Version="2.0" IssueInstant="%s" AssertionConsumerServiceURL="%s"><saml:Issuer>https://sp.example.com</saml:Issuer></samlp:AuthnRequest>' "$NOW" "$ACS" | openssl base64 -A)
curl -s "$BASE/saml-idp/$APP/sso" --data-urlencode SAMLRequest="$REQUEST" --data-urlencode RelayState=xyz -o form.html

# 3. The page posts SAMLResponse to the ACS URL by itself; decode it to look at the signed response.
sed -n 's/.*name="SAMLResponse" value="\([^"]*\)".*/\1/p' form.html | openssl base64 -d -A > response.xml
grep -o 'InResponseTo="[^"]*"' response.xml | head -1

最後のコマンドは InResponseTo="_req1" を表示します。サービスプロバイダーは、idp-metadata.xml の証明書を使って response.xml を検証します。

SCIM 2.0 エンドポイント

SCIM エンドポイントは、プロビジョニングコネクターや、ユーザーとグループを作成するスクリプトなど、SCIM クライアントをネットワークなしでテストするために使用します。各 <directory_id> は、プロセスが動いている間だけ存在する、独立した空のディレクトリです。

例:curl でユーザーをプロビジョニングする

この例には curl と jq が必要です。先に別のターミナルでサーバーを起動してください。

SCIM=http://127.0.0.1:4010/scim/v2/dir_local
AUTH='Authorization: Bearer keenauth-mock-scim-token'
CT='Content-Type: application/scim+json'

# 1. Create a user; the response carries the id, meta.version and an ETag header.
USER_ID=$(curl -s "$SCIM/Users" -H "$AUTH" -H "$CT" -d '{
  "schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
  "userName": "ada@example.test",
  "name": {"givenName": "Ada", "familyName": "Lovelace"},
  "emails": [{"value": "ada@example.test", "type": "work", "primary": true}]
}' | jq -r .id)

# 2. Find it with a filter.
curl -s -G "$SCIM/Users" -H "$AUTH" --data-urlencode 'filter=userName eq "ada@example.test"' | jq .totalResults

# 3. Deactivate it with PATCH, guarded by the version you read.
curl -s -X PATCH "$SCIM/Users/$USER_ID" -H "$AUTH" -H "$CT" -H 'If-Match: W/"1"' -d '{
  "schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
  "Operations": [{"op": "replace", "path": "active", "value": false}]
}' | jq -c '{active, version: .meta.version}'

# 4. Create a group with the user as a member, then delete the user.
curl -s "$SCIM/Groups" -H "$AUTH" -H "$CT" -d '{
  "schemas": ["urn:ietf:params:scim:schemas:core:2.0:Group"],
  "displayName": "Engineering",
  "members": [{"value": "'"$USER_ID"'"}]
}' | jq -c '.members[0] | {display, type}'
curl -s -o /dev/null -w '%{http_code}\n' -X DELETE "$SCIM/Users/$USER_ID" -H "$AUTH"

コマンドは 1、{"active":false,"version":"W/\"2\""}、{"display":"ada@example.test","type":"User"}、204 を表示します。

管理 API(/v1)

管理 API のエンドポイントは、KeenAuth の管理 API を呼び出すコード(SDK、CLI、デプロイスクリプトなど)を、ネットワークなしの CI でテストするために使用します。パスはホスト型のものと同じです。ホスト型の代わりに基底 URL http://127.0.0.1:4010/v1 を使うだけで、他は変わりません。エンドポイントは CLI に組み込まれた管理 API の OpenAPI ドキュメントから生成されるため、そのバージョンの API の全操作に応答します。

development 環境との違い。 モックサーバーが検証するのは、本文が必須フィールドを持つ JSON オブジェクトであることだけです。フィールドの形式、長さ、列挙値、一意性は検査されず、型の誤ったフィールドは無視されます。If-Match、Idempotency-Key、その他のヘッダーは無視され、ETag は返されません。権限、プランの上限、クォータ、レート制限、リージョンは検査されず、個人データはマスクされず、メールは送信されず、ジョブは実行されません(受け付けられたジョブは作成時の状態のままです)。環境は 1 つだけです。レコードはプロセスの存続期間中だけ存在し、最大 10,000 件です。実際の API の動作を確認するには、development 環境を使用してください。

例:curl で作成、読み取り、変更、削除する

この例には curl と jq が必要です。先に別のターミナルでサーバーを起動してください。

API=http://127.0.0.1:4010/v1
AUTH='Authorization: Bearer keenauth-mock-management-token'
CT='Content-Type: application/json'

# 1. Create an organization.
ORG_ID=$(curl -s "$API/organizations" -H "$AUTH" -H "$CT" -d '{"name": "Acme"}' | jq -r .id)

# 2. Read it back and list.
curl -s "$API/organizations/$ORG_ID" -H "$AUTH" | jq -c '{id: (.id | .[0:4]), name}'
curl -s "$API/organizations" -H "$AUTH" | jq -c '{count: (.data | length), list_metadata}'

# 3. Rename it with a merge patch.
curl -s -X PATCH "$API/organizations/$ORG_ID" -H "$AUTH" -H 'Content-Type: application/merge-patch+json' -d '{"name": "Acme Inc"}' | jq -r .name

# 4. Delete it; a second read is a 404 problem.
curl -s -o /dev/null -w '%{http_code}\n' -X DELETE "$API/organizations/$ORG_ID" -H "$AUTH"
curl -s "$API/organizations/$ORG_ID" -H "$AUTH" | jq -c '{status, code}'

# 5. Without the token.
curl -s "$API/organizations" | jq -c '{status, code}'

コマンドは {"id":"org_","name":"Acme"}、{"count":1,"list_metadata":{"after":null,"before":null}}、Acme Inc、204、{"status":404,"code":"not_found"}、{"status":401,"code":"unauthenticated"} を表示します。

非対応

テスト用クロック、DPoP、PAR、リクエストオブジェクト、デバイスフロー、クライアントクレデンシャル、トークン交換、ログアウトや取り消し、ホスト型ページはありません。SAML アイデンティティプロバイダーには、シングルログアウト、暗号化アサーション、アーティファクトバインディング、IdP 起点のサインインはありません。SCIM エンドポイントには、一括操作、ソート、/Me、入れ子のグループはなく、フィルターは or、not、括弧、gt/ge/lt/le を扱えません。これらのテストには development 環境を使用してください。