Wireguard tools for Nodejs
This lib includes a class and set of helper functions for working with WireGuard config files in javascript/typescript.
100% Typescript!
Check out the docs with
https://guardline-vpn.github.io/wireguard-tools/
To use
npm i wireguard-tools
or
yarn add wireguard-tools
Basic config
import path from 'path'
import { WgConfig } from 'wireguard-tools'
const filePath = path.join(__dirname, '/configs', '/guardline-server.conf')
const config1 = new WgConfig({
wgInterface: { address: ['10.10.1.1'] },
filePath
})
await config1.generateKeys()
Generate Keys
import path from 'path'
import { WgConfig } from 'wireguard-tools'
const filePath = path.join(__dirname, '/configs', '/guardline-server.conf')
const config1 = new WgConfig({
wgInterface: {
address: ['10.10.1.1']
},
filePath
})
const { publicKey, preSharedKey, privateKey } = await config1.generateKeys({ preSharedKey: true })
Write to disk
import path from 'path'
import { WgConfig } from 'wireguard-tools'
const filePath = path.join(__dirname, '/configs', '/guardline-server.conf')
const config1 = new WgConfig({
wgInterface: {
address: ['10.10.1.1']
},
filePath
})
await config1.generateKeys()
await config1.writeToFile()
Parse config from disk
import path from 'path'
import { WgConfig, getConfigObjectFromFile } from 'wireguard-tools'
const filePath = path.join(__dirname, '/configs', '/guardline-server.conf')
const thatConfigFromFile = await getConfigObjectFromFile({ filePath })
const config1 = new WgConfig({
...thatConfigFromFile,
filePath
})
// OR:
const config2 = new WgConfig({ filePath })
await config2.parseFile()
// OR:
const config3 = new WgConfig()
await config3.parseFile(filePath)
// Public key will not be available because it's not saved in the WireGuard config,
// so you need to generate keys again (it will use the existing private key)
await config1.generateKeys()
Bring up /bring down a WgConfig as a WireGuard interface
import path from 'path'
import { WgConfig } from 'wireguard-tools'
const filePath = path.join(__dirname, '/configs', '/guardline-server.conf')
const config1 = new WgConfig({
wgInterface: {
address: ['10.10.1.1']
},
filePath
})
await config1.generateKeys()
await config1.writeToFile()
// bring up
await config1.up()
// bring down
await config1.down()
To change a WgConfig while up, you need to restart
import path from 'path'
import { WgConfig } from 'wireguard-tools'
const filePath = path.join(__dirname, '/configs', '/guardline-server.conf')
const config1 = new WgConfig()
// Assuming the WireGuard config file is already on disk...
await config1.parseFile(filePath)
await config1.generateKeys()
// bring up
await config1.up()
// create new key pair
await config1.generateKeys({ overwrite: true })
// change the name
config1.wgInterface.name = 'new-name'
// write the file to save
await config1.writeToFile()
// restart for the changes to take effect
await config1.restart()
Or use the save() shorcut method to write and restart
Note, using this method will start the WireGuard interface if it's down unless { noUp: true }
is passed in.
import path from 'path'
import { WgConfig } from 'wireguard-tools'
const filePath = path.join(__dirname, '/configs', '/guardline-server.conf')
const config1 = new WgConfig()
// Assuming the WireGuard config file is already on disk...
await config1.parseFile(filePath)
await config1.generateKeys()
// bring up
await config1.up()
// create new key pair
await config1.generateKeys({ overwrite: true })
// change the name
config1.wgInterface.name = 'new-name'
// write the file and restart
await config1.save()
Generate and add peers from and to configs
import path from 'path'
import { WgConfig } from 'wireguard-tools'
const filePath = path.join(__dirname, '/configs', '/guardline-server.conf')
const filePath2 = path.join(__dirname, '/configs', '/guardline-client.conf')
const server = new WgConfig({
wgInterface: { address: ['10.10.1.1'] },
filePath
})
const client = new WgConfig({
wgInterface: { address: ['10.10.1.2'] },
filePath: filePath2
})
// gen keys
await Promise.all([
server.generateKeys({ preSharedKey: true }),
client.generateKeys({ preSharedKey: true })
])
// make a peer from server
const serverAsPeer = server.createPeer({
allowedIps: ['10.1.1.1/32'],
preSharedKey: server.preSharedKey
})
// add that as a peer to client
client.addPeer(serverAsPeer)
// make a peer from client and add it to server
server.addPeer(client.createPeer({
allowedIps: ['10.10.1.1/32'],
preSharedKey: client.preSharedKey
}))
Or use the createPeerPairs tool to do this with ease
import path from 'path'
import { WgConfig, createPeerPairs } from 'wireguard-tools'
// make a load of configs
let configs: WgConfig[] = []
for (let i = 1; i <= 10; i++) {
configs.push(new WgConfig({
wgInterface: {
address: [`10.10.1.${i}`],
name: `Client-${i}`
},
filePath: path.join(__dirname, '/configs', `/guardline-${i}.conf`)
}))
}
// get their key pairs
await Promise.all(configs.map(x => x.generateKeys()))
// add them all as peers of each other
createPeerPairs(configs.map(x => {
return {
config: x,
peerSettings: {
allowedIps: ['10.10.1.1/32']
}
}
}))
// write them all to disk
await Promise.all(configs.map(x => x.writeToFile()))
Or more advanced
import path from 'path'
import { WgConfig, createPeerPairs } from 'wireguard-tools'
// make a load of configs
let configs: WgConfig[] = []
for (let i = 1; i <= 10; i++) {
configs.push(new WgConfig({
wgInterface: {
address: [`10.10.1.${i}`],
privateKey: '',
name: `Client-${i}`
},
filePath: path.join(__dirname, '/configs', `/guardline-${i}.conf`)
}))
}
// get their key pairs
await Promise.all(configs.map(x => x.generateKeys()))
// add them all as peers of each other
createPeerPairs(configs.map(x => {
return {
config: x,
peerSettings: ({ thisConfig, peerConfig }) => {
const peerAddress = peerConfig.wgInterface.address
const peerPresharedKey = peerConfig.preSharedKey
return {
allowedIps: peerAddress,
preSharedKey: peerPresharedKey,
name: peerConfig.wgInterface.name,
persistentKeepalive: thisConfig.wgInterface.address.includes('10.10.1.1') ? 25 : undefined
}
}
}
}))
// write them all to disk
await Promise.all(configs.map(x => x.writeToFile()))
Random helpers
import path from 'path'
import {
checkWgIsInstalled,
generateKeyPair,
generateConfigString,
parseConfigString,
getConfigStringFromFile,
getConfigObjectFromFile,
} from ''
// check WireGuard is installed on the system and print version
const version = await checkWgIsInstalled()
console.log(version) // wireguard-tools v1.0.20200827 - https://git.zx2c4.com/wireguard-tools/
// generate a WG key pair (needs wg installed on system)
const { publicKey, privateKey, preSharedKey } = await generateKeyPair({ preSharedKey: true })
console.log({ publicKey, privateKey, preSharedKey })
/**
* {
* publicKey: '257CQncfArO8QLIcc23Hhyq2IvnBszCl8XUU9TA42Q4=',
* privateKey: '6AgToMLuTa3lQMIMwIBVkhwSM0PVLCZD1FpqU5y0l2Q=',
* preSharedKey: 'NlqKE2Ja7AAQhDZpevUwi7pjlnU7HZgcPLI0F/gVPfs='
* }
*/
// Generate a string version of the WgConfig suitable for saving to a Wireguard Config file
const configString = generateConfigString({
wgInterface: {
name: 'Client 1',
address: ['10.10.1.1'],
privateKey: '6AgToMLuTa3lQMIMwIBVkhwSM0PVLCZD1FpqU5y0l2Q'
},
peers: [
{
allowedIps: ['10.10.1.1/32'],
publicKey: 'FoSq0MiHw9nuHMiJcD2vPCzQScmn1Hu0ctfKfSfhp3s='
}
]
})
console.log(configString)
/**
* [Interface]
* # Name = Client 1
* Address = 10.10.1.1
* PrivateKey = 6AgToMLuTa3lQMIMwIBVkhwSM0PVLCZD1FpqU5y0l2Q
*
* [Peer]
* PublicKey = FoSq0MiHw9nuHMiJcD2vPCzQScmn1Hu0ctfKfSfhp3s=
* AllowedIPs = 10.10.1.1/32
*/
// Parse a config object from a WireGuard config file string
const configObj = parseConfigString(configString)
console.log(configObj)
/**
* {
* wgInterface: {
* address: [ '10.10.1.1' ],
* privateKey: '6AgToMLuTa3lQMIMwIBVkhwSM0PVLCZD1FpqU5y0l2Q',
* name: 'Client 1'
* },
* peers: [
* {
* allowedIps: [Array],
* publicKey: 'FoSq0MiHw9nuHMiJcD2vPCzQScmn1Hu0ctfKfSfhp3s='
* }
* ]
* }
*/
// Get a raw wireguard config string from a file
const confStringFromFile = getConfigStringFromFile({
filePath: path.join(__dirname, '/configs', '/wg0.conf')
})
console.log(confStringFromFile)
/**
* [Interface]
* # Name = Client 1
* Address = 10.10.1.1
* PrivateKey = 6AgToMLuTa3lQMIMwIBVkhwSM0PVLCZD1FpqU5y0l2Q
*
* [Peer]
* PublicKey = FoSq0MiHw9nuHMiJcD2vPCzQScmn1Hu0ctfKfSfhp3s=
* AllowedIPs = 10.10.1.1/32
*/
// Get a parsed WgConfigObject from a wireguard config file
const confObjFromFile = getConfigObjectFromFile({
filePath: path.join(__dirname, '/configs', '/wg0.conf')
})
console.log(confObjFromFile)
/**
* {
* wgInterface: {
* address: [ '10.10.1.1' ],
* privateKey: '6AgToMLuTa3lQMIMwIBVkhwSM0PVLCZD1FpqU5y0l2Q',
* name: 'Client 1'
* },
* peers: [
* {
* allowedIps: [Array],
* publicKey: 'FoSq0MiHw9nuHMiJcD2vPCzQScmn1Hu0ctfKfSfhp3s='
* }
* ]
* }
*/
Extensive example
Here is one extensive example of usage that should give you an idea of what to do:
import path from 'path'
import { WgConfig, getConfigObjectFromFile, createPeerPairs, checkWgIsInstalled } from 'wireguard-tools'
const filePath = path.join(__dirname, '/configs', '/guardline-server.conf')
const test = async () => {
try {
// make a new config
const config1 = new WgConfig({
wgInterface: {
address: ['10.10.1.1']
},
filePath
})
// give the config a name
config1.wgInterface.name = 'Guardline Server'
// update some other properties
config1.wgInterface.postUp = ['echo "Guardline Server Up!"']
config1.wgInterface.listenPort = 5280
// make a keypair for the config and a pre-shared key
const keypair = await config1.generateKeys({ preSharedKey: true })
// these keys will be saved to the config object
console.log(keypair.publicKey === config1.publicKey) // true
console.log(keypair.preSharedKey === config1.preSharedKey) // true
console.log(keypair.privateKey === config1.wgInterface.privateKey) // true
// write the config to disk
await config1.writeToFile()
// read that file into another config object
const thatConfigFromFile = await getConfigObjectFromFile({ filePath })
const config2FilePath = path.join(__dirname, '/configs', '/guardline-server-2.conf')
const config2 = new WgConfig({
...thatConfigFromFile,
filePath: config2FilePath
})
// both configs private key will be the same because config2 has been parsed
// from the file written by config
console.log(config1.wgInterface.privateKey === config2.wgInterface.privateKey)
// however, config2 doesn't have a public key becuase WireGuard doesn't save the
// the public key in the config file.
// To get the public key, you'll need to run generateKeys on config2
// it'll keep it's private key and derive a public key from it
await config2.generateKeys()
// so now the two public keys will be the same
console.log(config1.publicKey === config2.publicKey) // true
// you can generate a new keypair by passing an arg:
config2.generateKeys({ overwrite: true })
// so now their public/private keys are different
console.log(config1.publicKey === config2.publicKey) // false
// you can create a peer object from a WgConfig like this
const config2AsPeer = config2.createPeer({
allowedIps: ['10.10.1.1/32']
})
// you can add a peer to a config like this:
config1.addPeer(config2AsPeer)
// and remove a peer like this
config1.removePeer(config2.publicKey)
// or you make two WgConfigs peers of each other like this:
createPeerPairs([
{
config: config1,
// The peer settings to apply when adding this config as a peer
peerSettings: {
allowedIps: ['10.10.1.1'],
preSharedKey: config1.preSharedKey
}
},
{
config: config2,
peerSettings: {
allowedIps: ['10.10.1.2']
}
}
])
// That will end up with config1 having config2 as a peer
// and config2 having config1 as a peer
console.log(config1.getPeer(config2.publicKey)) // logs the peer
console.log(config2.getPeer(config1.publicKey)) // logs the peer
// Check that the system has wireguard installed and log the version like this
// (will throw an error if not installed)
const wgVersion = await checkWgIsInstalled()
console.log(wgVersion)
// if wireguard is installed, you can bring up your config like this:
// (make sure it's been written to file first!)
await config1.writeToFile()
await config1.up() // Wireguard interface is up
// you can change something about the interface while it's up
config1.wgInterface.dns = ['1.1.1.1']
config1.writeToFile()
// but make sure you restart the interface for your changes to take effect
await config1.restart()
// and finally, when you're done, take down the interface like this
await config1.down()
// Thanks for reading!
} catch (e) {
console.error(e)
}
}
test()
To develop
- Clone this repo
- Run
yarn
- Run
yarn watch
TODO
Write more docs as always