--- /dev/null
+---
+- hosts: all
+ become: true
+ vars:
+ teleport_join_token: "{{ teleport_join_token | default(omit) }}"
+
+ roles:
+ - teleport_ssh_node
--- /dev/null
+Perfect — here is the **cleaned and corrected README.md** with exactly your requested changes applied:
+
+* ✅ Removed point **7 (air-gapped / bastion-based environments)**
+* ✅ Removed the **Maintainers** section
+* ✅ Replaced real hostnames with **generic examples** (`bob`, `mac`)
+* ✅ Keeps GitHub-friendly rendering
+* ✅ Keeps the hostname → label example inline (as you wanted)
+
+You can copy this **as-is** into `roles/teleport_ssh_node/README.md`:
+
+---
+
+````md
+# teleport_ssh_node
+
+Ansible role to **install, configure, and join a Linux host as a Teleport SSH node**
+to an existing Teleport cluster.
+
+It supports:
+- Ubuntu / Debian
+- RHEL / CentOS / Rocky
+- Teleport 15.x (version pinned)
+- Idempotent re-runs
+- Safe hostname handling
+- Label-based node discovery
+
+---
+
+## What this role does
+
+1. Sets the system hostname to the node FQDN
+2. Installs Teleport **pinned to a specific version**
+3. Creates `/etc/teleport.yaml` from template
+4. Joins the node to the Teleport cluster using a join token
+5. Enables and starts the teleport service
+6. Registers the node with:
+ - Correct `nodename`
+ - `role` label derived from hostname
+
+---
+
+## Requirements
+
+### On the control node
+- Ansible 2.16+
+- SSH access to target nodes
+- Valid Teleport join token
+- Network access to the Teleport proxy
+
+### On the target node
+- Systemd
+- Python 3
+- Outbound access to the Teleport proxy
+
+---
+
+## Role Variables
+
+### Required
+
+| Variable | Description |
+|--------|------------|
+| `teleport_join_token` | Teleport node join token |
+| `teleport_ca_pin` | CA pin for secure joining |
+| `teleport_proxy` | Proxy address (host:port) |
+
+---
+
+### Optional
+
+| Variable | Default | Description |
+|--------|---------|------------|
+| `teleport_version` | `15.5.4` | Teleport version (pinned) |
+| `teleport_config_path` | `/etc/teleport.yaml` | Config file path |
+| `teleport_data_dir` | `/var/lib/teleport` | Data directory |
+
+---
+
+## Hostname handling and labels
+
+The role automatically derives the Teleport node identity from the host itself.
+
+For a host with FQDN:
+
+```text
+bob.example.com
+````
+
+The generated configuration will include:
+
+```yaml
+teleport:
+ nodename: bob.example.com
+
+ssh_service:
+ labels:
+ role: bob
+```
+
+This ensures stable node identity, clean RBAC matching, and predictable resource
+names in Teleport without requiring manual label management.
+
+---
+
+## Example Playbook
+
+```yaml
+- hosts: mac
+ become: true
+ vars_prompt:
+ - name: teleport_join_token
+ prompt: "Enter Teleport join token"
+ private: false
+ roles:
+ - teleport_ssh_node
+```
+
+---
+
+## Example Run
+
+```bash
+ansible-playbook join-teleport.yml \
+ -i inventory \
+ --limit bob.example.com
+```
+
+---
+
+## Teleport configuration
+
+The role generates `/etc/teleport.yaml` with:
+
+* pinned Teleport version
+* CA pin validation
+* proxy configuration
+* SSH service only (no auth/proxy)
+* hostname-based nodename and labels
+* safe defaults for production use
+
+---
+
+## Supported Operating Systems
+
+| OS | Supported |
+| --------------- | --------- |
+| RHEL 8/9 | ✅ |
+| Rocky 8/9 | ✅ |
+| CentOS Stream 9 | ✅ |
+| Ubuntu 20.04+ | ✅ |
+| Debian 11+ | ✅ |
+
+---
+
+## Idempotency
+
+The role is safe to run multiple times:
+
+* No duplicate joins
+* No reinstallation if version is pinned
+* Config updates trigger safe restart
+* Service state enforced
+
+---
+
+## License
+
+Apache-2.0
+
+```
--- /dev/null
+---
+teleport_version: "15.5.4"
+teleport_proxy: "teleport.ceph.com:443"
+teleport_ca_pin: "sha256:3dcff35bc57ea8570af409ee9bad6a6c7acff5556bf7c094d34034e5f826b235"
+teleport_join_token: ""
+
+teleport_pkg_version: "=15.5.4"
+# Full node name used by Teleport
+teleport_nodename: "{{ ansible_fqdn | default(ansible_hostname) }}"
+
+# Short role label (hostname without domain)
+teleport_role_label: "{{ ansible_hostname }}"
--- /dev/null
+---
+- name: restart teleport
+ systemd:
+ name: teleport
+ state: restarted
--- /dev/null
+---
+dependencies:
+ - role: secrets
--- /dev/null
+---
+- name: Set hostname with domain
+ become: true
+ hostname:
+ name: "{{ ansible_hostname }}.front.sepia.ceph.com"
+ when: ansible_fqdn is not defined or not ansible_fqdn.endswith('front.sepia.ceph.com')
+
+- name: Assert join token provided
+ assert:
+ that:
+ - teleport_join_token is defined
+ - teleport_join_token | length > 0
+ fail_msg: "teleport_join_token is required"
+
+- name: Render teleport config
+ template:
+ src: teleport.yaml.j2
+ dest: /etc/teleport.yaml
+ owner: root
+ group: root
+ mode: '0644'
+ notify: restart teleport
+
+- name: Enable and start teleport
+ systemd:
+ name: teleport
+ enabled: true
+ state: started
--- /dev/null
+---
+- name: Assert FQDN exists
+ assert:
+ that:
+ - ansible_fqdn is defined
+ - ansible_fqdn | length > 0
+ fail_msg: "ansible_fqdn is empty — fix hostname/DNS before continuing"
+
+- name: Set teleport facts
+ set_fact:
+ teleport_nodename: "{{ ansible_fqdn }}"
+ teleport_role_label: "{{ ansible_hostname }}"
--- /dev/null
+---
+- name: Ensure hostname is FQDN
+ hostname:
+ name: "{{ ansible_fqdn }}"
--- /dev/null
+---
+# Install Teleport
+
+########################
+# Ubuntu / Debian
+########################
+
+- name: Install dependencies (Debian/Ubuntu)
+ ansible.builtin.apt:
+ name: curl
+ state: present
+ update_cache: yes
+ when: ansible_os_family == "Debian"
+
+- name: Add Teleport GPG key (Debian/Ubuntu)
+ ansible.builtin.get_url:
+ url: https://apt.releases.teleport.dev/gpg
+ dest: /usr/share/keyrings/teleport-archive-keyring.asc
+ mode: '0644'
+ when: ansible_os_family == "Debian"
+
+- name: Add Teleport apt repo (pinned major v15)
+ ansible.builtin.apt_repository:
+ repo: >
+ deb [signed-by=/usr/share/keyrings/teleport-archive-keyring.asc]
+ https://apt.releases.teleport.dev/ubuntu
+ {{ ansible_distribution_release }} stable/v15
+ state: present
+ when: ansible_os_family == "Debian"
+
+- name: Install Teleport (pinned) on Debian/Ubuntu
+ ansible.builtin.apt:
+ name: "teleport{{ teleport_pkg_version }}"
+ state: present
+ update_cache: yes
+ when: ansible_os_family == "Debian"
+
+
+########################
+# RHEL / CentOS / EL9
+########################
+
+- name: Remove any old Teleport repo (EL)
+ ansible.builtin.file:
+ path: /etc/yum.repos.d/teleport.repo
+ state: absent
+ when: ansible_os_family == "RedHat"
+
+- name: Clean dnf metadata (EL)
+ ansible.builtin.command: dnf clean all
+ changed_when: false
+ when: ansible_os_family == "RedHat"
+
+- name: Install base deps safely (EL)
+ ansible.builtin.dnf:
+ name:
+ - curl
+ - dnf-plugins-core
+ state: present
+ disablerepo: "*"
+ enablerepo: "baseos,appstream"
+ when: ansible_os_family == "RedHat"
+
+- name: Install versionlock plugin (EL9)
+ ansible.builtin.dnf:
+ name: dnf-command(versionlock)
+ state: present
+ when: ansible_os_family == "RedHat"
+
+- name: Read OS ID (EL)
+ ansible.builtin.shell: ". /etc/os-release && echo $ID"
+ register: os_release_id
+ changed_when: false
+ when: ansible_os_family == "RedHat"
+
+- name: Add Teleport yum repo (pinned major v15)
+ ansible.builtin.command: >
+ dnf config-manager --add-repo
+ https://yum.releases.teleport.dev/{{ os_release_id.stdout | trim }}/{{ ansible_distribution_major_version }}/Teleport/{{ ansible_architecture }}/stable/v15/teleport.repo
+ args:
+ creates: /etc/yum.repos.d/teleport.repo
+ when: ansible_os_family == "RedHat"
+
+- name: Install Teleport (pinned) on EL
+ ansible.builtin.dnf:
+ name: "teleport-{{ teleport_version }}"
+ state: present
+ update_cache: true
+ when: ansible_os_family == "RedHat"
+
+- name: Lock Teleport version (EL)
+ ansible.builtin.command: dnf versionlock add teleport-{{ teleport_version }}
+ args:
+ creates: /etc/dnf/plugins/versionlock.list
+ when: ansible_os_family == "RedHat"
--- /dev/null
+---
+- include_tasks: install.yml
+- include_tasks: config.yml
--- /dev/null
+version: v3
+
+teleport:
+ nodename: {{ teleport_nodename }}
+ data_dir: /var/lib/teleport
+
+ join_params:
+ token_name: {{ teleport_join_token }}
+ method: token
+
+ proxy_server: {{ teleport_proxy }}
+ ca_pin: {{ teleport_ca_pin }}
+ diag_addr: ""
+
+ log:
+ output: stderr
+ severity: INFO
+ format:
+ output: text
+
+auth_service:
+ enabled: "no"
+
+ssh_service:
+ enabled: "yes"
+ labels:
+ role: {{ teleport_role_label }}
+ commands:
+ - name: hostname
+ command: [hostname]
+ period: 1m0s
+
+proxy_service:
+ enabled: "no"
+ https_keypairs: []
+ https_keypairs_reload_interval: 0s
+ acme: {}