コンテンツ セキュリティ ポリシー

コンテンツ セキュリティ ポリシー(CSP) は現在、モデル駆動型およびキャンバス Power Apps でサポートされています。 管理者は、CSP ヘッダーを送信するかどうか、およびその内容をある程度制御できます。 設定は環境レベルにあり、有効化すると、環境が含むすべてのアプリに適用されます。

CSP ヘッダー値の各コンポーネントは、ダウンロード可能なアセットを制御し、Mozilla Developer Network (MDN) で詳細に説明されています。 既定値は次のとおりです:

ディレクティブ 既定値 カスタマイズ可能
script-src * 'unsafe-inline' 'unsafe-eval' いいえ
worker-src 'self' blob: いいえ
style-src * 'unsafe-inline' いいえ
font-src * data: いいえ
frame-ancestors 'self' https://*.powerapps.com はい

この結果、既定の CSP は script-src * 'unsafe-inline' 'unsafe-eval'; worker-src 'self' blob:; style-src * 'unsafe-inline'; font-src * data:; frame-ancestors 'self' https://*.powerapps.com; となります。 マイクロソフトのロードマップでは、現在カスタマイズできないヘッダーを変更できるようになりました。

前提条件

  • Dynamics 365 Customer Engagementアプリおよびその他のモデル駆動型アプリの場合、CSPはオンライン環境およびDynamics 365 Customer Engagement (オンプレミスの) バージョン9.1以降を使用している組織でのみ使用できます。

CSP を構成しています

CSPは Power Platform 管理センターから切り替え、または設定することができます。 CSPを有効にすると、ポリシーに違反した場合にシナリオがブロックされる可能性があるため、まず開発/テスト環境で 環境 を有効にすることが重要です。 また、運用開始時での立ち上げを容易にするため、「レポート専用モード」にも対応しています。

CSP を構成するには、Power Platform 管理センター ->環境 ->設定 ->プライバシー + セキュリティに移動します。 次の図は、設定の既定の状態を示しています。

コンテンツ セキュリティ ポリシーの既定の設定

レポート中

「レポートを有効にする」トグルは、モデル駆動型アプリとキャンバスアプリが違反レポートを送信するかどうかを制御します。 これを有効にするには、エンドポイントを指定する必要があります。 違反レポートは、CSP が実施されているかどうかに関係なく、このエンドポイントに送信されます (CSP が実施されていない場合は、レポート専用モードを使用します)。 詳細については、レポート ドキュメント を参照してください。

レポート エンドポイントの有効化

実施

CSP の実行は、モデル駆動型アプリとキャンバス アプリで独立して制御され、ポリシーのきめ細かい制御を実現します。 モデル駆動型/キャンバス ピボットを使用して、目的のアプリ タイプを変更します。

"コンテンツ セキュリティ ポリシーを強制する" トグルは、特定のアプリの種類に対する既定のポリシーを適用します。 このトグルをオンにすると、この環境でのアプリの動作がポリシーに準拠するように変更されます。 したがって、推奨される有効化フローは次のようになります:

  1. 開発/テスト環境で適用します。
  2. 本番環境でレポート専用モードを有効にします。
  3. 違反が報告されなくなったら、本番環境で適用してください。

ディレクティブを構成する

このセクションでは、ポリシー内の個々のディレクティブを制御できます。 現在、frame-ancestors のみをカスタマイズできます。

CSP ディレクティブを構成する

既定のディレクティブのトグルをオンのままにすると、この記事の前半で表示されたテーブルで指定された既定値が使用されます。 トグルをオフにすると、管理者はディレクティブのカスタム値を指定し、それらを既定値に追加できるようになります。 以下の例では、frame-ancestors にカスタム値を設定しています。 この例では、ディレクティブは frame-ancestors: 'self' https://*.powerapps.com https://www.foo.com https://www.bar.com に設定されます。つまり、アプリを同じ作成元、https://*.powerapps.comhttps://www.foo.comhttps://www.bar.com でホストできても、他の作成元ではホストできないことを意味します。 追加ボタンを使用してエントリをリストに追加し、削除アイコンを使用してエントリを削除します。

カスタム CSP ディレクティブの設定

一般的な構成

Dynamics 365 アプリ を使用した Microsoft Teams の統合では、frame-ancestors に以下を追加します:

  • https://teams.microsoft.com/
  • https://teams.cloud.microsoft/
  • https://msteamstabintegration.dynamics.com/

Dynamics 365 App for Outlook については、frame-ancestors に以下を追加します:

  • Outlook Web App ホームページのオリジン
  • https://outlook.office.com
  • https://outlook.office365.com

Power BI レポートの埋め込み Power Apps の場合、以下を frame-ancestorsに追加します。

  • https://app.powerbi.com
  • https://ms-pbi.pbi.microsoft.com

重要な考慮事項

既定ののディレクティブをオフにし、空のリストで保存すると、ディレクティブが完全にオフになり、CSP 応答ヘッダの一部として送信されなくなります。

使用例

CSP の構成例をいくつか見てみましょう:

例 1

CSP の例 1

例:

  • レポートはオフになっています。
  • モデル駆動型の適用が有効になっています。
    • frame-ancestorshttps://www.foo.comhttps://www.bar.com にカスタマイズされています
  • キャンバスの適用は無効になっています。

有効なヘッダーは次のとおりです:

  • モデル駆動型アプリ: Content-Security-Policy: script-src * 'unsafe-inline' 'unsafe-eval'; worker-src 'self' blob:; style-src * 'unsafe-inline'; font-src * data:; frame-ancestors https://www.foo.com https://www.bar.com;
  • キャンバス アプリ: CSP ヘッダーは送信されません。

例 2

CSP の例 2

例:

  • レポートはオンになっています。
    • レポート エンドポイントは、https://www.mysite.com/myreportingendpoint にセットされています
  • モデル駆動型の適用が有効になっています。
    • frame-ancestors は既定のままです
  • キャンバスの適用は無効になっています。
    • frame-ancestorshttps://www.baz.com にカスタマイズされています

有効な CSP 値は次のとおりです:

  • モデル駆動型アプリ: Content-Security-Policy: script-src * 'unsafe-inline' 'unsafe-eval'; worker-src 'self' blob:; style-src * 'unsafe-inline'; font-src * data:; frame-ancestors 'self' https://*.powerapps.com; report-uri https://www.mysite.com/myreportingendpoint;
  • キャンバス アプリ: Content-Security-Policy-Report-Only: script-src * 'unsafe-inline' 'unsafe-eval'; worker-src 'self' blob:; style-src * 'unsafe-inline'; font-src * data:; frame-ancestors https://www.baz.com; report-uri https://www.mysite.com/myreportingendpoint;

組織設定

CSP は、以下の組織の設定を直接変更することで、UI を使用せずに設定することができます:

  • IsContentSecurityPolicyEnabled は、モデル駆動型アプリで Content-Security-Policy ヘッダが送信されるかどうかを制御します。

  • ContentSecurityPolicyConfiguration は、frame-ancestors部分の値を制御します (上記のように、設定されていない場合は 'self' に設定されます ContentSecurityPolicyConfiguration )。 この設定は、以下の構造を持つ JSON オブジェクトで表現されます – { "Frame-Ancestor": { "sources": [ { "source": "foo" }, { "source": "bar" } ] } }。 これは script-src * 'unsafe-inline' 'unsafe-eval'; worker-src 'self' blob:; style-src * 'unsafe-inline'; font-src * data:; frame-ancestors 'foo' 'bar'; に変換されます

    • (MDN から) HTTP コンテンツ セキュリティー ポリシー (CSP) の frame-ancestors ディレクティブは、<frame><iframe><object><embed><applet> を使用してページを埋め込むことができる有効な親を指定します。
  • IsContentSecurityPolicyEnabledForCanvas は、キャンバス アプリで Content-Security-Policy ヘッダが送信されるかどうかを制御します。

  • ContentSecurityPolicyConfigurationForCanvas は、上記の ContentSecurityPolicyConfiguration と同じ処理でキャンバスのポリシーを制御します。

  • ContentSecurityPolicyReportUri は、レポートを使用するかどうかを制御する。 この設定は、モデル駆動型アプリとキャンバスアプリの両方で使用されます。 有効な文字列は、指定されたエンドポイントに違反レポートを送信し、IsContentSecurityPolicyEnabled/IsContentSecurityPolicyEnabledForCanvas がオフになっている場合はレポート専用モードを使用します。 空の文字列はレポートを無効にします。 詳細については、レポート ドキュメント を参照してください。

UI を使用しない CSP の構成

特にオンプレミス構成など Power Platform 管理センターにない環境では、管理者はスクリプトを使用して CSP を構成し、直接設定を変更するケースも想定されます。

UI なしで CSP を有効にする

ステップ:

  • 組織エンティティの更新権限を持つユーザー (システム管理者は良い選択です) としてモデル駆動型アプリを使用し、ブラウザの開発ツールを起動します。
  • 以下のスクリプトをコンソールに貼り付けて実行します。
  • CSPを有効にするには、デフォルトの設定を渡します - enableFrameAncestors(["'self'"])
  • 他のオリジンがアプリを埋め込むことを可能にする例として - enableFrameAncestors(["*.powerapps.com", "'self'", "abcxyz"])
async function enableFrameAncestors(sources) {
    const baseUrl = Xrm.Utility.getGlobalContext().getClientUrl();

    if (!Array.isArray(sources) || sources.some(s => typeof s !== 'string')) {
        throw new Error('sources must be a string array');
    }

    const orgResponse = await fetch(`${baseUrl}/api/data/v9.1/organizations`);
    if (!orgResponse.ok) throw new Error('Failed to retrieve org info');
    const orgs = await orgResponse.json();
    const { organizationid, contentsecuritypolicyconfiguration, iscontentsecuritypolicyenabled } = orgs.value[0];

    console.log(`Organization Id: ${organizationid}`);
    console.log(`CSP Enabled?: ${iscontentsecuritypolicyenabled}`);
    console.log(`CSP Config: ${contentsecuritypolicyconfiguration}`);

    const orgProperty = prop => `${baseUrl}/api/data/v9.1/organizations(${organizationid})/${prop}`;

    console.log('Updating CSP configuration...')
    const config = {
        'Frame-Ancestor': {
            sources: sources.map(source => ({ source })),
        },
    };
    const cspConfigResponse = await fetch(orgProperty('contentsecuritypolicyconfiguration'), {
        method: 'PUT',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
            value: JSON.stringify(config),
        }),
    });

    if (!cspConfigResponse.ok) {
        throw new Error('Failed to update csp configuration');
    }
    console.log('Successfully updated CSP configuration!')

    if (iscontentsecuritypolicyenabled) {
        console.log('CSP is already enabled! Skipping update.')
        return;
    }

    console.log('Enabling CSP...')
    const cspEnableResponse = await fetch(orgProperty('iscontentsecuritypolicyenabled'), {
        method: 'PUT',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
            value: true,
        }),
    });

    if (!cspEnableResponse.ok) {
        throw new Error('Failed to enable csp');
    }
    console.log('Successfully enabled CSP!')
}

UI なしで CSP を無効にする

ステップ:

  • 組織エンティティの更新権限を持つユーザー (システム管理者は良い選択です) としてモデル駆動型アプリを使用し、ブラウザの開発ツールを起動します。
  • 次のスクリプトをコンソールに貼り付けて実行します。
  • CSP を無効化する場合はコンソールに貼り付けます: disableCSP()
async function disableCSP() {
    const baseUrl = Xrm.Utility.getGlobalContext().getClientUrl();

    const orgResponse = await fetch(`${baseUrl}/api/data/v9.1/organizations`);
    if (!orgResponse.ok) throw new Error('Failed to retrieve org info');
    const orgs = await orgResponse.json();
    const { organizationid, iscontentsecuritypolicyenabled } = orgs.value[0];

    console.log(`Organization Id: ${organizationid}`);
    console.log(`CSP Enabled?: ${iscontentsecuritypolicyenabled}`);

    const orgProperty = prop => `${baseUrl}/api/data/v9.1/organizations(${organizationid})/${prop}`;

    if (!iscontentsecuritypolicyenabled) {
        console.log('CSP is already disabled! Skipping update.')
        return;
    }

    console.log('Disabling CSP...')
    const cspEnableResponse = await fetch(orgProperty('iscontentsecuritypolicyenabled'), {
        method: 'PUT',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
            value: false,
        }),
    });

    if (!cspEnableResponse.ok) {
        throw new Error('Failed to disable csp');
    }
    console.log('Successfully disabled CSP!')
}