diff --git a/contrib/archive/backends/impl_ipa/__init__.py b/contrib/archive/backends/impl_ipa/__init__.py deleted file mode 100644 index 0c29a1462..000000000 --- a/contrib/archive/backends/impl_ipa/__init__.py +++ /dev/null @@ -1,437 +0,0 @@ -# Copyright 2014 Red Hat, Inc. -# -# Author: Rich Megginson -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. -import pprint -import time - -from oslo_config import cfg -from oslo_log import log as logging -from oslo_serialization import jsonutils as json -from oslo_utils import importutils -import requests - -from designate.backend import base -from designate import exceptions -from designate.i18n import _LE - - -LOG = logging.getLogger(__name__) - -IPA_DEFAULT_PORT = 443 - - -class IPABaseError(exceptions.Backend): - error_code = 500 - error_type = 'unknown_ipa_error' - - -class IPAAuthError(IPABaseError): - error_type = 'authentication_error' - - -# map of designate domain parameters to the corresponding -# ipa parameter -# NOTE: ipa manages serial, and does not honor -# increment_serial=False - this means the designate serial -# and the ipa serial will diverge if updates are made -# using increment_serial=False -domain2ipa = {'ttl': 'dnsttl', 'email': 'idnssoarname', - 'serial': 'idnssoaserial', 'expire': 'idnssoaexpire', - 'minimum': 'idnssoaminimum', 'refresh': 'idnssoarefresh', - 'retry': 'idnssoaretry'} - -# map of designate record types to ipa -rectype2iparectype = {'A': ('arecord', '%(data)s'), - 'AAAA': ('aaaarecord', '%(data)s'), - 'MX': ('mxrecord', '%(data)s'), - 'CNAME': ('cnamerecord', '%(data)s'), - 'TXT': ('txtrecord', '%(data)s'), - 'SRV': ('srvrecord', '%(data)s'), - 'NS': ('nsrecord', '%(data)s'), - 'PTR': ('ptrrecord', '%(data)s'), - 'SPF': ('spfrecord', '%(data)s'), - 'SSHFP': ('sshfprecord', '%(data)s'), - 'NAPTR': ('naptrrecord', '%(data)s'), - 'CAA': ('caarecord', '%(data)s'), - 'CERT': ('certrecord', '%(data)s'), - } - - -IPA_INVALID_DATA = 3009 -IPA_NOT_FOUND = 4001 -IPA_DUPLICATE = 4002 -IPA_NO_CHANGES = 4202 - - -class IPAUnknownError(IPABaseError): - pass - - -class IPACommunicationFailure(IPABaseError): - error_type = 'communication_failure' - pass - - -class IPAInvalidData(IPABaseError): - error_type = 'invalid_data' - pass - - -class IPADomainNotFound(IPABaseError): - error_type = 'domain_not_found' - pass - - -class IPARecordNotFound(IPABaseError): - error_type = 'record_not_found' - pass - - -class IPADuplicateDomain(IPABaseError): - error_type = 'duplicate_domain' - pass - - -class IPADuplicateRecord(IPABaseError): - error_type = 'duplicate_record' - pass - - -ipaerror2exception = { - IPA_INVALID_DATA: { - 'dnszone': IPAInvalidData, - 'dnsrecord': IPAInvalidData - }, - IPA_NOT_FOUND: { - 'dnszone': IPADomainNotFound, - 'dnsrecord': IPARecordNotFound - }, - IPA_DUPLICATE: { - 'dnszone': IPADuplicateDomain, - 'dnsrecord': IPADuplicateRecord - }, - # NOTE: Designate will send updates with all fields - # even if they have not changed value. If none of - # the given values has changed, IPA will return - # this error code - this can be ignored - IPA_NO_CHANGES: { - 'dnszone': None, - 'dnsrecord': None - } -} - - -def abs2rel_name(domain, rsetname): - """convert rsetname from absolute form foo.bar.tld. to the name - relative to the domain. For IPA, if domain is rsetname, then use - "@" as the relative name. If rsetname does not end with a subset - of the domain, the just return the raw rsetname - """ - if rsetname.endswith(domain): - idx = rsetname.rfind(domain) - if idx == 0: - rsetname = "@" - elif idx > 0: - rsetname = rsetname[:idx].rstrip(".") - return rsetname - - -class IPABackend(base.Backend): - __plugin_name__ = 'ipa' - - @classmethod - def get_cfg_opts(cls): - group = cfg.OptGroup( - name='backend:ipa', title="Configuration for IPA Backend" - ) - - opts = [ - cfg.StrOpt('ipa-host', default='localhost.localdomain', - help='IPA RPC listener host - must be FQDN'), - cfg.IntOpt('ipa-port', default=IPA_DEFAULT_PORT, - help='IPA RPC listener port'), - cfg.StrOpt('ipa-client-keytab', - help='Kerberos client keytab file'), - cfg.StrOpt('ipa-auth-driver-class', - default='designate.backend.impl_ipa.auth.IPAAuth', - help='Class that implements the authentication ' - 'driver for IPA'), - cfg.StrOpt('ipa-ca-cert', - help='CA certificate for use with https to IPA'), - cfg.StrOpt('ipa-base-url', default='/ipa', - help='Base URL for IPA RPC, relative to host[:port]'), - cfg.StrOpt('ipa-json-url', - default='/json', - help='URL for IPA JSON RPC, relative to IPA base URL'), - cfg.IntOpt('ipa-connect-retries', default=1, - help='How many times Designate will attempt to retry ' - 'the connection to IPA before giving up'), - cfg.BoolOpt('ipa-force-ns-use', default=False, - help='IPA requires that a specified ' - 'name server or SOA MNAME is resolvable - if this ' - 'option is set, Designate will force IPA to use a ' - 'given name server even if it is not resolvable'), - cfg.StrOpt('ipa-version', default='2.65', - help='IPA RPC JSON version') - ] - - return [(group, opts)] - - def start(self): - LOG.debug('IPABackend start') - self.request = requests.Session() - authclassname = cfg.CONF[self.name].ipa_auth_driver_class - authclass = importutils.import_class(authclassname) - self.request.auth = ( - authclass(cfg.CONF[self.name].ipa_client_keytab, - cfg.CONF[self.name].ipa_host)) - ipa_base_url = cfg.CONF[self.name].ipa_base_url - if ipa_base_url.startswith("http"): # full URL - self.baseurl = ipa_base_url - else: # assume relative to https://host[:port] - self.baseurl = "https://" + cfg.CONF[self.name].ipa_host - ipa_port = cfg.CONF[self.name].ipa_port - if ipa_port != IPA_DEFAULT_PORT: - self.baseurl += ":" + str(ipa_port) - self.baseurl += ipa_base_url - ipa_json_url = cfg.CONF[self.name].ipa_json_url - if ipa_json_url.startswith("http"): # full URL - self.jsonurl = ipa_json_url - else: # assume relative to https://host[:port] - self.jsonurl = self.baseurl + ipa_json_url - xtra_hdrs = {'Content-Type': 'application/json', - 'Referer': self.baseurl} - self.request.headers.update(xtra_hdrs) - self.request.verify = cfg.CONF[self.name].ipa_ca_cert - self.ntries = cfg.CONF[self.name].ipa_connect_retries - self.force = cfg.CONF[self.name].ipa_force_ns_use - - def create_zone(self, context, zone): - LOG.debug('Create Zone %r' % zone) - ipareq = {'method': 'dnszone_add', 'id': 0} - params = [zone['name']] - servers = self.central_service.get_zone_ns_records(self.admin_context) - # just use the first one for zone creation - add the others - # later, below - use force because designate assumes the NS - # already exists somewhere, is resolvable, and already has - # an A/AAAA record - args = {'idnssoamname': servers[0]['name']} - if self.force: - args['force'] = True - for dkey, ipakey in list(domain2ipa.items()): - if dkey in zone: - args[ipakey] = zone[dkey] - ipareq['params'] = [params, args] - self._call_and_handle_error(ipareq) - # add NS records for all of the other servers - if len(servers) > 1: - ipareq = {'method': 'dnsrecord_add', 'id': 0} - params = [zone['name'], "@"] - args = {'nsrecord': servers[1:]} - if self.force: - args['force'] = True - ipareq['params'] = [params, args] - self._call_and_handle_error(ipareq) - - def update_zone(self, context, zone): - LOG.debug('Update Zone %r' % zone) - ipareq = {'method': 'dnszone_mod', 'id': 0} - params = [zone['name']] - args = {} - for dkey, ipakey in list(domain2ipa.items()): - if dkey in zone: - args[ipakey] = zone[dkey] - ipareq['params'] = [params, args] - self._call_and_handle_error(ipareq) - - def delete_zone(self, context, zone): - LOG.debug('Delete Zone %r' % zone) - ipareq = {'method': 'dnszone_del', 'id': 0} - params = [zone['name']] - args = {} - ipareq['params'] = [params, args] - self._call_and_handle_error(ipareq) - - def create_recordset(self, context, domain, recordset): - LOG.debug('Discarding create_recordset call, not-applicable') - - def update_recordset(self, context, domain, recordset): - LOG.debug('Update RecordSet %r / %r' % (domain, recordset)) - # designate allows to update a recordset if there are no - # records in it - we should ignore this case - if not self._recset_has_records(context, recordset): - LOG.debug('No records in %r / %r - skipping' % (domain, recordset)) - return - # The only thing IPA allows is to change the ttl, since that is - # stored "per recordset" - if 'ttl' not in recordset: - return - ipareq = {'method': 'dnsrecord_mod', 'id': 0} - dname = domain['name'] - rsetname = abs2rel_name(dname, recordset['name']) - params = [domain['name'], rsetname] - args = {'dnsttl': recordset['ttl']} - ipareq['params'] = [params, args] - self._call_and_handle_error(ipareq) - - def delete_recordset(self, context, domain, recordset): - LOG.debug('Delete RecordSet %r / %r' % (domain, recordset)) - # designate allows to delete a recordset if there are no - # records in it - we should ignore this case - if not self._recset_has_records(context, recordset): - LOG.debug('No records in %r / %r - skipping' % (domain, recordset)) - return - ipareq = {'method': 'dnsrecord_mod', 'id': 0} - dname = domain['name'] - rsetname = abs2rel_name(dname, recordset['name']) - params = [domain['name'], rsetname] - rsettype = rectype2iparectype[recordset['type']][0] - args = {rsettype: None} - ipareq['params'] = [params, args] - self._call_and_handle_error(ipareq) - - def create_record(self, context, domain, recordset, record): - LOG.debug('Create Record %r / %r / %r' % (domain, recordset, record)) - ipareq = {'method': 'dnsrecord_add', 'id': 0} - params, args = self._rec_to_ipa_rec(domain, recordset, [record]) - ipareq['params'] = [params, args] - self._call_and_handle_error(ipareq) - - def update_record(self, context, domain, recordset, record): - LOG.debug('Update Record %r / %r / %r' % (domain, recordset, record)) - # for modify operations - IPA does not support a way to change - # a particular field in a given record - e.g. for an MX record - # with several values, IPA stores them like this: - # name: "server1.local." - # data: ["10 mx1.server1.local.", "20 mx2.server1.local."] - # we could do a search of IPA, compare the values in the - # returned array - but that adds an additional round trip - # and is error prone - # instead, we just get all of the current values and send - # them in one big modify - criteria = {'recordset_id': record['recordset_id']} - reclist = self.central_service.find_records(self.admin_context, - criteria) - ipareq = {'method': 'dnsrecord_mod', 'id': 0} - params, args = self._rec_to_ipa_rec(domain, recordset, reclist) - ipareq['params'] = [params, args] - self._call_and_handle_error(ipareq) - - def delete_record(self, context, domain, recordset, record): - LOG.debug('Delete Record %r / %r / %r' % (domain, recordset, record)) - ipareq = {'method': 'dnsrecord_del', 'id': 0} - params, args = self._rec_to_ipa_rec(domain, recordset, [record]) - args['del_all'] = 0 - ipareq['params'] = [params, args] - self._call_and_handle_error(ipareq) - - def ping(self, context): - LOG.debug('Ping') - # NOTE: This call will cause ipa to issue an error, but - # 1) it should not throw an exception - # 2) the response will indicate ipa is running - # 3) the bandwidth usage is minimal - ipareq = {'method': 'dnszone_show', 'id': 0} - params = ['@'] - args = {} - ipareq['params'] = [params, args] - retval = {'result': True} - try: - self._call_and_handle_error(ipareq) - except Exception as e: - retval = {'result': False, 'reason': str(e)} - return retval - - def _rec_to_ipa_rec(self, domain, recordset, reclist): - dname = domain['name'] - rsetname = abs2rel_name(dname, recordset['name']) - params = [dname, rsetname] - rectype = recordset['type'] - vals = [] - for record in reclist: - vals.append(rectype2iparectype[rectype][1] % record) - args = {rectype2iparectype[rectype][0]: vals} - ttl = recordset.get('ttl') or domain.get('ttl') - if ttl is not None: - args['dnsttl'] = ttl - return params, args - - def _ipa_error_to_exception(self, resp, ipareq): - exc = None - if resp['error'] is None: - return exc - errcode = resp['error']['code'] - method = ipareq['method'] - methtype = method.split('_')[0] - exclass = ipaerror2exception.get(errcode, {}).get(methtype, - IPAUnknownError) - if exclass: - LOG.debug("Error: ipa command [%s] returned error [%s]" % - (pprint.pformat(ipareq), pprint.pformat(resp))) - elif errcode: # not mapped - LOG.debug("Ignoring IPA error code %d: %s" % - (errcode, pprint.pformat(resp))) - return exclass - - def _call_and_handle_error(self, ipareq): - if 'version' not in ipareq['params'][1]: - ipareq['params'][1]['version'] = cfg.CONF[self.name].ipa_version - need_reauth = False - while True: - status_code = 200 - try: - if need_reauth: - self.request.auth.refresh_auth() - rawresp = self.request.post(self.jsonurl, - data=json.dumps(ipareq)) - status_code = rawresp.status_code - except IPAAuthError: - status_code = 401 - if status_code == 401: - if self.ntries == 0: - # persistent inability to auth - LOG.error(_LE("Error: could not authenticate to IPA - " - "please check for correct keytab file")) - # reset for next time - self.ntries = cfg.CONF[self.name].ipa_connect_retries - raise IPACommunicationFailure() - else: - LOG.debug("Refresh authentication") - need_reauth = True - self.ntries -= 1 - time.sleep(1) - else: - # successful - reset - self.ntries = cfg.CONF[self.name].ipa_connect_retries - break - try: - resp = json.loads(rawresp.text) - except ValueError: - # response was not json - some sort of error response - LOG.debug("Error: unknown error from IPA [%s]" % rawresp.text) - raise IPAUnknownError("unable to process response from IPA") - # raise the appropriate exception, if error - exclass = self._ipa_error_to_exception(resp, ipareq) - if exclass: - # could add additional info/message to exception here - raise exclass() - return resp - - def _recset_has_records(self, context, recordset): - """Return True if the recordset has records, False otherwise""" - criteria = {'recordset_id': recordset['id']} - num = self.central_service.count_records(self.admin_context, - criteria) - return num > 0 diff --git a/contrib/archive/backends/impl_ipa/auth.py b/contrib/archive/backends/impl_ipa/auth.py deleted file mode 100644 index 45ba6314d..000000000 --- a/contrib/archive/backends/impl_ipa/auth.py +++ /dev/null @@ -1,62 +0,0 @@ -# Copyright 2014 Red Hat, Inc. -# -# Author: Rich Megginson -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. -import logging -import os - -import kerberos -from requests import auth - -from designate.backend.impl_ipa import IPAAuthError -from designate.i18n import _LE -from designate.i18n import _LW -from designate.utils import generate_uuid - - -LOG = logging.getLogger(__name__) - - -class IPAAuth(auth.AuthBase): - def __init__(self, keytab, hostname): - # store the kerberos credentials in memory rather than on disk - os.environ['KRB5CCNAME'] = "MEMORY:" + generate_uuid() - self.token = None - self.keytab = keytab - self.hostname = hostname - if self.keytab: - os.environ['KRB5_CLIENT_KTNAME'] = self.keytab - else: - LOG.warning(_LW('No IPA client kerberos keytab file given')) - - def __call__(self, request): - if not self.token: - self.refresh_auth() - request.headers['Authorization'] = 'negotiate ' + self.token - return request - - def refresh_auth(self): - service = "HTTP@" + self.hostname - flags = kerberos.GSS_C_MUTUAL_FLAG | kerberos.GSS_C_SEQUENCE_FLAG - try: - (_, vc) = kerberos.authGSSClientInit(service, flags) - except kerberos.GSSError as e: - LOG.error(_LE("caught kerberos exception %r") % e) - raise IPAAuthError(str(e)) - try: - kerberos.authGSSClientStep(vc, "") - except kerberos.GSSError as e: - LOG.error(_LE("caught kerberos exception %r") % e) - raise IPAAuthError(str(e)) - self.token = kerberos.authGSSClientResponse(vc) diff --git a/contrib/archive/backends/impl_multi.py b/contrib/archive/backends/impl_multi.py deleted file mode 100644 index d977d1480..000000000 --- a/contrib/archive/backends/impl_multi.py +++ /dev/null @@ -1,153 +0,0 @@ -# Copyright (C) 2013 eNovance SAS -# -# Author: Artom Lifshitz -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -import logging - -from oslo_config import cfg -from oslo_utils import excutils - -from designate import backend -from designate.backend import base - - -LOG = logging.getLogger(__name__) -CFG_GROUP = 'backend:multi' - - -class MultiBackend(base.Backend): - """ - Multi-backend backend - - This backend dispatches calls to a master backend and a slave backend. - It enforces master/slave ordering semantics as follows: - - Creates for tsigkeys, servers and domains are done on the master first, - then on the slave. - - Updates for tsigkeys, servers and domains and all operations on records - are done on the master only. It's assumed masters and slaves use an - external mechanism to sync existing domains, most likely XFR. - - Deletes are done on the slave first, then on the master. - - If the create on the slave fails, the domain/tsigkey/server is deleted from - the master. If delete on the master fails, the domain/tdigkey/server is - recreated on the slave. - """ - __plugin_name__ = 'multi' - - @classmethod - def get_cfg_opts(cls): - group = cfg.OptGroup( - name=CFG_GROUP, title="Configuration for multi-backend Backend" - ) - - opts = [ - cfg.StrOpt('master', default='fake', help='Master backend'), - cfg.StrOpt('slave', default='fake', help='Slave backend'), - ] - - return [(group, opts)] - - def __init__(self, central_service): - super(MultiBackend, self).__init__(central_service) - self.central = central_service - self.master = backend.get_backend(cfg.CONF[CFG_GROUP].master, - central_service) - self.slave = backend.get_backend(cfg.CONF[CFG_GROUP].slave, - central_service) - - def start(self): - self.master.start() - self.slave.start() - - def stop(self): - self.slave.stop() - self.master.stop() - - def create_tsigkey(self, context, tsigkey): - self.master.create_tsigkey(context, tsigkey) - try: - self.slave.create_tsigkey(context, tsigkey) - except Exception: - with excutils.save_and_reraise_exception(): - self.master.delete_tsigkey(context, tsigkey) - - def update_tsigkey(self, context, tsigkey): - self.master.update_tsigkey(context, tsigkey) - - def delete_tsigkey(self, context, tsigkey): - self.slave.delete_tsigkey(context, tsigkey) - try: - self.master.delete_tsigkey(context, tsigkey) - except Exception: - with excutils.save_and_reraise_exception(): - self.slave.create_tsigkey(context, tsigkey) - - def create_zone(self, context, zone): - self.master.create_zone(context, zone) - try: - self.slave.create_zone(context, zone) - except Exception: - with excutils.save_and_reraise_exception(): - self.master.delete_zone(context, zone) - - def update_zone(self, context, zone): - self.master.update_zone(context, zone) - - def delete_zone(self, context, zone): - # Fetch the full zone from Central first, as we may - # have to recreate it on slave if delete on master fails - deleted_context = context.deepcopy() - deleted_context.show_deleted = True - - full_domain = self.central.find_zone( - deleted_context, {'id': zone['id']}) - - self.slave.delete_zone(context, zone) - try: - self.master.delete_zone(context, zone) - except Exception: - with excutils.save_and_reraise_exception(): - self.slave.create_zone(context, zone) - - [self.slave.create_record(context, zone, record) - for record in self.central.find_records( - context, {'domain_id': full_domain['id']})] - - def create_recordset(self, context, zone, recordset): - self.master.create_recordset(context, zone, recordset) - - def update_recordset(self, context, zone, recordset): - self.master.update_recordset(context, zone, recordset) - - def delete_recordset(self, context, zone, recordset): - self.master.delete_recordset(context, zone, recordset) - - def create_record(self, context, zone, recordset, record): - self.master.create_record(context, zone, recordset, record) - - def update_record(self, context, zone, recordset, record): - self.master.update_record(context, zone, recordset, record) - - def delete_record(self, context, zone, recordset, record): - self.master.delete_record(context, zone, recordset, record) - - def ping(self, context): - return { - 'master': self.master.ping(context), - 'slave': self.slave.ping(context) - } diff --git a/contrib/consume.py b/contrib/consume.py deleted file mode 100644 index c3f141d2c..000000000 --- a/contrib/consume.py +++ /dev/null @@ -1,39 +0,0 @@ -# Copyright 2016 Rackspace, Inc. -# -# Author: Tim Simmons -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. -""" -This dumb script allows you to see what's being dumped onto -the notifications.info queue - -nabbed from: -https://pika.readthedocs.io/en/latest/examples/blocking_consume.html -""" -import pika - - -def on_message(channel, method_frame, header_frame, body): - print(method_frame.delivery_tag) - print(body) - channel.basic_ack(delivery_tag=method_frame.delivery_tag) - - -connection = pika.BlockingConnection() -channel = connection.channel() -channel.basic_consume(on_message, 'notifications.info') -try: - channel.start_consuming() -except KeyboardInterrupt: - channel.stop_consuming() -connection.close() diff --git a/ipa-requirements.txt b/ipa-requirements.txt deleted file mode 100644 index f6e655c2c..000000000 --- a/ipa-requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -requests -kerberos