Discourse가 서버 IP/localhost를 사용자 IP로 표시

안녕하세요, 제 서버에 Discourse를 배포했는데, 사용자 IP 주소 감지를 제외하고는 모든 기능이 정상적으로 작동합니다. IP가 포함될 수 있는 모든 헤더를 가져오기 위해 Docker 컨테이너 외부에서 PHP 스크립트까지 작성했는데, 결과는 다음과 같습니다:

REMOTE_ADDR: 212.58.xxx.xxx
SERVER_PORT: 80
SERVER_ADDR: 85.25.xxx.xxx
SERVER_SOFTWARE: Apache:
HTTP_CF_CONNECTING_IP: 212.58.xxx.xxx
HTTP_CDN_LOOP: cloudflare
HTTP_X_REAL_IP: 162.158.xxx.xxx

세부 정보:
서버에는 BrainyCP 패널이 설치되어 있으며, Apache와 Nginx를 사용하고 있습니다(현재 웹사이트는 Docker 컨테이너를 리버스 프록시하는 Nginx를 사용 중입니다).
HTTP_CF_CONNECTING_IP는 Docker 컨테이너 내부에서 127.0.0.1으로 표시되지만, 외부에서는 정상적인 값을 가집니다.
커스텀 명령을 통해 헤더를 변경하지 않으면 Discourse는 서버의 IP를 표시합니다.

(현재 제 Discourse 인스턴스가 재구축 중이므로, 곧 더 많은 세부 정보를 추가하겠습니다.)

사용자님의 설정에 대해 정확히 알지는 못하지만, 게시글에서 Cloudflare를 확인했습니다. app.yml 파일에 Cloudflare 템플릿을 추가하셨나요?

  - "templates/cloudflare.template.yml"

수정: 잘못된 " "을 사용했습니다.

아니요, 템플릿 목록에 추가했고 이제 재빌드를 기다리고 있습니다

다시 빌드했지만 여전히 서버 IP가 표시됩니다. (커스텀 커맨드를 주석 처리해 두었습니다)

## 빌드 후 실행할 커스텀 커맨드
run:
  - exec: echo "Beginning of custom commands"
  ## 첫 번째 가입 시 'From' 이메일 주소를 설정하려면 주석을 해제하고 변경하세요:
  ## 첫 번째 가입 이메일을 받은 후 해당 줄을 다시 주석 처리하세요. 한 번만 실행하면 됩니다.
  - exec: rails r "SiteSetting.notification_email='noreply@zeronet.space'"
  #- replace:
  #   filename: /etc/nginx/conf.d/discourse.conf
  #   from: "types {"
  #   to: |
  #     set_real_ip_from 85.25.134.45;
  #     real_ip_header CF-Connection-IP;
  #     real_ip_recursive on;
  #     types {
  #- replace:
  #   filename: /etc/nginx/conf.d/discourse.conf
  #   from: $proxy_add_x_forwarded_for
  #   to: $send_http_cf_connection_ip;
  #   global: true
  - exec: echo "End of custom commands"

주석을 해제해야 하나요? (또한 app.yml에 Cloudflare 템플릿을 추가하기 전에 이미 작동하지 않았다는 점도 참고해 주세요)

주석 처리하지 않으면, $sent_http_cf_connection_ip는 127.0.0.1을 반환합니다.
image

Nginx가 Discourse 자체 대신 127.0.0.1을 보고 있을 가능성이 높으므로, nginx에서 realip를 설정해야 할 것입니다.

이미 realip 지시문을 추가하는 cloudflare.template.yml 파일을 추가했지만, 여전히 작동하지 않습니다.
커스텀 헤더로 변경하는 커스텀 명령도 제거했는데, 이제 Discourse가 localhost 대신 모든 사용자에게 서버 IP를 보고합니다.

또한 이것은 패널 자체에 의해 생성된 zeronet.space 도메인의 Nginx 설정입니다.

server {
	listen 85.25.xxx.xx:443 ssl http2;
	server_name  zeronet.space www.zeronet.space;
	root  /home/ay0ks/workspace/sites/zeronet.space;
	
	# ssl on;
	ssl_certificate  /etc/certs/ay0ks/zeronet.space_1655753906.crt;
	ssl_certificate_key /etc/certs/ay0ks/zeronet.space_1655753906.key;
	#ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
	#ssl_ciphers  "HIGH:!RC4:!aNULL:!MD5:!kEDH";
	ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3;
	ssl_ciphers TLS13-CHACHA20-POLY1305-SHA256:TLS13-AES-128-GCM-SHA256:TLS13-AES-256-GCM-SHA384:ECDHE:!COMPLEMENTOFDEFAULT;
	ssl_prefer_server_ciphers on;
	
	add_header Strict-Transport-Security 'max-age=604800';
	
	access_log /etc/nginx/vhost_logs/zeronet.space_access;
	error_log /etc/nginx/vhost_logs/zeronet.space_error;
	
	location ~ /.well-known {
		allow all;
	}
	
	location ~ /\.ht {
		deny all;
		access_log off;
		log_not_found off;
	}

	location / {
		root /home/ay0ks/workspace/sites/zeronet.space;
		proxy_pass http://85.25.xxx.xx:31080; # Discourse는 포트 31080/31443에 배포되어 있습니다
		proxy_redirect     off;
		proxy_force_ranges on;
		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;
		proxy_set_header   HTTPS $scheme;
		
		proxy_cache off;
		proxy_cache_key "$request_method|$http_if_modified_since|$http_if_none_match|$host|$request_uri";
		#access_log /etc/nginx/vhost_logs//home/ay0ks/workspace/sites/zeronet.space;
		
		proxy_cache_valid 3s;
		proxy_cache_min_uses 2;
		# proxy_cache_lock on;
		# proxy_cache_use_stale error timeout;
		# proxy_cache_use_stale updating http_502 http_504;
		limit_conn lone 100;
		# limit_req zone=ltwo burst=10;
		
		client_body_buffer_size    128k;
		client_max_body_size       1024m;
		proxy_connect_timeout      180;
		proxy_send_timeout         180;
		proxy_read_timeout         180;
		send_timeout               180;
		
		proxy_buffer_size          4k;
		proxy_buffers              8 32k;
		proxy_busy_buffers_size    68k;
		proxy_temp_file_write_size 10m;
	}

	# error_page  404              /404.html;
	# error_page   500 502 503 504  /50x.html;
}

또한 요청의 "경로"를 언급하고 싶습니다:
User -> Cloudflare -> Server(Nginx -> Docker -> Discourse)
그리고 사용자가 Docker 외부에서 Cloudflare의 헤더 CF-Connecting-IP에 표시됩니다.

Cloudflare의 실제 IP 관련 기능이 어떻게 작동하는지에 대해 자세히 알지는 못하지만, 제 추측으로는 Discourse의 nginx에서 set_real_ip_from 값이 프록시 nginx를 어떤 IP 주소로 인식하는지에 따라 달라져야 할 것 같습니다. 127.0.0.1일까요? 다른 내부 주소일까요? 아니면 공개된 IP 주소일까요? 어떤 주소를 인식할지 정확히 알 수 없습니다.

해당 주소를 확인한 후, 저는 Cloudflare 템플릿을 그대로 유지하면서 set_real_ip_from 항목에 대해 replace 지시문을 추가로 작성하는 방법을 추천합니다.

이와 함께, 프록시 nginx가 CF-Connecting-IP 헤더를 전달하도록 설정되어 있지 않거나 기본적으로 그렇게 하지 않는다면, 이를 전달하도록 구성해야 합니다. 이 부분은 제가 도움을 드리기 어렵습니다.

나와 같은 경로였는데, 내 문제는 nginx가 docker IP를 범위로 받아 realip를 설정하도록 구성되어 있지 않다는 것이었습니다.

set_real_ip_from 172.18.0.0/16;
real_ip_header X-Forwarded-For;
real_ip_recursive on;
set_real_ip_from 172.18.0.0/16;

따라서 요청 경로는 사용자 > Cloudflare > 서버 Nginx (SWAG Docker) > Discourse입니다.

proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Proto https;
proxy_set_header X-Forwarded-Ssl on;
proxy_set_header X-Real-IP $remote_addr;
templates:
  - "templates/postgres.template.yml"
  - "templates/redis.template.yml"
  - "templates/web.template.yml"
  - "templates/web.ratelimited.template.yml"
## Lets Encrypt(https)를 추가하려면 아래 두 줄의 주석을 해제하세요
#  - "templates/web.ssl.template.yml"
#  - "templates/web.letsencrypt.ssl.template.yml"
#  - "templates/web.socketed.template.yml"
  - "templates/cloudflare.template.yml"

이것이 문제의 원인이 될 수 있습니다. 패널이나 리버스 프록시가 설치되지 않은 서버에서 discourse를 설정해 보시고, 동일한 문제가 발생하는지 보고해 주세요.

첫 단계로, 아래에 제공된 세부 정보와 일치하도록 discourse의 location 블록을 수정하는 것부터 시작하세요: Run other websites on the same machine as Discourse

그건 문제의 해결책이 아니에요. 기본적으로 월 30유로짜리 전용 서버를 새로 사면 뭐든 해결되니까요 ㅋㅋ (그럼 이 스레드도 존재하지 않을 텐데).

시도해서 결과를 여기에 올리겠습니다

Discourse의 Nginx 설정(docker 내부)에도 헤더를 추가해야 하나요? 여전히 사용자 대신 서버 주소가 표시되고 있습니다.

말씀하신 경로는 제게는 이해가 되지 않습니다.

nginx가 discourse로, 그리고 discourse가 docker로 가는 구조인데, 왜 discourse가 자체 nginx를 또 사용하나요?

잘 모르겠어요. 저는 패널을 통해 Nginx 지시문들을 제 설정에 추가했고 (SSH로도 확인했습니다) 하지만 여전히 서버 IP가 표시되고 있어요.

다음은 제 app.yml 파일입니다:

## 이 파일은 올인원(All-in-one), 스탠드얼론 Discourse Docker 컨테이너 템플릿입니다
##
## 이 파일을 수정한 후에는 반드시 다시 빌드해야 합니다
## /var/discourse/launcher rebuild app
##
## 편집할 때 *매우* 주의하세요!
## YAML 파일은 공백이나 정렬 실수에 대해 매우 민감합니다!
## 필요할 경우 http://www.yamllint.com/ 에서 이 파일을 검증하세요

templates:
  - "templates/postgres.template.yml"
  - "templates/redis.template.yml"
  - "templates/web.template.yml"
  - "templates/web.ratelimited.template.yml"
  - "templates/cloudflare.template.yml"
## Lets Encrypt(https)를 추가하려면 다음 두 줄의 주석을 해제하세요
  #- "templates/web.ssl.template.yml"
  #- "templates/web.letsencrypt.ssl.template.yml"

## 이 컨테이너가 어떤 TCP/IP 포트를 노출해야 하는가?
## Discourse를 Apache나 nginx 같은 다른 웹서버와 동일한 포트를 공유하려면
## 세부 내용은 https://meta.discourse.org/t/17247을 참조하세요
expose:
  - "31080:80"   # http
  - "31443:443" # https

params:
  db_default_text_search_config: "pg_catalog.russian"

  ## db_shared_buffers를 총 메모리의 최대 25%로 설정하세요.
  ## bootstrap이 감지된 RAM 기반으로 자동으로 설정하거나, 직접 오버라이드할 수 있습니다
  db_shared_buffers: "4096MB"

  ## 정렬 성능을 향상시킬 수 있지만, 연결당 메모리 사용량이 증가합니다
  #db_work_mem: "40MB"

  ## 이 컨테이너가 어떤 Git 리비전을 사용해야 하는가? (기본값: tests-passed)
  #version: tests-passed

env:
  LC_ALL: ru_RU.UTF-8
  LANG: ru_RU.UTF-8
  LANGUAGE: ru_RU.UTF-8
  DISCOURSE_DEFAULT_LOCALE: ru

  ## 동시 웹 요청을 얼마나 지원할 수 있는가? 메모리와 CPU 코어 수에 따라 달라집니다.
  ## bootstrap이 감지된 CPU 기반으로 자동으로 설정하거나, 직접 오버라이드할 수 있습니다
  UNICORN_WORKERS: 8

  ## TODO: 이 Discourse 인스턴스가 응답할 도메인 이름
  ## 필수 항목입니다. Discourse는 IP 주소만으로는 작동하지 않습니다.
  DISCOURSE_HOSTNAME: 'zeronet.space'

  ## 컨테이너가 위에서 지정된 도메인 이름(-h 옵션)과 동일한 이름으로 시작되도록 하려면
  ## 주석을 해제하세요 (기본값 "$hostname-$config")
  #DOCKER_USE_HOSTNAME: true

  ## TODO: 초기 가입 시 관리자 및 개발자로 설정될 쉼표로 구분된 이메일 목록
  ## 예: 'user1@example.com,user2@example.com'
  DISCOURSE_DEVELOPER_EMAILS: 'contact@zeronet.space'

  ## TODO: 새 계정을 검증하고 알림을 보내는 데 사용되는 SMTP 메일 서버
  # SMTP 주소, 사용자 이름, 비밀번호는 필수입니다
  # 경고: SMTP 비밀번호에 '#' 문자가 포함되면 문제가 발생할 수 있습니다!
  DISCOURSE_SMTP_ADDRESS: smtp.zeronet.space
  DISCOURSE_SMTP_PORT: 587
  DISCOURSE_SMTP_USER_NAME: noreply@zeronet.space
  DISCOURSE_SMTP_PASSWORD: "xxxxxxx"
  DISCOURSE_SMTP_ENABLE_START_TLS: true
  DISCOURSE_SMTP_AUTHENTICATION: login
  DISCOURSE_SMTP_OPENSSL_VERIFY_MODE: none
  DISCOURSE_NOTIFICATION_EMAIL: "noreply@zeronet.space"
  #DISCOURSE_SMTP_DOMAIN: "zeronet.space"

  ## Lets Encrypt 템플릿을 추가했다면, 무료 SSL 인증서를 얻으려면 아래 주석을 해제하세요
  #LETSENCRYPT_ACCOUNT_EMAIL: me@example.com

  ## 이 Discourse 인스턴스를 위한 http 또는 https CDN 주소 (풀링 설정)
  ## 세부 내용은 https://meta.discourse.org/t/14857을 참조하세요
  #DISCOURSE_CDN_URL: https://discourse-cdn.example.com

  ## IP 주소 조회를 위한 maxmind 지리 위치 IP 주소 키
  ## 세부 내용은 https://meta.discourse.org/t/-/137387/23을 참조하세요
  #DISCOURSE_MAXMIND_LICENSE_KEY: 1234567890123456

## Docker 컨테이너는 상태가 없으며(stateless), 모든 데이터는 /shared에 저장됩니다
volumes:
  - volume:
      host: /var/discourse/shared/standalone
      guest: /shared
  - volume:
      host: /var/discourse/shared/standalone/log/var-log
      guest: /var/log

## 플러그인은 여기에 설치됩니다
## 세부 내용은 https://meta.discourse.org/t/19157을 참조하세요
hooks:
  after_code:
    - exec:
        cd: $home/plugins
        cmd:
          - git clone https://github.com/discourse/docker_manager.git

## 빌드 후 실행할 모든 커스텀 명령
run:
  - exec: echo "Beginning of custom commands"
  ## 첫 번째 가입 시 'From' 이메일 주소를 설정하려면 주석을 해제하고 수정하세요:
  ## 첫 가입 이메일을 받은 후, 해당 줄을 다시 주석 처리하세요. 한 번만 실행하면 됩니다.
  - exec: rails r "SiteSetting.notification_email='noreply@zeronet.space'"
  - exec: echo "End of custom commands"

답변은 위에 링크되어 있습니다. 다음과 같은 것이 필요합니다.

IP 감지 문제를 수정했습니다(이제 모든 것이 정상적으로 작동합니다) 하지만 이제 일부 이미지가 로드되지 않습니다

수정: 캐시 문제였네요, 이제 모든 것이 정상적으로 작동합니다! 감사합니다!