사내에서 Grafana, Jenkins, Portainer 같은 관리 도구를 띄워놓으면 편한데, 이걸 그냥 열어두면 불안하다. VPN을 구축하면 가장 좋겠지만 규모가 작은 팀이라 그건 좀 오버스펙이었다. 그래서 Nginx 리버스 프록시 앞에 Basic Auth를 걸어서 최소한의 보호막을 만들었다.
htpasswd 파일 생성
Basic Auth에 사용할 사용자/비밀번호 파일을 먼저 만든다. apache2-utils 패키지에 htpasswd 명령이 들어있다:
sudo apt install apache2-utils
# 파일 새로 생성 + 사용자 추가
sudo htpasswd -c /etc/nginx/.htpasswd admin
비밀번호를 물어보니까 입력하면 된다. 사용자를 추가할 때는 -c 옵션을 빼야 한다 (파일을 덮어쓰니까):
sudo htpasswd /etc/nginx/.htpasswd devuser
생성된 파일을 보면 이런 식이다:
admin:$apr1$xyz...암호화된비밀번호
devuser:$apr1$abc...암호화된비밀번호
Nginx 설정
리버스 프록시 + Basic Auth 조합의 Nginx 설정 파일이다. 예를 들어 내부에서 3000번 포트로 돌아가는 Grafana에 인증을 거는 경우:
server {
listen 80;
server_name grafana.example.com;
location / {
auth_basic "Restricted Access";
auth_basic_user_file /etc/nginx/.htpasswd;
proxy_pass http://localhost:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
auth_basic에 넣는 문자열은 브라우저 로그인 팝업에 표시되는 메시지다. 설정 후 문법 체크하고 리로드:
sudo nginx -t
sudo systemctl reload nginx
특정 경로만 인증 걸기
전체가 아니라 관리자 페이지만 인증을 걸고 싶을 때도 있다:
server {
listen 80;
server_name app.example.com;
# 일반 페이지는 인증 없이
location / {
proxy_pass http://localhost:8080;
proxy_set_header Host $host;
}
# 관리자 페이지만 인증
location /admin {
auth_basic "Admin Only";
auth_basic_user_file /etc/nginx/.htpasswd;
proxy_pass http://localhost:8080/admin;
proxy_set_header Host $host;
}
}
특정 IP는 인증 면제
사무실 IP에서 접속할 때는 인증을 건너뛰고 싶다면 satisfy 디렉티브를 쓴다:
location / {
satisfy any;
# 사무실 IP는 허용
allow 192.168.1.0/24;
deny all;
# 그 외는 Basic Auth
auth_basic "Restricted";
auth_basic_user_file /etc/nginx/.htpasswd;
proxy_pass http://localhost:3000;
}
satisfy any는 IP 허용 OR Basic Auth 중 하나만 통과하면 된다는 뜻이다. satisfy all로 바꾸면 둘 다 통과해야 한다.
Docker 환경에서 설정
Docker Compose로 Nginx를 운영한다면 조금 다르다. htpasswd 파일을 볼륨으로 마운트해야 한다:
services:
nginx:
image: nginx:alpine
ports:
- "80:80"
volumes:
- ./nginx/conf.d:/etc/nginx/conf.d
- ./nginx/.htpasswd:/etc/nginx/.htpasswd:ro
restart: unless-stopped
grafana:
image: grafana/grafana
environment:
- GF_SECURITY_ADMIN_PASSWORD=your-password
restart: unless-stopped
proxy_pass의 호스트명은 Docker 서비스 이름을 쓴다:
proxy_pass http://grafana:3000;
Docker 내부 네트워크에서는 서비스 이름으로 DNS가 자동 해결되니까 IP를 하드코딩할 필요가 없다.
WebSocket 지원
Grafana나 일부 앱은 WebSocket을 쓰는데, 리버스 프록시에서 별도 설정이 필요하다:
location / {
auth_basic "Restricted";
auth_basic_user_file /etc/nginx/.htpasswd;
proxy_pass http://localhost:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
}
Upgrade와 Connection 헤더를 넘겨줘야 WebSocket 핸드셰이크가 정상적으로 이루어진다. 이거 안 해서 실시간 대시보드가 안 되는 경우가 꽤 많다.
주의사항
Basic Auth는 Base64 인코딩일 뿐 암호화가 아니다. 반드시 HTTPS와 함께 사용해야 한다. HTTP로 Basic Auth를 쓰면 네트워크에서 비밀번호가 그대로 노출된다. Let’s Encrypt로 무료 SSL 인증서를 붙이는 건 어렵지 않으니 꼭 같이 적용하자.
완벽한 보안 솔루션은 아니지만, 간단한 내부 서비스 보호 용도로는 충분하다. 설정도 간단하고 추가 소프트웨어 없이 Nginx만으로 해결되니까.