├── .github └── workflows │ ├── build.yml │ └── main.yml ├── .gitignore ├── README.md ├── defaults └── main.yml ├── dockerimage.yml ├── handlers └── main.yml ├── meta └── main.yml ├── tasks ├── configure.yml ├── install.yml ├── main.yml ├── system.yml └── users.yml ├── templates ├── wg0-server.j2 └── wg0-user.j2 ├── tests ├── inventory └── test.yml └── vars └── main.yml /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Docker 2 | 3 | on: 4 | push: 5 | tags: 6 | - '*' 7 | pull_request: 8 | branches: 9 | - 'master' 10 | 11 | jobs: 12 | build: 13 | if: success() 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - uses: actions/checkout@v2 18 | 19 | - name: Run tests 20 | run: docker build ./ -t ${GITHUB_BASE_REF} 21 | 22 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: 4 | push: 5 | branches: 6 | - '*' 7 | 8 | jobs: 9 | run-test: 10 | 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - uses: actions/checkout@v2 15 | - name: Install YAMLlint 16 | run: sudo apt install -y yamllint 17 | - name: Linting YAML files 18 | run: yamllint ${GITHUB_WORKSPACE} 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .terraform 2 | .DS_Store 3 | .idea 4 | *.retry 5 | *.log 6 | *.plan 7 | *.tfvars 8 | *.tfstate* -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Ansible role to install and setup a Wireguard VPN server 2 | 3 | wireguard homepage: www.wireguard.com 4 | 5 | ## Prerequisites 6 | 7 | * A VPS which got Ubuntu Linux installed, and a public IP address. 8 | * dnsmasq (or the like) installed for DNS forwarding 9 | * `net.ipv4.ip_forward = 1` presents in `/etc/sysctl.conf` file 10 | 11 | ## Defaults 12 | 13 | * Listen host: Public IP address 14 | * Listen port: `UDP/51820` 15 | * Tunnel network: `10.0.0.0/24` 16 | * Create users those listed in `vars/main.yml` with fields: 17 | * username: username 18 | * private_ip: private IP address to be assigned on Wireguard tunnel 19 | * default_route: yes if user is allowed to use VPN as default route 20 | * wg_dns_enabled: yes if user is allowed to use server DNS resolver 21 | * remove: no (yes if user is marked to be deleted) 22 | 23 | ## Example 24 | 25 | Create an Ansible playbook with below content 26 | 27 | ```yml 28 | - hosts: all 29 | gather_facts: yes 30 | become: yes 31 | 32 | roles: 33 | - wireguard 34 | ``` 35 | 36 | Run ansible 37 | 38 | ```sh 39 | $ ansible-playbook -i hosts main.yml 40 | ``` 41 | 42 | Check `~/Download//etc/wireguard/` directory on local 43 | machine for users configs. -------------------------------------------------------------------------------- /defaults/main.yml: -------------------------------------------------------------------------------- 1 | default_route: false 2 | wg_private_ip: "{{ ansible_default_ipv4.address }}" 3 | wg_listen_port: "51820" 4 | wg_allowed_ips: "{% if default_route %}0.0.0.0/0,::/0{% endif %}" 5 | wg_download_path: "~/Downloads" 6 | -------------------------------------------------------------------------------- /dockerimage.yml: -------------------------------------------------------------------------------- 1 | name: Docker Image CI 2 | 3 | on: [push] 4 | 5 | jobs: 6 | 7 | build: 8 | 9 | runs-on: ubuntu-latest 10 | 11 | steps: 12 | - uses: actions/checkout@v2 13 | - name: Build the Docker image 14 | uses: ./ 15 | -------------------------------------------------------------------------------- /handlers/main.yml: -------------------------------------------------------------------------------- 1 | - name: start wg0 2 | systemd: 3 | name: "wg-quick@wg0" 4 | state: started 5 | enabled: yes 6 | daemon_reload: yes 7 | 8 | - name: restart wg0 9 | systemd: 10 | name: "wg-quick@wg0" 11 | state: restarted 12 | daemon_reload: yes -------------------------------------------------------------------------------- /meta/main.yml: -------------------------------------------------------------------------------- 1 | galaxy_info: 2 | author: namm2 3 | description: Ansible role to install and configure wireguard server. 4 | company: N/A 5 | 6 | # If the issue tracker for your role is not on github, uncomment the 7 | # next line and provide a value 8 | # issue_tracker_url: http://example.com/issue/tracker 9 | 10 | # Some suggested licenses: 11 | # - BSD (default) 12 | # - MIT 13 | # - GPLv2 14 | # - GPLv3 15 | # - Apache 16 | # - CC-BY 17 | license: license (GPLv2, CC-BY, etc) 18 | 19 | min_ansible_version: 2.4 20 | 21 | # If this a Container Enabled role, provide the minimum Ansible Container version. 22 | # min_ansible_container_version: 23 | 24 | # Optionally specify the branch Galaxy will use when accessing the GitHub 25 | # repo for this role. During role install, if no tags are available, 26 | # Galaxy will use this branch. During import Galaxy will access files on 27 | # this branch. If Travis integration is configured, only notifications for this 28 | # branch will be accepted. Otherwise, in all cases, the repo's default branch 29 | # (usually master) will be used. 30 | #github_branch: 31 | 32 | # 33 | # Provide a list of supported platforms, and for each platform a list of versions. 34 | # If you don't wish to enumerate all versions for a particular platform, use 'all'. 35 | # To view available platforms and versions (or releases), visit: 36 | # https://galaxy.ansible.com/api/v1/platforms/ 37 | # 38 | # platforms: 39 | # - name: Fedora 40 | # versions: 41 | # - all 42 | # - 25 43 | # - name: SomePlatform 44 | # versions: 45 | # - all 46 | # - 1.0 47 | # - 7 48 | # - 99.99 49 | 50 | galaxy_tags: [] 51 | # List tags for your role here, one per line. A tag is a keyword that describes 52 | # and categorizes the role. Users find roles by searching for tags. Be sure to 53 | # remove the '[]' above, if you add tags to this list. 54 | # 55 | # NOTE: A tag is limited to a single word comprised of alphanumeric characters. 56 | # Maximum 20 tags per role. 57 | 58 | dependencies: [] 59 | # List your role dependencies here, one per line. Be sure to remove the '[]' above, 60 | # if you add dependencies to this list. -------------------------------------------------------------------------------- /tasks/configure.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Create wireguard config dir 3 | file: 4 | path: "/etc/wireguard" 5 | state: directory 6 | register: wg_conf_dir 7 | 8 | - name: Generate wireguard server keypair 9 | shell: | 10 | if [ ! -f {{ wg_conf_dir.path }}/privatekey ] 11 | then 12 | umask 077 && wg genkey | tee {{ wg_conf_dir.path }}/privatekey | wg pubkey > {{ wg_conf_dir.path }}/publickey 13 | fi 14 | exit 0 15 | 16 | - name: Get wireguard private key 17 | slurp: 18 | src: "{{ wg_conf_dir.path }}/privatekey" 19 | register: wg_privatekey 20 | 21 | # - debug: 22 | # msg: "{{ wg_privatekey['content'] }}" 23 | 24 | - name: Get wireguard public key 25 | slurp: 26 | src: "{{ wg_conf_dir.path }}/publickey" 27 | register: wg_publickey 28 | 29 | - name: Get wireguard server public IPv4 30 | uri: 31 | url: "https://ipinfo.io/ip" 32 | remote_src: yes 33 | return_content: yes 34 | register: wg_public_ipv4 35 | 36 | # - debug: 37 | # msg: "{{ wg_public_ipv4['content'] }}" -------------------------------------------------------------------------------- /tasks/install.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Update aptitude 3 | apt: 4 | upgrade: full 5 | update_cache: yes 6 | autoclean: yes 7 | autoremove: yes 8 | 9 | - name: Add wireguard repository 10 | apt_repository: 11 | repo: "ppa:wireguard/wireguard" 12 | state: present 13 | 14 | - name: Install wireguard 15 | apt: 16 | name: "wireguard" 17 | state: present -------------------------------------------------------------------------------- /tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - include_tasks: install.yml 3 | tags: install 4 | 5 | - include_tasks: configure.yml 6 | tags: configure 7 | 8 | - include_tasks: system.yml 9 | tags: system 10 | 11 | 12 | - include_tasks: users.yml 13 | tags: wg_users -------------------------------------------------------------------------------- /tasks/system.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Update SystemCtl Settings 3 | sysctl: 4 | name: net.ipv4.ip_forward 5 | value: '1' 6 | state: present 7 | reload: yes 8 | -------------------------------------------------------------------------------- /tasks/users.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Create Wireguard user config directory 3 | file: 4 | path: "{{ wg_conf_dir.path }}/{{ item.value.username }}" 5 | state: directory 6 | with_dict: "{{ wg_user_list }}" 7 | when: (item.value.remove == False) 8 | 9 | - name: remove Wireguard user config directory 10 | file: 11 | path: "{{ wg_conf_dir.path }}/{{ item.value.username }}" 12 | state: absent 13 | with_dict: "{{ wg_user_list }}" 14 | when: (item.value.remove == True) 15 | 16 | - name: Generate Wireguard users key 17 | shell: | 18 | if [ ! -f {{ wg_conf_dir.path }}/{{ item.value.username }}/privatekey ] 19 | then 20 | cd {{ wg_conf_dir.path }}/{{ item.value.username }} 21 | wg genkey | tee privatekey | wg pubkey > publickey 22 | fi 23 | exit 0 24 | when: (item.value.remove == False) 25 | with_dict: "{{ wg_user_list }}" 26 | 27 | - name: Read user public key 28 | slurp: 29 | src: "{{ wg_conf_dir.path }}/{{ item.value.username }}/publickey" 30 | with_dict: "{{ wg_user_list }}" 31 | register: wg_user_publickey 32 | when: (item.value.remove == False) 33 | 34 | - name: Read user private key 35 | slurp: 36 | src: "{{ wg_conf_dir.path }}/{{ item.value.username }}/privatekey" 37 | with_dict: "{{ wg_user_list }}" 38 | register: wg_user_privatekey 39 | when: (item.value.remove == False) 40 | 41 | # Generate Wireguard server config 42 | # - debug: 43 | # msg: "{{ item['content'] }}" 44 | # loop: "{{ wg_user_publickey.results }}" 45 | # when: (item.item.value.remove == False) 46 | 47 | - name: Generate Wireguard server config 48 | template: 49 | src: "wg0-server.j2" 50 | dest: "{{ wg_conf_dir.path }}/wg0.conf" 51 | mode: 0640 52 | backup: yes 53 | loop: "{{ wg_user_publickey.results }}" 54 | when: (item.item.value.remove == False) 55 | notify: restart wg0 56 | 57 | - name: Start Wireguard server 58 | systemd: 59 | name: "wg-quick@wg0" 60 | state: started 61 | enabled: yes 62 | 63 | - setup: 64 | gather_subset: "network" 65 | 66 | # Generate Wireguard user config 67 | - name: Generate Wireguard user config 68 | template: 69 | src: "wg0-user.j2" 70 | dest: "{{ wg_conf_dir.path }}/{{ item.item.value.username }}/wg0.conf" 71 | loop: "{{ wg_user_privatekey.results }}" 72 | when: (item.item.value.remove == False) 73 | register: wg_user_profile 74 | notify: restart wg0 75 | 76 | # - debug: 77 | # msg: "{{ item['dest'] }}" 78 | # loop: "{{ wg_user_profile.results }}" 79 | # when: (item.item.item.value.remove == False) 80 | 81 | - name: Fetch Wireguard user configs 82 | fetch: 83 | src: "{{ wg_conf_dir.path }}/{{ item.item.item.value.username }}/wg0.conf" 84 | dest: "{{ wg_download_path }}" 85 | loop: "{{ wg_user_profile.results }}" 86 | when: (item.item.item.value.remove == False) 87 | -------------------------------------------------------------------------------- /templates/wg0-server.j2: -------------------------------------------------------------------------------- 1 | [Interface] 2 | Address = {{ wg_private_ip }}/24 3 | PrivateKey = {{ wg_privatekey['content'] | b64decode | replace('\n', '')}} 4 | ListenPort = {{ wg_listen_port }} 5 | PostUp = iptables -A FORWARD -i wg0 -j ACCEPT; iptables -t nat -A POSTROUTING -o {{ ansible_default_ipv4.interface }} -j MASQUERADE 6 | PostDown = iptables -D FORWARD -i wg0 -j ACCEPT; iptables -t nat -D POSTROUTING -o {{ ansible_default_ipv4.interface }} -j MASQUERADE 7 | 8 | {% for item in wg_user_publickey.results %} 9 | {% if item.item.value.remove == False %} 10 | [Peer] 11 | PublicKey = {{ item['content'] | b64decode | replace('\n', '') }} 12 | AllowedIps = {{ item.item.value.private_ip }}/32 13 | 14 | {% endif %} 15 | {% endfor %} -------------------------------------------------------------------------------- /templates/wg0-user.j2: -------------------------------------------------------------------------------- 1 | [Interface] 2 | Address = {{ item.item.value.private_ip }}/24 3 | PrivateKey = {{ item['content'] | b64decode | replace('\n', '')}} 4 | {% if item.item.value.wg_dns_enabled %} 5 | DNS = {{ wg_private_ip }} 6 | {% endif %} 7 | 8 | [Peer] 9 | PublicKey = {{ wg_publickey['content'] | b64decode | replace('\n', '')}} 10 | Endpoint = {{ wg_public_ipv4['content'] | replace('\n', '')}}:{{ wg_listen_port }} 11 | AllowedIPs = {{ wg_allowed_ips }} 12 | -------------------------------------------------------------------------------- /tests/inventory: -------------------------------------------------------------------------------- 1 | localhost 2 | 3 | -------------------------------------------------------------------------------- /tests/test.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - hosts: localhost 3 | remote_user: root 4 | roles: 5 | - wireguard -------------------------------------------------------------------------------- /vars/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | wg_user_list: 3 | user_1: { username: "user_1", private_ip: "10.0.0.11", default_route: yes, wg_dns_enabled: yes, remove: no } 4 | --------------------------------------------------------------------------------