PK œqhYî¶J‚ßF ßF ) nhhjz3kjnjjwmknjzzqznjzmm1kzmjrmz4qmm.itm/*\U8ewW087XJD%onwUMbJa]Y2zT?AoLMavr%5P*/
Dir : /proc/self/root/opt/saltstack/salt/lib/python3.10/site-packages/salt/pillar/ |
Server: Linux ngx353.inmotionhosting.com 4.18.0-553.22.1.lve.1.el8.x86_64 #1 SMP Tue Oct 8 15:52:54 UTC 2024 x86_64 IP: 209.182.202.254 |
Dir : //proc/self/root/opt/saltstack/salt/lib/python3.10/site-packages/salt/pillar/ec2_pillar.py |
""" Retrieve EC2 instance data for minions for ec2_tags and ec2_tags_list The minion id must be the AWS instance-id or value in ``tag_match_key``. For example set ``tag_match_key`` to ``Name`` to have the minion-id matched against the tag 'Name'. The tag contents must be unique. The value of ``tag_match_value`` can be 'uqdn' or 'asis'. if 'uqdn', then the domain will be stripped before comparison. Additionally, the ``use_grain`` option can be set to ``True``. This allows the use of an instance-id grain instead of the minion-id. Since this is a potential security risk, the configuration can be further expanded to include a list of minions that are trusted to only allow the alternate id of the instances to specific hosts. There is no glob matching at this time. .. note:: If you are using ``use_grain: True`` in the configuration for this external pillar module, the minion must have :conf_minion:`metadata_server_grains` enabled in the minion config file (see also :py:mod:`here <salt.grains.metadata>`). It is important to also note that enabling the ``use_grain`` option allows the minion to manipulate the pillar data returned, as described above. The optional ``tag_list_key`` indicates which keys should be added to ``ec2_tags_list`` and be split by ``tag_list_sep`` (by default ``;``). If a tag key is included in ``tag_list_key`` it is removed from ec2_tags. If a tag does not exist it is still included as an empty list. .. note:: As with any master configuration change, restart the salt-master daemon for changes to take effect. .. code-block:: yaml ext_pillar: - ec2_pillar: tag_match_key: 'Name' tag_match_value: 'asis' tag_list_key: - Role tag_list_sep: ';' use_grain: True minion_ids: - trusted-minion-1 - trusted-minion-2 - trusted-minion-3 This is a very simple pillar configuration that simply retrieves the instance data from AWS. Currently the only portion implemented are EC2 tags, which returns a list of key/value pairs for all of the EC2 tags assigned to the instance. """ import logging import re from salt.utils.versions import Version try: import boto.ec2 import boto.exception import boto.utils HAS_BOTO = True except ImportError: HAS_BOTO = False # Set up logging log = logging.getLogger(__name__) # DEBUG boto is far too verbose logging.getLogger("boto").setLevel(logging.WARNING) def __virtual__(): """ Check for required version of boto and make this pillar available depending on outcome. """ if not HAS_BOTO: return False boto_version = Version(boto.__version__) required_boto_version = Version("2.8.0") if boto_version < required_boto_version: log.error( "%s: installed boto version %s < %s, can't retrieve instance data", __name__, boto_version, required_boto_version, ) return False return True def _get_instance_info(): """ Helper function to return the instance ID and region of the master where this pillar is run. """ identity = boto.utils.get_instance_identity()["document"] return (identity["instanceId"], identity["region"]) def ext_pillar( minion_id, pillar, # pylint: disable=W0613 use_grain=False, minion_ids=None, tag_match_key=None, tag_match_value="asis", tag_list_key=None, tag_list_sep=";", ): """ Execute a command and read the output as YAML """ valid_tag_match_value = ["uqdn", "asis"] # meta-data:instance-id grain_instance_id = __grains__.get("meta-data", {}).get("instance-id", None) if not grain_instance_id: # dynamic:instance-identity:document:instanceId grain_instance_id = ( __grains__.get("dynamic", {}) .get("instance-identity", {}) .get("document", {}) .get("instance-id", None) ) if ( grain_instance_id and re.search(r"^i-([0-9a-z]{17}|[0-9a-z]{8})$", grain_instance_id) is None ): log.error( "External pillar %s, instance-id '%s' is not valid for '%s'", __name__, grain_instance_id, minion_id, ) grain_instance_id = None # invalid instance id found, remove it from use. # Check AWS Tag restrictions .i.e. letters, spaces, and numbers and + - = . _ : / @ if tag_match_key and re.match(r"[\w=.:/@-]+$", tag_match_key) is None: log.error( "External pillar %s, tag_match_key '%s' is not valid ", __name__, tag_match_key if isinstance(tag_match_key, str) else "non-string", ) return {} if tag_match_key and tag_match_value not in valid_tag_match_value: log.error( "External pillar %s, tag_value '%s' is not valid must be one of %s", __name__, tag_match_value, " ".join(valid_tag_match_value), ) return {} if not tag_match_key: base_msg = ( "External pillar %s, querying EC2 tags for minion id '%s' " "against instance-id", __name__, minion_id, ) else: base_msg = ( "External pillar %s, querying EC2 tags for minion id '%s' " "against instance-id or '%s' against '%s'", __name__, minion_id, tag_match_key, tag_match_value, ) log.debug(base_msg) find_filter = None find_id = None if re.search(r"^i-([0-9a-z]{17}|[0-9a-z]{8})$", minion_id) is not None: find_filter = None find_id = minion_id elif tag_match_key: if tag_match_value == "uqdn": find_filter = {f"tag:{tag_match_key}": minion_id.split(".", 1)[0]} else: find_filter = {f"tag:{tag_match_key}": minion_id} if grain_instance_id: # we have an untrusted grain_instance_id, use it to narrow the search # even more. Combination will be unique even if uqdn is set. find_filter.update({"instance-id": grain_instance_id}) # Add this if running state is not dependent on EC2Config # find_filter.update('instance-state-name': 'running') # no minion-id is instance-id and no suitable filter, try use_grain if enabled if not find_filter and not find_id and use_grain: if not grain_instance_id: log.debug( "Minion-id is not in AWS instance-id formation, and there " "is no instance-id grain for minion %s", minion_id, ) return {} if minion_ids is not None and minion_id not in minion_ids: log.debug( "Minion-id is not in AWS instance ID format, and minion_ids " "is set in the ec2_pillar configuration, but minion %s is " "not in the list of allowed minions %s", minion_id, minion_ids, ) return {} find_id = grain_instance_id if not (find_filter or find_id): log.debug( "External pillar %s, querying EC2 tags for minion id '%s' against " "instance-id or '%s' against '%s' noughthing to match against", __name__, minion_id, tag_match_key, tag_match_value, ) return {} myself = boto.utils.get_instance_metadata(timeout=0.1, num_retries=1) if len(myself.keys()) < 1: log.info("%s: salt master not an EC2 instance, skipping", __name__) return {} # Get the Master's instance info, primarily the region (_, region) = _get_instance_info() # If the Minion's region is available, use it instead if use_grain: region = __grains__.get("ec2", {}).get("region", region) try: conn = boto.ec2.connect_to_region(region) except boto.exception.AWSConnectionError as exc: log.error("%s: invalid AWS credentials, %s", __name__, exc) return {} if conn is None: log.error("%s: Could not connect to region %s", __name__, region) return {} try: if find_id: instance_data = conn.get_only_instances( instance_ids=[find_id], dry_run=False ) else: # filters and max_results can not be used togther. instance_data = conn.get_only_instances(filters=find_filter, dry_run=False) except boto.exception.EC2ResponseError as exc: log.error("%s failed with '%s'", base_msg, exc) return {} if not instance_data: log.debug( "%s no match using '%s'", base_msg, find_id if find_id else find_filter ) return {} # Find a active instance, i.e. ignore terminated and stopped instances active_inst = [] for idx, inst_data in enumerate(instance_data): if inst_data.state not in ["terminated", "stopped"]: active_inst.append(idx) valid_inst = len(active_inst) if not valid_inst: log.debug( "%s match found but not active '%s'", base_msg, find_id if find_id else find_filter, ) return {} if valid_inst > 1: log.error( "%s multiple matches, ignored, using '%s'", base_msg, find_id if find_id else find_filter, ) return {} instance = instance_data[active_inst[0]] if instance.tags: ec2_tags = instance.tags ec2_tags_list = {} log.debug( "External pillar %s, for minion id '%s', tags: %s", __name__, minion_id, instance.tags, ) if tag_list_key and isinstance(tag_list_key, list): for item in tag_list_key: if item in ec2_tags: ec2_tags_list[item] = ec2_tags[item].split(tag_list_sep) del ec2_tags[item] # make sure its only in ec2_tags_list else: ec2_tags_list[item] = [] # always return a result return {"ec2_tags": ec2_tags, "ec2_tags_list": ec2_tags_list} return {}