Last active
September 30, 2022 03:07
-
-
Save cloudnull/81f32cca27251fc1f707d6e6b8ffa4aa to your computer and use it in GitHub Desktop.
A simple dynamic Ansible inventory which uses node (server) information from teleport.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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 [email protected] | |
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()) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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 runpip install teleport-ansible
. Once installed you can access the inventory script with the commandteleport-ansible
. Once installed, set the script into Ansible config and profit.You can also download the maintained inventory script and run without installing.