caddy 를 이용한 역방향 프록시 설치 사용기 입니다.
안녕하세요
요즘 caddy 를 사용하시는 분들이 많아 보여서 궁금하기도 하고 http/3도 경험해 볼 겸
traefik 에서 넘어와 봤습니다.
기대한대로 속도도 빠르고 설정은 넘사벽으로 간단해서 빠져들것 같습니다.
간단한 설치과정을 공유하고자 글을 올려요.
우분투 22.04에서 사용중입니다.
1. 도커 이미지 만들기
적당한 폴더를 만들고 Dockerfile 을 만들어서 아래와 같이 작성했습니다.
Dockerfile
FROM caddy:builder AS builder
RUN xcaddy build \
--with github.com/caddy-dns/cloudflare \
--with github.com/hslatman/caddy-crowdsec-bouncer/http \
--with github.com/hslatman/caddy-crowdsec-bouncer/crowdsec \
--with github.com/caddyserver/transform-encoder \
--with github.com/corazawaf/coraza-caddy/v2@main
FROM caddy:latest
COPY --from=builder /usr/bin/caddy /usr/bin/caddy
빌드시 포함한 모듈
클라우드플레어에서 발급받은 도메인을 사용중이며 dns 방식으로 인증서를 자동으로 발급받기 위한 모듈입니다.
보안 관련 crowdsec 을 사용하기 위한 모듈입니다.
transform-encoder
access.log 로그를 apache 형식으로 전환하려고 설치했습니다.
OWASP Coreruleset 을 사용하기 위한 모듈입니다.
주의하실 점이 caddy 공홈에서 다운로드시 알려주는 github 주소를 따라서 설치 하면
이후 모듈 설명서와 동일하게 설정해도 오류가 발생하니, 위와같이 경로를 참조해 주셔야 합니다.
docker build --tag caddy:custom .
이렇게 입력하시면 이미지가 빌드 됩니다.
참고로 golang, xcaddy 는 미리 설치되어 있어야 합니다.
2. docker-compose.yml 작성하기
docker-compose.yml
version: "3.9"
services:
caddy:
image: caddy:custom
container_name: caddy
hostname: caddy
env_file: .env
restart: always
networks: # 각자 사용중인 네트워크 설정을 적용하시면 됩니다.
t2_proxy:
ipv4_address: 192.168.x.xxx
ports:
- "80:80"
- "443:443"
- "443:443/udp"
volumes:
- $DOCKERDIR/appdata/caddy/Caddyfile:/etc/caddy/Caddyfile
- $DOCKERDIR/appdata/caddy/caddy_config:/config
- $DOCKERDIR/appdata/caddy/caddy_data:/data
- $DOCKERDIR/appdata/caddy/ruleset:/ruleset
- $DOCKERDIR/shared:/shared
- $DOCKERDIR/logs/cloudserver/caddy/access.log:/var/log/access.log
environment:
- TZ=$TZ
cap_add:
- NET_ADMIN # udp buffer size error
labels:
- 'com.centurylinklabs.watchtower.enable=false'
내용설명
caddy:custom
이미지 이름은 빌드시 설정한 이름입니다.
.env
환경파일을 지정했습니다.
네트워크 설정은 각자 사용중인 네트워크 또는 설정 안하고 도커 기본 브릿지 네트워크를 사용하시면 됩니다.
443/ udp 포트는 http/3를 사용하기 위해 설정합니다.
기본으로 바인딩 된 볼륨 외에 ruleset 폴더는 coraza 규칙들을 저장하기 위한 폴더이며
shared 폴더는 내부망에서 https 로 역방향 프록시를 적용할때 필요한 인증서가 저장될 폴더입니다.
crowdsec 에서 로그를 파싱하기 위한 접근로그를 만듭니다.
access.log 과 Caddyfile 파일은 도커 생성시 자동으로 생성되지 않으니 미리 만들어 두세요.
net admin
설정은 보통 caddy 설치 하시면 로그에 udp 관련 오류가 잡히는데 호스트에서
관련 설정을 수정해 주더라도 도커로 실행되서 그런가 오류가 계속 발생해서 옵션을 주었습니다.
마지막 lables 설정은 커스텀 이미지라 watchtower 감시대상에서 제외하기 위한 설정입니다.
docker-compose.yml 이 설치된 폴더에 .env 파일을 만들고 사용할 환경변수를 저장해 줍니다.
.env
TZ="Asia/Seoul"
DOCKERDIR="/home/변경/docker"
DOMAINNAME_CLOUD_SERVER=변경.com
CLOUDFLARE_EMAIL=변경@gmail.com
CF_API_TOKEN=hcdXqKwsT7DLs변경
클플은 키가 아니라 토큰입니다. 사소해 보이지만 키넣고 안되서 고생했습니다. 메뉴얼은 꼼꼼히 봐야 되요 ㅠㅠ
발급 과정은 달소님이 잘 설명해 주셨습니다.
이제 도커를 올려줍니다.
docker-compose up -d
# 일반적으로 제가 자주 쓰는 형식입니다.
docker-compose -f custom.yml up -d --force-recreate 도커명
3. caddy 설정하기
caddy 의 설정 파일은 json 방식과 Caddyfile 방식으로 나눌 수 있습니다.
전자는 모든 caddy 설정이 가능하지만 복잡하고 후자는 대부분의 설정이 가능하지만 간단합니다.
저는 Caddyfile 방식으로 설정했습니다.
아래 올린 설정을 한번에 적용하기보다는 인증서 발급 > 프록시 설정 > 각종모듈 설정순으로 진행하시는게
오류 잡기 편하실 거에요. 설정을 적용하시고 싶으면
docker-compose exec -w /etc/caddy caddy caddy reload
입력하시면 도커를 재시작 할 필요 없이 간단히 적용됩니다.
간혹 이쁘게 정렬 안시켰다고 에러가 뜨면 아래와 같이 해결하시면 됩니다.
docker-compose exec -w /etc/caddy caddy caddy fmt --overwrite
이미 만들어둔 Caddyfile 을 편집합니다. 편의상 세부분으로 나누어 설명하나 하나의 파일 내용입니다.
Caddyfile - 공통설정
###################################
# common definition
###################################
(log) {
log {
format transform `{request>client_ip} - {request>user_id} [{ts}] "{request>method} {request>uri} {request>proto}" {status} {size}` {
time_format "02/Jan/2006:15:04:05 -0700"
time_local
}
output file /var/log/access.log
}
}
(common) {
encode zstd gzip
tls {
dns cloudflare {env.CF_API_TOKEN}
}
header {
Strict-Transport-Security "max-age=31536000; includeSubdomains; preload"
# Enable cross-site filter (XSS) and tell browser to block detected attacks
X-XSS-Protection "1; mode=block"
X-Content-Type-Options "nosniff"
Referrer-Policy "same-origin"
# Disallow the site to be rendered within a frame (clickjacking protection)
X-Frame-Options "ALLOW-FROM *.변경.com"
# Prevent search engines from indexing (optional)
X-Robots-Tag "none, noarchive, nosnippet, notranslate, noimageindex"
# Server name removing
-Server
# Content-Security-Policy "default-src 'none'; script-src 'self'; connect-src 'self'; img-src 'self'; style-src 'self';base-uri 'self';form-action 'self';frame-ancestors 변경.com *.변경.com"
Permissions-Policy "geolocation=(), camera=(), microphone=(), payment=(), usb=(), vr=()"
}
}
(authelia) {
forward_auth authelia:9091 {
uri /api/verify?rd=https://auth.변경.com/
copy_headers Remote-User Remote-Groups Remote-Name Remote-Email
@error status 4xx 5xx
handle_response @error {
import waf
reverse_proxy error-pages:8080 {
header_up +X-Code {rp.status_code}
}
}
}
import log
}
(reverse) {
@{args[1]} host {args[1]}.{$DOMAINNAME_CLOUD_SERVER}
handle @{args[1]} {
reverse_proxy 192.168.x.xx:{args[0]}
}
}
(reverse-inner) {
@{args[2]} host {args[2]}.{$DOMAINNAME_CLOUD_SERVER}
handle @{args[2]} {
reverse_proxy {args[2]}{args[1]}{args[0]}
}
}
(waf) {
coraza_waf {
load_owasp_crs
directives `
Include /ruleset/coraza-coreruleset/rules/@coraza.conf-recommended
Include /ruleset/coraza-coreruleset/rules/@crs-setup.conf.example
Include /ruleset/coraza-coreruleset/rules/@owasp_crs/*.conf
SecRuleEngine On
`
}
}
(chain-auth) {
crowdsec
import common
import authelia
}
(log)
일반적으로 사용시에는 로그포멧을 변경할 필요는 없습니다. 오히려 더 많은 정보를 json 방식으로 보여줍니다.
하지만 crowdsec 을 사용할 때 로그를 파싱하긴 하는데 정상적으로 규칙을 적용 하지 못합니다.
그래서 불편하지만 apache 형식으로 변환을 해주어야 정상동작 했습니다.
전반적인 crowdsec 설정은 여기서 다루기엔 너무 길어질 것 같아서 다른 글을 참고 해 주세요.
output 에 컴포즈 파일에서 설정한 도커 내부경로를 입력해 줍니다.
(common) / (authelia)
(common)
전송시 사용할 압축과 클라우드플레어 dns 방식을 이용한 인증서발급을 위한 설정이 적용되어있습니다.
보안 헤더 부분은 본인이 잘 아시는 방법으로 적용해 주시면 됩니다.
전 초보라서 블로거등에서 추천받은 대로 적용해서 사용중입니다.
(authelia)
2fa 인증기로 사용중인 authelia 관련 설정입니다. 안쓰시는분은 적용하실 필요가 없습니다.
사용 하시는 분을 위한 약간의 팁으로 제경우 4xx 5xx 에러페이지를 error-pages 라는 도커를 사용해서
이쁘게(?) 만들었는데 authelia 인증실패시 403 fobbiden 딸랑 한줄이 너무 심심해 보여서 페이지를 포워딩 시켜줬습니다. authelia 에서 에러 반환시 응답을 받아서 X-Code 헤더에 추가시켜준 후 에러페이지로 보내줍니다.
마지막으로 (authelia)의 설정은 모든 서브도메인에 적용되기 때문에
access.log 에 로그를 기록하도록 import 를 이용해 log 를 적용해 줍니다.
(reverse) / (reverse-inner)
(reverse)
caddy의 장점이 단 몇줄로 역방향 프록시가 가능하단 점인데요. 극대화 시키기 위해 단순화 시켰습니다.
적용 방법은 import reverse 도커외부포트 도커이름 이렇게 해주시면 됩니다.
(revers-inner)
docker-compose.yml 설정 중에 ports 는 도커 내부포트와 외부포트를 연결 시켜주는 설정입니다.
예를들어 8443:443 이라고 설정 하시면 도커내부의 443 포트는 호스트와 8443 으로 연결되게 됩니다.
이런 방법을 통해 호스트에서는 도커가 사용하는 포트로 부터 자유롭게 포트설정이 가능해 지는데요.
### 사족 이겠지만 ports 설정이 없는경우 외부로 열린 포트가 없으니
도커 브릿지 내부망이 아닌 외부에서는 접근 할 수 없습니다. ###
만약 caddy 가 다른 도커 예를들어 portainer (ports 9900:9000)와 동일한 네트워크에
물려있다면 (2.번 문단 networks 설정 참조 : t2_proxy) 서로간에는 portainer 의 내부포트(9000)를 이용해서
통신이 가능해 집니다. 뿐만아니라 192.168.x.xx:9900 이외에 portainer:9000 으로도 도커간 연결이 가능 합니다.
위 두 설정은 기본적으로 동일한 방법이나 변수가 하나 추가 됬는데, 그 이유는 {arg}:{arg} 이런 식으로 작성하면
오류가 납니다. : 문자를 일반적으로 코딩할때 범위 적용할때 쓰는 그런걸로 인식하나 봅니다.
어쩔수 없이 : 문자도 입력 받는 변수로 추가 해 줬습니다. (2.7.6 에서 버그수정 됨)
둘의 차이는 ports 설정을 통한 다른 도커 네트워크와의 연결인가 아니면
동일 네트워크에 속하는 도커로서 ports 설정이 없는 연결인가의 차이점입니다.
(waf) / (chain-auth)
(waf)
coraza-caddy 모듈을 사용하기 위한 설정입니다. 관련된 자세한 설명은 다른 게시물을 참조해 주세요.
제가 설명하기엔 지식이 모자라서... 죄송합니다. 단 설정과정에서 load_owasp_crs 이 옵션은 앞서 말씀드린 바와 같이 v2@main 브란치의 모듈을 불러와야 오류가 발생하지 않습니다.
사용되는 3개의 conf 는 github 에서 clone 해오시면 됩니다. 호스트에서 2.번 문단에서 바인드 시킨 ruleset 폴더로 가신다음 git clone https://github.com/corazawaf/coraza-coreruleset.git 을 실행 하시면 됩니다.
(chain-auth)
자주 쓰는 설정을 모아모아서 단순화 시켰습니다.
Caddyfile - server / modules
{
# debug
email {$CLOUDFLARE_EMAIL}
servers {
metrics
trusted_proxies static 173.245.48.0/20 103.21.244.0/22 103.22.200.0/22 103.31.4.0/22 \
141.101.64.0/18 108.162.192.0/18 190.93.240.0/20 188.114.96.0/20 197.234.240.0/22 \
198.41.128.0/17 162.158.0.0/15 104.16.0.0/13 104.24.0.0/14 172.64.0.0/13 131.0.72.0/22 \
2400:cb00::/32 2606:4700::/32 2803:f800::/32 2405:b500::/32 2405:8100::/32 2a06:98c0::/29 2c0f:f248::/32 \
10.0.0.0/8 172.16.0.0/16 192.168.0.0/16 fc00::/7
}
order coraza_waf first
order crowdsec first
crowdsec {
api_url http://crowdsec:8080
api_key 09zP5A7변경
ticker_interval 10s
# disable_streaming
# enable_hard_fails
}
# for metrics only 192./24
admin :2019
}
server / modules
debug
caddy 의 자체 로그 설정입니다. (엑세스 로그가 아님)
자동으로 인증서 발급을 위해 필요한 설정입니다.
metrics
api를 통한 정보를 보내줍니다. 아래 admin :2019 를 통해 api 접근이 가능하게 됩니다.
trusted proxy
클라우드플레어의 프록시를 신뢰하는 프록시로 추가 하고 내부망으로 예약된 주소도 포함시켜 줬습니다.
외부에서 접근하는 ip 를 정상적으로 표시하기 위해 설정 했습니다.
order
crowdsec 과 coraza 를 사용하기 위해 필요한 옵션입니다. crowdsec 사용안하시는 분은 패스하시면 됩니다.
Caddyfile - reverse proxy
###################################
# reverse proxy services
###################################
{$DOMAINNAME_CLOUD_SERVER} {
import waf
import chain-auth
reverse_proxy homepage:3000
}
auth.{$DOMAINNAME_CLOUD_SERVER} {
import waf
import chain-auth
reverse_proxy authelia:9091
}
*.{$DOMAINNAME_CLOUD_SERVER} {
import chain-auth
@dsm host dsm.{$DOMAINNAME_CLOUD_SERVER}
handle @dsm {
reverse_proxy https://192.168.x.xx:5001 {
transport http {
# tls_insecure_skip_verify
# tls_server_name xxx
tls_trusted_ca_certs /shared/cert/domain.com/dsm.pem
}
}
}
import reverse xxxx wyl
import reverse xxxx ip1
import reverse xxxx sh
import reverse xxxx whoogle
import reverse xxxx qbit
import reverse-inner 80 : speedtest
import reverse-inner 80 : heimdall
import reverse-inner 80 : filerun
import reverse-inner 2368 : ghost
import reverse-inner 3000 : grafana
import reverse-inner 5244 : alist
import reverse-inner 5800 : handbrake
import reverse-inner 7880 : goaccess
import reverse-inner 8080 : miniflux
import reverse-inner 8080 : shiori
import reverse-inner 8080 : dozzle
import reverse-inner 8080 : guacamole
import reverse-inner 8443 : vscode
import reverse-inner 9000 : portainer
import reverse-inner 53842 : gokapi
import reverse-inner 61208 : glances
handle {
abort
}
}
reverse proxy
domain.com auth.domain.com *.domain.com 총 세개의 부분으로 구성했습니다.
domain.com 은 homepage 도커로 연결됩니다.
처음 설정하시는 분들은 클라우드플레어 설정과 이부분만 간단히 설정 하시고
먼저 정상적으로 인증서가 발급되는지 확인하고 진행하시는게 오류잡기 편하실 거에요.
공통설정 부분에서 작성해 놓은 것이 요긴하게 쓰입니다. 필요한 설정들만 import 해서 사용하시면 됩니다.
dsm 설정을 보시면 내부적으로 https 통신이 사용되는 경우 쓸 수 있는 방법을 보여줍니다.
transport http 하단의 세개의 설정중에 제일 위 설정만 활성화 하면 유효성 인증을 불문하고 https 로 연결을 해주고,
아니면 위와같이 클라이언트의 인증서를 직접 caddy 에서 신뢰하라고 넣어 주실 수 도 있습니다.
traefik 의 경우 도커 내부에서 mkcert 같은 프로그램으로 루트 인증서를 만들어 두면 별다른 과정 없이 잘 동작 했는데 caddy 는 아직 조금 아쉽더라구요. opnsense, proxmox 역시 이 방법들을 활용하시면 연결이 가능 할 것 같습니다.
우... 길었습니다. 완전 길어서 읽는 분들이 엄청 피곤하실 것 같네요. ㅠㅜ
소감은 앞서 말씀드린 것처럼 압도적인 편의성인 것 같습니다. 이것저것 신경 안써도 되고 단 몇줄로 해결 된다는게 오래전 nginx 와 bsd 로 온갖 삽질을 하던게 주마등처럼 지나 가네요. traefik 과 비교해서도 속도도 꿀리지 않는 것 같습니다.
구글이나 클플로 측정해도 비슷비슷합니다. 진입장벽도 낮으니 한번씩 도전해 보시길 바라요 ^^/
homepage 이쁘네요.
저도 시간될때마다, 업데이트 중입니다.
내용 하나하나 알차고 완벽하네요!! 오픈소스 가이드 Caddy 쪽에 추가해도 괜찮을까요??
저도 따라해봐야겠네요 ㅎㅎ 감사합니다~~
물론입니다. 도움이 된거 같아 작성한 보람이 생기네요 ㅎㅎ
감사합니다
flags: 0x5000: not a directory
cmt alert