def _session(): """ Return the boto3 session to use for the KMS client. If aws_kms:profile_name is set in the salt configuration, use that profile. Otherwise, fall back on the default aws profile. We use the boto3 profile system to avoid having to duplicate individual boto3 configuration settings in salt configuration. """ profile_name = _cfg("profile_name") if profile_name: log.info('Using the "%s" aws profile.', profile_name) else: log.info( "aws_kms:profile_name is not set in salt. Falling back on default profile." ) try: return boto3.Session(profile_name=profile_name) except botocore.exceptions.ProfileNotFound as orig_exc: raise SaltConfigurationError( 'Boto3 could not find the "{}" profile configured in Salt.'.format( profile_name or "default" ) ) from orig_exc except botocore.exceptions.NoRegionError as orig_exc: raise SaltConfigurationError( "Boto3 was unable to determine the AWS " "endpoint region using the {} profile.".format(profile_name or "default") ) from orig_exc
def _set_function(self, function): ''' Based on the configuration, set to execute an old or a new function. :return: ''' full_name = "{m_name}.{f_name}".format( m_name=self._globals.get(self.MODULE_NAME, '') or self._globals['__name__'].split('.')[-1], f_name=function.__name__) if full_name.startswith("."): self._raise_later = CommandExecutionError( 'Module not found for function "{f_name}"'.format( f_name=function.__name__)) opts = self._globals.get('__opts__', '{}') use_deprecated = full_name in opts.get(self.CFG_USE_DEPRECATED, list()) use_superseded = full_name in opts.get(self.CFG_USE_SUPERSEDED, list()) if use_deprecated and use_superseded: raise SaltConfigurationError( "Function '{0}' is mentioned both in deprecated " "and superseded sections. Please remove any of that.".format( full_name)) old_function = self._globals.get(self._with_name or "_{0}".format(function.__name__)) if self._policy == self.OPT_IN: self._function = function if use_superseded else old_function else: self._function = old_function if use_deprecated else function
def get(key, service=None, profile=None): # pylint: disable=W0613 ''' Get a decrypted secret from the tISMd API ''' if not profile.get('url') or not profile.get('token'): raise SaltConfigurationError( "url and/or token missing from the tism sdb profile") request = {"token": profile['token'], "encsecret": key} result = http.query( profile['url'], method='POST', data=salt.utils.json.dumps(request), ) decrypted = result.get('body') if not decrypted: log.warning('tism.get sdb decryption request failed with error %s', result.get('error', 'unknown')) return 'ERROR' + six.text_type(result.get('status', 'unknown')) return decrypted
def get(key, service=None, profile=None): # pylint: disable=W0613 """ Get a decrypted secret from the tISMd API """ if not profile.get("url") or not profile.get("token"): raise SaltConfigurationError( "url and/or token missing from the tism sdb profile") request = {"token": profile["token"], "encsecret": key} result = http.query( profile["url"], method="POST", data=salt.utils.json.dumps(request), ) decrypted = result.get("body") if not decrypted: log.warning( "tism.get sdb decryption request failed with error %s", result.get("error", "unknown"), ) return "ERROR" + six.text_type(result.get("status", "unknown")) return decrypted
def _cfg_data_key(): """ Return the encrypted KMS data key from configuration. Raises SaltConfigurationError if not set. """ data_key = _cfg("data_key", "") if data_key: return data_key raise SaltConfigurationError("aws_kms:data_key is not set")
def __init__(self, name, module, **kwargs): self.name = name self.module = module self.kwargs = kwargs self.tokens = None for method in [f"{module}.install", f"{module}.remove"]: if method not in __salt__: raise SaltConfigurationError( f"ACME: Invalid resolver {name}: execution module {method} not found" )
def _api_decrypt(): """ Return the response dictionary from the KMS decrypt API call. """ kms = _kms() data_key = _cfg_data_key() try: return kms.decrypt(CiphertextBlob=data_key) except botocore.exceptions.ClientError as orig_exc: error_code = orig_exc.response.get("Error", {}).get("Code", "") if error_code != "InvalidCiphertextException": raise raise SaltConfigurationError( "aws_kms:data_key is not a valid KMS data key") from orig_exc
def sign(csr): """ Requests to sign a CSR using dehydrated. csr: Certificate signing request as PEM-encoded string. """ # Restores newlines in a PEM files that might have been lost by salt # argument parsing. match = _REGEXP_CSR.match(csr) if match: _, head, body, tail = match.groups() csr = "\n".join([head, *body.strip().split(" "), tail]) requested = _extract_domain_names(csr) auth_file = traverse_dict_and_list(__opts__, "acme:runner:auth_file", None) if auth_file: logging.debug("Use auth_file from %s", auth_file) with fopen(auth_file, "r") as f: auth = yaml.safe_load(f) if not isinstance(auth, dict): raise SaltConfigurationError("Invalid auth_file: must be a dict") logging.debug("Authorizing domain names for %s: %s", __opts__["id"], requested) for pattern, auth in auth.items(): if not fnmatch.fnmatch(__opts__["id"], pattern): continue for name in requested.copy(): for rule in auth: if fnmatch.fnmatch(name, rule): requested.remove(name) if requested: raise AuthorizationError( f"Unauthorized domains: {', '.join(requested)}") return __salt__["salt.cmd"]("acme.sign", csr)
def _verify(nameserver, port, zone, verify_timeout=120, **_kwargs): """ Verify all nameservers listed as NS in `zone` serve the current or a newer SOA serial. """ # Use primary nameserver for NS lookup and as first resolver # to handle local or split-horizon scenarios resolver = dns.resolver.Resolver(configure=False) try: ipaddress.ip_address(nameserver) resolver.nameservers = [nameserver] except ValueError as e: resolver.nameservers = _query_addresses(nameserver) if not resolver.nameservers: raise SaltConfigurationError( f"Nameserver not found: {nameserver}") from e # All resolved address of the primary NS must use the configured port resolver.nameserver_ports.update({ns: port for ns in resolver.nameservers}) # The public resolver first tries the primary NS first, otherwise falls # back to the system resolver. This is used to lookup e.g. other NS names # which might not be served by the primary. public = dns.resolver.Resolver() public.nameservers = resolver.nameservers + public.nameservers public.nameserver_ports.update(resolver.nameserver_ports) # Verify SOA serial propagation to all nameserver serial = resolver.query(zone, "SOA")[0].serial deadline = time.monotonic() + verify_timeout # Collect all NS records of the zone. We explicitly use the primary NS # as the system resolver might serve internal NS in a split-horizon setup. nameservers = [] resolvers = {} for rdata in resolver.query(zone, "NS", raise_on_no_answer=False): name = rdata.target.to_unicode() resolvers[name] = dns.resolver.Resolver(configure=False) resolvers[name].nameservers = _query_addresses(name, resolver=public) nameservers.append(name) if not nameservers: _LOG.warning("Skip DNS record verify: No nameservers found for %s", zone) return _LOG.info("Verify SOA serial %d for %d nameservers...", serial, len(nameservers)) while deadline > time.monotonic(): for ns in nameservers[:]: ns_serial = resolvers[ns].query(zone, "SOA")[0].serial if ns_serial < serial: _LOG.debug("Nameserver %s still at %d...", ns, ns_serial) else: nameservers.remove(ns) if nameservers: _LOG.debug("%d nameservers still pending...", len(nameservers)) time.sleep(0.5) else: _LOG.debug("All nameservers up-to-date!") break if nameservers: _LOG.error("Nameserver failed to update: %s", nameservers) raise SaltTimeoutError("Some nameserver failed to receive DNS updates")
def _salt_configuration_error(filename): ''' Raise an error to indicate error in the Salt configuration file ''' raise SaltConfigurationError('Configuration error in {0}'.format(filename))
def ext_pillar(minion_id, pillar, **pillarconfig): # type: (str, str, Dict[str, Any]) -> Dict[str, Dict[str, Union[str, Dict[str, str]]]] db = __salt__['dynamicsecrets.get_store']() # type: DynamicSecretsPillar if minion_id == __opts__['id']: if minion_id.endswith("_master"): minion_id = minion_id[0:-7] else: if 'dynamicsecrets.master_host_value' in __opts__: minion_id = __opts__['dynamicsecrets.master_host_value'] else: from salt.exceptions import SaltConfigurationError raise SaltConfigurationError( "If you configure your master 'id', you must set " "'dynamicsecrets.master_host_value' so dynamicsecrets can map secrets " "generated on the master to the correct minion's host name." ) # make sure all required secrets exist and filter them # according to the current minion's roles or host id this_node_secrets = {} if "config" not in pillarconfig: pillarconfig["config"] = {} if "grainmapping" not in pillarconfig: pillarconfig["grainmapping"] = {} if "pillarmapping" not in pillarconfig: pillarconfig["pillarmapping"] = {} if "hostmapping" not in pillarconfig: pillarconfig["hostmapping"] = {} for grain in pillarconfig["grainmapping"]: for grainvalue in pillarconfig["grainmapping"][grain]: nodevalues = __grains__.get(grain, []) _log.debug("dynamicsecrets matching %s=%s in %s", grain, grainvalue, nodevalues) # "*" matches every grainvalue as long as there is at least one value if nodevalues and grainvalue == "*" or grainvalue in nodevalues: for secret_name in pillarconfig["grainmapping"][grain][ grainvalue]: _log.debug( "adding secret %s to dynamicsecrets for grain match %s=%s", secret_name, grain, grainvalue) secret_config = pillarconfig["config"].get(secret_name, {}) host = "*" if secret_name in pillarconfig["config"]: if "unique-per-host" in pillarconfig["config"][secret_name] and \ pillarconfig["config"][secret_name]["unique-per-host"]: host = minion_id if secret_name is None: _log.error( "dynamicsecrets created None secret_name for data %s in %s", grain, gm) continue if secret_name not in this_node_secrets: this_node_secrets[secret_name] = db.get_or_create( secret_config, secret_name, host) for pillar in pillarconfig["pillarmapping"]: for pillarvalue in pillarconfig["pillarmapping"][pillar]: nodevalues = __pillars__.get(pillar, []) # "*" matches every grainvalue as long as there is at least one value if nodevalues and pillarvalue == "*" or pillarvalue in nodevalues: for secret_name in pillarconfig["pillarmapping"][pillar][ pillarvalue]: secret_config = pillarconfig["config"].get(secret_name, {}) host = "*" if secret_name in pillarconfig["config"]: if "unique-per-host" in pillarconfig["config"][secret_name] and \ pillarconfig["config"][secret_name]["unique-per-host"]: host = minion_id if secret_name is None: _log.error( "dynamicsecrets created None secret_name for data %s in %s", pillar, pillarvalue) continue if secret_name not in this_node_secrets: this_node_secrets[secret_name] = db.get_or_create( secret_config, secret_name, host) minion_match_keys = __salt__['dynamicsecrets.match_minion_id']( minion_id, pillarconfig["hostmapping"]) for minion_match_key in minion_match_keys: for secret_name in pillarconfig["hostmapping"][minion_match_key]: secret_config = pillarconfig["config"].get(secret_name, {}) host = "*" if secret_name in pillarconfig["config"]: if "unique-per-host" in pillarconfig["config"][secret_name] and \ pillarconfig["config"][secret_name]["unique-per-host"]: host = minion_id if secret_name is None: _log.error( "dynamicsecrets created None secret_name for data %s/%s in %s", minion_match_key, minion_id, pillarconfig["hostmapping"][minion_match_key]) continue if secret_name not in this_node_secrets: this_node_secrets[secret_name] = db.get_or_create( secret_config, secret_name, host) return {"dynamicsecrets": this_node_secrets}