TL;DR

  • 웹사이트를 수정하지 않고 Nginx 프록시 사이트를 보호하려면 HTTP 기본 인증(HTTP Basic Auth)으로 로그인한 뒤 로그인 쿠키를 설정하는 방식을 적용함.
  • ngx_http_auth_basic_module만 사용하면 일부 브라우저에서 페이지를 열 때마다 인증을 다시 요구하므로, 유효한 쿠키가 있으면 접근을 허용하고 없으면 로그인 경로로 리디렉션함.
  • login_cookies 파일은 사용자 이름과 무작위 문자열을 결합한 쿠키 값을 보관하며, .htpasswd 파일은 htpasswd 명령으로 생성함.
  • Nginx의 map 지시어로 쿠키의 사용자 이름과 예상 쿠키 값을 대조하고, 로그인 성공 후 원래 요청한 경로로 리디렉션함.
  • 내부 웹 서버에 SSH 전달 유닉스 소켓으로 연결하는 경우에도 각 보호 대상 위치에서 인증을 검사하고, X-Authenticated-User 헤더로 사용자 이름을 전달함.

문제

  • 웹사이트 자체를 수정하지 않고 Nginx로 프록시하는 사이트에 비밀번호를 설정하려는 상황임.
  • 개인용 단순 사이트이므로 일반 로그인 양식 대신 HTTP 인증을 사용할 때의 UI 문제는 감수할 수 있었음.
  • Nginx의 ngx_http_auth_basic_module로 HTTP 인증을 설정하는 과정은 간단했지만, 일부 브라우저에서는 페이지를 열 때마다 사용자 이름과 비밀번호를 다시 입력하라는 창이 나타남.
  • 비밀번호 관리자를 이용하면 추가 클릭만 하면 되지만, 여전히 좋지 않은 사용자 경험임.

해결 방법

  • 일반적인 로그인 과정처럼 로그인 쿠키를 설정해 반복 인증 요청을 피하는 여러 사례를 참고했으며, Nginx 설정에 적용하는 과정은 까다로웠고 기존 예제가 그대로 동작하지 않아 이를 바탕으로 다른 구성을 작성함.
  • 예제는 example.com 서버에서 https://example.com/private/ 하위 경로를 비밀번호로 보호함.
  • login_cookies 파일을 사용자 이름별 쿠키 값의 대응표로 불러옴. 각 값은 사용자 이름, | 문자, 무작위 문자열을 이어 붙인 형태임.
  • 쿠키에서 사용자 이름을 추출하고, 클라이언트가 보낸 쿠키와 해당 사용자의 예상 쿠키 값이 일치하는지 검사함.
  • 인증되지 않은 요청은 /private/login?redirect=...로 리디렉션됨. 로그인 경로에는 auth_basic과 .htpasswd를 설정하고, 인증이 성공하면 @login_success에서 쿠키를 설정한 뒤 원래 요청 경로로 돌려보냄.
  • .htpasswd 파일은 htpasswd 명령으로 만듦. 파일을 처음 생성할 때는 -c 옵션이 필요하며, 예제에서는 alice와 bob 계정을 추가함.
  • generate_cookies.sh 스크립트는 .htpasswd에서 사용자 이름을 읽고, 사용자별로 openssl rand -base64 33을 사용해 무작위 문자열을 생성해 login_cookies 내용을 만듦.
  • login_cookies와 .htpasswd에는 비밀 정보가 있으므로 웹 서버 사용자만 읽을 수 있게 설정하고, 웹 서버가 제공하는 경로 밖에 저장해야 함.

작동 방식

  • 이 구성은 Nginx map 지시어의 평가 시점에 의존함. map은 처음 사용될 때까지 평가되지 않으므로, 변수가 참조되는 시점을 조절해 계산에 쓰이는 입력을 제어함.
  • 로그인 쿠키가 있는 페이지 요청에서는 쿠키를 검증하고 요청을 허용함. 유효한 쿠키가 없으면 HTTP 기본 인증이 설정된 로그인 경로로 보내 브라우저의 인증 창을 띄움.
  • 로그인이 성공하면 쿠키를 설정하고 사용자를 원래 요청한 페이지로 돌려보냄.
  • 보호 경로에서 인증 여부를 확인할 때 쿠키에서 사용자 이름을 추출하고, 그 이름에 해당하는 예상 쿠키 값을 login_cookies에서 찾아 클라이언트 쿠키와 비교함. 두 값이 일치하면 인증 결과가 1이 되어 접근을 허용함.
  • 로그인 경로에서는 Nginx의 $remote_user 값으로 사용자 이름을 지정함. 이 값은 HTTP 인증 헤더의 사용자 이름이며, 이를 바탕으로 해당 사용자의 쿠키를 선택해 설정함.
  • $redirect_target은 요청 URL에서 redirect 매개변수의 경로를 추출함. 정규식 (?<redirect>/[^/].*)은 경로가 /로 시작하되 //로 시작하지 않도록 해 외부 사이트로 향하는 오픈 리디렉션을 막음.
  • 같은 도메인 안의 다른 경로로 리디렉션하는 것도 문제가 될 수 있다면, 유효한 URL만 허용하도록 정규식을 더 엄격하게 설정할 수 있음.

프록시 웹사이트

  • 앞선 예제는 정적 웹사이트를 대상으로 하지만, 실제 사용 사례는 프록시 웹사이트임. 내부 웹 서버에만 접근할 수 있던 사이트를 공개 인터넷에서 비밀번호 보호 상태로 이용하려는 구성임.
  • 내부 웹 서버를 가리키는 SSH 전달 유닉스 소켓을 계속 열어 두며, 예시 소켓 경로는 /path/to/proxied-http.sock임.
  • https://example.com/private/localapp/foo 요청은 SSH 프록시가 연결되는 localcomputer의 http://localcomputer/targetapp/foo로 프록시됨.
  • /private/localapp 위치에서도 사용자 인증 여부를 검사하고, 경로 끝의 슬래시를 정규화한 다음 /private/localapp/을 /targetapp/으로 다시 작성함.
  • 프록시 요청에는 X-Real-IP, X-Forwarded-For, X-Forwarded-Proto 헤더와 사용자 이름을 담은 비표준 X-Authenticated-User 헤더를 전달함. 프록시 버퍼링은 끄고 유닉스 소켓을 통해 요청을 전달함.
  • 인증 검사 조건은 각 위치 블록에 반복해서 넣어야 함.
  • 프록시 웹 서버에 직접 연결할 수 있는 클라이언트는 X-Authenticated-User 헤더에 임의의 값을 넣을 수 있음. 다만 해당 서버가 이 프록시를 통해서만 비밀번호 보호 상태로 제공되고 직접 접근이 제한돼 있다면 큰 문제가 아닐 수 있음.

로그인 쿠키 확인

  • HTTP 기본 인증을 사용하면 브라우저는 보통 사용자 이름과 비밀번호를 한 번 요청한 뒤 다시 묻지 않으며, 비밀번호가 거부된 경우에도 다시 묻지 않는 브라우저가 있음. 로그아웃 기능을 제공하지 않는 브라우저도 있어 로그인 쿠키만으로 인증됐는지 확인하기 어려움.
  • 쿠키만으로도 인증이 되는지 확인하기 위해, 비밀번호 확인 없이 고정 사용자 alice의 로그인 쿠키를 설정하는 /private/magiclogin 위치를 추가함.
  • 새 브라우저 세션에서 /private/magiclogin에 접속한 다음 /private로 이동했을 때 비밀번호 입력을 요구하지 않으면 쿠키 인증이 동작하는 것으로 확인할 수 있음.