OpenVPN의 LDAP 인증은 되는데 그룹 검사에서 실패한다면: 어디를 볼까
인증은 통과해도 그룹 검사가 모두를 거부할 수 있다. Authorization 블록, 멤버 속성, 디렉터리 엔드포인트를 차례로 확인한다.
자격 증명이 통과하고 터널이 올라오기 시작한 뒤 세션이 비밀번호가 아니라 그룹에 관한 메시지와 함께 실패한다. 이 조합은 실패를 LDAP 플러그인 작업의 후반부에 놓는다. 바인딩과 비밀번호 검사는 성공했고, 뒤따르는 멤버십 판단은 실패한 것이다. 두 절반은 같은 구성 블록에서 읽으므로 한쪽을 고쳐도 다른 쪽이 그대로 남을 수 있다. 어떤 단계인지 알려 주는 로그 줄에서 시작하고, 우연히 테스트 중인 계정에서 시작하지 않는다.
거부를 인가의 답으로 다루기
플러그인은 서비스 바인딩을 수행하고, 계정을 검색하고, 제공된 비밀번호를 검증한 다음 그 계정이 허용된 그룹에 속하는지 묻는다. 이는 서로 다른 네 가지 작업이며, 그룹에 관한 것은 마지막 하나뿐이다. 그래서 바인딩 성공 후 도착하는 거부는 클라이언트가 일반적인 인증 오류를 표시하더라도 비밀번호에 관해 아무것도 말하지 않는다. 구성을 바꾸기 전에 플러그인의 로그 수준을 올리는 것이 출력에서 그 네 가지를 갈라놓는다.
그룹 평가 자체를 찾는다. 그룹 쿼리가 전혀 나타나지 않으면 인가 블록에 도달하지 못한 것이고, 이는 보통 지시문이 그것을 소유한 블록 밖에 있거나 필수 옵션이 빠졌다는 뜻이다. 쿼리가 나타나는데 아무것도 반환하지 않으면 엔드포인트나 필터가 의도한 것과 다른 질문에 답하고 있다.
멤버십이 필터에 있는지 그룹 블록에 있는지 정하기
지원되는 형태는 두 가지이며, 이를 섞는 것이 작동하던 구성이 작동을 멈추는 길이다. 첫째는 사용자 객체의 속성을 요구해 멤버십을 사용자 검색으로 밀어 넣는다. 둘째는 사용자 검색을 그대로 두고 플러그인이 나중에 그룹 객체를 찾게 한다. 배포마다 하나를 고르고 어느 것을 골랐는지 적어 둔다.
<Authorization>
BaseDN "OU=People,DC=example,DC=org"
SearchFilter "(&(sAMAccountName=%u)(memberOf=CN=VPN Access,OU=Groups,DC=example,DC=org))"
RequireGroup false
</Authorization>그 형태에서는 필터가 모든 것을 정하므로 RequireGroup 은 false여야 하고, 그렇지 않으면 플러그인이 필터가 한 번도 언급하지 않은 그룹 객체를 찾으러 간다. 필터의 속성은 사용자 객체에 존재하고 채워져 있어야 하지만, 그룹이 중첩되면 그것이 보장되지 않는다.
다른 형태는 검색을 단순하게 유지하고 그 뒤에 그룹 조회를 추가한다. 그룹 객체를 명시적으로 지정하고, 플러그인은 사용자를 디렉터리가 그 그룹에 대해 반환하는 멤버와 비교한다. 사용자 필터는 아무것도 바뀌지 않으므로, 다른 팀이 그룹을 관리할 때 이 형태가 감사하기 더 쉽다.
<Authorization>
BaseDN "OU=People,DC=example,DC=org"
SearchFilter "(sAMAccountName=%u)"
RequireGroup true
Group "CN=VPN Access,OU=Groups,DC=example,DC=org"
</Authorization>맹점은 공통이다. 단순한 멤버십 비교는 중첩 그룹을 확장하지 않으므로, 상위 그룹을 통해 접근이 오는 계정은 실패하고 콘솔에는 여전히 멤버십이 표시된다. Active Directory는 그룹에 member 을 노출하고, 다른 스키마는 uniqueMember 를 사용하며, 잘못된 속성을 가리키면 오류 대신 빈 결과가 돌아온다.
그룹 객체를 담고 있는 엔드포인트 고르기
URL은 플러그인이 어느 디렉터리에 묻는지를 정하고, 389 포트의 도메인 컨트롤러는 자기 이름 지정 컨텍스트에만 답한다. 3268 포트의 글로벌 카탈로그는 전체 포리스트에 답하며, 읽기 전용이고 부분 속성 집합을 가진다. 계정이 한 도메인에 있고 VPN 그룹이 다른 도메인에 있으면, 단일 도메인 쿼리는 사용자를 찾고도 멤버십을 놓칠 수 있다. URL을 카탈로그 포트로 옮기면 거부가 해결되는 경우가 이 경우이고, 사람들이 지나치게 일반화하는 경우도 이 경우다.
부분 속성 집합이 한계인데, 카탈로그가 보유하지 않은 속성으로는 필터링할 수 없기 때문에 같은 변경이 한 배포에서는 작동하고 다른 배포에서는 아무것도 반환하지 않을 수 있다. 블록을 다시 쓰기 전에 실제 사용자와 그룹에 대해 엔드포인트가 실제로 무엇을 반환하는지 확인한다. 추천과 TLS는 다시 별개의 문제인데, 추천은 따라가야 하고, TLS를 요구하는 엔드포인트에는 그에 맞는 신뢰 파일이 필요하다.
계정 상태와 검색 범위를 각자의 필드에 두기
비활성 플래그는 필터에 속하고 그룹 검사에 속하지 않는다. Active Directory에서는 userAccountControl 의 두 번째 비트이며, 그 조건이 없으면 유효한 비밀번호를 가진 비활성 계정이 인증을 통과한 뒤 그룹 단계에서 실패하는데, 이는 인가 버그처럼 보인다. 검색 범위는 기본 DN에 속한다. 도메인 루트에서 시작하면 더 느리고, 대상으로 삼은 조직 단위 밖의 객체와 일치할 수 있다.
바인딩 계정의 권한도 좁게 유지한다. 넓은 디렉터리 권한은 잘못 구성된 필터를 성공한 쿼리로 바꾸고, 누군가 계정을 감사할 때까지 버그를 숨긴다. 멤버십은 디렉터리 자체에서 바꾸고, 절대 로컬 예외 목록에서 바꾸지 않아야 계정 비활성화만으로 접근을 제거할 수 있다. 다른 팀이 그룹을 관리한다면, 필터를 쓰기 전에 그들이 채우는 속성에 합의한다.
세 계정으로 결과를 증명하기
세 신원에 대해 구성을 실행한다. 허용된 멤버, 그룹 밖의 유효한 계정, 비활성화된 계정이다. 셋 다 거부되면 바인딩이나 그룹 쿼리가 깨진 것이고, 셋 다 수용되면 그룹 조건이 전혀 적용되지 않는 것이다. 혼합된 결과만이 구성이 중요한 경우를 구분한다는 증거가 된다.
플러그인을 탓하기 전에 같은 세 가지 답을 디렉터리에서 직접 읽는다. 첫 쿼리는 필터가 의존하는 그룹 속성을 보여 주고, 둘째는 계정 제어 값을 보여 준다. 자신의 기본 DN, 계정, 엔드포인트로 바꿔 넣는다:
ldapsearch -H ldap://dc01.example.org:3268 -x -D "[email protected]" -W -b "DC=example,DC=org" "(sAMAccountName=alice)" memberOf
ldapsearch -H ldap://dc01.example.org:3268 -x -D "[email protected]" -W -b "DC=example,DC=org" "(sAMAccountName=bob)" userAccountControlmemberOf 이 비어서 돌아오는 사용자는 디렉터리의 사실이지 플러그인 버그가 아니며, 비활성화된 뒤에도 인증되는 계정은 필터에 계정 제어 조건이 빠졌다는 뜻이다. 먼저 그 증거를 고치고, 그다음 세 계정 테스트를 다시 실행한다. 실패한 단계를 지목한 로그 줄을 구성 변경 옆에 남겨 두면, 다음 사람이 검색을 반복하지 않고 인가와 인증을 구분할 수 있다.