コンテンツにスキップ
BlackOps1.xは試験的なバージョンです。Production Readyは2.xを予定しています。Releases
BlackOps
Esc
navigateopen⌘Jpreview
このページの内容

Security

Status AuthorizationとCanonical Restricted Data、HTTP・Frontend・Diagnosticsの責任分界を確認する。

BlackOpsはOperation Lifecycleを追跡し、Observed Sinkへ出すSensitive値を制御する境界を提供します。一方、Application全体のSecurity Policyは決めません。Frameworkが提供する保護と、Application/運用が実装する保護を分けて設計してください。

ProjectionはCanonical Dataから用途に必要なFieldだけを選び、Mask/Exclude/Hashを適用した表現です。#[Sensitive]はObserved Journal Projectionを指定します。

責任分界

領域 Frameworkが提供する境界 Application/運用の責務
Typed Input OperationValueとBinding Metadataを検証する 業務Validation、入力Size制限、Content Policyを実装する
Frontend Contract HTTP Operationの入力名/型、Request Binding、Typed Resultを生成し、Sensitive実値をArtifact/Resultへ含めない Credential注入、Authentication/Authorization、CORS/CSRF、Browser Storage、生成物の配布範囲を管理する
Ephemeral Outcome HTTP Route付きでInlineへ解決されたOperationだけが実値をHTTPへ一度返し、Canonical Journal/Outcome Store/Status/Console/Deferredへ保存しない Response受領後のCookie化、CSRF、暗号化、Browser Storage、Token Rotation/失効を管理する
Sensitive Projection #[Sensitive]に従いObserved JournalでOmit/Mask/Hashする 対象PropertyとModeを選び、Raw値を独自Logへ出さない
Lifecycle Journal Event、Sequence、Operation/Attempt MetadataのShapeを提供する Sinkの保存先、閲覧権限、監査、可用性を構成する
Public/Internal API #[PublicApi]付き型とInternal Namespaceを区別する Public APIだけへ依存し、Upgrade時に互換性を確認する
Deferred Claim Lease、Heartbeat、FencingでStale Claimの確定を拒否する 外部副作用の冪等性、Downstreamの重複防止を設計する
Authentication PSR-15統合、三状態Result、Actorだけを渡す境界、Opt-in Session Token Lifecycle、Invalid Credentialの安全な401を提供する User/Password/Account State、JWT/OAuth/API Key/External IdP、Cookie Attribute/CSRFを実装する
Console Operation #[ConsoleCommand]を明示したScalar入力だけを公開し、CLI値とThrowable Detailを出力しない。Execution Actorを固定する ConsoleActorProviderで安全なActor参照だけを返し、OS/運用側でCommand実行権限を制御する
Authorization #[Authorize]、Policy Contract、型付きRequest/Decision、Build時DI登録を提供する Operation、Resource、TenantごとのPolicyと現在権限の検索を実装する
Status Authorization Subjectを最小情報へ投影し、Allow前にOutcome/Journal Detailを読まず、Unknown/Denyを同じ404へする OperationStatusAuthorizerをBindingし、Current Actor、Origin Actor、Tenant/Resource Policyを評価する
Tenant Isolation 提供しない Query、Credential、Schema/Database、Cache、LogでTenantを分離する
Transport Security HTTP Adapter境界を提供する TLS終端、Certificate、Network Policyを構成する
保存時暗号化 提供しない Canonical Journal、Transport Payload、Outcome、Backupを暗号化する
Key管理 提供しない KMS/HSM、権限、Rotation、失効手順を運用する
Sink Access Control Journal Observer Contractを提供する JSONL、Log Backend、Database、Object Storageの権限を制限する
Backup/Restore 提供しない 暗号化Backup、Restore Test、破棄手順を運用する
Retention 対象別Period、Hold、Plan、Purge、AuditのPrimitiveを提供する 保持期間、Legal Hold Policy、承認、監査保管を決める
Credential Rotation 提供しない Database、API、Cloud Credentialを安全に更新する
Diagnostics Safe Projection、Mask済みActor、安定Failure Classificationを提供する Canonical StoreとLogの閲覧権限、Retention、Incident手順を管理する

#[Sensitive]が行うこと

Value PropertyへAttributeを付けます。

use BlackOps\Core\Attribute\Sensitive;
use BlackOps\Core\Attribute\SensitiveMode;

public function __construct(
    #[Sensitive(SensitiveMode::Mask)]
    public string $recipientEmail,
) {}
Mode Observed Projection
SensitiveMode::Omit Fieldを出力しない
SensitiveMode::Mask 値を[masked]へ置き換える
SensitiveMode::Hash 値そのものではなく一方向のDigestを出力する

Hashは同一値の相関が必要な場合だけ使います。低Entropy値は推測攻撃の対象になり得るため、Hashを暗号化やTokenizationとして扱いません。

#[Sensitive]が行わないこと

#[Sensitive]は認証、認可、暗号化、Access Control、Retentionを代替しません。具体的には次を置き換えません。

  • Authentication/Authorization
  • Tenant Isolation
  • TLS
  • Canonical Store/Databaseの暗号化
  • Encryption Key管理
  • Journal/Log SinkのAccess Control
  • Backup暗号化
  • Retention Period/Legal Hold
  • Credential Rotation

Observed JSONLでMaskできても、Canonical JournalやTransport Payloadには再現に必要な値が残る場合があります。保存先の暗号化、最小権限、保持期間、削除手順を必ず構成してください。

Actorも同じ責任分界に従います。Canonical Journalは監査正本としてorigin/authorization/execution ActorのIDとTypeを保持します。Observed JournalとJSONLではActor Typeとnull関係を維持しながら、すべてのActor IDを[masked]へ置き換えます。Role、Permission、Credential、Token、Session、ClaimはCanonical/Observedのどちらにも保存しません。

Frontend Operation Contractの境界

#[Route]を持つOperationはRepository mainのFrontend Contractへ含まれます。SensitiveなOperationValue PropertyもRequest送信に必要なWrite-only Inputとして名前と型を生成しますが、Constructor Default、実値、Example、Fixture、Log HelperはFrontend Contract ManifestとGenerated Treeへ入れません。OperationValueを成功Outcomeへ混ぜず、通常OutcomeのSensitive PropertyはBuild Errorにします。

EphemeralOutcomeだけはCredential Propertyへ#[Sensitive]を必須にし、直接.fetch()のResponse型へ名前と型を生成します。実値、Default、ExampleはArtifactへ含めません。Ephemeral Operationには.status().wait()を生成せず、HTTP Response後にFrameworkから再取得できる経路を作りません。Canonical JournalはReceivedとCompletedを空Dataで記録するため、Operation IDとLifecycleを保ちながら入力と出力を再現不能にします。

Generated .fetch()はHTTP Responseを検証し、Validation/Internal/Transport ResultへRaw Body、Credential、Thrown Error Message、Stack Traceを残しません。Operation IDはServer Responseに存在するときだけ保持します。Generated Tree、Typed Result、Observed LogのどこにもSensitive実値を置かないことをApplicationのConsumer E2Eでも確認してください。

Generated Typeは認証、認可、暗号化、Access Control、Retentionを代替しません。Server-only createBlackOpsClient()へRequestごとのSession/TokenからDefault HeaderをBindingし、Browser向けGlobal Mutable Clientへ保存しないでください。FactoryとCall HeaderはCopy/Freezeされ、Case-insensitiveにMergeされます。Operation由来Header、Generated Content-Type、専用Optionから作るIdempotency-Keyは任意Headerで上書きできません。

ApplicationはCORS、CSRF、TLS、Browser Storage、Token Rotation、Frontend Source Map/Build Artifactの公開範囲を管理します。config/frontend.phpへCredentialやBase URLを保存しません。不正なBase URL、Fetch、Header、Credential、Idempotency KeyはNetwork Call前にinvalid_client_optionsへ丸め、値やThrown ErrorをResultへ含めません。BackendのIdempotency RecordはRaw Key、Credential、Canonical Value、任意Headerを保存せず、認証・認可後の同じScope/FingerprintだけをReplayします。

Status参照の認可

GET /operations/{operationId}はGlobal MiddlewareとAuthenticationの内側で動きますが、Operation実行時の#[Authorize]とは別にStatus参照を認可します。ApplicationはOperationStatusAuthorizerServiceProviderからBindingしてください。未登録時はFrameworkが常にDenyし、Status Detailを読みません。

OperationStatusAuthorizationRequestが渡すのはOperation ID、Operation Type、Current Actor、受付時のOrigin Actorだけです。Credential、Role、Token、Payload、Outcome、Journal DetailをRequestへ追加しません。Operation IDは相関KeyでありSecretではありませんが、知っているだけでは参照権限を得ません。

QuickstartのSampleOperationStatusAuthorizerは、両Actorが存在し、どちらもuserで、ID/Typeが完全一致するときだけAllowします。これは単一UserのLocal Exampleです。ProductionではApplicationがTenant、Resource、Role、Delegationを含むPolicyへ置き換えてください。

Request Public Response 理由
不正Credential 401 Subjectを読む前にAuthentication Middlewareが停止する
Anonymous、Unknown ID、Deny 404 operation_unavailable 存在と認可結果を区別させない
Allow済み、Detailあり 200 7 Stateのいずれかを返す
Allow済み、Retention期限切れを証明 410 operation_expired 認可後だけ期限切れを明かす

すべてのStatus ResponseはCache-Control: private, no-storeを持ち、Non-terminalだけに正整数Retry-Afterが付きます。Completed OutcomeはPublic Propertyだけを返します。Sensitive Input、Credential、Actor ID、Raw Error、Canonical Journal Detailは返しません。

Session Authentication Starter

make:authの生成、Build、Migration、Register/Login/LogoutのCopy可能な手順はAuthenticationを正本とします。Securityでは生成物の責務境界と環境設定だけを扱い、同じCommandやcurl例を重複管理しません。

BlackOpsはBearer SessionのToken生成、Hash保存、TTL、Rotation、RevocationをFramework側で提供します。User、Password、登録可否はApplicationが所有し、make:authが編集可能なStarterとして生成します。Install直後のSkeletonはAuthenticationMiddlewareをHTTP Pipelineへ登録済みです。別構成のApplicationでは、config/middleware.phpのHTTP Pipelineに同Middlewareがあることを確認してください。

環境を設定する

config/auth.phpはBootstrap時のEnvironment Snapshotから次を読みます。

Key Default 用途
AUTH_REGISTRATION_ENABLED true 新規登録の許可
AUTH_SESSION_TTL_SECONDS 28800 Sessionの絶対TTL
AUTH_SESSION_TOUCH_INTERVAL_SECONDS 300 last_used_at更新間隔

Auth Configurationのservicesは既存config/app.phpapp.servicesの後へ決定的にMergeされます。config/auth.phpがないApplicationの登録結果は変わりません。

Register/Login/Logoutの期待Status、401/422の境界、Bearer/Cookie Tokenの扱いはAuthenticationで確認してください。

Register/Login/LogoutはEphemeralOutcomeです。Raw PasswordとTokenはCanonical Journal、Outcome Store、Status、Generated Artifactへ保存されません。Tokenを返すのはRegister/LoginのHTTP Response一回だけです。Applicationは受領後の安全な保管、Cookie発行、CSRF、Access Controlを所有します。

Generated Frontend Objectは3 Operationとも.fetch().toRequest().url()だけを持ち、.status().wait()を持ちません。

再実行と更新

同じGenerator Versionの全Fileがある場合、通常実行は内容を比較せず次を返します。

Authentication starter is already current.

一部だけ存在する状態では何も書きません。--forceconfig/auth.phpAuthServiceProvider.phpApplicationSessionIdentityProvider.phpだけを更新します。Domain、Repository、Operation、User Migration、Session Migrationは上書きしません。

php blackops make:auth --force

Migrationは生成時点のImmutable Snapshotです。Framework Update後も実行済みMigrationを書き換えないでください。

HTTP Authenticationの境界

ApplicationはHttpAuthenticatorを実装し、CredentialなしをAuthenticationResult::anonymous()、有効なCredentialをauthenticated(new ActorRef($id, $type))、不正Credentialをinvalid('authentication.invalid')として返せます。Framework同梱のOpt-in Session Coreを使う場合はBearerSessionAuthenticatorまたはCookieSessionAuthenticatorを選びます。JWT/OAuth/API KeyとUser/Password/Account State PolicyはApplicationが所有します。

Session Coreは32-byte CSPRNG TokenとSHA-256 Hash保存、Absolute TTL、Rotation/Revocation/Cleanupを所有します。通常のOperation ValueへRaw TokenやPasswordを渡しません。make:authのRegister/Login/Logoutだけは#[Sensitive]なEphemeral Value/Outcomeとして現在のHTTP Response中に扱い、Canonical Journalへ空Projection、Outcome Storeへ非保存とします。ApplicationはRaw Tokenを発行直後に必要なCredential Surfaceへ変換し、通常のJournal、Outcome、Logへ残しません。CookieのSecureHttpOnlySameSite、Domain/Path、CSRF、Encryption、Access Control、Retention期間はApplication責務です。登録方法とMigration境界はSession AuthenticationをOpt-in登録するを参照してください。

FrameworkのAuthenticationMiddlewareはCredential自体をResult、Request Attribute、ExecutionContext、Journalへコピーしません。Authenticated時に渡すのはID/TypeだけのActorRefです。Invalid時はOperation IDを発行せず、安定Codeだけを含む401 JSONを返します。AuthenticatorのBackend障害はInvalidへ丸めず、上位のHTTP Error境界へ伝播します。

Authenticated ResultのActorRefは予約Request Attributeを経由し、OperationのActorContextへ接続されます。HTTP入口では同じ参照がorigin/authorization/execution Actorになります。Anonymous RequestにはActorContextを追加しません。config/middleware.phpへAuthentication Middlewareを登録しても認可Policyは自動では決まらないため、Operation単位で#[Authorize]を宣言してください。

QuickstartのX-Sample-TokenはLocal Development用の最小Exampleです。AuthenticatorはExpected TokenをApplication Runtime構成時に一度だけSnapshotし、比較にhash_equals()を使います。SAMPLE_API_TOKENの未設定、空文字、空白だけの値は構成ErrorとしてFail-closedにし、既知TokenへFallbackしません。Header値はOperation ValueへBindせず、Response、ExecutionContext、Transport、Journal、Outcomeへ保存しません。ProductionではApplicationがSession、Bearer Token、External IdP等とSecret管理へ置き換えてください。

Header欠落と不正Headerは同じ401でも境界が異なります。

Request Authentication Operation Lifecycle Response
Header欠落 Anonymousとして通過 #[Authorize]がRejectedを記録 Operation ID付き401
Header不一致 Invalidとして停止 Operationを受け付けずJournalなし Operation IDなし401
Header一致 ActorRefだけを追加 Policy評価後にHandler/Deferred受付へ進む Inline 200/Deferred 202

Operation Authorizationの責任境界

認可が必要なOperationには#[Authorize(ApplicationPolicy::class)]を一度だけ付けます。PolicyはAuthorizationRequestからOperation、Value、ExecutionContext、非nullのAuthorization Actorを読み、AuthorizationDecision::allow()unauthorized($code)forbid($code)のいずれかを返します。

Policy ClassはBuild時にCompiled ContainerへAutowired登録されます。RepositoryやPermission Service等のInterface BindingはApplicationのService Providerへ登録してください。PolicyはCredential、Token、Session、Role/Permission Snapshot、Backend例外をRequestやDecisionへ保存しません。現在のRole、Permission、Resource状態はDIしたApplication Serviceから評価します。

FrameworkはPolicy評価を固定Lifecycle Stageとして実行します。Inlineではoperation.receivedattempt.startedの後、Handler解決/実行前に評価します。Deferred受付ではoperation.receivedの後、Transport Enqueue前に評価します。拒否時は次のSequenceへoperation.rejectedを記録し、Handler/Enqueueへ進みません。

ActorがないPolicy付きOperationはPolicyを呼ばず、authorization.authentication_requiredでUnauthorizedになります。Policyが返したUnauthorized/ForbiddenはOperation ID付きの401/403 JSONへ変換されます。ResponseとJournalへ出すCodeには外部公開可能な安定Codeだけを使ってください。

Policy BackendのTimeout、接続障害、Policy解決/構築失敗は拒否Decisionへ丸めません。Frameworkは元の例外をRuntime Error境界へ渡し、401/403として扱いません。Credential、Role、Permission SnapshotもExecutionContext、Result、Journalへ追加しません。

Deferred Workerでの再認可

Deferred Operationは受付時だけでなく、各Worker Attemptでも同じPolicyを評価します。Workerはattempt.startedを記録した後、Handlerを呼ぶ直前に、Transportから復元した最新のValueとExecutionContextをPolicyへ渡します。PolicyはDIしたRepository等から現在の権限やResource状態を取得してください。RetryではPolicyを再評価するため、受付後に失効した権限をそのまま使い続けません。

Worker AttemptのActor Contextは、受付時のorigin/authorizationを維持し、executionだけをexecution.worker.idsystemへ置き換えます。Policyへ渡すActorはauthorization Actorです。Worker System Actorを代わりに使って権限を強化することはありません。

認可結果と障害は次のように分離します。

状況 Lifecycle Handler Retry
Policyなし 通常実行 実行する Handler結果に従う
authorization Actorなし operation.rejected 実行しない しない
Unauthorized/Forbidden operation.rejected 実行しない しない
Policy解決/構築/実行の予期しない例外 attempt.failed後にSupervisionへ渡す 実行しない Supervision Policyに従う

RetryableなPolicy Backend障害はBackoff後の次Attemptで再評価されます。Fail/Dead Letterへ到達した場合も、FailureのException Class/Messageと、受付Actor/Worker execution Actorの分離をCanonical Journalへ維持します。Credential、Role、Permission、ClaimのSnapshotはTransportやJournalへ保存しません。

Operation受理前のError

Console OperationはCredential、Actor ID、Secret入力をOptionとして受け取りません。#[Sensitive]がValueまたはOutcomeの到達可能PropertyにあるOperationはConsole公開できません。--jsonとHuman Outputはいずれも安定Code、Field、Rule、Operation ID等のSafe Fieldだけを表示し、入力値、Exception Message、Path、SQLを反射しません。

Route不一致、壊れたJSON、必要Header欠落等はOperation受理前のProtocol Errorです。Operation IDやLifecycle Journalはまだ存在しません。Reverse Proxy/HTTP AdapterのAccess LogとError Responseを安全に構成し、Request BodyやAuthorization Headerを無条件に記録しないでください。

Canonical DataとSafe Diagnostics

Canonical Journal、Deferred Transport Payload、Outcome Storeは再現性のためRaw Value、Raw Actor ID、Exception Messageを含み得るRestricted Dataです。Databaseへの最小権限、保存時暗号化、Backup、Retention、Purge AuditはApplication/運用が設計します。

HTTP Error、Application/Framework JSONL Log、Observed Journal、operation:inspect、Local ViewerはSafe Diagnostics Surfaceです。ここではCredentialを除外し、#[Sensitive]を適用し、Actor IDを[masked]へ置き換え、Exception MessageではなくFailure Type/Classificationだけを示します。Raw表示に切り替えるCLI Optionはありません。

Local Viewerは既定無効、明示起動、Loopback限定、起動ごとのRandom Bootstrap Token、Session Cookie、Read-only GET/HEAD、Cache-Control: no-storeを組み合わせます。TokenをShell History、Chat、Ticket、共有Logへ貼らず、調査後はViewer Processを終了してください。このLocal GateはProductionのAuthentication/AuthorizationやRemote Support UIの代替ではありません。

Production Check

  • Authentication/AuthorizationをOperation入口へ適用する
  • Tenant境界をDatabase、Cache、Log、Outcome取得で確認する
  • TLSとNetwork Policyを構成する
  • Canonical Data、Backup、Credentialを暗号化する
  • Sinkごとに最小権限と監査を設定する
  • Workerの外部副作用を冪等にする
  • Retention Period、Legal Hold、Purge承認を文書化する
  • Credential RotationとIncident Responseを検証する

既知の提供範囲はReleases、設定はConfigurationを確認してください。