Skip to content

Commit

Permalink
Merge remote-tracking branch 'wg-easy/master'
Browse files Browse the repository at this point in the history
  • Loading branch information
Dartegnian committed Jul 23, 2024
2 parents c6a653d + caad2e4 commit ef1d981
Show file tree
Hide file tree
Showing 17 changed files with 376 additions and 237 deletions.
6 changes: 5 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ COPY --from=build_node_modules /app /app
# than what runs inside of docker.
COPY --from=build_node_modules /node_modules /node_modules

# Copy the needed wg-password scripts
COPY --from=build_node_modules /app/wgpw.sh /bin/wgpw
RUN chmod +x /bin/wgpw

# Install Linux packages
RUN apk add --no-cache \
dpkg \
Expand All @@ -42,4 +46,4 @@ ENV DEBUG=Server,WireGuard

# Run Web UI
WORKDIR /app
CMD ["/usr/bin/dumb-init", "node", "server.js"]
CMD ["/usr/bin/dumb-init", "node", "server.js"]
111 changes: 19 additions & 92 deletions How_to_generate_an_bcrypt_hash.md
Original file line number Diff line number Diff line change
@@ -1,101 +1,28 @@
<!-- created by Mathys Lopinto (@mathys-lopinto) -->
# How to generate bcrypt hash
# wg-password

## Prerequisites
- Python 3
- bcrypt library
`wg-password` (wgpw) is a script that generates bcrypt password hashes for use with `wg-easy`, enhancing security by requiring passwords.

## Prerequisites Installation
### Windows
Download and install Python 3 from [official website](https://www.python.org/downloads/).
Check "Add python.exe to PATH" before running "Install Now".
## Features

Open Command Prompt (win + r, type "cmd" and press enter) and run the following command to install bcrypt library:
```bash
pip install bcrypt
```

### Debian based distributions
```bash
sudo apt-get update
sudo apt-get install python3 python3-pip
# If you use have install python using apt
sudo apt-get install python3-bcrypt
# If don't install python using apt
pip3 install bcrypt
# If you got externally-managed-environment error
pip3 install bcrypt --break-system-packages
```

### Fedora based distributions
```bash
sudo dnf update
sudo dnf install python3 python3-pip
# If you use have install python using dnf
sudo dnf install python3-bcrypt
# If don't install python using dnf
pip3 install bcrypt
# If you got externally-managed-environment error
pip3 install bcrypt --break-system-packages
```

### Arch Linux based distributions
```bash
sudo pacman -Syy
sudo pacman -S python python-pip
# If you use have install python using pacman
sudo pacman -S python-bcrypt
# If don't install python using pacman
pip3 install bcrypt
# If you got externally-managed-environment error
pip3 install bcrypt --break-system-packages
```
- Generate bcrypt password hashes.
- Easily integrate with `wg-easy` to enforce password requirements.

## Generating bcrypt hash from the command line
You can use the following one-liner command to generate a bcrypt hash directly in the cmd/ terminal:
```bash
python3 -c "import bcrypt; password = b'your_password_here'; assert len(password) < 72, 'Password must be less than 72 bytes due to bcrypt limitation'; hashed = bcrypt.hashpw(password, bcrypt.gensalt()); print(f'The hashed password is: {hashed.decode()}'); docker_interpolation = hashed.decode().replace('$', '$$'); print(f'The hashed password for a Docker env is: {docker_interpolation}')" # or python if you run this on Windows. CHANGE your_password_here BY YOUR PASSWORD
```
Please change ``your_password_here`` in the line by your own password.

## Generating bcrypt hash from an script file
### Do not name the file `bcrypt.py` as it will cause an error.
Create a python file with the following content:
```python
import bcrypt

# Initial password
password = b"your_password_here" # DO NOT REMOVE THE b

# Assert that the password is under 72 bytes
assert len(password) < 72, "Password must be less than 72 bytes due to bcrypt limitation"

# Generate a salt and hash the password
hashed = bcrypt.hashpw(password, bcrypt.gensalt())
## Usage with Docker

# Print the hashed password
print(f'The hashed password is: {hashed.decode()}')
To generate a bcrypt password hash using docker, run the following command :

# Prepare the hashed password for Docker environment variables
docker_interpolation = hashed.decode().replace("$", "$$")
print(f'The hashed password for a Docker env is: {docker_interpolation}')
```sh
docker run ghcr.io/wg-easy/wg-easy wgpw YOUR_PASSWORD
PASSWORD_HASH='$2b$12$coPqCsPtcFO.Ab99xylBNOW4.Iu7OOA2/ZIboHN6/oyxca3MWo7fW' // litteraly YOUR_PASSWORD
```

Replace `your_password_here` with the password you want to hash.
*Important* : make sure to enclose your password in single quotes when you run `docker run` command :

Run the python file and you will get the hashed password.

## Get the right hash
Copy the 2nd line of the output (after the : ) and use it as your hashed password.

__Exemple__
If the output is:
```txt
The hashed password is: $2b$12$NRiL4Kw4dKid.ix2WvZltOmaQBZjoX30shjHJXRVdEGshAxYWXXMe
The hashed password for an docker env is: $$2b$$12$$NRiL4Kw4dKid.ix2WvZltOmaQBZjoX30shjHJXRVdEGshAxYWXXMe
```

The docker line ``PASSWORD_HASH`` will be:
```txt
PASSWORD_HASH=$$2b$$12$$NRiL4Kw4dKid.ix2WvZltOmaQBZjoX30shjHJXRVdEGshAxYWXXMe
```
```bash
$ echo $2b$12$coPqCsPtcF
b2
$ echo "$2b$12$coPqCsPtcF"
b2
$ echo '$2b$12$coPqCsPtcF'
$2b$12$coPqCsPtcF
```
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ To automatically install & run wg-easy, simply run:

> 💡 Replace `YOUR_SERVER_IP` with your WAN IP, or a Dynamic DNS hostname.
>
> 💡 Replace `YOUR_ADMIN_PASSWORD_HASH` with a bcrypt password hash to log in on the Web UI. See How_to_generate_an_bcrypt_hash.md for know how generate the hash.
> 💡 Replace `YOUR_ADMIN_PASSWORD_HASH` with a bcrypt password hash to log in on the Web UI. See [How_to_generate_an_bcrypt_hash.md](./How_to_generate_an_bcrypt_hash.md) for know how generate the hash.
The Web UI will now be available on `http://0.0.0.0:51821`.

Expand All @@ -111,7 +111,7 @@ These options can be configured by setting environment variables using `-e KEY="
| `WG_HOST` | - | `vpn.myserver.com` | The public hostname of your VPN server. |
| `WG_DEVICE` | `eth0` | `ens6f0` | Ethernet device the wireguard traffic should be forwarded through. |
| `WG_PORT` | `51820` | `12345` | The public UDP port of your VPN server. WireGuard will listen on that (othwise default) inside the Docker container. |
| `WG_CONFIG_PORT`| `51820` | `12345` | The UDP port used on [Home Assistent Plugin](https://github.com/adriy-be/homeassistant-addons-jdeath/tree/main/wgeasy)
| `WG_CONFIG_PORT`| `51820` | `12345` | The UDP port used on [Home Assistant Plugin](https://github.com/adriy-be/homeassistant-addons-jdeath/tree/main/wgeasy)
| `WG_MTU` | `null` | `1420` | The MTU the clients will use. Server uses default WG MTU. |
| `WG_PERSISTENT_KEEPALIVE` | `0` | `25` | Value in seconds to keep the "connection" open. If this value is 0, then connections won't be kept alive. |
| `WG_DEFAULT_ADDRESS` | `10.8.0.x` | `10.6.0.x` | Clients IP address range. |
Expand Down
Binary file modified assets/screenshot.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
{
"version": "1.0.1",
"scripts": {
"sudobuild": "DOCKER_BUILDKIT=1 sudo docker build --tag wg-easy-m3 .",
"build": "DOCKER_BUILDKIT=1 docker build --tag wg-easy-m3 .",
"serve": "docker compose -f docker-compose.yml -f docker-compose.dev.yml up",
"sudostart": "sudo docker run --env WG_HOST=0.0.0.0 --name wg-easy-m3 --cap-add=NET_ADMIN --cap-add=SYS_MODULE --sysctl=\"net.ipv4.conf.all.src_valid_mark=1\" --mount type=bind,source=\"$(pwd)\"/config,target=/etc/wireguard -p 51820:51820/udp -p 51821:51821/tcp wg-easy-m3",
"start": "docker run --env WG_HOST=0.0.0.0 --name wg-easy-m3 --cap-add=NET_ADMIN --cap-add=SYS_MODULE --sysctl=\"net.ipv4.conf.all.src_valid_mark=1\" --mount type=bind,source=\"$(pwd)\"/config,target=/etc/wireguard -p 51820:51820/udp -p 51821:51821/tcp wg-easy-m3"
}
}
5 changes: 2 additions & 3 deletions src/config.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
'use strict';

const { release } = require('./package.json');
const { release: { version } } = require('./package.json');

module.exports.RELEASE = release;
module.exports.RELEASE = version;
module.exports.PORT = process.env.PORT || '51821';
module.exports.WEBUI_HOST = process.env.WEBUI_HOST || '0.0.0.0';
module.exports.PASSWORD = process.env.PASSWORD;
module.exports.PASSWORD_HASH = process.env.PASSWORD_HASH;
module.exports.WG_PATH = process.env.WG_PATH || '/etc/wireguard/';
module.exports.WG_DEVICE = process.env.WG_DEVICE || 'eth0';
Expand Down
28 changes: 19 additions & 9 deletions src/lib/Server.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,22 +28,18 @@ const {
PORT,
WEBUI_HOST,
RELEASE,
PASSWORD,
PASSWORD_HASH,
LANG,
UI_TRAFFIC_STATS,
UI_CHART_TYPE,
} = require('../config');

const requiresPassword = !!PASSWORD || !!PASSWORD_HASH;
const requiresPassword = !!PASSWORD_HASH;

/**
* Checks if `password` matches the PASSWORD_HASH.
*
* For backward compatibility it also allows `password` to match the clear text PASSWORD,
* but only if no PASSWORD_HASH is provided.
*
* If both enviornment variables are not set, the password is always invalid.
* If environment variable is not set, the password is always invalid.
*
* @param {string} password String to test
* @returns {boolean} true if matching environment, otherwise false
Expand All @@ -56,9 +52,6 @@ const isPasswordValid = (password) => {
if (PASSWORD_HASH) {
return bcrypt.compareSync(password, PASSWORD_HASH);
}
if (PASSWORD) {
return password === PASSWORD;
}

return false;
};
Expand Down Expand Up @@ -265,6 +258,23 @@ module.exports = class Server {
});
};

// backup_restore
const router3 = createRouter();
app.use(router3);

router3
.get('/api/wireguard/backup', defineEventHandler(async (event) => {
const config = await WireGuard.backupConfiguration();
setHeader(event, 'Content-Disposition', 'attachment; filename="wg0.json"');
setHeader(event, 'Content-Type', 'text/json');
return config;
}))
.put('/api/wireguard/restore', defineEventHandler(async (event) => {
const { file } = await readBody(event);
await WireGuard.restoreConfiguration(file);
return { success: true };
}));

// Static assets
const publicDir = '/app/www';
app.use(
Expand Down
117 changes: 73 additions & 44 deletions src/lib/WireGuard.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,54 +27,60 @@ const {

module.exports = class WireGuard {

async __buildConfig() {
this.__configPromise = Promise.resolve().then(async () => {
if (!WG_HOST) {
throw new Error('WG_HOST Environment Variable Not Set!');
}

debug('Loading configuration...');
let config;
try {
config = await fs.readFile(path.join(WG_PATH, 'wg0.json'), 'utf8');
config = JSON.parse(config);
debug('Configuration loaded.');
} catch (err) {
const privateKey = await Util.exec('wg genkey');
const publicKey = await Util.exec(`echo ${privateKey} | wg pubkey`, {
log: 'echo ***hidden*** | wg pubkey',
});
const address = WG_DEFAULT_ADDRESS.replace('x', '1');

config = {
server: {
privateKey,
publicKey,
address,
},
clients: {},
};
debug('Configuration generated.');
}

return config;
});

return this.__configPromise;
}

async getConfig() {
if (!this.__configPromise) {
this.__configPromise = Promise.resolve().then(async () => {
if (!WG_HOST) {
throw new Error('WG_HOST Environment Variable Not Set!');
}
const config = await this.__buildConfig();

debug('Loading configuration...');
let config;
try {
config = await fs.readFile(path.join(WG_PATH, 'wg0.json'), 'utf8');
config = JSON.parse(config);
debug('Configuration loaded.');
} catch (err) {
const privateKey = await Util.exec('wg genkey');
const publicKey = await Util.exec(`echo ${privateKey} | wg pubkey`, {
log: 'echo ***hidden*** | wg pubkey',
});
const address = WG_DEFAULT_ADDRESS.replace('x', '1');

config = {
server: {
privateKey,
publicKey,
address,
},
clients: {},
};
debug('Configuration generated.');
await this.__saveConfig(config);
await Util.exec('wg-quick down wg0').catch(() => {});
await Util.exec('wg-quick up wg0').catch((err) => {
if (err && err.message && err.message.includes('Cannot find device "wg0"')) {
throw new Error('WireGuard exited with the error: Cannot find device "wg0"\nThis usually means that your host\'s kernel does not support WireGuard!');
}

await this.__saveConfig(config);
await Util.exec('wg-quick down wg0').catch(() => { });
await Util.exec('wg-quick up wg0').catch((err) => {
if (err && err.message && err.message.includes('Cannot find device "wg0"')) {
throw new Error('WireGuard exited with the error: Cannot find device "wg0"\nThis usually means that your host\'s kernel does not support WireGuard!');
}

throw err;
});
// await Util.exec(`iptables -t nat -A POSTROUTING -s ${WG_DEFAULT_ADDRESS.replace('x', '0')}/24 -o ' + WG_DEVICE + ' -j MASQUERADE`);
// await Util.exec('iptables -A INPUT -p udp -m udp --dport 51820 -j ACCEPT');
// await Util.exec('iptables -A FORWARD -i wg0 -j ACCEPT');
// await Util.exec('iptables -A FORWARD -o wg0 -j ACCEPT');
await this.__syncConfig();

return config;
throw err;
});
// await Util.exec(`iptables -t nat -A POSTROUTING -s ${WG_DEFAULT_ADDRESS.replace('x', '0')}/24 -o ' + WG_DEVICE + ' -j MASQUERADE`);
// await Util.exec('iptables -A INPUT -p udp -m udp --dport 51820 -j ACCEPT');
// await Util.exec('iptables -A FORWARD -i wg0 -j ACCEPT');
// await Util.exec('iptables -A FORWARD -o wg0 -j ACCEPT');
await this.__syncConfig();
}

return this.__configPromise;
Expand Down Expand Up @@ -227,7 +233,9 @@ Endpoint = ${WG_HOST}:${WG_CONFIG_PORT}`;
const config = await this.getConfig();

const privateKey = await Util.exec('wg genkey');
const publicKey = await Util.exec(`echo ${privateKey} | wg pubkey`);
const publicKey = await Util.exec(`echo ${privateKey} | wg pubkey`, {
log: 'echo ***hidden*** | wg pubkey',
});
const preSharedKey = await Util.exec('wg genpsk');

// Calculate next IP
Expand Down Expand Up @@ -319,9 +327,30 @@ Endpoint = ${WG_HOST}:${WG_CONFIG_PORT}`;
await this.saveConfig();
}

async __reloadConfig() {
await this.__buildConfig();
await this.__syncConfig();
}

async restoreConfiguration(config) {
debug('Starting configuration restore process.');
const _config = JSON.parse(config);
await this.__saveConfig(_config);
await this.__reloadConfig();
debug('Configuration restore process completed.');
}

async backupConfiguration() {
debug('Starting configuration backup.');
const config = await this.getConfig();
const backup = JSON.stringify(config, null, 2);
debug('Configuration backup completed.');
return backup;
}

// Shutdown wireguard
async Shutdown() {
await Util.exec('wg-quick down wg0').catch(() => { });
await Util.exec('wg-quick down wg0').catch(() => {});
}

};
Loading

0 comments on commit ef1d981

Please sign in to comment.