본문으로 건너뛰기

격리 패턴

격리 패턴은 프론트엔드가 보낸 Tauri API 메시지가 Tauri 코어에 도달하기 전에 가로채서 JavaScript만을 통해 수정하는 방식입니다. 격리 패턴으로 주입된 안전한 JavaScript 코드를 격리 앱이라고 부릅니다.

사용하는 이유

격리 패턴의 주 목적은 원하지 않거나 악의를 품은 프론트엔드 호출로부터 개발자가 Tauri 코어를 보호할 장치를 제공하는 것입니다. 격리 패턴의 필요성은 프론트엔드에서 실행하는 신뢰할 수 없는 콘텐츠에서 오는 위협에서 떠올랐습니다. 의존성이 많은 앱에서 일반적입니다. 보안: 위협 모델에서 앱이 마주할 수 있는 수많은 출처의 위협 목록을 살펴보실 수 있습니다.

위에서 설명한 격리 패턴이 염두에 둔 가장 큰 위협 모델은 바로 개발 위협입니다. 수많은 프론트엔드 빌드 도구가 종종 수백 개의 깊게 중첩된 의존성으로 구성되어 있을 뿐만 아니라 복잡한 앱은 최종 출력물과 함께 나갈 많은 양의 (그리고 보통 깊게 중첩된) 의존성을 가질 수도 있습니다.

사용해야 할 때

Tauri는 가능하다면 격리 패턴을 쓰는 것을 적극 권장합니다. 격리 패턴은 프론트엔드가 보내는 모든 메시지를 가로채기에 어디에서든 쓸 수 있습니다.

또, 외부 Tauri API를 사용할 때마다 앱을 잠그는 것을 강하게 추천합니다. 개발자로서, 안전한 격리 앱을 활용해 IPC 입력을 검증해 예상된 인자만 있다고 보장할 수 있습니다. 예를 들어, 파일을 읽고 쓰는 호출이 앱이 예상한 위치 바깥의 경로에 접근하는지 검사하기를 원할 수 있습니다. 또다른 예시로, Tauri API HTTP fetch 호출이 Origin 헤더를 앱에서 예상한 값으로만 설정했는지 확신할 수 있습니다.

격리 패턴은 프론트엔드가 보내는 모든 메시지를 가로챕니다. 즉, 이벤트 같은 상시 작동 API조차도 활용할 수 있습니다. 몇몇 이벤트는 Rust 코드가 작업을 수행하도록 함에 따라, 같은 유형의 검증 기술이 같이 사용될 수 있습니다.

사용하는 방법

격리 패턴은 프론트엔드와 Tauri 코어 사이에서 IPC 메시지를 수정하는 안전한 앱의 주입에 대한 것입니다. 이 패턴은 메인 프론트엔드 앱과 나란히 안전하게 JavaScript를 실행하는 <iframe>의 샌드박스 기능으로 수행됩니다. Tauri는 페이지를 불러올 때 격리 패턴을 강제하여 모든 IPC 호출이 Tauri 코어 대신 샌드박스된 격리 앱을 먼저 거치도록 합니다. 메시지가 Tauri 코어에 전송될 준비가 되면, 브라우저의 SubtleCrypto 구현을 사용해 암호화되고, 메인 프론트엔드 앱으로 되돌아갑니다. 그렇게 되고 나면, Tauri 코어에 바로 전송돼, 평소처럼 해독되고 읽힙니다.

누군가가 손수 특정 버전의 키를 읽고 메시지가 암호화되기 전에 수정할 수 없도록, 앱을 실행할 때마다 새로운 키를 생성합니다.

IPC 메시지의 대략적인 진행 단계

쉽게 따라올 수 있도록, 격리 패턴에서 IPC 메시지 Tauri 코어로 보내질 때 어디를 거쳐 가는지 대략적인 진행 단계를 담아 목록을 만들었습니다:

  1. Tauri의 IPC 처리기가 메시지를 수신
  2. IPC 처리기 -> 격리 앱
  3. [sandbox] 격리 앱이 흐름에 개입해 메시지를 수정할 수 있음
  4. [sandbox] 메시지를 AES-GCM를 활용해 실행 중 생성된 키로 암호화
  5. [encrypted] 격리 앱 -> IPC 처리기
  6. [encrypted] IPC 처리기 -> Tauri 코어

메모: 화살표 (->)는 메시지 전송을 나타냅니다.

성능 영향

메시지의 암호화가 필요하기에, 안전한 격리 앱이 그 무엇도 하지 않는대도 브라운필드 패턴에 비해 추가적인 비용이 발생합니다. (주의깊게 작은 의존성을 유지하고 성능을 알맞게 유지하는) 성능에 민감한 앱이 아니라면 대부분의 앱은 IPC 메시지의 암호화/복호화 같은 실행 중 비용은 매우 작고 AES-GCM이 비교적 빠르기 때문에 크게 느끼지 못합니다. 만약 AES-GCM에 친숙하지 않다면, 그냥 이 맥락에서는 SubtleCrypto에 포함된 유일한 인증 모드 알고리즘이며, TLS가 내부적으로 이를 사용해 아마 매일 쓰고 계실 겁니다.

또, Tauri 앱을 시작할 대마다 생성되는 암호학적으로 안전한 키도 있습니다. 일반적으로 대부분의 데스크톱 환경에서는 시스템이 이미 충분한 엔트로피를 만들었기에 충분히 임의적인 숫자를 즉시 만들 수 있어 별로 영향이 없습니다. 만약 WebDriver를 활용한 통합 테스트를 수행하는 헤드리스 환경에서 실행하고 있는데 운영 체제가 엔트로피 생성 서비스를 포함하지 않는다면 haveged와 같은 엔트로피 생성 서비스를 설치하고 싶을 수 있습니다. Linux 5.6 (2020년 3월)은 이제 추측 실행을 사용한 엔트로피 생성을 포함합니다

한계점

플랫폼 간 불일치에서 격리 패턴의 몇가지 한계점이 발생합니다. 가장 중요한 한계점은 Windows에서 샌드박스된 <iframes> 내부에서 외부 파일이 올바르게 불러올 수 없는 것입니다. 이 때문에, 빌드 중에 격리 앱과 관련된 스크립트의 내용을 가져와 인라인으로 주입하는 간단한 인라인 단계 스크립트를 만들었습니다. 즉, <script src="index.js"></script>처럼 전형적인 번들링이나 단순한 파일 포함은 여전히 잘 작동하겠지만, ES 모듈 등 새로운 방식으로는 불러올 수 없습니다.

권장 사항

격리 앱이 개발 위협을 막는 역할을 하기 때문에, 격리 앱을 최대한 단순하게 유지하는 편이 좋습니다. 의존성을 최소화하려 애쓸 뿐만 아니라, 빌드 단계를 간략히 유지하기 위해 심혈을 기울여야 합니다. 그러면, 프론트엔드 앱 위의 격리 앱을 향한 공급망 공격을 걱정할 필요가 없을 것입니다.

격리 앱 생성

이 예제에서, 작은 헬로 월드 풍의 격리 앱을 만들고, 가상의 Tauri 앱에 엮어봅시다. 검증을 수행하지는 않고, 그냥 메시지를 넘겨보내며 웹뷰 콘솔에 내용물을 출력해보겠습니다.

이 예제의 목적에 따라, tauri.conf.json이 있는 디렉터리에 있다고 상상해봅시다. 가상의 Tauri 앱은 distDir../dist로 설정했습니다.

../dist-isolation/index.html:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>격리된 안전한 스크립트</title>
</head>
<body>
<script src="index.js"></script>
</body>
</html>

../dist-isolation/index.js:

window.__TAURI_ISOLATION_HOOK__ = (payload) => {
// 검증하거나 수정하지는 말고, 그냥 내용만 출력해봅시다.
console.log('hook', payload)
return payload
}

이제 해야 할 일은 tauri.conf.json 설정에서 격리 패턴을 사용하도록 설정하고, 격리 패턴을 브라운필드 패턴으로부터 부트스트랩하는 것입니다.

설정

메인 프론트엔드에서 distDir../dist로 설정한다고 해봅시다. 또, 격리 앱은 ../dist-isolation에 내보낸다고 해봅시다.

{
"build": {
"distDir": "../dist"
},
"tauri": {
"pattern": {
"use": "isolation",
"options": {
"dir": "../dist-isolation"
}
}
}
}