the-wireguard-effect
Version:
Cross platform wireguard api client for nodejs built on wireguard-go with effect-ts
313 lines (311 loc) • 15.3 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.toConfigs = exports.nodesByIp = exports.makeEmptyContext = exports.ipForNode = exports.generateVpnTunneledAccess = exports.generateStarConnections = exports.generateServerToServerAccess = exports.generateServerHubAndSpokeAccess = exports.generateRemoteTunneledAccess = exports.generateRemoteAccessToServer = exports.generateRemoteAccessToLan = exports.generateLanToLanAccess = exports.generateLanHubAndSpokeAccess = exports.generateKeys = exports.generateHubAndSpokeConnections = exports.computeAllowedIPsFromConnections = exports.addPreshareKeys = exports.addConnection = exports.addAllowedIPs = void 0;
var Array = _interopRequireWildcard(require("effect/Array"));
var Effect = _interopRequireWildcard(require("effect/Effect"));
var Function = _interopRequireWildcard(require("effect/Function"));
var Match = _interopRequireWildcard(require("effect/Match"));
var Option = _interopRequireWildcard(require("effect/Option"));
var Predicate = _interopRequireWildcard(require("effect/Predicate"));
var Record = _interopRequireWildcard(require("effect/Record"));
var Schema = _interopRequireWildcard(require("effect/Schema"));
var Tuple = _interopRequireWildcard(require("effect/Tuple"));
var assert = _interopRequireWildcard(require("node:assert"));
var InternetSchemas = _interopRequireWildcard(require("./InternetSchemas.js"));
var WireguardConfig = _interopRequireWildcard(require("./WireguardConfig.js"));
var WireguardErrors = _interopRequireWildcard(require("./WireguardErrors.js"));
var WireguardKey = _interopRequireWildcard(require("./WireguardKey.js"));
var internalInternalSchemas = _interopRequireWildcard(require("./internal/internetSchemas.js"));
function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
/**
* Tools to help generate wireguard configs for common situations.
*
* @since 1.0.0
*/
/** @internal */
const ipForNode = exports.ipForNode = /*#__PURE__*/Function.pipe(/*#__PURE__*/Match.type(), /*#__PURE__*/Match.when(/*#__PURE__*/Schema.is(InternetSchemas.IPv4), ({
ip
}) => ip), /*#__PURE__*/Match.when(/*#__PURE__*/Schema.is(InternetSchemas.IPv6), ({
ip
}) => ip), /*#__PURE__*/Match.whenOr(/*#__PURE__*/Schema.is(InternetSchemas.IPv4SetupData), /*#__PURE__*/Schema.is(InternetSchemas.IPv6SetupData), /*#__PURE__*/Schema.is(InternetSchemas.HostnameIPv4SetupData), /*#__PURE__*/Schema.is(InternetSchemas.HostnameIPv6SetupData), ([_, {
ip
}]) => ip), Match.exhaustive);
/** @internal */
const nodesByIp = nodes => Function.pipe(nodes, Array.map(node => Tuple.make(ipForNode(node), node)), Record.fromEntries);
/** @internal */
exports.nodesByIp = nodesByIp;
const makeEmptyContext = nodes => Record.map(nodesByIp(nodes), Function.constUndefined);
/**
* Generates private+public keys for all nodes in the network.
*
* @since 1.0.0
* @category Key Transformers
*/
exports.makeEmptyContext = makeEmptyContext;
const generateKeys = nodesLayer => ({
nodes: nodesLayer.nodes,
wireguardNetworkCidr: nodesLayer.wireguardNetworkCidr,
keys: Function.pipe(nodesLayer.nodes, makeEmptyContext, Record.map(WireguardKey.generateKeyPair))
});
/**
* Generates preshare keys for all nodes in the network.
*
* @since 1.0.0
* @category Key Transformers
*/
exports.generateKeys = generateKeys;
const addPreshareKeys = keysLayer => ({
nodes: keysLayer.nodes,
wireguardNetworkCidr: keysLayer.wireguardNetworkCidr,
keys: Record.map(keysLayer.keys, (keys, node) => node === ipForNode(keysLayer.nodes[0]) ? keys : {
...keys,
preshareKey: WireguardKey.generatePreshareKey()
})
});
/**
* Generates connections in a star pattern for all nodes in the network.
*
* @since 1.0.0
* @category Connection Transformers
*/
exports.addPreshareKeys = addPreshareKeys;
const generateStarConnections = keysLayer => {
const allNodeIps = Array.map(keysLayer.nodes, ipForNode);
const connections = Function.pipe(keysLayer.nodes, makeEmptyContext, Record.map((_, currentNode) => {
const neighbors = Array.filter(allNodeIps, ip => ip !== currentNode);
assert.ok(Array.isNonEmptyReadonlyArray(neighbors), "There must be at least one neighbor");
return neighbors;
}));
return {
connections,
nodes: keysLayer.nodes,
keys: keysLayer.keys,
wireguardNetworkCidr: keysLayer.wireguardNetworkCidr
};
};
/**
* Generates connections in a hub and spoke pattern for all nodes in the
* network.
*
* @since 1.0.0
* @category Connection Transformers
*/
exports.generateStarConnections = generateStarConnections;
const generateHubAndSpokeConnections = keysLayer => ({
nodes: keysLayer.nodes,
keys: keysLayer.keys,
wireguardNetworkCidr: keysLayer.wireguardNetworkCidr,
connections: Function.pipe(keysLayer.nodes, makeEmptyContext, Record.map((_, node) => {
if (node === ipForNode(keysLayer.nodes[0])) {
return internalInternalSchemas.tail(keysLayer.nodes);
} else {
return Array.of(keysLayer.nodes[0]);
}
}), Record.map(Array.map(ipForNode)))
});
/**
* Adds a direct connection between two nodes in the network.
*
* @since 1.0.0
* @category Connection Transformers
*/
exports.generateHubAndSpokeConnections = generateHubAndSpokeConnections;
const addConnection = exports.addConnection = /*#__PURE__*/Function.dual(3, (connectionsLayer, from, to) => {
const oldConnections = Record.get(connectionsLayer.connections, from);
const newConnections = Function.pipe(oldConnections, Option.map(connections => Array.append(connections, to)), Option.getOrElse(() => Array.of(to)));
return {
nodes: connectionsLayer.nodes,
keys: connectionsLayer.keys,
wireguardNetworkCidr: connectionsLayer.wireguardNetworkCidr,
connections: Record.set(connectionsLayer.connections, from, newConnections)
};
});
/**
* Generates the allowed IPs for each node in the network based on the
* connections is has to the other nodes.
*
* @since 1.0.0
* @category AllowedIPs Transformers
*/
const computeAllowedIPsFromConnections = connectionsLayer => ({
nodes: connectionsLayer.nodes,
keys: connectionsLayer.keys,
connections: connectionsLayer.connections,
wireguardNetworkCidr: connectionsLayer.wireguardNetworkCidr,
allowedIPs: Record.map(connectionsLayer.connections, Array.map(ip => ({
from: ip,
block: `${ip}/32`
})))
});
/**
* Adds an allowed IP to a node in the network.
*
* @since 1.0.0
* @category AllowedIPs Transformers
*/
exports.computeAllowedIPsFromConnections = computeAllowedIPsFromConnections;
const addAllowedIPs = exports.addAllowedIPs = /*#__PURE__*/Function.dual(4, (allowedIPsLayer, nodeToIp, nodeFromIp, cidrs) => {
const oldCidrs = Record.get(allowedIPsLayer.allowedIPs, nodeToIp);
const cidrsEncoded = Array.map(cidrs, cidr => Function.pipe(Match.value(cidr), Match.when(Schema.is(InternetSchemas.IPv4CidrBlock), ({
address,
mask
}) => `${address.ip}/${mask}`), Match.when(Schema.is(InternetSchemas.IPv6CidrBlock), ({
address,
mask
}) => `${address.ip}/${mask}`), Match.when(Predicate.isString, Function.identity), Match.exhaustive));
const mappedCidrs = Array.map(cidrsEncoded, cidr => ({
from: nodeFromIp,
block: cidr
}));
const newCidrs = Function.pipe(oldCidrs, Option.map(oldCidrs => Array.appendAll(oldCidrs, mappedCidrs)), Option.getOrElse(() => mappedCidrs));
return {
nodes: allowedIPsLayer.nodes,
keys: allowedIPsLayer.keys,
connections: allowedIPsLayer.connections,
wireguardNetworkCidr: allowedIPsLayer.wireguardNetworkCidr,
allowedIPs: Record.set(allowedIPsLayer.allowedIPs, nodeToIp, newCidrs)
};
});
/**
* Converts a network into configs.
*
* @since 1.0.0
* @category Generator
*/
const toConfigs = ({
allowedIPs,
connections,
keys,
nodes,
wireguardNetworkCidr
}) => {
const endpointsByIp = nodesByIp(nodes);
const peerConfigsByIp = Function.pipe(nodes, nodesByIp, Record.map(node => Effect.gen(function* () {
const keysForNode = Record.get(keys, ipForNode(node)).pipe(Option.getOrThrow);
const endpointForNode = Record.get(endpointsByIp, ipForNode(node)).pipe(Option.getOrThrow);
const endpointForNodeEncoded = yield* Schema.is(InternetSchemas.SetupData)(endpointForNode) ? Schema.encode(InternetSchemas.SetupData)(endpointForNode).pipe(Effect.map(x => x[0])) : Effect.succeed(undefined);
const address = Schema.is(InternetSchemas.SetupData)(endpointForNode) ? endpointForNode[1].ip : endpointForNode.ip;
return {
Address: address,
Endpoint: endpointForNodeEncoded,
PublicKey: keysForNode.publicKey,
PresharedKey: keysForNode.preshareKey
};
})));
const configs = Array.map(nodes, node => Effect.gen(function* () {
const keysForNode = Record.get(keys, ipForNode(node)).pipe(Option.getOrThrow);
const endpointForNode = Record.get(endpointsByIp, ipForNode(node)).pipe(Option.getOrThrow);
const allowedIPsForNode = Record.get(allowedIPs, ipForNode(node)).pipe(Option.getOrThrow);
const address = Schema.is(InternetSchemas.SetupData)(endpointForNode) ? endpointForNode[1].ip : endpointForNode.ip;
const listenPort = Schema.is(InternetSchemas.SetupData)(endpointForNode) ? endpointForNode[0].listenPort : 0;
const directConnectionsForNode = yield* Function.pipe(connections, Record.get(ipForNode(node)), Option.getOrThrow, Array.filterMap(peerAddress => Record.get(peerConfigsByIp, peerAddress)), Effect.allWith());
const dcsWithAllowedIPs = Array.map(directConnectionsForNode, dc => ({
Endpoint: dc.Endpoint,
PublicKey: dc.PublicKey,
PresharedKey: node === nodes[0] ? dc.PresharedKey : keysForNode.preshareKey,
AllowedIPs: Function.pipe(allowedIPsForNode, Array.filter(({
from
}) => from === dc.Address), Array.map(({
block
}) => block), x => new Set(x))
}));
return yield* Schema.decode(WireguardConfig.WireguardConfig)({
ListenPort: listenPort,
Peers: dcsWithAllowedIPs,
PrivateKey: keysForNode.privateKey,
Address: `${address}/${wireguardNetworkCidr.mask}`
});
}));
if (Predicate.isTupleOfAtLeast(configs, 2)) {
return Effect.all(configs);
} else {
return Effect.fail(new WireguardErrors.WireguardError({
message: "There must be at least two nodes"
}));
}
};
/**
* Use your phone or computer to remotely access just the wireguard server.
*
* @since 1.0.0
* @category Generators
*/
exports.toConfigs = toConfigs;
const generateRemoteAccessToServer = options => Function.flow(generateKeys, addPreshareKeys, generateStarConnections, computeAllowedIPsFromConnections)(options);
/**
* Builds on "Remote access to server", allowing you to access your entire LAN
* as well.
*
* @since 1.0.0
* @category Generators
*/
exports.generateRemoteAccessToServer = generateRemoteAccessToServer;
const generateRemoteAccessToLan = options => Function.flow(generateRemoteAccessToServer, addAllowedIPs(ipForNode(options.nodes[1]), ipForNode(options.nodes[0]), Array.isArray(options.lanNetworkCidr) ? options.lanNetworkCidr : Array.of(options.lanNetworkCidr)))(options);
/**
* Allows two servers to connect to each other.
*
* @since 1.0.0
* @category Generators
*/
exports.generateRemoteAccessToLan = generateRemoteAccessToLan;
const generateServerToServerAccess = options => Function.flow(generateKeys, addPreshareKeys, generateHubAndSpokeConnections, computeAllowedIPsFromConnections)(options);
/**
* Builds on "Server to server access", allowing two entire networks to
* communicate.
*
* @since 1.0.0
* @category Generators
*/
exports.generateServerToServerAccess = generateServerToServerAccess;
const generateLanToLanAccess = options => Function.flow(generateServerToServerAccess, addAllowedIPs(ipForNode(options.nodes[1]), ipForNode(options.nodes[0]), Array.isArray(options.server1Lan) ? options.server1Lan : Array.of(options.server1Lan)), addAllowedIPs(ipForNode(options.nodes[0]), ipForNode(options.nodes[1]), Array.isArray(options.server2Lan) ? options.server2Lan : Array.of(options.server2Lan)))(options);
/**
* Builds on "Remote access to server", except that all of the VPN clients can
* connect to each other as well. Note: all traffic between nodes must pass
* through the server.
*
* @since 1.0.0
* @category Generators
*/
exports.generateLanToLanAccess = generateLanToLanAccess;
const generateServerHubAndSpokeAccess = options => {
const clientNodes = Function.pipe(options.nodes, Array.map(ipForNode), Array.tailNonEmpty);
const firstFoldOnClientNodes = Array.reduce(clientNodes, Function.identity, (outerAcc, node) => {
const secondFoldOverOtherNodes = Array.reduce(Array.filter(clientNodes, n => n !== node), Function.identity, (innerAcc, otherNode) => Function.compose(addAllowedIPs(node, ipForNode(options.nodes[0]), [`${otherNode}/32`]), innerAcc));
return Function.compose(secondFoldOverOtherNodes, outerAcc);
});
const networkWithoutSpokeAccess = Function.flow(generateKeys, addPreshareKeys, generateHubAndSpokeConnections, computeAllowedIPsFromConnections)(options);
return firstFoldOnClientNodes(networkWithoutSpokeAccess);
};
/**
* Builds on "Server hub and spoke access", allowing you to access your entire
* LAN as well.
*
* @since 1.0.0
* @category Generators
*/
exports.generateServerHubAndSpokeAccess = generateServerHubAndSpokeAccess;
const generateLanHubAndSpokeAccess = options => {
const addAllowedIPsToAllNodes = Function.pipe(options.nodes, Array.map(ipForNode), Array.tailNonEmpty, Array.reduce(Function.identity, (acc, node) => Function.compose(addAllowedIPs(node, ipForNode(options.nodes[0]), Array.isArray(options.lanNetworkCidr) ? options.lanNetworkCidr : Array.of(options.lanNetworkCidr)), acc)));
return addAllowedIPsToAllNodes(generateServerHubAndSpokeAccess(options));
};
/**
* Route specific traffic through a commercial WireGuard VPN provider.
*
* @since 1.0.0
* @category Generators
*/
exports.generateLanHubAndSpokeAccess = generateLanHubAndSpokeAccess;
const generateVpnTunneledAccess = options => Function.flow(generateRemoteAccessToServer, addAllowedIPs(ipForNode(options.nodes[1]), ipForNode(options.nodes[0]), ["0.0.0.0/0"]))(options);
/**
* Securely access the Internet from untrusted networks by routing all of your
* traffic through the VPN and out the server's internet connection.
*
* @since 1.0.0
* @category Generators
*/
exports.generateVpnTunneledAccess = generateVpnTunneledAccess;
const generateRemoteTunneledAccess = options => Function.flow(generateRemoteAccessToLan, addAllowedIPs(ipForNode(options.nodes[1]), ipForNode(options.nodes[0]), ["0.0.0.0/0"]))(options);
exports.generateRemoteTunneledAccess = generateRemoteTunneledAccess;
//# sourceMappingURL=WireguardGenerate.js.map