Skip to content

fix: 내부 전용 API 의 외부 노출 차단을 nginx 설정 코드에 반영 #76

Description

@Hexeong

어떤 작업인가요?

내부 전용 API(/internal/**)를 외부 인터넷에서 호출할 수 없도록 하는 nginx 차단 규칙을, 운영 인스턴스에는 이미 수동 적용했고 코드에는 아직 반영되지 않은 상태입니다. 인스턴스가 재생성되면 유실되므로 modules/app_stack/scripts/nginx_setup.sh.tftpl 에 반영합니다.

배경

백업 실패 알림 API(POST /internal/alarms/db-backup)가 v2.6.1 로 prod 에 배포되면서, 이 경로가 외부 인터넷에 노출되었습니다.

서버의 SecurityConfiguration/internal/**permitAll() 로 두고 공유 토큰(internal-alarm.token)으로만 인증합니다. 즉 nginx 차단이 없으면 토큰 하나가 유일한 방어선입니다.

적용 전 외부에서 확인한 결과입니다.

POST https://api.solid-connection.com/internal/alarms/db-backup
→ 401 {"message":"요청을 인증할 수 없습니다."}

401 은 요청이 nginx 를 통과해 애플리케이션까지 도달했다는 뜻입니다.

DB EC2 는 API 서버의 app 포트(8080/9080)로 직접 요청하므로 nginx 를 거치지 않습니다. 따라서 외부 443 경로만 막으면 알림 기능에는 영향이 없습니다.

운영 인스턴스 반영 현황 (완료)

API EC2(i-016e57974be976a59)의 /etc/nginx/sites-available/solid-connection-server 에 아래 블록을 추가하고 nginx -t 통과 후 graceful reload 했습니다.

    # 3차 차단: 내부 전용 API 는 VPC 내부에서 app 포트로 직접 호출하므로 외부 노출을 막는다
    # DB EC2 의 백업 실패 알림은 8080/9080 직접 경로를 쓰므로 이 차단에 영향받지 않는다
    location ^~ /internal {
        return 444;
    }

443 server 블록(server_name api.solid-connection.com) 안, dotfile 차단 블록과 location / 사이에 위치합니다.

설계 결정

항목 결정 이유
응답 return 444 기존 차단(IP 직접 접근, 확장자 스캔, dotfile)과 동일한 컨벤션. deny all(403)은 "경로는 있지만 금지" 를 알려주는데, 444 는 응답 없이 연결을 끊어 경로 존재 자체를 숨김
접두사 /internal (trailing slash 없음) ^~ /internal/ 로 두면 /internal 이 매칭되지 않아 앱까지 도달함. 서버의 /internal/** 범위와 맞춤
위치 location / ^~ 는 prefix priority match 라 정규식 location 보다 먼저 평가됨

검증 결과

외부 인터넷에서 차단됨 (000 = 응답 없이 연결 종료):

GET  /internal                     → 000
GET  /internal/                    → 000
GET  /internal/alarms/db-backup    → 000
GET  /internal/anything            → 000
POST /internal/alarms/db-backup    → 000

일반 경로는 그대로 앱으로 전달됨 (500 은 앱이 반환한 응답):

GET /                  → 500
GET /universities      → 500
GET /actuator/health   → 500

DB EC2 에서 직접 호출하는 알림 경로는 영향 없음:

health 9081  → {"status":"UP"}
알림  9080   → 401

되돌릴 필요가 있으면 /root/nginx-sc-before-internal-block.bak 에 적용 전 파일이 있습니다.

작업 내용

  • modules/app_stack/scripts/nginx_setup.sh.tftpl 의 443 server 블록에 location ^~ /internal { return 444; } 를 추가합니다.
  • dotfile 차단 블록과 location / 사이에 두고, 기존 차단 주석 스타일(1차/2차 차단)에 맞춰 설명을 남깁니다.
  • stage 환경도 같은 모듈을 사용하므로 함께 적용되는지 확인합니다.
  • terraform plan 에서 인스턴스 교체가 발생하지 않는지 확인합니다.

주의: 코드 반영만으로는 운영에 적용되지 않습니다

aws_instance.api_serverlifecycle.ignore_changesuser_datauser_data_base64 가 있고, cloud-init 의 scripts-user 는 once-per-instance 로 동작합니다. 따라서 nginx_setup.sh.tftpl 을 수정해도 plan 에 아무 변화가 나타나지 않고 실행 중인 인스턴스에도 반영되지 않습니다.

이 이슈의 목적은 인스턴스가 재생성되는 시점에 설정이 유지되도록 하는 것입니다. 운영 반영은 위와 같이 이미 수동으로 완료했습니다.

같은 성격의 괴리가 다른 곳에도 있습니다.

  • 코드의 db_ec2_ami_id(ami-0501a03cd31b53e82)와 실제 인스턴스 AMI(ami-0d45c1f91adea31b8)
  • mysql_tuning.cnf (변경해도 반영되지 않음, binlog 설정이 MySQL 기본값에 의존)

런타임 설정을 배포 경로로 옮기는 작업은 별도 주제이며, 현재 튜닝 수요가 없어 보류한 상태입니다.

완료 조건

  • nginx_setup.sh.tftpl 에 차단 블록이 포함되어 있습니다.
  • 새 인스턴스를 기동했을 때 /internal 접근이 외부에서 차단됩니다.
  • DB EC2 의 백업 실패 알림이 정상 동작합니다.

Refs #66

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions