iCloud 비공개 릴레이로 생긴 접근불가
Outline VPN은 테라폼을 활용해서 AWS 인스턴스를 프로비저닝하고 outline-vpn 설정을 자동으로 구성해주는 프로젝트인데 outline-vpn으로 인스턴스를 프로비저닝 도중 에러가 발생하면서 프로비저닝이 중단된 상태가 발생하였다. 문제를 살펴보니 프로비저닝 동작 과정에서 인스턴스의 보안그룹 22번 포트 소스 아이피를 내 PC의 공인 아 이피로 제한하는데 비공개 릴레이 기능을 사용할 경우 소스 아이피가 애플의 릴레이 서버로 등록되면서 내 공인 IP가 보안그룹에 등록되는 것이 아닌 프록시 서버의 공인 아이피가 등록되어 인스턴스에 접근하지 못하면서 문제가 발생했던 것이었다. 여기서는 프록시 서버의 공인 아이피라고 이야기하였지만 정확하게 말하자면 GeoHash 값에 따라서 할당받는 아이피이다. 이는 뒤에서 자세하게 설명합니다.
위 사진처럼 iCloud의 50GB 데이터 스토리지를 월 1,100원 가량 지불하면서 비공개 릴레이 기능 또한 사용할 수 있게 되었지만 비공개 릴레이 사용으로 인해 발생되는 동작을 몰랐기에 위에서 발생했던 문제의 에러처리를 하지 못하였고 이를 위해서 먼저 아이클라우드(애플)에서 제공하는 비공개 릴레이 기능에 대해서 자세히 알아보고자 이 글을 작성하였습니다.
아이클라우드의 비공개 릴레이 기능은 네트워크 환경설정에서 아래 그림과 같이 IP 주소 추적 제한을 클릭할 경우 사용되는 기능입니다.
iCloud 비공개 릴레이는 사용자 데이터를 처리하는 서버에서 사용자가 누구인지 또는 사용자가 액세스하려는 항목에 대해서 사용자의 아이피(위치, 브라우징 활동) 등 민감할 수 있는 정보들을 가지고 있지 않도록 보장하며 이 보장을 위해서 사설 프록시 서버는 최신 암호화 및 전송 메커니즘을 사용해서 사용자 기기의 트래픽을 대상 웹사 이트(브라우저)로 전송하기 전에 Apple 및 파트너 인프라(아카마이 / 클라우드플레어)를 통해 트래픽을 중계한다고 설명하고 있습니다.
아래는 비공개 릴레이를 사용하지 않을 때 메타데이터에 접근을 보여주는 그림입니다.
비공개 릴레이를 사용할 경우 아래와 같이 작동합니다. 서버 도착 전까지 두 번의 프록시 서버를 거치는 것을 확인할 수 있습니다.
비공개 릴레이는 사용자를 식별하는 IP 주소가 사용자가 액세스하는 웹사이트로부터 사용자가 액세스하는 웹사이트의 이름과 분리되어야 한다는 원칙에 기반하여 이러한 분리를 위해 Apple은 듀얼 홉 아키텍처를 설계하였으며 이에 따라 사용자의 요청은 서로 다른 두 개의 인터넷 릴레이를 통해 전송된다고 합니다.
듀얼 홉 아키텍처
첫 번째 릴레이 서버는 전 세계 여러 지역에서 Apple이 직접 운영하며 사용자가 검색할 때 원래 IP 주소가 첫 번째 릴레이 서버와 네트워크에 표시되지만 사용자가 요청한 웹사이트 이름은 암호화되어 어느 쪽에서도 볼 수 없습니다.
두 번째 릴레이 서버에서는 원래 사용자의 IP를 알지 못하며 사용자에게 릴레이 IP 주소를 할당하기 위해 충분한 위치 정보만 수신하며 이에 따라 해당 지역에 매핑되는 릴레이 IP 주소를 할당 받습니다. 두 번째 릴레이 서버는 타사 파트너(클라우드 플레어)에 의해서 운영됩니다.
사용자가 할당받는 릴레이 아이피
프라이빗 릴레이에서 사용자에게 할당해 주는 아이피 주소는 사용자가 인터넷에 접속하는 국가 또는 지역에 매핑되는 대표 아이피 주소입니다. whatismyip에서 실제로 비공개 릴레이를 사용했을 때 아래와 같은 정보를 확인할 수 있었습니다.
첫 번째 릴레이 서버에서는 사용자가 와이파이를 연결하여 퍼블릭 아이피를 할당받고 이 오리지날 아이피를 Geohash로 변환하여 디바이스에 전달하면 디바이스는 이 정보를 두 번째 릴레이 서버에 전달하게 되고 두 번째 릴레이 서버에서는 전달받은 Geohash 정보를 바탕으로 사용자의 대략적인 위치를 파악하여 주소 풀에서 해당 위치와 매핑된 대표 릴레이 아이피 주소를 client IP로 변환하여 사용자가 접속했던 웹 서버에 전달하게 됩니다.
사용 프로토콜
사파리와 비공개 릴레이를 이용하는 애플리케이션의 연결은 가장 흔하게 사용되는 두 가지 인터넷 전송 프로토콜인 TCP와 UDP를 사용하며 이러한 연결을 프록시하기 위해 비공개 릴레이는 IETF 내의 MASQUE 그룹이 개발하고 있는 기술을 사용합니다. 구체적으로 MASQUE는 HTTP/3와 QUIC를 안전한 프록시 기술로 사용하는 방법입니다.
비공개 릴레이는 특히 QUIC의 몇몇 기능을 이용해 프록시 연결을 보다 효율적이고 안전하게 만들어주며 QUIC 다른 데이터 스트림의 다중화를 허용하므로 웹사이트에 대한 모든 연결을 단일 QUIC 연결로 보낼 수 있습니다. 또한 QUIC는 불안정한 데이터그램을 프록시하는 것을 지원하여 UDP를 사용하는 서버에 접근하는데 더 좋습니다.
QUIC에는 TLS 1.3이 내장되어 있어 사용자 디바이스와 프록시 간에 암호화된 세션을 설정하는 강력한 암호학적 핸드셰이크를 제공하며 프록시를 인증하기 위해 디바이스들은 TLS 핸드셰이크에서 보내진 원시 공개키를 검증하고 이를 별도로 인증된 설정에서 공유된 예상값과 비교합니다. QUIC는 이 핸드셰이크의 결과를 이용하여 이후 세션 동안의 트래픽을 보호합니다.
QUIC 트래픽이 차단된 네트워크에서는, 장치들이 HTTP/2 의 CONNECT 방법을 사용하여 프록시와 통신하며, 이때도 TLS 1.3과 원시 공개키가 필요합니다.
DNS 조회
DNS는 인터넷을 사용할 때 서버 이름을 IP 주소로 변환하는 시스템입니다. DNS 조회를 관찰하는 능력은 잠재적인 추적자가 사용자 활동을 모니터링하게 합니다. 디바이스가 보낸 모든 쿼리에 대한 DNS 이름 해석의 개인 정보를 보호하고 이러한 추적을 방지하기 위해, 비공개 릴레이는 Oblivious DNS over HTTPS (ODoH)를 사용합니다.
ODoH는 DNS 쿼리를 첫 번째 인터넷 릴레이를 통해 보내므로, DNS 서버는 쿼리를 발행하는 사용자를 식별할 수 없습니다. 각 쿼리 자체는 Hybrid Public Key Encryption (HPKE)를 사용하여 패딩되고 암호화되어, 첫 번째 인터넷 릴레이가 사용자가 조회하는 도메인 이름을 알 수 없도록 합니다.
ODoH를 통해 검색된 DNS 응답이 디바이스가 있는 네트워크에 대해 올바른 것임을 보장하기 위해, 디바이스는 첫 번째 인터넷 릴레이로부터 공개 IP 주소 서브넷을 알아낼 수 있고, 그 값을 EDNS0 Client Subnet 옵션을 사용하여 암호화된 쿼리에 포함시켜 DNS 서버에 보냅니다.
Safari와 암호화되지 않은 HTTP는 연결 프록싱을 사용하므로, 먼저 ODoH 쿼리를 할 필요가 없습니다. 그들은 IP 주소 대신 이름을 사용하여 프록시를 통해 연결합니다.
비공개 릴레이를 사용할 수 있는 디바이스 구분
비공개 릴레이는 유효한 애플 디바이스와 계정에서만 사용할 수 있도록 설계되었으며 이러한 디바이스 및 계정 구분은 인증을 기반으로 합니다. RSA Blind Signatures를 기반으로 한 유효한 익명 토큰을 제공하며 이 서명들은 연결을 설정할 때 각 프록시에게 일회용 토큰으로 전송되어 합법적인 디바이스와 불법적인 디바이스를 구분합니다. 프록시는 토큰을 공개키로 검증하여 사용자가 합법적인지 확인할 수 있지만, 실제로 사용자를 식별하지는 않습니다. 토큰과 키는 사용자가 최근에 인증했음을 보장하기 위해 일일히 교체됩니다.
이 블라인드 서명을 생성하기 위해 사용자의 디바이스는 애플 서버에 연결되고 인증되며 비공개 릴레이를 사용할 수 있는 애플 디바이스와 유효한 아이클라우드+ 계정을 사용하는 사용자만이 사용할 수 있도록 서버는 토큰을 제공하기 전에 Basic Attestation Authority (BAA) 서버를 사용하여 디바이스와 계정 인증을 수행합니다.
비공개 릴레이 사용시 남겨지는 로그
- 연결 속성 및 성능 지표
- IP 주소에서 파생된 네트워크 및 지역 정보
- 익명 토큰 유효성 검사의 성공률 및 성능
- 비공개 릴레이 시스템 자원 사용량
- 아이클라우드 계정
- 소프트웨어 버전
- 요청 타임스탬프
위 비공개 릴레이 정보를 바탕으로 만약 지역마다 할당하는 아이피 대역 정보가 있다면 내 공인 아이피가 이 아이피 대역에 포함되었을 때 비공개 릴레이를 사용하고 있는 것으로 간주할 수 있습니다. 인터넷 여기저기 찾아본 결과 이러한 아이피 대역을 mask-api.icloud.com 이곳에서 찾을 수 있었고 마침내 사용자가 비공개 릴레이를 사용하고 있는지의 여부를 판별할 수 있게 되었습니다.
Golang 코드 - 비공개 릴레이 사용여부 확인
작성한 코드는 다음과 같습니다.
package main
import (
"encoding/csv"
"fmt"
"io"
"log"
"net"
"net/http"
"strings"
)
type IPRange struct {
IPNet *net.IPNet
Country string
State string
City string
}
const (
icloudCSV = "https://mask-api.icloud.com/egress-ip-ranges.csv"
defaultIPv4URL = "http://ipv4.icanhazip.com"
)
func main() {
currentIPv4, err := getMyIP()
if err != nil {
fmt.Println(err)
}
ipRanges, err := fetchIPRanges(icloudCSV)
if err != nil {
log.Fatal(err)
}
ip := net.ParseIP(currentIPv4)
if ip == nil {
log.Fatal("Invalid IP address")
}
found := false
for _, ipRange := range ipRanges {
if ipRange.IPNet.Contains(ip) {
fmt.Printf("IP %s is within the range: %s, %s, %s, %s\n", currentIPv4, ipRange.IPNet.String(), ipRange.Country, ipRange.State, ipRange.City)
found = true
break
}
}
if !found {
fmt.Printf("IP %s is not within any IP range\n", currentIPv4)
}
}
func getMyIP() (string, error) {
resp, err := http.Get(defaultIPv4URL)
if err != nil {
return "", err
}
defer resp.Body.Close()
buf, err := io.ReadAll(resp.Body)
if err != nil {
return "", err
}
return strings.TrimSpace(string(buf)), nil
}
func fetchIPRanges(url string) ([]IPRange, error) {
resp, err := http.Get(url)
if err != nil {
return nil, err
}
defer resp.Body.Close()
ipRanges := make([]IPRange, 0)
reader := csv.NewReader(resp.Body)
for {
record, err := reader.Read()
if err == io.EOF {
break
}
if err != nil {
return nil, err
}
ipNet, err := parseIPNet(record[0])
if err != nil {
log.Println("Failed to parse IP range:", err)
continue
}
ipRange := IPRange{
IPNet: ipNet,
Country: record[1],
State: record[2],
City: record[3],
}
ipRanges = append(ipRanges, ipRange)
}
return ipRanges, nil
}
func parseIPNet(cidr string) (*net.IPNet, error) {
_, ipNet, err := net.ParseCIDR(cidr)
if err != nil {
return nil, err
}
return ipNet, nil
}
출처 보기
https://www.apple.com/privacy/docs/iCloud_Private_Relay_Overview_Dec2021.PDF
https://blog.cloudflare.com/icloud-private-relay/
https://blog.ip2location.com/knowledge-base/how-to-detect-icloud-private-relay/
https://community.fortinet.com/t5/FortiGate/
Technical-Tip-How-to-block-iCloud-Private-Relay-from-bypassing/ta-p/228629
https://mask-api.icloud.com/egress-ip-ranges.csv
https://developer.apple.com/support/prepare-your-network-for-icloud-private-relay/