新居にUniFiを導入する

注文住宅で家を建てたので,家の中のネットワークを丸ごと入れ替えた.新築の家なので当然なのだが,だいたい要望して金を払えば自由にできる.ので,好きなようにやってみる.

インターネット回線を引く

何はともあれインターネット回線を契約しなければならぬ.新築の家というのは,当然外部からの引込線もないわけで,これもNTTの人と工事日程を打ち合わせて,家に来てもらって宅内まで光回線ケーブルを引っ張ってもらう必要がある.

ある程度時間がかかることは想定していたけど,光コラボのプロバイダ経由申し込みで,工事日までだいたい4週間くらいかかった.これは間にお盆を挟んだのもあるが,それにしてもある程度の時間がかかるものである.

まず新築の家というのはNTT側でも何も登録されていない新しい住所になるわけで,これを登録してもらう必要がある.これで1週間,次にどの程度の工事が必要なのかの確認に1週間,それから日程決めて,実際に工事に来るという流れになった.

ちなみに申し込んだのはIIJmioの10Gbps(光クロス).

実家では壁の中の配線も自分でやったことがあるのだが,やったのは随分昔.久しぶりにこの状態を見た.

買った機器たち

Kubernetesクラスタのノードたちがいるので,スイッチを買わないとポート数が足らないというのは最初からわかっていた. そんなに高トラフィックにはならないだろうが,VLANを組みたかったのでL3スイッチにしている.

ただし,少し後悔するのは,今であればPro HD 24とかPro XG 10 PoEとかを買いたかった……俺が買った時点では,まだ日本で発売されておらず,2.5GbEのポートがほしければこのくらいしか選択肢がなかった.HDやXGになれば10GbEのポートもあるので,こっちがよかったなぁ.

あとU6+も,U7 Liteにしたかった.そもそもU6+の在庫がかなり薄くて,心配して早めに在庫確保していたんだけど,買った数週間後にU7 Liteも発表されていたので,もう少し待てばよかったと後悔.

ただ,ネットワーク機器なのでどうせまた買い替えるだろう.俺なら絶対買い替える.最後までこれで行こうなんて微塵も思っていない.

宅内LANの設備

設計時にLANケーブルをCAT6Aに指定していた.ついでにPF管もちゃんと通してその中にLANケーブル通してとリクエストしてある.いざとなったら自分で引き直せるように.

この手の記事では,よく天井マウントをしている記事が上がっているが,あえて天井マウントはやっていない.これも,俺なら絶対買い替える自身があった.UniFiのWiFiアクセスポイントは,天井マウントする際にマウンタの受け側を天井に埋め込む必要がある. オフィスのように露出していたり,石膏ボードがすぐにはがせるのであればよいのだが,個人宅の場合,天井の石膏ボードを剥がすというのは結構な大仕事だ. そんな拡張性が低い状態にはしたくなかったので,アクセスポイント用のLANの口は壁付けにしている.

壁付けであれば,

jp.store.ui.com

こういうのもあるし,背の高い本棚や収納があれば,その上に置いても良い.いずれにしろ拡張性が高さを考えて天井マウントにはしていない.

UniFi Dream Machine Special Editionの導入

ここが最大の難関だった.

まず契約していた回線はIIJmioの10Gbps,光コラボなので契約等の窓口はすべてIIJになっている. ここで大事なことは,契約したのはインターネット回線のみであり,ひかり電話等のサービスを申し込んでいないということだ.おそらくひかり電話の場合,ONUだけでなくレンタルルーターを貸与される?気がしていて,その場合今回の構成はあまりアテにならない.

ひかり電話を契約していないので,NTTから貸与されるONUのRJ45からLANケーブルを直接UMD SEのWAN側に接続することになる.

ファームウェアのバージョンアップが必須

現状光クロスの回線は,IPv4 over IPv6がデフォルトの構成になっており,PPPoEによるIPv4のみの接続はサポートされていないことが多い(全部確認したわけではないが).となるとIPoEが必須になるわけだが,公式の情報ではtransixのサポート記事のみが公開されている.

note.com

当然それに合わせてプロバイダも選択したので,IIJmioはtransixだ.

2025年に買ったUDM SEだったが,出荷時状態ではUniFi OS 3.1.16だった. 上記の記事によるとtransixのサポートが入ったのが3.2.6なので,とりあえず上げないと話にならない.

そう,本当に話にならないのであった. UDM SEにLANケーブルを接続し,Bluetoothスマホアプリからセットアップしようとしても,そもそもインターネットに繋がらないのである.なぜなら,この時点で俺は一切IPv4 over IPv6の設定をしていないから. そしてファームウェアバージョンが低いので,そもそもその設定項目がないのである.

ファームウェアはどうやってバージョンアップするのか? インターネット接続するらしい.無理だろ.

SSH接続で手動でファームウェアのバージョンアップをやる

まずテザリングでノートPCをインターネットにつなぐ.

ui.com

ここからファームウェアをダウンロードしてくる.

次に,ノートPCをLANケーブルでUDM SEに接続し http://192.168.1.1 を開く.ここでも当然初期セットアップを要求されるが,無理なのでオフラインセットアップを選択する. このときにパスワード設定をするので,このパスワードを覚えておく.

http://192.168.1.1 を開いて,パスワードを用いてadminユーザでログインする.

そして,https://192.168.1.1/network/default/devices から Device Updates and Settings を開く.ここでSSH Authenticationを有効化しておく.とりあえずパスワード認証で良い.

あとは root@1921.68.1.1sshできる.というわけで,先程ダウンロードしたファームウェアをscpでUDM SEに送りつける.

その先は,sshログインして,

help.ui.com

この案内に従って手動アップデートをやる. 基本的には,SCPさえしてしまえば

ubnt-systool fwupdate /tmp/fwupdate.bin

これをやるだけだった.

これでしばらくして再起動すると,UDM SEのファームウェアが無事最新になる.

インターネット接続設定をする

ここまで来るとようやくIPv4 over IPv6の設定が可能となる.

https://192.168.1.1/network/default/settings/internet ここで,PrimaryのWANを設定する.

  1. IPv4 ConnectionをIPv4 over IPv6P
  2. TypeはDS-Lite (UniFi OS 4.3.6ではMAP-Eも選択肢にあったので,いつの間にかMAP-Eもサポートしてるかも)
  3. Gateway Addressをgw.transix.jp
  4. IPv6 ConnectionをDHCPv6
  5. Prefix Delegation Sizeを60

これでインターネットに疎通することができた.

公式のNoteだと,IPv6 ConnectionはSLAACとなっているが,

www.facebook.com

こちらの情報から,光クロスでONU直結する場合はDHCPv6-PDになるとのこと.

IPv6アドレスも得たい

このままだとIPv4アドレスは割り振られているが,IPv6アドレスが割り振られていないので, https://192.168.1.1/network/default/settings/networks から,defaultネットワークのIPv6を有効化しておく.

  1. Client Address AssignmentをDHCPv6
  2. DHCPv6 Rangeを::2~::7d1
  3. RA PriorityをHigh

にしておく.

これでIPv6アドレスも割り振られるので,IPv6のサイトにはIPv6でアクセスできる.

その他の機器の接続

スイッチのPro Max 24とか,アクセスポイントのU6+は接続して,ソフトウェアアップデートをかけるだけ.もうインターネットに接続されているので怖くない.アップデートボタン押せば勝手にアップデートしてくれる.

ラックにマウントした

UDM SEもPro Max 24もラックマウント対応なので,2Uのラックを買ってきた.

https://www.sanwa.co.jp/product/syohin?code=CP-HBOX2U

見た目はでかいけど,内寸はかなりギリギリ.特にPro Max 24はサイズ的にギリギリだった.これ以上でかいスイッチに買い替える場合,このラックは流用できないな…….

VLANを導入する

そして,ずっとやりたかったのがこれ. そもそもなんでPro Max 24を買ったのかといえば,Kubernetesのノードがあって,これらのためにポートがいっぱい必要になるからであり,それらのVLANを普段遣いのLANと分離したかったからだ. 更にいうとSDSを運用する都合上,ノード間の通信は速ければ速いほど良い.なので,2.5GbEのポートを使いたかった.

しかしVLAN設定は驚くほど簡単だった.だいたいUI上で設定が完結する. https://192.168.1.1/network/default/settings/networks

CIDRを設定するくらいしかやることがない.

で,VLANを設定するからにはアクセス制御をしたいはずなので, https://192.168.1.1/network/default/settings/zones

ここでZoneの設定とAllow/Blockのルールをどんどこ足していく.デフォルトでVPN/Hotspot/DMZあたりは設定されているので,今回はDMZの中にKubernetesクラスタを放り込む.

で,DMZに対してAllow/Blockのルールを追加していく.

こんな感じになる.これで,DMZ側からInternalへは,Returnパケット以外は疎通しなくなる.

おわり

ちなみにNICは2.5GbEのままなので,10Gbpsまでの速度はでない.それにしてもかなり速くなって大満足である.

apt-getもdocker pullも格段に速くなった.ghcr.ioやgcr.ioやECRからのdocker pullはバックボーンのネットワークがまともであるので,格段に快適になった.

Rheomeshで録画機能をサポートする

h3poteto.hatenablog.com

ここで作っていたOSSに録画機能を入れた.

github.com

それと,ドキュメントサイトを作っておいたので,こちらに詳しい機能説明を載せてある.

h3poteto.github.io

録画機能と言ってもRTPパケットを転送するだけ

関数一つで録画ファイルまで生成してしまうのも悪くはないんだけれど,サーバとして稼働することを考えるとあんまり自由度がない. むしろもっとシンプルにして,エンコード等は外でやってもらうほうがいいかと思い,単にRTPパケットを転送するだけにした. ちなみにffmpegもGStreamerも,送られてきた生RTPパケットから動画ファイルを作り出せるので,録画自体はそれらにお任せする設計にしておく.

ただ,一部SDPはほしいかと思ったので,SDPを生成できるようにしておく.特にffmpegあたりはSDPを元に録画をするので,これがないと話にならない.

docs.rs

generate_sdp で,SDPの文字列が得られる.あとは,これを指定してffmpegを起動しておいて,

$ ffplay -protocol_whitelist file,rtp,udp -analyzeduration 10000000 -probesize 50000000 -f sdp -i stream.sdp

で,受信を待機させる. この状態で

docs.rs

start_recording すれば,生RTP転送が始まるので,ffplayで映像が確認できる.

録画したものをファイルに保存しておきたい場合は,

$ ffmpeg -protocol_whitelist file,rtp,udp -i stream.sdp -c copy output.mkv

こういうコマンドにしておくと output.mkv に録画されることになる.

GStreamerであれば,コマンドライン引数でこういうのを指定するので,SDPを読んだうえで

$ gst-launch-1.0 udpsrc port=30001 ! \
  application/x-rtp,payload=103,encoding-name=H264 ! \
  rtph264depay ! \
  h264parse ! \
  avdec_h264 ! \
  videoconvert ! \
  autovideosink

こんな感じのコマンドで待機しておく.使ってるエンコーディングによって多少パラメータは変わるが,そのへんは公式のドキュメントで,

gstreamer.freedesktop.org

gstreamer.freedesktop.org

このへんを調べてもらって,SDPにかかれているエンコーディングと合致したものを指定してもらいたい.

Exampleもあるよ

h3poteto.github.io

cameraのexampleに録画機能のexampleも付属させておいた.

これは画面上にSDPを出力するので,それをコピーしてffmpegなりGStreamerで受信してもらえれば良い.

次はrelayをもうちょっと改善しようと思っている.

旭川->稚内まで旅行してきた

久しぶりに旅行の写真でも載せておこうと思う.

まずは旭川カレーのちから.玉ねぎが爆発しとる.

なんとカレーの自販機もある.

そして,Otokoyama Sake Park高砂酒造

そこから名寄に移動.もち米の里らしい.

夜はBrew Hiveでビール.

翌朝は玉ねぎ屋のマグロ丼.

時間があったので朱鞠内湖へ.イトウは見えなかった.

昼はBSB.昨日Brew Hiveで飲んだビールの醸造所+レストラン.でも車運転してるのでビールは飲めない. ので,クラフトコーラを飲んだ.

あとはジンギスカン.自分ところで育てているらしいよ.

美深の道の駅にはチョウザメがいっぱいいる.

そこから遠別方面に向かい,あとはオロロンラインをひたすら北上する.

オトンルイ風力発電の風車.

海岸.

雲の中に見える利尻富士と夕日.

そしてノシャップ岬へ.

この日は,稚内で北門神社の祭りをやっていた.

翌日,ようやく最北端へ.

宗谷丘陵.

そして樺太食堂のウニ丼.

実は稚内のお祭りに被っていたことを全然知らずに来てしまったので,ある意味運がいいけど,稚内の普通の飲み屋に全然入れなかった. それもあって,北海道に来た割には海産物をあまり食べていない. そりゃ旭川も名寄も美深も,海まで100kmくらいはあるので,全然海産物の町じゃないのはわかっていたけど.

Cloudflare Zero TrustのWARP Clientを使った状態で広告をブロックする

h3poteto.hatenablog.com

Zero Trustを使い始めたんだけど,こいつをモバイル端末で使う場合,WARP Clientを使うことになる.前回説明した通り,ブラウザ利用であれば特にWARPは不要なんだが,アプリ内でAPIを叩くような場合はこれが必須となる. で,WARPというのはVPNも使っているので,自動的にVPNが設定される.この状態でAdblockするにはどうしたらいいだろうか.

通常モバイル端末でAdblockしようとすると,

  1. VPNをいれる
  2. 広告をブロックしてくれるブラウザを使う

の2択になると思う.ブラウザしか使わないのであれば,2は良い選択肢である.アプリ内ブラウザ等まで対応しようと思うと,1しか選択肢がなくなる. しかし,このVPNWARPVPNと競合するので,どちらか一つしかいれることができない.

というわけで,理想的にはWARPのVNPを有効化している状態でもAdblockできたら良い.

大前提: Split TunnelsとGatewayは別物

developers.cloudflare.com

Split Tunnels only impacts the flow of IP traffic. DNS requests are still resolved by Gateway and subject to DNS policies unless you add the domains to your Local Domain Fallback configuration.

Split Tunnelsはトラフィックを特定のTunnel経由にするというもの.対してGatewayはCloudflareのDNSであって,全く別のレイヤーの話をしている. WARP Clientを使うときは,常にDNSとしてCloudflareのDNSが使われている.ここの制御をするのがGatewayで,GatewayのpoliciesはDNSの挙動に関わる制御をしている.

対して,Split Tunnelsはドメインの解決後,トラフィックをどのルートで送るのかという話しかしていない.なので,ここにDNSは関係ない.

ということは,例えばSplit Tunnels側ではincludeルールで,example.comトラフィックのみをSplit Tunnelsに流しているとする.この場合でもWARP Clientを使えばGatewayを通るので,DNSFirewall Policyは普通に使える.

このGatewayを回避できるのは,Local Domain Fallbackだけだ.ここに設定したドメインだけはCloudflareのDNSを通さずにドメイン解決することができる.だから,GatewayのPolicyを回避させたいようなドメインがある場合はここに設定すると良い.localhost とかね.

Gatewayで特定のドメインをブロックする

qiita.com

基本的にはこの記事と同じことをしている.

Listを作るのは面倒くさかったので全部Terraformにしている.

locals {
  list_size   = 1000
  total_lists = ceil(length(var.ad_domains) / local.list_size)
}

resource "cloudflare_zero_trust_list" "ads" {
  count       = local.total_lists
  account_id  = var.account_id
  name        = "advertisements_${count.index + 1}"
  description = "List of advertisement domains ${count.index + 1}"
  type        = "DOMAIN"

  items = [
    for domain in slice(var.ad_domains, count.index * local.list_size, min((count.index + 1) * local.list_size, length(var.ad_domains))) : {
      value = domain
    }
  ]
}

Listsは上限が1000件なので,ドメインのリストを1000件ずつに分離してListを作っている.こうしておけば,ドメインリストが更新されたらterraform apply するだけで良い.

resource "cloudflare_zero_trust_gateway_policy" "block_ad" {
  account_id     = var.account_id
  name           = "Block ad"
  description    = ""
  device_posture = ""
  enabled        = true
  action         = "block"
  filters        = ["dns"]
  identity       = ""
  precedence     = 2047
  traffic = join(
    " or ",
    [for list in cloudflare_zero_trust_list.ads : format("any(dns.domains[*] in $%s)", list.id)]
  )
  rule_settings = {
    block_page_enabled                 = false
    insecure_disable_dnssec_validation = false
    ip_categories                      = false
    ip_indicator_feeds                 = false
    ignore_cname_category_matches      = false
    resolve_dns_through_cloudflare     = false
  }
}

あとはGateway Policyを作るだけ.

Split Tunnelsには特に触れない

すべてのトラフィックをcloudflared経由にする必要はないと思っていたので,Include IPs and domains で運用している.家のサービスだけをここに追加しているので,他のトラフィックはZero Trustを流れなくなる.

これでWARP ClientのVPN接続した場合でもある程度の広告がブロックできるようになった.

ちなみにDNSを迂回するのは現実的じゃない

世の中にはpi-hole みたいなものもあって,「OS側の設定でDNSを全部pi-holeに回しちゃえばいいじゃん」と思ったときもありました. ここには大きな問題があって,そもそもWARP ClientはDNSも保護されていると謳っている.それはつまり,DNS自体もCloudflareが用意するDNS over HTTPSが強制的に使われていることを意味している.一応確認したけど,OSでのDNS設定をWARP Clientは上書きしてくるので,この方法は無力だった.

もちろんWARP Clientを使わない前提であれば良い選択肢だと思う.

Cloudflare Zero TrustでおうちKubernetes上のサービスに外部からアクセスする

今更ながらZero Trustを構築したので,書いておく. 基本的にここにあんまりお金をかけたくないので,Cloudflareの利用料金は限りなくゼロに近い.唯一,どうしてもドメインが必要になってしまったので,ドメイン料金だけは払っている.

以前ImmichをおうちKubernetes上に構築していた.

h3poteto.hatenablog.com

これ,LAN内であればそのままアクセスできる構成にしていたけど,出先等でスマホアプリからも見られるようにしたかった.以前はTailscaleを使って,スマホアプリからVPNを張って見られるようにしていたんだが,これだとLAN内すべてのサービスに無制限にアクセスできちゃうのであまりよろしくない. 自分だけが使うのならまだしも,例えばImmichだけ仲間内に公開したい,というようなことを考えたときに,Tailscaleだとどうしても不安になってしまう.

例えば,longhornの管理画面とかは認証がなかったりするので,とても気になる.

というわけで,流行りのZero Trustを構築してみた.

概要

今現在俺が理解している認証の概要を書く.

まずCloudflare Zero Trustは,LAN内などのPrivate Network上にあるサービスを,認証付きで公開できる(Zero Trustの目的はこれだけではないので,そういう全体的なお話は別の記事を探してくれ).どうやって?というのも別記事を参照してほしいんだが,ここでの認証として大きく2タイプが存在する.

  1. ブラウザ上で動くWebアプリケーションに対してCloudflare Access経由で認証しアクセスを制御する
  2. WARP ClientでVPNを張ってアクセスする

1は基本的にブラウザ上で動くもの向けだ.例えばPrivate Network上のimmichを immich.example.local とかで公開するとする.このとき,example.local ドメインはCloudflareのDNSで管理されている必要がある.そして我々が immich.example.local にブラウザでアクセスすると,*.cloudflareaccess.com にリダイレクトされる.ここでSSOの認証を挟む.認証成功したら,Private Networkのimmichにリダイレクトされる形になる.

2はよくあるVPNと同じように,WARPというcloudflareが用意するクライアントアプリケーションを使う.このクライアントアプリケーションでログインすることで,VPNが貼られてPrivate Network上のサービスにアクセスできるようになる.ただし,ここでもアクセス制御は多少できるようになっている.

Cloudflare Zero Trustの準備

Authenticationの準備

とりあえずCloudflareのダッシュボードに入れる状態にしておく.

Zero Trustのダッシュボードで,Settings -> Authenticationと進む.Login methodsとしてGoogleを追加した.ここはどのような認証方法でも好きなものを選んだら良い.Google WorkspaceとGoogleがあるが,Workspaceはご存知の通りGoogle Workspaceを持っていればそれを使える.つまりWorkspaceユーザのみがログインできるということだ.

Googleの場合,個人のGoogleアカウントでOAuthアプリケーションを作ってそれを利用する.この場合,誰でもこのOAuthアプリにはアクセスできてしまうが,後述するPolicyでメールアドレスの制限をすることで,ログインできるユーザを絞り込むことができる.つまり,Workspaceを持っていて,独自のemail domainを持っているのであれば,Workspaceを選択すれば良い.そうではなく @gmail.com のユーザを,メールアドレス制限でログインできるようにするのがGoogleだ.

今回はGoogleで作成している.だいたいドキュメントどおりにOAuthをgoogle側で作って,ClientIDとClientSecretを設定してやれば問題ない.ログインできるユーザの制御は後述.

Policyの作成

Zero TrustのダッシュボードでAccess -> Policiesから新規にPolicyを作成する.このPolicy,後述するApplicationのアクセス制限と,WARP Clientのログイン制限の両方に使うことができる.両方とも同じPolicyを使ってもいいのだが,わかりにくいので俺は分離した.

まず,WARP Clientログイン用のPolicyを作る.

ここでEmailの制限ができる.前述の通り,googleのOAuthで誰でもOAuth認証はできるのだが,ここで認可されないので自分以外はWARP Clientにログインできないように制限することが可能となる.

次に,Cloudflare Access経由でログインしたときに,同じように認証できるようにPolicyを作っておく.条件はまったく同じ.

WARP Clientの設定

Device enrollment

Settings -> WARP Client -> Device enrollmentの設定をしておく.ここで先程作成したWARP Client用のPolicyを当てる.

Profile

次にProfileも作っておく.だいたいデフォルトでいいのだが,Split Tunnelsだけちょっと設定を入れている. 次のステップで,Tunnelsを作るのだが,WARP Clientで認証したユーザはどの範囲までCloudflare Tunnelsを通過させるかをここで設定することができる.もちろんすべての通信をTunnels経由にすることもできる.のだが,よくよく考えると俺はKubernetes上のサービスにアクセスしたいだけで,普段のインターネット接続をすべてZero Trust経由にしたいわけではない.なので,デフォルトは Exclude IPs and domains になっているところを Include IPs and domains に変更した.こうすることで,特定のIPやドメインのみをZero Trust経由にすることができる.

なお,Kubernetes API Server等,そもそもLAN内の一部のサービスに関しては,Zero Trust経由にしたくないものもある.なので,この Include IPs and domains はかなり狭い範囲に限定している.192.168.0.0/24 とかも指定できるのだが,LANをすべてZero Trust経由にしてしまうと,Kubernetesクラスタに問題が発生し,cloudflaredのPodが死んだときに何もアクセスできなくなってしまう.

Device posture

WARPを認証しているという事実を伝えるためだけのWARPというclient checksを追加した.これで,WARPで接続しているかどうかという条件を,Policy内で利用することができるようになる.

ドメインの登録

ここから先,サービスにホスト名を割り当てるために専用のドメインが必要になる.Zero Trustのダッシュボードから,Cloudflareのダッシュボードに移動し,Domain Registrationからドメインを一つ登録する.ここで買ってもいいし,外部で買ったドメインをここに登録してやってもいい.いずれにしろ,DNSとしてCloudflareのDNSを使う状態にしておく. そうしないと,ホスト名でアクセスしたときにCloudflare accessがリダイレクトを仕込むことができない.

Tunnelsの有効化とclodflaredの起動

Zero Trustのダッシュボードで,Network -> Tunnelsから新たにTunnelを一つ作る.このとき,Public HostnameとPrivate Networkの指定ができる.

Public Hostname

ここでは,このTunnel経由でアクセスしたいホスト名を指定する.ここで,指定するホスト名は必ず前述のドメインで登録したもののサブドメインである必要がある.ちなみにサブサブドメインを一度使ってみたのだが,これはうまく行かない.証明書がサブドメインまでしか対応しないので,追加でACMで証明書を用意する必要がある.が,これには追加課金が必要なのであまりおすすめしない. h3poteto.home みたいなドメインを買ったのであれば, immich.h3poteto.home までにしておくのが良い.

で,ここで接続先のサービスを指定する.まぁKubernetes内にTunnelを作るのであれば,Kubernetes内で解決できるホスト名やIPであれば問題ない.通常ServiceはClusterIP等で登録されているので,immich.h3poteto.home に対しては, http://immich-server.immich.svc.cluster.local:2283 みたいなServiceを設定しておけば良い.

Private Network

ここには,Tunnel経由でアクセスさせたいネットワークを記述する.例えばTunnel経由でLAN内全部アクセス許可したいのであれば, 192.168.0.0/24 を許可してしまってもいいわけだ.

ただ,先程言ったように,LAN内をすべてZero Trust経由アクセスにするのは,それはそれで弊害も大きいので必要な範囲に絞るのをお勧めする.

我が家のクラスタでは,MetalLBにより一部Service type LoadBalancerにIPを付与している.

h3poteto.hatenablog.com

ここにIP指定でアクセスさせたいものがある場合,そのIPを指定している.それ以外は許可していない.

Podのデプロイ

TunnelのOverviewにインストール方法が乗っている.とりあえずDocker版のコマンドをコピーして token を得る.あとは,deploymentとsecretを作って,podをデプロイしておく.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: cloudflared
  labels:
    app: cloudflared
spec:
  replicas: 1
  selector:
    matchLabels:
      app: cloudflared
  strategy:
    type: Recreate
  template:
    metadata:
      labels:
        app: cloudflared
    spec:
      containers:
        - name: cloudflared
          image: cloudflared
          args:
            - tunnel
            - --no-autoupdate
            - run
            - --token
            - "$(TOKEN)"
          resources:
            requests:
              cpu: 200m
              memory: 300Mi
            limits:
              memory: 600Mi
          envFrom:
            - secretRef:
                name: cloudflare-secrets

Accessの設定

ようやくアクセス制限だ.Access -> Applicationsから新しくApplicationを作る.ホスト名指定でアクセスするKubernetes上のアプリケーションであれば,Self-hostedを選べば良い. Public hostnameとして,先程TunnelのPublic Hostnameで指定したものと同じ値を指定する. immich.h3poteto.homeみたいな.で,ここにPolicyとして,Cloudflare Access経由のログインPolicyを当てる.

動作確認

これですべての用意が整った.ブラウザで https://immich.h3poteto.home を開く.

と,このような画面が表示される.google認証で,許可されたemailだった場合のみ,immichのページにアクセスできる.

アプリからAPIに直接アクセスする

デスクトップアプリやスマホアプリでの問題点

ブラウザであれば,上記のようにブラウザがリダイレクトを処理してくれて,セッショントークンをつけてくれるので,問題なくCloudflare access経由でリダイレクトされるようになる.問題はスマホアプリ等だ.だいたいのアプリはWeb APIを叩いてjson等で情報のやり取りをする.となると,いきなりCloudflare accessにリダイレクトされてしまっては困るわけだ.仮にAPI呼び出しがリダイレクトに追従していたとして,Cloudflare accessのログイン画面を突破することはできない.

WARP Clientを通していれば許可する

このような場合の解決策が,WARP Clientだ.先程,WARP Clientの設定で,Device postureを設定しておいた.このDevice postureを通過している場合に,リクエストをすべて受け付けるようにすれば,APIであってもリダイレクトを挟まずにアクセスすることができる.

新しく,Service AuthのPolicyを作成する.

developers.cloudflare.com

Bypassというのもあるが,これはアクセス制御を無効化してしまうので,あんまり推奨されない.WARPで認証している前提であればService Authで十分である.

ここでは,Warpが必須という条件をつけている.

この状態にしておいて,スマホWARP Clientをインストールし,自分のworkspaceにログインしておく.

あとは通常通りimmichのアプリを使うと無事APIを叩けるようになっている.

WARP Clientがない場合はCloudflare accessに回したい

Policyのルールの優先度は,Bypass/Service Auth > Alow/Blockとなる.

dev.classmethod.jp

そのため,Service Authに加えて通常のAllow/Blockでemail制限を書いておけば,WARP Clientがない場合にはCloudflare accessに飛ばされるようになる.

特定のIPを公開する

今まではホスト指定でのアクセスの話をしてきた.HTTPで通信する一般的なWebアプリケーションならこれでもいいのだが,TCPにしろUDPにしろちょっと変わったアプリケーションだと,複数のポートを使ったりするものがある.例えば,アプリケーション自体は myapp.h3poteto.home でアクセスできるのだが,使っているポートが複数あり, myapp.h3poteto.home:30320myapp.h3poteto.home:20234,のような形で,同じドメインで複数ポートを公開し使っている場合もある.こういうアプリケーションの場合,現状Zero TrustのPublic Hostnameでは対応していない.ポートごとに別のドメインを割り当てるのであれば可能なのだが,一つのドメインで複数のポートアクセスをする形では作られていない.

というわけで,こういう場合IPアドレスで指定する方が楽にできる.例えばこのドメイン192.168.0.130 のIPをMetalLBから割り当てられているとしよう.前述の通り,まずTunnelsのPrivate Networkに 192.168.0.130/32 を追加しておく.次に,WARP Clientの Include IPs and domains にも同じCIDRを追加しておく必要がある.

最後に,Access -> Applicationsで新規アプリケーションを追加する際に,Private networkを選択しアクセスを許可したいIPとポートの範囲を決める.このとき自動的にAllow ruleとBlock ruleができる.AllowのIdentityに

このようにEmailを指定しておく. こうすると,WARP Clientでyour-gmail-address で認証したユーザのみが 192.168.0.130 にアクセスできるようになる.

LANでしかアクセスしないサービスは除外する

ここまで構築してきて,いよいよ以前作っていた

h3poteto.hatenablog.com

この仕組みは不要になるかなぁと思っていたのだが,結局こちらも残すことにした.そもそも外部からのアクセスが一切不要なサービスや,先程のように同一ドメインで複数ポートを使うようなサービスも用意しているため,これらをすべてCloudflare経由にする必要がない.

外部からアクセスしてくる通信が,すべてCloudflare Zero Trustで守られる前提であれば,LANでしかアクセスできないサービスはそのまま維持していても特に問題ないのではなかろうか.

もう一歩やりたい

APIアクセスをService Authで実現していたが,ここでEmailによるフィルタリングができると最高だ.Cloudflare accessにリダイレクトせずにEmail制限をかける方法がないかなぁーと探索中.Private Networkであれば,WARPに認証したユーザのEmailでフィルタがかけられるので,同じようなことがService Auth + Self-hostedで実現できるといいのだが…….