ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Linux] 도커로 Cloudflare Tunnel + Nginx Proxy Manager + Authelia 구성으로 포트 공개하지 않고 안전하게 외부로 서비스 공개하기
    HomeLAB/Linux 2025. 2. 16. 21:44

    오라클 클라우드 Tree Tier에서는 생각보다 많은 컴퓨팅 자원을 무료로 사용할 수 있습니다. AMD x86 시스템에서는 1 CPU + 1GB의 인스턴스 2개, Ampere ARM 시스템에서는 4 CPU + 24GB의 인스턴스 1개까지 만들 수 있습니다. 원하면 전부 가능하나 통합 200기가의 용량은 그리 넉넉하지는 않지만 원한다면 쪼개어 3개의 인스턴스를 모두 만들 수 있습니다. 자세한 건 오라클 클라우드의 상시 무료 사용을 확인해 보면 됩니다.

     

    https://www.oracle.com/kr/cloud/free/#always-free

     

    저의 경우에는 Ampere ARM 시스템에 모두 몰아주어 4 CPU + 24GB + 200GB 인스턴스 1개를 운용하고 있습니다. 항상 인터넷과 연결되어 있는 클라우드 인스턴스 특성상 해킹과 여러 네트워크 공격에 노출되어 있습니다. 따라서 기존에는 NginxProxyManager에 80, 443 포트를 열어 리버스 프록시만 포트를 열어두는 것이었지만 좀 더 강력한 보호를 위해 Cloudflare Tunnel의 도움을 추가로 받기로 결정했습니다.

     

    기본적인 구조를 이미지로 정리

     

    Docker의 네트워크를 통해서 Host와 분리합니다. 인터넷에서 들어오는 요청을 Cloudflare Tunnel로 걸러냅니다. 이때, DDoS, 등의 Cloudflare의 고급 기능을 무료로 사용이 가능합니다. 그렇게 NginxProxyManager로 요청이 들어왔을 때 Authelia의 인증을 거쳐야 각 서비스 컨테이너로 접근이 가능해지는 방식입니다.

     

    다만, 이 글을 따라 하게 된다면 앞으로 모든 docker-compose.yaml 파일에 대한 ports 항목에 대한 부분은 전부 expose로 대체해야 하고 network 도 추가해야 합니다. 이러면 host에서는 바로 접근이 불가능하기에 주의하셔야 합니다.


    0. 시스템 사양

    Orlacle Cloud A1

    CPU: 4 CPU

    RAM: 24GB

    OS: Ubuntu 22.04

     

    1. 준비 사항

    준비해야 할 사항이 있습니다.

    1. 도메인 - 단, 구입한 곳이 어디든 상관없이 Cloudflare 계정을 만들고 등록해야 합니다. duckdns 같은 서비스는 사용 불가능합니다.

    2. 공식 도커 엔진 설치 - https://docs.docker.com/engine/install/ 공식 문서 확인하고 설치하시면 됩니다. 아마 일반적으로 우분투/데비안의 경우에는 apt install docker.io docker-compose로 설치했을 건데 이 경우에는 저와 명령어가 같지 않아 제대로 호환되지 않을 수 있습니다.(이 경우에는 docker compose 가 아닌 docker-compose로 명령어를 바꾸셔야 합니다.)

     

    위의 준비해야 할 부분은 구글에 잘 찾아보면 방법이 나오니, 설명을 잘 보고 따라 하면 어렵지 않게 하실 수 있을 겁니다.

     

    2. 최종 아키텍처

    ChatGPT로 해결할때 답변해준 부분 캡쳐

     

    위에서도 간략하게 설명했지만, 한 번 더 설명합니다. 어떻게 작동할지 ChatGPT로 한창 삽질(..)할 때 답변받은 겁니다. 그림과 글로 두 번 보면 어떻게 작동할지 대략적으로 감이 오셨으리라 생각합니다. 원래는 Authentik을 사용하려고 생각했는데 이전부터 Authelia를 사용해서 변경되었습니다. 아마 사용 방법이 크게 다르진 않을 것으로 보입니다.

     

    이제 본격적으로 시작해 볼 겁니다.

     

    2. cloudflared 설정

    cloudflared의 경우에는 docker-compose.yaml 파일을 먼저 작성하는 게 아니라 컨테이너에 있는 cloudflared로 명령을 보내 인증 파일과 터널의 정보가 담긴 json 파일을 생성해야 합니다.

     

    # 디렉터리 생성 - 권한을 위해 777로 생성
    mkdir -m 776 -p ./cloudflared/data
    
    # cloudflare에 로그인하여 인증 파일 생성하기
    docker run --rm -v ./cloudflare/data:/home/nonroot/.cloudflared cloudflare/cloudflared:latest tunnel login

     

    인증 파일 생성 시에는 아래와 같이 cloudflare 링크가 뜨는데, 이 링크에 아무 컴퓨터나 브라우저로 접속해서 인증하면 됩니다. 바로 링크로 접속했을 때 로그인 되어있지 않으면 로그인 진행 후 바로 대시보드로 이동합니다. 따라서 https://dash.cloudflare.com에서 먼저 로그인 진행 후 링크에 접속하시는 것을 추천드립니다.

     

     

    인증 완료 시 아래와 같이 안내 문구가 나오며 인증 파일이 생성됩니다.

     

    Leave cloudflared running to download the cert automatically.
    You have successfully logged in.
    If you wish to copy your credentials to a server, they have been saved to:
    /home/nonroot/.cloudflared/cert.pem

     

    이제 Cloudflare Tunnel을 생성할 차례입니다.

     

    # cloudflare에 터널 생성하기
    # 예시: docker run --rm -v ./cloudflared/data:/home/nonroot/.cloudflared cloudflare/cloudflared:latest tunnel create expbox
    docker run --rm -v ./cloudflared/data:/home/nonroot/.cloudflared cloudflare/cloudflared:latest tunnel create <원하는 터널 이름>

     

    위의 명령어를 입력하고 조금만 기다리면 터널이 새로 생성됩니다. 터널이 새로 생성되면서 터널에 대한 정보가 담긴 json 파일을 생성합니다.

     

     

    여기서 맨 마지막에 나오는 UUID를 기억해 두시거나 어딘가에 적어두시는 게 좋습니다. 이 UUID의 경우 추후에 사용할 cloudflared의 설정 파일에 사용됩니다. 하지만 잊어먹었더라도 cloudflared/data 디렉터리에 있는 json 파일의 이름을 확인하면 되니까 크게 걱정하지 않으셔도 됩니다.

     

    터널까지 생성되었다면 서브 도메인을 입력할 차례입니다. 무료 사용자의 경우 인증서가 루트 도메인(example.net), 서브 도메인(service1.example.net)까지만 허용됩니다. aaa.service.example.net 같은 서브의 서브(?) 도메인까지 인증서를 사용하려면 월 10달러나 되는 인증서를 구입해야 합니다.

     

    # cloudflare의 터널에서 CNAME 서브도메인 생성
    # 예시: docker run --rm -v ./cloudflared/data:/home/nonroot/.cloudflared cloudflare/cloudflared:latest tunnel route dns expbox "service.example.net"
    docker run --rm -v ./cloudflared/data:/home/nonroot/.cloudflared cloudflare/cloudflared:latest tunnel route dns <터널 이름> "<원하는 서브 도메인>"
    
    # TMI
    # 서버가 하나라서 굳이 서브 도메인을 나눌 필요가 없다면 <원하는 서브 도메인>을 와일드카드로 지정하셔도 됩니다.
    # 와일드카드 --> *.example.net

     

    명령어를 입력하면 아래와 같이 CNAME으로 서브 도메인이 생성됩니다.

     

    NPM, Authelia에 사용할 도메인 2개를 생성해 주시면 됩니다.

     

    쉘에 뜨는 내용
    웹의 Cloudflare DNS 레코드에서 확인한 내용

     

    서브 도메인을 하나씩 추가할 때마다 이렇게 고생하면서 명령어를 쳐야 하는가?라고 하시면 그건 아닙니다. Cloudflare Dashboard에서도 추가가 가능합니다. 아래의 접은 글을 확인해 보세요.

     

    더보기

    https://dash.cloudflare.com/ - 구입한 도메인 - 왼쪽 사이드바에서 DNS를 순서대로 누르면 확인할 수 있습니다.

     

     

    위와 같이 생성된 도메인을 볼 수 있습니다. 오른쪽의 편집 버튼을 누르면 이런 식으로 이름과 대상이 뜹니다. 이름의 경우 서브 도메인으로 되어있고 대상의 경우 <UUID>.cfargotunnel.com으로 지정이 되어있습니다. 여기서 대상의 URL을 가지고 새로운 서브도메인을 CNAME으로 만들면 됩니다.

     

    레코드 추가를 누르고, 유형은 CNAME, 이름(서브도메인)은 원하는 대로, 대상은 아까 봤던 <UUID>.cfargotunnel.com으로 지정하고 저장하면 됩니다. 프록시 상태는 주황색으로 켜두면 됩니다.

     

    이제는 cloudflared 디렉터리 내에 있는 data 디렉터리에 config.yaml 파일을 생성하고 편집하면 됩니다. 

     

    # vi로 편집하기
    touch cloudflared/data/config.yaml && vi cloudflared/data/config.yaml
    
    # nano로 편집하기
    touch cloudflared/data/config.yaml && nano cloudflared/data/config.yaml

     

    touch로 config.yaml 파일을 생성하고 vi, nano 등으로 아래의 내용을 넣으면 됩니다. 서비스 주소는 도커 컨테이너의 서비스 이름 혹은 컨테이너 이름으로 넣으면 됩니다.

     

    # 예시 UUID는 랜덤 생성기로 생성함
    # tunnel: 7e3e754f-8642-455d-b550-ad57414fb91f
    # credentials-file: /home/nonroot/.cloudflared/7e3e754f-8642-455d-b550-ad57414fb91f.json
    tunnel: <UUID>
    credentials-file: /home/nonroot/.cloudflared/<UUID>.json
    
    ingress:
      # 예시
      # NPM
      # - hostname: npm.example.net
      #   service: http://npm:80
      #   originRequest:
      #     noTLSVerify: true
      
      # NPM
      - hostname: <NPM 서브 도메인>
        service: https://npm:81 # NPM 서비스 이름 + 관리용 81 포트
        originRequest:
          noTLSVerify: true
          
      - hostname: <Authelia 서브 도메인>
        service: https://authelia:9091 # Authelia 서비스 이름 + 접속용 9091 포트
        originRequest:
          noTLSVerify: true
    
      - service: http_status:404
    
    loglevel: info

     

    위의 config.yaml의 경우 NPM의 초기 설정, Authelia의 정상 작동을 확인해야 하기에 일부러 컨테이너로 직결했습니다. 나중에 수정할 거니까 크게 개의치 마세요. 이제 cloudflared의 1차 설정을 끝냈습니다. 이제 다음 단계인 docker-compose.yaml 파일을 작성해 봅시다.

     

    3. Docker Network 생성

    cloudflared의 초기 설정과 이 과정인 network 생성과 서브넷 확인이 조금 복잡할 뿐이지 다음 과정부터 나오는 docker-compose.yaml 파일 작성은 그리 어렵지 않습니다. cloudflared, NPM, Authelia가 내부에서 잘 작동하기 위해서는 같은 네트워크에 묶어야 합니다. 따라서 network를 생성하고 거기에 컨테이너를 연결하는 방법을 사용할 겁니다.

     

    브리지 네트워크를 생성합니다. 

     

    # bridge 네트워크 생성
    # docker network create expbox
    docker network create <네트워크 이름>

     

    그리고 나중에 NPM에서 사용할 서브넷의 정보를 확인합니다.

     

    # 생성한 도커 네트워크의 서브넷 확인
    # docker network inspect expbox | grep -i subnet
    docker network inspect <네트워크 이름> | grep -i subnet

     

    도커 네트워크의 서브넷 확인

     

    확인하면 이렇게 서브넷이 나오는데 이 부분을 잘 기억하거나 다른 곳에 잠시 저장해 두시면 됩니다.

     

    4. cloudflared의 docker-compose.yaml 작성 및 실행

    주의) 이 글에서는 docker-compose.yaml 파일을 각 서비스별로 따로 작성한다는 가정으로 작성되어 있습니다.

     

    docker-compose.yaml 파일을 생성하고 편집합니다. docker-compose.yaml 파일은 cloudlflared 디렉터리 내부에 존재하는 기준으로 volumes를 작성했기에 앞으로 따라 하시는 부분에 주의가 필요합니다.

     

    # vi로 편집하기
    touch cloudflared/docker-compose.yaml && vi cloudflared/docker-compose.yaml
    
    # nano로 편집하기
    touch cloudflared/docker-compose.yaml && nano cloudflared/docker-compose.yaml

     

    services:
      cloudflared:
        image: 'cloudflare/cloudflared:latest'
        # container_name: Cloudflared-Tunnel
        container_name: <컨테이너 이름>
        restart: unless-stopped
        # command: tunnel --no-autoupdate run expbox
        command: tunnel --no-autoupdate run <tunnel 이름>
        volumes:
          - ./data:/home/nonroot/.cloudflared
        # 호스트에 프로그램이 실행되고 접근해야한다면, extra_hosts 부분의 주석을 삭제하세요.
        # extra_hosts:
        #   - host.docker.internal=host-gateway
        # networks:
        #   - expbox
        networks:
          - <네트워크 이름>
    
    # networks:
    #   expbox:
    #     external: true
    networks:
      <네트워크 이름>:
        external: true

     

    이제 cloudflared의 준비가 끝났습니다. 아래의 명령어로 컨테이너를 실행시킵니다.

     

    docker compose -f './cloudflared/docker-compose.yaml' up -d

     

    여기까지 잘 따라오셨다면 설정한 NPM 혹은 Authelia의 주소로 접속해 보면 아래와 같이 뜰 겁니다.

     

     

    서비스를 실행하지 않았기에 이게 정상입니다. 다만 다르게 뜨면 설정을 다시 확인해 볼 필요가 있습니다.

     

    5. NginxProxyManager의 docker-compose.yaml 작성 및 실행

    주의) 이 글에서는 docker-compose.yaml 파일을 각 서비스별로 따로 작성한다는 가정으로 작성되어 있습니다.

     

    docker-compose.yaml 파일을 생성하고 편집합니다. docker-compose.yaml 파일은 npm 디렉터리 내부에 존재하는 기준으로 volumes를 작성했기에 앞으로 따라 하시는 부분에 주의가 필요합니다.

     

    # vi로 편집하기
    mkdir npm && touch npm/docker-compose.yaml && vi npm/docker-compose.yaml
    
    # nano로 편집하기
    mkdir npm && touch npm/docker-compose.yaml && nano npm/docker-compose.yaml

     

    # 레퍼런스: https://nginxproxymanager.com/setup/#running-the-app
    services:
      npm:
        image: 'jc21/nginx-proxy-manager:latest'
        # container_name: NPM-TEST
        container_name: <컨테이너 이름>
        restart: unless-stopped
        # ports가 아님에 유의하세요.
        expose:
          - 80
          - 81
          - 443
        environment:
          # PUID/PGID의 경우, id -u, id -g 명령어로 확인 가능. 오라클 클라우드는 기본 1001.
          # root, 혹은 root 권한이 있는 계정이라면 권한 문제를 해결할 수 있기에 굳이 필요는 없습니다.
          # - PUID=1001
          # - PGID=1001
          - PUID=<PUID>
          - PGID=<PGID>
          - TZ=Asia/Seoul
        volumes:
          - ./data:/data
          - ./letsencrypt:/etc/letsencrypt
        # 호스트에 프로그램이 실행되고 접근해야한다면, extra_hosts 부분의 주석을 삭제하세요.
        # extra_hosts:
        #   - host.docker.internal=host-gateway
        # networks:
        #   - expbox
        networks:
          - <네트워크 이름>
    
    # networks:
    #   expbox:
    #     external: true
    networks:
      <네트워크 이름>:
        external: true

     

    이제 NginxProxyManager의 준비가 끝났습니다. 아래의 명령어로 컨테이너를 실행시킵니다.

     

    docker compose -f './npm/docker-compose.yaml' up -d

     

    여기까지 잘 따라오셨다면 NginxProxyManager가 실행되기까지 약 1분 정도 지난 뒤, NPM 주소로 접속해 보면 아래와 같이 뜰 겁니다. NPM의 로그인 화면이 나옵니다.

     

     

    기본 어드민 계정은 아래와 같습니다. 꼭 변경해 주세요.

    Email: admin@example.com
    Password: changeme

     

    아직 NPM의 리버스 프록시를 거치는 게 아니라서 나중에 전체적으로 수정할 예정입니다. 이대로 쓰시면 안 돼요.

     

    6. Authelia의 docker-compose.yaml 작성 및 실행

    주의) 이 글에서는 docker-compose.yaml 파일을 각 서비스별로 따로 작성한다는 가정으로 작성되어 있습니다.

     

    docker-compose.yaml 파일을 생성하고 편집합니다. docker-compose.yaml 파일은 Authelia 디렉터리 내부에 존재하는 기준으로 volumes를 작성했기에 앞으로 따라 하시는 부분에 주의가 필요합니다.

     

    # vi로 편집하기
    mkdir -p authelia/config && touch authelia/docker-compose.yaml && vi authelia/docker-compose.yaml
    
    # nano로 편집하기
    mkdir -p authelia/config && touch authelia/docker-compose.yaml && nano authelia/docker-compose.yaml

     

    # 레퍼런스: https://svrforum.com/svr/290217
    
    services:
      authelia:
        image: 'authelia/authelia:latest'
        # container_name: Authelia
        container_name: <컨테이너 이름>
        # ports가 아닌 expose임에 주의!
        expose:
          - 9091
        volumes:
          - ./config:/config
        environment:
          # PUID/PGID의 경우, id -u, id -g 명령어로 확인 가능. 오라클 클라우드는 기본 1001.
          # root, 혹은 root 권한이 있는 계정이라면 권한 문제를 해결할 수 있기에 굳이 필요는 없습니다.
          # - PUID=1001
          # - PGID=1001
          - PUID=<PUID>
          - PGID=<PGID>
          - TZ=Asia/Seoul
        # networks:
        #   - expbox
        networks:
          - <네트워크 이름>
    
    # networks:
    #   expbox:
    #     external: true
    networks:
      <네트워크 이름>:
        external: true

     

    이렇게 작성이 완료되었다고 해도 설정 파일이 없기에 제대로 작동되지 않습니다. 한 번 실행하고 나오는 configuration.yml 파일을 수정해도 되지만 작성한 뒤 사용해도 큰 문제가 없습니다. 많이 길어서 접어놨습니다.

     

    # vi로 편집하기
    touch authelia/config/configuration.yml && vi authelia/config/configuration.yml
    
    # nano로 편집하기
    touch authelia/config/configuration.yml && nano authelia/config/configuration.yml

     

    더보기
    # 레퍼런스: https://svrforum.com/svr/290217
    
    # yamllint disable rule:comments-indentation
    ---
    ###############################################################################
    #                           Authelia Configuration                            #
    ###############################################################################
    
    theme: dark #light/dark
    # jwt_secret: 1234567890abcdefghifjkl
    jwt_secret: <jwt 토큰 키> #any text or number you want to add here to create jwt Token
    
    # default_redirection_url: https://auth.example.net/
    default_redirection_url: https://<authelia 접속 도메인> #where to redirect for a non-existent URL
    
    server:
      host: 0.0.0.0
      port: 9091
      path: ""
      read_buffer_size: 4096
      write_buffer_size: 4096
      enable_pprof: false
      enable_expvars: false
      disable_healthcheck: false
      tls:
        key: ""
        certificate: ""
    
    log:
      level: 'debug'
    
    
    totp:
      # issuer: auth.example.net
      issuer: <authelia 접속 도메인> #your authelia top-level domain
      period: 30
      skew: 1
    
    authentication_backend:
      disable_reset_password: false
      refresh_interval: 5m
      file:
        path: /config/users_database.yml #this is where your authorized users are stored
        password:
          algorithm: argon2id
          iterations: 3
          key_length: 32
          salt_length: 16
          memory: 65536
          parallelism: 4
    
    # 주로 이 부분을 수정하면 됩니다.
    access_control:
      default_policy: deny
      rules:
        ## bypass rule
        # Authelia 도메인은 바이패스하면 됩니다.
        - domain: 
            # - "auth.example.net" #This should be your authentication URL
            - "<authelia 접속 도메인>"
          policy: bypass
        # - domain: "service1.example.net" #example subdomain to protect
        #   policy: one_factor
        #    - domain: "*.yourdomain.com" #example to protect all subdomains under top-level domain
        #      policy: one_factor
        #add or remove additional subdomains as necessary. currenlty only supports ONE top-level domain
        #any time you add a new subdomain, you will need to restart the Authelia container to recognize the new settings/rules
    
    session:
      # name: authelia_session
      # secret: unsecure_session_secret
      name: <원하는 session 이름>
      secret: <세션 토큰> #any text or number you want to add here to create jwt Token
      expiration: 3600  # 1 hour
      inactivity: 300  # 5 minutes
      # domain: example.net
      domain: <루트 도메인>  # Should match whatever your root protected domain is
    
    regulation:
      max_retries: 3
      find_time: 10m
      ban_time: 12h
    
    storage:
      local:
        path: /config/db.sqlite3 #this is your databse. You could use a mysql database if you wanted, but we're going to use this one.
      # encryption_key: 1234567890abcdefgdfasdfasdf
      encryption_key: <DB 비밀 키>
    
    notifier:
      disable_startup_check: true #true/false
      smtp:
        username: youremail@gmail.com #your email address
        password: Y0uRp@55W0rD! #your email password
        host: smtp.gmail.com #email smtp server
        port: 587 #email smtp port
        sender: youremail@gmail.com
        identifier: localhost
        subject: "[Authelia] {title}" #email subject
        startup_check_address: youremail@gmail.com
        disable_require_tls: false
        disable_html_emails: false
        tls:
          skip_verify: false
          minimum_version: TLS1.2

     

    비밀 키, 토큰의 경우 랜덤 문자열 생성기를 구글링 하셔서 적당히 50자 정도로 붙여 넣으시면 됩니다.

     

    # 100자 생성
    cat /dev/urandom | tr -dc 'A-Za-z0-9' | head -c 100

     

    혹은 위의 명령어를 입력하여 랜덤으로 생성하셔도 됩니다.

     

    또한 Authelia에서 사용할 유저 정보를 저장해 놓은 users_database.yml 파일이 필요합니다. 파일 작성하기 전, 비밀번호의 해시 값이 필요합니다. 요즘은 평문으로 비밀번호를 저장하는 것이 위험하기에 이렇게 단방향 해시값으로 비밀번호를 저장하는 경우가 많습니다. 아래의 사이트에서 생성할 수 있습니다.

     

    https://argon2.online/

     

    모든 부분을 이미지와 같게 만든 후, Salt의 톱니바퀴를 누르고 Plain Test Input에 비밀번호를 입력하면 비밀번호의 해시값을 받을 수 있습니다.

     

    하지만 아무래도 비밀번호라서 저런 외부 서비스보다는 로컬에서 해시값을 생성하는 게 더 안전할 수 있습니다. 따라서 docker에 명령어를 넣어서 해시값을 생성할 겁니다. 아래의 명령어를 사용하면 쉽게 생성이 가능합니다.

     

    # docker run --rm authelia/authelia:latest authelia crypto hash generate argon2 -v argon2id --password "authelia"
    docker run --rm authelia/authelia:latest authelia crypto hash generate argon2 -v argon2id --password "<원하는 비밀번호>"

     

    아래처럼 $argon2id$ 로 시작하는 문자열을 확인했다면, 복사해서 아래의 user_database.yml의 password 부분에 붙여 넣으시면 됩니다.

     

     

    이제 users_database.yml 파일을 작성합니다.

     

    # vi로 편집하기
    touch authelia/config/users_database.yml && vi authelia/config/users_database.yml
    
    # nano로 편집하기
    touch authelia/config/users_database.yml && nano authelia/config/users_database.yml

     

    # yamllint disable rule:line-length
    ---
    ###############################################################
    #                         Users Database                      #
    ###############################################################
    
    # This file can be used if you do not have an LDAP set up.
    
    # users:
    #   expbox77:
    #     disaled: false
    #     displayname: "Expbox"
    #     password: "$argon2id$v=19$m=65536,t=3,p=4$SUufn7Ttooc8T157AMBl5g$8fZ6BUJiabsSDRomsBNVGUYyjoNbS5hU+OhJQY0LkEQ" # Password is 'authelia'
    #     email: expbox77@gmail.com
    #     groups:
    #       - admins
    #       - dev
    
    users:
      <사용자 ID>:
        disaled: false
        displayname: <닉네임>
        password: <argon2id로 생성한 비밀번호 해시 값>
        email: <이메일>
        groups: # 그룹
          - admins
          - dev
    
            
    # yamllint enable rule:line-length

     

    이제 Authelia의 준비가 끝났습니다. 아래의 명령어로 컨테이너를 실행시킵니다.

     

    docker compose -f './authelia/docker-compose.yaml' up -d

     

    여기까지 설정이 잘 되었다면 Authelia가 실행되고 조금 뒤, Authelia 주소로 접속해 보면 아래와 같이 로그인 창이 뜹니다.

     

    로그인 전

     

    비밀번호 해시값을 잘 넣었다면 아래와 같이 인증됨 아이콘이 뜨면서, "안녕하세요 <닉네임>"이 뜹니다.

     

    로그인 후

     

    아직 NPM의 리버스 프록시를 거치는 게 아니라서 나중에 전체적으로 수정할 예정입니다. 이대로 쓰시면 안 돼요.

     

    참고)

    https://www.authelia.com/configuration/prologue/introduction/

    https://www.authelia.com/reference/prologue/introduction/

    위의 공식 문서를 확인해 보면 configuration.yml, users_database.yml 파일의 고급 설정이 가능합니다.

     

    7. NginxProxyManager의 Authelia, NPM 매니지먼트 페이지 설정

    이제 본격적인 설정 부분입니다. cloudflared, NPM, Authelia의 서비스를 모두 띄웠으나 현재는 NPM을 통하는 게 아닌 Cloudflare Tunnel을 통해서 서비스가 외부와 통신되는 형태입니다. 이 상태에서 Authelia의 인증 서비스를 이용하려면 Cloudflare에서 Application으로 등록하는 등의 복잡한 과정을 거쳐야 합니다.

     

    하지만 NPM을 사용하면 Authelia의 인증 서비스를 쉽게 사용할 수 있습니다. 보기에는 복잡할 수 있으나 프록시 호스트 설정 후 고급 설정 중 일부를 자신에게 맞게 바꾸고 붙여 넣기만 하면 끝나는 작업입니다. 먼저 NPM에 NPM 매니지먼트 페이지, Authelia를 등록하는 작업부터 하겠습니다.

     

    앞서 4번에서 진행했던 NPM 주소로 접속, Host - Proxy Hosts - Add Proxy Host를 순서대로 눌러 서비스를 지정합니다.

     

    NPM에서의 NPM 매니지먼트 페이지 설정

     

    먼저 NPM 매니지먼트 페이지부터 설정합니다. 위의 그림과 같이 설정하시면 됩니다. Domain Names에는 설정했던 NPM의 주소, Forward Hostname은 npm(NPM 컨테이너의 이름 혹은 서비스 이름), Forward Port에는 81(expose 포트)로 설정하면 됩니다. Websockets Support는 켜두는 게 좋다고 들어서 켰습니다. 딱히 큰 의미는 없습니다.

     

    NPM에서의 Authelia 설정

     

    그다음은 Authelia 설정입니다. NPM과 비슷하지만 Advanced 설정을 거쳐야 합니다. Domain Names에는 설정했던 Authelia의 주소, Forward Hostname은 authelia(Authelia 컨테이너의 이름 혹은 서비스 이름), Forward Port에는 9091(expose 포트)로 설정하면 됩니다. 여기서 Save를 눌러 나오지 말고 Advanced를 누릅니다.

     

    NPM에서의 Authelia Advanced 설정

     

    빈칸에 아래의 내용을 자신에 맞게 수정 후 붙여 넣습니다. 맨 아래의 set_real_ip_from은 3번에서 생성 후 확인한 네트워크의 서브넷을 입력하면 됩니다. 길어서 접어놨습니다.

     

    더보기
    # 참조: https://svrforum.com/svr/290217
    
    location / {
    set $upstream_authelia http://authelia:9091; # This example assumes a Docker deployment 
    proxy_pass $upstream_authelia;
    client_body_buffer_size 128k;
    
    #Timeout if the real server is dead
    proxy_next_upstream error timeout invalid_header http_500 http_502 http_503;
    
    # Advanced Proxy Config
    send_timeout 5m;
    proxy_read_timeout 360;
    proxy_send_timeout 360;
    proxy_connect_timeout 360;
    
    # Basic Proxy Config
    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 X-Forwarded-Host $http_host;
    proxy_set_header X-Forwarded-Uri $request_uri;
    proxy_set_header X-Forwarded-Ssl on;
    proxy_redirect  http://  $scheme://;
    proxy_http_version 1.1;
    proxy_set_header Connection "";
    proxy_cache_bypass $cookie_session;
    proxy_no_cache $cookie_session;
    proxy_buffers 64 256k;
    
    # If behind a reverse proxy, forwards the correct IP, assumes you're using Cloudflare. Adjust IP for your Docker network.
    # set_real_ip_from 172.18.0.0/16;
    set_real_ip_from <3번에서 확인한 도커 Network 서브넷>;
    real_ip_header CF-Connecting-IP;
    real_ip_recursive on;
    }

     

     

    8. cloudflared의 config.yaml 파일 수정 및 컨테이너 재시작

    NginxProxyManager의 프록시 설정이 끝났다면 cloudflared의 config.yaml 파일의 모든 서비스를 NPM 80 포트로 수정하고 재시작하면 됩니다.

     

    # vi로 편집하기
    touch cloudflared/data/config.yaml && vi cloudflared/data/config.yaml
    
    # nano로 편집하기
    touch cloudflared/data/config.yaml && nano cloudflared/data/config.yaml

     

    << 생략 >>
      
      # NPM
      - hostname: <NPM 서브 도메인>
        service: https://npm:80 # NPM의 80포트
        originRequest:
          noTLSVerify: true
          
      - hostname: <Authelia 서브 도메인>
        service: https://npm:80 # NPM의 80포트
        originRequest:
          noTLSVerify: true
    
      - service: http_status:404
    
    loglevel: info

     

    위처럼 수정이 완료되었으면, docker compose를 재시작하면 됩니다.

     

    docker compose -f './cloudflared/docker-compose.yaml' restart

     

    이제는 주소로 접속하게 되면 Cloudflare Tunnel -> NPM 순으로 연결됩니다. 기본적인 세팅이 완료되었습니다.

     

    9. 다른 컨테이너(서비스) 연결하기 

    이제는 NPM에서 Authelia를 적용할 다른 서비스들을 설정할 차례입니다. 나중에 부록으로 실습까지 할 예정이니 지금은 이렇게 하면 되는구나 하면서 읽어보시면 될 것 같습니다. 순서는 글에 적힌 대로 하셔도 되고 각 부분마다 순서를 다르게 하셔도 상관없습니다. 처음 Cloudflare에 도메인 주소를 추가하는 것은 내용이 중복되니 생략하겠습니다.

     

    1) 사용할 주소를 Cloudflare에 CNAME으로 등록합니다.

    --> 2번을 참고하셔서 등록하시면 됩니다.

     

    2) cloudflared의 config.yaml 파일에 사용할 주소를 추가합니다.

    # vi로 편집하기
    touch cloudflared/data/config.yaml && vi cloudflared/data/config.yaml
    
    # nano로 편집하기
    touch cloudflared/data/config.yaml && nano cloudflared/data/config.yaml

     

    << 생략 >>
    
      # 와일드카드
      # - hostname: *.example.net
      #   service: https://npm:80 # NPM의 80포트
      #   originRequest:
      #     noTLSVerify: true
    
      # 서비스1
      - hostname: <추가할 서비스 서브 도메인>
        service: https://npm:80 # NPM의 80포트
        originRequest:
          noTLSVerify: true
    
      - service: http_status:404
    
    loglevel: info

     

    위처럼 도메인의 추가가 완료되었으면, docker compose를 재시작하면 됩니다.

     

    docker compose -f './cloudflared/docker-compose.yaml' restart

     

    3) NPM에서 사용할 주소와 서비스를 등록합니다.

    NPM에서의 각 서비스 설정

     

    Domain Names에는 설정할 주소, Forward Hostname은 컨테이너의 Forward Hostname은 도커 컨테이너의 이름 혹은 서비스 이름, Forward Port에는 expose 포트로 설정하면 됩니다. 여기서 Save를 눌러 나오지 말고 Advanced를 누릅니다.

     

    어떻게 입력해야 할지 헷갈린다면 아래의 접은 글을 확인해 보세요.

     

    더보기

    위의 이미지는 아래의 docker-compose.yaml 예시를 보면 어떤 것인지 느낌이 오실 것으로 보입니다.

     

    # Nginx 웹 서버 예시
    services:
      web-app: # 서비스 이름
        image: nginx:latest
        container_name: my_nginx_web # 컨테이너 이름
        # ports:  # ports가 아닌 expose 사용
        #   - 80:80
        expose:
          - 80 # expose 포트
    
    << 생략 >>

     

    docker run으로 실행한 예시

     

    # docker run -d --network expbox-test --expose 80 --name nginx-web nginx:latest
    docker run -d --rm --network <네트워크 이름> --expose <expose 포트> --name <컨테이너 이름> <도커 이미지>

     

    NPM에서의 각 서비스의 Advanced 설정

     

    Advanced의 빈칸에 아래의 내용을 자신에 맞게 수정 후 붙여 넣습니다. 중간의 error_page 401 =302 부분에는 Authelia의 주소를 입력하고 맨 아래의 set_real_ip_from은 3번에서 확인한 네트워크의 서브넷을 입력하면 됩니다. 길어서 접어놨습니다.

     

    더보기
    # 참조: https://svrforum.com/svr/290217
    
    location /authelia {
    internal;
    set $upstream_authelia http://authelia:9091/api/verify;
    proxy_pass_request_body off;
    proxy_pass $upstream_authelia;    
    proxy_set_header Content-Length "";
    
    # Timeout if the real server is dead
    proxy_next_upstream error timeout invalid_header http_500 http_502 http_503;
    client_body_buffer_size 128k;
    proxy_set_header Host $host;
    # proxy_set_header X-Original-URL $scheme://$http_host$request_uri;
    proxy_set_header X-Original-URL https://$http_host$request_uri;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $remote_addr; 
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header X-Forwarded-Host $http_host;
    proxy_set_header X-Forwarded-Uri $request_uri;
    proxy_set_header X-Forwarded-Ssl on;
    proxy_redirect  http://  $scheme://;
    proxy_http_version 1.1;
    proxy_set_header Connection "";
    proxy_cache_bypass $cookie_session;
    proxy_no_cache $cookie_session;
    proxy_buffers 4 32k;
    
    send_timeout 5m;
    proxy_read_timeout 240;
    proxy_send_timeout 240;
    proxy_connect_timeout 240;
    }
    
    location / {
    set $upstream_app $forward_scheme://$server:$port;
    proxy_pass $upstream_app;
    
    auth_request /authelia;
    auth_request_set $target_url https://$http_host$request_uri;
    auth_request_set $user $upstream_http_remote_user;
    auth_request_set $email $upstream_http_remote_email;
    auth_request_set $groups $upstream_http_remote_groups;
    proxy_set_header Remote-User $user;
    proxy_set_header Remote-Email $email;
    proxy_set_header Remote-Groups $groups;
    
    # error_page 401 =302 https://auth.exmaple.net/?rd=$target_url;
    error_page 401 =302 https://<Authelia 주소>/?rd=$target_url;
    
    
    client_body_buffer_size 128k;
    
    proxy_next_upstream error timeout invalid_header http_500 http_502 http_503;
    
    send_timeout 5m;
    proxy_read_timeout 360;
    proxy_send_timeout 360;
    proxy_connect_timeout 360;
    
    proxy_set_header Host $host;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection upgrade;
    proxy_set_header Accept-Encoding gzip;
    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 X-Forwarded-Host $http_host;
    proxy_set_header X-Forwarded-Uri $request_uri;
    proxy_set_header X-Forwarded-Ssl on;
    proxy_redirect  http://  $scheme://;
    proxy_http_version 1.1;
    proxy_set_header Connection "";
    proxy_cache_bypass $cookie_session;
    proxy_no_cache $cookie_session;
    proxy_buffers 64 256k;
    
    # set_real_ip_from 172.18.0.0/16;
    set_real_ip_from <3번에서 확인한 도커 Network 서브넷>;
    real_ip_header CF-Connecting-IP;
    real_ip_recursive on;
    }

     

     

    4) Authelia의 configuration.yml 파일에 주소와 인증 방법을 추가합니다.

    configuration.yml 파일의 access_control: 부분에서 Authelia로 인증이 필요한 주소와 인증 방법을 추가합니다.

     

    # vi로 편집하기
    touch authelia/config/configuration.yml && vi authelia/config/configuration.yml
    
    # nano로 편집하기
    touch authelia/config/configuration.yml && nano authelia/config/configuration.yml

     

    << 생략 >>
    
    access_control:
      default_policy: deny
      rules:
        ## bypass rule
        - domain: 
            - "auth.example.net" #This should be your authentication URL
          policy: bypass
        # 추가한 부분
        - domain: "service1.exmaple.net" #example subdomain to protect
          policy: one_factor
        - domain: "service2.exmaple.net"
          policy: one_factor
    
    << 생략 >>

     

    Authelia 컨테이너를 재시작합니다.

     

    docker compose -f './authelia/docker-compose.yaml' restart

     

    모든 세팅이 마무리되었고 접속해 보면 정상적으로 작동하는 것을 확인할 수 있습니다. 만약 제대로 작동하지 않는다고 생각되면 시크릿 창에서 시도해 보시길 바랍니다. 그래도 안되면 조금 기다려보시거나 제대로 수정/추가했는지 천천히 살펴보시길 바랍니다.

     

    부록 1. 파일 및 디렉터리 경로

    계속 명령어로만 편집해서 파일과 디렉터리가 어떻게 되어있는지 감이 잘 안 오시는 분들을 위해서 준비했습니다. VS Code로 연결해서 확인해 봤습니다.

     

     

    cloudflared_npm_authelia 디렉터리가 이번 글을 위해서 만든 디렉터리입니다. 이 내부에서 모든 디렉터리를 생성하고 편집했다고 보시면 될 것 같아요.

     

    부록 2. 간단한 웹 서버로 실습

    이번에는 nginx 웹 서버 하나를 띄워서 Authelia의 인증을 거쳐서 접속하도록 할 겁니다. 아래의 명령어를 입력하면 nginx 웹 서버 컨테이너가 실행됩니다. 참고로 이때 SSH 연결을 계속 연결해두셔야 합니다.

     

    1) 도커 이미지를 다운로드하고 포그라운드에서 컨테이너를 실행합니다.

    아래의 명령어를 입력하고 쉘을 계속 띄워둡니다.

    # docker run --rm --network expbox-test --expose 80 --name nginx-web nginx:latest
    docker run --rm --network <네트워크 이름> --expose 80 --name nginx-web nginx:latest

     

    2) Cloudflare에 주소 추가

    https://dash.cloudflare.com/ - 구입한 도메인 - 왼쪽 사이드바에서 DNS를 순서대로 눌러 접속합니다.

     

     

    레코드 추가를 눌러 CNAME 유형, 이름에 원하는 서브도메인, 대상을 <UUID>.cfargotunnel.com으로 지정하고 저장합니다.

     

    3) cloudflared의 config.yaml 파일에 사용할 주소를 추가합니다.

    # vi로 편집하기
    touch cloudflared/data/config.yaml && vi cloudflared/data/config.yaml
    
    # nano로 편집하기
    touch cloudflared/data/config.yaml && nano cloudflared/data/config.yaml

     

    << 생략 >>
    
    #  - hostname: web.example.net
      - hostname: <사용할 도메인>
        service: http://npm:80 # NPM 80 포트 
        originRequest:
          noTLSVerify: true
    
      - service: http_status:404
    
    loglevel: info

     

    4) NPM에서 사용할 주소를 등록합니다.

     

    Domain Names는 웹 서버에 연결할 도메인으로 지정해 주면 됩니다. 여기서 Save를 눌러 나오지 말고 Advanced를 누릅니다.

     

    Advanced의 빈칸에 아래의 내용을 자신에 맞게 수정 후 붙여 넣습니다. 중간의 error_page 401 =302 부분에는 Authelia의 주소를 입력하고 맨 아래의 set_real_ip_from은 3번에서 확인한 네트워크의 서브넷을 입력하면 됩니다. 일부 생략되어 있습니다.

     

    << 생략 >>
    
    # error_page 401 =302 https://auth.exmaple.net/?rd=$target_url;
    error_page 401 =302 https://<Authelia 주소>/?rd=$target_url;
    
    << 생략 >>
    
    # set_real_ip_from 172.18.0.0/16;
    set_real_ip_from <3번에서 확인한 도커 Network 서브넷>;
    real_ip_header CF-Connecting-IP;
    real_ip_recursive on;
    }

     

    4) Authelia의 configuration.yml 파일에 주소와 인증 방법을 추가합니다.

    # vi로 편집하기
    touch authelia/config/configuration.yml && vi authelia/config/configuration.yml
    
    # nano로 편집하기
    touch authelia/config/configuration.yml && nano authelia/config/configuration.yml

     

    일부 생략했습니다. access_control: 부분에서 도메인을 추가하면 됩니다.

     

    << 생략 >>
    
    access_control:
      default_policy: deny
      rules:
        ## bypass rule
        - domain: 
            - "auth.example.net" #This should be your authentication URL
          policy: bypass
        - domain: "web.exmaple.net"
          policy: one_factor
    
    << 생략 >>

     

    Authelia 컨테이너를 재시작합니다.

     

     

    docker compose -f './authelia/docker-compose.yaml' restart

     

    5) 웹 페이지에 접속해서 확인합니다.

    접속 시 보게되는 화면

     

    접속 시 https://Authelia의 주소/?rd=https%3A%2F%2F접속하려는 주소%2F 링크로 리다이렉트 되며 위의 이미지와 같은 Authelia 로그인 페이지를 볼 수 있습니다.

     

    로그인 이후 리다이렉트 된 링크에서 보게되는 화면

     

    그리고 로그인하게 되면 원하는 서브도메인으로 리다이렉트 되면서 Nginx의 기본 화면을 볼 수 있습니다.

     

     

    이렇게 nginx 도커 컨테이너에서도 제대로 로그가 찍힙니다.

     

    예시로 띄워둔 웹 서버 도커 컨테이너는 Ctrl + C(윈도우) 혹은 control + C(맥)으로 종료할 수 있습니다.


     

    정말 자세하게 가이드 글을 적었는데 시간을 꽤 많이 소요하게 되었습니다. 쓰다가 고치고 더하고 날 지나니까 부족한 게 보이고.. 고생이 이만저만이 아니었습니다. 이게 어쩔 수 없는 게 이렇게 한 번 구축해 놓으면 문제 생길 때까지 그대로 놔두고 사용하기 때문에 나중 가면 어떻게 설정했는지 까먹을 수밖에 없습니다. 그래서 제가 설정 방법을 잊어먹었을 때, 이 글을 보면서 다시 제대로 설정할 수 있도록 하려면 최대한 자세하게 적을 수밖에 없더라고요.

     

    그래도 이렇게 다 적고 나니까 뿌듯합니다. 사실 절반 정도 적고나니까 좀 끊어서 글을 나눌까 싶기도 했는데 끊을 부분을 못잡아서 그냥 길게 적었습니다.

     


    레퍼런스

    https://svrforum.com/svr/290217

    https://www.reddit.com/r/selfhosted/comments/wpvy8c/using_cloudflare_tunnel_with_nginx_proxy_manager/

Designed by Tistory.