Esempio n. 1
0
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
Esempio n. 2
0
    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
Esempio n. 3
0
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
Esempio n. 4
0
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
Esempio n. 5
0
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")
Esempio n. 6
0
    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"
                )
Esempio n. 7
0
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
Esempio n. 8
0
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)
Esempio n. 9
0
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")
Esempio n. 10
0
def _salt_configuration_error(filename):
    '''
    Raise an error to indicate error in the Salt configuration file
    '''
    raise SaltConfigurationError('Configuration error in {0}'.format(filename))
Esempio n. 11
0
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}