<index> / <homelab> / teleport
[ en | fr ]
┌───────────────────────┐
│                       │
│                       │
│                       │
│                       │
│                       │
│                       │
│                       │
│                       │
│                       │
│                       │
│                       │
│                       │
│                       │
│                       │
│                       │
└───────────────────────┘
Bastion: Teleport for your Homelab
~ lululufr
CONTENTS
  0  why teleport — the pitch
  1  main components — the parts
  2  the bastion principle — the idea
  3  why teleport & why a bastion — the rationale
  4  the setup — target architecture
  5  reverse proxy — nginx + certbot
  6  teleport — the server
  7  conclusion — next steps

──[ 0. Why Teleport ]──

What I like about Teleport as a "gateway" into your Homelab:
    - Strong auth with MFA and WebAuthn
    - One way in for everything (SSH, web apps, Kubernetes, databases…)
    - Centralised access management with RBAC
    - A modern, decent-looking web UI
    - Full audit of every session and action
    - Zero Trust by design

Unlike a classic VPN — often blocked everywhere — Teleport reaches all your 
resources over HTTPS-encapsulated traffic (rarely blocked), and makes cert and
auth management way less painful. And where a VPN drops you onto the whole network (real security risk), Teleport
lets you say exactly who can reach what, when, and how.
──[ 1. Main Components ]──

Teleport is split into a few services you can install together or separately, 
depending on what you need:
    Auth Service
        The heart. Handles authentication and authorisation, holds the
        user DB, issues short-lived certs, enforces access policies.

    Proxy Service
        The public entry point that routes connections to internal
        resources. Hosts the web UI, handles secure tunnelling.

    SSH Service
        SSH access to servers, with session recording, replacing the
        traditional SSH keys with certs.

    Application Service
        Access to internal web apps without touching them.

    Database Service
        Secure DB access with identity management.

These can all live on one machine for a simple setup (perfect for a Homelab), or 
spread across several servers if you need to scale.
──[ 2. The Bastion Principle ]──

For people new to Teleport and bastions: a bastion is a server that's exposed 
(not necessarily on the internet) and on which auth is strict. Most importantly, a bastion is a *controlled* way into your resources. Think
reverse proxy, but with security as the main job.
graph TD
    B{Bastion Teleport} <--> Internet
    Server_1 <--> B
    Server_2 <--> B
    Server_3 <--> B
    Web_Apps <--> B
    Databases <--> B
──[ 3. Why Teleport & Why a Bastion ]──

Plenty of solutions let you reach your Homelab from outside — open source 
(Apache Guacamole…) and closed (WALLIX, CyberArk…). But to me none is as well-built and easy to use as Teleport, for a few reasons:
    1. Modern, well-documented, active community.
    2. The Community edition is free for individuals and small teams.
    3. Native Zero Trust with no shortcuts on security.
    4. Plays well with modern tooling (Kubernetes, cloud providers…).
    5. Audit and compliance built in from day one.

About the bastion: I've often been stuck without VPN for whatever reason — 
corporate firewall, no local admin on the workstation, not my own machine,
network restrictions, you name it. A bastion lets you reach your stuff with nothing but a web browser. That works
almost anywhere.
──[ 4. The Setup ]──

Onto the implementation.

What we're aiming for is what a secure, professional-grade Homelab would look 
like. Adapt as needed. End state:
    - One main Teleport server: our secure entry point to everything else.
    - One Nginx server: reverse proxy and SSL termination.
    - Private client servers, reachable through the bastion.
    - Public servers, directly accessible on the internet.

Target architecture:
graph TD
    Internet[Internet] --> ReverseProxy[Nginx Reverse Proxy]
    ReverseProxy --> TeleportProxy[Teleport Proxy - star.domain.test]
    TeleportProxy --> SSHTargets[SSH Servers]
    TeleportProxy --> WebApps[Web Apps - pfSense, NAS, ...]

    subgraph DMZ Zone
        ReverseProxy
        TeleportProxy
    end

    subgraph Private Zone
        SSHTargets
        WebApps
    end

    subgraph Public Zone
        ReverseProxy --> Blog[Public Blog - blog.domain.test]
    end
With this kind of setup you expose each piece the way it deserves: public for 
what can be public (website, blog, public services…), private for what shouldn't
be exposed but that you still need to drive remotely from the internet, without
VPN.
    NOTE: Reverse proxy and bastion can sit on the same machine for
    simplicity. For production, split them.

──[ 5. Reverse Proxy ]──

For this demo, the goal is to expose a bastion + its hosts, plus a public blog 
that sits outside the bastion. For more services, repeat the steps and update
configs. Throughout, the example domain is `domain.test` — swap in yours. Prereqs:
    [ ] A public IP. For a Homelab you can usually request a Full-Stack
        IPv4 from your ISP (free for most); a VPS works too.
    [ ] A domain with full DNS control (OVH, Cloudflare…).
    [ ] Machines (a hypervisor like Proxmox or VMware helps).

5.1 Server prep

Create a machine (or reuse the Teleport one) and install the bits:
    # System update
    sudo apt update && sudo apt upgrade -y

    # Nginx
    sudo apt install nginx -y

    # Certbot for Let's Encrypt (with the Cloudflare DNS plugin)
    sudo apt install curl certbot python3-certbot-nginx \
      python3-certbot-dns-cloudflare -y

    # Teleport (pin a stable version)
    TELEPORT_EDITION="oss"
    TELEPORT_VERSION="17.6.0"
    curl https://cdn.teleport.dev/install.sh | bash -s \
      ${TELEPORT_VERSION?} ${TELEPORT_EDITION?}
5.2 DNS configuration

Create the public DNS records at your provider:
    *.domain.test     -> your public IP   (wildcard for Teleport)
    domain.test       -> your public IP   (apex domain)
    blog.domain.test  -> your public IP   (example public service)

5.3 TLS certificates

For wildcards, DNS-01 is the way. First, store your Cloudflare API creds:
    sudo mkdir -p /root/.secrets
    sudo nano /root/.secrets/cloudflare.ini

    # /root/.secrets/cloudflare.ini
    dns_cloudflare_api_token = your_scoped_api_token
Tip: use a scoped API *token* (Zone:DNS:Edit) over the legacy global
    API key. Same result, way less to lose if the file leaks.

Then issue the certs:
    # Lock the file down
    sudo chmod 600 /root/.secrets/cloudflare.ini

    # Wildcard certificate for Teleport
    sudo certbot certonly \
      --dns-cloudflare \
      --dns-cloudflare-credentials /root/.secrets/cloudflare.ini \
      --dns-cloudflare-propagation-seconds 30 \
      -d '*.domain.test' -d domain.test

    # Certificate for the blog
    sudo certbot certonly \
      --dns-cloudflare \
      --dns-cloudflare-credentials /root/.secrets/cloudflare.ini \
      --dns-cloudflare-propagation-seconds 30 \
      -d blog.domain.test
5.4 Nginx, the blog (public service)
    # /etc/nginx/conf.d/blog.conf

    map $http_upgrade $connection_upgrade {
        default upgrade;
        ''      close;
    }

    upstream blog_backend {
        server 192.168.1.4:80;   # private IP of your blog server
        keepalive 32;
    }

    server {
        listen 443 ssl;
        http2 on;
        server_name blog.domain.test;

        ssl_certificate     /etc/letsencrypt/live/blog.domain.test/fullchain.pem;
        ssl_certificate_key /etc/letsencrypt/live/blog.domain.test/privkey.pem;
        ssl_protocols       TLSv1.2 TLSv1.3;

        add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
        add_header X-Frame-Options DENY always;
        add_header X-Content-Type-Options nosniff always;

        client_max_body_size 100M;
        proxy_read_timeout   600s;

        location / {
            proxy_pass http://blog_backend;
            proxy_http_version 1.1;
            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 Upgrade           $http_upgrade;
            proxy_set_header Connection        $connection_upgrade;
        }
    }

    server {
        listen 80;
        listen [::]:80;
        server_name blog.domain.test;
        return 301 https://$host$request_uri;
    }
5.5 Nginx, Teleport
    # /etc/nginx/conf.d/teleport.conf

    map $http_upgrade $connection_upgrade {
        default upgrade;
        ''      close;
    }

    upstream teleport_backend {
        server 192.168.1.2:443;  # IP of your Teleport server
        keepalive 32;
    }

    server {
        listen 443 ssl;
        http2 on;
        server_name *.domain.test domain.test;

        ssl_certificate     /etc/letsencrypt/live/domain.test/fullchain.pem;
        ssl_certificate_key /etc/letsencrypt/live/domain.test/privkey.pem;
        ssl_protocols       TLSv1.2 TLSv1.3;
        add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;

        # Re-encrypt to Teleport and carry the right SNI
        proxy_ssl_server_name on;
        proxy_ssl_name        $host;

        location / {
            proxy_pass https://teleport_backend;
            proxy_http_version 1.1;
            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 Upgrade           $http_upgrade;
            proxy_set_header Connection        $connection_upgrade;

            proxy_read_timeout 600s;
            proxy_buffering    off;
        }
    }

    server {
        listen 80;
        listen [::]:80;
        server_name *.domain.test domain.test;
        return 301 https://$host$request_uri;
    }
Test and reload:
    sudo nginx -t
    sudo systemctl reload nginx
    sudo systemctl status nginx
──[ 6. Teleport ]──

Now the main Teleport server. Once installed, one main file is enough to get 
going. 6.1 Basic config
    sudo nano /etc/teleport.yaml
    # /etc/teleport.yaml
    version: v3
    teleport:
      nodename: bastion-teleport
      data_dir: /var/lib/teleport
      log:
        output: stderr
        severity: INFO
      diag_addr: "127.0.0.1:3000"   # internal diagnostics

    auth_service:
      enabled: true
      cluster_name: "domain.test"
      listen_addr: 0.0.0.0:3025
      proxy_listener_mode: multiplex
      authentication:
        type: local
        second_factor: webauthn     # security keys (YubiKey, etc.)
        webauthn:
          rp_id: domain.test
      session_recording: node-sync
      tokens:
        - "proxy,node:your-secret-token-here"

    ssh_service:
      enabled: true
      listen_addr: 0.0.0.0:3022
      x11:
        enabled: true

    proxy_service:
      enabled: true
      web_listen_addr: 0.0.0.0:443
      tunnel_listen_addr: 0.0.0.0:3024
      public_addr: domain.test:443
      acme:
        enabled: false              # TLS is handled by Nginx + Let's Encrypt
      # Trust the X-Forwarded-* headers coming from the reverse proxy
      trust_x_forwarded_for: true

    # Application Service, preferred for exposing specific web UIs
    app_service:
      enabled: true
      apps:
        - name: pfsense
          uri: https://192.168.1.1
          public_addr: "pfsense.domain.test"
          insecure_skip_verify: true
          labels:
            env: "production"
            team: "infrastructure"
        - name: nas
          uri: http://192.168.1.10:5000
          public_addr: "nas.domain.test"
          insecure_skip_verify: true
          labels:
            env: "production"
            team: "storage"
Note: `insecure_skip_verify: true` skips TLS verification to the
    backend app. Fine on a trusted LAN segment; tighten it if the path
    to the app isn't fully yours.

6.2 Start it up
    # Validate the config
    sudo teleport configure --test

    # Enable and start
    sudo systemctl enable teleport
    sudo systemctl start teleport

    # Status and logs
    sudo systemctl status teleport
    sudo journalctl -u teleport -f
6.3 First admin
    sudo tctl users add admin --roles=editor,access \
      --logins=root,admin,john
6.4 Smoke test

Once configured you should be able to:
    1. Hit the web UI at https://domain.test
    2. See your apps under the Applications tab
    3. SSH into the servers you added
    4. Open web apps via their configured subdomains

──[ 7. Conclusion ]──

Your Homelab infra is up. Plenty of room to grow from here, but this is a solid 
base for hosting your services, reachable securely from anywhere, with nothing
but a browser. References:
    [0] Teleport documentation
        https://goteleport.com/docs/
    [1] Configuration examples
        https://github.com/gravitational/teleport/tree/master/examples
    [2] Teleport community
        https://goteleport.com/community/
    [3] Security best practices
        https://goteleport.com/docs/admin-guides/access-controls/

──[ EOF ]──