diff --git a/salt/orch/delete_hypervisor.sls b/salt/orch/delete_hypervisor.sls index 3f0bd02b6..784977e28 100644 --- a/salt/orch/delete_hypervisor.sls +++ b/salt/orch/delete_hypervisor.sls @@ -3,7 +3,14 @@ # https://securityonion.net/license; you may not use this file except in compliance with the # Elastic License 2.0. -{% set hypervisor = pillar.minion_id %} +{% set hypervisor = pillar.get('minion_id', '') %} + +{% if not hypervisor|regex_match('^[A-Za-z0-9._-]{1,253}$') %} +{% do salt.log.error('delete_hypervisor_orch: refusing unsafe minion_id=' ~ hypervisor) %} +delete_hypervisor_invalid_minion_id: + test.fail_without_changes: + - name: delete_hypervisor_invalid_minion_id +{% else %} ensure_hypervisor_mine_deleted: salt.function: @@ -20,3 +27,5 @@ update_salt_cloud_profile: - sls: - salt.cloud.config - concurrent: True + +{% endif %} diff --git a/salt/orch/vm_pillar_clean.sls b/salt/orch/vm_pillar_clean.sls index ca5c16054..9b6bf1ee5 100644 --- a/salt/orch/vm_pillar_clean.sls +++ b/salt/orch/vm_pillar_clean.sls @@ -12,7 +12,14 @@ {% if 'vrt' in salt['pillar.get']('features', []) %} {% do salt.log.debug('vm_pillar_clean_orch: Running') %} -{% set vm_name = pillar.get('vm_name') %} +{% set vm_name = pillar.get('vm_name', '') %} + +{% if not vm_name|regex_match('^[A-Za-z0-9._-]{1,253}$') %} +{% do salt.log.error('vm_pillar_clean_orch: refusing unsafe vm_name=' ~ vm_name) %} +vm_pillar_clean_invalid_name: + test.fail_without_changes: + - name: vm_pillar_clean_invalid_name +{% else %} delete_adv_{{ vm_name }}_pillar: module.run: @@ -24,6 +31,8 @@ delete_{{ vm_name }}_pillar: - file.remove: - path: /opt/so/saltstack/local/pillar/minions/{{ vm_name }}.sls +{% endif %} + {% else %} {% do salt.log.error( diff --git a/salt/reactor/check_hypervisor.sls b/salt/reactor/check_hypervisor.sls index 91b7c0c02..c0fa49ddc 100644 --- a/salt/reactor/check_hypervisor.sls +++ b/salt/reactor/check_hypervisor.sls @@ -3,12 +3,15 @@ # https://securityonion.net/license; you may not use this file except in compliance with the # Elastic License 2.0. -{% if data['id'].endswith('_hypervisor') and data['result'] == True %} +{% set hid = data['id'] %} +{% if hid|regex_match('^[A-Za-z0-9._-]{1,253}$') + and hid.endswith('_hypervisor') + and data['result'] == True %} {% if data['act'] == 'accept' %} check_and_trigger: runner.setup_hypervisor.setup_environment: - - minion_id: {{ data['id'] }} + - minion_id: {{ hid }} {% endif %} {% if data['act'] == 'delete' %} @@ -17,8 +20,7 @@ delete_hypervisor: - args: - mods: orch.delete_hypervisor - pillar: - minion_id: {{ data['id'] }} + minion_id: {{ hid }} {% endif %} {% endif %} - diff --git a/salt/reactor/createEmptyPillar.sls b/salt/reactor/createEmptyPillar.sls index c6c655bab..2076b53bd 100644 --- a/salt/reactor/createEmptyPillar.sls +++ b/salt/reactor/createEmptyPillar.sls @@ -1,7 +1,7 @@ #!py # Copyright Security Onion Solutions LLC and/or licensed to Security Onion Solutions LLC under one -# or more contributor license agreements. Licensed under the Elastic License 2.0 as shown at +# or more contributor license agreements. Licensed under the Elastic License 2.0 as shown at # https://securityonion.net/license; you may not use this file except in compliance with the # Elastic License 2.0. @@ -9,30 +9,42 @@ import logging import os import pwd import grp +import re + +log = logging.getLogger(__name__) + +PILLAR_ROOT = '/opt/so/saltstack/local/pillar/minions/' +_VMNAME_RE = re.compile(r'^[A-Za-z0-9._-]{1,253}$') + def run(): - vm_name = data['kwargs']['name'] - logging.error("createEmptyPillar reactor: vm_name: %s" % vm_name) - pillar_root = '/opt/so/saltstack/local/pillar/minions/' + vm_name = data.get('kwargs', {}).get('name', '') + if not _VMNAME_RE.match(str(vm_name)): + log.error("createEmptyPillar reactor: refusing unsafe vm_name=%r", vm_name) + return {} + + log.info("createEmptyPillar reactor: vm_name: %s", vm_name) pillar_files = ['adv_' + vm_name + '.sls', vm_name + '.sls'] try: - # Get socore user and group IDs socore_uid = pwd.getpwnam('socore').pw_uid socore_gid = grp.getgrnam('socore').gr_gid + pillar_root_real = os.path.realpath(PILLAR_ROOT) for f in pillar_files: - full_path = pillar_root + f - if not os.path.exists(full_path): - # Create empty file - os.mknod(full_path) - # Set ownership to socore:socore - os.chown(full_path, socore_uid, socore_gid) - # Set mode to 644 (rw-r--r--) - os.chmod(full_path, 0o640) - logging.error("createEmptyPillar reactor: created %s with socore:socore ownership and mode 644" % f) + full_path = os.path.join(PILLAR_ROOT, f) + resolved = os.path.realpath(full_path) + if os.path.dirname(resolved) != pillar_root_real: + log.error("createEmptyPillar reactor: refusing path outside pillar root: %s", resolved) + continue + if os.path.exists(resolved): + continue + os.mknod(resolved) + os.chown(resolved, socore_uid, socore_gid) + os.chmod(resolved, 0o640) + log.info("createEmptyPillar reactor: created %s with socore:socore ownership and mode 0640", f) except (KeyError, OSError) as e: - logging.error("createEmptyPillar reactor: Error setting ownership/permissions: %s" % str(e)) + log.error("createEmptyPillar reactor: Error setting ownership/permissions: %s", e) return {} diff --git a/salt/reactor/deleteKey.sls b/salt/reactor/deleteKey.sls index 4d522a4b5..a57d6370c 100644 --- a/salt/reactor/deleteKey.sls +++ b/salt/reactor/deleteKey.sls @@ -1,18 +1,40 @@ +#!py + # Copyright Security Onion Solutions LLC and/or licensed to Security Onion Solutions LLC under one # or more contributor license agreements. Licensed under the Elastic License 2.0 as shown at # https://securityonion.net/license; you may not use this file except in compliance with the # Elastic License 2.0. -remove_key: - wheel.key.delete: - - args: - - match: {{ data['name'] }} +import logging +import re -{{ data['name'] }}_pillar_clean: - runner.state.orchestrate: - - args: - - mods: orch.vm_pillar_clean - - pillar: - vm_name: {{ data['name'] }} +log = logging.getLogger(__name__) -{% do salt.log.info('deleteKey reactor: deleted minion key: %s' % data['name']) %} +_VMNAME_RE = re.compile(r'^[A-Za-z0-9._-]{1,253}$') + + +def run(): + name = data.get('name', '') + if not _VMNAME_RE.match(str(name)): + log.error("deleteKey reactor: refusing unsafe name=%r", name) + return {} + + log.info("deleteKey reactor: deleted minion key: %s", name) + + return { + 'remove_key': { + 'wheel.key.delete': [ + {'args': [ + {'match': name}, + ]}, + ], + }, + '%s_pillar_clean' % name: { + 'runner.state.orchestrate': [ + {'args': [ + {'mods': 'orch.vm_pillar_clean'}, + {'pillar': {'vm_name': name}}, + ]}, + ], + }, + }