MediaMTX 인증 설정에서 반대 방향의 오류 두 개를 만났습니다. 기본 인증값을 명시하면 정상 조합이 거부됐고, 환경변수로 사용자를 바꾸면 거부해야 할 충돌을 통과한 뒤 지정값이 버려졌습니다. 기본값을 정확히 구별해야 하는 비교가 양쪽에서 틀리고 있었습니다. 원인은 값의 표현과 메모리 공유에 하나씩 있었습니다.
저는 PR #6205에서 기본값의 표현을 정규화하고, 설정마다 중첩 기본값을 깊게 복사하는 변경을 함께 택했습니다. 하나만 고치면 다른 오류가 남고, 공유 기본값 때문에 앞선 로딩이 뒤의 테스트까지 통과시킬 수 있었기 때문입니다. 조사·구현·실측·리뷰는 AI와 협업했고 저는 두 변경의 범위와 허용·거부 조건을 확인했습니다. PR은 2026년 9월 20일 병합돼 v1.21.1 릴리스에 포함됐습니다.
의미가 같은 값이 서로 다른 표현을 가졌다
MediaMTX는 기본 authInternalUsers와 이전 버전의 인증 필드를 함께 처리할 때, 사용자가 내부 인증 설정을 실제로 바꿨는지 비교합니다. 기본값과 같다면 이전 필드에서 유도한 설정을 적용할 수 있지만, 사용자가 다른 인증값을 지정했다면 충돌을 거부해야 합니다.
문제는 IP 제한이 없는 표현이 한쪽에서는 nil 슬라이스이고 다른 쪽에서는 빈 슬라이스였다는 것입니다. 로드된 설정의 IPs는 ips: []와 정규화 과정을 거쳐 빈 값이 되지만, 패키지 수준 기본값에는 nil이 남았습니다. reflect.DeepEqual은 두 표현을 다르게 보므로 의미상 기본값인 설정까지 “변경된 인증 설정”으로 판정됐습니다.
그래서 제공되는 기본 설정 파일에 이전 인증 필드를 추가하면 서버 시작이 거부될 수 있었습니다. API로 이전 인증값을 추가할 때도 같은 비교에 걸렸습니다. 입력 경로의 차이보다 비교 기준의 내부 표현을 먼저 맞춰야 했습니다.
기본값이라는 이름이 불변성을 보장하지 않았다
더 큰 문제는 기본 사용자 슬라이스를 새 설정에 그대로 대입했다는 점입니다. 슬라이스 헤더가 복사돼도 중첩된 데이터의 저장 공간까지 독립적으로 복제되지는 않습니다. 환경변수가 로드된 사용자 항목을 바꾸면 전역 기본값도 함께 변할 수 있었습니다.
비교 기준까지 같은 값으로 바뀌면 “기본값과 다르지 않다”는 결과가 나옵니다. 실제로 환경변수로 지정한 사용자와 이전 인증 필드가 충돌하는데도 거부되지 않고, 지정한 사용자 값이 버려지는 경로가 생겼습니다. 또한 reload의 로딩과 인증 읽기가 같은 중첩 데이터를 공유하면서 -race가 보고하는 경쟁도 확인됐습니다.
공개 PR의 -race 재현은 같은 값을 다시 쓰는 경로에서도 공유된 메모리에 읽기와 쓰기가 겹친다는 것을 보여줬습니다. 이는 설정 소유권을 분리할 근거입니다. 그 시험에서 인증 우회나 잘못된 접근 허용까지 확인된 것은 아닙니다.
두 수정 중 하나만 선택할 수 없었던 이유
첫 번째 변경은 기본 IPs를 로딩 결과와 같은 빈 슬라이스 표현으로 정규화하는 것입니다. 두 번째는 기존 deepClone 도구를 사용해 기본값을 대입할 때 내부 값까지 복제하는 것입니다.
| 일부만 고칠 경우 | 남는 문제 |
|---|---|
| 깊은 복사만 적용 | nil과 빈 슬라이스의 비교 차이가 남아 정상적인 이전 인증 설정을 거부 |
| 정규화만 적용 | 중첩 기본값 공유가 남아 환경변수 수정과 앞선 설정 로딩이 비교 기준을 오염 |
| 두 변경 함께 적용 | 비교 표현을 맞추고 설정 인스턴스의 소유권을 분리 |
공유 상태 때문에 정규화 테스트의 초록색도 그대로 믿기 어려웠습니다. 같은 프로세스에서 앞선 설정 로딩이 전역 기본값까지 정규화하면, 뒤의 테스트는 수정이 없어도 맞는 표현을 만나 통과할 수 있습니다. 저는 값의 비교를 검증하는 테스트와 로드된 사용자를 바꿔도 전역 기본값이 남는지 확인하는 테스트를 나눴습니다. 두 원인을 제거했는지 각각 확인하기 위해서입니다.
기존에 Conf.Clone()과 Path.Clone()이 쓰던 도구를 재사용한 것도 범위를 좁힌 선택입니다. 이 문제만을 위해 별도의 복제 규칙을 추가하기보다, 중첩값 복제를 이미 담당하는 방식에 맞췄습니다.
허용해야 할 조합과 거부해야 할 조합을 함께 검증했다
회귀 테스트는 원인을 따라 네 가지로 나눴습니다. “기동이 된다”는 한 결과만 확인하면 반대쪽의 충돌 거부가 느슨해졌는지 놓칠 수 있습니다.
| 회귀 테스트 | 확인한 경계 |
|---|---|
| 기본값 정규화 | 로딩된 값과 비교 기준의 표현 일치 |
| 명시적 기본값 + deprecated auth | 기본값을 명시해도 정상 조합 허용 |
| 기본값 비공유 | 로드된 사용자를 바꿔도 전역 기본값 유지 |
| 환경변수 사용자 + deprecated auth | 사용자 지정값과 이전 필드의 충돌은 거부 |
이 변경 후에는 기본 인증값을 명시한 정상 설정이 다시 동작합니다. 반대로 환경변수로 내부 인증 사용자를 바꾸면서 이전 인증 필드를 함께 쓰는 설정은 이제 거부됩니다. 이전에는 시작되던 입력이 실패할 수 있다는 호환성 변화이며, 그 입력에서 사용자 지정값이 조용히 버려지던 문제를 명시적인 오류로 바꾼 것입니다.
비교하기 전에 소유권을 확인해야 했다
v1.21.1에 반영된 동작은 두 증상에 각각 답합니다. 기본값을 명시한 정상 조합은 허용하고, 사용자가 지정한 인증값이 이전 필드와 충돌하면 거부합니다. 로드된 설정을 바꿔도 비교 기준인 전역 기본값은 함께 바뀌지 않습니다.
이를 위해 비교 함수보다 먼저 확인할 것이 있었습니다. 같은 의미를 같은 형태로 표현하는지, 비교할 두 값이 실제로 독립된 소유자인지입니다. 어느 한쪽만 맞추면 정상 입력과 충돌 입력을 함께 구별할 수 없었고, 공유 상태가 남으면 테스트 순서까지 결과를 바꿀 수 있었습니다. 정규화와 복제를 한 수정으로 묶은 이유입니다.
이 글은 PokeClip·MediaMTX 기술 기록 모음의 일부입니다. 포트폴리오 전체본 39쪽에서 연결된 사례도 함께 볼 수 있습니다.