セキュリティ上の考慮事項
JSON:APIモジュールは、DrupalのEntity API、Field API、Typed Data APIを使用してDrupalで定義されたデータモデルを取得し、JSON:API仕様に準拠したAPIを介して公開し、Drupalが管理するデータ(エンティティ)との連携を容易にするように設計されています。
その際、データに対するDrupalのすべてのセキュリティ対策が尊重されます。
- エンティティアクセスが尊重されます。
- フィールドアクセスが尊重されます。
- データを変更する際には、検証制約が尊重されます。
internalフラグが尊重されます(エンティティタイプ定義、フィールド定義、またはプロパティ定義でフラグを設定する方法についてはドキュメントを参照してください)。
言い換えれば、JSON:APIは既存のセキュリティ対策のいずれも迂回せず、独自のレイヤーも追加しません。Drupalの基盤を再利用するのです。
エンティティタイプ、フィールドタイプ、データタイプのバグがセキュリティの脆弱性につながる可能性がある
それでも、エンティティタイプ、フィールドタイプ、データタイプと、それらのアクセス制御ハンドラーや検証制約を実装するコードにはバグが存在します。これはおおむねDrupalのレガシーに起因しています。Drupalは当初、検証制約ではなくフォーム検証コールバックを持っていました。APIファーストの考え方への移行はDrupalコアでは完了していると見なされるかもしれませんが、コントリビュートモジュールやカスタムモジュールでは保証されていません。
これらのバグはセキュリティの脆弱性につながる可能性があります。実際、過去にもそのような事例がありました。このような脆弱性はJSON:APIモジュールに限ったものではなく、たとえばRESTful Web Servicesモジュールや、Entity APIとやり取りするPHPコードにも影響します。
ただし、悪意のあるユーザーはPHP APIよりもJSON:APIやRESTful Web ServicesのようなHTTP APIに容易にアクセスできるため、この場合は特別な注意が必要です。他のHTTP APIモジュールとは異なり、JSON:APIはデフォルトでより広いAPIサーフェスを持っています。つまり、開発者体験を可能な限りスムーズにするために、internal以外のすべてのエンティティタイプがデフォルトで利用可能になります(もちろん、エンティティアクセスは引き続き尊重されます)。
6つのセキュリティ上の考慮事項
1. 安定版のコントリビュートモジュールを使用することの重要性
エンティティタイプ、フィールドタイプ、データタイプによって引き起こされるセキュリティの脆弱性は、セキュリティ勧告ポリシーの対象となるDrupal.orgで公開されている安定版モジュールに対してのみ、できるだけ早く解決されます。カスタムモジュールと非安定版のコントリビュートモジュールは対象外です。それらを使用している場合は、特別な注意を払ってください。
2. エンティティアクセスとフィールドアクセスの監査
JSON:APIを使用しているか、他のAPI系モジュールを使用しているかにかかわらず、Drupalサイトでエンティティアクセスとフィールドアクセスを監査することが常に推奨されます。これは、JSON:APIの書き込み機能が有効な場合に特に重要です。
3. 使用するものだけを公開する
特定のリソースタイプ(エンティティタイプ+バンドル)を公開する必要がない場合、それらへのアクセスが拒否されていることを確認したうえで、さらに進んで無効にすることもできます。リソースタイプやフィールドを無効にするには、カスタムモジュールで実装できるPHP APIを使用するか、無効化されたリソースタイプとフィールドのUIを提供するJSON:API Extrasコントリビュートモジュールを使用できます。これは常に可能というわけではありませんが、サイト所有者がすべてのAPIクライアントも所有している場合は、これを行うことでAPIサーフェスを可能な限り小さくできます。
4. 読み取り専用モード
特定のニーズにおいて、データの読み取りしか必要としない場合は、/admin/config/services/jsonapiでJSON:APIの読み取り専用モードを有効にすることができます。これにより、既存の検証制約や書き込みロジックに潜在する、まだ明らかになっていない仮説上のバグによるリスクが軽減されます。最近のデカップリングされたDrupalセットアップのほとんどはデータの読み取りしか必要としないため、読み取り専用モードはデフォルトで有効になっています。(DrupalコアのJSON:APIと、コントリビュートモジュールのバージョン2.4以降)
5. 隠匿によるセキュリティ:秘密のベースパス
JSON:APIのベースパスはデフォルトでは/jsonapiです。これを/hidden/b69dhj027ooae/jsonapiのようなものに変更できます。これは自動化された攻撃の効果を減らす方法のひとつです。sites/example.com/services.ymlがまだ存在しない場合は作成し、次の内容を追加します。
parameters:
jsonapi.base_path: /hidden/b69dhj027ooae/jsonapi
6. 一部のルートを削除して、作成または編集できるエンティティバンドルを制限する
JSON:API経由で一部のエンティティバンドルの作成または更新のみが必要な場合は、カスタムモジュール内で、POSTおよびPATCHルートのホワイトリストに指定されたもの以外をすべて削除するイベントサブスクライバーを実装できます。これは読み取り専用モードを無効にした後に効果があり、ルーターの再構築が必要になる場合があります。
モジュールのservices.ymlファイルにサービスを追加します。
services:
mymodule.route_subscriber:
class: Drupal\mymodule\Routing\JsonapiLimitingRouteSubscriber
tags:
- { name: event_subscriber }
イベントサブスクライバーを作成します。この例では、JSON:API経由でコンテンツを削除することも不可能になります。
<?php
namespace Drupal\mymodule\Routing;
use Drupal\Core\Routing\RouteSubscriberBase;
use Symfony\Component\Routing\RouteCollection;
/**
* Class JsonapiLimitingRouteSubscriber.
*
* Remove all DELETE routes from jsonapi resources to protect content.
*
* Remove POST and PATCH routes from jsonapi resources except for those
* we want end users to create and update via the decoupled API.
*/
class JsonapiLimitingRouteSubscriber extends RouteSubscriberBase {
/**
* {@inheritdoc}
*/
protected function alterRoutes(RouteCollection $collection) {
$mutable_types = $this->mutableResourceTypes();
foreach ($collection as $name => $route) {
$defaults = $route->getDefaults();
if (!empty($defaults['_is_jsonapi']) && !empty($defaults['resource_type'])) {
$methods = $route->getMethods();
if (in_array('DELETE', $methods)) {
// We never want to delete data, only unpublish.
$collection->remove($name);
}
else {
$resource_type = $defaults['resource_type'];
if (empty($mutable_types[$resource_type])) {
if (in_array('POST', $methods) || in_array('PATCH', $methods)) {
$collection->remove($name);
}
}
}
}
}
}
/**
* Get mutable resource types, exposed to user changes via API.
*
* @return array
* List of mutable jsonapi resource types as keys.
*/
public function mutableResourceTypes(): array {
return [
'node--article' => TRUE,
'node--document' => TRUE,
'custom_entity--custom_entity' => TRUE,
];
}
}
追加のパーミッションでJSON:APIのすべてのルートへのアクセスを制限する
バックエンド統合、限定的なAPIクライアント、その他の非公開のユースケースでJSON:APIを使用する場合、特定のパーミッションを持つユーザーにJSON:API全体を制限することが望ましいかもしれません。代わりに、または追加で、前述のルートサブスクライバーに次のスニペットを追加します。
// Limit access to all jsonapi routes with an extra permission.
foreach ($collection as $route) {
$defaults = $route->getDefaults();
if (!empty($defaults['_is_jsonapi'])) {
$route->setRequirement('_permission', 'FOO custom access jsonapi');
}
}
次に、FOO.permissions.ymlでそのパーミッションを定義し、目的のユーザーロールに付与します。
Drupalドキュメントからの記事です。