Teleport is a privileged access management platform that proxies SSH, Kubernetes, database, and application access through a centralized control plane. Because it sits in front of every privileged session, a security misconfiguration has outsized impact: a stolen identity certificate grants SSH access to every server the user can reach, over-broad Teleport roles grant Kubernetes cluster-admin equivalent access, and session recording gaps create audit trail blind spots. This guide covers systematic Teleport security assessment.
| Component | Port | Security Attack Surface |
|---|---|---|
| Teleport Proxy | 443/3080 | Web UI auth bypass, certificate theft via MITM |
| Auth Server | 3025 | Certificate issuance, role assignment, token theft |
| SSH Node | 3022 | Direct SSH bypass if Teleport node accessible without proxy |
| Kubernetes proxy | 3026 | Kubeconfig token extraction, impersonation |
| Database proxy | 3036 | Database credential extraction via proxy |
| tctl (admin CLI) | — | Token generation, role manipulation, CA export |
# Check Teleport version (version fingerprinting)
curl -s https://teleport.example.com/webapi/ping | python3 -c "
import json,sys
data = json.load(sys.stdin)
print('Version:', data.get('server_version','?'))
print('Cluster name:', data.get('cluster_name','?'))
print('Auth type:', data.get('auth',{}).get('type','?'))
print('SSO:', data.get('auth',{}).get('oidc',{}).get('display','none'))
"
# Check if Teleport auth server is directly accessible
nc -z TARGET 3025 && echo "Auth server port accessible"
nc -z TARGET 3022 && echo "SSH node port accessible without proxy"
# Direct node access (port 3022) bypasses Teleport proxy recording/enforcement
# Teleport issues SSH certificates with TTL and role extensions
# Stolen certificates valid for their TTL give full SSH access
# Check certificate properties of a legitimate Teleport cert
ssh-keygen -L -f ~/.tsh/keys/teleport.example.com/username-ssh/teleport.example.com-cert.pub
# Key fields to check:
# - Valid before: short TTL = less exposure window
# - Critical Options: force-command restrictions?
# - Extensions: teleport-traits (roles, logins)
# Test: can certificates be used outside Teleport proxy (direct SSH)?
# If node allows both Teleport cert auth AND direct SSH connections:
ssh -i ~/.tsh/keys/teleport.example.com/username -p 3022 TARGET
# Check if nodes require Teleport CA only
# /etc/teleport.yaml on node should have:
# ssh_service:
# enabled: yes
# listen_addr: 127.0.0.1:3022 # Loopback only — not externally accessible
#
# If listen_addr: 0.0.0.0:3022 → direct SSH access bypasses proxy
# Test certificate renewal without MFA (if per-session MFA is not required)
tsh login --proxy=teleport.example.com
tsh status | grep -E "expiry|roles|logins"
# Check if certificate extends beyond MaxSessionTTL
# Role-defined MaxSessionTTL should limit cert validity
# If admin issues certs manually with --ttl=720h = 30 days of access with stolen cert
# Teleport roles define allowed logins, node access, and Kubernetes permissions
# Over-broad roles = full cluster access with a single compromised account
# List all roles (requires admin or read access)
tctl get roles
# Check for overly permissive roles
tctl get roles -o json | python3 -c "
import json,sys
roles = json.load(sys.stdin)
if isinstance(roles, dict): roles = [roles]
for role in roles:
spec = role.get('spec',{})
allow = spec.get('allow',{})
logins = allow.get('logins',[])
node_labels = allow.get('node_labels',{})
k8s_groups = allow.get('kubernetes_groups',[])
k8s_labels = allow.get('kubernetes_labels',{})
print(f\"Role: {role.get('metadata',{}).get('name','?')}\")
if 'root' in logins: print(' [!] Allows root login')
if node_labels.get('*') == '*' or node_labels == {'*': ['*']}: print(' [!] Matches ALL nodes')
if 'system:masters' in k8s_groups: print(' [!] Kubernetes cluster-admin via system:masters')
if k8s_labels.get('*') == '*': print(' [!] Matches ALL Kubernetes clusters')
"
# Test for wildcard node label matching
# Role that allows access to nodes with ANY label = all nodes
# node_labels:
# '*': '*'
# Bypass: even production nodes are accessible
# Test: can a regular user request an admin role via role request?
tsh request create --roles=admin --reason="Testing access" 2>&1
# If request is auto-approved or no approval workflow = privilege escalation
# Teleport Kubernetes access uses impersonation and short-lived tokens
# The kubeconfig generated by `tsh kube login` contains a Teleport-issued credential
# Examine the generated kubeconfig
tsh kube login cluster-name 2>/dev/null
cat ~/.kube/config | python3 -c "
import yaml,sys
cfg = yaml.safe_load(sys.stdin)
for user in cfg.get('users',[]):
exec_config = user.get('user',{}).get('exec',{})
if exec_config:
print(f\"User: {user['name']}\")
print(f\" Exec command: {exec_config.get('command','?')}\")
print(f\" Args: {exec_config.get('args','?')}\")
# Teleport uses exec credential plugin — calls tsh kube credentials
"
# Extract the actual bearer token used for K8s API calls
kubectl config view --raw | grep token
# Or intercept via kubectl verbose output:
kubectl get pods -v=8 2>&1 | grep "Authorization: Bearer" | head -5
# Bearer token = Teleport-issued impersonation credential
# Valid for the kubeconfig TTL (default: 12h)
# Test what Kubernetes permissions the Teleport role grants
kubectl auth can-i --list --as="teleport-impersonated-user" 2>/dev/null | \
grep -E "create|delete|get|list" | head -20
# Check if Teleport enforces Kubernetes RBAC or bypasses it
# Teleport can use impersonation (respects K8s RBAC) or direct token (bypasses it)
# Look for ClusterRoleBinding for Teleport service account
kubectl get clusterrolebinding | grep teleport
kubectl get clusterrolebinding teleport -o yaml 2>/dev/null | grep -A 3 roleRef
# Teleport records SSH sessions and Kubernetes API calls
# Gaps: recording off for specific roles, storage failures, direct node access
# Check session recording configuration in cluster config
tctl get cluster_auth_preference -o json | python3 -c "
import json,sys
data = json.load(sys.stdin)
spec = data.get('spec',{})
print('Session recording:', spec.get('session_recording','node'))
# 'off' = no recording anywhere
# 'node' = recorded at node (default) — bypassed if node is compromised
# 'proxy' = recorded at proxy (stronger) — node compromise doesn't affect recording
# 'proxy-sync' = synchronous proxy recording
"
# Check individual roles for recording mode override
tctl get roles -o json | python3 -c "
import json,sys
roles = json.load(sys.stdin)
if isinstance(roles, dict): roles = [roles]
for role in roles:
options = role.get('spec',{}).get('options',{})
rec_mode = options.get('record_session',{})
if rec_mode:
print(f\"Role: {role.get('metadata',{}).get('name','?')}\")
print(f\" Session recording override: {rec_mode}\")
"
# Test: direct SSH connection bypasses proxy recording
# If Teleport node port 3022 is accessible directly:
ssh -i ~/.tsh/keys/PROXY/USER USER@NODE_IP -p 3022
# This session is NOT recorded in Teleport (bypasses proxy recording)
# Test: what happens if Teleport audit log storage is full?
# Teleport can be configured to 'block' or 'drop' on audit storage failure
# 'drop' = sessions continue without recording = audit gap
# Teleport Database Access proxies connections to databases
# Credentials are issued as short-lived certificates or tokens
# Test for credential extraction and session bypass
# List accessible databases
tsh db ls
# Login to a database and check what credentials are issued
tsh db login db-name
ls -la ~/.tsh/keys/PROXY/USER/db/
# Check if database certificate includes username impersonation
openssl x509 -in ~/.tsh/keys/PROXY/USER/db/db-cert.pem -text -noout | \
grep -A 5 "Subject:"
# CN=username maps to database user
# Test: can a Teleport database cert be used to connect directly (bypassing proxy)?
TELEPORT_DB_CERT=~/.tsh/keys/PROXY/USER/db/db-cert.pem
TELEPORT_DB_KEY=~/.tsh/keys/PROXY/USER/db/db-key.pem
# If database accepts cert auth directly:
psql "host=db.example.com sslcert=$TELEPORT_DB_CERT sslkey=$TELEPORT_DB_KEY user=admin dbname=postgres"
# If connects → Teleport proxy is bypassed = no session recording
# Check auto-provisioned database users
# Teleport can auto-create database users with matching roles
tctl get role developer -o json | python3 -c "
import json,sys
role = json.load(sys.stdin)
db_roles = role.get('spec',{}).get('allow',{}).get('db_roles',[])
db_names = role.get('spec',{}).get('allow',{}).get('db_names',[])
print(f'DB roles: {db_roles}')
print(f'DB names: {db_names}')
if 'superuser' in db_roles or 'rds_superuser' in db_roles:
print('[!] Role grants database superuser access')
"
# Teleport audit logs are critical for compliance and incident response
# Test for log completeness and tamper detection
# Check audit log destinations
tctl get cluster_auth_preference -o json | python3 -c "
import json,sys
data = json.load(sys.stdin)
# Audit log backend: filesystem, DynamoDB, Firestore, Athena
print('Check teleport.yaml for audit_events_uri and audit_sessions_uri')
"
# Query recent audit events
tctl events search --from=$(date -d '1 hour ago' -Iseconds 2>/dev/null || date -v-1H -Iseconds) \
--type=session.start,session.end,auth,user.login 2>/dev/null | head -20
# Check if audit events include session.data for recordings
tctl events search --type=session.upload,session.end 2>/dev/null | head -10
# Test: what happens if an admin deletes an audit event?
# Teleport audit logs should be immutable — test whether admin can delete events
tctl delete events 2>/dev/null || echo "Delete not supported (correct)"
# Check if enhanced session recording is enabled (captures commands, not just sessions)
tctl get cluster_auth_preference -o json | python3 -c "
import json,sys
data = json.load(sys.stdin)
enhanced = data.get('spec',{}).get('enhanced_recording',{})
print('Enhanced recording:', enhanced)
# enhanced_recording.enabled: true = captures individual commands, not just terminal output
"
proxy or proxy-sync — node recording is bypassable| Security Test | Method | Risk |
|---|---|---|
| Node port 3022 directly accessible | nc -z NODE 3022 | Critical |
| Role grants system:masters K8s group | tctl get roles — check kubernetes_groups | Critical |
| Recording mode: node (bypassable) | tctl get cluster_auth_preference | High |
| Role with wildcard node label access | Check node_labels: '*': '*' in roles | High |
| Role allows root SSH login | Check logins: root in roles | High |
| Database cert usable outside proxy | Direct psql/mysql with Teleport DB cert | High |
| Access requests auto-approved | tsh request create --roles=admin | Medium |
Ironimo tests Teleport deployments for node port exposure, session recording bypass paths, RBAC role privilege escalation, certificate TTL risks, database access credential extraction, and audit log completeness gaps.
Start free scan