🌍 네트워크

Tailscale

Tailscale

WireGuard 기반 메시 VPN으로 Zero Trust 네트워크 구축. MagicDNS, ACL, DERP 릴레이로 팀 협업 최적화.

📖 상세 설명

Tailscale은 WireGuard 프로토콜 기반의 메시 VPN 서비스로, 복잡한 네트워크 설정 없이 안전한 팀 협업 환경을 구축할 수 있습니다. 기존 VPN이 중앙 서버를 통해 모든 트래픽을 라우팅하는 방식과 달리, Tailscale은 P2P(Peer-to-Peer) 메시 네트워크를 구성하여 기기 간 직접 연결을 지원합니다.

Tailscale의 핵심은 Zero Trust 보안 모델입니다. 모든 연결은 WireGuard 암호화로 보호되며, ACL(Access Control List)을 통해 세밀한 접근 제어가 가능합니다. Google, Microsoft, GitHub 등의 SSO(Single Sign-On)와 연동하여 기업 환경에서도 쉽게 도입할 수 있습니다.

MagicDNS는 Tailscale의 강력한 기능 중 하나입니다. Tailnet 내의 모든 기기에 자동으로 DNS 이름을 부여하여 IP 주소를 외울 필요 없이 'macbook.tail12345.ts.net' 같은 이름으로 접속할 수 있습니다. 이는 동적 IP 환경에서도 안정적인 연결을 보장합니다.

DERP(Designated Encrypted Relay for Packets) 릴레이는 NAT 환경에서 직접 P2P 연결이 불가능할 때 활성화됩니다. 전 세계에 분산된 DERP 서버가 암호화된 트래픽을 중계하여 방화벽 뒤에 있는 기기도 안전하게 연결합니다. 연결이 수립되면 자동으로 직접 연결로 전환되어 최적의 성능을 유지합니다.

💻 코드 예제

# Tailscale 설치 (Linux)
curl -fsSL https://tailscale.com/install.sh | sh

# Tailscale 시작 및 인증
sudo tailscale up

# 현재 상태 확인
tailscale status

# 네트워크 내 기기 목록
tailscale status --json | jq '.Peer'

# MagicDNS로 SSH 접속
ssh user@devserver.tail12345.ts.net

# 특정 포트만 노출 (Exit Node)
sudo tailscale up --advertise-exit-node

# ACL 태그 적용
sudo tailscale up --advertise-tags=tag:server

# Tailscale IP 확인
tailscale ip -4  # IPv4 주소
tailscale ip -6  # IPv6 주소

# 연결 상태 디버깅
tailscale netcheck
tailscale ping devserver

# 파일 공유 (Taildrop)
tailscale file cp ./report.pdf user@macbook:

# 연결 해제
sudo tailscale down

# 로그아웃 (인증 해제)
sudo tailscale logout
// Tailscale API를 활용한 기기 관리 (Node.js)
const axios = require('axios');

const TAILSCALE_API_KEY = process.env.TAILSCALE_API_KEY;
const TAILNET = 'your-tailnet.ts.net';

const tailscaleApi = axios.create({
    baseURL: 'https://api.tailscale.com/api/v2',
    headers: {
        'Authorization': `Bearer ${TAILSCALE_API_KEY}`
    }
});

// 모든 기기 목록 조회
async function getDevices() {
    const { data } = await tailscaleApi.get(`/tailnet/${TAILNET}/devices`);
    return data.devices.map(device => ({
        name: device.name,
        addresses: device.addresses,
        os: device.os,
        lastSeen: device.lastSeen,
        online: device.online
    }));
}

// 기기 승인 (관리자 기능)
async function authorizeDevice(deviceId) {
    await tailscaleApi.post(`/device/${deviceId}/authorized`, {
        authorized: true
    });
    console.log(`Device ${deviceId} authorized`);
}

// 기기에 태그 할당
async function setDeviceTags(deviceId, tags) {
    await tailscaleApi.post(`/device/${deviceId}/tags`, {
        tags: tags  // ["tag:server", "tag:production"]
    });
}

// DNS 레코드 조회
async function getDNSRecords() {
    const { data } = await tailscaleApi.get(`/tailnet/${TAILNET}/dns/nameservers`);
    return data;
}

// 사용 예시
(async () => {
    const devices = await getDevices();
    devices.filter(d => d.online).forEach(d => {
        console.log(`${d.name}: ${d.addresses[0]}`);
    });
})();
package main

import (
    "context"
    "fmt"
    "log"
    "net"
    "net/http"

    "tailscale.com/client/tailscale"
    "tailscale.com/tsnet"
)

// tsnet을 사용한 Tailscale 임베디드 서버
func main() {
    // Tailscale 서버 인스턴스 생성
    srv := &tsnet.Server{
        Hostname: "my-app-server",
        AuthKey:  "tskey-auth-xxxxx", // 환경변수 권장
    }
    defer srv.Close()

    // Tailscale 네트워크에서 리스닝
    ln, err := srv.Listen("tcp", ":8080")
    if err != nil {
        log.Fatal(err)
    }
    defer ln.Close()

    // 현재 Tailscale 상태 확인
    lc, err := srv.LocalClient()
    if err != nil {
        log.Fatal(err)
    }

    status, err := lc.Status(context.Background())
    if err != nil {
        log.Fatal(err)
    }

    fmt.Printf("Tailscale IP: %v\n", status.TailscaleIPs)
    fmt.Printf("MagicDNS name: %s\n", status.Self.DNSName)

    // HTTP 서버 실행 (Tailscale 네트워크 전용)
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        // 요청자의 Tailscale 정보 확인
        who, err := lc.WhoIs(r.Context(), r.RemoteAddr)
        if err != nil {
            http.Error(w, "Unauthorized", 401)
            return
        }
        fmt.Fprintf(w, "Hello, %s!\n", who.UserProfile.LoginName)
    })

    log.Printf("Server running on Tailscale network...")
    log.Fatal(http.Serve(ln, nil))
}

🗣️ 실무에서 이렇게 말하세요

💬 원격 근무 환경 구축 논의에서
"VPN 장비 도입 대신 Tailscale을 검토해보죠. WireGuard 기반이라 속도가 빠르고, 별도 서버 없이 메시 네트워크로 구성되니까 SPOF(Single Point of Failure) 걱정도 없습니다. ACL로 개발자는 개발 서버만, 운영팀은 프로덕션만 접근하게 세분화할 수 있어요."
💬 인프라 팀 미팅에서
"NAT 뒤에 있는 사내 서버도 DERP 릴레이가 알아서 연결해주니까 포트포워딩이나 방화벽 설정 건드릴 필요가 없습니다. 그리고 MagicDNS 덕분에 IP 변경돼도 'db-primary.tail12345.ts.net'으로 접속하면 됩니다. 운영 부담이 확 줄어요."
💬 보안 감사 대응 시
"Tailscale은 Zero Trust 모델을 따릅니다. 모든 트래픽은 WireGuard로 E2E 암호화되고, 기기마다 고유 인증서가 발급됩니다. SAML/OIDC로 기업 IdP와 연동하면 퇴사자 계정 비활성화 시 즉시 네트워크 접근이 차단됩니다."

⚠️ 흔한 실수 & 주의사항

Exit Node 무분별 사용

Exit Node를 활성화하면 모든 인터넷 트래픽이 해당 기기를 통과합니다. 대역폭 비용과 속도 저하를 고려해 특정 서브넷만 라우팅하거나, 필요할 때만 --exit-node 옵션을 사용하세요.

ACL 설정 없이 운영

기본 설정은 모든 기기가 서로 접근 가능합니다. 프로덕션 환경에서는 반드시 ACL을 설정하여 'tag:dev' 기기는 'tag:prod' 서버에 접근 불가 등 최소 권한 원칙을 적용하세요.

개인 계정으로 팀 네트워크 생성

개인 Gmail 계정으로 Tailnet을 만들면 퇴사 시 전체 네트워크 관리 권한이 사라집니다. 반드시 회사 도메인 계정이나 GitHub Organization으로 생성하세요.

권장 설정

SAML/OIDC SSO 연동, MFA 필수 설정, Key Expiry(90일 권장) 활성화, 서브넷 라우터를 통한 레거시 시스템 연동. Audit Log를 SIEM에 연동하여 접근 이력을 모니터링하세요.

🔗 관련 용어

📚 더 배우기