Caddy Security로 웹페이지 보호하기

Caddy Security로 웹페이지 보호하기

안녕하세요. 팡킨입니다.

홈서버를 운영하다 보면 다른 사람이 들어와서는 안되는 서비스를 외부에 열어야 경우가 있습니다. 이런 경우 보통 authentik과 같은 서비스를 이용하는데요. 문제는 이런 서비스의 경우 구성이 복잡할 뿐만 아니라 원하는 서비스마다 따로 설정이 필요해서 상당히 귀찮습니다.

Caddy Security는 처음 한번 구성해두면 원하는 서비스에 딱 한 줄만 추가하여 사용할 수 있습니다. 이번에는 Caddy Security 설치법과 로컬 인증, 구글 로그인에 대해서 알아보겠습니다.

Caddy Security 설치하기

Caddy Security는 Caddy의 플러그인입니다. apt와 같은 패키지 관리툴로 설치한 Caddy에는 포함되어있지 않습니다. 따라서 플러그인을 포함해서 따로 빌드를 해야 합니다. 하지만 저는 관리 상의 이유로 공식 패키지와 빌드한 패키지를 같이 사용하는 것을 추천합니다.

데비안에 root로 진행합니다. 다른 배포판은 방법이 많이 다를겁니다.

공식 패키지 설치하기

이미 설치되신 분들은 넘기셔도 됩니다.

apt install -y curl debian-keyring debian-archive-keyring apt-transport-https
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | tee /etc/apt/sources.list.d/caddy-stable.list
apt update
apt install caddy

Caddy 빌드하기

소스를 받아서 빌드해도 됩니다만 상당히 귀찮습니다. 따라서 xcaddy를 활용하겠습니다. 우선 xcaddy를 설치해야 합니다.

curl -1sLf 'https://dl.cloudsmith.io/public/caddy/xcaddy/gpg.key' | gpg --dearmor -o /usr/share/keyrings/caddy-xcaddy-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/xcaddy/debian.deb.txt' | tee /etc/apt/sources.list.d/caddy-xcaddy.list
apt update
apt install xcaddy

caddy는 go언어를 사용하기 때문에 빌드를 위해서는 go를 설치해야 합니다. apt에서 제공하는 golang 패키지는 너무 옛날 버전이라 오류가 납니다. 직접 설치하겠습니다.

wget https://go.dev/dl/go1.21.1.linux-amd64.tar.gz
rm -rf /usr/local/go && tar -C /usr/local -xzf go1.21.1.linux-amd64.tar.gz

이제 go를 환경변수에 추가하겠습니다.

echo "export PATH=$PATH:/usr/local/go/bin" >> /etc/profile
source /etc/profile

설치가 끝나면 아래 명령어를 통해 빌드합니다

xcaddy build --with github.com/greenpau/caddy-security

xcaddy는 최신 소스를 자동으로 가져와서 빌드를 해줍니다. 시간이 꽤 오래 걸리니 차분하게 기다립시다.

공식 패키지와 빌드한 패키지 공존시키기

공식 패키지를 설치한 이유는 2가지입니다. 혹시 빌드한 패키지에 문제가 생겼을 때 공식 패키지로 전환할 수 있고, 서비스 파일같은 설정하기 귀찮을 것들을 알아서 설정해주기 때문입니다. 이제 아래 명령어로 두 패키지를 전환할 수 있게 설정하겠습니다.

dpkg-divert --divert /usr/bin/caddy.default --rename /usr/bin/caddy
mv ./caddy /usr/bin/caddy.custom
update-alternatives --install /usr/bin/caddy caddy /usr/bin/caddy.default 10
update-alternatives --install /usr/bin/caddy caddy /usr/bin/caddy.custom 50

이제 아래 명령어를 통해 두 패키지를 전환할 수 있습니다

update-alternatives --config caddy

로컬 인증 설정

가장 기본적인 로컬 인증 먼저 해보겠습니다. 로컬 인증은 2차 인증도 지원합니다.

아래 내용을 /etc/caddy/Caddyfile에 추가합니다. 도메인과 이메일은 자신의 것으로 변경하셔야 합니다.

cookie domain은 기본 도메인으로 입력하셔야 오류가 안납니다. 예를 들어 auth.yourdomain.com이면 cookie domain은 yourdomain.com으로 하세요.

{
        order authenticate before respond
        order authorize before basicauth

        security {
                local identity store localdb {
                        realm local
                        path {$HOME}/.local/caddy/users.json
                }

                authentication portal myportal {
                        crypto default token lifetime 3600
                        crypto key sign-verify {env.JWT_SHARED_KEY}
                        enable identity store localdb
                        cookie domain <<yourdomain.com>>
                        ui {
                                links {
                                        "My Identity" "/whoami" icon "las la-user"
                                }
                        }

                        transform user {
                                match origin local
                                action add role authp/user
                                ui link "Portal Settings" /settings icon "las la-cog"
                        }
                        transform user {
                                match origin local
                                match email <<[email protected]>>
                                action add role authp/admin
                                ui link "Test Service" https://test.<<yourdomain.com>>/ icon "las la-star"
                        }
                }

                authorization policy mypolicy {
                        set auth url https://auth.<<yourdomain.com>>/
                        crypto key verify {env.JWT_SHARED_KEY}
                        allow roles authp/admin
                }

        }
}

auth.<<yourdomain.com>> {
        authenticate with myportal
}

test.<<yourdomain.com>> {
        authorize with mypolicy
        respond * "Test Service!" 200
}

Caddy Security는 인증 과정에서 JWT를 사용합니다. 이를 위해 환경 변수에 공유키를 설정해야 합니다. /etc/caddy폴더에 env.conf 파일을 만들어 아래 내용을 입력합니다. <Random String>에는 임의의 문자열을 입력하시면 되는 데, 100자 이상으로 입력하시는 게 좋습니다.

JWT_SHARED_KEY=<Random String>

이제 에디터로 /lib/systemd/system/caddy.service 파일을 열어 [Service] 항목에 아래 내용을 추가합니다.

EnvironmentFile=/etc/caddy/env.conf

저장하셨으면 systemctl 데몬을 재시작 하고 caddy도 재시작합니다.

systemctl daemon-reload
service caddy restart

이제 /var/lib/caddy/.local/caddy/users.json파일을 열어보시면 기본 설정된 유저를 확인할 수 있습니다. users 안에 username과 email_address, email_addresses을 원하는 것으로 바꾸고 저장합니다. 비밀번호는 암호화 되어 있습니다. bcrypt로 해싱해서 직접 넣으셔도 되고 아래 명령어로 기본 암호를 확인하셔도 됩니다.

journalctl -xeu caddy.service | grep created

secret 뒷부분이 비밀번호입니다.

이제 service caddy restart로 재시작하고 auth.<<yourdomain.com>>에 접속합니다.

이 화면에서 설정하신 username이나 email을 입력하고 proceed를 누른 후 비밀번호를 입력합니다.

Portal Settings를 누르고 Password탭에서 비밀번호를 변경할 수 있습니다.

이제 로그아웃 후 test.<<yourdomain.com>> 페이지로 접속해보면 auth.<<yourdomain.com>>으로 리다이렉트하는 것을 볼 수 있습니다.

구글 로그인 설정하기

Google Cloud Platform 설정

먼저 Google Cloud Platform에 접속합니다.

Google Cloud Platform
Google Cloud Platform lets you build, deploy, and scale applications, websites, and services on the same infrastructure as Google.

새 프로젝트를 만들고 새로 만든 프로젝트로 전환합니다.

메뉴버튼을 눌러 API 및 서비스 - 사용자 인증 정보를 클릭한 후 동의 화면 구성을 클릭합니다.

User Type은 외부를 선택합니다.

앱 이름을 입력하고 이메일을 선택하고 개발자 연락처 정보에 이메일 입력합니다.

범위 추가 또는 삭제를 누른 후 email, profile과 openid를 선택합니다.

이제 저장 후 계속을 눌러 구성을 마무리합니다.

사용자 인증 정보로 돌아와서 사용자 인증 정보 만들기 - OAuth 클라이언트 ID를 클릭합니다.

웹 애플리케이션을 선택하고 이름을 지정합니다.

승인된 리디렉션 URI에는 아래와 같이 지정합니다.

https://auth.<<yourdomain.com>>/oauth2/google/authorization-code-callback
https://auth.<<yourdomain.com>>/auth/oauth2/google/authorization-code-callback

만들기를 클릭하면 나오는 클라이언트 ID와 클라이언트 보안 비밀번호를 메모합니다. 클라이언트 ID는 apps  앞부분만 메모하시면 됩니다.

Caddyfile 설정

에디터로 /etc/caddy/Caddyfile을 열어 내용을 수정합니다.

{
        order authenticate before respond
        order authorize before basicauth

        security {
                local identity store localdb {
                        realm local
                        path {$HOME}/.local/caddy/users.json
                }
                oauth identity provider google {
                        realm google
                        driver google
                        client_id {env.GOOGLE_CLIENT_ID}.apps.googleusercontent.com
                        client_secret {env.GOOGLE_CLIENT_SECRET}
                        scopes openid email profile
                }

                authentication portal myportal {
                        crypto default token lifetime 3600
                        crypto key sign-verify {env.JWT_SHARED_KEY}
                        enable identity store localdb
                        enable identity provider google
                        cookie domain <<yourdomain.com>>
                        ui {
                                links {
                                        "My Identity" "/whoami" icon "las la-user"
                                }
                        }

                        transform user {
                                match origin local
                                action add role authp/user
                                ui link "Portal Settings" /settings icon "las la-cog"
                        }
                        transform user {
                                match origin local
                                match email <<[email protected]>>
                                action add role authp/admin
                                ui link "Test Service" https://test.<<yourdomain.com>>/ icon "las la-star"
                        }
                        transform user {
                                match realm google
                                action add role authp/user
                        }

                        transform user {
                                match realm google
                                match email <<[email protected]>>
                                action add role authp/admin
                                ui link "Test Service" https://test.<<yourdomain.com>>/ icon "las la-star"
                        }
                }

                authorization policy mypolicy {
                        set auth url https://auth.<<yourdomain.com>>/
                        crypto key verify {env.JWT_SHARED_KEY}
                        allow roles authp/admin
                        validate bearer header
                        inject headers with claims
                }

        }
}

auth.<<yourdomain.com>> {
        authenticate with myportal
}

test.<<yourdomain.com>> {
        authorize with mypolicy
        respond * "Test Service!" 200
}

이제 /etc/caddy/env.conf 파일을 열어 아래와 같이 추가합니다.

GOOGLE_CLIENT_ID=<클라이언트 ID>
GOOGLE_CLIENT_SECRET=<클라이언트 보안 비밀번호>

이제 service caddy restart로 재시작 후 auth.<<yourdomain.com>>에 접속하면 google 버튼이 추가되었습니다.

google 버튼을 누르면 구글 로그인으로 인증할 수 있습니다.

마치며

이제 원하는 서비스에 authorize with mypolicy만 입력하면 로그인 해야만 접근할 수 있습니다. 인증 구현이 다른 서비스에 비해서는 간단해서 CLI에 거부감이 없으시다면 상당히 좋은 것 같습니다.

해보지는 않았습니다만, 다른 OIDC 서비스도 generic 드라이버를 사용해서 이용할 수 있는 것으로 보입니다. 아래 링크를 참고하세요.

authp.github.io/assets/conf/oauth/generic/Caddyfile at main · authp/authp.github.io
Documentation for Caddy v2 Auth Portal and Authorize Plugins. - authp/authp.github.io