├── .dockerignore ├── .github └── FUNDING.yml ├── Dockerfile ├── LICENSE ├── README.md ├── examples └── webhook │ ├── README.md │ └── bin │ ├── wgcg-gen.sh │ ├── wgcg-html-gpg.sh │ ├── wgcg-html-qrcode.sh │ └── wh.py ├── images └── wgcg.png ├── modules └── wgcg-install-wireguard.sh ├── monitoring └── wireguard-dashboard.json ├── wgcg-docker.sh ├── wgcg.conf ├── wgcg.sh └── wgfw.rules /.dockerignore: -------------------------------------------------------------------------------- 1 | # Include files 2 | * 3 | !wgcg.conf 4 | !wgcg.sh 5 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: psyhomb 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 13 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 14 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:20.04 2 | 3 | LABEL maintainer="psyhomb" 4 | 5 | ARG USER 6 | ARG UID 7 | 8 | ENV USER=${USER:-wgcg} \ 9 | UID=${UID:-1000} 10 | 11 | WORKDIR /data/wgcg 12 | 13 | COPY . ./ 14 | 15 | RUN case ${UID} in \ 16 | 0) HOME="/root" ;; \ 17 | *) HOME="/home/${USER}"; useradd -ou ${UID} ${USER} ;; \ 18 | esac \ 19 | && mkdir -p ${HOME}/.gnupg ${HOME}/wireguard/wgcg \ 20 | && chmod 700 ${HOME}/.gnupg \ 21 | && mv wgcg.conf ${HOME}/wireguard/wgcg/ \ 22 | && mv wgcg.sh /usr/local/bin/ \ 23 | && chmod 644 ${HOME}/wireguard/wgcg/wgcg.conf \ 24 | && chmod 755 /usr/local/bin/wgcg.sh \ 25 | && chown -R ${USER}:${USER} ${HOME} \ 26 | && apt-get update \ 27 | && apt-get -y install --no-install-recommends wireguard-tools openssh-client gpg gpg-agent qrencode grepcidr \ 28 | && apt-get -y --purge autoremove \ 29 | && apt-get clean \ 30 | && rm -vrf /var/lib/apt/lists/* 31 | 32 | USER ${USER} 33 | ENTRYPOINT ["/usr/local/bin/wgcg.sh"] 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Milos Buncic 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | wireguard-tools 2 | =============== 3 | 4 | Full documentation about manual Wireguard installation and configuration process can be found [here](https://gitlab.com/snippets/1897102). 5 | 6 | wgcg.sh 7 | ------- 8 | 9 |
10 | 11 |
12 | 13 | ### About 14 | 15 | This script is created to ease manual process of Wireguard configuration and will help you to automatically generate all the required configuration files (client and server), PKI key pairs and preshared key. 16 | 17 | ### Install dependencies 18 | 19 | **Arch** 20 | 21 | ```bash 22 | pacman -S wireguard-tools gnupg qrencode 23 | yay grepcidr 24 | ``` 25 | 26 | **Ubuntu** 27 | 28 | ```bash 29 | apt-get install wireguard-tools gpg qrencode grepcidr 30 | ``` 31 | 32 | **MacOS** 33 | 34 | ```bash 35 | brew install wireguard-tools gpg qrencode grepcidr 36 | ``` 37 | 38 | Make sure to append following line to [wgcg.conf](./wgcg.conf) file only if using MacOS. 39 | By doing this we will force script to use GNU instead of BSD command line utilities (e.g. `grep`) and prevent any possible incompatibility issues. 40 | 41 | ```bash 42 | echo -e '\n# Make sure script is using GNU command line utilities on MacOS\nexport PATH="/usr/local/opt/grep/libexec/gnubin:${PATH}"' >> wgcg.conf 43 | ``` 44 | 45 | ### Usage 46 | 47 | Before running the script we'll have to update [wgcg.conf](./wgcg.conf) configuration file. 48 | For most use cases the only variable we'd have to modify is `WGCG_SERVER_PUBLIC_IP`. 49 | 50 | ```bash 51 | # Server name (wireguard interface name e.g. wg0 || wg1 || wg2) 52 | WGCG_SERVER_NAME="wg0" 53 | 54 | # HostMin to HostMax range can be used to assign IP addresses to WireGuard clients 55 | # e.g. ./wgcg.sh -c foo 10.0.0.2 56 | # 57 | # Network: 10.0.0.0/22 58 | # HostMin: 10.0.0.1 59 | # HostMax: 10.0.3.254 60 | # HostIDs: 1022 61 | # 62 | # WireGuard server private IP address (with optional CIDR - default: 22) 63 | WGCG_SERVER_WG_IP="10.0.0.1" 64 | 65 | # Static server port 66 | WGCG_SERVER_PORT="52001" 67 | 68 | # Server's public IP or FQDN 69 | WGCG_SERVER_PUBLIC_IP="wg.yourdomain.com" 70 | 71 | # SSH server IP address (default: ${WGCG_SERVER_PUBLIC_IP}) (optional) 72 | # Note: This option can be used in case SSH server is listening on different IP address, 73 | # if not specified, ${WGCG_SERVER_PUBLIC_IP} will be used instead 74 | WGCG_SERVER_SSH_IP="" 75 | 76 | # SSH server port (optional) 77 | WGCG_SERVER_SSH_PORT="22" 78 | 79 | # Space separated list of DNS IPs (default: 1.1.1.1 1.0.0.1) (optional) 80 | WGCG_CLIENT_DNS_IPS="1.1.1.1 1.0.0.1" 81 | 82 | # Space separated list of subnets (with CIDR) required for split-tunneling (default: 0.0.0.0/0) (optional) 83 | WGCG_CLIENT_ALLOWED_IPS="0.0.0.0/0" 84 | 85 | # All configuration and key files will be stored in this directory 86 | WGCG_WORKING_DIR="${HOME}/wireguard/${WGCG_SERVER_NAME}" 87 | ``` 88 | 89 | Copy [wgcg.conf](./wgcg.conf) and [wgfw.rules](./wgfw.rules) files to `wgcg` directory. 90 | 91 | ```bash 92 | mkdir -p ${HOME}/wireguard/wgcg 93 | cp wgcg.conf ${HOME}/wireguard/wgcg/ 94 | cp wgfw.rules ${HOME}/wireguard/wgcg/ 95 | ``` 96 | 97 | Copy [wgcg.sh](./wgcg.sh) script to `/usr/local/bin` directory. 98 | 99 | ```bash 100 | cp wgcg.sh /usr/local/bin/ 101 | ``` 102 | 103 | It is also possible to specify custom configuration file by passing `WGCG_CONFIG_FILE` environment variable. 104 | 105 | ```bash 106 | WGCG_CONFIG_FILE="${HOME}/wireguard/wgcg/wgcg.conf" wgcg.sh 107 | ``` 108 | 109 | Print help and current default options. 110 | 111 | ```bash 112 | wgcg.sh -h 113 | ``` 114 | 115 | Output: 116 | 117 | ```plain 118 | Usage: 119 | wgcg.sh options 120 | 121 | Options: 122 | -P|--sysprep filename.sh Install WireGuard kernel module, required tools and scripts (will establish SSH connection with server) 123 | -s|--add-server-config Generate server configuration 124 | -c|--add-client-config client_name client_wg_ip Generate client configuration 125 | -B|--add-clients-batch filename.csv[:rewrite|:norewrite] Generate configuration for multiple clients in batch mode 126 | Supported action modes are 'rewrite' or 'norewrite' (default) 127 | 'rewrite' action mean regenerate ALL, 'norewrite' mean generate only configs and keys for new clients 128 | -e|--encrypt-config client_name [passphrase] Encrypt configuration file by using symmetric encryption (if passphrase not specified it will be generated - RECOMMENDED) 129 | -d|--decrypt-config client_name Decrypt configuration file and print it out on stdout 130 | -r|--rm-client-config client_name Remove client configuration 131 | -q|--gen-qr-code client_name [-] Generate QR code (PNG format) from client configuration file, if - is used, QR code will be printed out on stdout instead 132 | -l|--list-used-ips List all clients IPs that are currently in use 133 | -S|--sync Synchronize server configuration (will establish SSH connection with server) 134 | -h|--help Show this help 135 | 136 | Current default options: 137 | WGCG_SERVER_NAME="wg0" 138 | WGCG_SERVER_WG_IP="10.0.0.1" 139 | WGCG_SERVER_PORT="52001" 140 | WGCG_SERVER_PUBLIC_IP="wg.yourdomain.com" 141 | WGCG_SERVER_SSH_PORT="22" 142 | WGCG_CLIENT_DNS_IPS="1.1.1.1 1.0.0.1" 143 | WGCG_CLIENT_ALLOWED_IPS="0.0.0.0/0" 144 | WGCG_WORKING_DIR="/home/username/wireguard/wg0" 145 | ``` 146 | 147 | [wgcg-install-wireguard.sh](./modules/wgcg-install-wireguard.sh) module will do all required system preparations on the WireGuard server (running the module is idempotent operation): 148 | 149 | - Install `wireguard` kernel module and tools 150 | - Load the module 151 | - Generate `wgfw.sh` script 152 | - Enable IP forwarding (routing) 153 | 154 | **Note:** You have to run it only once! 155 | 156 | ```bash 157 | wgcg.sh --sysprep modules/wgcg-install-wireguard.sh 158 | ``` 159 | 160 | Generate server keys and config. 161 | 162 | ```bash 163 | wgcg.sh -s 164 | ``` 165 | 166 | Generate client config, PKI key pairs and update server config (add new Peer block) 167 | 168 | ```bash 169 | wgcg.sh -c foo 10.0.0.2 170 | ``` 171 | 172 | or to generate multiple client configs at once, create `client-configs.csv` file 173 | 174 | ```bash 175 | cat > client-configs.csv <<'EOF' 176 | foo,10.0.0.2 177 | bar,10.0.0.3 178 | EOF 179 | ``` 180 | 181 | and run. 182 | 183 | ```bash 184 | wgcg.sh -B client-configs.csv 185 | ``` 186 | 187 | By default `-B` will only generate client config and key files for newly added clients, if you plan to regenerate config and key files for ALL clients that are specified in the csv file, 188 | you'll have to use `rewrite` action mode, globally or per client line, in case both are specified last one has precedence. 189 | 190 | Global `rewrite` action mode 191 | 192 | ```bash 193 | wgcg.sh -B client-configs.csv:rewrite 194 | ``` 195 | 196 | or per client line. 197 | 198 | **Note:** It is also possible to protect individual client from regenerating config and key files by specifying `norewrite` action. 199 | 200 | ```bash 201 | cat > client-configs.csv <<'EOF' 202 | foo,10.0.0.2,rewrite 203 | bar,10.0.0.3,norewrite 204 | EOF 205 | ``` 206 | 207 | Remove client config, PKI key pairs and update server config (remove Peer block). 208 | 209 | ```bash 210 | wgcg.sh -r foo 211 | ``` 212 | 213 | Synchronize local server configuration file with server (live update). 214 | 215 | ```bash 216 | wgcg.sh --sync 217 | ``` 218 | 219 | In order to send client configuration file to a person safely, you can use GPG symmetric encryption to encrypt data before sending it, then you can send configuration file to a person via one channel ([webwormhole](https://webwormhole.io)) and passphrase via different channel ([ots](https://github.com/sniptt-official/ots)). 220 | 221 | Encrypt configuration file. 222 | 223 | ```bash 224 | wgcg.sh -e foo 225 | ``` 226 | 227 | To test passphrase just run decrypt command, if everything is OK client configuration will be printed out on the standard output. 228 | 229 | ```bash 230 | wgcg.sh -d foo 231 | ``` 232 | 233 | ### Multi-Configuration 234 | 235 | It is also possible to manage multiple clusters with single script. 236 | Create configuration file and command alias for every cluster. 237 | 238 | **Note:** Append following lines to `~/.zshrc` or `~/.bashrc` file. 239 | 240 | ```bash 241 | alias wgcg-office1.sh="WGCG_CONFIG_FILE=${HOME}/wireguard/wgcg/office1.conf wgcg.sh" 242 | alias wgcg-office2.sh="WGCG_CONFIG_FILE=${HOME}/wireguard/wgcg/office2.conf wgcg.sh" 243 | ``` 244 | 245 | ```bash 246 | source ~/.zshrc 247 | # or 248 | source ~/.bashrc 249 | ``` 250 | 251 | ```bash 252 | wgcg-office1.sh -h 253 | ``` 254 | 255 | ### Firewall rules 256 | 257 | Custom firewall rules, in iptables compatible format, can be added using [wgfw.rules](./wgfw.rules) file. All rules from this file are going to be applied in idempotent manner on the server side at server startup time or each time `wgcg.sh --sync` command is executed. 258 | 259 | ### Demo 260 | 261 |
262 | 263 |
264 | 265 | ### Docker 266 | 267 | It is also possible to run the script inside of Docker container with already preinstalled dependecies. 268 | 269 | Build docker image. 270 | 271 | ```bash 272 | docker build --no-cache --force-rm --build-arg USER=${USER} --build-arg UID=${UID} -t wgcg . 273 | ``` 274 | 275 | Run the script. 276 | 277 | ```bash 278 | ./wgcg-docker.sh -h 279 | ``` 280 | 281 | or if you are not using default configuration filename (`wgcg.conf`). 282 | 283 | ```bash 284 | WGCG_CONFIG_FILE="${HOME}/wireguard/wgcg/wg0.conf" ./wgcg-docker.sh -h 285 | ``` 286 | 287 | ### Monitoring 288 | 289 | #### Prometheus 290 | 291 | - [prometheus_wireguard_exporter](https://github.com/MindFlavor/prometheus_wireguard_exporter) 292 | 293 | #### Grafana 294 | 295 | - [wireguard-dashboard.json](./monitoring/wireguard-dashboard.json) 296 | -------------------------------------------------------------------------------- /examples/webhook/README.md: -------------------------------------------------------------------------------- 1 | wgcg with webhook 2 | ================= 3 | 4 | Here we're going to show how we can use [wgcg.sh](../../README.md) tool in combination with [webhook](https://github.com/adnanh/webhook) service to create endpoint from where client will be able to download WireGuard configuration. 5 | 6 | We'll assume that [wgcg.sh](../../README.md) is already configured and ready to use. 7 | 8 | The only difference from standard configuration is that you will have to create 2 configuration files if you plan to configure 2 WireGuard servers behind LB and to set valid SSH IP address (`WGCG_SERVER_SSH_IP`) in both configuration files, all other settings should be the same: 9 | 10 | - `/root/wireguard/wgcg/wg-1.conf` 11 | - `/root/wireguard/wgcg/wg-2.conf` 12 | 13 | Preparation 14 | ----------- 15 | 16 | Copy all the scripts from local [bin](./bin) to `/usr/local/bin` directory on the remote server where [wgcg.sh](../../README.md) script is already installed. 17 | 18 | Download, install and configure `webhook` and `nginx` services. 19 | 20 | ### Webhook 21 | 22 | Install `webhook` binary. 23 | 24 | ```bash 25 | WEBHOOK_VERSION="2.7.0" 26 | wget https://github.com/adnanh/webhook/releases/download/${WEBHOOK_VERSION}/webhook-linux-amd64.tar.gz 27 | tar xzvf webhook-linux-amd64.tar.gz 28 | mv webhook-linux-amd64/webhook /usr/local/bin/webhook_${WEBHOOK_VERSION} 29 | cd /usr/local/bin 30 | chown root:root webhook_${WEBHOOK_VERSION} 31 | ln -snf webhook_${WEBHOOK_VERSION} webhook 32 | cd 33 | ``` 34 | 35 | Specify command line options that will be used by `webhook` service. 36 | 37 | ```bash 38 | cat > /etc/default/webhook <<'EOF' 39 | ### SSL termination on Webhook layer 40 | #OPTIONS="-hooks=/etc/webhook/hooks.json -hotreload -ip 127.0.0.1 -port 9000 -secure -cert /etc/letsencrypt/live/wgcg.yourdomain.com/fullchain.pem -key /etc/letsencrypt/live/wgcg.yourdomain.com/privkey.pem -verbose" 41 | 42 | ### SSL termination on Nginx layer 43 | OPTIONS="-hooks=/etc/webhook/hooks.json -hotreload -ip 127.0.0.1 -port 9000 -verbose" 44 | EOF 45 | ``` 46 | 47 | Create systemd unit for `webhook` service. 48 | 49 | ```bash 50 | systemctl edit --force --full webhook.service 51 | ``` 52 | 53 | ```plain 54 | [Unit] 55 | Description=Webhook Service 56 | Documentation=https://github.com/adnanh/webhook 57 | 58 | [Service] 59 | EnvironmentFile=/etc/default/webhook 60 | ExecStart=/usr/local/bin/webhook $OPTIONS 61 | Restart=on-failure 62 | 63 | [Install] 64 | WantedBy=multi-user.target 65 | ``` 66 | 67 | Create first part of `webhook` configuration file that will be used by our scripts to automatically generate the main configuration file => `/etc/webhook/hooks.json` 68 | 69 | ```bash 70 | mkdir -p /etc/webhook && cat > /etc/webhook/main.json <<'EOF' 71 | [ 72 | { 73 | "id": "wgcg", 74 | "execute-command": "/usr/local/bin/wgcg-html-gpg.sh", 75 | "include-command-output-in-response": true, 76 | "response-headers": [ 77 | { 78 | "name": "Cache-Control", 79 | "value": "no-store, no-cache, must-revalidate" 80 | } 81 | ], 82 | "pass-arguments-to-command": [ 83 | { 84 | "source": "url", 85 | "name": "servername" 86 | }, 87 | { 88 | "source": "url", 89 | "name": "username" 90 | } 91 | ], 92 | "trigger-rule": {} 93 | } 94 | ] 95 | EOF 96 | ``` 97 | 98 | ### Nginx 99 | 100 | Install `nginx` service. 101 | 102 | ```bash 103 | apt install nginx 104 | ``` 105 | 106 | Create vhost configuration. 107 | 108 | **Note:** Don't forget to replace `wgcg.yourdomain.com` domain name with real domain name and to generate certificate for it (see `certbot` section down below). 109 | 110 | ```bash 111 | cat > /etc/nginx/sites-available/wgcg.yourdomain.com.conf <<'EOF' 112 | # Disable emitting nginx version 113 | server_tokens off; 114 | 115 | # Sets the maximum allowed size of the client request body 116 | # Setting size to 0 disables checking of client request body size 117 | #client_max_body_size 0; 118 | 119 | server { 120 | listen 80 default_server; 121 | server_name wgcg.yourdomain.com; 122 | 123 | #access_log /var/log/nginx/wgcg.yourdomain.com-acme_access.log; 124 | #error_log /var/log/nginx/wgcg.yourdomain.com-acme_error.log; 125 | 126 | ## https://certbot.eff.org/docs/using.html#webroot 127 | #location ^~ /.well-known/acme-challenge/ { 128 | # root /usr/share/nginx/wgcg.yourdomain.com; 129 | #} 130 | 131 | location / { 132 | return 301 https://$server_name$request_uri; 133 | } 134 | } 135 | 136 | server { 137 | listen 443 ssl; 138 | server_name wgcg.yourdomain.com; 139 | 140 | access_log /var/log/nginx/wgcg.yourdomain.com_access.log; 141 | error_log /var/log/nginx/wgcg.yourdomain.com_error.log; 142 | 143 | ssl_certificate /etc/letsencrypt/live/wgcg.yourdomain.com/fullchain.pem; 144 | ssl_certificate_key /etc/letsencrypt/live/wgcg.yourdomain.com/privkey.pem; 145 | #ssl_trusted_certificate /etc/nginx/conf.d/ssl/ca-certs.pem; 146 | 147 | ssl_session_cache shared:SSL:20m; 148 | ssl_session_timeout 10m; 149 | 150 | ssl_prefer_server_ciphers on; 151 | ssl_protocols TLSv1.2 TLSv1.3; 152 | ssl_ciphers ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:ECDH+3DES:DH+3DES:RSA+AESGCM:RSA+AES:RSA+3DES:!aNULL:!MD5:!DSS; 153 | 154 | add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; 155 | 156 | location /healthcheck { 157 | add_header Content-Type "text/plain"; 158 | return 200 "OK"; 159 | } 160 | 161 | location / { 162 | #satisfy all; 163 | 164 | #allow 10.0.0.0/8; 165 | #deny all; 166 | 167 | auth_basic "wgcg"; 168 | auth_basic_user_file /etc/nginx/.htpasswd; 169 | 170 | proxy_pass http://127.0.0.1:9000; 171 | proxy_set_header Host $host; 172 | proxy_set_header X-Real-IP $remote_addr; 173 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 174 | proxy_set_header X-Forwarded-Proto $scheme; 175 | } 176 | } 177 | EOF 178 | ``` 179 | 180 | Disable `default` vhost and enable our newly added vhost configuration. 181 | 182 | ```bash 183 | cd /etc/nginx/sites-enabled 184 | rm -f default 185 | ln -snf /etc/nginx/sites-available/wgcg.yourdomain.com.conf 186 | cd 187 | ``` 188 | 189 | Create `test` user that will be used for Nginx Basic Auth. 190 | 191 | Install required utils. 192 | 193 | ```bash 194 | apt install apache2-utils 195 | ``` 196 | 197 | Create a user. 198 | 199 | ```bash 200 | htpasswd -c /etc/nginx/.htpasswd test 201 | ``` 202 | 203 | Use Let's Encrypt with [certbot](https://certbot.eff.org/all-instructions) client to generate certificates if needed. 204 | 205 | ```bash 206 | apt install certbot 207 | ``` 208 | 209 | **Note:** We're using DNS TXT RR for verification because our Nginx instance isn't internet-facing. 210 | 211 | ```bash 212 | certbot certonly --manual --preferred-challenges dns 213 | ``` 214 | 215 | **Note:** Please be sure to name it exactly like it is specified in the Nginx configuration file. 216 | 217 | ```bash 218 | certbot certificates 219 | ``` 220 | 221 | Fire up 222 | ------- 223 | 224 | Now when all the components are in place we are ready to fire up the services. 225 | 226 | Generate webhook's main configuration file => `/etc/webhook/hooks.json` 227 | 228 | **Note:** This script has to be executed only once and before `webhook` service is started for the first time. 229 | 230 | ```bash 231 | wh.py 232 | ``` 233 | 234 | Enable and start `webhook` and restart `nginx` service. 235 | 236 | ```bash 237 | systemctl enable --now webhook 238 | systemctl restart nginx 239 | ``` 240 | 241 | Check if everything is running without errors. 242 | 243 | ```bash 244 | journalctl -fu webhook 245 | journalctl -fu nginx 246 | ``` 247 | 248 | Usage 249 | ----- 250 | 251 | Generate client configuration. 252 | 253 | ```bash 254 | wgcg-gen.sh add test@yourdomain.com 10.0.0.2 255 | ``` 256 | 257 | Remove client configuration. 258 | 259 | ```bash 260 | wgcg-gen.sh remove test@yourdomain.com 261 | ``` 262 | 263 | List existing clients. 264 | 265 | ```bash 266 | wgcg-gen.sh list 267 | ``` 268 | 269 | When new client is added, URL where client can download configuration will be printed out. 270 | 271 | Example: 272 | 273 | https://wgcg.yourdomain.com/hooks/wgcg?servername=server1&username=test@yourdomain.com&token=QwhRKi2WNz9UFqqUE6nZsNckQ2jDQtGfqqvCl6kC 274 | -------------------------------------------------------------------------------- /examples/webhook/bin/wgcg-gen.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Author: Milos Buncic 3 | # Date: 2020/06/10 4 | # Description: Generate and sync WireGuard configuration and publish the configuration via HTTP endpoint 5 | 6 | export WGCG_CONFIG_FILE="${HOME}/wireguard/wgcg/wg-1.conf" 7 | source ${WGCG_CONFIG_FILE} 8 | 9 | WEBHOOK_ENDPOINT="https://wgcg.yourdomain.com/hooks/wgcg?servername=${WGCG_SERVER_NAME}" 10 | WEBHOOK_CONFIG_PATH="/etc/webhook" 11 | 12 | 13 | help() { 14 | echo "Usage:" 15 | echo " $(basename ${0}) options" 16 | echo 17 | echo "Options:" 18 | echo " list List existing clients" 19 | echo " add client_name private_ip Add a new client" 20 | echo " remove client_name Remove client" 21 | echo " sync Synchronize server configuration" 22 | echo " help Show this help" 23 | } 24 | 25 | 26 | genpass() { 27 | local length=${1:-40} 28 | local re='^[0-9]*$' 29 | 30 | if [[ ${length} =~ ${re} ]]; then 31 | # LC_CTYPE=C required if running on MacOS 32 | LC_CTYPE=C tr -dc 'A-Za-z0-9' < /dev/urandom | head -c ${length} | xargs 33 | else 34 | return 1 35 | fi 36 | } 37 | 38 | 39 | gen_webhook_config() { 40 | local client_name=${1} 41 | local auth_file=${2} 42 | local client_token=$(genpass) 43 | 44 | cat > ${auth_file} < 15 | 16 | wgcg 17 | 18 | 71 | 72 | 73 | 74 |

'"${USERNAME}"'

75 |
76 | 77 |
78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 |

# Install required packages
brew install wireguard-tools gpg

# Create encrypted configuration file
cat > /tmp/'"${SERVER_NAME}"'.conf.asc <<"EOF"
'$(sed "s/$/
/" ${CONFIG_GPG_FILE} | tr -d "\n")'EOF

# Decrypt configuration file and set permissions
mkdir -p /usr/local/etc/wireguard && \
gpg -o /usr/local/etc/wireguard/'"${SERVER_NAME}"'.conf -d /tmp/'"${SERVER_NAME}"'.conf.asc && \
chmod 600 /usr/local/etc/wireguard/'"${SERVER_NAME}"'.conf && \
rm -f /tmp/'"${SERVER_NAME}"'.conf.asc

# Bring up the VPN tunnel
wg-quick up '"${SERVER_NAME}"'

# Bring down the VPN tunnel
wg-quick down '"${SERVER_NAME}"'

95 |
96 | 97 | 98 | 99 | ' 100 | -------------------------------------------------------------------------------- /examples/webhook/bin/wgcg-html-qrcode.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Author: Milos Buncic 3 | # Date: 2020/06/10 4 | # Description: Generate HTML page with embedded base64 encoded QRCode 5 | 6 | SERVER_NAME=${1} 7 | USERNAME=${2} 8 | 9 | QRCODE_FILE="/root/wireguard/${SERVER_NAME}/client-${USERNAME}.conf.png" 10 | if [[ ! -f "${QRCODE_FILE}" ]]; then 11 | exit 1 12 | fi 13 | 14 | BASE64_QRCODE=$(echo -n `base64 "${QRCODE_FILE}"` | sed 's/ \+//g') 15 | 16 | echo ' 17 | 18 | wgcg 19 | 20 | 70 | 71 | 72 | 73 |

'"${USERNAME}"'

74 | QRCode 75 |
76 | 77 | 78 | 79 | 80 |
81 | 82 | 83 | 84 | 85 |
86 |
87 | 88 | 102 | 103 | 104 | 105 | ' 106 | -------------------------------------------------------------------------------- /examples/webhook/bin/wh.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Author: Milos Buncic 3 | # Date: 2020/06/10 4 | # Description: Generate main configuration file (hooks.json) for webhook service 5 | 6 | import os 7 | import sys 8 | import json 9 | 10 | # Webhook working directory 11 | WH_DIR = '/etc/webhook' 12 | # Webhook configuration file 13 | WH_CONFIG = '{}/hooks.json'.format(WH_DIR) 14 | 15 | 16 | def writefile(filename, text): 17 | """ Write JSON object to file """ 18 | try: 19 | with open(filename, 'w') as f: 20 | f.write(json.dumps(text, indent=2, ensure_ascii=False, sort_keys=True)) 21 | f.write('\n') 22 | except IOError: 23 | print('Error while writing to file') 24 | 25 | 26 | def readfile(filename): 27 | """ Read JSON from file and return dict """ 28 | try: 29 | with open(filename, 'r') as f: 30 | return json.load(f) 31 | except IOError: 32 | print('Error while reading from file') 33 | 34 | 35 | def main(): 36 | main_file = '{}/main.json'.format(WH_DIR) 37 | if not os.path.isfile(main_file): 38 | print('{} file does not exist!'.format(main_file)) 39 | sys.exit(1) 40 | 41 | mf = readfile(main_file) 42 | 43 | files = os.listdir(WH_DIR) 44 | l = [] 45 | for f in files: 46 | auth_file = '{}/{}'.format(WH_DIR, f) 47 | if f == 'main.json' or f == 'hooks.json' or os.path.isdir(auth_file) or os.path.splitext(auth_file)[1] != '.json': 48 | continue 49 | 50 | l.append(readfile(auth_file)) 51 | 52 | for i in range(0, len(mf)): 53 | mf[i]['trigger-rule']['or'] = l 54 | 55 | writefile(WH_CONFIG, mf) 56 | 57 | 58 | if __name__ == '__main__': 59 | main() 60 | -------------------------------------------------------------------------------- /images/wgcg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psyhomb/wireguard-tools/570b027dc6251eb5239bf36e531cf390d5b7bf70/images/wgcg.png -------------------------------------------------------------------------------- /modules/wgcg-install-wireguard.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Author: Milos Buncic 3 | # Date: 2019/10/01 4 | # Description: Prepare Wireguard server 5 | 6 | set -e 7 | 8 | echo "Installing Wireguard and required dependencies on the server, please wait..." 9 | echo 10 | 11 | # Installing wireguard kernel module and required dependencies 12 | # DEPRECATION NOTICE: WireGuard packages have now moved into official Ubuntu repository (Ubuntu 20.04, 19.10, 18.04, and 16.04) 13 | #add-apt-repository ppa:wireguard/wireguard 14 | apt-get update && apt-get install -y linux-headers-$(uname -r) wireguard 15 | mkdir -p /etc/wireguard 16 | 17 | # Allow module to be loaded at boot time 18 | echo wireguard > /etc/modules-load.d/wgcg.conf 19 | 20 | # Load the module 21 | echo -e "\nLoading module..." 22 | echo -e "NOTE: If error encountered please try upgrading the Linux kernel to the latest version available and reboot\n" 23 | modprobe -v wireguard 24 | 25 | ### Generate wgfw.sh script - will be used for adding required firewall rules 26 | cat > /usr/local/bin/wgfw.sh < \${proc_ip_forward} 59 | ;; 60 | 'false'|'0') 61 | echo 0 > \${proc_ip_forward} 62 | esac 63 | } 64 | 65 | # Delete user defined firewall chain 66 | flush_custom_rules() { 67 | ( 68 | \${IPTABLES_BIN} -F \${CHAIN_NAME} 69 | \${IPTABLES_BIN} -D FORWARD -j \${CHAIN_NAME} 70 | \${IPTABLES_BIN} -X \${CHAIN_NAME} 71 | ) &> /dev/null 72 | 73 | return 0 74 | } 75 | 76 | # Set server startup firewall rules 77 | rules() { 78 | local action=\${1} 79 | 80 | case \${action} in 81 | 'add'|'a') 82 | action="-A" 83 | ;; 84 | 'insert'|'i') 85 | action="-I" 86 | ;; 87 | 'del'|'d') 88 | action="-D" 89 | esac 90 | 91 | # Rules below this line will be set only at server startup. 92 | \${IPTABLES_BIN} -t nat \${action} POSTROUTING -o \${PRIVATE_INTERFACE} -j MASQUERADE 93 | } 94 | 95 | # Set custom firewall rules 96 | set_custom_rules() { 97 | local rule_args 98 | local rule_action 99 | local rule_rest 100 | local rule_cmd 101 | local check_output 102 | local check_status 103 | local malformed_rules_counter 104 | 105 | if ! \${IPTABLES_BIN} -L \${CHAIN_NAME} &> /dev/null; then 106 | \${IPTABLES_BIN} -N \${CHAIN_NAME} 107 | \${IPTABLES_BIN} -I FORWARD -j \${CHAIN_NAME} 108 | \${IPTABLES_BIN} -A \${CHAIN_NAME} -j RETURN 109 | fi 110 | 111 | if [[ ! -f \${CUSTOM_RULES_FILE} ]] || [[ -z "\$(grep -Ev '^( *#|$)' \${CUSTOM_RULES_FILE})" ]]; then 112 | echo -e "\${GREEN}INFO\${NONE}: \${CUSTOM_RULES_FILE} does not exist or empty, no custom firewall rules will be applied" 113 | return 0 114 | fi 115 | 116 | malformed_rules_counter=0 117 | while read rule; do 118 | rule_args=(\$(echo \${rule})) 119 | rule_action=\${rule_args[0]} 120 | rule_rest=\${rule_args[@]:2:\$((\${#rule_args[@]}-1))} 121 | 122 | check_output=\$(\${IPTABLES_BIN} -C \${CHAIN_NAME} \${rule_rest} 2>&1) 123 | check_status=\${?} 124 | 125 | if [[ \${check_status} -eq 2 ]]; then 126 | echo -e "\${RED}ERROR\${NONE}: firewall rule command malformed: \${check_output} => \${RED}\${IPTABLES_BIN} \${rule_action} \${CHAIN_NAME} \${rule_rest}\${NONE}" 127 | let malformed_rules_counter++ 128 | fi 129 | done < <(grep -Ev '^( *#|$)' \${CUSTOM_RULES_FILE}) 130 | 131 | if [[ \${malformed_rules_counter} -gt 0 ]]; then 132 | echo -e "\n\${GREEN}INFO\${NONE}: 0 firewall rules have been applied due to \${RED}\${malformed_rules_counter}\${NONE} malformed rule/s, fix all the malformed rules and try again" 133 | return 2 134 | fi 135 | 136 | while read rule; do 137 | rule_args=(\$(echo \${rule})) 138 | rule_action=\${rule_args[0]} 139 | rule_rest=\${rule_args[@]:2:\$((\${#rule_args[@]}-1))} 140 | 141 | check_output=\$(\${IPTABLES_BIN} -C \${CHAIN_NAME} \${rule_rest} 2>&1) 142 | check_status=\${?} 143 | 144 | rule_cmd="\${IPTABLES_BIN} \${rule_action} \${CHAIN_NAME} \${rule_rest}" 145 | if [[ \${check_status} -eq 1 ]]; then 146 | if [[ \${rule_action} =~ ^-[AI]$ ]]; then 147 | \${rule_cmd} 148 | elif [[ \${rule_action} == "-D" ]]; then 149 | echo -e "\${YELLOW}WARNING\${NONE}: firewall rule does not exist, delete action will be skipped: \${check_output} => \${GREEN}\${rule_cmd}\${NONE}" 150 | fi 151 | elif [[ \${check_status} -eq 0 ]] && [[ \${rule_action} == "-D" ]]; then 152 | \${rule_cmd} 153 | fi 154 | done < <(grep -Ev '^( *#|$)' \${CUSTOM_RULES_FILE}) 155 | 156 | \${IPTABLES_BIN} -D \${CHAIN_NAME} -j RETURN &> /dev/null && \${IPTABLES_BIN} -A \${CHAIN_NAME} -j RETURN 157 | } 158 | 159 | case \${1} in 160 | 'add') 161 | ip_forward 1 162 | rules add 163 | set_custom_rules 164 | ;; 165 | 'set') 166 | ip_forward 1 167 | set_custom_rules 168 | ;; 169 | 'del') 170 | ip_forward 0 171 | rules del 172 | flush_custom_rules 173 | ;; 174 | *) 175 | echo "Usage: \$(basename \${0}) add|set|del" 176 | esac 177 | EOF 178 | 179 | ### Enable permanent IP forwarding (routing) 180 | #cat > /etc/sysctl.d/10-wgcg.conf <<'EOF' && sysctl -p /etc/sysctl.d/10-wgcg.conf 181 | ## Enable IP forwarding (routing) - WireGuard 182 | #net.ipv4.ip_forward = 1 183 | #EOF 184 | -------------------------------------------------------------------------------- /monitoring/wireguard-dashboard.json: -------------------------------------------------------------------------------- 1 | { 2 | "__inputs": [ 3 | { 4 | "name": "DS_PROMETHEUS", 5 | "label": "Prometheus", 6 | "description": "", 7 | "type": "datasource", 8 | "pluginId": "prometheus", 9 | "pluginName": "Prometheus" 10 | } 11 | ], 12 | "__requires": [ 13 | { 14 | "type": "grafana", 15 | "id": "grafana", 16 | "name": "Grafana", 17 | "version": "7.0.5" 18 | }, 19 | { 20 | "type": "panel", 21 | "id": "graph", 22 | "name": "Graph", 23 | "version": "" 24 | }, 25 | { 26 | "type": "datasource", 27 | "id": "prometheus", 28 | "name": "Prometheus", 29 | "version": "1.0.0" 30 | }, 31 | { 32 | "type": "panel", 33 | "id": "table-old", 34 | "name": "Table (old)", 35 | "version": "" 36 | } 37 | ], 38 | "annotations": { 39 | "list": [ 40 | { 41 | "builtIn": 1, 42 | "datasource": "-- Grafana --", 43 | "enable": true, 44 | "hide": true, 45 | "iconColor": "rgba(0, 211, 255, 1)", 46 | "name": "Annotations & Alerts", 47 | "type": "dashboard" 48 | } 49 | ] 50 | }, 51 | "editable": true, 52 | "gnetId": null, 53 | "graphTooltip": 0, 54 | "id": null, 55 | "iteration": 1602541218129, 56 | "links": [], 57 | "panels": [ 58 | { 59 | "cacheTimeout": null, 60 | "columns": [ 61 | { 62 | "$$hashKey": "object:197", 63 | "text": "Current", 64 | "value": "current" 65 | } 66 | ], 67 | "datasource": "${DS_PROMETHEUS}", 68 | "description": "", 69 | "fieldConfig": { 70 | "defaults": { 71 | "custom": {} 72 | }, 73 | "overrides": [] 74 | }, 75 | "fontSize": "100%", 76 | "gridPos": { 77 | "h": 16, 78 | "w": 7, 79 | "x": 0, 80 | "y": 0 81 | }, 82 | "id": 7, 83 | "links": [], 84 | "pageSize": null, 85 | "pluginVersion": "6.2.1", 86 | "scroll": true, 87 | "showHeader": true, 88 | "sort": { 89 | "col": 1, 90 | "desc": false 91 | }, 92 | "styles": [ 93 | { 94 | "$$hashKey": "object:340", 95 | "alias": "", 96 | "align": "auto", 97 | "colorMode": "row", 98 | "colors": [ 99 | "rgba(50, 172, 45, 0.97)", 100 | "rgba(237, 129, 40, 0.89)", 101 | "rgba(245, 54, 54, 0.9)" 102 | ], 103 | "dateFormat": "YYYY-MM-DD HH:mm:ss", 104 | "decimals": 0, 105 | "mappingType": 1, 106 | "pattern": "Current", 107 | "thresholds": [ 108 | "600", 109 | "3600" 110 | ], 111 | "type": "number", 112 | "unit": "s" 113 | } 114 | ], 115 | "targets": [ 116 | { 117 | "expr": "time() - wireguard_latest_handshake_seconds{node=\"$node\"}", 118 | "format": "time_series", 119 | "interval": "", 120 | "intervalFactor": 1, 121 | "legendFormat": "{{allowed_ips}} ({{friendly_name}})", 122 | "refId": "A" 123 | } 124 | ], 125 | "timeFrom": null, 126 | "timeShift": null, 127 | "title": "Latest Handshake", 128 | "transform": "timeseries_aggregations", 129 | "type": "table-old" 130 | }, 131 | { 132 | "aliasColors": { 133 | "sent 10.70.0.2": "green" 134 | }, 135 | "bars": false, 136 | "dashLength": 10, 137 | "dashes": false, 138 | "datasource": "${DS_PROMETHEUS}", 139 | "fieldConfig": { 140 | "defaults": { 141 | "custom": {} 142 | }, 143 | "overrides": [] 144 | }, 145 | "fill": 1, 146 | "fillGradient": 0, 147 | "gridPos": { 148 | "h": 8, 149 | "w": 17, 150 | "x": 7, 151 | "y": 0 152 | }, 153 | "hiddenSeries": false, 154 | "id": 8, 155 | "legend": { 156 | "alignAsTable": false, 157 | "avg": true, 158 | "current": false, 159 | "hideEmpty": true, 160 | "hideZero": true, 161 | "max": true, 162 | "min": true, 163 | "rightSide": true, 164 | "show": true, 165 | "total": false, 166 | "values": true 167 | }, 168 | "lines": true, 169 | "linewidth": 1, 170 | "links": [], 171 | "nullPointMode": "null", 172 | "options": { 173 | "dataLinks": [] 174 | }, 175 | "percentage": false, 176 | "pointradius": 2, 177 | "points": false, 178 | "renderer": "flot", 179 | "seriesOverrides": [], 180 | "spaceLength": 10, 181 | "stack": false, 182 | "steppedLine": false, 183 | "targets": [ 184 | { 185 | "expr": "irate(wireguard_sent_bytes_total{node=\"$node\"}[5m])", 186 | "format": "time_series", 187 | "instant": false, 188 | "interval": "", 189 | "intervalFactor": 1, 190 | "legendFormat": "{{allowed_ips}} ({{friendly_name}})", 191 | "refId": "A" 192 | } 193 | ], 194 | "thresholds": [], 195 | "timeFrom": null, 196 | "timeRegions": [], 197 | "timeShift": null, 198 | "title": "Sent Bytes", 199 | "tooltip": { 200 | "shared": true, 201 | "sort": 0, 202 | "value_type": "individual" 203 | }, 204 | "type": "graph", 205 | "xaxis": { 206 | "buckets": null, 207 | "mode": "time", 208 | "name": null, 209 | "show": true, 210 | "values": [] 211 | }, 212 | "yaxes": [ 213 | { 214 | "$$hashKey": "object:975", 215 | "format": "Bps", 216 | "label": null, 217 | "logBase": 1, 218 | "max": null, 219 | "min": null, 220 | "show": true 221 | }, 222 | { 223 | "$$hashKey": "object:976", 224 | "format": "short", 225 | "label": null, 226 | "logBase": 1, 227 | "max": null, 228 | "min": null, 229 | "show": true 230 | } 231 | ], 232 | "yaxis": { 233 | "align": false, 234 | "alignLevel": null 235 | } 236 | }, 237 | { 238 | "aliasColors": { 239 | "sent 10.70.0.2": "green" 240 | }, 241 | "bars": false, 242 | "dashLength": 10, 243 | "dashes": false, 244 | "datasource": "${DS_PROMETHEUS}", 245 | "fieldConfig": { 246 | "defaults": { 247 | "custom": {} 248 | }, 249 | "overrides": [] 250 | }, 251 | "fill": 1, 252 | "fillGradient": 0, 253 | "gridPos": { 254 | "h": 8, 255 | "w": 17, 256 | "x": 7, 257 | "y": 8 258 | }, 259 | "hiddenSeries": false, 260 | "id": 4, 261 | "legend": { 262 | "alignAsTable": false, 263 | "avg": true, 264 | "current": false, 265 | "hideEmpty": true, 266 | "hideZero": true, 267 | "max": true, 268 | "min": true, 269 | "rightSide": true, 270 | "show": true, 271 | "total": false, 272 | "values": true 273 | }, 274 | "lines": true, 275 | "linewidth": 1, 276 | "links": [], 277 | "nullPointMode": "null", 278 | "options": { 279 | "dataLinks": [] 280 | }, 281 | "percentage": false, 282 | "pointradius": 2, 283 | "points": false, 284 | "renderer": "flot", 285 | "seriesOverrides": [], 286 | "spaceLength": 10, 287 | "stack": false, 288 | "steppedLine": false, 289 | "targets": [ 290 | { 291 | "expr": "irate(wireguard_received_bytes_total{node=\"$node\"}[5m])", 292 | "format": "time_series", 293 | "instant": false, 294 | "interval": "", 295 | "intervalFactor": 1, 296 | "legendFormat": "{{allowed_ips}} ({{friendly_name}})", 297 | "refId": "A" 298 | } 299 | ], 300 | "thresholds": [], 301 | "timeFrom": null, 302 | "timeRegions": [], 303 | "timeShift": null, 304 | "title": "Received Bytes", 305 | "tooltip": { 306 | "shared": true, 307 | "sort": 0, 308 | "value_type": "individual" 309 | }, 310 | "type": "graph", 311 | "xaxis": { 312 | "buckets": null, 313 | "mode": "time", 314 | "name": null, 315 | "show": true, 316 | "values": [] 317 | }, 318 | "yaxes": [ 319 | { 320 | "$$hashKey": "object:975", 321 | "format": "Bps", 322 | "label": null, 323 | "logBase": 1, 324 | "max": null, 325 | "min": null, 326 | "show": true 327 | }, 328 | { 329 | "$$hashKey": "object:976", 330 | "format": "short", 331 | "label": null, 332 | "logBase": 1, 333 | "max": null, 334 | "min": null, 335 | "show": true 336 | } 337 | ], 338 | "yaxis": { 339 | "align": false, 340 | "alignLevel": null 341 | } 342 | } 343 | ], 344 | "refresh": "30s", 345 | "schemaVersion": 25, 346 | "style": "dark", 347 | "tags": [ 348 | "prometheus", 349 | "wireguard", 350 | "network" 351 | ], 352 | "templating": { 353 | "list": [ 354 | { 355 | "allValue": null, 356 | "current": {}, 357 | "datasource": "${DS_PROMETHEUS}", 358 | "definition": "label_values(wireguard_latest_handshake_seconds{job=\"wireguard-exporter\"}, node)", 359 | "hide": 0, 360 | "includeAll": false, 361 | "label": "Node:", 362 | "multi": false, 363 | "name": "node", 364 | "options": [], 365 | "query": "label_values(wireguard_latest_handshake_seconds{job=\"wireguard-exporter\"}, node)", 366 | "refresh": 1, 367 | "regex": "", 368 | "skipUrlSync": false, 369 | "sort": 0, 370 | "tagValuesQuery": "", 371 | "tags": [], 372 | "tagsQuery": "", 373 | "type": "query", 374 | "useTags": false 375 | } 376 | ] 377 | }, 378 | "time": { 379 | "from": "now-30m", 380 | "to": "now" 381 | }, 382 | "timepicker": { 383 | "refresh_intervals": [ 384 | "10s", 385 | "30s", 386 | "1m", 387 | "5m", 388 | "15m", 389 | "30m", 390 | "1h", 391 | "2h", 392 | "1d" 393 | ], 394 | "time_options": [ 395 | "5m", 396 | "15m", 397 | "1h", 398 | "6h", 399 | "12h", 400 | "24h", 401 | "2d", 402 | "7d", 403 | "30d" 404 | ] 405 | }, 406 | "timezone": "", 407 | "title": "WireGuard", 408 | "uid": "c0T_8RkZz", 409 | "version": 37 410 | } 411 | -------------------------------------------------------------------------------- /wgcg-docker.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Author: Milos Buncic 3 | # Date: 2020/06/04 4 | # Description: Run Wireguard config generator in Docker container 5 | 6 | DOCKER_IMAGE="wgcg:latest" 7 | 8 | CONTAINER_HOME="/home/${USER}" 9 | if [[ ${UID} -eq 0 ]]; then 10 | CONTAINER_HOME="/root" 11 | fi 12 | 13 | OPTIONS=" 14 | -e WGCG_CONFIG_FILE 15 | -v ${HOME}/.ssh:${CONTAINER_HOME}/.ssh 16 | -v ${HOME}/wireguard:${CONTAINER_HOME}/wireguard 17 | " 18 | if [[ -n ${SSH_AUTH_SOCK} ]]; then 19 | OPTIONS=" 20 | ${OPTIONS} 21 | -e SSH_AUTH_SOCK=${CONTAINER_HOME}/ssh-agent.sock 22 | -v ${SSH_AUTH_SOCK}:${CONTAINER_HOME}/ssh-agent.sock 23 | " 24 | fi 25 | 26 | docker run -it --rm --user ${UID} ${OPTIONS} ${DOCKER_IMAGE} ${@} 27 | -------------------------------------------------------------------------------- /wgcg.conf: -------------------------------------------------------------------------------- 1 | # Server name (wireguard interface name e.g. wg0 || wg1 || wg2) 2 | WGCG_SERVER_NAME="wg0" 3 | 4 | # HostMin to HostMax range can be used to assign IP addresses to WireGuard clients 5 | # e.g. ./wgcg.sh -c foo 10.0.0.2 6 | # 7 | # Network: 10.0.0.0/22 8 | # HostMin: 10.0.0.1 9 | # HostMax: 10.0.3.254 10 | # HostIDs: 1022 11 | # 12 | # WireGuard server private IP address (with optional CIDR - default: 22) 13 | WGCG_SERVER_WG_IP="10.0.0.1" 14 | 15 | # Static server port 16 | WGCG_SERVER_PORT="52001" 17 | 18 | # Server's public IP or FQDN 19 | WGCG_SERVER_PUBLIC_IP="wg.yourdomain.com" 20 | 21 | # SSH server IP address (default: ${WGCG_SERVER_PUBLIC_IP}) (optional) 22 | # Note: This option can be used in case SSH server is listening on different IP address, 23 | # if not specified, ${WGCG_SERVER_PUBLIC_IP} will be used instead 24 | WGCG_SERVER_SSH_IP="" 25 | 26 | # SSH server port (optional) 27 | WGCG_SERVER_SSH_PORT="22" 28 | 29 | # Space separated list of DNS IPs (default: 1.1.1.1 1.0.0.1) (optional) 30 | WGCG_CLIENT_DNS_IPS="1.1.1.1 1.0.0.1" 31 | 32 | # Space separated list of subnets (with CIDR) required for split-tunneling (default: 0.0.0.0/0) (optional) 33 | WGCG_CLIENT_ALLOWED_IPS="0.0.0.0/0" 34 | 35 | # All configuration and key files will be stored in this directory 36 | WGCG_WORKING_DIR="${HOME}/wireguard/${WGCG_SERVER_NAME}" 37 | -------------------------------------------------------------------------------- /wgcg.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Author: Milos Buncic 3 | # Date: 2019/09/25 4 | # Description: Wireguard config generator 5 | 6 | ### Import global variables from configuration file 7 | CONFIG_FILE="${HOME}/wireguard/wgcg/wgcg.conf" 8 | for CF in "${WGCG_CONFIG_FILE}" "${CONFIG_FILE}"; do 9 | if [[ -f "${CF}" ]]; then 10 | CONFIG_FILE="${CF}" 11 | source "${CONFIG_FILE}" 12 | break 13 | fi 14 | done 15 | 16 | ### Global variables 17 | # Default options 18 | # Server name (wireguard interface name e.g. wg0 || wg1 || wg2) 19 | SERVER_NAME=${WGCG_SERVER_NAME} 20 | # VPN (WG) IP private address 21 | SERVER_WG_IP=${WGCG_SERVER_WG_IP} 22 | # Static server port 23 | SERVER_PORT=${WGCG_SERVER_PORT} 24 | # Server's public IP or FQDN 25 | # To discover server's public IP use: curl -sSL https://ifconfig.co 26 | SERVER_PUBLIC_IP=${WGCG_SERVER_PUBLIC_IP} 27 | # SSH server IP address (default: ${WGCG_SERVER_PUBLIC_IP}) 28 | # Note: This option can be used in case SSH server is listening on different IP address, 29 | # if not specified, ${WGCG_SERVER_PUBLIC_IP} will be used instead 30 | SERVER_SSH_IP=${WGCG_SERVER_SSH_IP} 31 | # SSH server port (default: 22) 32 | SERVER_SSH_PORT=${WGCG_SERVER_SSH_PORT} 33 | # File with custom firewall rules in iptables compatible format that will be applied in idempotent manner on the server side at server startup time or each time `wgcg.sh --sync` command is executed 34 | SERVER_FW_CUSTOM_RULES_FILE=${WGCG_SERVER_FW_CUSTOM_RULES_FILE:-"${HOME}/wireguard/wgcg/wgfw.rules"} 35 | # Space separated list of DNS IPs (default: 1.1.1.1 1.0.0.1) 36 | CLIENT_DNS_IPS=${WGCG_CLIENT_DNS_IPS} 37 | # Space separated list of subnets (with CIDR) required for split-tunneling (default: 0.0.0.0/0) 38 | CLIENT_ALLOWED_IPS=${WGCG_CLIENT_ALLOWED_IPS} 39 | # Working directory where all generated files will be stored 40 | WORKING_DIR=${WGCG_WORKING_DIR} 41 | 42 | ### Text colors 43 | RED="\033[31m" 44 | GREEN="\033[32m" 45 | YELLOW="\033[33m" 46 | BLUE="\033[34m" 47 | NONE="\033[0m" 48 | 49 | ### Dependencies required by the script 50 | DEPS=( 51 | "wg" 52 | "gpg" 53 | "qrencode" 54 | "grepcidr" 55 | ) 56 | 57 | # Check if all dependencies are installed 58 | for DEP in ${DEPS[@]}; do 59 | if ! which ${DEP} &> /dev/null; then 60 | echo -e "${RED}ERROR${NONE}: ${BLUE}${DEP}${NONE} tool isn't installed!" 61 | STAT=1 62 | fi 63 | done 64 | [[ ${STAT} -eq 1 ]] && exit 1 65 | 66 | 67 | ### Functions 68 | # Show help 69 | help() { 70 | echo -e "${BLUE}Usage${NONE}:" 71 | echo -e " ${GREEN}$(basename ${0})${NONE} options" 72 | echo 73 | echo -e "${BLUE}Options${NONE}:" 74 | echo -e " ${GREEN}-P${NONE}|${GREEN}--sysprep${NONE} filename.sh Install WireGuard kernel module, required tools and scripts (will establish SSH connection with server)" 75 | echo -e " ${GREEN}-s${NONE}|${GREEN}--add-server-config${NONE} Generate server configuration" 76 | echo -e " ${GREEN}-c${NONE}|${GREEN}--add-client-config${NONE} client_name client_wg_ip Generate client configuration" 77 | echo -e " ${GREEN}-B${NONE}|${GREEN}--add-clients-batch${NONE} filename.csv[:rewrite|:norewrite] Generate configuration for multiple clients in batch mode" 78 | echo -e " Supported action modes are 'rewrite' or 'norewrite' (default)" 79 | echo -e " 'rewrite' action mean regenerate ALL, 'norewrite' mean generate only configs and keys for new clients" 80 | echo -e " ${GREEN}-e${NONE}|${GREEN}--encrypt-config${NONE} client_name [passphrase] Encrypt configuration file by using symmetric encryption (if passphrase not specified it will be generated - RECOMMENDED)" 81 | echo -e " ${GREEN}-d${NONE}|${GREEN}--decrypt-config${NONE} client_name Decrypt configuration file and print it out on stdout" 82 | echo -e " ${GREEN}-r${NONE}|${GREEN}--rm-client-config${NONE} client_name Remove client configuration" 83 | echo -e " ${GREEN}-q${NONE}|${GREEN}--gen-qr-code${NONE} client_name [-] Generate QR code (PNG format) from client configuration file, if - is used, QR code will be printed out on stdout instead" 84 | echo -e " ${GREEN}-l${NONE}|${GREEN}--list-used-ips${NONE} List all clients IPs that are currently in use" 85 | echo -e " ${GREEN}-S${NONE}|${GREEN}--sync${NONE} Synchronize server configuration (will establish SSH connection with server)" 86 | echo -e " ${GREEN}-h${NONE}|${GREEN}--help${NONE} Show this help" 87 | echo 88 | echo -e "${BLUE}Current default options${NONE}:" 89 | echo -e " WGCG_SERVER_NAME=${GREEN}\"${SERVER_NAME}\"${NONE}" 90 | echo -e " WGCG_SERVER_WG_IP=${GREEN}\"${SERVER_WG_IP}\"${NONE}" 91 | echo -e " WGCG_SERVER_PORT=${GREEN}\"${SERVER_PORT}\"${NONE}" 92 | echo -e " WGCG_SERVER_PUBLIC_IP=${GREEN}\"${SERVER_PUBLIC_IP}\"${NONE}" 93 | [[ -n ${SERVER_SSH_IP} ]] && echo -e " WGCG_SERVER_SSH_IP=${GREEN}\"${SERVER_SSH_IP}\"${NONE}" 94 | [[ -n ${SERVER_SSH_PORT} ]] && echo -e " WGCG_SERVER_SSH_PORT=${GREEN}\"${SERVER_SSH_PORT}\"${NONE}" 95 | [[ -n ${CLIENT_DNS_IPS} ]] && echo -e " WGCG_CLIENT_DNS_IPS=${GREEN}\"${CLIENT_DNS_IPS}\"${NONE}" 96 | [[ -n ${CLIENT_ALLOWED_IPS} ]] && echo -e " WGCG_CLIENT_ALLOWED_IPS=${GREEN}\"${CLIENT_ALLOWED_IPS}\"${NONE}" 97 | echo -e " WGCG_WORKING_DIR=${GREEN}\"${WORKING_DIR}\"${NONE}" 98 | } 99 | 100 | 101 | # Check mandatory global variables 102 | check_variables() { 103 | if [[ -z ${SERVER_NAME} ]] || [[ -z ${SERVER_WG_IP} ]] || [[ -z ${SERVER_PORT} ]] || [[ -z ${SERVER_PUBLIC_IP} ]] || [[ -z ${WORKING_DIR} ]]; then 104 | echo -e "${RED}ERROR${NONE}: Missing mandatory variables, please check and modify ${GREEN}${CONFIG_FILE}${NONE} configuration file accordingly!" 105 | help 106 | return 1 107 | fi 108 | } 109 | 110 | check_variables || exit ${?} 111 | 112 | 113 | # Validator for IP addresses and service ports 114 | validator() { 115 | local mode="${1}" 116 | local value="${2}" 117 | local ret regex ip_octets fqdn cidr hostid netid 118 | 119 | ret=0 120 | case ${mode} in 121 | 'ipaddress') 122 | # validator ipaddress 1.1.1.1 123 | regex='^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$' 124 | ip_address=${value} 125 | ip_octets=(${value//./ }) 126 | 127 | if [[ ${ip_address} =~ ${regex} ]]; then 128 | for ip_octet in ${ip_octets[@]}; do 129 | if [[ ${ip_octet} -gt 255 ]]; then 130 | ret=1 131 | break 132 | fi 133 | 134 | if [[ ${#ip_octet} -gt 1 ]] && [[ ${ip_octet:0:1} -eq 0 ]]; then 135 | ret=1 136 | break 137 | fi 138 | done 139 | else 140 | ret=1 141 | fi 142 | ;; 143 | 'svcport') 144 | # validator svcport 80 145 | regex='^[0-9]{1,5}$' 146 | svc_port=${value} 147 | 148 | if [[ ! ${svc_port} =~ ${regex} ]] || [[ ${svc_port} -gt 65535 ]]; then 149 | ret=1 150 | fi 151 | ;; 152 | 'fqdn') 153 | # validator fqdn test.yourdomain.com 154 | regex='(?=^.{4,253}$)(^((?!-)[a-zA-Z0-9-]{0,62}[a-zA-Z0-9]\.)+[a-zA-Z]{2,63}$)' 155 | fqdn=${value} 156 | 157 | if ! echo ${fqdn} | grep -Pq ${regex}; then 158 | ret=1 159 | fi 160 | ;; 161 | 'cidr') 162 | # validator cidr 24 163 | regex='^[0-9]{1,2}$' 164 | cidr=${value} 165 | 166 | if [[ ! ${cidr} =~ ${regex} ]] || [[ ${cidr} -gt 32 ]]; then 167 | ret=1 168 | fi 169 | ;; 170 | 'in_cidr') 171 | # validator in_cidr "10.0.0.2 10.0.0.1/22" 172 | value=(${value}) 173 | hostid=${value[0]} 174 | netid=${value[1]} 175 | 176 | if ! echo ${hostid} | grepcidr -e ${netid} &> /dev/null; then 177 | ret=1 178 | fi 179 | esac 180 | 181 | return ${ret} 182 | } 183 | 184 | 185 | # Generate password of specified length 186 | genpass() { 187 | local length=${1:-40} 188 | local re='^[0-9]*$' 189 | 190 | if [[ ${length} =~ ${re} ]]; then 191 | # LC_CTYPE=C required if running on MacOS 192 | LC_CTYPE=C tr -dc 'A-Za-z0-9#$!@:/%' < /dev/urandom 2> /dev/null | head -c ${length} | xargs 193 | else 194 | return 1 195 | fi 196 | } 197 | 198 | 199 | # Encrypt configuration file by using symmetric encryption 200 | encrypt() { 201 | local client_name="${1}" 202 | local passphrase="${2:-$(genpass)}" 203 | 204 | local client_config="${WORKING_DIR}/client-${client_name}.conf" 205 | local client_config_asc="${client_config}.asc" 206 | 207 | if [[ ! -f ${client_config} ]]; then 208 | echo -e "${RED}ERROR${NONE}: Client config ${BLUE}${client_config}${NONE} could not be found!" 209 | exit 1 210 | fi 211 | 212 | gpg \ 213 | --yes \ 214 | --armor \ 215 | --pinentry-mode loopback \ 216 | --passphrase "${passphrase}" \ 217 | --symmetric ${client_config} 2> /dev/null 218 | 219 | if [[ ${?} -eq 0 ]]; then 220 | chmod 600 ${client_config_asc} 221 | echo -e "${GREEN}INFO${NONE}: Client config ${BLUE}${client_config}${NONE} => ${BLUE}${client_config_asc##*/}${NONE} has been successfully encrypted with following passphrase: ${RED}${passphrase}${NONE}" 222 | echo -e "${GREEN}INFO${NONE}: Client can use gpg tool to decrypt configuration file: ${GREEN}gpg -o ${client_config##*/} -d ${client_config_asc##*/}${NONE}" 223 | else 224 | echo -e "${RED}ERROR${NONE}: Failed to encrypt ${BLUE}${client_config}${NONE} configuration file!" 225 | exit 1 226 | fi 227 | } 228 | 229 | 230 | # Decrypt configuration file and print it out on stdout 231 | decrypt() { 232 | local client_name="${1}" 233 | 234 | local client_config="${WORKING_DIR}/client-${client_name}.conf" 235 | local client_config_asc="${client_config}.asc" 236 | 237 | if [[ ! -f ${client_config_asc} ]]; then 238 | echo -e "${RED}ERROR${NONE}: Encrypted client config ${BLUE}${client_config_asc}${NONE} could not be found!" 239 | exit 1 240 | fi 241 | 242 | gpg --decrypt ${client_config_asc} 2> /dev/null 243 | 244 | if [[ ${?} -ne 0 ]]; then 245 | echo -e "${RED}ERROR${NONE}: Failed to decrypt ${BLUE}${client_config_asc}${NONE} configuration file!" 246 | exit 1 247 | fi 248 | } 249 | 250 | 251 | # Prepare system to run Wireguard 252 | wg_sysprep() { 253 | local sysprep_module="${1}" 254 | local server_ssh_ip="${2}" 255 | local server_ssh_port="${3:-22}" 256 | 257 | local server_prepared="${WORKING_DIR}/.sysprepared" 258 | 259 | if [[ -f ${server_prepared} ]]; then 260 | echo -e "${YELLOW}WARNING${NONE}: System has already been prepared to run Wireguard!" 261 | echo -ne "Are you sure you want to run it again? (${GREEN}yes${NONE}/${RED}no${NONE}): " 262 | read answer 263 | 264 | [[ ${answer} != "yes" ]] && exit 1 265 | fi 266 | 267 | if [[ ! -f ${sysprep_module} ]]; then 268 | echo -e "${RED}ERROR${NONE}: Sysprep module ${RED}${sysprep_module}${NONE} could not be found!" 269 | exit 1 270 | fi 271 | 272 | if ! validator ipaddress ${server_ssh_ip} && ! validator fqdn ${server_ssh_ip}; then 273 | echo -e "${RED}ERROR${NONE}: ${RED}${server_ssh_ip}${NONE} is not valid IP address nor FQDN!" 274 | exit 1 275 | fi 276 | 277 | local sysprep_module_script="${sysprep_module##*/}" 278 | cat ${sysprep_module} | ssh -p ${server_ssh_port} root@${server_ssh_ip} " 279 | cat > /usr/local/bin/${sysprep_module_script} && \ 280 | chmod +x /usr/local/bin/${sysprep_module_script} && \ 281 | /usr/local/bin/${sysprep_module_script} 282 | " 283 | if [[ ${?} -ne 0 ]]; then 284 | echo -e "${RED}ERROR${NONE}: Something went wrong, execution of sysprep module ${BLUE}${sysprep_module_script}${NONE} failed!" 285 | exit 1 286 | fi 287 | 288 | touch ${server_prepared} 289 | echo -e "${GREEN}INFO${NONE}: Sysprep module executed successfully, Wireguard server is now ready to receive configuration file!" 290 | } 291 | 292 | 293 | # Remove client configuration file 294 | remove_client_config() { 295 | local client_name="${1}" 296 | local server_name="${2}" 297 | 298 | local client_config="${WORKING_DIR}/client-${client_name}.conf" 299 | local server_config="${WORKING_DIR}/server-${server_name}.conf" 300 | 301 | if [[ -z ${client_name} ]] || [[ -z ${server_name} ]]; then 302 | help 303 | exit 1 304 | fi 305 | 306 | if [[ ! -f ${client_config} ]]; then 307 | echo -e "${RED}ERROR${NONE}: Client config ${BLUE}${client_config}${NONE} could not be found!" 308 | exit 1 309 | fi 310 | 311 | if [[ ! -f ${server_config} ]]; then 312 | echo -e "${RED}ERROR${NONE}: Server config ${BLUE}${server_config}${NONE} could not be found!" 313 | exit 1 314 | fi 315 | 316 | # Delete Peer block if client_name exist 317 | sed -i.backup "/### ${client_name} - START/,/### ${client_name} - END/d" ${server_config} 318 | # Delete all empty lines at end of file 319 | sed -i.backup -e :a -e '/^\n*$/{$d;N;ba' -e '}' ${server_config} 320 | # Suppress repeated empty output lines 321 | cat -s ${server_config} > ${server_config}.backup 322 | mv ${server_config}.backup ${server_config} 323 | 324 | # Delete config and key files 325 | rm -f ${WORKING_DIR}/client-${client_name}{.conf,.conf.png,.conf.asc,-private.key,-public.key} 326 | 327 | echo -e "${GREEN}INFO${NONE}: Client config ${RED}${client_config}${NONE} has been successfully removed!" 328 | } 329 | 330 | 331 | # Generate preshared, server and client keys 332 | gen_keys() { 333 | local name_prefix="${1}" 334 | 335 | local private_key="${WORKING_DIR}/${name_prefix}-private.key" 336 | local public_key="${WORKING_DIR}/${name_prefix}-public.key" 337 | local preshared_key="${WORKING_DIR}/preshared.key" 338 | 339 | wg genkey | tee ${private_key} | wg pubkey > ${public_key} 340 | [[ ! -f ${preshared_key} ]] && wg genpsk > ${preshared_key} 2> /dev/null 341 | chmod 600 ${private_key} ${preshared_key} 342 | } 343 | 344 | 345 | # Generate server configuration file 346 | gen_server_config() { 347 | local server_name="${1}" 348 | local server_wg_ip_cidr="${2}" 349 | local server_port="${3}" 350 | 351 | local server_wg_ip="${server_wg_ip_cidr%%/*}" 352 | local cidr="$(echo ${server_wg_ip_cidr} | awk -F'/' '{print $2}')" 353 | local cidr="${cidr:-22}" 354 | 355 | [[ ! -d ${WORKING_DIR} ]] && mkdir -p ${WORKING_DIR} 356 | 357 | local server_private_key="${WORKING_DIR}/server-${server_name}-private.key" 358 | local server_config="${WORKING_DIR}/server-${server_name}.conf" 359 | local server_generated="${WORKING_DIR}/.server-${server_name}.generated" 360 | 361 | validator ipaddress ${server_wg_ip} 362 | if [[ ${?} -ne 0 ]]; then 363 | echo -e "${RED}ERROR${NONE}: ${RED}${server_wg_ip}${NONE} is not valid IP address!" 364 | exit 1 365 | fi 366 | 367 | validator svcport ${server_port} 368 | if [[ ${?} -ne 0 ]]; then 369 | echo -e "${RED}ERROR${NONE}: ${RED}${server_port}${NONE} is not valid port number!" 370 | exit 1 371 | fi 372 | 373 | validator cidr ${cidr} 374 | if [[ ${?} -ne 0 ]]; then 375 | echo -e "${RED}ERROR${NONE}: ${RED}${cidr}${NONE} is not valid CIDR!" 376 | exit 1 377 | fi 378 | 379 | if [[ -f ${server_private_key} ]]; then 380 | echo -e "${YELLOW}WARNING${NONE}: This is destructive operation, also it will require regeneration of all client configs!" 381 | echo -ne "Server config and keys are already generated, do you want to overwrite it? (${GREEN}yes${NONE}/${RED}no${NONE}): " 382 | read answer 383 | 384 | [[ ${answer} != "yes" ]] && exit 1 385 | fi 386 | 387 | gen_keys server-${server_name} 388 | 389 | cat > ${server_config} < ${BLUE}${server_config_match}${NONE}" 456 | return 1 457 | fi 458 | 459 | server_wg_ip_cidr=$(awk '/^Address =/ {print $NF}' ${server_config}) 460 | server_wg_ip=${server_wg_ip_cidr%%/*} 461 | cidr=${server_wg_ip_cidr##*/} 462 | validator in_cidr "${client_wg_ip} ${server_wg_ip}/${cidr}" 463 | if [[ ${?} -ne 0 ]]; then 464 | echo -e "${RED}ERROR${NONE}: WG private IP address ${RED}${client_wg_ip}${NONE} is not in the same subnet as server's IP address => ${GREEN}${server_wg_ip}/${cidr}${NONE}" 465 | return 1 466 | fi 467 | 468 | if [[ -f ${client_private_key} ]]; then 469 | # Condition will be skipped if function called from the gen_client_config_batch() function 470 | if [[ ${SKIP_ANSWER} == false ]]; then 471 | echo -e "${YELLOW}WARNING${NONE}: All files for this client will be regenerated!" 472 | echo -ne "Config and key files for client ${GREEN}${client_name}${NONE} are already generated, do you want to overwrite it? (${GREEN}yes${NONE}/${RED}no${NONE}): " 473 | read answer 474 | 475 | [[ ${answer} == "yes" ]] || return 1 476 | 477 | remove_client_config ${client_name} ${server_name} 478 | else 479 | if [[ ${BATCH_REWRITE} == true ]]; then 480 | remove_client_config ${client_name} ${server_name} 481 | else 482 | return 1 483 | fi 484 | fi 485 | else 486 | if find ${WORKING_DIR} -maxdepth 1 | grep -Eq "client-.*\.conf$"; then 487 | client_config_match=$(grep -l "^Address = ${client_wg_ip}" ${WORKING_DIR}/client-*.conf) 488 | if [[ -n ${client_config_match} ]]; then 489 | echo -e "${RED}ERROR${NONE}: WG private IP address ${RED}${client_wg_ip}${NONE} already in use => ${BLUE}${client_config_match}${NONE}" 490 | return 1 491 | fi 492 | fi 493 | fi 494 | 495 | gen_keys client-${client_name} 496 | 497 | cat > ${client_config} <> ${server_config} < /dev/null" 621 | if [[ ${?} -ne 0 ]]; then 622 | echo -e "${YELLOW}WARNING${NONE}: It looks like ${GREEN}wireguard-tools${NONE} package isn't installed, please run script with ${GREEN}--sysprep${NONE} option first" 623 | exit 1 624 | fi 625 | 626 | if [[ -f ${SERVER_FW_CUSTOM_RULES_FILE} ]]; then 627 | cat ${SERVER_FW_CUSTOM_RULES_FILE} | ssh -p ${server_ssh_port} root@${server_ssh_ip} "cat > /etc/wireguard/${SERVER_FW_CUSTOM_RULES_FILE##*/} && chmod 600 /etc/wireguard/${SERVER_FW_CUSTOM_RULES_FILE##*/}" 628 | if [[ ${?} -eq 0 ]]; then 629 | ssh -p ${server_ssh_port} root@${server_ssh_ip} " 630 | if [[ -x "/usr/local/bin/wgfw.sh" ]]; then 631 | /usr/local/bin/wgfw.sh set 632 | fi 633 | " || return ${?} 634 | else 635 | echo -e "${RED}ERROR${NONE}: Syncing firewall custom rules ${BLUE}${SERVER_FW_CUSTOM_RULES_FILE}${NONE} with server ${BLUE}${server_ssh_ip}${NONE} failed!" 636 | exit 1 637 | fi 638 | fi 639 | 640 | cat ${server_config} | ssh -p ${server_ssh_port} root@${server_ssh_ip} "cat > /etc/wireguard/${server_name}.conf && chmod 600 /etc/wireguard/${server_name}.conf" 641 | if [[ ${?} -eq 0 ]]; then 642 | ssh -p ${server_ssh_port} root@${server_ssh_ip} " 643 | if ! systemctl is-enabled wg-quick@${server_name}.service &> /dev/null; then 644 | systemctl enable --now wg-quick@${server_name}.service &> /dev/null 645 | fi 646 | 647 | if systemctl is-active wg-quick@${server_name}.service &> /dev/null; then 648 | wg syncconf ${server_name} <(sed '/^Address =/d;/^DNS =/d;/^MTU =/d;/^PreUp =/d;/^PostUp =/d;/^PreDown =/d;/^PostDown =/d;/^SaveConfig =/d' /etc/wireguard/${server_name}.conf) 649 | fi 650 | " 651 | else 652 | echo -e "${RED}ERROR${NONE}: Syncing configuration ${BLUE}${server_config}${NONE} with server ${BLUE}${server_ssh_ip}${NONE} failed!" 653 | exit 1 654 | fi 655 | } 656 | 657 | 658 | case ${1} in 659 | '-P'|'--sysprep') 660 | shift 661 | # sysprep_module, server_ssh_ip, server_ssh_port 662 | wg_sysprep ${1:-''} ${SERVER_SSH_IP:-${SERVER_PUBLIC_IP}} ${SERVER_SSH_PORT} 663 | ;; 664 | '-s'|'--add-server-config') 665 | shift 666 | # server_name, server_wg_ip, server_port 667 | gen_server_config ${SERVER_NAME} ${SERVER_WG_IP} ${SERVER_PORT} 668 | ;; 669 | '-c'|'--add-client-config') 670 | shift 671 | # client_name, client_wg_ip, server_name, server_port, server_public_ip 672 | gen_client_config ${1:-''} ${2:-''} ${SERVER_NAME} ${SERVER_PORT} ${SERVER_PUBLIC_IP} "${CLIENT_DNS_IPS}" "${CLIENT_ALLOWED_IPS}" 673 | [[ ${?} -ne 0 ]] && exit 1 674 | # client_name 675 | gen_qr ${1} 676 | ;; 677 | '-e'|'--encrypt-config') 678 | shift 679 | # client_name, passphrase 680 | encrypt ${1} "${2}" 681 | ;; 682 | '-d'|'--decrypt-config') 683 | shift 684 | # client_name 685 | decrypt ${1} 686 | ;; 687 | '-r'|'--rm-client-config') 688 | shift 689 | # client_name, server_name 690 | remove_client_config ${1:-''} ${SERVER_NAME} 691 | ;; 692 | '-B'|'--add-clients-batch') 693 | shift 694 | # client_batch_csv_file 695 | gen_client_config_batch ${1} 696 | ;; 697 | '-q'|'--gen-qr-code') 698 | shift 699 | # client_name, output 700 | gen_qr ${1} ${2} 701 | ;; 702 | '-l'|'--list-used-ips') 703 | wg_list_used_ips 704 | ;; 705 | '-S'|'--sync') 706 | shift 707 | # server_name, server_ssh_ip, server_ssh_port 708 | wg_sync ${SERVER_NAME} ${SERVER_SSH_IP:-${SERVER_PUBLIC_IP}} ${SERVER_SSH_PORT} 709 | ;; 710 | *) 711 | help 712 | esac 713 | -------------------------------------------------------------------------------- /wgfw.rules: -------------------------------------------------------------------------------- 1 | # Custom firewall rules in iptables compatible format that will be applied in idempotent manner on the server side at server startup time or each time `wgcg.sh --sync` command is executed. 2 | # 3 | # Note: Chain name cannot be changed, WGCG-CUSTOM will always be enforced even if some other chain name is specified. 4 | # Note: When deleting a rule always delete a whole group of rules for particular client. 5 | # 6 | # Examples: 7 | # # client-1: Allow HTTP and HTTPS traffic from client source IP 10.0.0.10 to destination IP 192.168.0.10, block everything else coming from this source IP. 8 | # -A WGCG-CUSTOM -s 10.0.0.10 -d 192.168.0.10 -p tcp -m multiport --dports 80,443 -j ACCEPT 9 | # -A WGCG-CUSTOM -s 10.0.0.10 -j DROP 10 | # 11 | # # client-2: Allow SSH traffic from client source IP 10.0.0.11 to destination IP 192.168.0.11, block everything else coming from this source IP. 12 | # -A WGCG-CUSTOM -s 10.0.0.11 -d 192.168.0.11 -p tcp --dport 22 -j ACCEPT 13 | # -A WGCG-CUSTOM -s 10.0.0.11 -j DROP 14 | # 15 | # # client-3: Delete following rule if exist. 16 | # -D WGCG-CUSTOM -s 10.0.0.12 -d 192.168.0.12 -p tcp --dport 5432 -j ACCEPT 17 | --------------------------------------------------------------------------------