def processConfig(configReader, module, postOp=None, preOp=readHandlerDefault, defaultConfig={}): configOrig = configReader.getRawConfigOf(str(module)) # apply general config defaults and the default section config = applyDefault( configOrig, defaultConfig ) # must be here because following section depends on default values log.debug(config) for secName, content in config.items(): if preOp is not None: preOp({ 'content': dict(content), 'module': str(module), 'config': config, 'secname': str(secName) }) config[secName].update(configOrig[secName]) if postOp is not None: postOp({ 'content': dict(content), 'module': str(module), 'config': config, 'secname': str(secName) }) log.debug(config) configReader.updateConfig({str(module): config}) return config
def prepare(config, state): subState = state.getSubstate('cert') for certSecName, certConfig in config['cert'].items(): if 'DEFAULT' == certSecName: continue if 'handler' not in certConfig: continue certState = subState.getSubstate(certSecName) if certState.isDone(): continue domains = [ k for k, v in config['domain'].items() if 'cert' in v and certSecName in v['cert'] ] log.info('Create certificate for section \"{}\"'.format(certSecName)) log.info(' -> {}'.format(', '.join(domains))) log.debug(certConfig) domainAccessTable = createDomainAccessTable(config, domains) handlerNames = certConfig['handler'].split('/') handler = __import__('cryptdomainmgr.modules.cert.handler' + str(handlerNames[0]), fromlist=('cryptdomainmgr', 'modules', 'cert')) statedir = getStateDir(config, 'cert', certSecName) handler.prepare(certConfig, certState, statedir, domains, domainAccessTable)
def readHandlerDefault(args): if not 'keybasename' in args['content']: args['content']['keybasename'] = str(args['secname']) if 'handler' in args['content']: log.debug('handler in content') handlerNames = args['content']['handler'].split('/') handler = __import__('cryptdomainmgr.modules.{}.handler{}'.format(str(args['module']), handlerNames[0]), fromlist=('cryptdomainmgr', 'modules', str(args['module']))) args['config'][args['secname']].update(handler.defaultConfig)
def parseCAA(caaRR): caaStr = caaRR['content'] log.debug(caaStr) caa = {} caa['flag'], caa['tag'], caa['url'] = caaStr.split(' ') caa['url'] = caa['url'][1:-1] caa = {str(k): str(v) for k, v in caa.items()} log.debug(caa) return caa
def interpreteConfig(cr, sh): domainconfigOrig = cr.getRawConfigOf('domain', True) domainconfig = applyDefault(domainconfigOrig) # must be here because following section depends on default values for domain, content in domainconfig.items(): for f in ['A', 'AAAA', 'DKIM', 'TLSA', 'MX', 'SRV', 'CAA', 'DMARC', 'SOA', 'SPF', 'Handler']: domainconfig[domain].update((globals()['interprete{}'.format(f)](content))) log.debug(domainconfig) cr.updateConfig({'domain': domainconfig}) return domainconfig
def encDNSemail(x): xSpl = x.split('@') log.debug(xSpl) if 1 == len(xSpl): return x elif 1 < len(xSpl): return xSpl[0].replace('.', '\\.') + '.' + xSpl[1] + '.' else: raise (TypeError('No valid email address'))
def getIPv6(aaaa='auto'): if 'auto' != aaaa: return aaaa try: ipv6Str = curlGet('ipv6.icanhazip.com') log.debug(ipv6Str) except Exception as e: return None return sanIPv6(ipv6Str)
def qry(self, filterDict): if type(filterDict) is list: self.__rv = [self.qry(e) for e in filterDict] return self.__rv log.debug(filterDict) createKeyDomainIfNotExists(filterDict) self.__rv = self.handler.info(filterDict) log.debug(self.__rv) return self.__rv
def makeIP6(aaaa): if aaaa is None: aaaa = 'auto' if type(aaaa) is not list: aaaa = [aaaa] log.debug(aaaa) aaaa = [getIPv6(e) for e in aaaa] aaaa = [e for e in aaaa if e is not None] log.debug(aaaa) return aaaa
def interprete(self, sh): self.sections = getSections(self.cp) log.info('Interpreting config sections') for secName in self.sections: log.info(' - {}'.format(secName)) handler = __import__('cryptdomainmgr.modules.' + str(secName) + '.confighandler', fromlist=('cryptdomainmgr', 'modules')) handler.interpreteConfig(self, sh) log.debug(self.config)
def resolveAuto(config, serviceConfig, depends): if 'auto' in serviceConfig: log.debug("This will never be called!") if depends not in config: return [] dependKeys = config[depends] serviceConfig.extend(dependKeys) serviceConfig = [ e for e in serviceConfig if e != 'auto' if e != 'DEFAULT' ] return serviceConfig
def deleteRv(self, deleteRv, preserveRv=[]): deleteIds = set(flatten(extractIds(deleteRv))) preserveIds = set(flatten(extractIds(preserveRv))) deleteOnlyIds = deleteIds - preserveIds log.debug('deleteRecords {}'.format(deleteOnlyIds)) for e in deleteOnlyIds: rr = self.qry({'recordId': e}) infoRecord(rr[0], 'delete') self.__rv = [self.delById(e) for e in deleteOnlyIds] log.debug(self.__rv) return self.__rv
def cleanup(config, state): subState = state.getSubstate('service') for serviceSecName, serviceConfig in config['service'].items(): if 'DEFAULT' == serviceSecName: continue serviceState = subState.getSubstate(serviceSecName) if serviceState.isDone(): continue log.info('Cleanup service for section \"{}\"'.format(serviceSecName)) log.debug(serviceConfig) handler = __import__('cryptdomainmgr.modules.service.handler'+str(serviceSecName), fromlist=('cryptdomainmgr', 'modules','service')) handler.cleanup(serviceConfig, serviceState, state)
def addTLSAfromCert(self, name, certFilenames, tlsaTypes=[[3, 0, 1], [3, 0, 2], [3, 1, 1], [3, 1, 2], [2, 0, 1], [2, 0, 2], [2, 1, 1], [2, 1, 2]]): if 'auto' == str(tlsaTypes): tlsaTypes = [[3, 0, 1], [3, 0, 2], [3, 1, 1], [3, 1, 2], [2, 0, 1], [2, 0, 2], [2, 1, 1], [2, 1, 2]] log.debug('name = %s' % name) log.debug('certFilenames = %s' % certFilenames) self.addTLSA(name, tlsaRecordsFromCertFile(certFilenames, tlsaTypes))
def delete(self, deleteDict, preserveDict=[], wild=False): if wild is True: deleteRv = self.qryWild(deleteDict) preserveRv = self.qryWild(preserveDict) elif callable(wild): deleteRv = self.qryWild(deleteDict, wild) preserveRv = self.qryWild(preserveDict, wild) else: deleteRv = self.qry(deleteDict) preserveRv = self.qry(preserveDict) log.debug(deleteRv) return self.deleteRv(deleteRv, preserveRv)
def setDMARC(self, name, dmarcDict): log.debug(dmarcDict) if {} == dmarcDict: self.delDMARC(name) return dmarc = {'v': 'DMARC1', 'p': 'none'} dmarc.update(dmarcDict) dmarc = {k: v for k, v in dmarc.items() if '' != v} dmarcStr = formatDMARC(dmarc) self.update({ 'name': '_dmarc.' + str(name), 'type': 'TXT' }, {'content': dmarcStr})
def update(self, baseRecord, updateDict): matchRv = self.qry(baseRecord) matchIds = set(flatten(extractIds(matchRv))) if len(matchRv) > 0: baseRecord = matchRv[0] baseRecord.update(updateDict) if len(matchIds) > 0: log.debug('updateRecord {}'.format(baseRecord)) infoRecord(baseRecord, 'update') self.__rv = self.handler.update(baseRecord) log.debug(self.__rv) else: self.__rv = self.add(baseRecord) return self.__rv
def prepare(config, state): subState = state.getSubstate('domain') for domainSecName, domainConfig in config['domain'].items(): if 'DEFAULT' == domainSecName: continue if 'handler' not in domainConfig: continue domainState = subState.getSubstate(domainSecName) if domainState.isDone(): continue log.info('Create resource records for section \"{}\"'.format(domainSecName)) log.debug(domainConfig) handlerNames = domainConfig['handler'].split('/') handler = __import__('cryptdomainmgr.modules.domain.handler'+str(handlerNames[0]), fromlist=('cryptdomainmgr', 'modules','domain')) handler.prepare(domainConfig, domainState, domainSecName, state)
def rollover(config, state): subState = state.getSubstate('service') for serviceSecName, serviceConfig in config['service'].items(): if 'DEFAULT' == serviceSecName: continue if 'handler' not in serviceConfig: continue serviceState = subState.getSubstate(serviceSecName) if serviceState.isDone(): continue log.info('Rollover service for section \"{}\"'.format(serviceSecName)) log.debug(serviceConfig) handlerNames = serviceConfig['handler'].split('/') handler = __import__('cryptdomainmgr.modules.service.handler'+str(handlerNames[0]), fromlist=('cryptdomainmgr', 'modules','service')) handler.rollover(serviceConfig, serviceState, state)
def prepare(config, state): subState = state.getSubstate('dhparam') for dhparamSecName, dhparamConfig in config['dhparam'].items(): if 'DEFAULT' == dhparamSecName: continue if 'handler' not in dhparamConfig: continue dhparamState = subState.getSubstate(dhparamSecName) if dhparamState.isDone(): continue log.info('Create dhparams for section \"{}\"'.format(dhparamSecName)) log.debug(dhparamConfig) handlerNames = dhparamConfig['handler'].split('/') handler = __import__('cryptdomainmgr.modules.dhparam.handler'+str(handlerNames[0]), fromlist=('cryptdomainmgr', 'modules','dhparam')) statedir = getStateDir(config, 'dhparam', dhparamSecName) handler.prepare(dhparamConfig, dhparamState, statedir, dhparamSecName)
def interpreteConfig(cr, sh): domainconfigOrig = cr.getRawConfigOf('domain', True) domainconfig = applyDefault( domainconfigOrig ) # must be here because following section depends on default values for domain, content in domainconfig.items(): for f in [ 'A', 'AAAA', 'DKIM', 'TLSA', 'MX', 'SRV', 'CAA', 'DMARC', 'SOA', 'SPF', 'Handler', 'Cert' ]: domainconfig[domain].update( (globals()['interprete{}'.format(f)](content))) certSections = set(cr.getRawConfigOf('cert').keys()) certEntry = set({}) certDoesNotExist = set({}) if 'cert' in domainconfig[domain]: certEntry = set(domainconfig[domain]['cert']) if 'auto' in certEntry: certEntry = certEntry - 'auto' + certSections certDoesNotExist = certEntry - certSections for missing in certDoesNotExist: log.warn( "Section cert:{} referenced in domain:{} does not exist!". format(missing, domain)) domainconfig[domain]['cert'] = list( set(domainconfig[domain]['cert']) - certDoesNotExist) addDKIMcont = set( [e['content'] for e in domainconfig[domain]['dkimAggrAdd']]) dkimSections = set(cr.getRawConfigOf('dkim').keys()) dkimDoesNotExist = addDKIMcont - dkimSections for missing in dkimDoesNotExist: log.warn("Section dkim:{} referenced in domain:{} does not exist!". format(missing, domain)) for e in domainconfig[domain]['dkimAggrAdd']: if e['content'] not in dkimDoesNotExist: del e if 'requires' not in domainconfig[domain]: domainconfig[domain]['requires'] = {} domainconfig[domain]['requires'][ 'dkim'] = addDKIMcont - dkimDoesNotExist domainconfig[domain]['requires']['cert'] = certEntry - certDoesNotExist log.debug(domainconfig) cr.updateConfig({'domain': domainconfig}) return domainconfig
def login(self, domain): if 'user' in self.__loggedInCredentials and 'pass' in self.__loggedInCredentials: if self.getUser(domain) == self.__loggedInCredentials[ 'user'] and self.getPasswd( domain) == self.__loggedInCredentials['pass']: return self.connect() loggedInCredentials = { 'lang': 'en', 'user': self.getUser(domain), 'pass': self.getPasswd(domain) } log.debug(loggedInCredentials) self.__rv = self.__conn.login(loggedInCredentials['user'], loggedInCredentials['pass']) log.debug(self.__rv) if 1000 != self.__rv['code']: return self.__loggedInCredentials = dict(loggedInCredentials) self.__openedDomain = str(domain) self.__isLoggedIn = True
def parseTLSAentry(record): key = record['name'] keyList = key.split('.') log.debug(keyList) val = record['content'] valList = val.split(' ') tlsa = { 'name': '.'.join(keyList[2:]), 'port': keyList[0], 'proto': keyList[1], 'usage': valList[0], 'selector': valList[1], 'matchingtype': valList[2], 'tlsa': valList[3] } #tlsa = {'port': keyList[0], 'proto': keyList[1], 'usage': valList[0], 'selector': valList[1], 'matchingtype': valList[2], 'tlsa': valList[3]} if '_' == tlsa['port'][0]: tlsa['port'] = tlsa['port'][1:] if '_' == tlsa['proto'][0]: tlsa['proto'] = tlsa['proto'][1:] tlsa['tlsa'] = tlsa['tlsa'].replace('\n', '') return tlsa
def add(self, updateDict): if type(updateDict) is list: self.__rv = [self.add(e) for e in updateDict] return self.__rv if 'ttl' not in updateDict: updateDict['ttl'] = self.defaultTTL try: log.debug('createRecord {}'.format(updateDict)) if 'id' not in updateDict: self.__rv = self.handler.create(updateDict) infoRecord(updateDict, 'add (new)') log.debug(self.__rv) except Exception as e: if 1 < len(e.args): self.__rv = e.args[1] else: self.__rv = e.args if 2302 == self.__rv['code']: infoRecord(updateDict, 'add (exists)') log.debug(self.__rv) return self.__rv
def interpreteConfig(cr, sh): defaultServiceConfig = {'cert': 'auto', 'dkim': 'auto', 'dhparam': 'auto'} serviceConfig = cr.getRawConfigOf('service') log.debug(serviceConfig) # apply general config defaults and the default section serviceConfig = applyDefault( serviceConfig, defaultServiceConfig ) # must be here because following section depends on default values log.debug(serviceConfig) for serviceSecName, content in serviceConfig.items(): content = dict(content) for depends in ['cert', 'dkim', 'dhparam']: if depends in content: serviceConfig[serviceSecName][depends] = resolveAuto( cr, [ e for e in content[depends].replace(' ', '').split(',') if len(e) > 0 ], depends) log.debug(serviceConfig) cr.updateConfig({'service': serviceConfig}) return serviceConfig
def prepare(certConfig, certState, statedir, domainList, domainAccessTable): if 'dehydrated' != certConfig['handler'].split('/')[0]: return if 0 == len(domainList): return email = certConfig['email'] keysize = 4096 if 'keysize' in certConfig: keysize = certConfig['keysize'] if 'extraflags' in certConfig: extraFlags = certConfig['extraflags'] extraFlags = [e if '-' == e[0] else '--' + e for e in extraFlags] if '--staging' in extraFlags: ca = "https://acme-staging-v02.api.letsencrypt.org/directory" extraFlags.remove('--staging') if 'staging' in extraFlags: extraFlags.remove('staging') elif 'ca' in certConfig: ca = certConfig['ca'] else: ca = "https://acme-v02.api.letsencrypt.org/directory" makeDir(os.path.realpath(statedir)) confFilename = os.path.normpath(os.path.join(statedir, 'dehydrated.conf')) confFile = open(confFilename, 'w') confFile.write('CA={}\n'.format(str(ca))) confFile.write('CONTACT_EMAIL={}\n'.format(str(email))) confFile.write('KEYSIZE={}\n'.format(int(keysize))) confFile.write('CERTDIR={}\n'.format(os.path.join(statedir, 'certs'))) confFile.write('\n') confFile.close() here = os.path.dirname(os.path.realpath(__file__)) args = [ os.path.join(here, 'dehydrated/dehydrated'), '-f', confFilename, '--accept-terms', '-c', '-t', 'dns-01', '-k', os.path.join(here, 'hook.sh') ] log.debug(extraFlags) args.extend(extraFlags) for d in domainList: args.extend(['-d', str(d)]) log.debug(args) certState.setOpStateRunning() i = 2 while True: try: log.info('Starting DNS-01 authentication') rv = runCmd(' '.join(args), stderr=STDOUT, env=dict(os.environ, DOMAINACCESSTABLE=domainAccessTable, STATEDIR=statedir, WAITSEC="{}".format(3**i))) break except CalledProcessError as e: if 'ERROR: Lock file' in e.output: lockFilename = e.output.split('ERROR: Lock file \'')[-1].split( '\' present, aborting')[0] log.warn('Lock file from imcomplete run found: {}'.format( lockFilename)) log.warn(' -> Removing') os.remove(lockFilename) elif 'Incorrect TXT record' in e.output: log.info( ' -> Invalid DNS-01 challenge, maybe due to DNS caching interval. Trying to wait longer!' ) i += 1 log.info( ' -> Will wait {} s to give challenge time to propagate DNS cache.' .format(3**i)) elif 'NXDOMAIN' in e.output: log.info( ' -> Missing DNS-01 challenge, maybe due to DNS caching interval. Trying to wait longer!' ) i += 1 log.info( ' -> Will wait {} s to give challenge time to propagate DNS cache.' .format(3**i)) else: raise (e) if 9 == i: raise (e) res = [] rv = rv.splitlines() for s, e in enumerate(rv): if '---- DEPLOYMENTRESULT ----' == e[:len('---- DEPLOYMENTRESULT ----' )]: break for i, e in enumerate(rv[s + 1:]): if '---- END DEPLOYMENTRESULT ----' == e[:len( '---- END DEPLOYMENTRESULT ----')]: break res.append(e) resDict = {e.split('=')[0].lower(): e.split('=')[1] for e in res} resDict['san'] = list(domainList) if 'running' == certState.opstate: certState.registerResult(resDict) certState.setOpStateDone() return rv
def addSRV(self, name, srvDict): log.debug(srvDict) srvDictList = defaultDictList({'prio': 10, 'weight': 0}, srvDict) srvRRdictList = formatSRVentry(name, srvDictList) self.addDictList({}, srvRRdictList)
def extractIds(rv): if type(rv) is list: return [extractIds(e) for e in rv] log.debug(rv) return rv['id']
def prepare(certConfig, certState, statedir, domainList, domainAccessTable): if 'dehydrated' != certConfig['handler'].split('/')[0]: return if 0 == len(domainList): return email = certConfig['email'] keysize = 4096 if 'keysize' in certConfig: keysize = certConfig['keysize'] if 'extraflags' in certConfig: extraFlags = certConfig['extraflags'] extraFlags = [e if '-' == e[0] else '--' + e for e in extraFlags] if '--staging' in extraFlags: ca = "https://acme-staging.api.letsencrypt.org/directory" extraFlags.remove('--staging') if 'staging' in extraFlags: extraFlags.remove('staging') elif 'ca' in certConfig: ca = certConfig['ca'] else: ca = "https://acme-v02.api.letsencrypt.org/directory" makeDir(os.path.realpath(statedir)) confFilename = os.path.normpath(os.path.join(statedir, 'dehydrated.conf')) confFile = open(confFilename, 'w') confFile.write('CA={}\n'.format(str(ca))) confFile.write('CONTACT_EMAIL={}\n'.format(str(email))) confFile.write('KEYSIZE={}\n'.format(int(keysize))) confFile.write('CERTDIR={}\n'.format(os.path.join(statedir, 'certs'))) confFile.write('\n') confFile.close() here = os.path.dirname(os.path.realpath(__file__)) args = [ os.path.join(here, 'dehydrated/dehydrated'), '-f', confFilename, '--accept-terms', '-c', '-t', 'dns-01', '-k', os.path.join(here, 'hook.sh') ] log.debug(extraFlags) args.extend(extraFlags) for d in domainList: args.extend(['-d', str(d)]) log.debug(args) certState.setOpStateRunning() for i in range(10): try: rv = check_output(args, env=dict(os.environ, DOMAINACCESSTABLE=domainAccessTable)) log.info(rv) break except CalledProcessError as e: log.error(e.output) time.sleep(1) if 9 == i: raise (e) res = [] rv = rv.splitlines() for s, e in enumerate(rv): if '---- DEPLOYMENTRESULT ----' == e[:len('---- DEPLOYMENTRESULT ----' )]: break for i, e in enumerate(rv[s + 1:]): if '---- END DEPLOYMENTRESULT ----' == e[:len( '---- END DEPLOYMENTRESULT ----')]: break res.append(e) resDict = {e.split('=')[0].lower(): e.split('=')[1] for e in res} resDict['san'] = list(domainList) if 'running' == certState.opstate: certState.registerResult(resDict) certState.setOpStateDone() return rv