Create a Certificate Signing Request (CSR) for a
    particular Certificate Authority (CA)

    ca_name
        name of the CA
    bits
        number of RSA key bits, default is 2048
    CN
        common name in the request, default is "localhost"
    C
        country, default is "US"
    ST
        state, default is "Utah"
    L
        locality, default is "Centerville", the city where SaltStack originated
    O
        organization, default is "SaltStack"
        NOTE: Must the same as CA certificate or an error will be raised
    OU
        organizational unit, default is None
    emailAddress
        email address for the request, default is None
    subjectAltName
        valid subjectAltNames in full form, e.g. to add DNS entry you would call
        this function with this value:

        examples: ['DNS:somednsname.com',
                'DNS:1.2.3.4',
                'IP:1.2.3.4',
                'IP:2001:4801:7821:77:be76:4eff:fe11:e51',
                'email:me@i.like.pie.com']

    .. note::
        some libraries do not properly query IP: prefixes, instead looking
        for the given req. source with a DNS: prefix. To be thorough, you
        may want to include both DNS: and IP: entries if you are using
        subjectAltNames for destinations for your TLS connections.
        e.g.:
        requests to https://1.2.3.4 will fail from python's
        requests library w/out the second entry in the above list

    .. versionadded:: 2015.8.0

    cert_type
        Specify the general certificate type. Can be either `server` or
        `client`. Indicates the set of common extensions added to the CSR.

        .. code-block:: cfg

            server: {
               'basicConstraints': 'CA:FALSE',
               'extendedKeyUsage': 'serverAuth',
               'keyUsage': 'digitalSignature, keyEncipherment'
            }

            client: {
               'basicConstraints': 'CA:FALSE',
               'extendedKeyUsage': 'clientAuth',
               'keyUsage': 'nonRepudiation, digitalSignature, keyEncipherment'
            }

    type_ext
        boolean.  Whether or not to extend the filename with CN_[cert_type]
        This can be useful if a server and client certificate are needed for
        the same CN. Defaults to False to avoid introducing an unexpected file
        naming pattern

        The files normally named some_subject_CN.csr and some_subject_CN.key
        will then be saved

    replace
        Replace this signing request even if it exists

        .. versionadded:: 2015.5.1

    Writes out a Certificate Signing Request (CSR) If the file already
    exists, the function just returns assuming the CSR already exists.

    If the following values were set::

        ca.cert_base_path='/etc/pki'
        ca_name='koji'
        CN='test.egavas.org'

    the resulting CSR, and corresponding key, would be written in the
    following location::

        /etc/pki/koji/certs/test.egavas.org.csr
        /etc/pki/koji/certs/test.egavas.org.key

    CLI Example:

    .. code-block:: bash

        salt '*' tls.create_csr test

def create_csr(ca_name,
               bits=2048,
               CN='localhost',
               C='US',
               ST='Utah',
               L='Salt Lake City',
               O='SaltStack',
               OU=None,
               emailAddress=None,
               subjectAltName=None,
               cacert_path=None,
               ca_filename=None,
               csr_path=None,
               csr_filename=None,
               digest='sha256',
               type_ext=False,
               cert_type='server',
               replace=False):
    '''
    Create a Certificate Signing Request (CSR) for a
    particular Certificate Authority (CA)

    ca_name
        name of the CA
    bits
        number of RSA key bits, default is 2048
    CN
        common name in the request, default is "localhost"
    C
        country, default is "US"
    ST
        state, default is "Utah"
    L
        locality, default is "Centerville", the city where SaltStack originated
    O
        organization, default is "SaltStack"
        NOTE: Must the same as CA certificate or an error will be raised
    OU
        organizational unit, default is None
    emailAddress
        email address for the request, default is None
    subjectAltName
        valid subjectAltNames in full form, e.g. to add DNS entry you would call
        this function with this value:

        examples: ['DNS:somednsname.com',
                'DNS:1.2.3.4',
                'IP:1.2.3.4',
                'IP:2001:4801:7821:77:be76:4eff:fe11:e51',
                'email:me@i.like.pie.com']

    .. note::
        some libraries do not properly query IP: prefixes, instead looking
        for the given req. source with a DNS: prefix. To be thorough, you
        may want to include both DNS: and IP: entries if you are using
        subjectAltNames for destinations for your TLS connections.
        e.g.:
        requests to https://1.2.3.4 will fail from python's
        requests library w/out the second entry in the above list

    .. versionadded:: 2015.8.0

    cert_type
        Specify the general certificate type. Can be either `server` or
        `client`. Indicates the set of common extensions added to the CSR.

        .. code-block:: cfg

            server: {
               'basicConstraints': 'CA:FALSE',
               'extendedKeyUsage': 'serverAuth',
               'keyUsage': 'digitalSignature, keyEncipherment'
            }

            client: {
               'basicConstraints': 'CA:FALSE',
               'extendedKeyUsage': 'clientAuth',
               'keyUsage': 'nonRepudiation, digitalSignature, keyEncipherment'
            }

    type_ext
        boolean.  Whether or not to extend the filename with CN_[cert_type]
        This can be useful if a server and client certificate are needed for
        the same CN. Defaults to False to avoid introducing an unexpected file
        naming pattern

        The files normally named some_subject_CN.csr and some_subject_CN.key
        will then be saved

    replace
        Replace this signing request even if it exists

        .. versionadded:: 2015.5.1

    Writes out a Certificate Signing Request (CSR) If the file already
    exists, the function just returns assuming the CSR already exists.

    If the following values were set::

        ca.cert_base_path='/etc/pki'
        ca_name='koji'
        CN='test.egavas.org'

    the resulting CSR, and corresponding key, would be written in the
    following location::

        /etc/pki/koji/certs/test.egavas.org.csr
        /etc/pki/koji/certs/test.egavas.org.key

    CLI Example:

    .. code-block:: bash

        salt '*' tls.create_csr test
    '''
    set_ca_path(cacert_path)

    if not ca_filename:
        ca_filename = '{0}_ca_cert'.format(ca_name)

    if not ca_exists(ca_name, ca_filename=ca_filename):
        return ('Certificate for CA named "{0}" does not exist, please create '
                'it first.').format(ca_name)

    if not csr_path:
        csr_path = '{0}/{1}/certs/'.format(cert_base_path(), ca_name)

    if not os.path.exists(csr_path):
        os.makedirs(csr_path)

    CN_ext = '_{0}'.format(cert_type) if type_ext else ''

    if not csr_filename:
        csr_filename = '{0}{1}'.format(CN, CN_ext)

    csr_f = '{0}/{1}.csr'.format(csr_path, csr_filename)

    if not replace and os.path.exists(csr_f):
        return 'Certificate Request "{0}" already exists'.format(csr_f)

    key = OpenSSL.crypto.PKey()
    key.generate_key(OpenSSL.crypto.TYPE_RSA, bits)

    req = OpenSSL.crypto.X509Req()

    req.get_subject().C = C
    req.get_subject().ST = ST
    req.get_subject().L = L
    req.get_subject().O = O
    if OU:
        req.get_subject().OU = OU
    req.get_subject().CN = CN
    if emailAddress:
        req.get_subject().emailAddress = emailAddress

    try:
        extensions = get_extensions(cert_type)['csr']

        extension_adds = []

        for ext, value in extensions.items():
            if isinstance(value, six.string_types):
                value = salt.utils.stringutils.to_bytes(value)
            extension_adds.append(
                OpenSSL.crypto.X509Extension(
                    salt.utils.stringutils.to_bytes(ext),
                    False,
                    value
                )
            )
    except AssertionError as err:
        log.error(err)
        extensions = []

    if subjectAltName:
        if X509_EXT_ENABLED:
            if isinstance(subjectAltName, six.string_types):
                subjectAltName = [subjectAltName]

            extension_adds.append(
                OpenSSL.crypto.X509Extension(
                    b'subjectAltName',
                    False,
                    b', '.join(salt.utils.data.encode(subjectAltName))
                )
            )
        else:
            raise ValueError('subjectAltName cannot be set as X509 '
                             'extensions are not supported in pyOpenSSL '
                             'prior to version 0.15.1. Your '
                             'version: {0}.'.format(OpenSSL_version))

    if X509_EXT_ENABLED:
        req.add_extensions(extension_adds)

    req.set_pubkey(key)
    req.sign(key, salt.utils.stringutils.to_str(digest))

    # Write private key and request
    with salt.utils.files.fopen('{0}/{1}.key'.format(csr_path,
                                                     csr_filename),
                                'wb+') as priv_key:
        priv_key.write(
            salt.utils.stringutils.to_bytes(
                OpenSSL.crypto.dump_privatekey(OpenSSL.crypto.FILETYPE_PEM,
                                               key)
            )
        )

    with salt.utils.files.fopen(csr_f, 'wb+') as csr:
        csr.write(
            salt.utils.stringutils.to_bytes(
                OpenSSL.crypto.dump_certificate_request(
                    OpenSSL.crypto.FILETYPE_PEM,
                    req
                )
            )
        )

    ret = 'Created Private Key: "{0}{1}.key." '.format(
        csr_path,
        csr_filename
    )
    ret += 'Created CSR for "{0}": "{1}{2}.csr."'.format(
        CN,
        csr_path,
        csr_filename
    )

    return ret