MiniRent 엔지니어링 가이드

클라우드 Mac에서 iOS ATS 예외와 TLS 연결을 검사하는 방법

클라우드 Mac에서 iOS ATS 예외와 TLS 연결을 검사하는 방법

테스트 환경에서는 항상 API에 접속할 수 있었는데, 아카이브를 테스트 팀에 전달하자마자 네트워크 오류가 발생하는 경우가 있습니다. 반대로 더 위험한 상황도 있습니다. 인증서 문제를 임시로 우회하려고 설정한 NSAllowsArbitraryLoads가 배포 구성에 그대로 남아 있으면 App은 정상으로 돌아온 것처럼 보이지만, 원래 ATS가 차단해야 할 연결까지 모두 허용하게 됩니다. 이 두 가지 사고를 방지하려면 저장소의 Info.plist만 확인해서는 안 되며, 클라우드 Mac에서 실제로 컴파일된 App을 검사해야 합니다.

먼저 게이트에서 검사할 항목 정의하기

실제로 운용할 수 있는 ATS 게이트는 최소한 정적 구성, 호스트 측 TLS 핸드셰이크, 애플리케이션 계층 요청의 세 단계로 나눠야 합니다. 각 단계에서 확인하는 문제가 다르므로, 한 번의 curl 성공으로 전체 검증을 대신할 수 없습니다.

계층 검사 대상 실패 시 우선 확인할 항목
정적 구성 App 내부의 최종 Info.plist 빌드 구성, 스크립트에 의한 변경, 도메인 예외
TLS 점검 클라우드 Mac에서 대상 엔드포인트까지의 연결 DNS, 인증서 체인, 프로토콜 버전, 리디렉션
애플리케이션 요청 URLSession의 실제 동작 ATS, 세션 구성, 인증 및 응답 파싱

먼저 정책을 명확히 정의해야 합니다. 배포 산출물에서는 NSAllowsArbitraryLoads를 금지하고, NSExceptionDomains에는 검토를 통과한 도메인만 포함해야 합니다. 임시 예외에는 담당자와 제거 조건을 명시해야 하며, 로컬 디버깅용 설정이 Release 구성에 들어가서는 안 됩니다.

ATS 예외는 “일단 네트워크를 동작시키기 위한” 범용 스위치가 아니라, 적용 범위와 사유, 제거 계획을 설명해야 하는 보안 변경 사항입니다.

최종 빌드 산출물 감사하기

소스의 plist는 INFOPLIST_KEY_*, 서로 다른 xcconfig 또는 빌드 스크립트에 의해 덮어써질 수 있습니다. 먼저 Release 빌드를 완료한 후 산출물 경로를 확인합니다.

set -euo pipefail

xcodebuild \
  -scheme "$SCHEME" \
  -configuration Release \
  -sdk iphonesimulator \
  -derivedDataPath "$PWD/.derived-data" \
  build

APP_PATH="$(find "$PWD/.derived-data/Build/Products" \
  -type d -name '*.app' -path '*Release-*' -print -quit)"

test -n "$APP_PATH"
PLIST="$APP_PATH/Info.plist"
plutil -lint "$PLIST"
plutil -extract NSAppTransportSecurity json -o - "$PLIST" \
  > "$PWD/ats-effective.json" 2>/dev/null || printf '{}
' > "$PWD/ats-effective.json"

게이트에서는 “ATS 딕셔너리가 없음”과 “빈 딕셔너리가 존재함”을 오류가 아닌 정상 상태로 처리해야 합니다. 실제로 실패시켜야 하는 경우는 광범위한 연결 허용이나 승인되지 않은 예외가 있을 때입니다. 다음 스크립트는 허용 목록을 환경 변수로 전달해 팀 내부 도메인이 공개 스크립트에 하드코딩되지 않도록 합니다.

import json
import os
import sys

with open(sys.argv[1], encoding="utf-8") as f:
    ats = json.load(f)

if ats.get("NSAllowsArbitraryLoads") is True:
    raise SystemExit("NSAllowsArbitraryLoads is forbidden")

approved = {
    item.strip().lower()
    for item in os.getenv("ATS_APPROVED_DOMAINS", "").split(",")
    if item.strip()
}
exceptions = ats.get("NSExceptionDomains", {})

unknown = sorted(set(map(str.lower, exceptions)) - approved)
if unknown:
    raise SystemExit("Unapproved ATS domains: " + ", ".join(unknown))

실행할 때는 python3 ci/audit_ats.py ats-effective.json을 사용합니다. CI 로그에는 키 이름과 검사 결과를 남길 수 있지만, 요청 토큰이나 Cookie, 전체 인증 헤더를 출력해서는 안 됩니다.

검토 가능한 예외 목록 만들기

도메인만 비교하는 것으로는 충분하지 않습니다. 각 예외에는 허용할 ATS 키, 적용 환경, 사유, 재검토 조건을 기록해야 합니다. 특히 NSIncludesSubdomains에 주의해야 합니다. 이 키는 적용 범위를 모든 하위 도메인으로 확장하므로, 현재 사용하는 API가 하나뿐이라는 이유로 기본 활성화해서는 안 됩니다.

목록은 저장소 내부의 JSON 또는 YAML로 관리하고, 검토 시 다음 항목을 확인하는 것이 좋습니다.

  • 도메인은 정확히 지정해야 하며 와일드카드 형태의 설명은 허용하지 않습니다.
  • 프로젝트 기준에 미달하는 TLS 버전으로 낮추는 설정을 금지합니다.
  • 단일 리디렉션을 처리하려고 전체 상위 도메인을 허용해서는 안 됩니다.
  • 디버깅용 엔드포인트는 Debug 구성에만 포함해야 합니다.
  • 예외를 제거한 후에는 빌드 및 요청 테스트를 다시 실행해야 합니다.

구성 간 설정 유출 확인하기

Debug와 Release를 각각 빌드한 다음, 두 개의 ats-effective.json을 내보내 차이를 비교합니다. 패킷 캡처나 로컬 서비스에만 필요한 키가 Release에 나타나면 즉시 실패 처리해야 합니다. 동일한 plist라도 빌드 설정에 따라 서로 다른 결과가 주입될 수 있으므로 소스 파일만 비교해서는 안 됩니다.

TLS와 리디렉션 체인 점검하기

정적 감사가 통과되면 빌드를 수행하는 클라우드 Mac에서 대상 주소를 점검합니다. nscurl은 ATS 진단 매트릭스를 출력하므로 프로토콜, 인증서 체인, 순방향 비밀성 문제를 파악하는 데 적합합니다.

test -n "${API_URL:-}"
/usr/bin/nscurl --ats-diagnostics "$API_URL" \
  > "$PWD/ats-diagnostics.txt" 2>&1

이 출력은 진단에는 유용하지만, 단순히 “파일에 PASS가 있음”을 게이트 통과 조건으로 삼기에는 적합하지 않습니다. 진단 모드는 여러 가지 완화 조합을 시도하기 때문입니다. CI에서는 프로젝트가 실제로 사용하는 URL로 제어된 요청을 한 번 전송하고, 제한 시간과 리디렉션 횟수, 응답 코드를 제한해 명확하게 판정해야 합니다.

curl --fail --silent --show-error \
  --proto '=https' \
  --tlsv1.2 \
  --max-time 15 \
  --max-redirs 3 \
  --output /dev/null \
  "$API_URL"

curl 성공은 호스트 측 연결 경로를 사용할 수 있다는 의미일 뿐입니다. iOS App의 ATS 구성을 적용하지 않으며, App의 세션 프록시, 요청 헤더 또는 인증 로직이 올바르다는 사실도 입증하지 못합니다.

애플리케이션 계층 회귀 테스트 추가하기

마지막으로 프로덕션 네트워크 스택을 사용하는 경량 테스트 타깃을 추가합니다. 테스트 환경에서 주입한 URL을 읽고 URLSession으로 상태 확인 요청을 보낸 다음, 요청이 완료되고 상태 코드가 약속된 조건을 충족하며 HTTPS가 아닌 주소로 리디렉션되지 않았는지 검증해야 합니다. 테스트 코드에 고정 자격 증명을 작성해서는 안 됩니다.

충분하되 과도하지 않은 증거 보존하기

실패 시에는 최종 ATS 딕셔너리, 도메인 목록 비교 결과, nscurl 출력, 요청 상태 코드, 리디렉션 대상의 호스트 이름, Xcode 빌드 구성 이름만 보관하면 됩니다. 인증서 전문, 액세스 토큰, 전체 응답 본문은 일반적으로 장기 로그에 남길 필요가 없습니다.

실행 순서는 “Release 빌드 → 최종 plist 감사 → TLS 점검 → URLSession 테스트”로 고정합니다. 이렇게 하면 구성 드리프트를 발견할 수 있을 뿐 아니라, App 정책이 연결을 거부한 것인지 대상 엔드포인트의 인증서 체인, DNS 또는 리디렉션이 변경된 것인지도 구분할 수 있습니다.

출시 전 체크리스트

병합하기 전에 배포 산출물에 임의 네트워크 허용 설정이 없는지, 모든 도메인 예외가 승인 목록에 포함되어 있는지, 하위 도메인 적용 범위가 명확히 검토되었는지 확인해야 합니다. TLS 점검에는 실제 대상 주소를 사용하고, 애플리케이션 계층 테스트는 실제 URLSession 구성을 통해 실행해야 합니다. 실패가 발생하면 먼저 산출물과 진단 파일을 보존한 후 구성을 수정해야 하며, 인증서나 리디렉션 문제를 새로운 예외로 덮어서는 안 됩니다.

이 게이트의 목적은 모든 네트워크 장애를 자동으로 없애는 것이 아니라, 장애를 처리 가능한 계층으로 구분하고 임시 디버깅 설정이 다음 배포에 조용히 포함되지 않도록 보장하는 것입니다.

자주 묻는 질문

소스의 Info.plist만 검사하면 안 되는 이유는 무엇인가요?

빌드 설정과 스크립트가 최종 값을 바꿀 수 있으므로 실제 App 번들에 포함된 Info.plist를 기준으로 검사해야 합니다.

nscurl 진단이 성공하면 App 연결도 보장되나요?

아닙니다. ATS 정책, 리디렉션, 인증 경로를 포함하는 URLSession 요청 테스트를 별도로 실행해야 합니다.

어떤 ATS 설정에서 CI를 실패시켜야 하나요?

NSAllowsArbitraryLoads, 승인 목록에 없는 도메인 예외, 배포 빌드에 포함된 디버그 전용 허용 설정을 차단해야 합니다.

독점 물리 장비

클라우드 Mac mini로 다음 개발 또는 빌드 작업 실행하기

두 가지 M4 구성, 네 가지 대여 기간, 현재 판매 중인 다섯 개 노드 중에서 선택하세요. 실제 사용 가능 여부는 콘솔의 실시간 응답을 기준으로 합니다.

구성 선택 후 대여하기