1
0
Fork 0
mirror of https://github.com/ansible-collections/community.general.git synced 2024-09-14 20:13:21 +02:00

inventory plugins: make data obtained from remote unsafe (#8098)

Make data obtained from remote unsafe.
This commit is contained in:
Felix Fontein 2024-03-25 06:17:09 +01:00 committed by GitHub
parent b389f8637f
commit d62fe154d2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 88 additions and 51 deletions

View file

@ -0,0 +1,6 @@
security_fixes:
- "cobbler, gitlab_runners, icinga2, linode, lxd, nmap, online, opennebula, proxmox, scaleway, stackpath_compute, virtualbox,
and xen_orchestra inventory plugin - make sure all data received from the remote servers is marked as unsafe, so remote
code execution by obtaining texts that can be evaluated as templates is not possible
(https://www.die-welt.net/2024/03/remote-code-execution-in-ansible-dynamic-inventory-plugins/,
https://github.com/ansible-collections/community.general/pull/8098)."

View file

@ -117,6 +117,7 @@ from ansible.errors import AnsibleError
from ansible.module_utils.common.text.converters import to_text
from ansible.plugins.inventory import BaseInventoryPlugin, Cacheable, to_safe_group_name
from ansible.module_utils.six import text_type
from ansible.utils.unsafe_proxy import wrap_var as make_unsafe
# xmlrpc
try:
@ -274,9 +275,9 @@ class InventoryModule(BaseInventoryPlugin, Cacheable):
for host in self._get_systems():
# Get the FQDN for the host and add it to the right groups
if self.inventory_hostname == 'system':
hostname = host['name'] # None
hostname = make_unsafe(host['name']) # None
else:
hostname = host['hostname'] # None
hostname = make_unsafe(host['hostname']) # None
interfaces = host['interfaces']
if set(host['mgmt_classes']) & set(self.include_mgmt_classes):
@ -296,7 +297,7 @@ class InventoryModule(BaseInventoryPlugin, Cacheable):
if ivalue['management'] or not ivalue['static']:
this_dns_name = ivalue.get('dns_name', None)
if this_dns_name is not None and this_dns_name != "":
hostname = this_dns_name
hostname = make_unsafe(this_dns_name)
self.display.vvvv('Set hostname to %s from %s\n' % (hostname, iname))
if hostname == '':
@ -361,18 +362,18 @@ class InventoryModule(BaseInventoryPlugin, Cacheable):
if ip_address is None and ip_address_first is not None:
ip_address = ip_address_first
if ip_address is not None:
self.inventory.set_variable(hostname, 'cobbler_ipv4_address', ip_address)
self.inventory.set_variable(hostname, 'cobbler_ipv4_address', make_unsafe(ip_address))
if ipv6_address is None and ipv6_address_first is not None:
ipv6_address = ipv6_address_first
if ipv6_address is not None:
self.inventory.set_variable(hostname, 'cobbler_ipv6_address', ipv6_address)
self.inventory.set_variable(hostname, 'cobbler_ipv6_address', make_unsafe(ipv6_address))
if self.get_option('want_facts'):
try:
self.inventory.set_variable(hostname, 'cobbler', host)
self.inventory.set_variable(hostname, 'cobbler', make_unsafe(host))
except ValueError as e:
self.display.warning("Could not set host info for %s: %s" % (hostname, to_text(e)))
if self.get_option('want_ip_addresses'):
self.inventory.set_variable(self.group, 'cobbler_ipv4_addresses', ip_addresses)
self.inventory.set_variable(self.group, 'cobbler_ipv6_addresses', ipv6_addresses)
self.inventory.set_variable(self.group, 'cobbler_ipv4_addresses', make_unsafe(ip_addresses))
self.inventory.set_variable(self.group, 'cobbler_ipv6_addresses', make_unsafe(ipv6_addresses))

View file

@ -83,6 +83,7 @@ keyed_groups:
from ansible.errors import AnsibleError, AnsibleParserError
from ansible.module_utils.common.text.converters import to_native
from ansible.plugins.inventory import BaseInventoryPlugin, Constructable
from ansible.utils.unsafe_proxy import wrap_var as make_unsafe
try:
import gitlab
@ -105,11 +106,11 @@ class InventoryModule(BaseInventoryPlugin, Constructable):
else:
runners = gl.runners.all()
for runner in runners:
host = str(runner['id'])
host = make_unsafe(str(runner['id']))
ip_address = runner['ip_address']
host_attrs = vars(gl.runners.get(runner['id']))['_attrs']
host_attrs = make_unsafe(vars(gl.runners.get(runner['id']))['_attrs'])
self.inventory.add_host(host, group='gitlab_runners')
self.inventory.set_variable(host, 'ansible_host', ip_address)
self.inventory.set_variable(host, 'ansible_host', make_unsafe(ip_address))
if self.get_option('verbose_output', True):
self.inventory.set_variable(host, 'gitlab_runner_attributes', host_attrs)

View file

@ -102,6 +102,7 @@ from ansible.errors import AnsibleParserError
from ansible.plugins.inventory import BaseInventoryPlugin, Constructable
from ansible.module_utils.urls import open_url
from ansible.module_utils.six.moves.urllib.error import HTTPError
from ansible.utils.unsafe_proxy import wrap_var as make_unsafe
class InventoryModule(BaseInventoryPlugin, Constructable):
@ -240,15 +241,15 @@ class InventoryModule(BaseInventoryPlugin, Constructable):
"""Convert Icinga2 API data to JSON format for Ansible"""
groups_dict = {"_meta": {"hostvars": {}}}
for entry in json_data:
host_attrs = entry['attrs']
host_attrs = make_unsafe(entry['attrs'])
if self.inventory_attr == "name":
host_name = entry.get('name')
host_name = make_unsafe(entry.get('name'))
if self.inventory_attr == "address":
# When looking for address for inventory, if missing fallback to object name
if host_attrs.get('address', '') != '':
host_name = host_attrs.get('address')
host_name = make_unsafe(host_attrs.get('address'))
else:
host_name = entry.get('name')
host_name = make_unsafe(entry.get('name'))
if self.inventory_attr == "display_name":
host_name = host_attrs.get('display_name')
if host_attrs['state'] == 0:
@ -265,7 +266,7 @@ class InventoryModule(BaseInventoryPlugin, Constructable):
# If the address attribute is populated, override ansible_host with the value
if host_attrs.get('address') != '':
self.inventory.set_variable(host_name, 'ansible_host', host_attrs.get('address'))
self.inventory.set_variable(host_name, 'hostname', entry.get('name'))
self.inventory.set_variable(host_name, 'hostname', make_unsafe(entry.get('name')))
self.inventory.set_variable(host_name, 'display_name', host_attrs.get('display_name'))
self.inventory.set_variable(host_name, 'state',
host_attrs['state'])

View file

@ -122,6 +122,7 @@ compose:
from ansible.errors import AnsibleError
from ansible.plugins.inventory import BaseInventoryPlugin, Constructable, Cacheable
from ansible.utils.unsafe_proxy import wrap_var as make_unsafe
try:
@ -198,20 +199,21 @@ class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable):
def _add_instances_to_groups(self):
"""Add instance names to their dynamic inventory groups."""
for instance in self.instances:
self.inventory.add_host(instance.label, group=instance.group)
self.inventory.add_host(make_unsafe(instance.label), group=instance.group)
def _add_hostvars_for_instances(self):
"""Add hostvars for instances in the dynamic inventory."""
ip_style = self.get_option('ip_style')
for instance in self.instances:
hostvars = instance._raw_json
hostname = make_unsafe(instance.label)
for hostvar_key in hostvars:
if ip_style == 'api' and hostvar_key in ['ipv4', 'ipv6']:
continue
self.inventory.set_variable(
instance.label,
hostname,
hostvar_key,
hostvars[hostvar_key]
make_unsafe(hostvars[hostvar_key])
)
if ip_style == 'api':
ips = instance.ips.ipv4.public + instance.ips.ipv4.private
@ -220,9 +222,9 @@ class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable):
for ip_type in set(ip.type for ip in ips):
self.inventory.set_variable(
instance.label,
hostname,
ip_type,
self._ip_data([ip for ip in ips if ip.type == ip_type])
make_unsafe(self._ip_data([ip for ip in ips if ip.type == ip_type]))
)
def _ip_data(self, ip_list):
@ -253,21 +255,22 @@ class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable):
self._add_instances_to_groups()
self._add_hostvars_for_instances()
for instance in self.instances:
variables = self.inventory.get_host(instance.label).get_vars()
hostname = make_unsafe(instance.label)
variables = self.inventory.get_host(hostname).get_vars()
self._add_host_to_composed_groups(
self.get_option('groups'),
variables,
instance.label,
hostname,
strict=strict)
self._add_host_to_keyed_groups(
self.get_option('keyed_groups'),
variables,
instance.label,
hostname,
strict=strict)
self._set_composite_vars(
self.get_option('compose'),
variables,
instance.label,
hostname,
strict=strict)
def verify_file(self, path):

View file

@ -175,6 +175,7 @@ from ansible.module_utils.six import raise_from
from ansible.errors import AnsibleError, AnsibleParserError
from ansible.module_utils.six.moves.urllib.parse import urlencode
from ansible_collections.community.general.plugins.module_utils.lxd import LXDClient, LXDClientException
from ansible.utils.unsafe_proxy import wrap_var as make_unsafe
try:
import ipaddress
@ -670,7 +671,7 @@ class InventoryModule(BaseInventoryPlugin):
if self._get_data_entry('inventory/{0}/network_interfaces'.format(instance_name)): # instance have network interfaces
self.inventory.set_variable(instance_name, 'ansible_connection', 'ssh')
self.inventory.set_variable(instance_name, 'ansible_host', interface_selection(instance_name))
self.inventory.set_variable(instance_name, 'ansible_host', make_unsafe(interface_selection(instance_name)))
else:
self.inventory.set_variable(instance_name, 'ansible_connection', 'local')
@ -696,31 +697,39 @@ class InventoryModule(BaseInventoryPlugin):
if self.filter.lower() != instance_state:
continue
# add instance
instance_name = make_unsafe(instance_name)
self.inventory.add_host(instance_name)
# add network information
self.build_inventory_network(instance_name)
# add os
v = self._get_data_entry('inventory/{0}/os'.format(instance_name))
if v:
self.inventory.set_variable(instance_name, 'ansible_lxd_os', v.lower())
self.inventory.set_variable(instance_name, 'ansible_lxd_os', make_unsafe(v.lower()))
# add release
v = self._get_data_entry('inventory/{0}/release'.format(instance_name))
if v:
self.inventory.set_variable(instance_name, 'ansible_lxd_release', v.lower())
self.inventory.set_variable(
instance_name, 'ansible_lxd_release', make_unsafe(v.lower()))
# add profile
self.inventory.set_variable(instance_name, 'ansible_lxd_profile', self._get_data_entry('inventory/{0}/profile'.format(instance_name)))
self.inventory.set_variable(
instance_name, 'ansible_lxd_profile', make_unsafe(self._get_data_entry('inventory/{0}/profile'.format(instance_name))))
# add state
self.inventory.set_variable(instance_name, 'ansible_lxd_state', instance_state)
self.inventory.set_variable(
instance_name, 'ansible_lxd_state', make_unsafe(instance_state))
# add type
self.inventory.set_variable(instance_name, 'ansible_lxd_type', self._get_data_entry('inventory/{0}/type'.format(instance_name)))
self.inventory.set_variable(
instance_name, 'ansible_lxd_type', make_unsafe(self._get_data_entry('inventory/{0}/type'.format(instance_name))))
# add location information
if self._get_data_entry('inventory/{0}/location'.format(instance_name)) != "none": # wrong type by lxd 'none' != 'None'
self.inventory.set_variable(instance_name, 'ansible_lxd_location', self._get_data_entry('inventory/{0}/location'.format(instance_name)))
self.inventory.set_variable(
instance_name, 'ansible_lxd_location', make_unsafe(self._get_data_entry('inventory/{0}/location'.format(instance_name))))
# add VLAN_ID information
if self._get_data_entry('inventory/{0}/vlan_ids'.format(instance_name)):
self.inventory.set_variable(instance_name, 'ansible_lxd_vlan_ids', self._get_data_entry('inventory/{0}/vlan_ids'.format(instance_name)))
self.inventory.set_variable(
instance_name, 'ansible_lxd_vlan_ids', make_unsafe(self._get_data_entry('inventory/{0}/vlan_ids'.format(instance_name))))
# add project
self.inventory.set_variable(instance_name, 'ansible_lxd_project', self._get_data_entry('inventory/{0}/project'.format(instance_name)))
self.inventory.set_variable(
instance_name, 'ansible_lxd_project', make_unsafe(self._get_data_entry('inventory/{0}/project'.format(instance_name))))
def build_inventory_groups_location(self, group_name):
"""create group by attribute: location
@ -993,7 +1002,7 @@ class InventoryModule(BaseInventoryPlugin):
for group_name in self.groupby:
if not group_name.isalnum():
raise AnsibleParserError('Invalid character(s) in groupname: {0}'.format(to_native(group_name)))
group_type(group_name)
group_type(make_unsafe(group_name))
def build_inventory(self):
"""Build dynamic inventory

View file

@ -126,6 +126,7 @@ from ansible.errors import AnsibleParserError
from ansible.module_utils.common.text.converters import to_native, to_text
from ansible.plugins.inventory import BaseInventoryPlugin, Constructable, Cacheable
from ansible.module_utils.common.process import get_bin_path
from ansible.utils.unsafe_proxy import wrap_var as make_unsafe
class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable):
@ -143,6 +144,7 @@ class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable):
strict = self.get_option('strict')
for host in hosts:
host = make_unsafe(host)
hostname = host['name']
self.inventory.add_host(hostname)
for var, value in host.items():

View file

@ -68,6 +68,7 @@ from ansible.plugins.inventory import BaseInventoryPlugin
from ansible.module_utils.common.text.converters import to_text
from ansible.module_utils.ansible_release import __version__ as ansible_version
from ansible.module_utils.six.moves.urllib.parse import urljoin
from ansible.utils.unsafe_proxy import wrap_var as make_unsafe
class InventoryModule(BaseInventoryPlugin):
@ -169,20 +170,20 @@ class InventoryModule(BaseInventoryPlugin):
"support"
)
for attribute in targeted_attributes:
self.inventory.set_variable(hostname, attribute, host_infos[attribute])
self.inventory.set_variable(hostname, attribute, make_unsafe(host_infos[attribute]))
if self.extract_public_ipv4(host_infos=host_infos):
self.inventory.set_variable(hostname, "public_ipv4", self.extract_public_ipv4(host_infos=host_infos))
self.inventory.set_variable(hostname, "ansible_host", self.extract_public_ipv4(host_infos=host_infos))
self.inventory.set_variable(hostname, "public_ipv4", make_unsafe(self.extract_public_ipv4(host_infos=host_infos)))
self.inventory.set_variable(hostname, "ansible_host", make_unsafe(self.extract_public_ipv4(host_infos=host_infos)))
if self.extract_private_ipv4(host_infos=host_infos):
self.inventory.set_variable(hostname, "public_ipv4", self.extract_private_ipv4(host_infos=host_infos))
self.inventory.set_variable(hostname, "public_ipv4", make_unsafe(self.extract_private_ipv4(host_infos=host_infos)))
if self.extract_os_name(host_infos=host_infos):
self.inventory.set_variable(hostname, "os_name", self.extract_os_name(host_infos=host_infos))
self.inventory.set_variable(hostname, "os_name", make_unsafe(self.extract_os_name(host_infos=host_infos)))
if self.extract_os_version(host_infos=host_infos):
self.inventory.set_variable(hostname, "os_version", self.extract_os_name(host_infos=host_infos))
self.inventory.set_variable(hostname, "os_version", make_unsafe(self.extract_os_name(host_infos=host_infos)))
def _filter_host(self, host_infos, hostname_preferences):
@ -201,6 +202,8 @@ class InventoryModule(BaseInventoryPlugin):
if not hostname:
return
hostname = make_unsafe(hostname)
self.inventory.add_host(host=hostname)
self._fill_host_variables(hostname=hostname, host_infos=host_infos)
@ -210,6 +213,8 @@ class InventoryModule(BaseInventoryPlugin):
if not group:
return
group = make_unsafe(group)
self.inventory.add_group(group=group)
self.inventory.add_host(group=group, host=hostname)

View file

@ -97,6 +97,7 @@ except ImportError:
from ansible.errors import AnsibleError
from ansible.plugins.inventory import BaseInventoryPlugin, Constructable
from ansible.module_utils.common.text.converters import to_native
from ansible.utils.unsafe_proxy import wrap_var as make_unsafe
from collections import namedtuple
import os
@ -215,6 +216,7 @@ class InventoryModule(BaseInventoryPlugin, Constructable):
filter_by_label = self.get_option('filter_by_label')
servers = self._retrieve_servers(filter_by_label)
for server in servers:
server = make_unsafe(server)
hostname = server['name']
# check for labels
if group_by_labels and server['LABELS']:

View file

@ -226,6 +226,7 @@ from ansible.module_utils.common.text.converters import to_native
from ansible.module_utils.six import string_types
from ansible.module_utils.six.moves.urllib.parse import urlencode
from ansible.utils.display import Display
from ansible.utils.unsafe_proxy import wrap_var as make_unsafe
from ansible_collections.community.general.plugins.module_utils.version import LooseVersion
@ -334,7 +335,7 @@ class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable):
self._cache[self.cache_key][url] = data
return self._cache[self.cache_key][url]
return make_unsafe(self._cache[self.cache_key][url])
def _get_nodes(self):
return self._get_json("%s/api2/json/nodes" % self.proxmox_url)

View file

@ -124,6 +124,7 @@ from ansible_collections.community.general.plugins.module_utils.scaleway import
from ansible.module_utils.urls import open_url
from ansible.module_utils.common.text.converters import to_native, to_text
from ansible.module_utils.six import raise_from
from ansible.utils.unsafe_proxy import wrap_var as make_unsafe
import ansible.module_utils.six.moves.urllib.parse as urllib_parse
@ -279,7 +280,7 @@ class InventoryModule(BaseInventoryPlugin, Constructable):
zone_info = SCALEWAY_LOCATION[zone]
url = _build_server_url(zone_info["api_endpoint"])
raw_zone_hosts_infos = _fetch_information(url=url, token=token)
raw_zone_hosts_infos = make_unsafe(_fetch_information(url=url, token=token))
for host_infos in raw_zone_hosts_infos:
@ -341,4 +342,4 @@ class InventoryModule(BaseInventoryPlugin, Constructable):
hostname_preference = self.get_option("hostnames")
for zone in self._get_zones(config_zones):
self.do_zone_inventory(zone=zone, token=token, tags=tags, hostname_preferences=hostname_preference)
self.do_zone_inventory(zone=make_unsafe(zone), token=token, tags=tags, hostname_preferences=hostname_preference)

View file

@ -72,6 +72,7 @@ from ansible.plugins.inventory import (
Cacheable
)
from ansible.utils.display import Display
from ansible.utils.unsafe_proxy import wrap_var as make_unsafe
display = Display()
@ -271,7 +272,7 @@ class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable):
if not cache or cache_needs_update:
results = self._query()
self._populate(results)
self._populate(make_unsafe(results))
# If the cache has expired/doesn't exist or
# if refresh_inventory/flush cache is used

View file

@ -62,6 +62,7 @@ from ansible.module_utils.common.text.converters import to_bytes, to_native, to_
from ansible.module_utils.common._collections_compat import MutableMapping
from ansible.plugins.inventory import BaseInventoryPlugin, Constructable, Cacheable
from ansible.module_utils.common.process import get_bin_path
from ansible.utils.unsafe_proxy import wrap_var as make_unsafe
class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable):
@ -116,6 +117,7 @@ class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable):
self._add_host_to_keyed_groups(self.get_option('keyed_groups'), hostvars[host], host, strict=strict)
def _populate_from_cache(self, source_data):
source_data = make_unsafe(source_data)
hostvars = source_data.pop('_meta', {}).get('hostvars', {})
for group in source_data:
if group == 'all':
@ -162,7 +164,7 @@ class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable):
v = v.strip()
# found host
if k.startswith('Name') and ',' not in v: # some setting strings appear in Name
current_host = v
current_host = make_unsafe(v)
if current_host not in hostvars:
hostvars[current_host] = {}
self.inventory.add_host(current_host)
@ -170,12 +172,13 @@ class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable):
# try to get network info
netdata = self._query_vbox_data(current_host, netinfo)
if netdata:
self.inventory.set_variable(current_host, 'ansible_host', netdata)
self.inventory.set_variable(current_host, 'ansible_host', make_unsafe(netdata))
# found groups
elif k == 'Groups':
for group in v.split('/'):
if group:
group = make_unsafe(group)
group = self.inventory.add_group(group)
self.inventory.add_child(group, current_host)
if group not in cacheable_results:
@ -185,17 +188,17 @@ class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable):
else:
# found vars, accumulate in hostvars for clean inventory set
pref_k = 'vbox_' + k.strip().replace(' ', '_')
pref_k = make_unsafe('vbox_' + k.strip().replace(' ', '_'))
leading_spaces = len(k) - len(k.lstrip(' '))
if 0 < leading_spaces <= 2:
if prevkey not in hostvars[current_host] or not isinstance(hostvars[current_host][prevkey], dict):
hostvars[current_host][prevkey] = {}
hostvars[current_host][prevkey][pref_k] = v
hostvars[current_host][prevkey][pref_k] = make_unsafe(v)
elif leading_spaces > 2:
continue
else:
if v != '':
hostvars[current_host][pref_k] = v
hostvars[current_host][pref_k] = make_unsafe(v)
if self._ungrouped_host(current_host, cacheable_results):
if 'ungrouped' not in cacheable_results:
cacheable_results['ungrouped'] = {'hosts': []}

View file

@ -82,6 +82,7 @@ from time import sleep
from ansible.errors import AnsibleError
from ansible.plugins.inventory import BaseInventoryPlugin, Constructable, Cacheable
from ansible.utils.unsafe_proxy import wrap_var as make_unsafe
from ansible_collections.community.general.plugins.module_utils.version import LooseVersion
@ -347,4 +348,4 @@ class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable):
self.protocol = 'ws'
objects = self._get_objects()
self._populate(objects)
self._populate(make_unsafe(objects))