def __init__(self): self.cli = CLI()
class Generate: nginxPath = "/etc/nginx/sites-enabled/" nginxCerts = "/opt/woodCDN/certs/" reload = False def __init__(self): self.cli = CLI() self.cert = Cert() self.templator = Templator() def run(self): while True: self.certs() self.nginx() time.sleep(60) def certs(self): print("Updating certs") data = self.cli.query(['SELECT * FROM certs']) files, current = os.listdir(self.nginxCerts), [] if 'values' in data['results'][0]: for entry in data['results'][0]['values']: if entry[2] == "@": domain = entry[1] if entry[2] != "@": domain = entry[2] + "." + entry[1] current.append(domain + "-fullchain.pem") current.append(domain + "-privkey.pem") if domain + "-fullchain.pem" not in files or entry[ 5] > os.path.getmtime(self.nginxCerts + domain + "-fullchain.pem"): print("Writing", domain + "-fullchain.pem") with open(self.nginxCerts + domain + "-fullchain.pem", 'w') as out: out.write(entry[3]) self.reload = True else: print(domain + "-fullchain.pem", "skipping") if domain + "-privkey.pem" not in files or entry[ 5] > os.path.getmtime(self.nginxCerts + domain + "-privkey.pem"): print("Writing", domain + "-privkey.pem") with open(self.nginxCerts + domain + "-privkey.pem", 'w') as out: out.write(entry[4]) self.reload = True else: print(domain + "-privkey.pem", "skipping") self.cert.syncCerts(current, files, self.nginxCerts) def nginx(self): print("Updating nginx") data = self.cli.query(['SELECT * FROM vhosts WHERE type = "proxy"']) files, current = os.listdir(self.nginxPath), [] if 'values' in data['results'][0]: for entry in data['results'][0]['values']: if entry[2] == "@": domain = entry[1] if entry[2] != "@": domain = entry[2] + "." + entry[1] current.append("cdn-" + domain) #If the vhost does not exists or the database timestamp is newer than the file timestamp if "cdn-" + domain not in files or entry[5] > os.path.getmtime( self.nginxPath + "cdn-" + domain): print("Writing HTTP config for", domain) http = self.templator.nginxHTTP(domain, entry[4]) vhost = self.templator.nginxWrap(domain, http) with open(self.nginxPath + "cdn-" + domain, 'w') as out: out.write(vhost) self.reload = True #If the vhost exist lets do some modifications if os.path.isfile(self.nginxPath + "cdn-" + domain): with open(self.nginxPath + "cdn-" + domain, 'r') as f: file = f.read() if "443" not in file and os.path.isfile( self.nginxCerts + domain + "-fullchain.pem") and os.path.isfile( self.nginxCerts + domain + "-privkey.pem"): print("Writing HTTPS config for", domain) http = self.templator.nginxHTTP(domain, entry[4]) https = self.templator.nginxHTTPS(domain, entry[4]) vhost = self.templator.nginxWrap(domain, http + https) with open(self.nginxPath + "cdn-" + domain, 'w') as out: out.write(vhost) self.reload = True elif "443" not in file: print("Cert missing for", domain, "skipping") else: print("cdn-" + domain, "skipping") #vhosts removed from database for file in files: if file not in current and "cdn-" in file: os.remove(path + file) self.reload = True if self.reload: #Gracefull reloading, won't impact incomming or ongoing connections print("Reloading nginx") subprocess.run( ["/usr/bin/sudo", "/usr/sbin/service", "nginx", "reload"])
class Cert(rqlite): def __init__(self): self.cli = CLI() def addCert(self, data): print("adding", data[0]) response = self.execute([ 'INSERT INTO certs(domain,subdomain,fullchain,privkey,updated) VALUES(?, ?, ?, ?, ?)', data[0], data[1], data[2], data[3], data[4] ]) print(json.dumps(response, indent=4, sort_keys=True)) def updateCert(self, data): print("updating", data[0]) response = self.execute([ 'UPDATE certs SET fullchain = ?,privkey = ?,updated = ? WHERE domain = ? AND subdomain =?', data[2], data[3], data[4], data[0], data[1] ]) print(json.dumps(response, indent=4, sort_keys=True)) def deleteCert(self, data): response = self.execute([ 'DELETE FROM certs WHERE domain=? and subdomain=?', data[0], data[1] ]) print(json.dumps(response, indent=4, sort_keys=True)) def getCert(self, fullDomain, domain, subdomain, email, update=False): directory = "https://acme-v02.api.letsencrypt.org/directory" #directory = "https://acme-staging-v02.api.letsencrypt.org/directory" try: client = simple_acme_dns.ACMEClient( domains=[fullDomain], email=email, directory=directory, nameservers=["8.8.8.8", "1.1.1.1"], new_account=True, generate_csr=True) except Exception as e: print(e) return False for acmeDomain, token in client.request_verification_tokens(): print("adding {domain} --> {token}".format(domain=acmeDomain, token=token)) response = self.cli.addVHost( [domain, "_acme-challenge." + subdomain, 'TXT', token]) if response is False: return False print("Waiting for dns propagation") try: if client.check_dns_propagation(timeout=1200): print("Requesting certificate") client.request_certificate() fullchain = client.certificate.decode() privkey = client.private_key.decode() if update is False: self.addCert([ domain, subdomain, fullchain, privkey, int(time.time()) ]) else: self.updateCert([ domain, subdomain, fullchain, privkey, int(time.time()) ]) else: print("Failed to issue certificate for " + str(client.domains)) client.deactivate_account() return False except Exception as e: print(e) return False finally: self.cli.deleteVhost( [domain, "_acme-challenge." + subdomain, 'TXT']) return True def syncCerts(self, current, files, path): #certs removed from database for file in files: if file not in current: os.remove(path + file)
def __init__(self): self.cli = CLI() self.cert = Cert() self.templator = Templator()
#!/usr/bin/python3 import sys, time, socket sys.path.append("..") # Adds higher directory to python modules path. from Class.cli import CLI cli = CLI() hostname = socket.gethostname() if "." in hostname: sub = hostname.split(".", 1)[0] else: sub = hostname while True: status = cli.execute( ["UPDATE pops SET lastrun = ? WHERE name = ?", int(time.time()), sub]) time.sleep(30)
#!/usr/bin/python3 import time, sys sys.path.append("..") # Adds higher directory to python modules path. from Class.cli import CLI from Class.cert import Cert cli = CLI() cert = Cert() status = cli.status() if status is False: print("rqlite gone") state = status['store']['raft']['state'] if state == "Leader": print("Getting doamins") domains = cli.query([ 'SELECT * FROM vhosts as v JOIN domains as d ON v.domain=d.domain LEFT JOIN certs as c ON v.domain=c.domain AND v.subdomain=c.subdomain WHERE v.type = "proxy"' ]) if domains is False: print("rqlite gone") sys.exit() if 'values' not in domains['results'][0]: print("no vhosts added") sys.exit() for row in domains['results'][0]['values']: target = row[1] if row[2] is not "@": target = row[2] + "." + row[1] if row[9] == None:
#!/usr/bin/python3 from Class.cli import CLI from Class.cert import Cert import sys cli = CLI() cert = Cert() if len(sys.argv) == 1: print("init, domain, vhost, pop, cert") elif sys.argv[1] == "init": cli.init() elif sys.argv[1] == "domain": if len(sys.argv) == 2: print( "domain add <name> <email> <ns1>,<ns2>\ndomain list\ndomain del <name>" ) elif sys.argv[2] == "add": cli.addDomain(sys.argv[3:]) elif sys.argv[2] == "list": cli.getTable("domains") elif sys.argv[2] == "del": cli.deleteDomain(sys.argv[3:]) elif sys.argv[1] == "vhost": if len(sys.argv) == 2: print( "vhost add <domain> <subdomain> <type> <value>\nvhost list\nvhost del <domain> <subdomain> <type>" ) elif sys.argv[2] == "add": cli.addVHost(sys.argv[3:]) elif sys.argv[2] == "list":
#!/usr/bin/python3 -u from sys import stdin, stderr, exit from Class.cli import CLI from Class.data import Data import geoip2.database, time reader = geoip2.database.Reader("/opt/woodCDN/GeoLite2-City.mmdb") cli = CLI() data = Data() nameservers, lastupdate, vhosts, pops = {}, time.time(), {}, {} def updateData(): fallback = False data = cli.query([ "SELECT * FROM domains", 'SELECT * FROM vhosts WHERE type != "proxy"', "SELECT * FROM pops" ]) if (data is False or "values" not in data['results'][0] or "values" not in data['results'][1] or "values" not in data['results'][2]): stderr.write("domains/vhosts/pops table missing or empty\n") return False pops = [ x for x in data['results'][2]['values'] if x[4] + 60 > int(time.time()) ] if len(pops) == 0: