summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristian Breunig <christian@breunig.cc>2024-01-05 22:35:59 +0100
committerChristian Breunig <christian@breunig.cc>2024-01-08 21:11:13 +0100
commit1b85e7a9442aa71e2137df44747bd184c4a8b6de (patch)
tree1b2f52cb56b102262aa8279c2fc66d1b2e9da61a
parent69b8c448c7c8fe32bb607dbc4465e4b56df39bfa (diff)
downloadvyos-1x-1b85e7a9442aa71e2137df44747bd184c4a8b6de.tar.gz
vyos-1x-1b85e7a9442aa71e2137df44747bd184c4a8b6de.zip
https: T5886: migrate https certbot to new "pki certificate" CLI tree
(cherry picked from commit 9ab6665c80c30bf446d94620fc9d85b052d48072)
-rw-r--r--data/templates/https/nginx.default.j212
-rw-r--r--debian/control1
-rw-r--r--interface-definitions/include/version/https-version.xml.i2
-rw-r--r--interface-definitions/service_https.xml.in18
-rwxr-xr-xsrc/conf_mode/service_https.py67
-rwxr-xr-xsrc/conf_mode/service_https_certificates_certbot.py114
-rwxr-xr-xsrc/migration-scripts/https/5-to-669
7 files changed, 89 insertions, 194 deletions
diff --git a/data/templates/https/nginx.default.j2 b/data/templates/https/nginx.default.j2
index 80239ea56..a530c14ba 100644
--- a/data/templates/https/nginx.default.j2
+++ b/data/templates/https/nginx.default.j2
@@ -18,12 +18,7 @@ server {
root /srv/localui;
-{% if server.certbot %}
- ssl_certificate {{ server.certbot_dir }}/live/{{ server.certbot_domain_dir }}/fullchain.pem;
- ssl_certificate_key {{ server.certbot_dir }}/live/{{ server.certbot_domain_dir }}/privkey.pem;
- include {{ server.certbot_dir }}/options-ssl-nginx.conf;
- ssl_dhparam {{ server.certbot_dir }}/ssl-dhparams.pem;
-{% elif server.vyos_cert %}
+{% if server.vyos_cert %}
ssl_certificate {{ server.vyos_cert.crt }};
ssl_certificate_key {{ server.vyos_cert.key }};
{% else %}
@@ -33,7 +28,12 @@ server {
#
include snippets/snakeoil.conf;
{% endif %}
+ ssl_session_cache shared:le_nginx_SSL:10m;
+ ssl_session_timeout 1440m;
+ ssl_session_tickets off;
+
ssl_protocols TLSv1.2 TLSv1.3;
+ ssl_ciphers 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!3DES:!MD5:!PSK';
# proxy settings for HTTP API, if enabled; 503, if not
location ~ ^/(retrieve|configure|config-file|image|container-image|generate|show|reboot|reset|poweroff|docs|openapi.json|redoc|graphql) {
diff --git a/debian/control b/debian/control
index bbe9df1f1..871a1f0f7 100644
--- a/debian/control
+++ b/debian/control
@@ -129,7 +129,6 @@ Depends:
pppoe,
procps,
python3,
- python3-certbot-nginx,
python3-cryptography,
python3-hurry.filesize,
python3-inotify,
diff --git a/interface-definitions/include/version/https-version.xml.i b/interface-definitions/include/version/https-version.xml.i
index fa18278f3..525314dbd 100644
--- a/interface-definitions/include/version/https-version.xml.i
+++ b/interface-definitions/include/version/https-version.xml.i
@@ -1,3 +1,3 @@
<!-- include start from include/version/https-version.xml.i -->
-<syntaxVersion component='https' version='5'></syntaxVersion>
+<syntaxVersion component='https' version='6'></syntaxVersion>
<!-- include end -->
diff --git a/interface-definitions/service_https.xml.in b/interface-definitions/service_https.xml.in
index 223f10962..57f36a982 100644
--- a/interface-definitions/service_https.xml.in
+++ b/interface-definitions/service_https.xml.in
@@ -192,24 +192,6 @@
<children>
#include <include/pki/ca-certificate.xml.i>
#include <include/pki/certificate.xml.i>
- <node name="certbot" owner="${vyos_conf_scripts_dir}/service_https_certificates_certbot.py">
- <properties>
- <help>Request or apply a letsencrypt certificate for domain-name</help>
- </properties>
- <children>
- <leafNode name="domain-name">
- <properties>
- <help>Domain name(s) for which to obtain certificate</help>
- <multi/>
- </properties>
- </leafNode>
- <leafNode name="email">
- <properties>
- <help>Email address to associate with certificate</help>
- </properties>
- </leafNode>
- </children>
- </node>
</children>
</node>
#include <include/interface/vrf.xml.i>
diff --git a/src/conf_mode/service_https.py b/src/conf_mode/service_https.py
index cb40acc9f..2e7ebda5a 100755
--- a/src/conf_mode/service_https.py
+++ b/src/conf_mode/service_https.py
@@ -22,7 +22,6 @@ from copy import deepcopy
from time import sleep
import vyos.defaults
-import vyos.certbot_util
from vyos.base import Warning
from vyos.config import Config
@@ -33,8 +32,6 @@ from vyos.pki import wrap_certificate
from vyos.pki import wrap_private_key
from vyos.template import render
from vyos.utils.process import call
-from vyos.utils.process import is_systemd_service_running
-from vyos.utils.process import is_systemd_service_active
from vyos.utils.network import check_port_availability
from vyos.utils.network import is_listen_port_bind_service
from vyos.utils.file import write_file
@@ -46,12 +43,11 @@ config_file = '/etc/nginx/sites-available/default'
systemd_override = r'/run/systemd/system/nginx.service.d/override.conf'
cert_dir = '/etc/ssl/certs'
key_dir = '/etc/ssl/private'
-certbot_dir = vyos.defaults.directories['certbot']
api_config_state = '/run/http-api-state'
systemd_service = '/run/systemd/system/vyos-http-api.service'
-# https config needs to coordinate several subsystems: api, certbot,
+# https config needs to coordinate several subsystems: api,
# self-signed certificate, as well as the virtual hosts defined within the
# https config definition itself. Consequently, one needs a general dict,
# encompassing the https and other configs, and a list of such virtual hosts
@@ -63,7 +59,6 @@ default_server_block = {
'name' : ['_'],
'api' : False,
'vyos_cert' : {},
- 'certbot' : False
}
def get_config(config=None):
@@ -80,7 +75,6 @@ def get_config(config=None):
https = conf.get_config_dict(base, get_first_key=True, with_pki=True)
- https['children_changed'] = diff.node_changed_children(base)
https['api_add_or_delete'] = diff.node_changed_presence(base + ['api'])
if 'api' not in https:
@@ -100,7 +94,6 @@ def get_config(config=None):
http_api['vrf'] = conf.return_value(vrf_path)
https['api'] = http_api
-
return https
def verify(https):
@@ -119,25 +112,17 @@ def verify(https):
cert_name = certificates['certificate']
if cert_name not in https['pki']['certificate']:
- raise ConfigError("Invalid certificate on https configuration")
+ raise ConfigError('Invalid certificate on https configuration')
pki_cert = https['pki']['certificate'][cert_name]
if 'certificate' not in pki_cert:
- raise ConfigError("Missing certificate on https configuration")
+ raise ConfigError('Missing certificate on https configuration')
if 'private' not in pki_cert or 'key' not in pki_cert['private']:
raise ConfigError("Missing certificate private key on https configuration")
-
- if 'certbot' in https['certificates']:
- vhost_names = []
- for _, vh_conf in https.get('virtual-host', {}).items():
- vhost_names += vh_conf.get('server-name', [])
- domains = https['certificates']['certbot'].get('domain-name', [])
- domains_found = [domain for domain in domains if domain in vhost_names]
- if not domains_found:
- raise ConfigError("At least one 'virtual-host <id> server-name' "
- "matching the 'certbot domain-name' is required.")
+ else:
+ Warning('No certificate specified, using buildin self-signed certificates!')
server_block_list = []
@@ -255,22 +240,6 @@ def generate(https):
for block in server_block_list:
block['vyos_cert'] = vyos_cert_data
- # letsencrypt certificate using certbot
-
- certbot = False
- cert_domains = cert_dict.get('certbot', {}).get('domain-name', [])
- if cert_domains:
- certbot = True
- for domain in cert_domains:
- sub_list = vyos.certbot_util.choose_server_block(server_block_list,
- domain)
- if sub_list:
- for sb in sub_list:
- sb['certbot'] = True
- sb['certbot_dir'] = certbot_dir
- # certbot organizes certificates by first domain
- sb['certbot_domain_dir'] = cert_domains[0]
-
if 'api' in list(https):
vhost_list = https.get('api-restrict', {}).get('virtual-host', [])
if not vhost_list:
@@ -283,7 +252,6 @@ def generate(https):
data = {
'server_block_list': server_block_list,
- 'certbot': certbot
}
render(config_file, 'https/nginx.default.j2', data)
@@ -297,27 +265,18 @@ def apply(https):
https_service_name = 'nginx.service'
if https is None:
- if is_systemd_service_active(f'{http_api_service_name}'):
- call(f'systemctl stop {http_api_service_name}')
+ call(f'systemctl stop {http_api_service_name}')
call(f'systemctl stop {https_service_name}')
return
- if 'api' in https['children_changed']:
- if 'api' in https:
- if is_systemd_service_running(f'{http_api_service_name}'):
- call(f'systemctl reload {http_api_service_name}')
- else:
- call(f'systemctl restart {http_api_service_name}')
- # Let uvicorn settle before (possibly) restarting nginx
- sleep(1)
- else:
- if is_systemd_service_active(f'{http_api_service_name}'):
- call(f'systemctl stop {http_api_service_name}')
+ if 'api' in https:
+ call(f'systemctl reload-or-restart {http_api_service_name}')
+ # Let uvicorn settle before (possibly) restarting nginx
+ sleep(1)
+ else:
+ call(f'systemctl stop {http_api_service_name}')
- if (not is_systemd_service_running(f'{https_service_name}') or
- https['api_add_or_delete'] or
- set(https['children_changed']) - set(['api'])):
- call(f'systemctl restart {https_service_name}')
+ call(f'systemctl reload-or-restart {https_service_name}')
if __name__ == '__main__':
try:
diff --git a/src/conf_mode/service_https_certificates_certbot.py b/src/conf_mode/service_https_certificates_certbot.py
deleted file mode 100755
index 1a6a498de..000000000
--- a/src/conf_mode/service_https_certificates_certbot.py
+++ /dev/null
@@ -1,114 +0,0 @@
-#!/usr/bin/env python3
-#
-# Copyright (C) 2019-2020 VyOS maintainers and contributors
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License version 2 or later as
-# published by the Free Software Foundation.
-#
-# 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 sys
-import os
-
-import vyos.defaults
-from vyos.config import Config
-from vyos import ConfigError
-from vyos.utils.process import cmd
-from vyos.utils.process import call
-from vyos.utils.process import is_systemd_service_running
-
-from vyos import airbag
-airbag.enable()
-
-vyos_conf_scripts_dir = vyos.defaults.directories['conf_mode']
-vyos_certbot_dir = vyos.defaults.directories['certbot']
-
-dependencies = [
- 'service_https.py',
-]
-
-def request_certbot(cert):
- email = cert.get('email')
- if email is not None:
- email_flag = '-m {0}'.format(email)
- else:
- email_flag = ''
-
- domains = cert.get('domains')
- if domains is not None:
- domain_flag = '-d ' + ' -d '.join(domains)
- else:
- domain_flag = ''
-
- certbot_cmd = f'certbot certonly --config-dir {vyos_certbot_dir} -n --nginx --agree-tos --no-eff-email --expand {email_flag} {domain_flag}'
-
- cmd(certbot_cmd,
- raising=ConfigError,
- message="The certbot request failed for the specified domains.")
-
-def get_config():
- conf = Config()
- if not conf.exists('service https certificates certbot'):
- return None
- else:
- conf.set_level('service https certificates certbot')
-
- cert = {}
-
- if conf.exists('domain-name'):
- cert['domains'] = conf.return_values('domain-name')
-
- if conf.exists('email'):
- cert['email'] = conf.return_value('email')
-
- return cert
-
-def verify(cert):
- if cert is None:
- return None
-
- if 'domains' not in cert:
- raise ConfigError("At least one domain name is required to"
- " request a letsencrypt certificate.")
-
- if 'email' not in cert:
- raise ConfigError("An email address is required to request"
- " a letsencrypt certificate.")
-
-def generate(cert):
- if cert is None:
- return None
-
- # certbot will attempt to reload nginx, even with 'certonly';
- # start nginx if not active
- if not is_systemd_service_running('nginx.service'):
- call('systemctl start nginx.service')
-
- request_certbot(cert)
-
-def apply(cert):
- if cert is not None:
- call('systemctl restart certbot.timer')
- else:
- call('systemctl stop certbot.timer')
- return None
-
- for dep in dependencies:
- cmd(f'{vyos_conf_scripts_dir}/{dep}', raising=ConfigError)
-
-if __name__ == '__main__':
- try:
- c = get_config()
- verify(c)
- generate(c)
- apply(c)
- except ConfigError as e:
- print(e)
- sys.exit(1)
diff --git a/src/migration-scripts/https/5-to-6 b/src/migration-scripts/https/5-to-6
new file mode 100755
index 000000000..b4159f02f
--- /dev/null
+++ b/src/migration-scripts/https/5-to-6
@@ -0,0 +1,69 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2024 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# 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/>.
+
+# T5886: Add support for ACME protocol (LetsEncrypt), migrate https certbot
+# to new "pki certificate" CLI tree
+
+import os
+import sys
+
+from vyos.configtree import ConfigTree
+from vyos.defaults import directories
+
+vyos_certbot_dir = directories['certbot']
+
+if len(sys.argv) < 2:
+ print("Must specify file name!")
+ sys.exit(1)
+
+file_name = sys.argv[1]
+
+with open(file_name, 'r') as f:
+ config_file = f.read()
+
+config = ConfigTree(config_file)
+
+base = ['service', 'https', 'certificates']
+if not config.exists(base):
+ # Nothing to do
+ sys.exit(0)
+
+# both domain-name and email must be set on CLI - ensured by previous verify()
+domain_names = config.return_values(base + ['certbot', 'domain-name'])
+email = config.return_value(base + ['certbot', 'email'])
+config.delete(base)
+
+# Set default certname based on domain-name
+cert_name = 'https-' + domain_names[0].split('.')[0]
+# Overwrite certname from previous certbot calls if available
+if os.path.exists(f'{vyos_certbot_dir}/live'):
+ for cert in [f.path.split('/')[-1] for f in os.scandir(f'{vyos_certbot_dir}/live') if f.is_dir()]:
+ cert_name = cert
+ break
+
+for domain in domain_names:
+ config.set(['pki', 'certificate', cert_name, 'acme', 'domain-name'], value=domain, replace=False)
+ config.set(['pki', 'certificate', cert_name, 'acme', 'email'], value=email)
+
+# Update Webserver certificate
+config.set(base + ['certificate'], value=cert_name)
+
+try:
+ with open(file_name, 'w') as f:
+ f.write(config.to_string())
+except OSError as e:
+ print("Failed to save the modified config: {}".format(e))
+ sys.exit(1)