Skip to content

Instantly share code, notes, and snippets.

@jcubic
Last active June 25, 2021 10:30
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save jcubic/89f822213c837b48901dcb3128c75c3c to your computer and use it in GitHub Desktop.
Save jcubic/89f822213c837b48901dcb3128c75c3c to your computer and use it in GitHub Desktop.
Upload SSL Certificate to DirectAdmin controlled domains
#!/bin/bash
output=$(mktemp);
sudo certbot certonly --manual --expand --manual-public-ip-logging-ok \
--preferred-challenges http -n \
-d <LIST OF COMMA SEPARATED DOMAINS AND SUBDOMAINS>\
--manual-auth-hook ./cert.py --agree-tos --email <EMAIL ADRESS> 2>&1 | tee $output
grep "Certificate not yet due for renewal" $output > /dev/null || \
sudo ./cert.py -d <COMMA SEPARATED LIST OF DOMAINS>
#!/usr/bin/env python
# Script for lensencypt cert and challanges in DirectAdmin
#
# Copyright (c) 2018 Jakub Jankiewicz <https://jcubic.pl/me>
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import urllib
import urllib2
import httplib
import urlparse
import ssl
import re
import os
import tempfile
from ftplib import FTP, error_perm
import publicsuffix
username = ''
password = ''
host = ''
# url of direct admin page
directadmin = ''
def connection(url):
if url.scheme == 'http':
return httplib.HTTPConnection(url.netloc)
elif url.scheme == 'https':
return httplib.HTTPSConnection(url.netloc)
else:
raise Exception("wrong scheme %s" % (urls.scheme))
def get(url, cookie = None, referer = None):
url = urlparse.urlparse(url)
conn = connection(url)
headers = {}
if cookie is not None:
headers['Cookie'] = cookie
if referer is not None:
headers['Referer'] = referer
conn.request("GET", url.path, headers = headers)
res = conn.getresponse()
headers = list2dict(res.getheaders())
return res
def post(url, data, cookie = None, referer = None):
urlpart = urlparse.urlparse(url)
conn = connection(urlpart)
data = urllib.urlencode(data)
headers = {}
if cookie is not None:
headers['Cookie'] = cookie
if referer is not None:
headers['Referer'] = referer
conn.request("POST", urlpart.path, data, headers)
res = conn.getresponse()
headers = list2dict(res.getheaders())
if headers.has_key('set-cookie'):
cookies = headers['set-cookie'].split(';')[0].strip()
if headers.has_key('location'):
return get(headers['location'], cookies, referer = url)
return res
def list2dict(lst):
dict = {}
for k,v in lst:
dict[k] = v
return dict
ssl._https_verify_certificates(False)
def head(url):
url = urlparse.urlparse(url)
conn = connection(url)
if url.path == '':
conn.request("HEAD", '/')
else:
conn.request("HEAD", url.path)
return conn.getresponse()
def location(url):
while True:
res = head(url)
headers = list2dict(res.getheaders())
if not headers.has_key('location'):
return url
url = headers['location']
def placeFiles(ftp, path):
for name in os.listdir(path):
localpath = os.path.join(path, name)
if os.path.isfile(localpath):
print "writing %s" % name
ftp.storbinary('STOR ' + name, open(localpath,'rb'))
elif os.path.isdir(localpath):
if name not in ftp.nlst():
print "mkdir %s" % name
ftp.mkd(name)
else:
print "process %s" % name
ftp.cwd(name)
placeFiles(ftp, localpath)
ftp.cwd("..")
def mkchallange(host, username, password, directory = '/', quiet = False):
tmp_directory = tempfile.mkdtemp()
path = os.path.join(tmp_directory, '.well-known', 'acme-challenge')
os.makedirs(path)
fname = os.environ['CERTBOT_TOKEN']
f = open(os.path.join(path, fname), 'w')
f.write(os.environ['CERTBOT_VALIDATION'])
f.close()
con = FTP(host)
con.login(username, password)
con.cwd(directory)
placeFiles(con, tmp_directory)
if __name__ == '__main__':
from optparse import OptionParser
from sys import argv
parser = OptionParser()
parser.add_option('-c', '--cert')
parser.add_option('-d', '--domains')
(options, args) = parser.parse_args()
if options.domains is None:
if os.environ.has_key('CERTBOT_DOMAIN'):
psl_file = publicsuffix.fetch()
psl = publicsuffix.PublicSuffixList(psl_file)
domain = psl.get_public_suffix(os.environ['CERTBOT_DOMAIN'])
print "upload %s on %s domain" % (os.environ['CERTBOT_DOMAIN'], domain)
sub = os.environ['CERTBOT_DOMAIN'].replace(domain, '')
if len(sub) > 0 and sub[0] == '.':
sub = sub[1:]
path = '/domains/%s/public_html/%s' % (domain, sub)
mkchallange(host, username, password, path)
else:
print "\n".join(["usage: %s OPTIONS" % (argv[0]),
"-c --cert first domain from certbot",
"-d --d comma separated domains names"])
else:
domains = [x.strip() for x in options.domains.split(',')]
if options.cert is None:
cert_dir = domains[0]
else:
cert_dir = options.cert
full = open('/etc/letsencrypt/live/%s/fullchain.pem' % (cert_dir))
private = open('/etc/letsencrypt/live/%s/privkey.pem' % (cert_dir))
cert = "%s\n%s" % (full.read(), private.read())
ca = get('https://letsencrypt.org/certs/lets-encrypt-x3-cross-signed.pem.txt').read()
full.close()
private.close()
url = location(directadmin)
res = post("".join([url, '/CMD_LOGIN']), {
'referer': '/CMD_LOGIN',
'username': username,
'password': password
})
headers = list2dict(res.getheaders())
cookies = headers['set-cookie'].split(';')[0].strip()
for domain in domains:
domain = domain = psl.get_public_suffix(domain)
data = {
'domain':domain,
'action':'save',
'certificate': cert,
'type': 'paste',
'submit': 'Zapisz'
}
referer = "".join([url, "/CMD_SSL?domain=%s" % domain])
html = post("".join([url, '/CMD_SSL']), data, cookies, referer = referer).read()
if re.search("Certyfikat i Klucz", html):
print "Successful Certificate upload"
else:
print "Error Certificate upload"
data = {
'domain': domain,
'action': 'save',
'active': 'yes',
'type': 'cacert',
'cacert': ca,
'submit': 'Zapisz'
}
referer = "".join([url, '/CMD_SSL?DOMAIN=%s&view=cacert' % domain])
html = post("".join([url, '/CMD_SSL']), data, cookies, referer = referer).read()
if re.search("Powodzenie", html):
print "Successful Root Certificate upload"
else:
print "Error Root Certificate upload"
@jcubic
Copy link
Author

jcubic commented Feb 12, 2018

You need to update this script with:

  • your username password
  • host for ftp
  • url to direct admin
  • your Language for successful check (re.search invocation) and value of submit in post data
  • structure of ftp directories for the domains for uploading challenges, in my case is /domains/<DOMAIN>/public_html/<SUB DOMAIN>

The script require publicsuffix2 module

sudo pip install publicsuffix2

And of course certbot tool to get certificate from letsencrypt, to install on GNU/Linux

sudo dnf install certbot # fedora

or

sudo apt-get install certbot # ubuntu

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment