]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph-cm-ansible.git/commitdiff
teleport_ssh_node role for SSH node enrollment wip-teleport-ssh-node
authorAdam Kraitman <akraitma@li-8b09b2cc-35b7-11b2-a85c-cd1dbade58f9.ibm.com>
Mon, 19 Jan 2026 17:23:52 +0000 (19:23 +0200)
committerAdam Kraitman <akraitma@li-8b09b2cc-35b7-11b2-a85c-cd1dbade58f9.ibm.com>
Tue, 3 Feb 2026 20:44:06 +0000 (22:44 +0200)
This adds a new Ansible role that:
- installs Teleport with pinned version
- sets hostname and nodename consistently
- generates teleport.yaml from templates
- joins nodes using token + CA pin
- enables and manages teleport.service
- derives SSH labels from hostname automatically

Designed for repeatable, safe enrollment of SSH nodes into Teleport.

Signed-off-by: Adam Kraitman <akraitma@li-8b09b2cc-35b7-11b2-a85c-cd1dbade58f9.ibm.com>
join-teleport.yml [new file with mode: 0644]
roles/teleport_ssh_node/README.md [new file with mode: 0644]
roles/teleport_ssh_node/defaults/main.yml [new file with mode: 0644]
roles/teleport_ssh_node/handlers/main.yml [new file with mode: 0644]
roles/teleport_ssh_node/meta/main.yml [new file with mode: 0644]
roles/teleport_ssh_node/tasks/config.yml [new file with mode: 0644]
roles/teleport_ssh_node/tasks/facts.yml [new file with mode: 0644]
roles/teleport_ssh_node/tasks/hostname.yml [new file with mode: 0644]
roles/teleport_ssh_node/tasks/install.yml [new file with mode: 0644]
roles/teleport_ssh_node/tasks/main.yml [new file with mode: 0644]
roles/teleport_ssh_node/templates/teleport.yaml.j2 [new file with mode: 0644]

diff --git a/join-teleport.yml b/join-teleport.yml
new file mode 100644 (file)
index 0000000..ba7e635
--- /dev/null
@@ -0,0 +1,8 @@
+---
+- hosts: all
+  become: true
+  vars:
+    teleport_join_token: "{{ teleport_join_token | default(omit) }}"
+
+  roles:
+    - teleport_ssh_node
diff --git a/roles/teleport_ssh_node/README.md b/roles/teleport_ssh_node/README.md
new file mode 100644 (file)
index 0000000..7e37f4d
--- /dev/null
@@ -0,0 +1,170 @@
+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
+
+```
diff --git a/roles/teleport_ssh_node/defaults/main.yml b/roles/teleport_ssh_node/defaults/main.yml
new file mode 100644 (file)
index 0000000..c69e13b
--- /dev/null
@@ -0,0 +1,12 @@
+---
+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 }}"
diff --git a/roles/teleport_ssh_node/handlers/main.yml b/roles/teleport_ssh_node/handlers/main.yml
new file mode 100644 (file)
index 0000000..979e6f2
--- /dev/null
@@ -0,0 +1,5 @@
+---
+- name: restart teleport
+  systemd:
+    name: teleport
+    state: restarted
diff --git a/roles/teleport_ssh_node/meta/main.yml b/roles/teleport_ssh_node/meta/main.yml
new file mode 100644 (file)
index 0000000..313fd69
--- /dev/null
@@ -0,0 +1,3 @@
+---
+dependencies:
+  - role: secrets
diff --git a/roles/teleport_ssh_node/tasks/config.yml b/roles/teleport_ssh_node/tasks/config.yml
new file mode 100644 (file)
index 0000000..623477d
--- /dev/null
@@ -0,0 +1,28 @@
+---
+- 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
diff --git a/roles/teleport_ssh_node/tasks/facts.yml b/roles/teleport_ssh_node/tasks/facts.yml
new file mode 100644 (file)
index 0000000..a301979
--- /dev/null
@@ -0,0 +1,12 @@
+---
+- 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 }}"
diff --git a/roles/teleport_ssh_node/tasks/hostname.yml b/roles/teleport_ssh_node/tasks/hostname.yml
new file mode 100644 (file)
index 0000000..4360b0b
--- /dev/null
@@ -0,0 +1,4 @@
+---
+- name: Ensure hostname is FQDN
+  hostname:
+    name: "{{ ansible_fqdn }}"
diff --git a/roles/teleport_ssh_node/tasks/install.yml b/roles/teleport_ssh_node/tasks/install.yml
new file mode 100644 (file)
index 0000000..86ecc7b
--- /dev/null
@@ -0,0 +1,95 @@
+---
+# 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"
diff --git a/roles/teleport_ssh_node/tasks/main.yml b/roles/teleport_ssh_node/tasks/main.yml
new file mode 100644 (file)
index 0000000..1869ca1
--- /dev/null
@@ -0,0 +1,3 @@
+---
+- include_tasks: install.yml
+- include_tasks: config.yml
diff --git a/roles/teleport_ssh_node/templates/teleport.yaml.j2 b/roles/teleport_ssh_node/templates/teleport.yaml.j2
new file mode 100644 (file)
index 0000000..a13fe79
--- /dev/null
@@ -0,0 +1,37 @@
+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: {}