Beispiel #1
0
def parse_key(kind, key):
    try:
        key = b64decode(key)

        if type(key) != bytes:
            raise ValueError('{} has to be bytes'.format(kind))
        elif len(key) != wgctl.util.netlink.WG_KEY_LEN:
            raise ValueError('{} length has to be {}'.format(
                kind, wgctl.util.netlink.WG_KEY_LEN))

        return key
    except Exception as e:
        fatal('could not read {}: {}'.format(kind, e))
Beispiel #2
0
def get_config(instance):
  config_path = '/etc/wireguard/{}.yml'.format(instance)
  if path.isfile(instance):
    config_path = instance
    instance = Path(instance).resolve().stem
  
  try:
    with open(config_path) as stream:
      config = yaml.load(stream)
  except FileNotFoundError:
    fatal('could not read file: {}'.format(config_path))
  except yaml.YAMLError:
    fatal('could not parse configuration')

  check_config(config)

  return instance, config
Beispiel #3
0
def down(context, instance):
    instance, config = get_config(instance)

    if not WireGuard().device_exists(ifname=instance):
        fatal('tunnel interface is already down.')

    if 'pre_down' in config['interface']:
        from wgctl.util.exec import run

        info('running pre-down commands')

        for cmd in config['interface']['pre_down']:
            run(context, cmd)

    port = config['interface']['listen_port']

    ip = IPRoute()
    ip.link('delete', ifname=instance)

    nets = [peer.get('allowed_ips', []) for peer in config['peers']]
    nets = [item for sublist in nets for item in sublist]

    if '0.0.0.0/0' in nets:
        try:
            ip.rule('delete',
                    table=254,
                    FRA_SUPPRESS_PREFIXLEN=0,
                    priority=18000)
            ip.rule('delete',
                    fwmark=port,
                    fwmask=0,
                    table=port,
                    priority=20000)
        except:
            pass

    ok('tunnel brought down successfully')
Beispiel #4
0
def fail(message=None):
  if message is None:
    fatal('could not parse configuration')
  else:
    fatal('could not parse configuration: {}'.format(message))
Beispiel #5
0
def info(context, instance):
    wg = WireGuard()

    if not wg.device_exists(instance):
        fatal('device does not exist')

    instance, config = get_config(instance)
    interface = WireGuard().get_device_dict(ifname=instance)
    interface = interface[instance]

    fwmark = None
    if interface['fwmark'] > 0:
        fwmark = interface['fwmark']

    attrs = [
        attr('interface:', instance),
        attr('public key:', format_key(interface['public_key'])),
        attr('listening port:', interface['listen_port']),
        attr('fwmark:', fwmark)
    ]

    output = print_tunnel(config.get('description', '<no tunnel description>'))
    output += ''.join([line for line in attrs if line is not None])

    for peer in interface.get('peers', []):
        output += '\n'

        key = format_key(peer['public_key'])
        peerconf = [
            peerconf for peerconf in config['peers']
            if peerconf['public_key'] == key
        ]

        description = '<no peer description>'
        if len(peerconf) > 0 and 'description' in peerconf[0]:
            description = peerconf[0]['description']

        endpoint = None
        if peer.get('endpoint'):
            endpoint = '{}:{}'.format(peer['endpoint'][0], peer['endpoint'][1])

        allowed_ips = None
        if peer.get('allowedips'):
            allowed_ips = ', '.join(peer['allowedips'])

        if peer['last_handshake_time'].year == 1970:
            handshake = None
        else:
            handshake = timeago.format(peer['last_handshake_time'],
                                       datetime.now())

        if peer['persistent_keepalive_interval'] > 0:
            keepalive = f'Every {peer["persistent_keepalive_interval"]}s'
        else:
            keepalive = None

        rx_bytes = peer['rx_bytes']
        tx_bytes = peer['tx_bytes']
        traffic = rx_bytes + tx_bytes

        attrs = [
            attr('public key:', key, pad=4),
            attr('endpoint:', endpoint, pad=4),
            attr('allowed ips:', allowed_ips, pad=4),
            attr('preshared key?',
                 not all(c == '0' for c in peer['preshared_key'].hex()),
                 pad=4),
            attr('last handshake:', handshake, pad=4),
            attr('persistent keepalive:', keepalive, pad=4),
            attr('transfer:',
                 f'↓ {rx_bytes} B ↑ {tx_bytes} B',
                 rx_bytes + tx_bytes > 0,
                 pad=4)
        ]

        output += print_peer(description)
        output += ''.join([line for line in attrs if line is not None])

    print(output)
Beispiel #6
0
def up(context, instance):
    instance, config = get_config(instance)
    wg = WireGuard()

    if wg.device_exists(ifname=instance):
        fatal('tunnel interface is already up.')

    try:
        with open(config['interface']['private_key']) as key:
            config['interface']['private_key'] = key.readline().strip()
    except Exception:
        fatal('could not read private key file')

    port = config['interface']['listen_port']
    fwmark = config['interface'].get('fwmark')

    address, cidr = None, None
    if config['interface'].get('address') is not None:
        address, cidr = parse_net(config['interface']['address'])

    ip = IPRoute()
    try:
        ip.link('add', ifname=instance, kind='wireguard')
    except Exception as e:
        fatal('could not create device: {}'.format(e))

    index = ip.link_lookup(ifname=instance)[0]
    ip.link('set', index=index, state='up')

    if address is not None and cidr is not None:
        ip.addr('add', index=index, address=address, prefixlen=cidr)

    WireGuard().set_device(ifindex=index, config=config)

    for peer in config['peers']:
        if peer.get('allowed_ips'):
            for aip in peer['allowed_ips']:
                try:
                    if aip == '0.0.0.0/0':
                        ip.route('add', dst='0.0.0.0/0', oif=index, table=port)
                        ip.rule('add',
                                table=254,
                                FRA_SUPPRESS_PREFIXLEN=0,
                                priority=18000)
                        ip.rule('add',
                                fwmark=port,
                                fwmask=0,
                                table=port,
                                priority=20000)
                    else:
                        ip.route('add', dst=aip, oif=index)
                except Exception as e:
                    fatal('could not create route: {}'.format(e))

    if 'post_up' in config['interface']:
        from wgctl.util.exec import run

        info('running post-up commands')

        for cmd in config['interface']['post_up']:
            run(context, cmd)

    ok('tunnel tunnel set up successfully')
Beispiel #7
0
  def set_device(self, ifname=None, ifindex=None, config={}):
    msg = wgmsg()
    msg['cmd'] = WG_CMD_SET_DEVICE
    msg['version'] = WG_GENL_VERSION

    if ifname != None:
      msg['attrs'].append(['WGDEVICE_A_IFNAME', ifname])
    elif ifindex != None:
      msg['attrs'].append(['WGDEVICE_A_IFINDEX', ifindex])
    else:
      raise ValueError('ifname or ifindex are unset')

    interface = config.get('interface')
    if interface is None:
      fatal('configuration is missing "interface" key')
    
    port = interface.get('listen_port')
    pkey = interface.get('private_key')
    if not all([port, pkey]):
      fatal('interface must have at least a "private_key" and "listen_port"')

    fwmark = interface.get('fwmark')
    peers = config.get('peers')

    if port != None:
      msg['attrs'].append(['WGDEVICE_A_LISTEN_PORT', port])
    if fwmark != None:
      msg['attrs'].append(['WGDEVICE_A_FWMARK', fwmark])
    if pkey != None:
      msg['attrs'].append(['WGDEVICE_A_PRIVATE_KEY', parse_key('private_key', pkey)])
    
    if peers is not None:
      wgpeers = []
      
      for peer in peers:
        wgpeer = wgmsg.wgpeer()

        if 'preshared_key' in peer:
          if len(peer['preshared_key']) != 64:
            fatal('pre-shared key must be 64 hexadecimal characters')
          
          wgpeer['attrs'].append(['WGPEER_A_PRESHARED_KEY', bytearray.fromhex(peer['preshared_key'])])

        if 'persistent_keepalive_interval' in peer:
          try:
            wgpeer['attrs'].append(['WGPEER_A_PERSISTENT_KEEPALIVE_INTERVAL', int(peer['persistent_keepalive_interval'])])
          except ValueError:
            pass

        if 'endpoint' in peer:
          try:
            host, port = peer['endpoint'].rsplit(':')
            port = int(port)
            address = \
              struct.pack('H', socket.AF_INET) + \
              struct.pack('!H12s', port, socket.inet_aton(host))

            addr = wgmsg.wgpeer.sockaddr()
            addr.setvalue(address)

            wgpeer['attrs'].append(['WGPEER_A_ENDPOINT', addr])
          except ValueError:
            raise ValueError('peer endpoint is malformed')
        
        if 'public_key' in peer:
          wgpeer['attrs'].append(['WGPEER_A_PUBLIC_KEY', parse_key('public_key', peer['public_key'])])

        if 'allowed_ips' in peer:
          wgips = []
          
          for ip in peer['allowed_ips']:
            net, cidr = ip.rsplit('/')
            cidr = int(cidr)

            wgip = wgmsg.wgpeer.wgallowedip()
            wgip['attrs'].append(['WGALLOWEDIP_A_FAMILY', AF_INET])
            wgip['attrs'].append(['WGALLOWEDIP_A_IPADDR', net])
            wgip['attrs'].append(['WGALLOWEDIP_A_CIDR_MASK', cidr])

            wgips.append(wgip)
          
          wgpeer['attrs'].append(['WGPEER_A_ALLOWEDIPS', wgips])

        wgpeers.append(wgpeer)
      
      msg['attrs'].append(['WGDEVICE_A_PEERS', wgpeers])

    return self.put(msg, msg_type=self.prid, msg_flags=NLM_F_REQUEST)
Beispiel #8
0
from os import getuid
from wgctl.util.cli import fatal
from base64 import b64encode

from wgctl.commands import \
  version, \
  conn, \
  status

CONTEXT_SETTINGS = dict(help_option_names=['-h', '--help'])


@click.group(context_settings=CONTEXT_SETTINGS)
@click.pass_context
@click.option('--verbose', '-v', is_flag=True, default=False)
def main(context, verbose):
    context.obj = {'verbose': verbose}

    pass


if getuid() > 0:
    fatal('this should be run as root')

main.add_command(version.version)
main.add_command(conn.up)
main.add_command(conn.down)
main.add_command(conn.downup)
main.add_command(status.status)
main.add_command(status.info)