Example #1
0
def gen_key(minion_id, dns_name=None, password=None, key_len=2048):
    '''
    Generate and return a private_key. If a ``dns_name`` is passed in, the
    private_key will be cached under that name.

    CLI Example:

    .. code-block:: bash

        salt-run digicert.gen_key <minion_id> [dns_name] [password]
    '''
    keygen_type = 'RSA'

    if keygen_type == "RSA":
        gen = RSA.generate(bits=key_len)
        private_key = gen.exportKey('PEM', password)
        if dns_name is not None:
            bank = 'digicert/domains'
            cache = salt.cache.Cache(__opts__, syspaths.CACHE_DIR)
            try:
                data = cache.fetch(bank, dns_name)
                data['private_key'] = private_key
                data['minion_id'] = minion_id
            except TypeError:
                data = {'private_key': private_key, 'minion_id': minion_id}
            cache.store(bank, dns_name, data)
    return private_key
Example #2
0
def show_cert(id_):
    '''
    Show certificate requests for this API key

    CLI Example:

    .. code-block:: bash

        salt-run venafi.show_cert 01234567-89ab-cdef-0123-456789abcdef
    '''
    data = __utils__['http.query'](
        '{0}/certificaterequests/{1}/certificate'.format(_base_url(), id_),
        params={
            'format': 'PEM',
            'chainOrder': 'ROOT_FIRST'
        },
        status=True,
        text=True,
        header_dict={
            'tppl-api-key': _api_key()
        },
    )
    status = data['status']
    if six.text_type(status).startswith('4') or six.text_type(
            status).startswith('5'):
        raise CommandExecutionError('There was an API error: {0}'.format(
            data['error']))
    data = data.get('body', '')
    csr_data = __utils__['http.query'](
        '{0}/certificaterequests/{1}'.format(_base_url(), id_),
        status=True,
        decode=True,
        decode_type='json',
        header_dict={
            'tppl-api-key': _api_key()
        },
    )
    status = csr_data['status']
    if six.text_type(status).startswith('4') or six.text_type(
            status).startswith('5'):
        raise CommandExecutionError('There was an API error: {0}'.format(
            csr_data['error']))
    csr_data = csr_data.get('dict', {})
    certs = _parse_certs(data)
    dns_name = ''
    for item in csr_data['certificateName'].split(','):
        if item.startswith('cn='):
            dns_name = item.split('=')[1]
    #certs['CSR Data'] = csr_data

    cache = salt.cache.Cache(__opts__, syspaths.CACHE_DIR)
    domain_data = cache.fetch('venafi/domains', dns_name)
    if domain_data is None:
        domain_data = {}
    certs['private_key'] = domain_data.get('private_key')
    domain_data.update(certs)
    cache.store('venafi/domains', dns_name, domain_data)

    certs['request_id'] = id_
    return certs
Example #3
0
def gen_key(minion_id, dns_name=None, password=None, key_len=2048):
    """
    Generate and return a private_key. If a ``dns_name`` is passed in, the
    private_key will be cached under that name.

    CLI Example:

    .. code-block:: bash

        salt-run digicert.gen_key <minion_id> [dns_name] [password]
    """
    keygen_type = "RSA"

    if keygen_type == "RSA":
        if HAS_M2:
            gen = RSA.gen_key(key_len, 65537)
            private_key = gen.as_pem(cipher="des_ede3_cbc",
                                     callback=lambda x: six.b(password))
        else:
            gen = RSA.generate(bits=key_len)
            private_key = gen.exportKey("PEM", password)
        if dns_name is not None:
            bank = "digicert/domains"
            cache = salt.cache.Cache(__opts__, syspaths.CACHE_DIR)
            try:
                data = cache.fetch(bank, dns_name)
                data["private_key"] = private_key
                data["minion_id"] = minion_id
            except TypeError:
                data = {"private_key": private_key, "minion_id": minion_id}
            cache.store(bank, dns_name, data)
    return private_key
Example #4
0
def set_(key, value, service=None, profile=None):  # pylint: disable=W0613
    '''
    Set a key/value pair in the cache service
    '''
    key, profile = _parse_key(key, profile)
    cache = salt.cache.Cache(__opts__)
    cache.store(profile['bank'], key, value)
    return get(key, service, profile)
Example #5
0
        def _update_metadata(repo, repo_info):
            dl_path = '{0}/SPM-METADATA'.format(repo_info['url'])
            if dl_path.startswith('file://'):
                dl_path = dl_path.replace('file://', '')
                with salt.utils.fopen(dl_path, 'r') as rpm:
                    metadata = yaml.safe_load(rpm)
            else:
                metadata = self._query_http(dl_path, repo_info)

            cache.store('.', repo, metadata)
Example #6
0
        def _update_metadata(repo, repo_info):
            dl_path = "{}/SPM-METADATA".format(repo_info["url"])
            if dl_path.startswith("file://"):
                dl_path = dl_path.replace("file://", "")
                with salt.utils.files.fopen(dl_path, "r") as rpm:
                    metadata = salt.utils.yaml.safe_load(rpm)
            else:
                metadata = self._query_http(dl_path, repo_info)

            cache.store(".", repo, metadata)
Example #7
0
def ext_pillar(minion_id,
               pillar,  # pylint: disable=W0613
               url=None,
               ca_bundle=None,
               username=None,
               password=None):
    '''
    Read pillar data from HTTP response.

    :param url String to make request
    :param ca_bundle Path to CA bundle
    :param username Username for basic-auth
    :param password Password for basic-auth
    :returns dict with pillar data to add
    :returns empty if error
    '''
    log.debug("Fetching velum pillar data")

    cache = salt.cache.Cache(__opts__)

    if username is None:
        with open(os.environ['VELUM_INTERNAL_API_USERNAME_FILE'], 'r') as f:
            username = f.read().strip()

    if password is None:
        with open(os.environ['VELUM_INTERNAL_API_PASSWORD_FILE'], 'r') as f:
            password = f.read().strip()

    try:
        data = __salt__['http.query'](url=url,
                                      ca_bundle=ca_bundle,
                                      username=username,
                                      password=password,
                                      decode=True,
                                      decode_type='json')
    except Exception as e:
        log.error('Error when getting pillar from Velum: {0}. Will try to use the cache...'.format(e))
        data = {}

    if data and 'dict' in data:
        try:
            cache.store('caasp/pillar', minion_id, data['dict'])
        except Exception as e:
            log.warning('Error when populating the cache: {0}. Moving on, not critical'.format(e))
        return data['dict']

    if cache.contains('caasp/pillar', minion_id):
        log.warning('Serving pillar from cache for minion {0}, since {1} was not available'.format(minion_id, url))
        return cache.fetch('caasp/pillar', minion_id)

    log.error('Error caught on query to {0}. More Info:\n'.format(url))
    for k, v in six.iteritems(data):
        log.error(k + ' : ' + v)

    return {}
Example #8
0
        def _update_metadata(repo, repo_info):
            dl_path = '{0}/SPM-METADATA'.format(repo_info['url'])
            if dl_path.startswith('file://'):
                dl_path = dl_path.replace('file://', '')
                with salt.utils.fopen(dl_path, 'r') as rpm:
                    metadata = yaml.safe_load(rpm)
            else:
                response = http.query(dl_path, text=True)
                metadata = yaml.safe_load(response.get('text', '{}'))

            cache.store('.', repo, metadata)
Example #9
0
def _id_map(minion_id, dns_name):
    '''
    Maintain a relationship between a minion and a dns name
    '''
    bank = 'venafi/minions'
    cache = salt.cache.Cache(__opts__, syspaths.CACHE_DIR)
    dns_names = cache.fetch(bank, minion_id)
    if not isinstance(dns_names, list):
        dns_names = []
    if dns_name not in dns_names:
        dns_names.append(dns_name)
    cache.store(bank, minion_id, dns_names)
Example #10
0
def _id_map(minion_id, dns_name):
    """
    Maintain a relationship between a minion and a DNS name
    """

    cache = salt.cache.Cache(__opts__, syspaths.CACHE_DIR)
    dns_names = cache.fetch(CACHE_BANK_NAME, minion_id)
    if not isinstance(dns_names, list):
        dns_names = []
    if dns_name not in dns_names:
        dns_names.append(dns_name)
    cache.store(CACHE_BANK_NAME, minion_id, dns_names)
Example #11
0
def _cache(bank, key, fun, **kwargs):
    '''
    Cache an Azure ARM object
    '''
    items = cache.fetch(bank, key)
    if items is None:
        items = {}
        item_list = fun(**kwargs)
        for item in item_list:
            items[item.name] = object_to_dict(item)
        cache.store(bank, key, items)
    return items
Example #12
0
def ext_pillar(
        minion_id,
        pillar,  # pylint: disable=W0613
        url=None,
        ca_bundle=None,
        username=None,
        password=None):
    '''
    Read pillar data from HTTP response.

    :param url String to make request
    :param ca_bundle Path to CA bundle
    :param username Username for basic-auth
    :param password Password for basic-auth
    :returns dict with pillar data to add
    :returns empty if error
    '''

    log = logging.getLogger(__name__)
    cache = salt.cache.Cache(__opts__)

    if username is None:
        with open(os.environ['VELUM_INTERNAL_API_USERNAME_FILE'], 'r') as f:
            username = f.read().strip()

    if password is None:
        with open(os.environ['VELUM_INTERNAL_API_PASSWORD_FILE'], 'r') as f:
            password = f.read().strip()

    data = __salt__['http.query'](url=url,
                                  ca_bundle=ca_bundle,
                                  username=username,
                                  password=password,
                                  decode=True,
                                  decode_type='json')

    if 'dict' in data:
        cache.store('caasp/pillar', minion_id, data['dict'])
        return data['dict']
    elif cache.contains('caasp/pillar', minion_id):
        log.warning(
            'Serving pillar from cache for minion {0}, since {1} was not available'
            .format(minion_id, url))
        return cache.fetch('caasp/pillar', minion_id)

    log.error('Error caught on query to {0}. More Info:\n'.format(url))
    for k, v in six.iteritems(data):
        log.error(k + ' : ' + v)

    return {}
Example #13
0
def _cache(bank, key, fun, **kwargs):
    '''
    Cache an Azure ARM object
    '''
    items = cache.fetch(bank, key)
    if items is None:
        items = {}
        try:
            item_list = fun(**kwargs)
        except CloudError as exc:
            log.warn(
                'There was a cloud error calling {0} with kwargs {1}: {2}'.
                format(fun, kwargs, exc))
        for item in item_list:
            items[item.name] = object_to_dict(item)
        cache.store(bank, key, items)
    return items
Example #14
0
def _process_hostkeys(client, pillars, cache):
    log.debug("Retriving host keys for minions: %s", pillars.keys())
    try:
        if use_certs_param:
            cmd_run = client.cmd_iter(pillars.keys(),
                                      'ssh.host_keys',
                                      kwarg={
                                          'private': False,
                                          'certs': False
                                      },
                                      **{expr_keyname: 'list'})
        else:
            cmd_run = client.cmd_iter(pillars.keys(),
                                      'ssh.host_keys',
                                      kwarg={'private': False},
                                      **{expr_keyname: 'list'})
        for rets in cmd_run:
            for minion_id, resp in rets.iteritems():
                log.trace("Minion '%s' returned: %s", minion_id, resp)
                try:
                    if resp['retcode'] != 0:
                        log.warn(
                            "Minion '%s' returned an error running"
                            " 'ssh.host_keys': %s", minion_id, resp['ret'])
                        continue
                    if not use_certs_param:
                        for key in resp['ret'].keys():
                            if '-cert.pub' in key or '-cert-' in resp['ret'][
                                    key]:
                                del resp['ret'][key]
                    log.trace("Found host keys for minion '%s'", minion_id)
                    try:
                        cache.store('sshpki/hostkeys', minion_id, resp['ret'])
                        log.debug("Stored host keys for minion '%s'",
                                  minion_id)
                    except:
                        log.warn("Failed to store host keys for minion '%s'",
                                 minion_id,
                                 exc_info=True)
                except:
                    log.warn("Error processing return data for minion '%s'",
                             minion_id,
                             exc_info=True)
        log.debug("Host key processing complete")
    except:
        log.warn("Error retriving host keys for minions", exc_info=True)
Example #15
0
def store(bank, key, data, cachedir=None):
    '''
    Lists entries stored in the specified bank.

    CLI Example:

    .. code-block:: bash

        salt-run cache.store mycache mykey 'The time has come the walrus said'
    '''
    if cachedir is None:
        cachedir = __opts__['cachedir']

    try:
        cache = salt.cache.Cache(__opts__, cachedir=cachedir)
    except TypeError:
        cache = salt.cache.Cache(__opts__)
    return cache.store(bank, key, data)
Example #16
0
def request(
        minion_id,
        dns_name=None,
        zone='default',
        request_id=None,
        country='US',
        state='California',
        loc='Palo Alto',
        org='Beta Organization',
        org_unit='Beta Group',
        password=None,
        zone_id=None,
    ):
    '''
    Request a new certificate

    Uses the following command:

    .. code-block:: bash

        VCert enroll -z <zone> -k <api key> -cn <domain name>

    CLI Example:

    .. code-block:: bash

        salt-run venafi.request <minion_id> <dns_name>
    '''
    if password is not None:
        if password.startswith('sdb://'):
            password = __salt__['sdb.get'](password)

    if zone_id is None:
        zone_id = __opts__.get('venafi', {}).get('zone_id')

    if zone_id is None and zone is not None:
        zone_id = get_zone_id(zone)

    if zone_id is None:
        raise CommandExecutionError(
            'Either a zone or a zone_id must be passed in or '
            'configured in the master file. This id can be retreived using '
            'venafi.show_company <domain>'
        )

    private_key = gen_key(minion_id, dns_name, zone, password)

    csr = gen_csr(
        minion_id,
        dns_name,
        zone=zone,
        country=country,
        state=state,
        loc=loc,
        org=org,
        org_unit=org_unit,
        password=password,
    )

    pdata = salt.utils.json.dumps({
        'zoneId': zone_id,
        'certificateSigningRequest': csr,
    })

    qdata = __utils__['http.query'](
        '{0}/certificaterequests'.format(_base_url()),
        method='POST',
        data=pdata,
        decode=True,
        decode_type='json',
        header_dict={
            'tppl-api-key': _api_key(),
            'Content-Type': 'application/json',
        },
    )

    request_id = qdata['dict']['certificateRequests'][0]['id']
    ret = {
        'request_id': request_id,
        'private_key': private_key,
        'csr': csr,
        'zone': zone,
    }

    bank = 'venafi/domains'
    cache = salt.cache.Cache(__opts__, syspaths.CACHE_DIR)
    data = cache.fetch(bank, dns_name)
    if data is None:
        data = {}
    data.update({
        'minion_id': minion_id,
        'request_id': request_id,
        'private_key': private_key,
        'zone': zone,
        'csr': csr,
    })
    cache.store(bank, dns_name, data)
    _id_map(minion_id, dns_name)

    return ret
Example #17
0
def gen_csr(
        minion_id,
        dns_name,
        zone='default',
        country=None,
        state=None,
        loc=None,
        org=None,
        org_unit=None,
        password=None,
    ):
    '''
    Generate a csr using the host's private_key.
    Analogous to:

    .. code-block:: bash

        VCert gencsr -cn [CN Value] -o "Beta Organization" -ou "Beta Group" \
            -l "Palo Alto" -st "California" -c US

    CLI Example:

    .. code-block:: bash

        salt-run venafi.gen_csr <minion_id> <dns_name>
    '''
    tmpdir = tempfile.mkdtemp()
    os.chmod(tmpdir, 0o700)

    bank = 'venafi/domains'
    cache = salt.cache.Cache(__opts__, syspaths.CACHE_DIR)
    data = cache.fetch(bank, dns_name)
    if data is None:
        data = {}
    if 'private_key' not in data:
        data['private_key'] = gen_key(minion_id, dns_name, zone, password)

    tmppriv = '{0}/priv'.format(tmpdir)
    tmpcsr = '{0}/csr'.format(tmpdir)
    with salt.utils.files.fopen(tmppriv, 'w') as if_:
        if_.write(salt.utils.stringutils.to_str(data['private_key']))

    if country is None:
        country = __opts__.get('venafi', {}).get('country')

    if state is None:
        state = __opts__.get('venafi', {}).get('state')

    if loc is None:
        loc = __opts__.get('venafi', {}).get('loc')

    if org is None:
        org = __opts__.get('venafi', {}).get('org')

    if org_unit is None:
        org_unit = __opts__.get('venafi', {}).get('org_unit')

    subject = '/C={0}/ST={1}/L={2}/O={3}/OU={4}/CN={5}'.format(
        country,
        state,
        loc,
        org,
        org_unit,
        dns_name,
    )

    cmd = "openssl req -new -sha256 -key {0} -out {1} -subj '{2}'".format(
        tmppriv,
        tmpcsr,
        subject
    )
    if password is not None:
        cmd += ' -passin pass:{0}'.format(password)
    output = __salt__['salt.cmd']('cmd.run', cmd)

    if 'problems making Certificate Request' in output:
        raise CommandExecutionError(
            'There was a problem generating the CSR. Please ensure that you '
            'have the following variables set either on the command line, or '
            'in the venafi section of your master configuration file: '
            'country, state, loc, org, org_unit'
        )

    with salt.utils.files.fopen(tmpcsr, 'r') as of_:
        csr = salt.utils.stringutils.to_unicode(of_.read())

    data['minion_id'] = minion_id
    data['csr'] = csr
    cache.store(bank, dns_name, data)
    return csr
Example #18
0
def _process_userkeys(client, pillars, cache):
    log.debug("Retriving user keys for minions: %s", pillars.keys())

    minion_users = {}
    for minion_id in pillars:
        try:
            try:
                users = pillars[minion_id]['users_by_minion'][minion_id]
            except (KeyError, TypeError):
                users = pillars[minion_id]['users']
        except (KeyError, TypeError):
            users = {}
        if not users:
            log.debug("No user keys set for minion '%s'", minion_id)
            continue
        minion_users[minion_id] = users

    minion_users_custkeys = {}
    for minion_id in minion_users.keys():
        for user in minion_users[minion_id].keys():
            if minion_users[minion_id][user] is None:
                minion_users[minion_id][user] = {}
            elif 'pubkey_path' in minion_users[minion_id][user]:
                if not minion_id in minion_users_custkeys:
                    minion_users_custkeys[minion_id] = {}
                minion_users_custkeys[minion_id][user] = minion_users[
                    minion_id][user]
                del minion_users[minion_id][user]
        if not minion_users[minion_id]:
            del minion_users[minion_id]
    log.trace("Minion user data for users with custom keys: %s",
              minion_users_custkeys)
    log.trace("Minion user data for users with default keys: %s", minion_users)

    for minion_id, users in minion_users.iteritems():
        log.debug("Retriving default user keys for minion '%s'", minion_id)
        try:
            resp = next(
                client.cmd_iter([minion_id],
                                'ssh.user_keys', [users.keys()],
                                kwarg={'prvfile': False},
                                **{expr_keyname: 'list'}))[minion_id]
        except StopIteration:
            log.debug(
                "No response retriving default user keys for minion '%s'",
                minion_id)
            continue
        except:
            log.warn("Error retriving default user keys for minion '%s'",
                     minion_id,
                     exc_info=True)
            continue
        log.trace("Minion '%s' returned: %s", minion_id, resp)
        try:
            if resp['retcode'] != 0:
                log.warn(
                    "Minion '%s' returned an error running"
                    " 'ssh.user_keys': %s", minion_id, resp['ret'])
                continue
            log.trace("Found user keys for minion '%s'", minion_id)
            bank = 'sshpki/userkeys/{}'.format(minion_id)
            exc = False
            for user, key in resp['ret'].iteritems():
                try:
                    cache.store(bank, user, key)
                    log.trace("Stored keys for user '%s' on minion '%s'", user,
                              minion_id)
                except:
                    log.warn(
                        "Failed to store keys for user '%s' on minion '%s'",
                        user,
                        minion_id,
                        exc_info=True)
                    exc = True
                    break
            if exc:
                continue
            log.debug("Stored default user keys for minion '%s'", minion_id)
        except:
            log.warn("Error processing return data for minion '%s'",
                     minion_id,
                     exc_info=True)
    for minion_id, users in minion_users_custkeys.iteritems():
        log.debug("Retriving custom user keys for minion '%s'", minion_id)
        last_failed = False
        for user, options in users.iteritems():
            log.trace("Retriving keys for user '%s' on minion '%s'", user,
                      minion_id)
            try:
                resp = next(
                    client.cmd_iter([minion_id],
                                    'ssh.user_keys', [user],
                                    kwarg={
                                        'pubfile': options.get('pubkey_path'),
                                        'prvfile': False
                                    },
                                    **{expr_keyname: 'list'}))[minion_id]
            except StopIteration:
                log.debug(
                    "No response retriving keys for user '%s' on minion '%s'",
                    user, minion_id)
                if last_failed:
                    log.debug("Skipping minion '%s' due to repeat failure",
                              minion_id)
                    break
                else:
                    last_failed = True
                    continue
            except:
                log.warn("Error retriving keys for user '%s' on minion '%s'",
                         user,
                         minion_id,
                         exc_info=True)
                continue
            last_failed = False
            log.trace("Minion '%s' returned: %s", minion_id, resp)
            try:
                if resp['retcode'] != 0:
                    log.warn(
                        "Minion '%s' returned an error running"
                        " 'ssh.user_keys': %s", minion_id, resp['ret'])
                    continue
                if not resp['ret']:
                    continue
                log.trace("Found keys for user '%s' on minion '%s'", user,
                          minion_id)
                try:
                    cache.store('sshpki/userkeys/{}'.format(minion_id), user,
                                resp['ret'][user])
                    log.trace("Stored keys for user '%s' on minion '%s'", user,
                              minion_id)
                except:
                    log.warn(
                        "Failed to store keys for user '%s' on minion '%s'",
                        user,
                        minion_id,
                        exc_info=True)
                    continue
            except:
                log.warn(
                    "Error processing return data for user '%s' on minion '%s'",
                    user,
                    minion_id,
                    exc_info=True)
        log.debug("Stored custom user keys for minion '%s'", minion_id)
    log.debug("User key processing complete")
Example #19
0
def gen_csr(
    minion_id,
    dns_name,
    organization_id,
    ou_name=None,
    key_len=2048,
    shatype="sha256",
    password=None,
):
    """

    CLI Example:

    .. code-block:: bash

        salt-run digicert.gen_csr <minion_id> <dns_name>
    """
    org_details = get_org_details(organization_id)

    if "error" in org_details:
        raise SaltRunnerError(
            "Problem getting organization details for organization_id={0} ({1})"
            .format(organization_id, org_details["error"]))
    if org_details["dict"].get("status", "active") == "inactive":
        raise SaltRunnerError(
            "Organization with organization_id={0} is marked inactive".format(
                organization_id))

    tmpdir = tempfile.mkdtemp()
    os.chmod(tmpdir, 0o700)

    bank = "digicert/domains"
    cache = salt.cache.Cache(__opts__, syspaths.CACHE_DIR)
    data = cache.fetch(bank, dns_name)
    if data is None:
        data = {}
    if "private_key" not in data:
        data["private_key"] = gen_key(minion_id,
                                      dns_name,
                                      password,
                                      key_len=key_len)

    tmppriv = "{0}/priv".format(tmpdir)
    tmpcsr = "{0}/csr".format(tmpdir)
    with salt.utils.files.fopen(tmppriv, "w") as if_:
        if_.write(salt.utils.stringutils.to_str(data["private_key"]))

    subject = "/C={0}/ST={1}/L={2}/O={3}".format(
        org_details["dict"]["country"],
        org_details["dict"]["state"],
        org_details["dict"]["city"],
        org_details["dict"]["display_name"],
    )

    if ou_name:
        subject = subject + "/OU={0}".format(ou_name)

    subject = subject + "/CN={0}".format(dns_name)

    cmd = "openssl req -new -{0} -key {1} -out {2} -subj '{3}'".format(
        shatype, tmppriv, tmpcsr, subject)
    output = __salt__["salt.cmd"]("cmd.run", cmd)

    if "problems making Certificate Request" in output:
        raise CommandExecutionError(
            "There was a problem generating the CSR. Please ensure that you "
            "have a valid Organization established inside CertCentral")

    with salt.utils.files.fopen(tmpcsr, "r") as of_:
        csr = salt.utils.stringutils.to_unicode(of_.read())

    data["minion_id"] = minion_id
    data["csr"] = csr
    cache.store(bank, dns_name, data)
    return csr
Example #20
0
def request(
    minion_id,
    dns_name=None,
    zone=None,
    country=None,
    state=None,
    loc=None,
    org=None,
    org_unit=None,
    key_password=None,
    csr_path=None,
    pkey_path=None,
):
    """
    Request a new certificate

    CLI Example:

    .. code-block:: bash

        salt-run venafi.request <minion_id> <dns_name>
    """

    log.info("Requesting Venafi certificate")
    if zone is None:
        log.error(msg=str("Missing zone parameter"))
        sys.exit(1)

    if key_password is not None:
        if key_password.startswith("sdb://"):
            key_password = __salt__["sdb.get"](key_password)
    conn = _init_connection()

    if csr_path is None:
        request = CertificateRequest(
            common_name=dns_name,
            country=country,
            province=state,
            locality=loc,
            organization=org,
            organizational_unit=org_unit,
            key_password=key_password,
        )
        zone_config = conn.read_zone_conf(zone)
        log.info("Updating request from zone %s", zone_config)
        request.update_from_zone_config(zone_config)
    else:
        log.info("Will use generated CSR from %s", csr_path)
        log.info("Using CN %s", dns_name)
        try:
            with salt.utils.files.fopen(csr_path) as csr_file:
                csr = csr_file.read()
            request = CertificateRequest(csr=csr, common_name=dns_name)
        except Exception as e:
            raise Exception("Unable to open file {file}: {excp}".format(
                file=csr_path, excp=e))
    conn.request_cert(request, zone)

    # TODO: add timeout parameter here
    timeout_seconds = 300
    timeout = time.time() + timeout_seconds
    cert = None
    while cert is None and time.time() < timeout:
        cert = conn.retrieve_cert(request)
        if cert is None:
            time.sleep(5)

    if csr_path is None:
        private_key = request.private_key_pem
    else:
        if pkey_path:
            try:
                with salt.utils.files.fopen(pkey_path) as pkey_file:
                    private_key = pkey_file.read()
            except Exception as e:
                raise Exception("Unable to open file {file}: {excp}".format(
                    file=pkey_path, excp=e))
        else:
            private_key = None

    cache = salt.cache.Cache(__opts__, syspaths.CACHE_DIR)
    data = {
        "minion_id": minion_id,
        "cert": cert.cert,
        "chain": cert.chain,
        "pkey": private_key,
    }
    cache.store(CACHE_BANK_NAME, dns_name, data)
    return cert.cert, private_key
Example #21
0
def order_certificate(
    minion_id,
    common_name,
    organization_id,
    validity_years,
    cert_key_passphrase=None,
    signature_hash=None,
    key_len=2048,
    dns_names=None,
    organization_units=None,
    server_platform=None,
    custom_expiration_date=None,
    comments=None,
    disable_renewal_notifications=False,
    product_type_hint=None,
    renewal_of_order_id=None,
):
    """
    Order a certificate.  Requires that an Organization has been created inside Digicert's CertCentral.

    See here for API documentation:
    https://www.digicert.com/services/v2/documentation/order/order-ssl-determinator

    CLI Example:

    .. code-block:: bash

        salt-run digicert.order_certificate my_minionid my.domain.com 10 \
            3 signature_hash=sha256 \
            dns_names=['this.domain.com', 'that.domain.com'] \
            organization_units='My Domain Org Unit' \
            comments='Comment goes here for the approver'

    This runner can also be used to renew a certificate by passing `renewal_of_order_id`.
    Previous order details can be retrieved with digicertapi.list_orders.
    """

    if dns_names and isinstance(dns_names, six.string_types):
        dns_names = [dns_names]
    if dns_names and not isinstance(dns_names, collections.Sequence):
        raise SaltRunnerError(
            "order_certificate needs a single dns_name, or an array of dns_names."
        )
    certificate = {"common_name": common_name}
    certificate["dns_names"] = dns_names

    if signature_hash:
        certificate["signature_hash"] = signature_hash
    else:
        certificate["signature_hash"] = __opts__.get("digicert", {}).get(
            "shatype", "sha256")

    body = {}

    if organization_units and isinstance(organization_units, six.string_types):
        organization_units = [organization_units]
    if organization_units and not isinstance(organization_units,
                                             collections.Sequence):
        raise SaltRunnerError("Organization_units is not a valid data type.")
    if organization_units:
        certificate["organization_units"] = organization_units

    if organization_units:
        # Currently the Digicert API requires organization units to be an array
        # but only pays attention to the first one.
        csr = gen_csr(
            minion_id,
            common_name,
            organization_id,
            ou_name=organization_units[0],
            shatype=certificate["signature_hash"],
            key_len=key_len,
            password=cert_key_passphrase,
        )
    else:
        csr = gen_csr(
            minion_id,
            common_name,
            organization_id,
            shatype=certificate["signature_hash"],
            key_len=key_len,
            password=cert_key_passphrase,
        )

    certificate["csr"] = csr

    if server_platform:
        certificate["server_platform"]["id"] = server_platform

    body["organization"] = {"id": organization_id}

    if custom_expiration_date:
        body["custom_expiration_date"] = custom_expiration_date

    if validity_years:
        body["validity_years"] = validity_years

    if comments:
        body["comments"] = comments

    body["disable_renewal_notifications"] = disable_renewal_notifications

    if product_type_hint:
        body["product"] = {"type_hint": product_type_hint}
    if renewal_of_order_id:
        body["renewal_of_order_id"] = renewal_of_order_id

    body["certificate"] = certificate
    encoded_body = salt.utils.json.dumps(body)

    qdata = salt.utils.http.query(
        "{0}/order/certificate/ssl".format(_base_url()),
        method="POST",
        data=encoded_body,
        decode=True,
        decode_type="json",
        header_dict={
            "X-DC-DEVKEY": _api_key(),
            "Content-Type": "application/json"
        },
        raise_error=False,
    )
    if "errors" not in qdata["dict"]:
        bank = "digicert/domains"
        cache = salt.cache.Cache(__opts__, syspaths.CACHE_DIR)
        data = cache.fetch(bank, common_name)
        if data is None:
            data = {}
        data.update({
            "minion_id": minion_id,
            "order_id": qdata["dict"]["requests"][0]["id"],
            "csr": csr,
        })
        cache.store(bank, common_name, data)
        _id_map(minion_id, common_name)

    return {"order": qdata["dict"]}
Example #22
0
def get_certificate(
    order_id=None,
    certificate_id=None,
    minion_id=None,
    cert_format="pem_all",
    filename=None,
):
    """
    Retrieve a certificate by order_id or certificate_id and write it to stdout or a filename.

    A list of permissible cert_formats is here:
        https://www.digicert.com/services/v2/documentation/appendix-certificate-formats

    CLI Example:

    .. code-block:: bash

        salt-run digicert.get_certificate order_id=48929454 cert_format=apache

    Including a 'filename' will write the certificate to the desired file.
    Note that some cert formats are zipped files, and some are binary.

    If the certificate has not been issued, this function will return the order details
    inside of which will be a status (one of pending, rejected, processing, issued,
    revoked, canceled, needs_csr, and needs_approval)

    If for some reason you want to pipe the output of this command to a file or other
    command you will want to leave off the ``filename`` argument and make sure to include
    ``--no-color`` so there will be no terminal ANSI escape sequences.

    """

    if order_id:
        order_cert = salt.utils.http.query(
            "{0}/order/certificate/{1}".format(_base_url(), order_id),
            method="GET",
            raise_error=False,
            decode=True,
            decode_type="json",
            header_dict={
                "X-DC-DEVKEY": _api_key(),
                "Content-Type": "application/json",
            },
        )
        if order_cert["dict"].get("status") != "issued":
            return {"certificate": order_cert["dict"]}

        if order_cert["dict"].get("errors", False):
            return {"certificate": order_cert["dict"]}

        certificate_id = order_cert["dict"].get("certificate").get("id", None)
        common_name = order_cert["dict"].get("certificate").get("common_name")

    if not certificate_id:
        return {
            "certificate": {
                "errors": {
                    "code":
                    "unknown",
                    "message":
                    "Unknown error, no certificate ID passed on command line or in body returned from API",
                }
            }
        }

    if filename:
        ret_cert = salt.utils.http.query(
            "{0}/certificate/{1}/download/format/{2}".format(
                _base_url(), certificate_id, cert_format),
            method="GET",
            decode=False,
            text=False,
            headers=True,
            text_out=filename,
            raise_error=False,
            header_dict={"X-DC-DEVKEY": _api_key()},
        )
    else:
        ret_cert = salt.utils.http.query(
            "{0}/certificate/{1}/download/format/{2}".format(
                _base_url(), certificate_id, cert_format),
            method="GET",
            text=False,
            decode=False,
            raise_error=False,
            header_dict={"X-DC-DEVKEY": _api_key()},
        )
    if "errors" in ret_cert:
        return {"certificate": ret_cert}

    if "body" not in ret_cert:
        ret = {"certificate": ret_cert}
        cert = ret_cert
    if isinstance(ret_cert, dict):
        ret = ret_cert["body"]
        cert = ret
    else:
        ret = ret_cert
        cert = ret

    tmpfilename = None
    if not filename:
        fd, tmpfilename = tempfile.mkstemp()
        filename = tmpfilename
        os.write(fd, cert)
        os.close(fd)

    cmd = [
        "openssl",
        "x509",
        "-noout",
        "-subject",
        "-nameopt",
        "multiline",
        "-in",
        filename,
    ]
    out = subprocess.check_output(cmd)
    common_name = None
    for l in out.splitlines():
        common_name_match = re.search(" *commonName *= *(.*)", l)
        if common_name_match:
            common_name = common_name_match.group(1)
            break
    if tmpfilename:
        os.unlink(tmpfilename)

    if common_name:
        bank = "digicert/domains"
        cache = salt.cache.Cache(__opts__, syspaths.CACHE_DIR)
        try:
            data = cache.fetch(bank, common_name)
        except TypeError:
            data = {"certificate": cert}
        cache.store(bank, common_name, data)

    if "headers" in ret_cert:
        return {
            "certificate": {
                "filename":
                filename,
                "original_filename":
                ret_cert["headers"].get("Content-Disposition", "Not provided"),
                "Content-Type":
                ret_cert["headers"].get("Content-Type", "Not provided"),
            }
        }

    return {"certificate": cert}
Example #23
0
def gen_csr(minion_id,
            dns_name,
            organization_id,
            ou_name=None,
            key_len=2048,
            shatype='sha256',
            password=None):
    '''

    CLI Example:

    .. code-block:: bash

        salt-run digicert.gen_csr <minion_id> <dns_name>
    '''
    org_details = get_org_details(organization_id)

    if 'error' in org_details:
        raise SaltRunnerError(
            'Problem getting organization details for organization_id={0} ({1})'
            .format(organization_id, org_details['error']))
    if org_details['dict'].get('status', 'active') == 'inactive':
        raise SaltRunnerError(
            'Organization with organization_id={0} is marked inactive'.format(
                organization_id))

    tmpdir = tempfile.mkdtemp()
    os.chmod(tmpdir, 0o700)

    bank = 'digicert/domains'
    cache = salt.cache.Cache(__opts__, syspaths.CACHE_DIR)
    data = cache.fetch(bank, dns_name)
    if data is None:
        data = {}
    if 'private_key' not in data:
        data['private_key'] = gen_key(minion_id,
                                      dns_name,
                                      password,
                                      key_len=key_len)

    tmppriv = '{0}/priv'.format(tmpdir)
    tmpcsr = '{0}/csr'.format(tmpdir)
    with salt.utils.files.fopen(tmppriv, 'w') as if_:
        if_.write(salt.utils.stringutils.to_str(data['private_key']))

    subject = '/C={0}/ST={1}/L={2}/O={3}'.format(
        org_details['dict']['country'], org_details['dict']['state'],
        org_details['dict']['city'], org_details['dict']['display_name'])

    if ou_name:
        subject = subject + '/OU={0}'.format(ou_name)

    subject = subject + '/CN={0}'.format(dns_name)

    cmd = "openssl req -new -{0} -key {1} -out {2} -subj '{3}'".format(
        shatype, tmppriv, tmpcsr, subject)
    output = __salt__['salt.cmd']('cmd.run', cmd)

    if 'problems making Certificate Request' in output:
        raise CommandExecutionError(
            'There was a problem generating the CSR. Please ensure that you '
            'have a valid Organization established inside CertCentral')

    with salt.utils.files.fopen(tmpcsr, 'r') as of_:
        csr = salt.utils.stringutils.to_unicode(of_.read())

    data['minion_id'] = minion_id
    data['csr'] = csr
    cache.store(bank, dns_name, data)
    return csr
Example #24
0
def order_certificate(minion_id,
                      common_name,
                      organization_id,
                      validity_years,
                      cert_key_passphrase=None,
                      signature_hash=None,
                      key_len=2048,
                      dns_names=None,
                      organization_units=None,
                      server_platform=None,
                      custom_expiration_date=None,
                      comments=None,
                      disable_renewal_notifications=False,
                      product_type_hint=None,
                      renewal_of_order_id=None):
    '''
    Order a certificate.  Requires that an Organization has been created inside Digicert's CertCentral.

    See here for API documentation:
    https://www.digicert.com/services/v2/documentation/order/order-ssl-determinator

    CLI Example:

    .. code-block:: bash

        salt-run digicert.order_certificate my_minionid my.domain.com 10 \
            3 signature_hash=sha256 \
            dns_names=['this.domain.com', 'that.domain.com'] \
            organization_units='My Domain Org Unit' \
            comments='Comment goes here for the approver'

    This runner can also be used to renew a certificate by passing `renewal_of_order_id`.
    Previous order details can be retrieved with digicertapi.list_orders.
    '''

    if dns_names and isinstance(dns_names, six.string_types):
        dns_names = [dns_names]
    if dns_names and not isinstance(dns_names, collections.Sequence):
        raise SaltRunnerError(
            'order_certificate needs a single dns_name, or an array of dns_names.'
        )
    certificate = {'common_name': common_name}
    certificate['dns_names'] = dns_names

    if signature_hash:
        certificate['signature_hash'] = signature_hash
    else:
        certificate['signature_hash'] = __opts__.get('digicert', {}).get(
            'shatype', 'sha256')

    body = {}

    if organization_units and isinstance(organization_units, six.string_types):
        organization_units = [organization_units]
    if organization_units and not isinstance(organization_units,
                                             collections.Sequence):
        raise SaltRunnerError('Organization_units is not a valid data type.')
    if organization_units:
        certificate['organization_units'] = organization_units

    if organization_units:
        # Currently the Digicert API requires organization units to be an array
        # but only pays attention to the first one.
        csr = gen_csr(minion_id,
                      common_name,
                      organization_id,
                      ou_name=organization_units[0],
                      shatype=certificate['signature_hash'],
                      key_len=key_len,
                      password=cert_key_passphrase)
    else:
        csr = gen_csr(minion_id,
                      common_name,
                      organization_id,
                      shatype=certificate['signature_hash'],
                      key_len=key_len,
                      password=cert_key_passphrase)

    certificate['csr'] = csr

    if server_platform:
        certificate['server_platform']['id'] = server_platform

    body['organization'] = {'id': organization_id}

    if custom_expiration_date:
        body['custom_expiration_date'] = custom_expiration_date

    if validity_years:
        body['validity_years'] = validity_years

    if comments:
        body['comments'] = comments

    body['disable_renewal_notifications'] = disable_renewal_notifications

    if product_type_hint:
        body['product'] = {'type_hint': product_type_hint}
    if renewal_of_order_id:
        body['renewal_of_order_id'] = renewal_of_order_id

    body['certificate'] = certificate
    encoded_body = salt.utils.json.dumps(body)

    qdata = salt.utils.http.query('{0}/order/certificate/ssl'.format(
        _base_url()),
                                  method='POST',
                                  data=encoded_body,
                                  decode=True,
                                  decode_type='json',
                                  header_dict={
                                      'X-DC-DEVKEY': _api_key(),
                                      'Content-Type': 'application/json',
                                  },
                                  raise_error=False)
    if 'errors' not in qdata['dict']:
        bank = 'digicert/domains'
        cache = salt.cache.Cache(__opts__, syspaths.CACHE_DIR)
        data = cache.fetch(bank, common_name)
        if data is None:
            data = {}
        data.update({
            'minion_id': minion_id,
            'order_id': qdata['dict']['requests'][0]['id'],
            'csr': csr,
        })
        cache.store(bank, common_name, data)
        _id_map(minion_id, common_name)

    return {'order': qdata['dict']}
Example #25
0
def run_common_cache_tests(subtests, cache):
    bank = "fnord/kevin/stuart"
    # ^^^^ This bank can be just fnord, or fnord/foo, or any mildly reasonable
    # or possibly unreasonably nested names.
    #
    # No. Seriously. Try import string; bank = '/'.join(string.ascii_letters)
    # - it works!
    # import string; bank = "/".join(string.ascii_letters)
    good_key = "roscivs"
    bad_key = "monkey"

    with subtests.test("non-existent bank should be empty on cache start"):
        assert not cache.contains(bank=bank)
        assert cache.list(bank=bank) == []

    with subtests.test("after storing key in bank it should be in cache list"):
        cache.store(bank=bank, key=good_key, data=b"\x01\x04\x05fnordy data")
        assert cache.list(bank) == [good_key]

    with subtests.test("after storing value, it should be fetchable"):
        expected_data = "trombone pleasantry"
        cache.store(bank=bank, key=good_key, data=expected_data)
        assert cache.fetch(bank=bank, key=good_key) == expected_data

    with subtests.test("bad key should still be absent from cache"):
        assert cache.fetch(bank=bank, key=bad_key) == {}

    with subtests.test("storing new value should update it"):
        # Double check that the data was still the old stuff
        old_data = expected_data
        assert cache.fetch(bank=bank, key=good_key) == old_data
        new_data = "stromboli"
        cache.store(bank=bank, key=good_key, data=new_data)
        assert cache.fetch(bank=bank, key=good_key) == new_data

    with subtests.test("storing complex object works"):
        new_thing = {
            "some": "data",
            42: "wheee",
            "some other": {"sub": {"objects": "here"}},
        }

        cache.store(bank=bank, key=good_key, data=new_thing)
        actual_thing = cache.fetch(bank=bank, key=good_key)
        if isinstance(cache, salt.cache.MemCache):
            # MemCache should actually store the object - everything else
            # should create a copy of it.
            assert actual_thing is new_thing
        else:
            assert actual_thing is not new_thing
        assert actual_thing == new_thing

    with subtests.test("contains returns true if key in bank"):
        assert cache.contains(bank=bank, key=good_key)

    with subtests.test("contains returns true if bank exists and key is None"):
        assert cache.contains(bank=bank, key=None)

    with subtests.test(
        "contains returns False when bank not in cache and/or key not in bank"
    ):
        assert not cache.contains(bank=bank, key=bad_key)
        assert not cache.contains(bank="nonexistent", key=good_key)
        assert not cache.contains(bank="nonexistent", key=bad_key)
        assert not cache.contains(bank="nonexistent", key=None)

    with subtests.test("flushing nonexistent key should not remove other keys"):
        cache.flush(bank=bank, key=bad_key)
        assert cache.contains(bank=bank, key=good_key)

    with subtests.test(
        "flushing existing key should not remove bank if no more keys exist"
    ):
        pytest.skip(
            "This is impossible with redis. Should we make localfs behave the same way?"
        )
        cache.flush(bank=bank, key=good_key)
        assert cache.contains(bank=bank)
        assert cache.list(bank=bank) == []

    with subtests.test(
        "after existing key is flushed updated should not return a timestamp for that key"
    ):
        cache.store(bank=bank, key=good_key, data="fnord")
        cache.flush(bank=bank, key=good_key)
        timestamp = cache.updated(bank=bank, key=good_key)
        assert timestamp is None

    with subtests.test(
        "after flushing bank containing a good key, updated should not return a timestamp for that key"
    ):
        cache.store(bank=bank, key=good_key, data="fnord")
        cache.flush(bank=bank, key=None)
        timestamp = cache.updated(bank=bank, key=good_key)
        assert timestamp is None

    with subtests.test("flushing bank with None as key should remove bank"):
        cache.flush(bank=bank, key=None)
        assert not cache.contains(bank=bank)

    with subtests.test("Exception should happen when flushing None bank"):
        # This bit is maybe an accidental API, but currently there is no
        # protection at least with the localfs cache when bank is None. If
        # bank is None we try to `os.path.normpath` the bank, which explodes
        # and is at least the current behavior. If we want to change that
        # this test should change. Or be removed altogether.
        # TODO: this should actually not raise. Not sure if there's a test that we can do here... or just call the code which will fail if there's actually an exception. -W. Werner, 2021-09-28
        pytest.skip(
            "Skipping for now - etcd, redis, and mysql do not raise. Should ensure all backends behave consistently"
        )
        with pytest.raises(Exception):
            cache.flush(bank=None, key=None)

    with subtests.test("Updated for non-existent key should return None"):
        timestamp = cache.updated(bank="nonexistent", key="whatever")
        assert timestamp is None

    with subtests.test("Updated for key should return a reasonable time"):
        before_storage = int(time.time())
        cache.store(bank="fnord", key="updated test part 2", data="fnord")
        after_storage = int(time.time())

        timestamp = cache.updated(bank="fnord", key="updated test part 2")

        assert before_storage <= timestamp <= after_storage

    with subtests.test(
        "If the module raises SaltCacheError then it should make it out of updated"
    ):
        with patch.dict(
            cache.modules._dict,
            {"{}.updated".format(cache.driver): MagicMock(side_effect=SaltCacheError)},
        ), pytest.raises(SaltCacheError):
            cache.updated(bank="kaboom", key="oops")

    with subtests.test(
        "cache.cache right after a value is cached should not update the cache"
    ):
        expected_value = "some cool value yo"
        cache.store(bank=bank, key=good_key, data=expected_value)
        result = cache.cache(
            bank=bank,
            key=good_key,
            fun=lambda **kwargs: "bad bad value no good",
            value="some other value?",
            loop_fun=lambda x: "super very no good bad",
        )
        fetch_result = cache.fetch(bank=bank, key=good_key)

        assert result == fetch_result == expected_value

    with subtests.test(
        "cache.cache should update the value with the result of fun when value was updated longer than expiration",
    ), patch(
        "salt.cache.Cache.updated",
        return_value=42,  # Dec 31, 1969... time to update the cache!
        autospec=True,
    ):
        expected_value = "this is the return value woo woo woo"
        cache.store(bank=bank, key=good_key, data="not this value")
        cache_result = cache.cache(
            bank=bank, key=good_key, fun=lambda *args, **kwargs: expected_value
        )
        fetch_result = cache.fetch(bank=bank, key=good_key)

        assert cache_result == fetch_result == expected_value

    with subtests.test(
        "cache.cache should update the value with all of the outputs from loop_fun if loop_fun was provided",
    ), patch(
        "salt.cache.Cache.updated",
        return_value=42,
        autospec=True,
    ):
        expected_value = "SOME HUGE STRING OKAY?"

        cache.store(bank=bank, key=good_key, data="nope, not me")
        cache_result = cache.cache(
            bank=bank,
            key=good_key,
            fun=lambda **kwargs: "some huge string okay?",
            loop_fun=str.upper,
        )
        fetch_result = cache.fetch(bank=bank, key=good_key)

        assert cache_result == fetch_result
        assert "".join(fetch_result) == expected_value

    with subtests.test(
        "cache.cache should update the value if the stored value is empty but present and expiry is way in the future"
    ), patch(
        "salt.cache.Cache.updated",
        return_value=time.time() * 2,
        autospec=True,
    ):
        # Unclear if this was intended behavior: currently any falsey data will
        # be updated by cache.cache. If this is incorrect, this test should
        # be updated or removed.
        expected_data = "some random string whatever"
        for empty in ("", (), [], {}, 0, 0.0, False, None):
            with subtests.test(empty=empty):
                cache.store(
                    bank=bank, key=good_key, data=empty
                )  # empty chairs and empty data
                cache_result = cache.cache(
                    bank=bank, key=good_key, fun=lambda **kwargs: expected_data
                )
                fetch_result = cache.fetch(bank=bank, key=good_key)

                assert cache_result == fetch_result == expected_data

    with subtests.test("cache.cache should store a value if it does not exist"):
        expected_result = "some result plz"
        cache.flush(bank=bank, key=None)
        assert cache.fetch(bank=bank, key=good_key) == {}
        cache_result = cache.cache(
            bank=bank, key=good_key, fun=lambda **kwargs: expected_result
        )
        fetch_result = cache.fetch(bank=bank, key=good_key)

        assert cache_result == fetch_result
        assert fetch_result == expected_result
        assert cache_result == fetch_result == expected_result
Example #26
0
def gen_key(minion_id, dns_name=None, zone='default', password=None):
    '''
    Generate and return an private_key. If a ``dns_name`` is passed in, the
    private_key will be cached under that name. The type of key and the
    parameters used to generate the key are based on the default certificate
    use policy associated with the specified zone.

    CLI Example:

    .. code-block:: bash

        salt-run venafi.gen_key <minion_id> [dns_name] [zone] [password]
    '''
    # Get the default certificate use policy associated with the zone
    # so we can generate keys that conform with policy

    # The /v1/zones/tag/{name} API call is a shortcut to get the zoneID
    # directly from the name

    qdata = __utils__['http.query'](
        '{0}/zones/tag/{1}'.format(_base_url(), zone),
        method='GET',
        decode=True,
        decode_type='json',
        header_dict={
            'tppl-api-key': _api_key(),
            'Content-Type': 'application/json',
        },
    )

    zone_id = qdata['dict']['id']

    # the /v1/certificatepolicies?zoneId API call returns the default
    # certificate use and certificate identity policies

    qdata = __utils__['http.query'](
        '{0}/certificatepolicies?zoneId={1}'.format(_base_url(), zone_id),
        method='GET',
        decode=True,
        decode_type='json',
        header_dict={
            'tppl-api-key': _api_key(),
            'Content-Type': 'application/json',
        },
    )

    policies = qdata['dict']['certificatePolicies']

    # Extract the key length and key type from the certificate use policy
    # and generate the private key accordingly

    for policy in policies:
        if policy['certificatePolicyType'] == "CERTIFICATE_USE":
            keyTypes = policy['keyTypes']
            # in case multiple keytypes and key lengths are supported
            # always use the first key type and key length
            keygen_type = keyTypes[0]['keyType']
            key_len = keyTypes[0]['keyLengths'][0]

    if int(key_len) < 2048:
        key_len = 2048

    if keygen_type == "RSA":
        gen = RSA.generate(bits=key_len)
        private_key = gen.exportKey('PEM', password)
        if dns_name is not None:
            bank = 'venafi/domains'
            cache = salt.cache.Cache(__opts__, syspaths.CACHE_DIR)
            try:
                data = cache.fetch(bank, dns_name)
                data['private_key'] = private_key
                data['minion_id'] = minion_id
            except TypeError:
                data = {'private_key': private_key,
                        'minion_id': minion_id}
            cache.store(bank, dns_name, data)
    return private_key
Example #27
0
def get_certificate(order_id=None,
                    certificate_id=None,
                    minion_id=None,
                    cert_format='pem_all',
                    filename=None):
    '''
    Retrieve a certificate by order_id or certificate_id and write it to stdout or a filename.

    A list of permissible cert_formats is here:
        https://www.digicert.com/services/v2/documentation/appendix-certificate-formats

    CLI Example:

    .. code-block:: bash

        salt-run digicert.get_certificate order_id=48929454 cert_format=apache

    Including a 'filename' will write the certificate to the desired file.
    Note that some cert formats are zipped files, and some are binary.

    If the certificate has not been issued, this function will return the order details
    inside of which will be a status (one of pending, rejected, processing, issued,
    revoked, canceled, needs_csr, and needs_approval)

    If for some reason you want to pipe the output of this command to a file or other
    command you will want to leave off the ``filename`` argument and make sure to include
    ``--no-color`` so there will be no terminal ANSI escape sequences.

    '''

    if order_id:
        order_cert = salt.utils.http.query('{0}/order/certificate/{1}'.format(
            _base_url(), order_id),
                                           method='GET',
                                           raise_error=False,
                                           decode=True,
                                           decode_type='json',
                                           header_dict={
                                               'X-DC-DEVKEY': _api_key(),
                                               'Content-Type':
                                               'application/json',
                                           })
        if order_cert['dict'].get('status') != 'issued':
            return {'certificate': order_cert['dict']}

        if order_cert['dict'].get('errors', False):
            return {'certificate': order_cert['dict']}

        certificate_id = order_cert['dict'].get('certificate').get('id', None)
        common_name = order_cert['dict'].get('certificate').get('common_name')

    if not certificate_id:
        return {
            'certificate': {
                'errors': {
                    'code':
                    'unknown',
                    'message':
                    'Unknown error, no certificate ID passed on command line or in body returned from API'
                }
            }
        }

    if filename:
        ret_cert = salt.utils.http.query(
            '{0}/certificate/{1}/download/format/{2}'.format(
                _base_url(), certificate_id, cert_format),
            method='GET',
            decode=False,
            text=False,
            headers=True,
            text_out=filename,
            raise_error=False,
            header_dict={
                'X-DC-DEVKEY': _api_key(),
            })
    else:
        ret_cert = salt.utils.http.query(
            '{0}/certificate/{1}/download/format/{2}'.format(
                _base_url(), certificate_id, cert_format),
            method='GET',
            text=False,
            decode=False,
            raise_error=False,
            header_dict={
                'X-DC-DEVKEY': _api_key(),
            })
    if 'errors' in ret_cert:
        return {'certificate': ret_cert}

    if 'body' not in ret_cert:
        ret = {'certificate': ret_cert}
        cert = ret_cert
    if isinstance(ret_cert, dict):
        ret = ret_cert['body']
        cert = ret
    else:
        ret = ret_cert
        cert = ret

    tmpfilename = None
    if not filename:
        fd, tmpfilename = tempfile.mkstemp()
        filename = tmpfilename
        os.write(fd, cert)
        os.close(fd)

    cmd = [
        'openssl', 'x509', '-noout', '-subject', '-nameopt', 'multiline',
        '-in', filename
    ]
    out = subprocess.check_output(cmd)
    common_name = None
    for l in out.splitlines():
        common_name_match = re.search(' *commonName *= *(.*)', l)
        if common_name_match:
            common_name = common_name_match.group(1)
            break
    if tmpfilename:
        os.unlink(tmpfilename)

    if common_name:
        bank = 'digicert/domains'
        cache = salt.cache.Cache(__opts__, syspaths.CACHE_DIR)
        try:
            data = cache.fetch(bank, common_name)
        except TypeError:
            data = {'certificate': cert}
        cache.store(bank, common_name, data)

    if 'headers' in ret_cert:
        return {
            'certificate': {
                'filename':
                filename,
                'original_filename':
                ret_cert['headers'].get('Content-Disposition', 'Not provided'),
                'Content-Type':
                ret_cert['headers'].get('Content-Type', 'Not provided')
            }
        }

    return {'certificate': cert}