콘텐츠 보안 정책

콘텐츠 보안 정책 (CSP)는 현재 모델 기반 및 캔버스에서 지원됩니다 Power Apps. 관리자는 CSP 헤더의 전송 여부와 포함된 내용을 제어할 수 있습니다. 설정은 환경 수준에 있습니다. 즉, 켜면 환경의 모든 앱에 적용됩니다.

CSP 헤더 값의 각 구성 요소는 다운로드할 수 있는 자산을 제어하며 Mozilla Developer Network(MDN)에서 자세한 내용을 볼 수 있습니다. 기본값은 다음과 같습니다.

지시문 기본값 사용자 지정 가능
스크립트-src * 'unsafe-inline' 'unsafe-eval' 아니요
작업자-src 'self' blob: 아니요
스타일-src * 'unsafe-inline' 아니요
글꼴-src * data: 아니요
프레임 조상 '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.com, https://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-ancestors 사용자 정의되어 있습니다 https://www.foo.com https://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;

조직 설정

CSP는 UI를 사용하지 않고 다음 조직 설정을 직접 수정하여 구성할 수 있습니다.

  • IsContentSecurityPolicyEnabled 는 모델 기반 앱에서 Content-Security-Policy 헤더를 전송할지 여부를 제어합니다.

  • ContentSecurityPolicyConfiguration 은 프레임 조상 부분의 값을 제어합니다(위에서 볼 수 있듯이, 'self' 설정되지 않은 경우 ContentSecurityPolicyConfiguration 로 설정됨). 이 설정은 { "Frame-Ancestor": { "sources": [ { "source": "foo" }, { "source": "bar" } ] } } 구조를 사용하는 JSON 개체로 나타냅니다. 이는 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!')
}