131 lines
4.8 KiB
Python
131 lines
4.8 KiB
Python
#!/usr/bin/env python3
|
|
"""Request, install, and verify BaoTa-managed HTTPS for xim.bchongw.com."""
|
|
import http.client
|
|
import json
|
|
import os
|
|
import socket
|
|
import ssl
|
|
import subprocess
|
|
import sys
|
|
import time
|
|
import urllib.request
|
|
from pathlib import Path
|
|
|
|
|
|
PANEL = Path('/www/server/panel')
|
|
DOMAIN = 'xim.bchongw.com'
|
|
PROJECT = 'xim_im'
|
|
EXPECTED_IP = '8.219.70.152'
|
|
GO_CONF = PANEL / ('vhost/nginx/go_' + PROJECT + '.conf')
|
|
|
|
|
|
def request(**values):
|
|
result = public.dict_obj()
|
|
for key, value in values.items():
|
|
setattr(result, key, value)
|
|
return result
|
|
|
|
|
|
def command(*args):
|
|
result = subprocess.run(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
|
|
universal_newlines=True)
|
|
if result.returncode:
|
|
raise RuntimeError('{} failed: {}'.format(args[0], result.stderr.strip()))
|
|
return result.stdout.strip()
|
|
|
|
|
|
def fetch(url, expected):
|
|
last_error = None
|
|
for _ in range(40):
|
|
try:
|
|
with urllib.request.urlopen(url, timeout=8) as response:
|
|
body = response.read().decode('utf-8')
|
|
if response.status == 200 and expected in body:
|
|
return body
|
|
except Exception as error:
|
|
last_error = error
|
|
time.sleep(0.5)
|
|
raise RuntimeError('Public verification failed for {}: {}'.format(url, last_error))
|
|
|
|
|
|
def main():
|
|
if os.geteuid() != 0 or not PANEL.is_dir():
|
|
raise SystemExit('Run as root on the BaoTa host')
|
|
os.chdir(str(PANEL))
|
|
sys.path.insert(0, str(PANEL))
|
|
sys.path.insert(0, str(PANEL / 'class'))
|
|
global public
|
|
import public
|
|
import panelSite
|
|
from acme_v2 import acme_v2
|
|
from projectModel.goModel import main as GoProject
|
|
|
|
addresses = sorted(set(socket.gethostbyname_ex(DOMAIN)[2]))
|
|
if EXPECTED_IP not in addresses:
|
|
raise RuntimeError('DNS does not point to this server: ' + json.dumps(addresses))
|
|
go = GoProject()
|
|
project = go.get_project_find(PROJECT)
|
|
if not project or not go.get_project_run_state(project_name=PROJECT):
|
|
raise RuntimeError('BaoTa Go project is not running')
|
|
if DOMAIN + ':80' not in project['project_config']['domains']:
|
|
raise RuntimeError('Expected domain is not bound to the project')
|
|
fetch('http://' + DOMAIN + '/healthz', '"status":"ok"')
|
|
|
|
site_id = str(project['id'])
|
|
args = request(id=site_id, auth_to=site_id, auth_type='http',
|
|
auto_wildcard='0', domains=json.dumps([DOMAIN]),
|
|
siteName=PROJECT, ca='letsencrypt')
|
|
certificate = acme_v2().apply_cert_api(args)
|
|
if not certificate.get('status'):
|
|
raise RuntimeError('Certificate request failed: ' + str(certificate.get('msg')))
|
|
if 'private_key' not in certificate or 'cert' not in certificate:
|
|
raise RuntimeError('Certificate authority returned an incomplete result')
|
|
|
|
install = panelSite.panelSite().SetSSL(request(
|
|
siteName=PROJECT,
|
|
key=certificate['private_key'],
|
|
csr=certificate['cert'] + certificate.get('root', ''),
|
|
))
|
|
if not install.get('status'):
|
|
raise RuntimeError('Certificate install failed: ' + str(install.get('msg')))
|
|
redirect = panelSite.panelSite().HttpToHttps(request(siteName=PROJECT))
|
|
if not redirect.get('status'):
|
|
raise RuntimeError('HTTPS redirect enablement failed: ' + str(redirect.get('msg')))
|
|
command('/www/server/nginx/sbin/nginx', '-t')
|
|
command('/etc/init.d/nginx', 'reload')
|
|
|
|
fetch('https://' + DOMAIN + '/healthz', '"status":"ok"')
|
|
fetch('https://' + DOMAIN + '/admin/', '星遇社交运营中心')
|
|
connection = http.client.HTTPConnection(DOMAIN, 80, timeout=8)
|
|
connection.request('GET', '/healthz')
|
|
response = connection.getresponse()
|
|
response.read()
|
|
if response.status not in (301, 308):
|
|
raise RuntimeError('HTTP is not redirected to HTTPS: ' + str(response.status))
|
|
if not (response.getheader('Location') or '').startswith('https://' + DOMAIN):
|
|
raise RuntimeError('HTTP redirect target is incorrect')
|
|
|
|
context = ssl.create_default_context()
|
|
with socket.create_connection((DOMAIN, 443), timeout=8) as raw:
|
|
with context.wrap_socket(raw, server_hostname=DOMAIN) as secure:
|
|
peer = secure.getpeercert()
|
|
protocol = secure.version()
|
|
ssl_state = panelSite.panelSite().GetSSL(request(siteName=PROJECT))
|
|
if not ssl_state.get('status') or not ssl_state.get('httpTohttps'):
|
|
raise RuntimeError('BaoTa does not report SSL and forced HTTPS as enabled')
|
|
result = {
|
|
'status': 'HTTPS_DEPLOYMENT_OK',
|
|
'domain': DOMAIN,
|
|
'project': PROJECT,
|
|
'dns': addresses,
|
|
'tls_protocol': protocol,
|
|
'certificate_not_after': peer.get('notAfter'),
|
|
'http_redirect': response.status,
|
|
'nginx_config': str(GO_CONF),
|
|
}
|
|
print(json.dumps(result, ensure_ascii=False))
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|