Skip to content

Instantly share code, notes, and snippets.

@cloudnull
Last active September 30, 2022 03:07
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save cloudnull/81f32cca27251fc1f707d6e6b8ffa4aa to your computer and use it in GitHub Desktop.
Save cloudnull/81f32cca27251fc1f707d6e6b8ffa4aa to your computer and use it in GitHub Desktop.
A simple dynamic Ansible inventory which uses node (server) information from teleport.
#!/usr/bin/env python3
"""Teleport Inventory Documentation.
Very simple inventory script which will use teleport as a dynamic inventory source.
The script assumes you've logged in and have access to the teleport service.
The script will read machines and return JSON information from teleport. Once the
return information is sourced, it will format the inventory using the node name
as the target host and the labels as hostvars and groups.
Example Setup
When running this inventory ansible requires some basic instructions which can
be set via environment variables or through the ansible config file.
$ export ANSIBLE_SCP_IF_SSH=False
$ export ANSIBLE_SSH_ARGS="-F ${HOME}/.ssh/teleport.cfg"
$ export ANSIBLE_INVENTORY_ENABLED=script
$ export ANSIBLE_HOST_KEY_CHECKING=False
NOTE(cloudnull): The ${HOME}/.ssh/teleport.cfg file is generated by using the
`tsh config` command then customized based on individual
needs.
Example ssh configuration entry to proxy commands through teleport.
$ export TELEPORT_DOMAIN=teleport.example.com
$ export TELEPORT_USER=cloudnull
$ cat > ~/.ssh/teleport.cfg <<EOF
Host * !${TELEPORT_DOMAIN}
UserKnownHostsFile "${HOME}/.tsh/known_hosts"
IdentityFile "${HOME}/.tsh/keys/${TELEPORT_DOMAIN}/${TELEPORT_USER}"
CertificateFile "${HOME}/.tsh/keys/${TELEPORT_DOMAIN}/${TELEPORT_USER}-ssh/${TELEPORT_DOMAIN}-cert.pub"
PubkeyAcceptedKeyTypes +ssh-rsa-cert-v01@openssh.com
Port 3022
CheckHostIP no
ProxyCommand "$(which tsh)" proxy ssh --cluster=${TELEPORT_DOMAIN} --proxy=${TELEPORT_DOMAIN} %r@%h:%p
EOF
NOTE(cloudnull): The above ssh configuration needs to have all variables
replaced to work.
Example login process
$ tsh login --proxy=teleport.example.com --user=teleport-admin
If browser window does not open automatically, open it by clicking on the link:
http://127.0.0.1:42337/83facfc0-e899-44dc-aa6c-32179e3d5b0b
> Profile URL: https://teleport.example.com:443
Logged in as: teleport-admin
Cluster: teleport.example.com
Roles: access
Logins: root, ubuntu, debian, fedora, centos, -teleport-internal-join
Kubernetes: enabled
Valid until: 2022-09-16 08:03:05 -0500 CDT [valid for 12h0m0s]
Extensions: permit-agent-forwarding, permit-port-forwarding, permit-pty
NOTE(cloudnull): The above tsh login will provide access to the teleport cluster, which
will allow you to run inventory script which will return an Ansible
inventory JSON.
Inventory return example.
>>> {
... "_meta": {
... "hostvars": {
... "alluvial-0": {
... "ansible_user": "ubuntu",
... "teleport_id": 1663292578982084621,
... "arch": "x86_64",
... "codename": "jammy",
... "environment": "dev"
... },
... "compute-0": {
... "ansible_user": "carter",
... "teleport_id": 1663292546493240726,
... "arch": "x86_64",
... "codename": "jammy",
... "environment": "prod"
... },
... "curator-0": {
... "ansible_user": "debian",
... "teleport_id": 1663292584620339609,
... "arch": "x86_64",
... "codename": "bullseye",
... "environment": "prod"
... },
... "plexmediaserver-0": {
... "ansible_user": "debian",
... "teleport_id": 1663292540612813467,
... "arch": "x86_64",
... "codename": "bullseye",
... "environment": "prod"
... },
... "rtmprelay-0": {
... "ansible_user": "debian",
... "teleport_id": 1663292583673456110,
... "arch": "x86_64",
... "codename": "bullseye",
... "environment": "prod"
... },
... "teleport-0": {
... "ansible_user": "debian",
... "teleport_id": 1663292588270337453,
... "arch": "x86_64",
... "codename": "bullseye",
... "environment": "prod"
... },
... "thelounge-0": {
... "ansible_user": "debian",
... "teleport_id": 1663292533524229273,
... "arch": "x86_64",
... "codename": "bullseye",
... "environment": "prod"
... },
... "unbound-0": {
... "ansible_user": "debian",
... "teleport_id": 1663292535344206453,
... "arch": "x86_64",
... "codename": "bullseye",
... "environment": "prod"
... },
... "unifi-0": {
... "ansible_user": "debian",
... "teleport_id": 1663292528952201832,
... "arch": "x86_64",
... "codename": "bullseye",
... "environment": "prod"
... },
... "wireguard-0": {
... "ansible_user": "debian",
... "teleport_id": 1663292535570354336,
... "arch": "x86_64",
... "codename": "bullseye",
... "environment": "prod"
... }
... }
... },
... "all": {
... "hosts": [
... "alluvial-0",
... "compute-0",
... "curator-0",
... "plexmediaserver-0",
... "rtmprelay-0",
... "teleport-0",
... "thelounge-0",
... "unbound-0",
... "unifi-0",
... "wireguard-0"
... ],
... "children": []
... },
... "x86_64": {
... "hosts": [
... "alluvial-0",
... "compute-0",
... "curator-0",
... "plexmediaserver-0",
... "rtmprelay-0",
... "teleport-0",
... "thelounge-0",
... "unbound-0",
... "unifi-0",
... "wireguard-0"
... ],
... "children": []
... },
... "jammy": {
... "hosts": [
... "alluvial-0",
... "compute-0"
... ],
... "children": []
... },
... "dev": {
... "hosts": [
... "alluvial-0"
... ],
... "children": []
... },
... "teleport_cloudnull_dev": {
... "hosts": [
... "wireguard-0"
... ],
... "children": []
... },
... "prod": {
... "hosts": [
... "compute-0",
... "curator-0",
... "plexmediaserver-0",
... "rtmprelay-0",
... "teleport-0",
... "thelounge-0",
... "unbound-0",
... "unifi-0",
... "wireguard-0"
... ],
... "children": []
... },
... "bullseye": {
... "hosts": [
... "curator-0",
... "plexmediaserver-0",
... "rtmprelay-0",
... "teleport-0",
... "thelounge-0",
... "unbound-0",
... "unifi-0",
... "wireguard-0"
... ],
... "children": []
... }
... }
NOTE(cloudnull): Once the ssh config is in place, the Ansible configuration is set, and
teleport is logged in, Ansible can be run natively.
Example Ansible Ad-Hoc command
$ ansible -i teleport-inventory.py -m ping all
Example Ansible Playbook command
$ ansible-playbook -i teleport-inventory.py example-playbook.yaml
"""
import json
import subprocess
INVENTORY = {
"_meta": {"hostvars": {}},
"all": {
"hosts": [],
"children": [],
},
}
def _group_add(group_name, hostname):
"""Set group information for a given name and host.
:param group_name: String
:param hostname: String
"""
try:
group = INVENTORY[group_name]
except KeyError:
# Per ansible spec, group names can not have dashes in them.
group_name = group_name.replace("-", "_")
# Per ansible spec, group names can not have periods in them.
group_name = group_name.replace(".", "_")
# Per ansible spec, group names can not start with an int.
try:
int(group_name[0])
except ValueError:
pass
else:
return
INVENTORY.setdefault(group_name, {"hosts": [], "children": []})
group = INVENTORY[group_name]
group["hosts"].append(hostname)
group["hosts"] = sorted(set(group["hosts"]))
def main():
"""Return dynamic inventory using teleport data.
:returns: String
"""
# Presently using subprocess to get the node JSON, may revise this later.
tsh_return = subprocess.run(
"tsh ls --all --format=json", shell=True, capture_output=True
)
try:
teleport_items = json.loads(tsh_return.stdout)
except Exception:
raise SystemExit("No inventory JSON data found. Check for valid login via tsh.")
else:
if not teleport_items:
raise SystemExit(
"Nothing available via teleport, check for valid login via tsh."
)
for item in teleport_items:
node = item["node"]
hostname = node["spec"]["hostname"]
_group_add(group_name=item["cluster"], hostname=hostname)
meta_data = INVENTORY["_meta"]["hostvars"]
all_data = INVENTORY["all"]
all_data["hosts"].append(hostname)
all_data["hosts"] = sorted(set(all_data["hosts"]))
for k, v in node["metadata"]["labels"].items():
try:
host_vars = meta_data[hostname]
except KeyError:
host_vars = meta_data[hostname] = dict()
host_vars[k] = v
host_vars["teleport_id"] = node["metadata"]["id"]
# If a label is an ansible variable, omit it from groups
if k.startswith("ansible_"):
continue
_group_add(group_name=v, hostname=hostname)
return json.dumps(INVENTORY, indent=4)
if __name__ == "__main__":
print(main())
@cloudnull
Copy link
Author

cloudnull commented Sep 30, 2022

This gist has been promoted to a real repository. You can find the repository here: https://github.com/cloudnull/teleport-ansible

The repo is also installable via pip, just run pip install teleport-ansible. Once installed you can access the inventory script with the command teleport-ansible. Once installed, set the script into Ansible config and profit.

export ANSIBLE_INVENTORY="$(command -v teleport-ansible)"

You can also download the maintained inventory script and run without installing.

curl https://raw.githubusercontent.com/cloudnull/teleport-ansible/master/teleport-inventory.py -o teleport-inventory.py

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment