class TransIpAcmeDnsClient(AcmeDnsClient):
    def __init__(self, authoritative_name_servers, config, sleep_config):
        super().__init__(authoritative_name_servers, sleep_config)
        self.tid = DomainService(login=config['username'],
                                 private_key_file=config['key-file'])
        self.dns_records = {}

    def load_domain(self, domain):
        if domain in self.dns_records:
            return

        self.dns_records[domain] = self.tid.get_info(domain).dnsEntries

    def build_record(self, domain, name, challenge):
        return DnsEntry(name[:-(len(domain) + 1)], 1, DnsEntry.TYPE_TXT,
                        challenge)

    def _queue_add(self, domain, name, challenge):
        self.load_domain(domain)
        record = self.build_record(domain, name, challenge)
        if record in self.dns_records[domain]:
            raise Exception('record already exists for %s -> %s' %
                            (name, challenge))

        self.dns_records[domain].append(record)

    def _queue_delete(self, domain, name, challenge):
        self.load_domain(domain)
        record = self.build_record(domain, name, challenge)

        try:
            self.dns_records[domain].remove(record)
        except ValueError:
            pass

    def execute(self):
        for domain in self.dns_records:
            self.tid.set_dns_entries(domain, self.dns_records[domain])
Beispiel #2
0
class Provider(BaseProvider):
    """
    Provider class for Transip

    provider_options can be overwritten by a Provider to setup custom defaults.
    They will be overwritten by any options set via the CLI or Env.
    order is:

    """
    def __init__(self, config):
        super(Provider, self).__init__(config)
        self.provider_name = "transip"
        self.domain_id = None

        username = self._get_provider_option("auth_username")
        key_file = self._get_provider_option("auth_api_key")

        if not username or not key_file:
            raise Exception("No username and/or keyfile was specified")

        self.client = DomainService(login=username, private_key_file=key_file)

    # Authenticate against provider,
    # Make any requests required to get the domain's id for this provider,
    # so it can be used in subsequent calls.
    # Should throw an error if authentication fails for any reason,
    # of if the domain does not exist.
    def _authenticate(self):
        # This request will fail when the domain does not exist,
        # allowing us to check for existence
        domain = self.domain
        try:
            self.client.get_info(domain)
        except BaseException:
            raise Exception("Could not retrieve information about {0}, "
                            "is this domain yours?".format(domain))
        self.domain_id = domain

    # Create record. If record already exists with the same content, do nothing'
    def _create_record(self, rtype, name, content):
        records = self.client.get_info(self.domain).dnsEntries

        if self._filter_records(records, rtype, name, content):
            # Nothing to do, record already exists
            LOGGER.debug("create_record: already exists")
            return True

        records.append(
            DnsEntry(
                **{
                    "name": self._relative_name(name),
                    "record_type": rtype,
                    "content": self._bind_format_target(rtype, content),
                    "expire": self._get_lexicon_option("ttl"),
                }))

        self.client.set_dns_entries(self.domain, records)
        status = (len(
            self._list_records_internal(
                rtype, name, content, show_output=False)) >= 1)
        LOGGER.debug("create_record: %s", status)
        return status

    # List all records. Return an empty list if no records found
    # type, name and content are used to filter records.
    # If possible filter during the query, otherwise filter after response is received.
    def _list_records(self, rtype=None, name=None, content=None):
        return self._list_records_internal(rtype=rtype,
                                           name=name,
                                           content=content)

    def _list_records_internal(self,
                               rtype=None,
                               name=None,
                               content=None,
                               show_output=True):
        all_records = self._convert_records(
            self.client.get_info(self.domain).dnsEntries)
        records = self._filter_records(records=all_records,
                                       rtype=rtype,
                                       name=name,
                                       content=content)

        if show_output:
            LOGGER.debug("list_records: %s", records)
        return records

    # Update a record. Identifier must be specified.
    def _update_record(self,
                       identifier=None,
                       rtype=None,
                       name=None,
                       content=None):
        if not (rtype or name or content):
            raise Exception(
                "At least one of rtype, name or content must be specified.")

        all_records = self._list_records_internal(show_output=False)
        filtered_records = self._filter_records(all_records, rtype, name)

        for record in filtered_records:
            all_records.remove(record)
        all_records.append({
            "name": name,
            "type": rtype,
            "content": self._bind_format_target(rtype, content),
            "ttl": self._get_lexicon_option("ttl"),
        })

        self.client.set_dns_entries(self.domain,
                                    self._convert_records_back(all_records))
        status = (len(
            self._list_records_internal(
                rtype, name, content, show_output=False)) >= 1)
        LOGGER.debug("update_record: %s", status)
        return status

    # Delete an existing record.
    # If record does not exist, do nothing.
    # If an identifier is specified, use it, otherwise do a lookup using type, name and content.
    def _delete_record(self,
                       identifier=None,
                       rtype=None,
                       name=None,
                       content=None):
        if not (rtype or name or content):
            raise Exception(
                "At least one of rtype, name or content must be specified.")

        all_records = self._list_records_internal(show_output=False)
        filtered_records = self._filter_records(all_records, rtype, name,
                                                content)

        for record in filtered_records:
            all_records.remove(record)

        self.client.set_dns_entries(self.domain,
                                    self._convert_records_back(all_records))
        status = (len(
            self._list_records_internal(rtype,
                                        name,
                                        content,
                                        show_output=False)) == 0)
        LOGGER.debug("delete_record: %s", status)
        return status

    def _full_name(self, record_name):
        if record_name == "@":
            record_name = self.domain
        return super(Provider, self)._full_name(record_name)

    def _relative_name(self, record_name):
        name = super(Provider, self)._relative_name(record_name)
        if not name:
            name = "@"
        return name

    def _bind_format_target(self, rtype, target):
        if rtype == "CNAME" and not target.endswith("."):
            target += "."
        return target

    # Convert the objects from transip to dicts, for easier processing
    def _convert_records(self, records):
        _records = []
        for record in records:
            _records.append({
                "id":
                "{0}-{1}".format(self._full_name(record.name), record.type),
                "name":
                self._full_name(record.name),
                "type":
                record.type,
                "content":
                record.content,
                "ttl":
                record.expire,
            })
        return _records

    def _to_dns_entry(self, _entry):
        return DnsEntry(
            self._relative_name(_entry["name"]),
            _entry["ttl"],
            _entry["type"],
            _entry["content"],
        )

    def _convert_records_back(self, _records):
        return [self._to_dns_entry(record) for record in _records]

    # Filter a list of records based on criteria
    def _filter_records(self, records, rtype=None, name=None, content=None):
        _records = []
        for record in records:
            if ((not rtype or record["type"] == rtype) and
                (not name
                 or self._full_name(record["name"]) == self._full_name(name))
                    and (not content or record["content"] == content)):
                _records.append(record)
        return _records

    def _request(self, action="GET", url="/", data=None, query_params=None):
        # Helper _request is not used in Transip.
        pass
Beispiel #3
0
class Provider(BaseProvider):
    def __init__(self, options, provider_options={}):
        super(Provider, self).__init__(options)
        self.options.update(provider_options)
        self.provider_name = 'transip'
        self.domain_id = None

        username = self.options.get('auth_username')
        key_file = self.options.get('auth_api_key')

        if not username or not key_file:
            raise Exception("No username and/or keyfile was specified")

        self.client = DomainService(login=username, private_key_file=key_file)

    # Authenticate against provider,
    # Make any requests required to get the domain's id for this provider, so it can be used in subsequent calls.
    # Should throw an error if authentication fails for any reason, of if the domain does not exist.
    def authenticate(self):
        ## This request will fail when the domain does not exist,
        ## allowing us to check for existence
        domain = self.options.get('domain')
        try:
            self.client.get_info(domain)
        except:
            raise
            raise Exception("Could not retrieve information about {0}, "
                            "is this domain yours?".format(domain))
        self.domain_id = domain

    # Create record. If record already exists with the same content, do nothing'
    def create_record(self, type, name, content):
        records = self.client.get_info(self.options.get('domain')).dnsEntries
        if self._filter_records(records, type, name, content):
            # Nothing to do, record already exists
            print('create_record: already exists')
            return True

        records.append(
            DnsEntry(
                **{
                    "name": self._relative_name(name),
                    "record_type": type,
                    "content": self._bind_format_target(type, content),
                    "expire": self.options.get('ttl') or 86400
                }))

        self.client.set_dns_entries(self.options.get('domain'), records)
        status = len(self.list_records(type, name, content,
                                       show_output=False)) >= 1
        print("create_record: {0}".format(status))
        return status

    # List all records. Return an empty list if no records found
    # type, name and content are used to filter records.
    # If possible filter during the query, otherwise filter after response is received.
    def list_records(self,
                     type=None,
                     name=None,
                     content=None,
                     show_output=True):
        all_records = self._convert_records(
            self.client.get_info(self.options.get('domain')).dnsEntries)
        records = self._filter_records(records=all_records,
                                       type=type,
                                       name=name,
                                       content=content)

        if show_output:
            print('list_records: {0}'.format(records))
        return records

    # Update a record. Identifier must be specified.
    def update_record(self,
                      identifier=None,
                      type=None,
                      name=None,
                      content=None):
        if not (type or name or content):
            raise Exception(
                "At least one of type, name or content must be specified.")

        all_records = self.list_records(show_output=False)
        filtered_records = self._filter_records(all_records, type, name)

        for record in filtered_records:
            all_records.remove(record)
        all_records.append({
            "name": name,
            "type": type,
            "content": self._bind_format_target(type, content),
            "ttl": self.options.get('ttl') or 86400
        })

        self.client.set_dns_entries(self.options.get('domain'),
                                    self._convert_records_back(all_records))
        status = len(self.list_records(type, name, content,
                                       show_output=False)) >= 1
        print("update_record: {0}".format(status))
        return status

    # Delete an existing record.
    # If record does not exist, do nothing.
    # If an identifier is specified, use it, otherwise do a lookup using type, name and content.
    def delete_record(self,
                      identifier=None,
                      type=None,
                      name=None,
                      content=None):
        if not (type or name or content):
            raise Exception(
                "At least one of type, name or content must be specified.")

        all_records = self.list_records(show_output=False)
        filtered_records = self._filter_records(all_records, type, name,
                                                content)

        for record in filtered_records:
            all_records.remove(record)

        self.client.set_dns_entries(self.options.get('domain'),
                                    self._convert_records_back(all_records))
        status = len(self.list_records(type, name, content,
                                       show_output=False)) == 0
        print("delete_record: {0}".format(status))
        return status

    def _full_name(self, record_name):
        if record_name == "@":
            record_name = self.options['domain']
        return super(Provider, self)._full_name(record_name)

    def _relative_name(self, record_name):
        name = super(Provider, self)._relative_name(record_name)
        if not name:
            name = "@"
        return name

    def _bind_format_target(self, type, target):
        if type == "CNAME" and not target.endswith("."):
            target += "."
        return target

    # Convert the objects from transip to dicts, for easier processing
    def _convert_records(self, records):
        _records = []
        for record in records:
            _records.append({
                "id":
                "{0}-{1}".format(self._full_name(record.name), record.type),
                "name":
                self._full_name(record.name),
                "type":
                record.type,
                "content":
                record.content,
                "ttl":
                record.expire
            })
        return _records

    def _to_dns_entry(self, _entry):
        return DnsEntry(self._relative_name(_entry['name']), _entry['ttl'],
                        _entry['type'], _entry['content'])

    def _convert_records_back(self, _records):
        return [self._to_dns_entry(record) for record in _records]

    # Filter a list of records based on criteria
    def _filter_records(self, records, type=None, name=None, content=None):
        _records = []
        for record in records:
            if (not type or record['type'] == type) and \
               (not name or record['name'] == self._full_name(name)) and \
               (not content or record['content'] == content):
                _records.append(record)
        return _records
class _TransipClient(object):
    """Encapsulates all communication with the Transip API."""

    def __init__(self, username, key_file):
        self.logger = logger.getChild(self.__class__.__name__)
        self.domain_service = DomainService(login=username, private_key_file=key_file)

    def add_txt_record(self, domain_name, record_name, record_content):
        """
        Add a TXT record using the supplied information.

        :param str domain_name: The domain to use to associate the record with.
        :param str record_name: The record name (typically beginning with '_acme-challenge.').
        :param str record_content: The record content (typically the challenge validation).
        :raises certbot.errors.PluginError: if an error occurs communicating with the Transip
                                            API
        """
        try:
            domain = self._find_domain(domain_name)
        except suds.WebFault as e:
            self.logger.error('Error finding domain using the Transip API: %s', e)
            raise errors.PluginError('Error finding domain using the Transip API: {0}'
                                     .format(e))

        domain_records = self.get_dns_entries(domain_name)

        try:
            new_record = DnsEntry(
                name=self._compute_record_name(domain, record_name),
                record_type='TXT',
                content=record_content,
                expire=1,
            )
        except suds.WebFault as e:
            self.logger.error('Error getting DNS records using the Transip API: %s', e)
            return

        domain_records.append(new_record)

        try:
            self.domain_service.set_dns_entries(domain_name=domain, dns_entries=domain_records)
            self.logger.info('Successfully added TXT record')
        except suds.WebFault as e:
            self.logger.error('Error adding TXT record using the Transip API: %s', e)
            raise errors.PluginError('Error adding TXT record using the Transip API: {0}'
                                     .format(e))

    def del_txt_record(self, domain_name, record_name, record_content):
        """
        Delete a TXT record using the supplied information.

        Note that both the record's name and content are used to ensure that similar records
        created concurrently (e.g., due to concurrent invocations of this plugin) are not deleted.

        Failures are logged, but not raised.

        :param str domain_name: The domain to use to associate the record with.
        :param str record_name: The record name (typically beginning with '_acme-challenge.').
        :param str record_content: The record content (typically the challenge validation).
        """
        try:
            domain = self._find_domain(domain_name)
        except suds.WebFault as e:
            self.logger.error('Error finding domain using the Transip API: %s', e)
            return

        domain_records = self._get_dns_entries(domain_name=domain)

        matching_records = [record for record in domain_records
                            if record.type == 'TXT'
                            and record.name == self._compute_record_name(domain, record_name)
                            and record.content == record_content]

        for record in matching_records:
            self.logger.info('Removing TXT record with name: %s', record.name)
            del domain_records[domain_records.index(record)]

        try:
            self.domain_service.set_dns_entries(domain_name=domain, dns_entries=domain_records)
        except suds.WebFault as e:
            self.logger.error('Error while storing DNS records: %s', e)

    def _get_dns_entries(self, domain, retries=3, backoff=5):
        """
        Get all DNS entries for this domain.

        :param str domain_name: The domain to use to associate the record with.
        :raises certbot.errors.PluginError: if an error occurs communicating with the Transip
                                            API
        """
        def _get_dns_entries_transip(domain):
            try:
                dns_entries = self.domain_service.get_info(domain_name=domain).dnsEntries
            except suds.WebFault as e:
                self.logger.error('Error getting DNS records using the Transip API: %s', e)
                raise errors.PluginError('Error finding DNS entries using the Transip API: {0}'
                                         .format(domain))
            return dns_entries

        dns_entries = _get_dns_entries_transip(domain)

        # If there are no DNS entries try again
        # Retry after 5 seconds, 10 seconds and 20 seconds
        if not dns_entries:
            self.logger.error('Error getting DNS records using the Transip API: '
                              'retry in {} seconds'.format(backoff))
            for retry in range(retries):
                time.sleep(backoff)
                dns_entries = _get_dns_entries_transip(domain)
                backoff = backoff * 2
                if dns_entries:
                    break

        # If there are still no entries the Transip API gives back the wrong data
        if not dns_entries:
            self.logger.error('Error getting DNS records using the Transip API: '
                              'Empty record set for {}'.format(domain))
            raise errors.PluginError('Error finding DNS entries using the Transip API: '
                                     'Empty record set for {}'.format(domain))

        return dns_entries

    def _find_domain(self, domain_name):
        """
        Find the domain object for a given domain name.

        :param str domain_name: The domain name for which to find the corresponding Domain.
        :returns: The Domain, if found.
        :rtype: `str`
        :raises certbot.errors.PluginError: if no matching Domain is found.
        """
        domain_name_guesses = dns_common.base_domain_name_guesses(domain_name)

        domains = self.domain_service.get_domain_names()

        for guess in domain_name_guesses:
            if guess in domains:
                self.logger.debug('Found base domain for %s using name %s', domain_name, guess)
                return guess

        raise errors.PluginError('Unable to determine base domain for {0} using names: {1}.'
                                 # .format(domain_name, domain_name_guesses)
                                 )

    @staticmethod
    def _compute_record_name(domain, full_record_name):
        # The domain, from Transip's point of view, is automatically appended.
        return full_record_name.rpartition("." + domain)[0]
Beispiel #5
0
def main():
    """
    Updates all DNS entries in a TransIP account to the value of the IP number of the
    internet connection that is used when calling this script.

    Handy when running a server at home and the ISP has given a new IP number.

    The IP number of 'check-host' is resolved and compared to the current public IP number.
    If they differ, all dns entries that have the 'old' ip number will be updated to 'new'.

    For exanple: ./bulk_update_account.py --check-host check.example.org --user-name example-user

    Warning: this will bulk update all relevant DNS entries in all domains for an account!
    """

    parser = argparse.ArgumentParser()

    parser.add_argument('-u',
                        '--user-name',
                        help='TransIP username',
                        dest='user_name')
    parser.add_argument('-c',
                        '--check-host',
                        help='Check host',
                        dest='check_host')

    parser.add_argument('--dns-server',
                        help='DNS server to use',
                        dest='dns_server')
    parser.add_argument('--api-key',
                        help='TransIP private key',
                        dest='api_key_file')

    args = parser.parse_args()

    if not args.user_name:
        print('Please provide your TransIP username.')
        exit(1)

    if not args.check_host:
        print(
            'Please provide a hostname to be checked for a changed public IP address.'
        )
        exit(1)

    if not args.api_key_file:
        args.api_key_file = 'decrypted_key'

    if not args.dns_server:
        # TransIP DNS server
        args.dns_server = '195.135.195.195'

    domain_service = DomainService(args.user_name, args.api_key_file)

    # Validate that check-host belongs to the account given
    if len([
            domain_name for domain_name in domain_service.get_domain_names()
            if args.check_host.endswith('.' + domain_name)
    ]) != 1:
        print(
            'The check-host parameter does not match with any of your domain names.'
        )
        exit(1)

    # Find 'old' and 'new' IP number
    res = resolver.Resolver()
    res.nameservers = [args.dns_server]
    old_ip = res.query(args.check_host)[0].address
    current_ip = requests.get('https://api.ipify.org?format=json').json()['ip']

    if old_ip != current_ip:

        print('Old ip: {}, current ip: {}. Updating DNS entries.'.format(
            old_ip, current_ip))

        for update_domain in domain_service.get_domain_names():

            print('Updating {}'.format(update_domain))

            # Create a new set with entries to be updated
            dns_entries = [
                DnsEntry(
                    entry['name'], entry['expire'], entry['type'], current_ip
                    if entry['content'] == old_ip else entry['content'])
                for entry in domain_service.get_info(update_domain).dnsEntries
            ]

            try:
                result = domain_service.set_dns_entries(
                    update_domain, dns_entries)
                if result is not None:
                    print(result)
            except WebFault as err:
                print(err)
                exit(1)
    else:
        print(
            'Old ip and current ip ({}) are the same. Not updating DNS entries.'
            .format(old_ip))
Beispiel #6
0
class TransipProvider(BaseProvider):
    '''
    Transip DNS provider

    transip:
        class: octodns.provider.transip.TransipProvider
        # Your Transip account name (required)
        account: yourname
        # Path to a private key file (required if key is not used)
        key_file: /path/to/file
        # The api key as string (required if key_file is not used)
        key: |
            \'''
            -----BEGIN PRIVATE KEY-----
            ...
            -----END PRIVATE KEY-----
            \'''
        # if both `key_file` and `key` are presented `key_file` is used

    '''
    SUPPORTS_GEO = False
    SUPPORTS_DYNAMIC = False
    SUPPORTS = set(
        ('A', 'AAAA', 'CNAME', 'MX', 'SRV', 'SPF', 'TXT', 'SSHFP', 'CAA'))
    # unsupported by OctoDNS: 'TLSA'
    MIN_TTL = 120
    TIMEOUT = 15
    ROOT_RECORD = '@'

    def __init__(self, id, account, key=None, key_file=None, *args, **kwargs):
        self.log = getLogger('TransipProvider[{}]'.format(id))
        self.log.debug('__init__: id=%s, account=%s, token=***', id, account)
        super(TransipProvider, self).__init__(id, *args, **kwargs)

        if key_file is not None:
            self._client = DomainService(account, private_key_file=key_file)
        elif key is not None:
            self._client = DomainService(account, private_key=key)
        else:
            raise TransipConfigException(
                'Missing `key` of `key_file` parameter in config')

        self.account = account
        self.key = key

        self._currentZone = {}

    def populate(self, zone, target=False, lenient=False):

        exists = False
        self._currentZone = zone
        self.log.debug('populate: name=%s, target=%s, lenient=%s', zone.name,
                       target, lenient)

        before = len(zone.records)
        try:
            zoneInfo = self._client.get_info(zone.name[:-1])
        except WebFault as e:
            if e.fault.faultcode == '102' and target is False:
                # Zone not found in account, and not a target so just
                # leave an empty zone.
                return exists
            elif e.fault.faultcode == '102' and target is True:
                self.log.warning('populate: Transip can\'t create new zones')
                raise TransipNewZoneException(
                    ('populate: ({}) Transip used ' +
                     'as target for non-existing zone: {}').format(
                         e.fault.faultcode, zone.name))
            else:
                self.log.error('populate: (%s) %s ', e.fault.faultcode,
                               e.fault.faultstring)
                raise e

        self.log.debug('populate: found %s records for zone %s',
                       len(zoneInfo.dnsEntries), zone.name)
        exists = True
        if zoneInfo.dnsEntries:
            values = defaultdict(lambda: defaultdict(list))
            for record in zoneInfo.dnsEntries:
                name = zone.hostname_from_fqdn(record['name'])
                if name == self.ROOT_RECORD:
                    name = ''

                if record['type'] in self.SUPPORTS:
                    values[name][record['type']].append(record)

            for name, types in values.items():
                for _type, records in types.items():
                    data_for = getattr(self, '_data_for_{}'.format(_type))
                    record = Record.new(zone,
                                        name,
                                        data_for(_type, records),
                                        source=self,
                                        lenient=lenient)
                    zone.add_record(record, lenient=lenient)
        self.log.info('populate:   found %s records, exists = %s',
                      len(zone.records) - before, exists)

        self._currentZone = {}
        return exists

    def _apply(self, plan):
        desired = plan.desired
        changes = plan.changes
        self.log.debug('apply: zone=%s, changes=%d', desired.name,
                       len(changes))

        self._currentZone = plan.desired
        try:
            self._client.get_info(plan.desired.name[:-1])
        except WebFault as e:
            self.log.exception('_apply: get_info failed')
            raise e

        _dns_entries = []
        for record in plan.desired.records:
            if record._type in self.SUPPORTS:
                entries_for = getattr(self,
                                      '_entries_for_{}'.format(record._type))

                # Root records have '@' as name
                name = record.name
                if name == '':
                    name = self.ROOT_RECORD

                _dns_entries.extend(entries_for(name, record))

        try:
            self._client.set_dns_entries(plan.desired.name[:-1], _dns_entries)
        except WebFault as e:
            self.log.warning(
                ('_apply: Set DNS returned ' +
                 'one or more errors: {}').format(e.fault.faultstring))
            raise TransipException(200, e.fault.faultstring)

        self._currentZone = {}

    def _entries_for_multiple(self, name, record):
        _entries = []

        for value in record.values:
            _entries.append(DnsEntry(name, record.ttl, record._type, value))

        return _entries

    def _entries_for_single(self, name, record):

        return [DnsEntry(name, record.ttl, record._type, record.value)]

    _entries_for_A = _entries_for_multiple
    _entries_for_AAAA = _entries_for_multiple
    _entries_for_NS = _entries_for_multiple
    _entries_for_SPF = _entries_for_multiple
    _entries_for_CNAME = _entries_for_single

    def _entries_for_MX(self, name, record):
        _entries = []

        for value in record.values:
            content = "{} {}".format(value.preference, value.exchange)
            _entries.append(DnsEntry(name, record.ttl, record._type, content))

        return _entries

    def _entries_for_SRV(self, name, record):
        _entries = []

        for value in record.values:
            content = "{} {} {} {}".format(value.priority, value.weight,
                                           value.port, value.target)
            _entries.append(DnsEntry(name, record.ttl, record._type, content))

        return _entries

    def _entries_for_SSHFP(self, name, record):
        _entries = []

        for value in record.values:
            content = "{} {} {}".format(value.algorithm,
                                        value.fingerprint_type,
                                        value.fingerprint)
            _entries.append(DnsEntry(name, record.ttl, record._type, content))

        return _entries

    def _entries_for_CAA(self, name, record):
        _entries = []

        for value in record.values:
            content = "{} {} {}".format(value.flags, value.tag, value.value)
            _entries.append(DnsEntry(name, record.ttl, record._type, content))

        return _entries

    def _entries_for_TXT(self, name, record):
        _entries = []

        for value in record.values:
            value = value.replace('\\;', ';')
            _entries.append(DnsEntry(name, record.ttl, record._type, value))

        return _entries

    def _parse_to_fqdn(self, value):

        # Enforce switch from suds.sax.text.Text to string
        value = str(value)

        # TransIP allows '@' as value to alias the root record.
        # this provider won't set an '@' value, but can be an existing record
        if value == self.ROOT_RECORD:
            value = self._currentZone.name

        if value[-1] != '.':
            self.log.debug('parseToFQDN: changed %s to %s', value,
                           '{}.{}'.format(value, self._currentZone.name))
            value = '{}.{}'.format(value, self._currentZone.name)

        return value

    def _get_lowest_ttl(self, records):
        _ttl = 100000
        for record in records:
            _ttl = min(_ttl, record['expire'])
        return _ttl

    def _data_for_multiple(self, _type, records):

        _values = []
        for record in records:
            # Enforce switch from suds.sax.text.Text to string
            _values.append(str(record['content']))

        return {
            'ttl': self._get_lowest_ttl(records),
            'type': _type,
            'values': _values
        }

    _data_for_A = _data_for_multiple
    _data_for_AAAA = _data_for_multiple
    _data_for_NS = _data_for_multiple
    _data_for_SPF = _data_for_multiple

    def _data_for_CNAME(self, _type, records):
        return {
            'ttl': records[0]['expire'],
            'type': _type,
            'value': self._parse_to_fqdn(records[0]['content'])
        }

    def _data_for_MX(self, _type, records):
        _values = []
        for record in records:
            preference, exchange = record['content'].split(" ", 1)
            _values.append({
                'preference': preference,
                'exchange': self._parse_to_fqdn(exchange)
            })
        return {
            'ttl': self._get_lowest_ttl(records),
            'type': _type,
            'values': _values
        }

    def _data_for_SRV(self, _type, records):
        _values = []
        for record in records:
            priority, weight, port, target = record['content'].split(' ', 3)
            _values.append({
                'port': port,
                'priority': priority,
                'target': self._parse_to_fqdn(target),
                'weight': weight
            })

        return {
            'type': _type,
            'ttl': self._get_lowest_ttl(records),
            'values': _values
        }

    def _data_for_SSHFP(self, _type, records):
        _values = []
        for record in records:
            algorithm, fp_type, fingerprint = record['content'].split(' ', 2)
            _values.append({
                'algorithm': algorithm,
                'fingerprint': fingerprint.lower(),
                'fingerprint_type': fp_type
            })

        return {
            'type': _type,
            'ttl': self._get_lowest_ttl(records),
            'values': _values
        }

    def _data_for_CAA(self, _type, records):
        _values = []
        for record in records:
            flags, tag, value = record['content'].split(' ', 2)
            _values.append({'flags': flags, 'tag': tag, 'value': value})

        return {
            'type': _type,
            'ttl': self._get_lowest_ttl(records),
            'values': _values
        }

    def _data_for_TXT(self, _type, records):
        _values = []
        for record in records:
            _values.append(record['content'].replace(';', '\\;'))

        return {
            'type': _type,
            'ttl': self._get_lowest_ttl(records),
            'values': _values
        }
Beispiel #7
0
class Provider(BaseProvider):

    """
    provider_options can be overwritten by a Provider to setup custom defaults.
    They will be overwritten by any options set via the CLI or Env.
    order is:

    """
    def provider_options(self):
        return {'ttl': 86400}

    def __init__(self, options, engine_overrides=None):
        super(Provider, self).__init__(options, engine_overrides)
        self.provider_name = 'transip'
        self.domain_id = None

        username = self.options.get('auth_username')
        key_file = self.options.get('auth_api_key')

        if not username or not key_file:
            raise Exception("No username and/or keyfile was specified")

        self.client = DomainService(
            login=username,
            private_key_file=key_file
        )

    # Authenticate against provider,
    # Make any requests required to get the domain's id for this provider, so it can be used in subsequent calls.
    # Should throw an error if authentication fails for any reason, of if the domain does not exist.
    def authenticate(self):
        ## This request will fail when the domain does not exist,
        ## allowing us to check for existence
        domain = self.options.get('domain')
        try:
            self.client.get_info(domain)
        except:
            raise
            raise Exception("Could not retrieve information about {0}, "
                                "is this domain yours?".format(domain))
        self.domain_id = domain

    # Create record. If record already exists with the same content, do nothing'
    def create_record(self, type, name, content):
        records = self.client.get_info(self.options.get('domain')).dnsEntries

        if self._filter_records(records, type, name, content):
            # Nothing to do, record already exists
            logger.debug('create_record: already exists')
            return True

        records.append(DnsEntry(**{
            "name": self._relative_name(name),
            "record_type": type,
            "content": self._bind_format_target(type, content),
            "expire": self.options.get('ttl')
        }))

        self.client.set_dns_entries(self.options.get('domain'), records)
        status = len(self.list_records(type, name, content, show_output=False)) >= 1
        logger.debug('create_record: %s', status)
        return status

    # List all records. Return an empty list if no records found
    # type, name and content are used to filter records.
    # If possible filter during the query, otherwise filter after response is received.
    def list_records(self, type=None, name=None, content=None, show_output=True):
        all_records = self._convert_records(self.client.get_info(self.options.get('domain')).dnsEntries)
        records = self._filter_records(
            records=all_records,
            type=type,
            name=name,
            content=content
        )

        if show_output:
            logger.debug('list_records: %s', records)
        return records

    # Update a record. Identifier must be specified.
    def update_record(self, identifier=None, type=None, name=None, content=None):
        if not (type or name or content):
            raise Exception("At least one of type, name or content must be specified.")

        all_records = self.list_records(show_output=False)
        filtered_records = self._filter_records(all_records, type, name)

        for record in filtered_records:
            all_records.remove(record)
        all_records.append({
            "name": name,
            "type": type,
            "content": self._bind_format_target(type, content),
            "ttl": self.options.get('ttl')
        })

        self.client.set_dns_entries(self.options.get('domain'), self._convert_records_back(all_records))
        status = len(self.list_records(type, name, content, show_output=False)) >= 1
        logger.debug('update_record: %s', status)
        return status

    # Delete an existing record.
    # If record does not exist, do nothing.
    # If an identifier is specified, use it, otherwise do a lookup using type, name and content.
    def delete_record(self, identifier=None, type=None, name=None, content=None):
        if not (type or name or content):
            raise Exception("At least one of type, name or content must be specified.")

        all_records = self.list_records(show_output=False)
        filtered_records = self._filter_records(all_records, type, name, content)

        for record in filtered_records:
            all_records.remove(record)

        self.client.set_dns_entries(self.options.get('domain'), self._convert_records_back(all_records))
        status = len(self.list_records(type, name, content, show_output=False)) == 0
        logger.debug('delete_record: %s', status)
        return status

    def _full_name(self, record_name):
        if record_name == "@":
            record_name = self.options['domain']
        return super(Provider, self)._full_name(record_name)

    def _relative_name(self, record_name):
        name = super(Provider, self)._relative_name(record_name)
        if not name:
            name = "@"
        return name

    def _bind_format_target(self, type, target):
        if type == "CNAME" and not target.endswith("."):
            target += "."
        return target

    # Convert the objects from transip to dicts, for easier processing
    def _convert_records(self, records):
        _records = []
        for record in records:
            _records.append({
                "id": "{0}-{1}".format(self._full_name(record.name), record.type),
                "name": self._full_name(record.name),
                "type": record.type,
                "content": record.content,
                "ttl": record.expire
            })
        return _records

    def _to_dns_entry(self, _entry):
        return DnsEntry(self._relative_name(_entry['name']), _entry['ttl'], _entry['type'], _entry['content'])

    def _convert_records_back(self, _records):
        return [self._to_dns_entry(record) for record in _records]

    # Filter a list of records based on criteria
    def _filter_records(self, records, type=None, name=None, content=None):
        _records = []
        for record in records:
            if (not type or record['type'] == type) and \
               (not name or self._full_name(record['name']) == self._full_name(name)) and \
               (not content or record['content'] == content):
                _records.append(record)
        return _records