Skip to content

Commit

Permalink
http-api: T5782: use single config-mode script for https and http-api
Browse files Browse the repository at this point in the history
  • Loading branch information
jestabro committed Dec 1, 2023
1 parent 006931b commit 4fcf4ce
Show file tree
Hide file tree
Showing 7 changed files with 122 additions and 122 deletions.
1 change: 0 additions & 1 deletion data/config-mode-dependencies/vyos-1x.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
{
"firewall": {"group_resync": ["nat", "policy-route"]},
"http_api": {"https": ["https"]},
"interfaces_bonding": {
"ethernet": ["interfaces-ethernet"]
},
Expand Down
3 changes: 1 addition & 2 deletions interface-definitions/https.xml.in
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,9 @@
#include <include/allow-client.xml.i>
</children>
</tagNode>
<node name="api" owner="${vyos_conf_scripts_dir}/http-api.py">
<node name="api">
<properties>
<help>VyOS HTTP API configuration</help>
<priority>1002</priority>
</properties>
<children>
<node name="keys">
Expand Down
24 changes: 24 additions & 0 deletions python/vyos/configdiff.py
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,30 @@ def is_node_changed(self, path=[]):
return True
return False

def node_changed_presence(self, path=[]) -> bool:
if self._diff_tree is None:
raise NotImplementedError("diff_tree class not available")

path = self._make_path(path)
before = self._diff_tree.left.exists(path)
after = self._diff_tree.right.exists(path)
return (before and not after) or (not before and after)

def node_changed_children(self, path=[]) -> list:
if self._diff_tree is None:
raise NotImplementedError("diff_tree class not available")

path = self._make_path(path)
add = self._diff_tree.add
sub = self._diff_tree.sub
children = set()
if add.exists(path):
children.update(add.list_nodes(path))
if sub.exists(path):
children.update(sub.list_nodes(path))

return list(children)

def get_child_nodes_diff_str(self, path=[]):
ret = {'add': {}, 'change': {}, 'delete': {}}

Expand Down
29 changes: 29 additions & 0 deletions smoketest/scripts/cli/test_service_https.py
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,35 @@ def test_api_auth(self):
success = r.json()['data']['ShowVersion']['success']
self.assertTrue(success)

@ignore_warning(InsecureRequestWarning)
def test_api_add_delete(self):
address = '127.0.0.1'
key = 'VyOS-key'
url = f'https://{address}/retrieve'
payload = {'data': '{"op": "showConfig", "path": []}', 'key': f'{key}'}
headers = {}

self.cli_set(base_path)
self.cli_commit()

r = request('POST', url, verify=False, headers=headers, data=payload)
# api not configured; expect 503
self.assertEqual(r.status_code, 503)

self.cli_set(base_path + ['api', 'keys', 'id', 'key-01', 'key', key])
self.cli_commit()

r = request('POST', url, verify=False, headers=headers, data=payload)
# api configured; expect 200
self.assertEqual(r.status_code, 200)

self.cli_delete(base_path + ['api'])
self.cli_commit()

r = request('POST', url, verify=False, headers=headers, data=payload)
# api deleted; expect 503
self.assertEqual(r.status_code, 503)

@ignore_warning(InsecureRequestWarning)
def test_api_show(self):
address = '127.0.0.1'
Expand Down
112 changes: 0 additions & 112 deletions src/conf_mode/http-api.py

This file was deleted.

73 changes: 67 additions & 6 deletions src/conf_mode/https.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,24 @@

import os
import sys
import json

from copy import deepcopy
from time import sleep

import vyos.defaults
import vyos.certbot_util

from vyos.config import Config
from vyos.configdiff import get_config_diff
from vyos.configverify import verify_vrf
from vyos import ConfigError
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
Expand All @@ -42,6 +47,9 @@
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,
# self-signed certificate, as well as the virtual hosts defined within the
# https config definition itself. Consequently, one needs a general dict,
Expand All @@ -67,11 +75,35 @@ def get_config(config=None):
if not conf.exists(base):
return None

diff = get_config_diff(conf)

https = conf.get_config_dict(base, get_first_key=True)

if https:
https['pki'] = conf.get_config_dict(['pki'], key_mangling=('-', '_'),
get_first_key=True, no_tag_node_value_mangle=True)
no_tag_node_value_mangle=True,
get_first_key=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:
return https

http_api = conf.get_config_dict(base + ['api'], key_mangling=('-', '_'),
no_tag_node_value_mangle=True,
get_first_key=True,
with_recursive_defaults=True)

if http_api.from_defaults(['graphql']):
del http_api['graphql']

# Do we run inside a VRF context?
vrf_path = ['service', 'https', 'vrf']
if conf.exists(vrf_path):
http_api['vrf'] = conf.return_value(vrf_path)

https['api'] = http_api

return https

Expand Down Expand Up @@ -103,7 +135,7 @@ def verify(https):

if 'certbot' in https['certificates']:
vhost_names = []
for vh, vh_conf in https.get('virtual-host', {}).items():
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]
Expand Down Expand Up @@ -167,6 +199,14 @@ def generate(https):
if https is None:
return None

if 'api' not in https:
if os.path.exists(systemd_service):
os.unlink(systemd_service)
else:
render(systemd_service, 'https/vyos-http-api.service.j2', https['api'])
with open(api_config_state, 'w') as f:
json.dump(https['api'], f, indent=2)

server_block_list = []

# organize by vhosts
Expand Down Expand Up @@ -254,10 +294,31 @@ def generate(https):
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')
http_api_service_name = 'vyos-http-api.service'
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 {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 (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}')

if __name__ == '__main__':
try:
Expand Down
2 changes: 1 addition & 1 deletion src/services/vyos-http-api-server
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ from vyos.configsession import ConfigSession, ConfigSessionError

import api.graphql.state

api_config_state = '/tmp/api-config-state'
api_config_state = '/run/http-api-state'
CFG_GROUP = 'vyattacfg'

debug = True
Expand Down

0 comments on commit 4fcf4ce

Please sign in to comment.