Ejemplo n.º 1
0
def _verify_assertion_binary_is_installed():
    """Make sure the specified xmlsec binary is installed.

    If the binary specified in configuration isn't installed, make sure we
    leave some sort of useful error message for operators since the absense of
    it is going to throw an HTTP 500.

    """
    try:
        # `check_output` just returns the output of whatever is passed in
        # (hence the name). We don't really care about where the location of
        # the binary exists, though. We just want to make sure it's actually
        # installed and if an `CalledProcessError` isn't thrown, it is.
        subprocess.check_output(  # nosec : The contents of this command are
            # coming from either the default
            # configuration value for
            # CONF.saml.xmlsec1_binary or an operator
            # supplied location for that binary. In
            # either case, it is safe to assume this
            # input is coming from a trusted source and
            # not a possible attacker (over the API).
            ['/usr/bin/which', CONF.saml.xmlsec1_binary])
    except subprocess.CalledProcessError:
        msg = (
            'Unable to locate %(binary)s binary on the system. Check to make '
            'sure it is installed.') % {
                'binary': CONF.saml.xmlsec1_binary
            }
        tr_msg = _('Unable to locate %(binary)s binary on the system. Check to'
                   'make sure it is installed.') % {
                       'binary': CONF.saml.xmlsec1_binary
                   }
        LOG.error(msg)
        raise exception.SAMLSigningError(reason=tr_msg)
Ejemplo n.º 2
0
def _sign_assertion(assertion):
    """Sign a SAML assertion.

    This method utilizes ``xmlsec1`` binary and signs SAML assertions in a
    separate process. ``xmlsec1`` cannot read input data from stdin so the
    prepared assertion needs to be serialized and stored in a temporary
    file. This file will be deleted immediately after ``xmlsec1`` returns.
    The signed assertion is redirected to a standard output and read using
    subprocess.PIPE redirection. A ``saml.Assertion`` class is created
    from the signed string again and returned.

    Parameters that are required in the CONF::
    * xmlsec_binary
    * private key file path
    * public key file path
    :return: XML <Assertion> object

    """
    xmlsec_binary = CONF.saml.xmlsec1_binary
    idp_private_key = CONF.saml.keyfile
    idp_public_key = CONF.saml.certfile

    # xmlsec1 --sign --privkey-pem privkey,cert --id-attr:ID <tag> <file>
    certificates = '%(idp_private_key)s,%(idp_public_key)s' % {
        'idp_public_key': idp_public_key,
        'idp_private_key': idp_private_key
    }

    command_list = [
        xmlsec_binary, '--sign', '--privkey-pem', certificates, '--id-attr:ID',
        'Assertion'
    ]

    try:
        # NOTE(gyee): need to make the namespace prefixes explicit so
        # they won't get reassigned when we wrap the assertion into
        # SAML2 response
        file_path = fileutils.write_to_tempfile(
            assertion.to_string(nspair={
                'saml': saml2.NAMESPACE,
                'xmldsig': xmldsig.NAMESPACE
            }))
        command_list.append(file_path)
        stdout = subprocess.check_output(command_list)
    except Exception as e:
        msg = _LE('Error when signing assertion, reason: %(reason)s')
        msg = msg % {'reason': e}
        LOG.error(msg)
        raise exception.SAMLSigningError(reason=e)
    finally:
        try:
            os.remove(file_path)
        except OSError:
            pass

    return saml2.create_class_from_xml_string(saml.Assertion, stdout)
Ejemplo n.º 3
0
def _sign_assertion(assertion):
    """Sign a SAML assertion.

    This method utilizes ``xmlsec1`` binary and signs SAML assertions in a
    separate process. ``xmlsec1`` cannot read input data from stdin so the
    prepared assertion needs to be serialized and stored in a temporary file.
    This file will be deleted immediately after ``xmlsec1`` returns. The signed
    assertion is redirected to a standard output and read using
    ``subprocess.PIPE`` redirection. A ``saml.Assertion`` class is created from
    the signed string again and returned.

    Parameters that are required in the CONF::
    * xmlsec_binary
    * private key file path
    * public key file path
    :returns: XML <Assertion> object

    """
    # Ensure that the configured certificate paths do not contain any commas,
    # before we string format a comma in between them and cause xmlsec1 to
    # explode like a thousand fiery supernovas made entirely of unsigned SAML.
    for option in ('keyfile', 'certfile'):
        if ',' in getattr(CONF.saml, option, ''):
            raise exception.UnexpectedError(
                'The configuration value in `keystone.conf [saml] %s` cannot '
                'contain a comma (`,`). Please fix your configuration.' %
                option)

    # xmlsec1 --sign --privkey-pem privkey,cert --id-attr:ID <tag> <file>
    certificates = '%(idp_private_key)s,%(idp_public_key)s' % {
        'idp_public_key': CONF.saml.certfile,
        'idp_private_key': CONF.saml.keyfile,
    }

    # Verify that the binary used to create the assertion actually exists on
    # the system. If it doesn't, log a warning for operators to go and install
    # it. Requests for assertions will fail with HTTP 500s until the package is
    # installed, so providing something useful in the logs is about the best we
    # can do.
    _verify_assertion_binary_is_installed()

    command_list = [
        CONF.saml.xmlsec1_binary, '--sign', '--privkey-pem', certificates,
        '--id-attr:ID', 'Assertion'
    ]

    file_path = None
    try:
        # NOTE(gyee): need to make the namespace prefixes explicit so
        # they won't get reassigned when we wrap the assertion into
        # SAML2 response
        file_path = fileutils.write_to_tempfile(
            assertion.to_string(nspair={
                'saml': saml2.NAMESPACE,
                'xmldsig': xmldsig.NAMESPACE
            }))
        command_list.append(file_path)
        stdout = subprocess.check_output(
            command_list,  # nosec : The contents
            # of the command list are coming from
            # a trusted source because the
            # executable and arguments all either
            # come from the config file or are
            # hardcoded. The command list is
            # initialized earlier in this function
            # to a list and it's still a list at
            # this point in the function. There is
            # no opportunity for an attacker to
            # attempt command injection via string
            # parsing.
            stderr=subprocess.STDOUT)
    except Exception as e:
        msg = 'Error when signing assertion, reason: %(reason)s%(output)s'
        LOG.error(msg, {
            'reason': e,
            'output': ' ' + e.output if hasattr(e, 'output') else ''
        })
        raise exception.SAMLSigningError(reason=e)
    finally:
        try:
            if file_path:
                os.remove(file_path)
        except OSError:  # nosec
            # The file is already gone, good.
            pass

    return saml2.create_class_from_xml_string(saml.Assertion, stdout)
Ejemplo n.º 4
0
def _sign_assertion(assertion):
    """Sign a SAML assertion.

    This method utilizes ``xmlsec1`` binary and signs SAML assertions in a
    separate process. ``xmlsec1`` cannot read input data from stdin so the
    prepared assertion needs to be serialized and stored in a temporary
    file. This file will be deleted immediately after ``xmlsec1`` returns.
    The signed assertion is redirected to a standard output and read using
    subprocess.PIPE redirection. A ``saml.Assertion`` class is created
    from the signed string again and returned.

    Parameters that are required in the CONF::
    * xmlsec_binary
    * private key file path
    * public key file path
    :returns: XML <Assertion> object

    """
    xmlsec_binary = CONF.saml.xmlsec1_binary
    idp_private_key = CONF.saml.keyfile
    idp_public_key = CONF.saml.certfile

    # xmlsec1 --sign --privkey-pem privkey,cert --id-attr:ID <tag> <file>
    certificates = '%(idp_private_key)s,%(idp_public_key)s' % {
        'idp_public_key': idp_public_key,
        'idp_private_key': idp_private_key
    }

    command_list = [
        xmlsec_binary, '--sign', '--privkey-pem', certificates, '--id-attr:ID',
        'Assertion'
    ]

    file_path = None
    try:
        # NOTE(gyee): need to make the namespace prefixes explicit so
        # they won't get reassigned when we wrap the assertion into
        # SAML2 response
        file_path = fileutils.write_to_tempfile(
            assertion.to_string(nspair={
                'saml': saml2.NAMESPACE,
                'xmldsig': xmldsig.NAMESPACE
            }))
        command_list.append(file_path)
        stdout = subprocess.check_output(
            command_list,  # nosec : The contents
            # of the command list are coming from
            # a trusted source because the
            # executable and arguments all either
            # come from the config file or are
            # hardcoded. The command list is
            # initialized earlier in this function
            # to a list and it's still a list at
            # this point in the function. There is
            # no opportunity for an attacker to
            # attempt command injection via string
            # parsing.
            stderr=subprocess.STDOUT)
    except Exception as e:
        msg = _LE('Error when signing assertion, reason: %(reason)s%(output)s')
        LOG.error(msg, {
            'reason': e,
            'output': ' ' + e.output if hasattr(e, 'output') else ''
        })
        raise exception.SAMLSigningError(reason=e)
    finally:
        try:
            if file_path:
                os.remove(file_path)
        except OSError:  # nosec
            # The file is already gone, good.
            pass

    return saml2.create_class_from_xml_string(saml.Assertion, stdout)