内容安全策略

内容安全策略 (CSP)目前在模型驱动和 Canvas 中受支持 Power Apps。 管理员可以控制是否发送 CSP 标头,并在一定程度上控制它包含的内容。 这些设置位于环境级别,这意味着一旦打开,它将应用于环境中的所有应用程序。

CSP 标头值的每个组件都控制可以下载的资产,在 Mozilla 开发人员网络 (MDN) 上有更详细的描述。 默认值如下:

指令 默认值 可自定义
脚本源 * 'unsafe-inline' 'unsafe-eval'
worker-src 的 'self' blob:
样式源 * 'unsafe-inline'
字体源 * 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 客户互动应用和其他模型驱动应用,CSP 仅在在线环境和具有 Dynamics 365 客户互动(本地)版本 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 应用程序主页来源
  • https://outlook.office.com
  • https://outlook.office365.com

要将 Power Apps 嵌入 Power BI 报告,请添加以下内容至 frame-ancestors

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

重要考虑因素

关闭默认指令并使用空列表保存将完全关闭指令,不会将其作为 CSP 响应头的一部分发送。

示例

我们来看几个 CSP 配置示例:

示例 1

CSP 示例 1

在示例中:

  • 报告关闭。
  • 模型驱动强制执行启用。
    • frame-ancestors 被自定义为 https://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-ancestors 被自定义为 https://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;

组织设置

通过直接修改以下组织设置,可以在不使用 UI 的情况下配置 CSP:

  • IsContentSecurityPolicyEnabled 控制是否在模型驱动应用中发送 Content-Security-Policy 标头。

  • ContentSecurityPolicyConfiguration 控制 frame-ancestors 部分的值(如上所示,如果未设置,则设置为 'self' if 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 Content-Security-Policy (CSP) frame-ancestors 指令指定可以使用 <frame><iframe><object><embed><applet> 嵌入页面的有效父级。
  • IsContentSecurityPolicyEnabledForCanvas 控制是否在画布应用程序中发送 Content-Security-Policy 标头。

  • ContentSecurityPolicyConfigurationForCanvas 使用上述相同的过程 ContentSecurityPolicyConfiguration 来控制 canvas 的策略。

  • 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!')
}