diff options
Diffstat (limited to 'src')
-rwxr-xr-x | src/conf_mode/dynamic_dns.py | 55 | ||||
-rwxr-xr-x | src/conf_mode/https.py | 109 | ||||
-rwxr-xr-x | src/conf_mode/interfaces-ethernet.py | 6 | ||||
-rwxr-xr-x | src/conf_mode/snmp.py | 139 | ||||
-rwxr-xr-x | src/migration-scripts/snmp/0-to-1 | 56 | ||||
-rwxr-xr-x | src/op_mode/wireguard.py | 40 | ||||
-rwxr-xr-x | src/services/vyos-http-api-server | 120 | ||||
-rwxr-xr-x | src/utils/vyos-config-to-json | 40 |
8 files changed, 319 insertions, 246 deletions
diff --git a/src/conf_mode/dynamic_dns.py b/src/conf_mode/dynamic_dns.py index ff3c1f825..027a7f7e3 100755 --- a/src/conf_mode/dynamic_dns.py +++ b/src/conf_mode/dynamic_dns.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2018 VyOS maintainers and contributors +# Copyright (C) 2018-2019 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 @@ -13,8 +13,6 @@ # # 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 os import sys @@ -23,16 +21,17 @@ import jinja2 from vyos.config import Config from vyos import ConfigError -config_file = r'/etc/ddclient.conf' +config_file = r'/etc/ddclient/ddclient.conf' cache_file = r'/var/cache/ddclient/ddclient.cache' +pid_file = r'/var/run/ddclient/ddclient.pid' config_tmpl = """ ### Autogenerated by dynamic_dns.py ### daemon=1m syslog=yes ssl=yes -pid=/var/run/ddclient/ddclient.pid -cache=/var/cache/ddclient/ddclient.cache +pid={{ pid_file }} +cache={{ cache_file }} {% for interface in interfaces -%} @@ -48,11 +47,11 @@ use=if, if={{ interface.interface }} {% for rfc in interface.rfc2136 -%} {% for record in rfc.record %} # RFC2136 dynamic DNS configuration for {{ record }}.{{ rfc.zone }} -server={{ rfc.server }} -protocol=nsupdate -password={{ rfc.keyfile }} -ttl={{ rfc.ttl }} -zone={{ rfc.zone }} +server={{ rfc.server }}, +protocol=nsupdate, +password={{ rfc.keyfile }}, +ttl={{ rfc.ttl }}, +zone={{ rfc.zone }}, {{ record }} {% endfor -%} {% endfor -%} @@ -60,12 +59,16 @@ zone={{ rfc.zone }} {% for srv in interface.service %} {% for host in srv.host %} # DynDNS provider configuration for {{ host }} -protocol={{ srv.protocol }} -max-interval=28d -login={{ srv.login }} -password='{{ srv.password }}' +protocol={{ srv.protocol }}, +max-interval=28d, +login={{ srv.login }}, +password='{{ srv.password }}', {% if srv.server -%} -server={{ srv.server }} +server={{ srv.server }}, +{% endif -%} +{% if 'cloudflare' in srv.protocol -%} +{% set zone = host.split('.',1) -%} +zone={{ zone[1] }}, {% endif -%} {{ host }} {% endfor %} @@ -91,6 +94,8 @@ default_service_protocol = { default_config_data = { 'interfaces': [], + 'cache_file': cache_file, + 'pid_file': pid_file } def get_config(): @@ -237,8 +242,15 @@ def generate(dyndns): if dyndns is None: return None - tmpl = jinja2.Template(config_tmpl) + dirname = os.path.dirname(dyndns['pid_file']) + if not os.path.exists(dirname): + os.mkdir(dirname) + dirname = os.path.dirname(config_file) + if not os.path.exists(dirname): + os.mkdir(dirname) + + tmpl = jinja2.Template(config_tmpl) config_text = tmpl.render(dyndns) with open(config_file, 'w') as f: f.write(config_text) @@ -246,11 +258,16 @@ def generate(dyndns): return None def apply(dyndns): - if os.path.exists(cache_file): - os.unlink(cache_file) + if os.path.exists(dyndns['cache_file']): + os.unlink(dyndns['cache_file']) + + if os.path.exists('/etc/ddclient.conf'): + os.unlink('/etc/ddclient.conf') if dyndns is None: os.system('/etc/init.d/ddclient stop') + if os.path.exists(dyndns['pid_file']): + os.unlink(dyndns['pid_file']) else: os.system('/etc/init.d/ddclient restart') diff --git a/src/conf_mode/https.py b/src/conf_mode/https.py index f948063e9..d7fcb74de 100755 --- a/src/conf_mode/https.py +++ b/src/conf_mode/https.py @@ -30,34 +30,34 @@ config_file = '/etc/nginx/sites-available/default' # Please be careful if you edit the template. config_tmpl = """ -### Autogenerated by http-api.py ### +### Autogenerated by https.py ### # Default server configuration # server { listen 80 default_server; listen [::]:80 default_server; server_name _; - return 302 https://$server_name$request_uri; + return 301 https://$server_name$request_uri; } -{% for addr, names in listen_addresses.items() %} +{% for server in server_block_list %} server { # SSL configuration # -{% if addr == '*' %} - listen 443 ssl default_server; - listen [::]:443 ssl default_server; +{% if server.address == '*' %} + listen 443 ssl; + listen [::]:443 ssl; {% else %} - listen {{ addr }}:443 ssl; + listen {{ server.address }}:443 ssl; {% endif %} -{% for name in names %} +{% for name in server.name %} server_name {{ name }}; {% endfor %} -{% if vyos_cert %} - include {{ vyos_cert.conf }}; +{% if server.vyos_cert %} + include {{ server.vyos_cert.conf }}; {% else %} # # Self signed certs generated by the ssl-cert package @@ -67,46 +67,9 @@ server { {% endif %} # proxy settings for HTTP API, if enabled; 503, if not - location ~ /(retrieve|configure) { -{% if api %} - proxy_pass http://localhost:{{ api.port }}; - proxy_buffering off; -{% else %} - return 503; -{% endif %} - } - - error_page 501 502 503 =200 @50*_json; - - location @50*_json { - default_type application/json; - return 200 '{"error": "Start service in configuration mode: set service https api"}'; - } - -} -{% else %} -server { - # SSL configuration - # - listen 443 ssl default_server; - listen [::]:443 ssl default_server; - - server_name _; - -{% if vyos_cert %} - include {{ vyos_cert.conf }}; -{% else %} - # - # Self signed certs generated by the ssl-cert package - # Don't use them in a production server! - # - include snippets/snakeoil.conf; -{% endif %} - - # proxy settings for HTTP API, if enabled; 503, if not - location ~ /(retrieve|configure) { -{% if api %} - proxy_pass http://localhost:{{ api.port }}; + location ~ /(retrieve|configure|config-file|image) { +{% if server.api %} + proxy_pass http://localhost:{{ server.api.port }}; proxy_buffering off; {% else %} return 503; @@ -125,8 +88,16 @@ server { {% endfor %} """ +default_server_block = { + 'address' : '*', + 'name' : ['_'], + # api : + # vyos_cert : + # le_cert : +} + def get_config(): - https = vyos.defaults.https_data + server_block_list = [] conf = Config() if not conf.exists('service https'): return None @@ -134,25 +105,36 @@ def get_config(): conf.set_level('service https') if conf.exists('listen-address'): - addrs = {} for addr in conf.list_nodes('listen-address'): - addrs[addr] = ['_'] + server_block = {'address' : addr} + server_block['name'] = ['_'] if conf.exists('listen-address {0} server-name'.format(addr)): names = conf.return_values('listen-address {0} server-name'.format(addr)) - addrs[addr] = names[:] - https['listen_addresses'] = addrs + server_block['name'] = names[:] + server_block_list.append(server_block) + if not server_block_list: + server_block_list.append(default_server_block) + + vyos_cert_data = {} if conf.exists('certificates'): if conf.exists('certificates system-generated-certificate'): - https['vyos_cert'] = vyos.defaults.vyos_cert_data + vyos_cert_data = vyos.defaults.vyos_cert_data + if vyos_cert_data: + for block in server_block_list: + block['vyos_cert'] = vyos_cert_data + api_data = {} if conf.exists('api'): - https['api'] = vyos.defaults.api_data - - if conf.exists('api port'): - port = conf.return_value('api port') - https['api']['port'] = port - + api_data = vyos.defaults.api_data + if conf.exists('api port'): + port = conf.return_value('api port') + api_data['port'] = port + if api_data: + for block in server_block_list: + block['api'] = api_data + + https = {'server_block_list' : server_block_list} return https def verify(https): @@ -162,6 +144,9 @@ 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] + tmpl = jinja2.Template(config_tmpl, trim_blocks=True) config_text = tmpl.render(https) with open(config_file, 'w') as f: diff --git a/src/conf_mode/interfaces-ethernet.py b/src/conf_mode/interfaces-ethernet.py index cd40aff3e..a9ed6bfb6 100755 --- a/src/conf_mode/interfaces-ethernet.py +++ b/src/conf_mode/interfaces-ethernet.py @@ -130,7 +130,7 @@ def get_config(): print("Interface not specified") # check if ethernet interface has been removed - cfg_base = 'interfaces ethernet ' + eth['intf'] + cfg_base = ['interfaces', 'ethernet', eth['intf']] if not conf.exists(cfg_base): eth['deleted'] = True # we can not bail out early as ethernet interface can not be removed @@ -249,7 +249,7 @@ def get_config(): if conf.exists('vif-s'): for vif_s in conf.list_nodes('vif-s'): # set config level to vif-s interface - conf.set_level(cfg_base + ' vif-s ' + vif_s) + conf.set_level(cfg_base + ['vif-s', vif_s]) eth['vif_s'].append(vlan_to_dict(conf)) # re-set configuration level to parse new nodes @@ -263,7 +263,7 @@ def get_config(): if conf.exists('vif'): for vif in conf.list_nodes('vif'): # set config level to vif interface - conf.set_level(cfg_base + ' vif ' + vif) + conf.set_level(cfg_base + ['vif', vif]) eth['vif'].append(vlan_to_dict(conf)) return eth diff --git a/src/conf_mode/snmp.py b/src/conf_mode/snmp.py index cba1fe319..b64cccbfa 100755 --- a/src/conf_mode/snmp.py +++ b/src/conf_mode/snmp.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2018 VyOS maintainers and contributors +# Copyright (C) 2018-2019 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 @@ -13,24 +13,21 @@ # # 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 shutil import stat import pwd -import time - import jinja2 -import random -import binascii import re import vyos.version import vyos.validate +from binascii import hexlify +from shutil import move +from time import sleep +from stat import S_IRWXU,S_IXGRP,S_IXOTH from vyos.config import Config from vyos import ConfigError @@ -78,7 +75,7 @@ createUser {{ u.name }} {%- elif u.authPassword %} createUser {{ u.name }} {{ u.authProtocol | upper }} "{{ u.authPassword }}" {{ u.privProtocol | upper }} {{ u.privPassword }} {%- else %} -usmUser 1 3 {{ u.engineID }} "{{ u.name }}" "{{ u.name }}" NULL {{ u.authOID }} {{ u.authMasterKey }} {{ u.privOID }} {{ u.privMasterKey }} 0x +usmUser 1 3 {{ v3_engineid }} "{{ u.name }}" "{{ u.name }}" NULL {{ u.authOID }} {{ u.authMasterKey }} {{ u.privOID }} {{ u.privMasterKey }} 0x {%- endif %} {%- endfor %} @@ -117,9 +114,6 @@ monitor -r 10 -e linkDownTrap "Generate linkDown" ifOperStatus == 2 ######################## # configurable section # ######################## -{% if v3_tsm_key %} -[snmp] localCert {{ v3_tsm_key }} -{%- endif %} # Default system description is VyOS version sysDescr VyOS {{ version }} @@ -130,7 +124,7 @@ SysDescr {{ description }} {%- endif %} # Listen -agentaddress unix:/run/snmpd.socket{% if listen_on %}{% for li in listen_on %},{{ li }}{% endfor %}{% else %},udp:161,udp6:161{% endif %}{% if v3_tsm_key %},tlstcp:{{ v3_tsm_port }},dtlsudp::{{ v3_tsm_port }}{% endif %} +agentaddress unix:/run/snmpd.socket{% if listen_on %}{% for li in listen_on %},{{ li }}{% endfor %}{% else %},udp:161,udp6:161{% endif %} # SNMP communities {%- for c in communities %} @@ -192,25 +186,23 @@ view {{ v.name }} included .{{ oid.oid }} # context sec.model sec.level match read write notif {%- for g in v3_groups %} access {{ g.name }} "" usm {{ g.seclevel }} exact {{ g.view }} {% if g.mode == 'ro' %}none{% else %}{{ g.view }}{% endif %} none -access {{ g.name }} "" tsm {{ g.seclevel }} exact {{ g.view }} {% if g.mode == 'ro' %}none{% else %}{{ g.view }}{% endif %} none {%- endfor %} # trap-target {%- for t in v3_traps %} -trapsess -v 3 {{ '-Ci' if t.type == 'inform' }} -e {{ t.engineID }} -u {{ t.secName }} -l {{ t.secLevel }} -a {{ t.authProtocol }} {% if t.authPassword %}-A {{ t.authPassword }}{% elif t.authMasterKey %}-3m {{ t.authMasterKey }}{% endif %} -x {{ t.privProtocol }} {% if t.privPassword %}-X {{ t.privPassword }}{% elif t.privMasterKey %}-3M {{ t.privMasterKey }}{% endif %} {{ t.ipProto }}:{{ t.ipAddr }}:{{ t.ipPort }} +trapsess -v 3 {{ '-Ci' if t.type == 'inform' }} -e {{ v3_engineid }} -u {{ t.secName }} -l {{ t.secLevel }} -a {{ t.authProtocol }} {% if t.authPassword %}-A {{ t.authPassword }}{% elif t.authMasterKey %}-3m {{ t.authMasterKey }}{% endif %} -x {{ t.privProtocol }} {% if t.privPassword %}-X {{ t.privPassword }}{% elif t.privMasterKey %}-3M {{ t.privMasterKey }}{% endif %} {{ t.ipProto }}:{{ t.ipAddr }}:{{ t.ipPort }} {%- endfor %} # group {%- for u in v3_users %} group {{ u.group }} usm {{ u.name }} -group {{ u.group }} tsm {{ u.name }} {% endfor %} {%- endif %} {% if script_ext %} # extension scripts {%- for ext in script_ext|sort %} -extend\t{{ext}}\t{{script_ext[ext]}} +extend {{ ext.name }} {{ ext.script }} {%- endfor %} {% endif %} """ @@ -244,11 +236,9 @@ default_config_data = { 'v3_engineid': '', 'v3_groups': [], 'v3_traps': [], - 'v3_tsm_key': '', - 'v3_tsm_port': '10161', 'v3_users': [], 'v3_views': [], - 'script_ext': {} + 'script_ext': [] } def rmfile(file): @@ -266,10 +256,10 @@ def get_config(): version_data = vyos.version.get_version_data() snmp['version'] = version_data['version'] - # create an internal snmpv3 user of the form 'vyattaxxxxxxxxxxxxxxxx' + # create an internal snmpv3 user of the form 'vyosxxxxxxxxxxxxxxxx' # os.urandom(8) returns 8 bytes of random data - snmp['vyos_user'] = 'vyatta' + binascii.hexlify(os.urandom(8)).decode('utf-8') - snmp['vyos_user_pass'] = binascii.hexlify(os.urandom(16)).decode('utf-8') + snmp['vyos_user'] = 'vyos' + hexlify(os.urandom(8)).decode('utf-8') + snmp['vyos_user_pass'] = hexlify(os.urandom(16)).decode('utf-8') if conf.exists('community'): for name in conf.list_nodes('community'): @@ -357,9 +347,13 @@ def get_config(): # 'set service snmp script-extensions' # if conf.exists('script-extensions'): - for extname in conf.list_nodes('script-extensions extension-name'): - snmp['script_ext'][extname] = '/config/user-data/' + conf.return_value('script-extensions extension-name ' + extname + ' script') + for extname in conf.list_nodes('script-extensions extension-name'): + extension = { + 'name': extname, + 'script' : conf.return_value('script-extensions extension-name {} script'.format(extname)) + } + snmp['script_ext'].append(extension) ######################################################################### # ____ _ _ __ __ ____ _____ # @@ -375,15 +369,11 @@ def get_config(): else: snmp['v3_enabled'] = True - # # 'set service snmp v3 engineid' - # if conf.exists('v3 engineid'): snmp['v3_engineid'] = conf.return_value('v3 engineid') - # # 'set service snmp v3 group' - # if conf.exists('v3 group'): for group in conf.list_nodes('v3 group'): v3_group = { @@ -404,14 +394,11 @@ def get_config(): snmp['v3_groups'].append(v3_group) - # # 'set service snmp v3 trap-target' - # if conf.exists('v3 trap-target'): for trap in conf.list_nodes('v3 trap-target'): trap_cfg = { 'ipAddr': trap, - 'engineID': '', 'secName': '', 'authProtocol': 'md5', 'authPassword': '', @@ -425,11 +412,6 @@ def get_config(): 'secLevel': 'noAuthNoPriv' } - if conf.exists('v3 trap-target {0} engineid'.format(trap)): - # Set the context engineID used for SNMPv3 REQUEST messages scopedPdu. - # If not specified, this will default to the authoritative engineID. - trap_cfg['engineID'] = conf.return_value('v3 trap-target {0} engineid'.format(trap)) - if conf.exists('v3 trap-target {0} user'.format(trap)): # Set the securityName used for authenticated SNMPv3 messages. trap_cfg['secName'] = conf.return_value('v3 trap-target {0} user'.format(trap)) @@ -483,19 +465,7 @@ def get_config(): snmp['v3_traps'].append(trap_cfg) - # - # 'set service snmp v3 tsm' - # - if conf.exists('v3 tsm'): - if conf.exists('v3 tsm local-key'): - snmp['v3_tsm_key'] = conf.return_value('v3 tsm local-key') - - if conf.exists('v3 tsm port'): - snmp['v3_tsm_port'] = conf.return_value('v3 tsm port') - - # # 'set service snmp v3 user' - # if conf.exists('v3 user'): for user in conf.list_nodes('v3 user'): user_cfg = { @@ -504,19 +474,15 @@ def get_config(): 'authPassword': '', 'authProtocol': 'md5', 'authOID': 'none', - 'engineID': '', 'group': '', 'mode': 'ro', 'privMasterKey': '', 'privPassword': '', 'privOID': '', - 'privTsmKey': '', 'privProtocol': 'des' } - # # v3 user {0} auth - # if conf.exists('v3 user {0} auth encrypted-key'.format(user)): user_cfg['authMasterKey'] = conf.return_value('v3 user {0} auth encrypted-key'.format(user)) @@ -532,36 +498,21 @@ def get_config(): user_cfg['authProtocol'] = type user_cfg['authOID'] = OIDs[type] - # - # v3 user {0} engineid - # - if conf.exists('v3 user {0} engineid'.format(user)): - user_cfg['engineID'] = conf.return_value('v3 user {0} engineid'.format(user)) - - # # v3 user {0} group - # if conf.exists('v3 user {0} group'.format(user)): user_cfg['group'] = conf.return_value('v3 user {0} group'.format(user)) - # # v3 user {0} mode - # if conf.exists('v3 user {0} mode'.format(user)): user_cfg['mode'] = conf.return_value('v3 user {0} mode'.format(user)) - # # v3 user {0} privacy - # if conf.exists('v3 user {0} privacy encrypted-key'.format(user)): user_cfg['privMasterKey'] = conf.return_value('v3 user {0} privacy encrypted-key'.format(user)) if conf.exists('v3 user {0} privacy plaintext-key'.format(user)): user_cfg['privPassword'] = conf.return_value('v3 user {0} privacy plaintext-key'.format(user)) - if conf.exists('v3 user {0} privacy tsm-key'.format(user)): - user_cfg['privTsmKey'] = conf.return_value('v3 user {0} privacy tsm-key'.format(user)) - # load default value type = user_cfg['privProtocol'] if conf.exists('v3 user {0} privacy type'.format(user)): @@ -573,9 +524,7 @@ def get_config(): snmp['v3_users'].append(user_cfg) - # # 'set service snmp v3 view' - # if conf.exists('v3 view'): for view in conf.list_nodes('v3 view'): view_cfg = { @@ -599,23 +548,11 @@ def verify(snmp): ### check if the configured script actually exist under /config/user-data if snmp['script_ext']: - for ext in snmp['script_ext']: - if not os.path.isfile(snmp['script_ext'][ext]): - print ("WARNING: script: " + snmp['script_ext'][ext] + " doesn\'t exist") - else: - os.chmod(snmp['script_ext'][ext], 0o555) - - # bail out early if SNMP v3 is not configured - if not snmp['v3_enabled']: - return None - - tsmKeyPattern = re.compile('^[0-9A-F]{2}(:[0-9A-F]{2}){19}$', re.IGNORECASE) - - if snmp['v3_tsm_key']: - if not tsmKeyPattern.match(snmp['v3_tsm_key']): - if not os.path.isfile('/etc/snmp/tls/certs/' + snmp['v3_tsm_key']): - if not os.path.isfile('/config/snmp/tls/certs/' + snmp['v3_tsm_key']): - raise ConfigError('TSM key must be fingerprint or filename in "/config/snmp/tls/certs/" folder') + for ext in snmp['script_ext']: + if not os.path.isfile(ext['script']): + print ("WARNING: script: {} doesn't exist".format(ext['script'])) + else: + os.chmod(ext['script'], S_IRWXU|S_IXGRP|S_IXOTH) for listen in snmp['listen_address']: addr = listen[0] @@ -635,6 +572,10 @@ def verify(snmp): else: print('WARNING: SNMP listen address {0} not configured!'.format(addr)) + # bail out early if SNMP v3 is not configured + if not snmp['v3_enabled']: + return None + if 'v3_groups' in snmp.keys(): for group in snmp['v3_groups']: # @@ -683,13 +624,6 @@ def verify(snmp): if not 'privPassword' and 'privMasterKey' in trap.keys(): raise ConfigError('v3 trap: "user" must be specified') - if 'type' in trap.keys(): - if trap['type'] == 'trap' and trap['engineID'] == '': - raise ConfigError('must specify engineid if type is "trap"') - else: - raise ConfigError('"type" must be specified') - - if 'v3_users' in snmp.keys(): for user in snmp['v3_users']: # @@ -721,21 +655,9 @@ def verify(snmp): if user['privPassword'] == '' and user['privMasterKey'] == '': raise ConfigError('Must specify encrypted-key or plaintext-key for user privacy') - if user['privMasterKey'] and user['engineID'] == '': - raise ConfigError('Can not have "encrypted-key" without engineid') - - if user['authPassword'] == '' and user['authMasterKey'] == '' and user['privTsmKey'] == '': - raise ConfigError('Must specify auth or tsm-key for user auth') - if user['mode'] == '': raise ConfigError('Must specify user mode ro/rw') - if user['privTsmKey']: - if not tsmKeyPattern.match(snmp['v3_tsm_key']): - if not os.path.isfile('/etc/snmp/tls/certs/' + snmp['v3_tsm_key']): - if not os.path.isfile('/config/snmp/tls/certs/' + snmp['v3_tsm_key']): - raise ConfigError('User TSM key must be fingerprint or filename in "/config/snmp/tls/certs/" folder') - if 'v3_views' in snmp.keys(): for view in snmp['v3_views']: if not view['oids']: @@ -804,7 +726,7 @@ def apply(snmp): if os.path.exists(volatiledir) and os.path.isdir(volatiledir): files = os.listdir(volatiledir) for f in files: - shutil.move(volatiledir + '/' + f, nonvolatiledir) + move(volatiledir + '/' + f, nonvolatiledir) os.chmod(nonvolatiledir + '/' + f, stat.S_IWUSR | stat.S_IRUSR) os.rmdir(volatiledir) @@ -825,7 +747,7 @@ def apply(snmp): snmpReady = False while not snmpReady: while not os.path.exists(config_file_user): - time.sleep(1) + sleep(1) with open(config_file_user, 'r') as f: for line in f: @@ -858,7 +780,6 @@ def apply(snmp): # Now update the running configuration # # Currently when executing os.system() the environment does not have the vyos_libexec_dir variable set, see T685 - os.system('vyos_libexec_dir=/usr/libexec/vyos /opt/vyatta/sbin/my_set service snmp v3 user "{0}" engineid {1} > /dev/null'.format(cfg['user'], engineID)) os.system('vyos_libexec_dir=/usr/libexec/vyos /opt/vyatta/sbin/my_set service snmp v3 user "{0}" auth encrypted-key {1} > /dev/null'.format(cfg['user'], cfg['auth_pw'])) os.system('vyos_libexec_dir=/usr/libexec/vyos /opt/vyatta/sbin/my_set service snmp v3 user "{0}" privacy encrypted-key {1} > /dev/null'.format(cfg['user'], cfg['priv_pw'])) os.system('vyos_libexec_dir=/usr/libexec/vyos /opt/vyatta/sbin/my_delete service snmp v3 user "{0}" auth plaintext-key > /dev/null'.format(cfg['user'])) diff --git a/src/migration-scripts/snmp/0-to-1 b/src/migration-scripts/snmp/0-to-1 new file mode 100755 index 000000000..a836f7011 --- /dev/null +++ b/src/migration-scripts/snmp/0-to-1 @@ -0,0 +1,56 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2019 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 +from vyos.configtree import ConfigTree + +if (len(sys.argv) < 1): + 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) +config_base = ['service', 'snmp', 'v3'] + +if not config.exists(config_base): + # Nothing to do + sys.exit(0) +else: + # we no longer support a per trap target engine ID (https://phabricator.vyos.net/T818) + if config.exists(config_base + ['v3', 'trap-target']): + for target in config.list_nodes(config_base + ['v3', 'trap-target']): + config.delete(config_base + ['v3', 'trap-target', target, 'engineid']) + + # we no longer support a per user engine ID (https://phabricator.vyos.net/T818) + if config.exists(config_base + ['v3', 'user']): + for user in config.list_nodes(config_base + ['v3', 'user']): + config.delete(config_base + ['v3', 'user', user, 'engineid']) + + # we drop TSM support as there seem to be no users and this code is untested + # https://phabricator.vyos.net/T1769 + if config.exists(config_base + ['v3', 'tsm']): + config.delete(config_base + ['v3', 'tsm']) + + 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) diff --git a/src/op_mode/wireguard.py b/src/op_mode/wireguard.py index f6978554d..38c061cf4 100755 --- a/src/op_mode/wireguard.py +++ b/src/op_mode/wireguard.py @@ -23,8 +23,8 @@ import shutil import subprocess import syslog as sl import re -import time +from vyos.ifconfig import WireGuardIf from vyos import ConfigError from vyos.config import Config @@ -40,41 +40,6 @@ def check_kmod(): sl.syslog(sl.LOG_ERR, "modprobe wireguard failed") raise ConfigError("modprobe wireguard failed") - -def showint(interface): - output = subprocess.check_output(["wg", "show", interface], universal_newlines=True) - c = Config() - c.set_level("interfaces wireguard {}".format(interface)) - description = c.return_effective_value("description".format(interface)) - """ if the interface has a description, modify the output to include it """ - if (description): - output = re.sub(r"interface: {}".format(re.escape(interface)),"interface: {}\n Description: {}".format(interface,description),output) - - """ pull the last handshake times. Assume if the handshake was greater than 5 minutes, the tunnel is down """ - peer_timeouts = {} - last_hs_output = subprocess.check_output(["wg", "show", interface, "latest-handshakes"], universal_newlines=True) - for match in re.findall(r'(\S+)\s+(\d+)',last_hs_output): - peer_timeouts[match[0]] = match[1] - - """ modify all the peers, reformat to provide VyOS config provided peername, whether the tunnel is up/down """ - for peer in c.list_effective_nodes(' peer'): - pubkey = c.return_effective_value("peer {} pubkey".format(peer)) - status = "" - if int(peer_timeouts[pubkey]) > 0: - #Five minutes and the tunnel is still up - if (time.time() - int(peer_timeouts[pubkey]) < (60*5)): - status = "UP" - else: - status = "DOWN" - elif (peer_timeouts[pubkey] is None): - status = "DOWN" - elif (int(peer_timeouts[pubkey]) == 0): - status = "DOWN" - - output = re.sub(r"peer: {}".format(re.escape(pubkey)),"peer: {}\n Status: {}\n public key: {}".format(peer,status,pubkey),output) - - print(output) - def generate_keypair(pk, pub): """ generates a keypair which is stored in /config/auth/wireguard """ old_umask = os.umask(0o027) @@ -185,7 +150,8 @@ if __name__ == '__main__': if args.listkdir: list_key_dirs() if args.showinterface: - showint(args.showinterface) + intf = WireGuardIf(args.showinterface) + intf.op_show_interface() if args.delkdir: if args.location: del_key_dir(args.location) diff --git a/src/services/vyos-http-api-server b/src/services/vyos-http-api-server index afab9be70..571ec1258 100755 --- a/src/services/vyos-http-api-server +++ b/src/services/vyos-http-api-server @@ -27,12 +27,13 @@ import vyos.config import bottle +from functools import wraps + from vyos.configsession import ConfigSession, ConfigSessionError from vyos.config import VyOSError DEFAULT_CONFIG_FILE = '/etc/vyos/http-api.conf' - CFG_GROUP = 'vyattacfg' app = bottle.default_app() @@ -61,16 +62,23 @@ def success(data): resp = {"success": True, "data": data, "error": None} return json.dumps(resp) +def auth_required(f): + @wraps(f) + def decorated_function(*args, **kwargs): + key = bottle.request.forms.get("key") + api_keys = app.config['vyos_keys'] + id = check_auth(api_keys, key) + if not id: + return error(401, "Valid API key is required") + return f(*args, **kwargs) + + return decorated_function + @app.route('/configure', method='POST') +@auth_required def configure(): session = app.config['vyos_session'] config = app.config['vyos_config'] - api_keys = app.config['vyos_keys'] - - key = bottle.request.forms.get("key") - id = check_auth(api_keys, key) - if not id: - return error(401, "Valid API key is required") strict_field = bottle.request.forms.get("strict") if strict_field == "true": @@ -177,17 +185,11 @@ def configure(): return success(None) @app.route('/retrieve', method='POST') +@auth_required def get_value(): config = app.config['vyos_config'] session = app.config['vyos_session'] - api_keys = app.config['vyos_keys'] - - key = bottle.request.forms.get("key") - id = check_auth(api_keys, key) - if not id: - return error(401, "Valid API key is required") - command = bottle.request.forms.get("data") command = json.loads(command) @@ -205,11 +207,21 @@ def get_value(): elif op == 'exists': res = config.exists(path) elif op == 'showConfig': - config_format = 'raw' + config_format = 'json' if 'configFormat' in command: config_format = command['configFormat'] - res = session.show_config(command['path'], format=config_format) + res = session.show_config(path=command['path']) + if config_format == 'json': + config_tree = vyos.configtree.ConfigTree(res) + res = json.loads(config_tree.to_json()) + elif config_format == 'json_ast': + config_tree = vyos.configtree.ConfigTree(res) + res = json.loads(config_tree.to_json_ast()) + elif config_format == 'raw': + pass + else: + return error(400, "\"{0}\" is not a valid config format") else: return error(400, "\"{0}\" is not a valid operation".format(op)) except VyOSError as e: @@ -220,6 +232,82 @@ def get_value(): return success(res) +@app.route('/config-file', method='POST') +@auth_required +def config_file_op(): + config = app.config['vyos_config'] + session = app.config['vyos_session'] + + command = bottle.request.forms.get("data") + command = json.loads(command) + + try: + op = command['op'] + except KeyError: + return error(400, "Missing required field \"op\"") + + try: + if op == 'save': + try: + path = command['file'] + except KeyError: + path = '/config/config.boot' + res = session.save_config(path) + elif op == 'load': + try: + path = command['file'] + except KeyError: + return error(400, "Missing required field \"file\"") + res = session.load_config(path) + res = session.commit() + else: + return error(400, "\"{0}\" is not a valid operation".format(op)) + except VyOSError as e: + return error(400, str(e)) + except Exception as e: + print(traceback.format_exc(), file=sys.stderr) + return error(500, "An internal error occured. Check the logs for details.") + + return success(res) + +@app.route('/image', method='POST') +@auth_required +def config_file_op(): + config = app.config['vyos_config'] + session = app.config['vyos_session'] + + command = bottle.request.forms.get("data") + command = json.loads(command) + + try: + op = command['op'] + except KeyError: + return error(400, "Missing required field \"op\"") + + try: + if op == 'add': + try: + url = command['url'] + except KeyError: + return error(400, "Missing required field \"url\"") + res = session.install_image(url) + elif op == 'delete': + try: + name = command['name'] + except KeyError: + return error(400, "Missing required field \"name\"") + res = session.remove_image(name) + else: + return error(400, "\"{0}\" is not a valid operation".format(op)) + except VyOSError as e: + return error(400, str(e)) + except Exception as e: + print(traceback.format_exc(), file=sys.stderr) + return error(500, "An internal error occured. Check the logs for details.") + + return success(res) + + if __name__ == '__main__': # systemd's user and group options don't work, do it by hand here, # else no one else will be able to commit diff --git a/src/utils/vyos-config-to-json b/src/utils/vyos-config-to-json new file mode 100755 index 000000000..e03fd6a59 --- /dev/null +++ b/src/utils/vyos-config-to-json @@ -0,0 +1,40 @@ +#!/usr/bin/python3 + +import sys +import json + +from signal import signal, SIGPIPE, SIG_DFL +from vyos.configtree import ConfigTree + +signal(SIGPIPE,SIG_DFL) + +config_string = None +if (len(sys.argv) == 1): + # If no argument given, act as a pipe + config_string = sys.stdin.read() +else: + file_name = sys.argv[1] + try: + with open(file_name, 'r') as f: + config_string = f.read() + except OSError as e: + print("Could not read config file {0}: {1}".format(file_name, e), file=sys.stderr) + +# This script is usually called with the output of "cli-shell-api showCfg", which does not +# escape backslashes. "ConfigTree()" expects escaped backslashes when parsing a config +# string (and also prints them itself). Therefore this script would fail. +# Manually escape backslashes here to handle backslashes in any configuration strings +# properly. The alternative would be to modify the output of "cli-shell-api showCfg", +# but that may be break other things who rely on that specific output. +config_string = config_string.replace("\\", "\\\\") + +try: + config = ConfigTree(config_string) + json_str = config.to_json() + # Pretty print + json_str = json.dumps(json.loads(json_str), indent=4, sort_keys=True) +except ValueError as e: + print("Could not parse the config file: {0}".format(e), file=sys.stderr) + sys.exit(1) + +print(json_str) |