Zero Trust Network Access (ZTNA)
Understanding Teleport and Zero Trust Access
SSH, databases, and apps through one secure gateway. Certificate-based, no passwords!
This guide covers SSH Access (highlighted)
Traditional SSH:
With Teleport: Login once with SSO, get a short-lived certificate, access any server, every session is recorded!
| Phase | Time | Difficulty |
|---|---|---|
| Understanding Concepts | 15-20 min | Reading |
| Teleport Installation | 30-45 min | Easy |
| Adding SSH Nodes | 20-30 min | Easy |
| Authentik SSO Setup | 30-45 min | Medium |
| Roles & Recording | 20-30 min | Medium |
Total: 3-4 hours
Understanding Teleport architecture
Three services work together to provide secure access
| Traditional SSH Keys | Teleport Certificates |
|---|---|
| Never expire | Expire in hours (configurable) |
| Must be copied to each server | Work on any enrolled server |
| Hard to revoke | Instant revocation |
| No audit trail | Every action logged |
| Manual management | Automatic via SSO |
"Never trust, always verify"
Zero Trust means:
Traditional: A key card gets you into the building, then you can go anywhere.
Zero Trust: You need to badge in at every door, and your access is logged.
What you need before starting
Make sure Project A (Authentik) is working
| Resource | Additional | Total with Project A+B |
|---|---|---|
| RAM | +1 GB | ~10 GB minimum |
| Storage | +5 GB | ~110 GB minimum |
| Ports | 443, 3023, 3024, 3025 | Web + SSH + Tunnel |
Add these DNS records pointing to your server:
| Record | Type | Purpose |
|---|---|---|
| teleport.yourdomain.com | A | Main Teleport access |
| *.teleport.yourdomain.com | A | Per-node web access |
Deploy Teleport with Docker
SSH into your server first. These commands run on the server, not your local machine.
# Navigate to your homelab directory
cd ~/homelab-iam
# Create Teleport directories
mkdir -p teleport/{config,data}
# Verify structure
ls -la teleport/
cat > ~/homelab-iam/teleport/config/teleport.yaml << 'EOF'
version: v3
teleport:
nodename: teleport
data_dir: /var/lib/teleport
log:
output: stderr
severity: INFO
auth_service:
enabled: true
cluster_name: homelab
listen_addr: 0.0.0.0:3025
tokens:
- proxy,node,app:your-secure-join-token-here
# Session recording
session_recording: node-sync
proxy_service:
enabled: true
web_listen_addr: 0.0.0.0:3080
public_addr: teleport.yourdomain.com:443
# ACME for Let's Encrypt (or use Traefik)
https_keypairs: []
acme:
enabled: false
ssh_service:
enabled: true
labels:
env: homelab
role: main
EOF
Change teleport.yourdomain.com to your actual domain and generate a secure token for your-secure-join-token-here
# Generate a secure random token
openssl rand -hex 32
# Copy the output and replace "your-secure-join-token-here"
# in teleport.yaml with this value
# Example output:
# a1b2c3d4e5f6...
Add this service to your docker-compose.yml:
# Add to docker-compose.yml services section
# ==========================================
# TELEPORT - Zero Trust Access
# ==========================================
teleport:
image: public.ecr.aws/gravitational/teleport:15
container_name: teleport
restart: unless-stopped
hostname: teleport
volumes:
- ./teleport/config:/etc/teleport
- ./teleport/data:/var/lib/teleport
ports:
- "3023:3023" # SSH proxy
- "3024:3024" # SSH tunnel
- "3025:3025" # Auth service
command: start --config=/etc/teleport/teleport.yaml
networks:
- traefik-public
labels:
- "traefik.enable=true"
- "traefik.http.routers.teleport.rule=Host(\`teleport.${DOMAIN}\`)"
- "traefik.http.routers.teleport.entrypoints=websecure"
- "traefik.http.routers.teleport.tls.certresolver=letsencrypt"
- "traefik.http.services.teleport.loadbalancer.server.port=3080"
cd ~/homelab-iam
# Pull the Teleport image
docker compose pull teleport
# Start Teleport
docker compose up -d teleport
# Check it's running
docker compose ps teleport
# View logs
docker logs teleport --tail=50
# Create admin user with editor role
docker exec teleport tctl users add admin --roles=editor,access --logins=root,ubuntu
# You'll get a signup URL like:
# https://teleport.yourdomain.com:443/web/invite/xxxxx
# Open this URL to set password and configure MFA
Open the signup URL in your browser to create your admin account with MFA.
Connect servers to Teleport
Install Teleport agent on servers you want to access via SSH
Run these commands on the SERVER you want to protect (not the Teleport server):
# Add Teleport repository
sudo curl https://apt.releases.teleport.dev/gpg \
-o /usr/share/keyrings/teleport-archive-keyring.asc
echo "deb [signed-by=/usr/share/keyrings/teleport-archive-keyring.asc] \
https://apt.releases.teleport.dev/ubuntu $(lsb_release -cs) stable/v15" | \
sudo tee /etc/apt/sources.list.d/teleport.list > /dev/null
# Install Teleport
sudo apt update
sudo apt install -y teleport
# Create agent config
sudo cat > /etc/teleport.yaml << 'EOF'
version: v3
teleport:
nodename: my-server
data_dir: /var/lib/teleport
auth_token: your-secure-join-token-here
auth_server: teleport.yourdomain.com:443
auth_service:
enabled: false
proxy_service:
enabled: false
ssh_service:
enabled: true
labels:
env: homelab
type: server
EOF
# Start Teleport service
sudo systemctl enable teleport
sudo systemctl start teleport
# Check status
sudo systemctl status teleport
Replace your-secure-join-token-here with the token from step 3, and yourdomain.com with your actual domain.
# On the TELEPORT server, list nodes
docker exec teleport tctl nodes ls
# You should see:
# Node Name Address Labels
# ---------- ----------- ------
# my-server 192.168.x.x env=homelab
# Or check in Web UI: Resources → Servers
Your server is now protected by Teleport. You can SSH through the web UI or CLI!
Install the Teleport client on your local machine:
# macOS
brew install teleport
# Ubuntu/Debian (on your LOCAL machine)
curl https://goteleport.com/static/install.sh | bash -s 15.0.0
# Login to Teleport
tsh login --proxy=teleport.yourdomain.com:443
# List available servers
tsh ls
# SSH to a server
tsh ssh ubuntu@my-server
Login to Teleport using Authentik
Use your Authentik credentials to access Teleport
| Field | Value |
|---|---|
| Name | Teleport SAML Provider |
| Authorization flow | default-provider-authorization-implicit-consent |
| ACS URL | https://teleport.yourdomain.com/v1/webapi/saml/acs/authentik |
| Audience | https://teleport.yourdomain.com/v1/webapi/saml/acs/authentik |
| Service Provider Binding | Post |
| Name | Teleport |
| Slug | teleport |
| Provider | Teleport SAML Provider |
# Create SAML connector config
cat > ~/homelab-iam/teleport/authentik-saml.yaml << 'EOF'
kind: saml
version: v2
metadata:
name: authentik
spec:
display: "Login with Authentik"
acs: https://teleport.yourdomain.com/v1/webapi/saml/acs/authentik
entity_descriptor_url: https://authentik.yourdomain.com/api/v3/providers/saml/YOUR_PROVIDER_ID/metadata/
attributes_to_roles:
- name: "http://schemas.xmlsoap.org/claims/Group"
value: "admins"
roles: ["editor", "access"]
- name: "http://schemas.xmlsoap.org/claims/Group"
value: "users"
roles: ["access"]
EOF
# Apply the connector
docker exec teleport tctl create -f /etc/teleport/authentik-saml.yaml
# Note: Copy the yaml file to the teleport config directory first
cp ~/homelab-iam/teleport/authentik-saml.yaml ~/homelab-iam/teleport/config/
Replace YOUR_PROVIDER_ID with the numeric ID from Authentik (visible in the provider URL).
You can now login to Teleport using your Authentik account. One identity, all access!
Define who can access what
Users only get access to servers they need
# Create developer role
cat > ~/homelab-iam/teleport/config/role-developer.yaml << 'EOF'
kind: role
version: v7
metadata:
name: developer
spec:
allow:
# Can only SSH to servers with env=dev label
node_labels:
env: ["dev", "staging"]
# Can only login as these users
logins: ["ubuntu", "developer"]
# SSH rules
rules:
- resources: ["session"]
verbs: ["list", "read"]
options:
# Session TTL
max_session_ttl: 8h
# Require MFA for SSH
require_session_mfa: true
EOF
# Apply the role
docker exec teleport tctl create -f /etc/teleport/role-developer.yaml
# Create admin role
cat > ~/homelab-iam/teleport/config/role-admin.yaml << 'EOF'
kind: role
version: v7
metadata:
name: sysadmin
spec:
allow:
# Can SSH to any server
node_labels:
'*': '*'
# Can login as root
logins: ["root", "ubuntu", "admin"]
# Full access to Teleport resources
rules:
- resources: ["*"]
verbs: ["*"]
options:
max_session_ttl: 4h
require_session_mfa: true
# Record all sessions
record_session:
default: best_effort
EOF
# Apply the role
docker exec teleport tctl create -f /etc/teleport/role-admin.yaml
| Permission | Developer | SysAdmin |
|---|---|---|
| Server Access | dev, staging only | All servers |
| Login Users | ubuntu, developer | root, ubuntu, admin |
| Session Length | 8 hours | 4 hours |
| MFA Required | Yes | Yes |
Record and replay SSH sessions
Every keystroke recorded. Replay sessions for security review or training!
In the Teleport Web UI:
# List recordings
docker exec teleport tctl recordings ls
# Play a specific recording
docker exec -it teleport tctl recordings play SESSION_ID
Session recordings help meet compliance requirements like PCI-DSS, HIPAA, and SOC2 which require audit trails of privileged access.
Make sure everything works
Verify all components are working correctly
# Check Teleport status
docker exec teleport tctl status
# List all nodes
docker exec teleport tctl nodes ls
# List all users
docker exec teleport tctl users ls
# List all roles
docker exec teleport tctl get roles
# Test SSH (from your local machine)
tsh login --proxy=teleport.yourdomain.com
tsh ssh ubuntu@my-server
Cause: Token mismatch or network issue
Solution:
• Verify token matches in both configs
• Check node can reach teleport.yourdomain.com:443
• Check node logs: journalctl -u teleport
Cause: SAML configuration mismatch
Solution:
• Verify ACS URL matches exactly
• Check entity_descriptor_url is accessible
• Ensure user has groups mapped in Authentik
Cause: Role doesn't allow access
Solution:
• Check user's assigned roles
• Verify node_labels match server labels
• Ensure login user is in allowed logins
Zero Trust Network Access is live!
You've implemented enterprise-grade Zero Trust access!