#!/usr/bin/env python3 # # Copyright (C) 2019-2023 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 . import sys from copy import deepcopy import vyos.defaults import vyos.certbot_util from vyos.config import Config from vyos.configverify import verify_vrf from vyos import ConfigError from vyos.util import call from vyos.util import dict_search from vyos.template import render from vyos import airbag airbag.enable() config_file = '/etc/nginx/sites-available/default' systemd_override = r'/etc/systemd/system/nginx.service.d/override.conf' certbot_dir = vyos.defaults.directories['certbot'] # https config needs to coordinate several subsystems: api, certbot, # 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 # (server blocks in nginx terminology) to pass to the jinja2 template. default_server_block = { 'id' : '', 'address' : '*', 'port' : '443', 'name' : ['_'], 'api' : {}, 'vyos_cert' : {}, 'certbot' : False } def get_config(config=None): if config: conf = config else: conf = Config() if not conf.exists('service https'): return None server_block_list = [] https_dict = conf.get_config_dict('service https', get_first_key=True) # organize by vhosts vhost_dict = https_dict.get('virtual-host', {}) if not vhost_dict: # no specified virtual hosts (server blocks); use default server_block_list.append(default_server_block) else: for vhost in list(vhost_dict): server_block = deepcopy(default_server_block) server_block['id'] = vhost data = vhost_dict.get(vhost, {}) server_block['address'] = data.get('listen-address', '*') server_block['port'] = data.get('listen-port', '443') name = data.get('server-name', ['_']) server_block['name'] = name server_block_list.append(server_block) # get certificate data cert_dict = https_dict.get('certificates', {}) # self-signed certificate vyos_cert_data = {} if 'system-generated-certificate' in list(cert_dict): vyos_cert_data = vyos.defaults.vyos_cert_data if vyos_cert_data: 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] # get api data api_set = False api_data = {} if 'api' in list(https_dict): api_set = True api_data = vyos.defaults.api_data api_settings = https_dict.get('api', {}) if api_settings: port = api_settings.get('port', '') if port: api_data['port'] = port vhosts = https_dict.get('api-restrict', {}).get('virtual-host', []) if vhosts: api_data['vhost'] = vhosts[:] if 'socket' in list(api_settings): api_data['socket'] = True if api_data: vhost_list = api_data.get('vhost', []) if not vhost_list: for block in server_block_list: block['api'] = api_data else: for block in server_block_list: if block['id'] in vhost_list: block['api'] = api_data # return dict for use in template https = {'server_block_list' : server_block_list, 'api_set': api_set, 'certbot': certbot} vrf_path = ['service', 'https', 'vrf'] if conf.exists(vrf_path): https['vrf'] = conf.return_value(vrf_path) return https def verify(https): if https is None: return None if https['certbot']: for sb in https['server_block_list']: if sb['certbot']: return None raise ConfigError("At least one 'virtual-host server-name' " "matching the 'certbot domain-name' is required.") verify_vrf(https) # Verify API server settings, if present if 'api' in https: keys = dict_search('api.keys.id', https) gql_auth_type = dict_search('api.graphql.authentication.type', https) # If "api graphql" is not defined and `gql_auth_type` is None, # there's certainly no JWT auth option, and keys are required jwt_auth = (gql_auth_type == "token") # Check for incomplete key configurations in every case valid_keys_exist = False if keys: for k in keys: if 'key' not in keys[k]: raise ConfigError(f'Missing HTTPS API key string for key id "{k}"') else: valid_keys_exist = True # If only key-based methods are enabled, # fail the commit if no valid key configurations are found if (not valid_keys_exist) and (not jwt_auth): raise ConfigError('At least one HTTPS API key is required unless GraphQL token authentication is enabled') return None def generate(https): if https is None: return None if 'server_block_list' not in https or not https['server_block_list']: https['server_block_list'] = [default_server_block] render(config_file, 'https/nginx.default.tmpl', https) render(systemd_override, 'https/override.conf.tmpl', https) return None def apply(https): # Reload systemd manager configuration call('systemctl daemon-reload') if https is not None: call('systemctl restart nginx.service') else: call('systemctl stop nginx.service') if __name__ == '__main__': try: c = get_config() verify(c) generate(c) apply(c) except ConfigError as e: print(e) sys.exit(1)