mirror of
https://github.com/freedombox/FreedomBox.git
synced 2026-01-21 07:55:00 +00:00
189 lines
5.9 KiB
Python
Executable File
189 lines
5.9 KiB
Python
Executable File
#!/usr/bin/python3
|
|
#
|
|
# This file is part of Plinth.
|
|
#
|
|
# This program is free software: you can redistribute it and/or modify
|
|
# it under the terms of the GNU Affero 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 Affero General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU Affero General Public License
|
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
#
|
|
|
|
"""
|
|
Configuration helper for Let's Encrypt.
|
|
"""
|
|
|
|
import argparse
|
|
import json
|
|
import os
|
|
import subprocess
|
|
import sys
|
|
|
|
from plinth import action_utils
|
|
|
|
TEST_MODE = False
|
|
LIVE_DIRECTORY = '/etc/letsencrypt/live/'
|
|
APACHE_PREFIX = '/etc/apache2/sites-available/'
|
|
APACHE_CONFIGURATION = '''
|
|
<IfModule mod_gnutls.c>
|
|
<VirtualHost _default_:443>
|
|
ServerAdmin webmaster@localhost
|
|
ServerName {domain}
|
|
DocumentRoot /var/www/html
|
|
<Directory />
|
|
Options FollowSymLinks
|
|
AllowOverride None
|
|
</Directory>
|
|
<Directory /var/www/html>
|
|
Options Indexes FollowSymLinks MultiViews
|
|
AllowOverride None
|
|
Order allow,deny
|
|
allow from all
|
|
</Directory>
|
|
ScriptAlias /cgi-bin/ /usr/lib/cgi-bin/
|
|
<Directory "/usr/lib/cgi-bin">
|
|
AllowOverride None
|
|
Options +ExecCGI -MultiViews +SymLinksIfOwnerMatch
|
|
Order allow,deny
|
|
Allow from all
|
|
</Directory>
|
|
ErrorLog ${{APACHE_LOG_DIR}}/error.log
|
|
# Possible values include: debug, info, notice, warn, error, crit, alert, emerg.
|
|
LogLevel warn
|
|
CustomLog ${{APACHE_LOG_DIR}}/ssl_access.log combined
|
|
# GnuTLS Switch: Enable/Disable SSL/TLS for this virtual host.
|
|
GnuTLSEnable On
|
|
# Automatically obtained certficates from Let's Encrypt
|
|
GnuTLSCertificateFile /etc/letsencrypt/live/{domain}/fullchain.pem
|
|
GnuTLSKeyFile /etc/letsencrypt/live/{domain}/privkey.pem
|
|
# See http://www.outoforder.cc/projects/apache/mod_gnutls/docs/#GnuTLSPriorities
|
|
GnuTLSPriorities NORMAL
|
|
</VirtualHost>
|
|
</IfModule>
|
|
'''
|
|
|
|
|
|
def parse_arguments():
|
|
"""Return parsed command line arguments as dictionary."""
|
|
parser = argparse.ArgumentParser()
|
|
subparsers = parser.add_subparsers(dest='subcommand', help='Sub command')
|
|
|
|
subparsers.add_parser(
|
|
'get-status', help='Return the status of configured domains.')
|
|
revoke_parser = subparsers.add_parser(
|
|
'revoke', help='Disable and domain and revoke its certificate.')
|
|
revoke_parser.add_argument(
|
|
'--domain', help='Domain name to revoke certificate for')
|
|
obtain_parser = subparsers.add_parser(
|
|
'obtain', help='Obtain certficate for a domain and setup website.')
|
|
obtain_parser.add_argument(
|
|
'--domain', help='Domain name to obtain certificate for')
|
|
|
|
return parser.parse_args()
|
|
|
|
|
|
def get_certficate_expiry(domain):
|
|
"""Return the expiry date of a certificate."""
|
|
certificate_file = os.path.join(LIVE_DIRECTORY, domain, 'cert.pem')
|
|
output = subprocess.check_output(['openssl', 'x509', '-enddate', '-noout',
|
|
'-in', certificate_file])
|
|
return output.decode().strip().split('=')[1]
|
|
|
|
|
|
def subcommand_get_status(_):
|
|
"""Return a JSON dictionary of currently configured domains."""
|
|
try:
|
|
domains = os.listdir(LIVE_DIRECTORY)
|
|
except OSError:
|
|
domains = []
|
|
|
|
domains = [domain for domain in domains
|
|
if os.path.isdir(os.path.join(LIVE_DIRECTORY, domain))]
|
|
|
|
domain_status = {}
|
|
for domain in domains:
|
|
domain_status[domain] = {
|
|
'certificate_available': True,
|
|
'expiry_date': get_certficate_expiry(domain),
|
|
'web_enabled':
|
|
action_utils.webserver_is_enabled(domain, kind='site')
|
|
}
|
|
|
|
print(json.dumps({'domains': domain_status}))
|
|
|
|
|
|
def subcommand_revoke(arguments):
|
|
"""Disable a domain and revoke the certificate."""
|
|
domain = arguments.domain
|
|
|
|
command = ['letsencrypt', 'revoke', '--domain', domain, '--cert-path',
|
|
os.path.join(LIVE_DIRECTORY, domain, 'cert.pem')]
|
|
if TEST_MODE:
|
|
command.append('--staging')
|
|
|
|
process = subprocess.Popen(command, stdout=subprocess.PIPE,
|
|
stderr=subprocess.PIPE)
|
|
stdout, stderr = process.communicate()
|
|
if process.returncode:
|
|
print(stderr.decode(), file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
action_utils.webserver_disable(domain, kind='site')
|
|
|
|
|
|
def subcommand_obtain(arguments):
|
|
"""Obtain a certificate for a domain and setup website."""
|
|
domain = arguments.domain
|
|
|
|
command = [
|
|
'letsencrypt', 'certonly', '--text', '--agree-tos',
|
|
'--register-unsafely-without-email', '--domain', arguments.domain,
|
|
'--authenticator', 'webroot', '--webroot-path', '/var/www/html/',
|
|
'--renew-by-default']
|
|
if TEST_MODE:
|
|
command.append('--staging')
|
|
|
|
process = subprocess.Popen(command, stdout=subprocess.PIPE,
|
|
stderr=subprocess.PIPE)
|
|
stdin, stderr = process.communicate()
|
|
if process.returncode:
|
|
print(stderr.decode(), file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
setup_webserver_config(domain)
|
|
|
|
action_utils.webserver_enable(domain, kind='site')
|
|
|
|
|
|
def setup_webserver_config(domain):
|
|
"""Create SSL web server configuration for a domain.
|
|
|
|
Do so only if there is no configuration existing.
|
|
"""
|
|
file_name = os.path.join(APACHE_PREFIX, domain + '.conf')
|
|
if os.path.isfile(file_name):
|
|
return
|
|
|
|
with open(file_name, 'w') as file_handle:
|
|
file_handle.write(APACHE_CONFIGURATION.format(domain=domain))
|
|
|
|
|
|
def main():
|
|
"""Parse arguments and perform all duties."""
|
|
arguments = parse_arguments()
|
|
|
|
subcommand = arguments.subcommand.replace('-', '_')
|
|
subcommand_method = globals()['subcommand_' + subcommand]
|
|
subcommand_method(arguments)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|