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

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions