diff options
Diffstat (limited to 'src')
| -rwxr-xr-x | src/conf_mode/dynamic_dns.py | 51 | ||||
| -rwxr-xr-x | src/conf_mode/https.py | 2 | ||||
| -rwxr-xr-x | src/op_mode/wireguard.py | 40 | ||||
| -rwxr-xr-x | src/services/vyos-http-api-server | 106 | 
4 files changed, 128 insertions, 71 deletions
| diff --git a/src/conf_mode/dynamic_dns.py b/src/conf_mode/dynamic_dns.py index ff3c1f825..c4a95f5f1 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,12 @@ 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 -%}  {{ host }}  {% endfor %} @@ -91,6 +90,8 @@ default_service_protocol = {  default_config_data = {      'interfaces': [], +    'cache_file': cache_file, +    'pid_file': pid_file  }  def get_config(): @@ -237,8 +238,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 +254,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..233c815bc 100755 --- a/src/conf_mode/https.py +++ b/src/conf_mode/https.py @@ -67,7 +67,7 @@ server {  {% endif %}          # proxy settings for HTTP API, if enabled; 503, if not -        location ~ /(retrieve|configure) { +        location ~ /(retrieve|configure|config-file|image) {  {% if api %}                  proxy_pass http://localhost:{{ api.port }};                  proxy_buffering off; diff --git a/src/op_mode/wireguard.py b/src/op_mode/wireguard.py index f6978554d..6860aa3ea 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.interface import Interface  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 = Interface(args.showinterface) +            intf.print_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..04c44c2be 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) @@ -220,6 +222,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 | 
