설계 관련 고민을 하면서 재밌는 글을 찾았다. 바로 [ 서비스 엣지에서의 인증과 토큰에 구애 받지 않는 id 전파 ] 라는 주제의 넷플릭스 기술 블로그 글이다. 2021년 글이지만 관련 레퍼런스가 많이 없어서 아직 유용한 포스트 같다.
Edge Authentication and Token-Agnostic Identity Propagation
이 포스트는
정도에게 추천하고 싶다.
직접 번역(기 돌리는 거)하면서 포스트 내용을 정리한 내용을 나의 견해 조금씩과 함께 공유하고자 한다.
개발자들은 보안 프로토콜, 신원 토큰, 사용자 및 장치 인증과 같은 문제를 처리하는 것이 어려울 수 있습니다. Netflix는 200M+의 사용자와 수천 종류의 장치를 가지고 있어 이러한 문제의 범위가 훨씬 더 커집니다. 이러한 복잡성을 줄이기 위해 Netflix는 네트워크의 가장자리에 사용자 및 장치 인증 및 다양한 보안 프로토콜 및 토큰의 복잡한 처리를 이동하기로 결정했습니다.
초기 Netflix는 DVD 대여를 위한 웹사이트였습니다. 시간이 지나며 스트리밍 콘텐츠 기능이 추가되었고, 그 후 다양한 장치로 확장되었습니다. 이러한 변화로 인해 서비스는 사용자와 장치를 인식하고 접근 권한을 부여하기 위해 여러 토큰과 보안 프로토콜을 이해해야하는 부담을 지게 되었습니다.
예전의 로그인 플로우는 여러 단계를 거쳐야 했으며, 이는 토큰 관리의 복잡성을 높였습니다. 토큰은 여러 시스템에 의해 소비되거나 변경될 수 있었습니다. Netflix의 규모는 지속적으로 확장되었고, 많은 요청들이 인증을 필요로 했습니다.
한 종류의 토큰 뿐만 아니라 다양한 프로토콜의 요청을 위해 토큰의 종류도 다양하여 이에 대응하기에 많은 부담이 있었습니다.
Edge 엔지니어링 팀은 오래된 API 서버 아키텍처에서 새로운 PaaS 기반 접근법으로의 마이그레이션 중이었습니다. 이로 인해 복잡성이 더해졌습니다. 새로운 PaaS 모델에서는 어떻게 이러한 신원 토큰을 다룰 것인가의 문제가 제기되었습니다.
그래서 Netflix는 대규모에서 인증 및 신원 토큰을 처리하기 위한 복잡하고 비효율적인 솔루션을 가지고 있었습니다. 여러 종류의 신원 토큰, 각각의 특별한 처리가 필요하며, 이러한 로직은 다양한 시스템에서 복제되었습니다. 중요한 신원 데이터는 서버 시스템 전체에서 일관되지 않게 전파되었습니다.
→ 간단하게 요약하자면 로그인이 복잡했고, 토큰 종류가 많아서 여러 서비스에서 부담이 커져서 해결책이 필요했다는 내용이다.
Netflix는 인증 문제를 해결하기 위한 통합된 신원 모델이 필요하다고 판단했습니다. 네트워크의 가장자리로 인증 및 프로토콜 종료를 옮김으로써 이를 실현하였고, 서버 시스템 전체에 전파하기 위한 새로운 무결성이 보호된 토큰에 무관한 신원 객체를 생성하였습니다.
보안 향상, 복잡성 감소 및 사용자 경험 개선을 목표로, 장치 인증 작업 및 사용자 식별과 인증 토큰 관리를 서비스 가장자리에 중앙집중화하는 방법을 고려하였습니다. 이를 위해 Zuul (클라우드 게이트웨이)이 토큰 검사 및 페이로드 암/복호화의 종료 지점이 되도록 설계하였습니다.
Edge 인증 서비스(EAS)는 장치 및 사용자의 인증 및 식별을 스택의 상위로 이동하는 구조적 개념이자 각 토큰 유형을 처리하기 위해 개발된 서비스 세트입니다.
EAS는 Zuul 내에서 실행되는 일련의 필터로서, 그들의 도메인을 지원하기 위해 외부 서비스에 요청할 수 있습니다. EAS는 토큰의 읽기 전용 처리를 통해 "Passports"를 생성하는 것도 포함합니다.
EAS가 요청을 처리하는 기본 패턴은 아래와 같다.
정상적인 경우에는 Zuul이 유효하고 만료되지 않은 대다수의 토큰을 처리할 수 있으며, Edge Auth 서비스는 나머지 요청을 처리합니다. EAS 서비스는 장애에 대한 내성을 갖추도록 설계되었습니다. 실패 시나리오에서는 Zuul 내의 EAS 필터가 신원을 전달하도록 허용하고 다음 요청에서 갱신 호출이 다시 예약되도록 지시합니다.
서비스에서 서비스로 신뢰도가 낮은 신원을 전달하는 것을 의미하는 쉽게 변경 가능한 신원 구조는 충분하지 않습니다. 토큰에 무관한 신원 구조가 필요했습니다.
우리는 "Passport"라는 신원 구조를 도입하여 사용자와 장치의 신원 정보를 균일한 방식으로 전파할 수 있게 했습니다. Passport는 또한 토큰의 일종이지만, 내부 구조가 외부 토큰과 다르기 때문에 많은 이점이 있습니다. 그러나 하위 시스템은 여전히 사용자와 장치의 신원에 대한 접근이 필요합니다.
Passport는 각 요청에 대해 Edge에서 생성된 단기 신원 구조입니다. 즉, 요청의 수명에 제한되며 Netflix 생태계 내에서 완전히 내부적입니다. 이러한 Passport는 Zuul에서 일련의 Identity Filters를 통해 생성됩니다. Passport는 사용자 및 장치의 신원을 모두 포함하며, protobuf 형식이며 HMAC에 의해 무결성이 보호됩니다.
간단히 말하면, Passport는 Netflix 시스템 내에서 일관된 방식으로 사용자와 장치의 신원을 전파하기 위한 내부 토큰입니다. 이는 보안을 강화하고 서비스 간의 통신을 단순화하는 데 도움을 줍니다.
Passport는 앞서 언급했듯이 Protocol Buffer로 모델링되어 있습니다. 가장 상위 수준에서 Passport의 정의는 다음과 같습니다:
message Passport {
Header header = 1;
UserInfo user_info = 2;
DeviceInfo device_info = 3;
Integrity user_integrity = 4;
Integrity device_integrity = 5;
}
여기서 Header
요소는 Passport를 생성한 서비스의 이름을 전달합니다. 더 흥미로운 것은 사용자와 장치에 관련된 정보입니다.
UserInfo
요소는 요청을 하는 사용자를 식별하기 위해 필요한 모든 정보를 포함하고 있으며, DeviceInfo
요소는 사용자가 Netflix를 방문하는 장치에 대한 모든 필요한 정보를 포함하고 있습니다.
message UserInfo {
Source source = 1;
int64 created = 2;
int64 expires = 3;
Int64Wrapper customer_id = 4;
...
PassportAuthenticationLevel authentication_level = 11;
repeated UserAction actions = 12;
}
message DeviceInfo {
Source source = 1;
int64 created = 2;
int64 expires = 3;
StringValue esn = 4;
Int32Value device_type = 5;
repeated DeviceAction actions = 7;
PassportAuthenticationLevel authentication_level = 8;
...
}
UserInfo
와 DeviceInfo
모두 요청에 대한 Source
와 PassportAuthenticationLevel
을 포함하고 있습니다. Source
목록은 사용되는 프로토콜과 주장을 검증하는데 사용되는 서비스와 관련된 주장의 분류입니다. PassportAuthenticationLevel
은 우리가 인증 주장에 대입하는 신뢰도의 수준입니다.
enum Source {
NONE = 0;
COOKIE = 1;
COOKIE_INSECURE = 2;
MSL = 3;
PARTNER_TOKEN = 4;
...
}
enum PassportAuthenticationLevel {
LOW = 1; // 신뢰할 수 없는 전송
HIGH = 2; // TLS 위의 보안 토큰
HIGHEST = 3; // MSL 또는 사용자 자격증명
}
하위 응용 프로그램은 이러한 값을 사용하여 권한 및/또는 사용자 경험 결정을 내릴 수 있습니다.
간단히 말하면, Passport는 사용자와 장치에 대한 식별 정보를 안전하게 전달하기 위한 구조로, Netflix 서비스 내부에서 사용됩니다. 각 요소와 열거형 값은 Netflix 서비스의 인증 및 권한 부여에 필요한 중요한 정보를 제공합니다.
Passport의 무결성은 HMAC (hash-based message authentication code)를 통해 보호됩니다. HMAC은 특정 유형의 MAC으로서 암호 해시 함수와 비밀 암호 키를 포함합니다. 이를 사용하여 메시지의 데이터 무결성 및 인증성을 동시에 검증할 수 있습니다.
사용자와 장치의 무결성은 다음과 같이 정의됩니다:
message Integrity {
int32 version = 1;
string key_name = 2;
bytes hmac = 3;
}
Integrity 요소의 버전 1은 HMAC에 SHA-256을 사용하며 ByteArray로 인코딩됩니다. Integrity의 미래 버전은 다른 해시 함수 또는 인코딩을 사용할 수 있습니다. 버전 1에서 HMAC 필드는 MacSpec.SHA_256에서의 256비트를 포함합니다.
무결성 보호는 Passport가 생성된 후 Passport 필드가 변경되지 않음을 보장합니다. 클라이언트 응용 프로그램은 포함된 모든 값의 사용 전에 Passport의 무결성을 검사하기 위해 Passport Introspector를 사용할 수 있습니다.
요약하면, Passport의 무결성은 암호화된 HMAC을 통해 보장되며, 이로 인해 Passport의 데이터가 생성된 후에 변경되지 않았음을 확인할 수 있습니다.
Passport 객체 자체는 불투명합니다(추상화 되어있다). 클라이언트는 Passport Introspector를 사용하여 헤더에서 Passport를 추출하고 그 안의 내용을 검색할 수 있습니다. Passport Introspector는 Passport 바이너리 데이터 위에 있는 래퍼입니다. 클라이언트는 팩토리를 통해 Introspector를 생성한 후 기본 접근자 메서드에 액세스 할 수 있습니다.
public interface PassportIntrospector {
Long getCustomerId();
Long getAccountOwnerId();
String getEsn();
Integer getDeviceTypeId();
String getPassportAsString();
...
}
위에 표시된 Passport 프로토콜 버퍼 정의에서는 Passport Actions가 정의되어 있습니다.
message UserInfo {
repeated UserAction actions = 12;
...
}
message DeviceInfo {
repeated DeviceAction actions = 7;
...
}
Passport Actions는 사용자 또는 장치 신원에 대한 업데이트가 수행되었을 때 하위 서비스에서 보낸 명시적 신호입니다. 이 신호는 EAS에서 해당 유형의 토큰을 생성하거나 업데이트하는 데 사용됩니다.
이 모든 솔루션들이 함께 작동하는 예를 들어 설명을 마무리하겠습니다.
인증 및 프로토콜 종료를 Edge로 이동하고 신원으로서의 Passport 도입으로 이전에 설명된 Login Flow는 다음과 같이 변형되었습니다:
요약하면, 이 새로운 로그인 플로우는 인증을 통합하고, 보다 안전하게 처리하며, Passport라는 새로운 신원 메커니즘을 도입함으로써 보다 간소화되었습니다.
(→ 아마 2년 전이기 때문에 더 많은 내용이 개선되었을 것이다)
외부 토큰과 내부 토큰을 분리하는 것이 가장 큰 컨셉인 것으로 보인다. 그래서 내부에서의 통신은 더 신뢰할 수 있게 되고, 통일된 형태이기 때문에 처리 비용이 줄고, edge에서 외부 토큰을 처리하기 때문에 각 서비스에서는 관련 로직을 대폭 줄일 수 있을 뿐만 아니라 User 저장소의 트래픽도 줄일 수 있다. 여러 설계 고민이 있는 내게 너무너무 필요한 개념이었다.
해당 글의 passport는 protobuf를 기반으로 한다고 하였다.
Practical API Design at Netflix, Part 1: Using Protobuf FieldMask
더 찾아보니 이 글도 재미있다. gRPC로 주로 마이크로 서비스들 간의 통신을 하는데, 그때 주로 사용되는 직렬화 프로토콜이 protobuf(Protocol Buffer)이다.
아직 http/https 프로토콜만 익숙해서 Spring Cloud OpenFeign으로만 마이크로 서비스들끼리 통신하는데, 만약 기존 프로젝트에 passport라는 개념을 적용하게 된다면 그 형태를 조금 커스텀 해야할 것 같다.
개쩐다