8 min read

Pangolin + Authentik으로 홈서버 SSO 구축하기

로그인을 매번 반복하는 게 문제였다

홈서버에 서비스를 하나둘 올리다 보면 어느 순간 깨닫습니다. "나 지금 몇 개나 로그인하고 있는 거지?"

파일 관리자, 다운로드 클라이언트, 모니터링 대시보드, DNS 관리 패널… 각각 자체 인증을 갖고 있고, 비밀번호도 제각각이고, 세션 만료 시점도 다릅니다. 외부에서 접속할 때마다 서비스별로 로그인을 반복하는 게 불편했습니다.

"한 번 로그인하면 전부 통하게 할 수 없을까?" 가 출발점이었습니다.
사실 이미 OIDC 같은 개념이 있는건 충분히 알고 있으니 이제 홈서버에 적용하는 일만 남은거죠.


선택지: Authelia vs Authentik

셀프호스팅 SSO 솔루션으로 가장 많이 언급되는 두 가지가 있습니다.

Authelia — 가볍고 설정이 YAML 기반. 2FA 지원. 단순한 ForwardAuth 용도로는 최적. 하지만 OIDC Provider 기능이 제한적이고, 관리 UI가 없어서 설정 변경할 때마다 파일을 수정하고 재시작해야 합니다.

Authentik — 무겁지만 풀스택 IdP. OIDC/SAML/LDAP 전부 지원. 웹 UI에서 Provider, Application, Flow를 관리할 수 있고, 사용자/그룹 관리도 됩니다. 대신 PostgreSQL이 필요하고 메모리를 꽤 먹어요.

저는 Authentik을 골랐습니다. 이유는:

  1. Pangolin이 OIDC IdP 연동을 네이티브로 지원함
  2. 나중에 서비스별로 세밀한 접근 제어가 필요할 수 있음 (그룹별 허용 등)
  3. 관리 UI가 있어야 설정 바꿀 때 SSH 안 타도 됨

메모리 부담이 걱정이었는데, server + worker + postgres 합쳐서 약 400~500MB 정도. 8GB RAM 서버에서 감당 가능한 수준이었습니다.


전체 구조

1.png

Badger라는 Traefik 플러그인입니다. Pangolin이 자체적으로 만든 건데, 요청이 들어올 때 세션 쿠키를 확인하고, 유효하지 않으면 Pangolin 로그인 페이지로 보냅니다. 기존의 ForwardAuth 방식과 비슷하지만, Pangolin 생태계 안에서 더 깔끔하게 동작해요.


Step 1: Authentik 배포

Docker Compose로 올립니다. 구조는 단순해요.

# docker-compose.yml (예시)
services:
  postgresql:
    image: postgres:16-alpine
    environment:
      POSTGRES_DB: authentik
      POSTGRES_USER: authentik
      POSTGRES_PASSWORD: ${PG_PASS}
    volumes:
      - db_data:/var/lib/postgresql/data

  server:
    image: ghcr.io/goauthentik/server:2025.10
    command: server
    environment:
      AUTHENTIK_POSTGRESQL__HOST: postgresql
      AUTHENTIK_POSTGRESQL__PASSWORD: ${PG_PASS}
      AUTHENTIK_SECRET_KEY: ${SECRET_KEY}
    ports:
      - "7080:9000"
    depends_on:
      postgresql:
        condition: service_healthy

  worker:
    image: ghcr.io/goauthentik/server:2025.10
    command: worker
    environment:
      AUTHENTIK_POSTGRESQL__HOST: postgresql
      AUTHENTIK_POSTGRESQL__PASSWORD: ${PG_PASS}
      AUTHENTIK_SECRET_KEY: ${SECRET_KEY}
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock

주의할 점: server 컨테이너는 리버스 프록시 네트워크에 붙여야 합니다. Traefik이 Authentik에 접근할 수 있어야 하니까요. worker는 내부 네트워크만 있으면 됩니다.

초기 설정은 http://서버IP:7080/if/flow/initial-setup/으로 접속해서 관리자 계정을 만듭니다.


Step 2: Authentik에서 OIDC Provider 생성

Authentik 관리 화면에서:

  1. Provider 생성 (OAuth2/OpenID Provider)

    • Name: Pangolin SSO
    • Authorization flow: default-provider-authorization-implicit-consent (매번 동의 안 물어보게)
    • Client ID: 자동 생성됨 (복사해둘 것)
    • Client Secret: 자동 생성됨 (복사해둘 것)
    • Redirect URI: https://pangolin.example.com/api/v1/auth/idp/oidc/2/callback
  2. Application 생성

    • Name: Pangolin
    • Slug: pangolin
    • Provider: 위에서 만든 Pangolin SSO 선택

여기서 Redirect URI가 중요합니다. Pangolin이 IdP를 등록하면 콜백 URL을 알려주는데, 그걸 정확히 맞춰야 해요. 안 맞으면 인증 후 "redirect_uri mismatch" 에러가 납니다.


Step 3: Pangolin에 IdP 등록

Pangolin 대시보드 → Settings → Identity Providers에서 OIDC를 추가합니다.

  • Name: Sign in with Authentik
  • Client ID / Secret: Authentik에서 복사한 값
  • Auth URL: https://auth.example.com/application/o/authorize/
  • Token URL: https://auth.example.com/application/o/token/
  • Identifier Path: email
  • Scopes: openid profile email
  • Auto Provision: 켜기 (Authentik 계정이 있으면 Pangolin에 자동 등록)

등록하면 Pangolin 로그인 페이지에 "Sign in with Authentik" 버튼이 생깁니다.


Step 4: 리소스별 SSO 적용

Pangolin에서 리소스(서비스)를 만들 때 SSO 보호를 켜면, 해당 서브도메인에 접근할 때 Badger 플러그인이 세션을 확인합니다.

예를 들어:

  • files.example.com → SSO 켜짐 → 로그인 필요
  • blog.example.com → SSO 꺼짐 → 누구나 접근 가능

SSO를 켜면 안 되는 서비스도 있습니다:

  • Authentik 자체 (로그인 페이지에 접근을 못하면 순환 참조)
  • API를 외부에서 호출하는 서비스 (Ghost API 등)
  • WebDAV/SFTP처럼 브라우저가 아닌 클라이언트가 접근하는 서비스
서비스 SSO 이유
파일 관리자 ✅ 민감한 데이터
다운로드 클라이언트 ✅ 외부 노출 위험
모니터링 대시보드 ✅ 서버 정보 노출
DNS 관리 ✅ 설정 변경 가능
블로그 ❌ 공개 콘텐츠
음악 공유 앱 ❌ 자체 인증 있음
이력서 ❌ 공개 페이지

Ghost API와 SSO 충돌

블로그에 SSO를 켰더니 Ghost Admin API 호출이 전부 막혔습니다.

Pangolin의 Resource Rules로 해결했습니다. 특정 경로만 SSO를 우회하는 규칙을 추가할 수 있어요.

규칙 1: /ghost/api/* → PASS (SSO 우회)
규칙 2: /ghost/*     → ACCEPT (SSO 적용)
규칙 3: *            → ACCEPT (SSO 적용)

우선순위가 중요합니다. API 경로를 먼저 매칭해서 통과시키고, 나머지 Ghost 관리 페이지는 SSO로 보호합니다. 이 순서가 바뀌면 API도 막혀버려요.


ForwardAuth vs Badger: 뭐가 다른가

Pangolin 이전에는 Traefik + ForwardAuth 미들웨어로 SSO를 구현하는 게 일반적이었습니다.

# ForwardAuth 방식 (예시)
middlewares:
  authentik:
    forwardAuth:
      address: "http://authentik:9000/outpost.goauthentik.io/auth/traefik"
      authResponseHeaders:
        - X-authentik-username
        - X-authentik-groups
        - X-authentik-email
      trustForwardHeader: true

이 방식도 동작하지만, 서비스마다 미들웨어를 수동으로 붙여야 합니다. Traefik 동적 설정 파일을 수정하거나, Docker 라벨을 추가하거나.

Pangolin + Badger 방식은 UI에서 토글 하나로 SSO를 켜고 끌 수 있습니다. 내부적으로는 비슷한 일을 하지만, 관리 포인트가 Pangolin 대시보드 하나로 모입니다.

다만 Pangolin의 Badger 방식은 Pangolin 생태계에 종속됩니다. Traefik을 직접 관리하는 게 편한 사람은 ForwardAuth가 나을 수도 있어요. 저는 서비스가 20개 가까이 되니까 UI에서 관리하는 게 압도적으로 편했습니다.


CrowdSec 연동: SSO 위에 한 겹 더

SSO만으로는 부족한 부분이 있습니다. 로그인 페이지 자체에 대한 브루트포스 공격이나, 알려진 악성 IP의 접근을 막으려면 별도 프로텍션 레이어가 필요해요.

CrowdSec을 Traefik 플러그인으로 붙였습니다. 커뮤니티 기반 IP 블랙리스트 + 로컬 로그 분석으로 의심스러운 접근을 차단합니다.

이것만 하더라도 귀찮은 탐지봇 녀석들 엿을 먹여줄 수 있습니다.

# Traefik 동적 설정 (예시)
middlewares:
  crowdsec:
    plugin:
      crowdsec-bouncer:
        crowdsecLapiScheme: http
        crowdsecLapiHost: crowdsec:8080
        crowdsecLapiKey: ${BOUNCER_API_KEY}

Pangolin의 Middleware Manager를 통해 특정 리소스에만 CrowdSec을 적용할 수도 있습니다. 저는 외부 노출되는 서비스 전부에 걸어뒀어요. 음. 이런게 왜 필요한지에 대한 상세는
나중에 또 다른 포스팅에서 다뤄보면 좋을거같습니다.


돌이켜보면

좋았던 점: 한 번 로그인하면 SSO가 적용된 모든 서비스에 세션이 유지됩니다. 모바일에서도 브라우저 하나로 전부 접근 가능. 새 서비스 추가할 때 토글 하나면 끝.

아쉬웠던 점: Authentik이 생각보다 무겁습니다. 초기 설정도 Provider → Application → Pangolin IdP 등록 → 콜백 URL 맞추기까지 삽질이 좀 있었어요. 특히 Redirect URI 불일치 에러는 디버깅이 짜증났습니다.

다시 한다면: 서비스가 5개 이하였으면 Authelia로 갔을 겁니다. 가볍고 설정도 단순하니까요. 하지만 10개 넘어가면 Authentik의 웹 UI와 OIDC 네이티브 지원이 확실히 편합니다. 서비스 수에 따라 선택이 달라지는 문제라고 생각해요.


참고 자료