Cloudflare Zero Trust の Resolver Policies を含めた DNS フィルタリングの動作を確認する

# WARP 基本動作
Cloudflare Zero Trust で WARP クライアントエージェントを使うときの基本動作は以下のようになります。

DNS クエリについては Gateway の DNS resolver に対して DoH で Wireguard トンネルの外で投げる形です。

https://developers.cloudflare.com/cloudflare-one/connections/connect-devices/warp/configure-warp/route-traffic/warp-architecture/

>
>```mermaid:
>flowchart LR
>subgraph Device
>W[WARP client] -.-> D
>D[DNS proxy]
>W -.-> V[Virtual interface]
>end
>subgraph Cloudflare
>A[Zero Trust account]
>subgraph Gateway
>G[DNS resolver]
>N[L3/L4 firewall]
>end
>end
>W<--Device orchestration-->A
>D<--DoH-->G
>V<--Wireguard-->N
>N --> O[(Application)]
>```

そのため、下記ドキュメントにある通り DoH IP への通信をファイアウォールで許可する必要があります。

https://developers.cloudflare.com/cloudflare-one/connections/connect-devices/warp/deployment/firewall/

# Cloudflare Zero Trust における DNS クエリの扱い
基本動作から諸々の設定を鑑みた場合の動作は以下のようになります。

例えば、ブラウザの `Secure DNS` 設定や CLI ツールの種類によっては WARP クライアントの `Local DNS proxy` まで到達しないケースもあります。

また、その後の `Local Domain Fallback` 設定によって　DNS フィルタリングをバイパスする（させる）ケースがあります。

```mermaid
---
title: How DNS query works with Cloudflare Zero Trust
---
graph TB

Agentless[Agentless options] --> |DNS over TCP/UDP 53|RIP
Agentless[Agentless options] --> |DNS over HTTPS|DoH
Agentless[Agentless options] --> |DNS over TLS|DoT
Agentless[Agentless options] --> |HTTP|BISO & PE
DoT & DoH & RIP --> G
BISO & PE --> |DNS query|G

D{{DNS request}} -->  Command & Browser
Browser --> SecureDNS{Secure DNS ?}
SecureDNS --> |"Yes<br>(Bypass the local DNS proxy)"|SDR[Secure DNS resolver]
SecureDNS --> |No|hosts{"/etc/hosts ?"}
hosts --> |"Yes<br>(Bypass the local DNS proxy)"|H[Resolve entries in /etc/hosts]
ping --> hosts
hosts --> |No|DNSproxy["Local DNS proxy<br>(127.0.2.2 and 127.0.2.3)"] -->R{In Local Domain Fallback ?}
dig --> |Ignore /etc/hosts file|DNSproxy
%%R -- "Yes<br>(Bypass DNS policies)" --> DNSServers{DNS Servers<br>are specified ?}
R -- "Yes with DNS Servers<br>(Bypass DNS policies)" --> F["Private DNS resolver<br>(subject to Network Policies)"] 
R -- "Yes without DNS Servers<br>(Bypass DNS policies)" --> Local[Local Resolution]
%%DNSServers --> |Yes|F["Private DNS resolver<br>(subject to Network Policies)"]
%%DNSServers --> |No|Local[Local Resolution]
R -- No -----> G

subgraph LocalPC [Local PC]
direction TB
Local
D
subgraph Command
ping
dig["dig, nslookup and host"]
end
subgraph Browser
SecureDNS
end
subgraph WARP [WARP Client]
DNSproxy
R
end
hosts
H
end


subgraph CF [Cloudflare Zero Trust]
direction TB
subgraph G [DNS Policies]
direction TB
Before[Before DNS resolution]
subgraph During [During DNS resolution]
direction TB
ResolverPolicy[Resolver Policy]
CustomResolver[Custom Resolver]
1111[1.1.1.1]
ResolverPolicy ---> |No|1111
ResolverPolicy --> |Yes|CustomResolver
8888["ex) 8.8.8.8"]
PrivateDNS2[Private DNS resolver]
CustomResolver --> |Public|8888
CustomResolver --> |Private|PrivateDNS2
end
After[After DNS resolution]
end
BISO[Clientless Web Isolation]
PE[Proxy Endpoint]
DoT
DoH
RIP[Gateway Resolver IP]
end


Before --> During
During --> After
```

# Resolver Policies
DNS フィルタリングをバイパスしてしまうことは、ログの機会を逸し、透明性の観点からも必ずしも良いことではありません。

そのため、Resolver Policies を使って解決できる課題がいくつかあります。

:::note info
2024年5月時点では Only available on Enterprise plans となっています。
:::

https://developers.cloudflare.com/cloudflare-one/policies/gateway/resolver-policies/

- Cloudflare Tunnel や Magic WAN トンネルを経由した Private DNS 名前解決に DNS ポリシーを適用できる
    - [Local Domain Fallback](https://developers.cloudflare.com/cloudflare-one/connections/connect-devices/warp/configure-warp/route-traffic/local-domains/) でもPrivate DNS 名前解決自体は実現可能ですが、DNS ポリシーをバイパスするため、名前解決に関するフィルタリングやログができません。一方で Resolver Policies を使えば、Private DNS 名前解決に加えて DNS ポリシーによるフィルタリングやログが可能です。
- エージェントレスな接続方式でも、プライベートウェブアプリに到達できる
    - [Proxy Endpoint](https://developers.cloudflare.com/cloudflare-one/connections/connect-devices/agentless/pac-files/) や [Clientless Web Isolation](https://developers.cloudflare.com/cloudflare-one/policies/browser-isolation/setup/clientless-browser-isolation/) のエージェントレスアクセスからも Private DNS 名前解決ができるようになるため、Cloudflare Tunnel や Magic WAN トンネル先のプライベート IP に対してウェブアクセスが可能になります。
- `1.1.1.1` 以外のパブリックリゾルバ（`8.8.8.8` や `9.9.9.9` 等）を利用できる
    - どうしても `1.1.1.1` で名前解決できない場合には、必要になることがあるかもしれず、取れる選択肢があることは重要です。


# テスト
以下のような構成でテストします。

```mermaid
graph LR

subgraph Device
W[WARP client] -.-> D
D[DNS proxy]
W -.-> V[Virtual interface]
Browser
end

subgraph Cloudflare
subgraph VNet [Virtual networks]
subgraph Tunnel [Tunnel Routes]
subgraph Subnet ["10.146.0.0/20"]
end
end
end
subgraph G [DNS Policies]
direction TB
direction TB
subgraph ResolverPolicy [Resolver Policy]
CustomResolver[Custom Resolver]
end
%%ResolverPolicy --> CustomResolver
end
CustomResolver --> |10.146.0.10|Subnet
HTTP[HTTP / Network Policies]
Browser --> |xxx.cloudflareaccess.com/browser|BISO[Clientless Web Isolation] --> G & HTTP
end

subgraph Corp [Corporate Network]
subgraph Connector [Connector Server<br><div align=center>10.146.0.12]
cloudflared["cloudflared"]
end
p10[Private DNS Resolver<br>10.146.0.10]
p20[Private Web Server<br>10.146.0.11]
end

D ---> |DNS query|G
Subnet --> |DNS|cloudflared --> |DNS|p10
Subnet --> |HTTP|cloudflared
cloudflared --> |HTTP|p20
V --> HTTP --> |10.146.0.11|Subnet

```

## Resolver Policies 設定
ホスト名が `resolve-private.example.com` なら、カスタムリゾルバ `10.146.0.10` を使います。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/101361/11a0242f-b119-b190-c32b-c3bdfe5f991b.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/101361/dcbdc838-d4c9-ce38-783d-b3a81126a6c8.png)

## 動作確認（WARP）
- `resolve-private.example.com` にアクセスすると、サーバーの IP アドレスが `10.146.0.11` と名前解決されてアクセスしたことがわかります。
- また、Cloudflare Gateway の証明書が表示されたため、Cloudflare のフォワードプロキシ経由でのアクセスと確認できます。
- ウェブサーバーから見たときのソース IP は `10.146.0.12` となりますが、これはコネクタサーバーにおける `cloudflared` によって `SNAT` がおこなわれた形となり、想定した通りの結果です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/101361/d8363007-3c20-c57c-af1e-f489fa42b3df.png)

### ログ（WARP）

https://developers.cloudflare.com/logs/reference/log-fields/account/gateway_dns/

```json:gateway-dns-log.json
{
  "ApplicationID": 0,
  "ColoCode": "NRT",
  "ColoID": 382,
  "CustomResolveDurationMs": 18,
  "CustomResolverAddress": "10.146.0.10:53",
  "CustomResolverPolicyID": "f867d359-06ef-4fbb-99a6-587c91b7c94e",
  "CustomResolverPolicyName": "resolve-private.example.com 10.146.0.10",
  "CustomResolverResponse": "Success",
  "Datetime": "2024-05-26T15:07:52Z",
  "DeviceID": "05ebdb78-19ec-11ef-a2cc-a206d6485a19",
  "DeviceName": "xxxxx",
  "DstIP": "162.159.36.1",
  "DstPort": 443,
  "Email": "non_identity@xxxxx.cloudflareaccess.com",
  "IsResponseCached": false,
  "Location": "Nuro",
  "LocationID": "76c7ca27-5d1c-4c1b-a3b3-e841a538921f",
  "MatchedCategoryIDs": [],
  "MatchedCategoryNames": [],
  "MatchedIndicatorFeedIDs": [],
  "MatchedIndicatorFeedNames": [],
  "Policy": "",
  "PolicyID": "",
  "Protocol": "https",
  "QueryCategoryIDs": [],
  "QueryCategoryNames": [],
  "QueryIndicatorFeedIDs": [],
  "QueryIndicatorFeedNames": [],
  "QueryName": "resolve-private.example.com",
  "QueryNameReversed": "com.example.resolve-private",
  "QuerySize": 60,
  "QueryType": 1,
  "QueryTypeName": "A",
  "RCode": 0,
  "RData": [
    {
      "type": "1",
      "data": "D3Jlc29sdmUtcHJpdmF0ZQdraGF5YW1hB2NvbXBhbnkAAAEAAQAADhAABAqSAAs="
    }
  ],
  "ResolvedIPs": [
    "10.146.0.11"
  ],
  "ResolverDecision": "allowedOnNoPolicyMatch",
  "SrcIP": "x.x.x.x",
  "SrcPort": 0,
  "TimeZone": "",
  "TimeZoneInferredMethod": "noScheduleConfigured",
  "UserID": "b7761faf-27e6-4d08-a35a-802db584b739"
}
```

## 動作確認（Clientless Web Isolation）

[Clientless Web Isolation](https://developers.cloudflare.com/learning-paths/zero-trust-web-access/alternative-onramps/clientless-rbi/#clientless-web-isolation) 経由でも同様の動作を確認できます。

これによりエージェント不要で社内ウェブアプリへのアクセスが可能になります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/101361/81775b3c-700f-0b4d-f3aa-7307afcb6283.png)

### ログ（Clientless Web Isolation）
`DstIP` や `SrcIP` が WARP アクセスとは異なって見えることがわかります。

https://developers.cloudflare.com/logs/reference/log-fields/account/gateway_dns/

```json:gateway-dns-log.json
{
  "ApplicationID": 0,
  "ColoCode": "NRT",
  "ColoID": 22,
  "CustomResolveDurationMs": 21,
  "CustomResolverAddress": "10.146.0.10:53",
  "CustomResolverPolicyID": "f867d359-06ef-4fbb-99a6-587c91b7c94e",
  "CustomResolverPolicyName": "resolve-private.example.com 10.146.0.10",
  "CustomResolverResponse": "Success",
  "Datetime": "2024-05-26T15:46:29Z",
  "DeviceID": "00000000-0000-0000-0000-000000000000",
  "DeviceName": "",
  "DstIP": "",
  "DstPort": 443,
  "Email": "user1@example.com",
  "IsResponseCached": false,
  "Location": "Nuro",
  "LocationID": "76c7ca27-5d1c-4c1b-a3b3-e841a538921f",
  "MatchedCategoryIDs": [],
  "MatchedCategoryNames": [],
  "MatchedIndicatorFeedIDs": [],
  "MatchedIndicatorFeedNames": [],
  "Policy": "",
  "PolicyID": "",
  "Protocol": "https",
  "QueryCategoryIDs": [],
  "QueryCategoryNames": [],
  "QueryIndicatorFeedIDs": [],
  "QueryIndicatorFeedNames": [],
  "QueryName": "resolve-private.example.com",
  "QueryNameReversed": "com.example.resolve-private",
  "QuerySize": 60,
  "QueryType": 1,
  "QueryTypeName": "A",
  "RCode": 0,
  "RData": [
    {
      "type": "1",
      "data": "D3Jlc29sdmUtcHJpdmF0ZQdraGF5YW1hB2NvbXBhbnkAAAEAAQAADhAABAqSAAs="
    }
  ],
  "ResolvedIPs": [
    "10.146.0.11"
  ],
  "ResolverDecision": "allowedOnNoPolicyMatch",
  "SrcIP": "0.0.0.0",
  "SrcPort": 0,
  "TimeZone": "",
  "TimeZoneInferredMethod": "noScheduleConfigured",
  "UserID": "4b79fcad-0452-41be-99c0-73f777b31a6f"
}
```

## Gateway activity logs
以下の画面からもログを確認できます。

https://developers.cloudflare.com/cloudflare-one/insights/logs/gateway-logs/

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/101361/1f1f4382-8fba-606c-0bed-588b5dbfa291.png)


# まとめ

今後は Private DNS を使う場合には Resolver Policies が推奨となりそうです。

どうしてもローカル端末からでしかアクセスできないようなドメインに関してのみ、Local Domain Fallback を使う、といった住み分けになりそうです。

構成さえ理解すれば、技術的には特に難しくないかなと思うのでぜひ使ってみてください。

# 参考：DNS ポリシーの評価

```mermaid
graph
subgraph Eval [DNS Polices Evaluation phase]
direction LR
Users
subgraph Before [Before DNS Resolution]
direction TB
Application
Categories[Content Categories]
ResolverIP[DNS Resolver IP]
DoHSubdomain[DoH Subdomain]
Domain
Host
Feed[Indicator Feed]
Location
Type[Query Record Type]
SecCategories[Security Categories]
SourceContinent[Source Continent]
SourceCountry[Source Country]
SourceIP[Source IP]
Application ~~~ Categories ~~~ ResolverIP ~~~ DoHSubdomain ~~~ Domain ~~~ Host ~~~ Feed
Location ~~~ Type ~~~ SecCategories ~~~ SourceContinent ~~~ SourceCountry ~~~ SourceIP
end
subgraph During [During DNS resolution]
direction TB
AuthNameserverIP[Authoritative Nameserver IP]
end
subgraph After [After DNS resolution]
direction TB
CNAME[DNS CNAME Record]
MX[DNS MX Record]
PTR[DNS PTR Record]
TXT[DNS TXT Record]
Continent[Resolved Continent]
Country[Resolved Country]
IP[Resolved IP]
CNAME ~~~ MX ~~~ PTR ~~~ TXT ~~~ Continent ~~~ Country ~~~ IP
end
Before --> During --> After
end
```

# 参考リンク

- [/etc/hosts が反映されない？安心してください。反映されてますよ。 #Yosemite - Qiita](https://qiita.com/kakipo/items/71f091ecd1fa27fde2ed)
- Firefox ブラウザツール
    - `about:networking#dnslookuptool`
        - [Firefox is ignoring Windows hosts file : r/firefox](https://www.reddit.com/r/firefox/comments/144oh7e/firefox_is_ignoring_windows_hosts_file/)
    - `about:networking#dns`
        - [Firefox ignores /etc/hosts on Macos | Firefox Support Forum | Mozilla Support](https://support.mozilla.org/mk/questions/1444318)
    - [Firefox のキャッシュを消去するには | Firefox ヘルプ](https://support.mozilla.org/ja/kb/how-clear-firefox-cache)