Enterprise エディション
SaaS — 詳細リファレンス
Enterprise SaaS モジュールは、NextPDF ベースのサービスのためのマルチテナントの構成要素を提供します。
TenantContextは、認証済みコンテキストからのみ解決される、イミュータブルな ID 値オブジェクトです。ApiKeyGeneratorとApiKeyAuthenticatorは、プレフィックス付き・チェックサム付き・ハッシュ保存の API キーを発行および検証します。QuotaCheckerは、テナントごとのクォータに対してリクエストをゲートします(80% で警告、100% で拒否、使用量が不明な場合はフェイルクローズで拒否)。SidecarJwtMinterは、コンポーネント間呼び出しのための短命な HS256 サービストークンを生成します。UsageMeterとStripeMeteringSyncerは、使用量イベントをプルし、決定的なべき等性を用いて課金プロバイダーに同期します。
提供状況とライセンス
「提供状況とライセンス」という見出しのセクションこのケイパビリティは NextPDF Enterprise(nextpdf/enterprise)に同梱されており、Enterprise ティアのライセンスエンベロープで有効化されます。その権利がないデプロイでは、ケイパビリティのクラスはロードされません。エディションを比較してライセンスを取得。
SaaS サーフェスは Enterprise の基本ケイパビリティであり、機能ごとの独立したフラグはありません。NextPDF Core(Apache-2.0)と NextPDF Pro には、テナンシー、API キー、クォータのモデルがありません。このケイパビリティに下位ティアの同等品はありません。
composer require nextpdf/enterprise:^3パブリック API サーフェス
「パブリック API サーフェス」という見出しのセクションすべてのシンボルは NextPDF\Enterprise\SaaS 配下にあります。
| シンボル | パラメーター | デフォルト挙動 | 戻り値 | スロー/失敗条件 | 備考 |
|---|---|---|---|---|---|
TenantContext | string $tenantId, string $source, array $scopes = ['read'] | イミュータブルな ID 値オブジェクト | 値オブジェクト | なし | ソース: jwt、mtls、api_key。hasScope() / hasAnyScope() でスコープを判定 |
TenantContext::singleTenant() | なし | read、write、admin を持つ固定 default テナント | TenantContext | なし | シングルテナントデプロイ向け |
ApiKeyAuthenticator::authenticate() | string $rawKey | 6 ステップの検証後にコンテキスト解決 | TenantContext | ApiKeyAuthenticationException(HTTP 401) | コンテキストの source は api_key。スコープはキーレコードから複製 |
ApiKeyAuthenticator::requireScope() | TenantContext $context, ApiKeyScope $requiredScope | 明示的なスコープアサーション | void | ApiKeyAuthenticationException::insufficientScope()(HTTP 403) | スコープ強制は別個の明示的なステップ |
ApiKeyGenerator::generateLive() / ::generateTest() | なし | 新規キー: プレフィックス、32 文字の base62 本体(192 ビットエントロピー)、4 文字のチェックサム | array{key, hash, prefix} | なし | プレフィックス npf_live_ / npf_test_。hash はストレージダイジェスト |
ApiKeyGenerator::validateChecksum() | string $key | プレフィックス、長さ、CRC32 チェックサムの形状チェック | bool | なし | データストア参照前の入力ミスガード。セキュリティコントロールではない |
ApiKeyGenerator::hashKey()(静的) | string $key | 生キーの SHA-256 16 進ダイジェスト | string | なし | キーの唯一の保存表現 |
ApiKeyGenerator::isLiveKey() / ::isTestKey() | string $key | プレフィックス検査 | bool | なし | 参照なしで環境が判別可能 |
ApiKey | id、テナント、キーハッシュ、表示プレフィックス、スコープマスク、作成/有効期限/失効の各時刻 | 保存キーレコード。平文は保存されない | 値オブジェクト | なし | isActive()、isRevoked()、isExpired()、scopeNames() |
ApiKeyScope | backed enum: Read = 1、Write = 2、Admin = 4 | ビットマスクスコープモデル | enum | なし | maskFromNames()、fromName()、fullAccess()。未知の名前はマスクビルダーで無視 |
ApiKeyRepositoryInterface | — | ストレージコントラクト。ハッシュのみ保存 | — | 実装依存 | findByHash()、findActiveByTenant()、store()、revoke() |
SidecarJwtMinter::__construct() | string $secret, issuer, audience, int $ttlSeconds = 300 | 構築時に 16 バイト未満の署名シークレットを拒否 | インスタンス | InvalidArgumentException | 128 ビットの鍵強度下限。32 バイト以上のランダムバイトを推奨 |
SidecarJwtMinter::mint() | TenantContext $tenant | iss、aud、sub、scope、tenant_id、iat、exp、jti を持つ HS256 JWT | string | クレームエンコード失敗時に JsonException | デフォルト有効期間 5 分。jti は 16 ランダムバイトの 16 進エンコード |
QuotaChecker::check() | TenantContext $tenant, TenantQuota $quota | 現在の使用量を読み取り、80% で警告、100% で拒否、使用量不明時は拒否 | array{allowed: bool, warning_percentage: float|null} | QuotaExceededException, QuotaUnavailableException | 両しきい値でアラートコールバックを呼び出し |
TenantQuota | float $maxCuPerPeriod、コレクション、ストレージバイト、同時実行ジョブ | 期間ごとの上限。80% ソフトしきい値定数 | 値オブジェクト | なし | fromConfig() デフォルト: 10,000 CU、100 コレクション、10 GB、10 ジョブ |
QuotaExceededException::toErrorEnvelope() | なし | SPEC-QUOTA-001 エラーエンベロープ | array | — | HTTP 402、リトライ不可。現在値、上限、リセット時刻を保持 |
QuotaUnavailableException::toErrorEnvelope() | なし | SPEC-QUOTA-503 エラーエンベロープ | array | — | HTTP 503、リトライ可能。理由 usage_undeterminable |
UsageMeter::pullUsage() | array<string, int> $watermarks | 構成済みの各使用量ソースホストをそのカーソルからポーリング | array{events, instance_id} | 全ホスト到達不能時に UsageMeterException | 部分的な障害は許容。到達不能ホストはログ記録してスキップ |
UsageMeter::getCurrentUsage() | string $tenantId | 当期のコンピュートユニット使用量 | float | 使用量が判定不能な場合に UsageMeterException | 解析可能なゼロは権威あり。不明な使用量は例外をスロー |
StripeMeteringSyncer::sync() | array<string, int> $watermarks | プル、変換、送信の 1 サイクル | array{watermarks, sent, failed} | なし。送信失敗は DLQ コールバックへルーティング | プル失敗はカーソルを保持する no-op サイクルを返す |
StripeAdapter::sendMeterEvent() | MeterEvent $event | べき等性ヘッダー付きでプロバイダーへ POST | void | StripeSyncException | HTTP 429 と 5xx はリトライ可能。その他の 4xx はリトライ不可 |
StripeAdapter::sendBatch() | list<MeterEvent> $events | 各イベントを送信し、失敗を収集 | list<StripeSyncException> | なし | 空リストは全イベント成功を意味する |
MeterEvent | メーター名、テナント、値、べき等性キー、タイムスタンプ | イミュータブルなメーターイベント値オブジェクト | 値オブジェクト | なし | toStripePayload() がプロバイダーペイロードをシリアライズ |
final readonly class ApiKeyAuthenticator{ public function __construct( private ApiKeyRepositoryInterface $repository, private ApiKeyGenerator $generator, private LoggerInterface $logger, ) {}
public function authenticate(string $rawKey): TenantContext {}
public function requireScope(TenantContext $context, ApiKeyScope $requiredScope): void {}}final class QuotaChecker{ public function __construct( private readonly UsageMeterInterface $usageMeter, private readonly LoggerInterface $logger, private readonly Closure $quotaAlertCallback, ) {}
/** @return array{allowed: bool, warning_percentage: float|null} */ public function check(TenantContext $tenant, TenantQuota $quota): array {}}interface UsageMeterInterface{ /** @return array<string, mixed> */ public function pullUsage(array $watermarks): array;
public function getCurrentUsage(string $tenantId): float;}final class StripeMeteringSyncer{ public function __construct( private readonly UsageMeterInterface $usageMeter, private readonly StripeAdapterInterface $stripeAdapter, private readonly LoggerInterface $logger, private readonly Closure $dlqCallback, ) {}
/** @return array{watermarks: array<string, int>, sent: int, failed: int} */ public function sync(array $watermarks): array {}}final readonly class SidecarJwtMinter{ public function __construct( private string $secret, private string $issuer = 'nextpdf-enterprise', private string $audience = 'nextpdf-spectrum', private int $ttlSeconds = self::DEFAULT_TTL_SECONDS, ) {}
public function mint(TenantContext $tenant): string {}}挙動コントラクト
「挙動コントラクト」という見出しのセクション- テナント ID。 テナントコンテキストはイミュータブルです(テナント識別子、解決ソース、スコープ)。ID は認証済みコンテキストからのみ解決され(
jwt、mtls、api_key)、クライアントが提供したヘッダーやクエリパラメーターからは決して解決されません。シングルテナントデプロイでは、フルスコープを持つ固定のdefaultコンテキストを使用します。 - 認証順序。 API キー認証は固定された順序で進行します(チェックサム、SHA-256 ハッシュ、リポジトリ参照、失効チェック、有効期限チェック、コンテキスト解決)。未知、失効、有効期限切れのキーは 3 つの別個の結果であり、いずれも HTTP 401。スコープ不足は HTTP 403 です。
- キーの秘匿。 生キーは保存もログ記録もされず、その SHA-256 ダイジェストのみが保存・参照されます。認証器自体はバイト単位のシークレット比較を行いません。定時間のダイジェスト参照はリポジトリ実装のコントラクトです。
- クォータしきい値。 80% のソフトリミットではリクエストは続行し、警告パーセンテージが返され、アラートコールバックが発火します。100% のハードリミットでは、リセット時刻(翌月初日の UTC 午前 0 時)を保持する
SPEC-QUOTA-001(HTTP 402)とともにリクエストが拒否されます。 - クォータのフェイルクローズ。 判定不能な使用量は、
SPEC-QUOTA-503(HTTP 503、リトライ可能)とともにリクエストを拒否します。不明な使用量がゼロとして扱われることは決してありません。本物の解析可能なゼロ使用量は権威があり、許可されます。 - アラートの重複排除。 チェッカーはアラートの重複排除を行いません。期間ごとの重複排除はコールバックの責任です。
- 計測同期。 サイクルはスケジュールされており、リクエストパス上では決して実行されません。ソースごとのウォーターマークから再開し、各カーソルを正常に送信されたイベント ID のうち最も大きいものまで進めます。べき等性キーは決定的であり(テナント、期間、イベント ID)、再送信されたイベントはプロバイダー側の重複排除で統合されます。
- プル失敗。 失敗したプルは、ウォーターマークを保持する no-op サイクル(
sent0、failed0)を返します。次のサイクルは、そのウィンドウをスキップするのではなく再試行します。 - サービストークン。 トークンは共有シークレットを用いた HS256 であり、
iss、aud、sub、scope、tenant_id、iat、exp、および一意のjtiを保持します。デフォルトの有効期間は 5 分です。構築時に 16 バイト未満のシークレットをフェイルクローズで拒否します。
エッジケースと障害モード
「エッジケースと障害モード」という見出しのセクション- 不正な形式のキーはチェックサムに失敗し、データストアへのアクセス前に拒否されます。形式は正しいが未知のキーは、参照後に拒否されます。いずれも無効キーの結果として現れます。
- 未知、失効、有効期限切れのキーは別個の例外ファクトリを使用します。
keyExpiredフラグが真になるのは有効期限切れの結果の場合のみです。これらを別個のクライアントレスポンスにマッピングしてください。 QuotaChecker::check()は許可時にのみ戻り、返されるallowedは常にtrueです。拒否と利用不可は例外的な結果です。TenantQuota::usagePercentage()は、非正のクォータに対して0.0を返します。fromConfig()は、欠落した値をデフォルトで補い、整数の上限を最低 1 にクランプします。- ウォーターマークはソースごとです。ウォーターマークが存在しない場合は、そのソースのストリームの先頭(カーソル
0)から開始します。マルチソースのデプロイは独立したウォーターマークを維持します。 - 変換は、非配列のイベント、操作またはテナントが欠落もしくは空のイベント、非正の値、マッピングされていない操作を、サイクルを失敗させずにスキップします。使用可能な正の整数 ID を欠くイベントは、警告とともに拒否されます。ランダムなフォールバックキーはプロバイダー側の重複排除を無効化し、テナントに二重課金する可能性があるためです。
- 連続 10 回の送信失敗は、クリティカルログエントリにエスカレーションされます。カウンターは、送信が成功するたびにリセットされます。失敗したすべてのイベントは、それでもデッドレターコールバックに到達します。
- 使用量ソースホストからの不正な形式の JSON ボディは、サイクル失敗ではなく空のイベントリストを生じます。
pullUsage()は、構成済みの全ホストが到達不能な場合にのみ例外をスローします。
FIPS モードの挙動
「FIPS モードの挙動」という見出しのセクション- ダイジェストと MAC のプリミティブは、ホスト PHP の暗号プロバイダーを通じた SHA-256 および HMAC-SHA256 です。FIPS 制約付きのビルドは、承認されていないアルゴリズムに対してダウングレードするのではなくフェイルクローズします。SaaS レイヤーは独自の暗号ポリシーを一切追加しません。
- キー本体とトークン識別子は、CSPRNG(
random_int()、random_bytes())から生成されます。 - CRC32 チェックサムは暗号コントロールではなく、FIPS モードの影響を受けません。
以下の記述は、引用された条項に対するケイパビリティを説明するものです。これらは認証の主張ではありません。NextPDF はこのモジュールについていかなる認証も保持していません。
| 挙動 | 参照 |
|---|---|
サービストークン exp の not-after セマンティクス | RFC 7519 §4.1.4 |
| サービストークンの JWS コンパクトシリアライゼーション | RFC 7515 §3.1 |
| 16 バイトの HS256 シークレット下限。人間が記憶可能なパスワードを MAC キーに使用しない | RFC 8725 §3.5(脅威: §2.2) |
| リポジトリのダイジェスト参照における定時間コントラクト | OWASP ASVS 5.0 §11.2.4 |
| API キーストレージダイジェスト SHA-256 | FIPS 180-4(コード宣言) |
RFC 8725 および OWASP ASVS 5.0 の引用は RAG 検証済みであり、完全な参照識別子はこのページのフロントマターに記録されています。FIPS 180-4、FIPS 198-1、BSI TR-02102-1 の参照は、製品ソース内でコード宣言されており(hash('sha256', …) およびミンターの文書化された鍵下限)、このページのために RAG コーパスから取得したものではありません。ASVS §11.2.4 の定時間要件は、認証器クラス自体ではなく、オペレーターが提供するリポジトリ実装を拘束します。
開発ノート
「開発ノート」という見出しのセクションApiKeyRepositoryInterfaceとStripeAdapterInterfaceの永続的な実装を提供してください。パッケージはコントラクトと PSR-18 プロバイダークライアントを同梱しており、永続化層は含みません。- 依存関係は PSR 抽象化のみです(PSR-3 ロガー、PSR-18 HTTP クライアント、PSR-17 リクエストおよびストリームファクトリ)。プロバイダー SDK は不要です。
- 計測同期はスケジュールされたジョブとして実行してください。各サイクル後に、返されたウォーターマークを永続的に保存してください。
- クォータの警告パーセンテージを、たとえば警告ヘッダーとしてクライアントに表示し、クォータアラートをコールバック内で期間ごとに重複排除してください。
- トークンミンターのシークレットは、高エントロピーのランダム値として構成から供給してください。32 バイト以上のランダムバイトを推奨します。パスワードから導出しては決してなりません。
- キープレフィックスにより、参照なしで環境が判別可能です。プレフィックスは保存されるダイジェストに含まれるため、sandbox キーと production キーが衝突することは決してありません。
- 内部メカニズムの詳細は、ソースリポジトリの内部ドキュメントに留まり、本マニュアルの対象外です。
本ページは、外部から観測可能な挙動とサポート対象のパブリック API サーフェスのみを記載します。内部名前空間パス、ヘルパークラス、メカニズムテーブル、ランブックのファイル名、チケットプレフィックスは対象外です。