공유 서브도메인, 두 개의 입구: 누가 어떤 기록을 보는가
서브도메인은 브랜딩을 선택하고, 검증된 멤버십은 데이터를 선택한다. 요청이 사무실 주소에서 오든 VPN을 통해 오든 동일한 테넌트 검사를 실행한다.
클리닉 A와 클리닉 B는 하나의 코드베이스, 두 개의 서브도메인, 그리고 사무실과 VPN을 통해 오는 방문자들이 섞인 트래픽을 공유한다. 네트워크 팀은 보통 두 입구를 따로 설정하는데, 애플리케이션이 그렇게 하지 않는 한 괜찮다. 아래 검사는 애플리케이션에 속하며 요청이 어느 문을 사용했든 동일하게 동작해야 한다.
첫째: 호스트 이름은 테넌트를 지정할 뿐이다
요청된 호스트 이름을 관리자가 유지하는 매핑에 대조해 해석하고, 알 수 없는 값은 모두 거부한다. 원시 헤더 대신 프레임워크가 검증한 호스트 값을 사용해, 조작된 Host가 조회 키를 주입하지 못하게 한다. 일치하면 코드는 어떤 테넌트 구성과 브랜딩을 로드할지 알게 된다. 하지만 호출자가 그 테넌트의 데이터를 볼 수 있는지는 말해 주지 않는다. 그 두 번째 질문은 별도의 입력을 가진 별도 함수에 둔다.
호스트 이름 조회 한 번으로 컨텍스트와 권한을 함께 반환하는 공용 헬퍼 함수는 버그의 시작이다.
둘째: 멤버십은 세션에서 온다
인증된 사용자의 활성 멤버십을 조회하고 첫 번째 검사에서 나온 테넌트와 비교한다. 두 클리닉에 모두 속한 사용자는 전환할 수 있지만, 그 전환은 URL에서 추론한 것이 아니라 요청 컨텍스트에 기록된 명시적 행동이어야 한다. 한 클리닉에 속한 사용자가 다른 클리닉을 요청하면 거부하며, 다운로드와 API 호출도 포함한다.
기록 식별자와 테넌트 매개변수를 편집해 부정 사례를 시험한다. 둘 중 하나라도 변경했을 때 데이터가 반환되면, 범위를 검증된 멤버십이 아니라 요청에서 읽고 있는 것이다.
쿠키 범위와 싱글 사인온은 계정을 어디에서 제시할 수 있는지 결정한다. 그 계정이 무엇을 읽을 수 있는지는 결정하지 않는다.
셋째: 네트워크는 사람에 대해 아무것도 증명하지 않는다
VPN 게이트웨이는 터널을 인증하고, 공개 허용 목록은 주소 범위를 인증한다. 둘 다 사람을 식별하지 않는다. NAT는 많은 사용자를 하나의 출발지 주소로 모으고, 사무실 범위도 건물의 모든 사람에게 같은 일을 한다. 둘 다 전송 사실로 취급하고, 권한 부여는 애플리케이션 안에, 또는 신원을 명시적으로 주장하며 그에 맞게 검토되는 통합 안에 둔다.
DNS는 세 번째 전송 사실이다. 사설 응답에는 사설 경로가 필요하고, 공개 프런트엔드에는 지원되는 오리진 경로가 필요하다. 어느 것도 권한을 담지 않는다.
벽에 붙일 한 문장: 네트워크는 요청이 어떻게 도착하는지를 결정하고, 애플리케이션은 무엇을 읽을 수 있는지를 결정한다.
넷째: 두 입구에서 매트릭스를 실행한다
통제된 계정으로 A를 A에서, B를 B에서, A를 B에서, 알 수 없는 호스트 이름, 그리고 멤버십이 없는 인증된 계정을 시험한다. 네 번째와 다섯 번째는 닫힌 상태로 실패해야 하며 절대 기본 테넌트로 떨어져서는 안 된다. 같은 매트릭스를 VPN 입구로도 실행하고, 계정마다 세션 병을 하나씩 둔다:
curl -s -o /dev/null -w '%{http_code}' -b jar.txt https://clinic-b.example.com/records/42
curl -s -o /dev/null -w '%{http_code}' -b jar.txt https://clinic-a.example.com/records/42
그다음 허용 목록 항목 하나를 바꾸고 다시 실행한다. 결과가 움직이면 네트워크 제어가 권한 부여 결정을 내리고 있다는 뜻이다.
프런트엔드를 바꿀 때마다 매트릭스를 반복하는 것은 검사를 건너뛰는 두 번째 코드 경로가 이 경계가 침식되는 가장 흔한 방식이기 때문이다. 기대되는 결과는 지루하다: 모든 입구가 같은 답을 낸다.