├── .gitignore ├── LICENSE ├── README.md ├── ansible.cfg ├── gcp_resources_delete.yml ├── gcp_service_account_create.yml ├── gcp_service_account_delete.yml ├── inventory └── hosts.yml ├── requirements.txt ├── roles ├── docker │ ├── tasks │ │ └── main.yml │ └── vars │ │ └── main.yml ├── duckdns │ ├── files │ │ ├── duckdns.service │ │ └── duckdns.sh │ ├── tasks │ │ └── main.yml │ └── templates │ │ └── default.cfg.j2 ├── dynamips │ ├── tasks │ │ └── main.yml │ └── vars │ │ └── main.yml ├── gcp │ ├── defaults │ │ └── main.yml │ └── tasks │ │ └── main.yml ├── gns3 │ ├── files │ │ ├── gns3.service │ │ └── gns3_server.conf │ ├── handlers │ │ └── main.yml │ └── tasks │ │ └── main.yml ├── i386-arch │ └── tasks │ │ └── main.yml ├── iol │ ├── files │ │ └── create_demo_iol_lic.py │ └── tasks │ │ └── main.yml ├── iouyap │ ├── tasks │ │ └── main.yml │ └── vars │ │ └── main.yml ├── libvirt │ ├── tasks │ │ └── main.yml │ └── vars │ │ └── main.yml ├── pip3 │ ├── tasks │ │ └── main.yml │ └── vars │ │ └── main.yml ├── qemu-kvm │ ├── tasks │ │ └── main.yml │ └── vars │ │ └── main.yml ├── ubridge │ └── tasks │ │ └── main.yml ├── user │ └── tasks │ │ └── main.yml ├── vpcs │ ├── files │ │ └── install_vpcs.sh │ └── tasks │ │ └── main.yml └── wireguard │ ├── handlers │ └── main.yml │ ├── tasks │ └── main.yml │ ├── templates │ └── wg.conf.j2 │ └── vars │ └── main.yml ├── site.yml └── vars.yml /.gitignore: -------------------------------------------------------------------------------- 1 | .venv/ 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Marc Weisel 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 | # GNS3 v2 with Google Compute Engine 2 | 3 | This project provides an automated deployment procedure for [GNS3 server](https://github.com/GNS3/gns3-server) on a Google Compute Engine (GCE) [VM instance](https://cloud.google.com/compute/docs/instances). 4 | 5 | ## Prerequisites 6 | 7 | 1. [Google Cloud Platform](https://cloud.google.com) (GCP) account 8 | 2. [Duck DNS](https://www.duckdns.org) account 9 | 10 | ## Guide 11 | 12 | [GNS3 v2 with Google Compute Engine](http://binarynature.blogspot.com/2018/08/gns3-v2-google-compute-engine.html) 13 | 14 | ## License 15 | 16 | This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details 17 | -------------------------------------------------------------------------------- /ansible.cfg: -------------------------------------------------------------------------------- 1 | [defaults] 2 | inventory = inventory 3 | roles_path = roles 4 | interpreter_python = auto_silent 5 | private_key_file = $HOME/.ssh/google_compute_engine 6 | host_key_checking = False 7 | remote_tmp = /var/tmp 8 | retry_files_enabled = False 9 | callback_whitelist = timer 10 | 11 | [ssh_connection] 12 | pipelining = True -------------------------------------------------------------------------------- /gcp_resources_delete.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: delete gcp resources 3 | hosts: localhost 4 | gather_facts: no 5 | 6 | vars_files: 7 | - vars.yml 8 | 9 | tasks: 10 | - name: delete firewall rule (wireguard) 11 | gcp_compute_firewall: 12 | name: "{{ gcp_fr_name }}" 13 | project: "{{ gcp_project_id }}" 14 | auth_kind: "{{ gcp_cred_kind }}" 15 | service_account_file: "{{ gcp_cred_file_dir }}/{{ gcp_cred_file }}" 16 | state: absent 17 | 18 | - name: delete vm instance 19 | gcp_compute_instance: 20 | name: "{{ gcp_vm_name }}" 21 | zone: "{{ gcp_zone }}" 22 | project: "{{ gcp_project_id }}" 23 | auth_kind: "{{ gcp_cred_kind }}" 24 | service_account_file: "{{ gcp_cred_file_dir }}/{{ gcp_cred_file }}" 25 | state: absent 26 | 27 | - name: delete disk 28 | gcp_compute_disk: 29 | name: "{{ gcp_disk_name }}" 30 | zone: "{{ gcp_zone }}" 31 | project: "{{ gcp_project_id }}" 32 | auth_kind: "{{ gcp_cred_kind }}" 33 | service_account_file: "{{ gcp_cred_file_dir }}/{{ gcp_cred_file }}" 34 | state: absent 35 | ... -------------------------------------------------------------------------------- /gcp_service_account_create.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: create gcp service account and key file 3 | hosts: localhost 4 | gather_facts: no 5 | 6 | vars_files: 7 | - vars.yml 8 | 9 | tasks: 10 | - name: create directory for service account key file 11 | file: 12 | path: "{{ gcp_cred_file_dir }}" 13 | mode: 0700 14 | state: directory 15 | 16 | - name: check for service account 17 | command: > 18 | gcloud iam service-accounts describe 19 | {{ gcp_svc_acct_id }}@{{ gcp_project_id }}.iam.gserviceaccount.com 20 | --project {{ gcp_project_id }} 21 | ignore_errors: yes 22 | changed_when: no 23 | register: svc_acct 24 | 25 | - name: create service account 26 | command: > 27 | gcloud iam service-accounts create {{ gcp_svc_acct_id }} 28 | --display-name 'Ansible service account' 29 | --project {{ gcp_project_id }} 30 | when: svc_acct.rc != 0 31 | 32 | - name: check for service account key file 33 | stat: 34 | path: "{{ gcp_cred_file_dir }}/{{ gcp_cred_file }}" 35 | register: key_file 36 | 37 | - name: create service account key file 38 | command: > 39 | gcloud iam service-accounts keys create 40 | {{ gcp_cred_file_dir }}/{{ gcp_cred_file }} 41 | --iam-account 42 | {{ gcp_svc_acct_id }}@{{ gcp_project_id }}.iam.gserviceaccount.com 43 | --project {{ gcp_project_id }} 44 | when: not key_file.stat.exists 45 | 46 | - name: check for role attached to service account 47 | command: > 48 | gcloud projects get-iam-policy {{ gcp_project_id }} 49 | --flatten="bindings[].members" 50 | --filter="bindings.members:serviceAccount: 51 | {{ gcp_svc_acct_id }}@{{ gcp_project_id }}.iam.gserviceaccount.com" 52 | --format=text 53 | changed_when: no 54 | register: iam_policy 55 | 56 | - name: add role to service account 57 | command: > 58 | gcloud projects add-iam-policy-binding {{ gcp_project_id }} 59 | --member serviceAccount:{{ gcp_svc_acct_id }}@{{ gcp_project_id }}.iam.gserviceaccount.com 60 | --role roles/{{ gcp_role }} 61 | when: gcp_role not in iam_policy.stdout 62 | ... -------------------------------------------------------------------------------- /gcp_service_account_delete.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: delete gcp service account and key file 3 | hosts: localhost 4 | gather_facts: no 5 | 6 | vars_files: 7 | - vars.yml 8 | 9 | tasks: 10 | - name: check for role attached to service account 11 | command: > 12 | gcloud projects get-iam-policy {{ gcp_project_id }} 13 | --flatten="bindings[].members" 14 | --filter="bindings.members:serviceAccount: 15 | {{ gcp_svc_acct_id }}@{{ gcp_project_id }}.iam.gserviceaccount.com" 16 | --format=text 17 | changed_when: no 18 | register: iam_policy 19 | 20 | - name: delete role from service account 21 | command: > 22 | gcloud projects remove-iam-policy-binding {{ gcp_project_id }} 23 | --member serviceAccount:{{ gcp_svc_acct_id }}@{{ gcp_project_id }}.iam.gserviceaccount.com 24 | --role roles/{{ gcp_role }} 25 | when: gcp_role in iam_policy.stdout 26 | 27 | - name: check for service account 28 | command: > 29 | gcloud iam service-accounts describe 30 | {{ gcp_svc_acct_id }}@{{ gcp_project_id }}.iam.gserviceaccount.com 31 | --project {{ gcp_project_id }} 32 | ignore_errors: yes 33 | changed_when: no 34 | register: svc_acct 35 | 36 | - name: delete service account 37 | command: > 38 | gcloud iam service-accounts delete 39 | {{ gcp_svc_acct_id }}@{{ gcp_project_id }}.iam.gserviceaccount.com 40 | --project {{ gcp_project_id }} 41 | when: svc_acct.rc == 0 42 | 43 | - name: delete service account key file and directory 44 | file: 45 | path: "{{ gcp_cred_file_dir }}" 46 | state: absent 47 | ... -------------------------------------------------------------------------------- /inventory/hosts.yml: -------------------------------------------------------------------------------- 1 | --- 2 | all: 3 | hosts: 4 | localhost: 5 | ansible_connection: local 6 | #ansible_python_interpreter: .venv/bin/python 7 | ... -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | ansible 2 | requests 3 | google-auth 4 | -------------------------------------------------------------------------------- /roles/docker/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: get and install dependencies 3 | apt: 4 | name: "{{ packages }}" 5 | state: present 6 | become: yes 7 | 8 | - name: add gpg key 9 | apt_key: 10 | url: https://download.docker.com/linux/ubuntu/gpg 11 | state: present 12 | become: yes 13 | 14 | - name: add docker apt repository 15 | apt_repository: 16 | repo: deb [arch=amd64] https://download.docker.com/linux/ubuntu {{ ansible_distribution_release }} stable 17 | state: present 18 | become: yes 19 | 20 | - name: get and install 21 | apt: 22 | name: docker-ce 23 | become: yes 24 | 25 | - name: add gns3 user to docker system group 26 | user: 27 | name: gns3 28 | groups: docker 29 | append: yes 30 | become: yes 31 | ... -------------------------------------------------------------------------------- /roles/docker/vars/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | packages: 3 | - apt-transport-https 4 | - ca-certificates 5 | - curl 6 | - software-properties-common 7 | ... -------------------------------------------------------------------------------- /roles/duckdns/files/duckdns.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=DuckDNS update job 3 | After=network-online.target 4 | 5 | [Service] 6 | Type=oneshot 7 | ExecStart=/usr/local/bin/duckdns 8 | 9 | [Install] 10 | WantedBy=multi-user.target -------------------------------------------------------------------------------- /roles/duckdns/files/duckdns.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | logger -t DuckDNS "Updating DuckDNS entries" 4 | EXITCODE=0 5 | for file in /etc/duckdns.d/*.cfg 6 | do 7 | source "${file}" 8 | logger -t DuckDNS "Executing config file '${file}'" 9 | OUTPUT=$(curl -k -s "https://www.duckdns.org/update?domains=${duckdns_domain}&token=${duckdns_token}&ip=") 10 | logger -t DuckDNS ${OUTPUT} 11 | if [ "${OUTPUT}" == "KO" ]; then 12 | logger -t DuckDNS "You should check if your domain/token is correct because the server responded negatively!" 13 | $EXITCODE=1 14 | fi 15 | done 16 | 17 | exit $EXITCODE 18 | -------------------------------------------------------------------------------- /roles/duckdns/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: create script 3 | copy: 4 | src: duckdns.sh 5 | dest: /usr/local/bin/duckdns 6 | become: yes 7 | 8 | - name: set script as executable 9 | file: 10 | path: /usr/local/bin/duckdns 11 | mode: 0755 12 | become: yes 13 | 14 | - name: create systemd unit file 15 | copy: 16 | src: duckdns.service 17 | dest: /etc/systemd/system/duckdns.service 18 | become: yes 19 | 20 | - name: create configuration directory 21 | file: 22 | path: /etc/duckdns.d 23 | state: directory 24 | become: yes 25 | 26 | - name: create configuration file 27 | template: 28 | src: default.cfg.j2 29 | dest: /etc/duckdns.d/default.cfg 30 | become: yes 31 | 32 | - name: enable and start service 33 | systemd: 34 | name: duckdns 35 | enabled: yes 36 | state: started 37 | become: yes 38 | ... -------------------------------------------------------------------------------- /roles/duckdns/templates/default.cfg.j2: -------------------------------------------------------------------------------- 1 | duckdns_domain={{ ddns_domain }} 2 | duckdns_token={{ ddns_token }} -------------------------------------------------------------------------------- /roles/dynamips/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: get and install dependencies 3 | apt: 4 | name: "{{ packages }}" 5 | state: present 6 | become: yes 7 | 8 | - name: git clone (master branch) 9 | git: 10 | repo: https://github.com/GNS3/dynamips.git 11 | dest: "{{ src_dir }}/dynamips" 12 | register: dynamips_source 13 | 14 | - block: 15 | - name: create build directory 16 | file: 17 | path: "{{ src_dir }}/dynamips/build" 18 | state: directory 19 | 20 | - name: run cmake 21 | command: cmake .. -DDYNAMIPS_ARCH=x86 22 | args: 23 | chdir: "{{ src_dir }}/dynamips/build" 24 | 25 | - name: make install 26 | make: 27 | chdir: "{{ src_dir }}/dynamips/build" 28 | target: install 29 | become: yes 30 | 31 | - name: set capabilities 32 | capabilities: 33 | path: /usr/local/bin/dynamips 34 | capability: cap_net_admin,cap_net_raw=ep 35 | state: present 36 | become: yes 37 | when: dynamips_source is changed 38 | ... -------------------------------------------------------------------------------- /roles/dynamips/vars/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | packages: 3 | - libc6-dev-i386 4 | - gcc-multilib 5 | - libelf-dev:i386 6 | - libelf-dev 7 | - libpcap0.8-dev:i386 8 | - libpcap0.8-dev 9 | - cmake 10 | - git 11 | ... -------------------------------------------------------------------------------- /roles/gcp/defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | gcp_disk_size: 30 3 | gcp_disk_type: pd-standard # pd-ssd 4 | # gcloud compute images list --filter="name~'ubuntu-minimal-1804'" 5 | gcp_disk_image: ubuntu-minimal-2004-lts 6 | gcp_vm_type: f1-micro 7 | # https://cloud.google.com/compute/docs/cpu-platforms 8 | gcp_vm_cpu: Intel Skylake 9 | gcp_vm_preemptible: no 10 | gcp_tag: gns3 11 | ... -------------------------------------------------------------------------------- /roles/gcp/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: create firewall rule (wireguard) 3 | gcp_compute_firewall: 4 | name: "{{ gcp_fr_name }}" 5 | allowed: 6 | - ip_protocol: udp 7 | ports: 8 | - '51820' 9 | target_tags: 10 | - "{{ gcp_tag }}" 11 | project: "{{ gcp_project_id }}" 12 | auth_kind: "{{ gcp_cred_kind }}" 13 | service_account_file: "{{ gcp_cred_file_dir }}/{{ gcp_cred_file }}" 14 | state: present 15 | 16 | - name: create disk 17 | gcp_compute_disk: 18 | name: "{{ gcp_disk_name }}" 19 | size_gb: "{{ gcp_disk_size }}" 20 | type: "{{ gcp_disk_type }}" 21 | source_image: "projects/ubuntu-os-cloud/global/images/family/{{ gcp_disk_image }}" 22 | licenses: 23 | - 'https://www.googleapis.com/compute/v1/projects/vm-options/global/licenses/enable-vmx' 24 | zone: "{{ gcp_zone }}" 25 | project: "{{ gcp_project_id }}" 26 | auth_kind: "{{ gcp_cred_kind }}" 27 | service_account_file: "{{ gcp_cred_file_dir }}/{{ gcp_cred_file }}" 28 | state: present 29 | register: disk 30 | 31 | - name: create vm instance 32 | gcp_compute_instance: 33 | name: "{{ gcp_vm_name }}" 34 | machine_type: "{{ gcp_vm_type }}" 35 | min_cpu_platform: "{{ gcp_vm_cpu }}" 36 | disks: 37 | - auto_delete: no 38 | boot: yes 39 | source: "{{ disk }}" 40 | network_interfaces: 41 | - access_configs: 42 | - name: External NAT 43 | type: ONE_TO_ONE_NAT 44 | scheduling: 45 | preemptible: "{{ gcp_vm_preemptible }}" 46 | service_accounts: 47 | - email: "{{ gcp_svc_acct_id }}@{{ gcp_project_id }}.iam.gserviceaccount.com" 48 | scopes: 49 | - https://www.googleapis.com/auth/cloud-platform 50 | tags: 51 | items: 52 | - "{{ gcp_tag }}" 53 | zone: "{{ gcp_zone }}" 54 | project: "{{ gcp_project_id }}" 55 | auth_kind: "{{ gcp_cred_kind }}" 56 | service_account_file: "{{ gcp_cred_file_dir }}/{{ gcp_cred_file }}" 57 | state: present 58 | register: vm 59 | 60 | - name: verify vm instance is up 61 | fail: 62 | msg: "The VM instance is not in the RUNNING state." 63 | when: "'RUNNING' not in vm.status" 64 | 65 | # $HOME/.ssh/config 66 | - name: create/update host entry for local ssh config 67 | command: gcloud compute config-ssh --quiet --project {{ gcp_project_id }} 68 | changed_when: no 69 | 70 | - name: create var for public ipv4 address 71 | set_fact: 72 | public_ip: "{{ vm.networkInterfaces[0].accessConfigs[0].natIP }}" 73 | 74 | - name: wait for ssh connection to become available 75 | wait_for: 76 | host: "{{ public_ip }}" 77 | port: 22 78 | delay: 10 79 | timeout: 60 80 | 81 | - name: add host to group 82 | add_host: 83 | name: "{{ public_ip }}" 84 | group: gns3 85 | changed_when: no 86 | ... 87 | -------------------------------------------------------------------------------- /roles/gns3/files/gns3.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=GNS3 server 3 | After=network.target network-online.target 4 | Wants=network-online.target 5 | 6 | [Service] 7 | Type=forking 8 | User=gns3 9 | Group=gns3 10 | PermissionsStartOnly=true 11 | ExecStartPre=/bin/mkdir -p /var/log/gns3 /var/run/gns3 12 | ExecStartPre=/bin/chown -R gns3:gns3 /var/log/gns3 /var/run/gns3 13 | ExecStart=/usr/local/bin/gns3server --log /var/log/gns3/gns3.log \ 14 | --pid /var/run/gns3/gns3.pid --daemon 15 | ExecReload=/bin/kill -s HUP $MAINPID 16 | Restart=on-abort 17 | PIDFile=/var/run/gns3/gns3.pid 18 | 19 | [Install] 20 | WantedBy=multi-user.target 21 | -------------------------------------------------------------------------------- /roles/gns3/files/gns3_server.conf: -------------------------------------------------------------------------------- 1 | [Server] 2 | host = 0.0.0.0 3 | port = 3080 4 | images_path = /opt/gns3/GNS3/images 5 | projects_path = /opt/gns3/GNS3/projects 6 | appliances_path = /opt/gns3/GNS3/appliances 7 | symbols_path = /opt/gns3/GNS3/symbols 8 | report_errors = True 9 | 10 | ; First console port of the range allocated to devices 11 | console_start_port_range = 5000 12 | ; Last console port of the range allocated to devices 13 | console_end_port_range = 10000 14 | ; First port of the range allocated for inter-device communication. Two ports are allocated per link. 15 | udp_start_port_range = 20000 16 | ; Last port of the range allocated for inter-device communication. Two ports are allocated per link 17 | udp_end_port_range = 30000 18 | 19 | [Dynamips] 20 | allocate_aux_console_ports = False 21 | mmap_support = True 22 | sparse_memory_support = True 23 | ghost_ios_support = True 24 | 25 | [IOU] 26 | iourc_path = /opt/gns3/iourc 27 | license_check = True 28 | 29 | [Qemu] 30 | enable_kvm = True 31 | require_kvm = True 32 | enable_hardware_acceleration = True 33 | require_hardware_acceleration = True 34 | -------------------------------------------------------------------------------- /roles/gns3/handlers/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: restart gns3 3 | systemd: 4 | name: gns3 5 | enabled: yes 6 | daemon_reload: yes 7 | state: restarted 8 | become: yes 9 | ... -------------------------------------------------------------------------------- /roles/gns3/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: get and install 3 | pip: 4 | name: gns3-server 5 | version: "{{ gns3_version }}" 6 | executable: pip3 7 | state: present 8 | notify: 9 | - restart gns3 10 | become: yes 11 | 12 | - name: create directory for configuration file 13 | file: 14 | path: /etc/xdg/GNS3 15 | state: directory 16 | owner: gns3 17 | group: gns3 18 | become: yes 19 | 20 | - name: create configuration file 21 | copy: 22 | src: gns3_server.conf 23 | dest: /etc/xdg/GNS3/gns3_server.conf 24 | owner: gns3 25 | group: gns3 26 | notify: 27 | - restart gns3 28 | become: yes 29 | 30 | - name: create systemd unit file 31 | copy: 32 | src: gns3.service 33 | dest: /etc/systemd/system/gns3.service 34 | notify: 35 | - restart gns3 36 | become: yes 37 | 38 | - name: enable and start service 39 | systemd: 40 | name: gns3 41 | enabled: yes 42 | state: started 43 | become: yes 44 | ... -------------------------------------------------------------------------------- /roles/i386-arch/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: check if i386 arch is enabled 3 | command: dpkg --print-foreign-architectures 4 | register: i386_check 5 | changed_when: "'i386' not in i386_check.stdout" 6 | 7 | - block: 8 | - name: enable i386 architecture 9 | command: dpkg --add-architecture i386 10 | 11 | - name: update apt cache 12 | apt: 13 | update_cache: yes 14 | become: yes 15 | when: i386_check is changed 16 | ... -------------------------------------------------------------------------------- /roles/iol/files/create_demo_iol_lic.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import os 4 | import socket 5 | import hashlib 6 | import struct 7 | 8 | hostid = os.popen('hostid').read().strip() 9 | hostname = socket.gethostname() 10 | ioukey = int(hostid,16) 11 | for x in hostname: 12 | ioukey = ioukey + ord(x) 13 | 14 | iouPad1 = b'\x4B\x58\x21\x81\x56\x7B\x0D\xF3\x21\x43\x9B\x7E\xAC\x1D\xE6\x8A' 15 | iouPad2 = b'\x80' + 39*b'\0' 16 | md5input = iouPad1 + iouPad2 + struct.pack('!L', ioukey) + iouPad1 17 | iouLicense = hashlib.md5(md5input).hexdigest()[:16] 18 | 19 | fpath = '/opt/gns3/iourc' 20 | with open(fpath, 'wt') as f: 21 | f.write('[license]\n') 22 | f.write(hostname + ' = ' + iouLicense + ';\n') 23 | -------------------------------------------------------------------------------- /roles/iol/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: install libssl 3 | apt: 4 | name: libssl1.1:i386 5 | state: present 6 | become: yes 7 | 8 | - name: create libcrypto symlink 9 | file: 10 | src: /lib/i386-linux-gnu/libcrypt.so.1 11 | dest: /usr/lib/libcrypto.so.4 12 | state: link 13 | become: yes 14 | 15 | - name: prevent excesscoll iol error 16 | sysctl: 17 | name: net.unix.max_dgram_qlen 18 | value: '10000' 19 | state: present 20 | become: yes 21 | 22 | - name: block phone home 23 | lineinfile: 24 | path: /etc/hosts 25 | insertafter: '^127\.0\.1\.1' 26 | line: "127.0.0.84\txml.cisco.com" 27 | state: present 28 | become: yes 29 | 30 | - name: create demo iol license file 31 | script: create_demo_iol_lic.py 32 | args: 33 | chdir: /opt/gns3 34 | executable: python3 35 | creates: /opt/gns3/iourc 36 | become: yes 37 | 38 | - name: change file ownership for demo iol license file 39 | file: 40 | path: /opt/gns3/iourc 41 | owner: gns3 42 | group: gns3 43 | become: yes 44 | ... -------------------------------------------------------------------------------- /roles/iouyap/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: get and install dependencies 3 | apt: 4 | name: "{{ packages }}" 5 | state: present 6 | become: yes 7 | 8 | - name: git clone iniparser (master branch) 9 | git: 10 | repo: http://github.com/ndevilla/iniparser.git 11 | dest: "{{ src_dir }}/iniparser" 12 | register: iniparser_source 13 | 14 | - name: make iniparser 15 | make: 16 | chdir: "{{ src_dir }}/iniparser" 17 | when: iniparser_source is changed 18 | 19 | - name: copy files 20 | copy: 21 | src: "{{ item.file }}" 22 | dest: "{{ item.dest }}" 23 | remote_src: yes 24 | loop: 25 | - file: "{{ src_dir }}/iniparser/libiniparser.a" 26 | dest: /usr/lib/libiniparser.a 27 | - file: "{{ src_dir }}/iniparser/libiniparser.so.1" 28 | dest: /usr/lib/libiniparser.so.1 29 | - file: "{{ src_dir }}/iniparser/src/iniparser.h" 30 | dest: /usr/local/include/iniparser.h 31 | - file: "{{ src_dir }}/iniparser/src/dictionary.h" 32 | dest: /usr/local/include/dictionary.h 33 | become: yes 34 | 35 | - name: git clone iouyap (master branch) 36 | git: 37 | repo: https://github.com/GNS3/iouyap.git 38 | dest: "{{ src_dir }}/iouyap" 39 | force: yes 40 | register: iouyap_source 41 | 42 | - block: 43 | - name: make iouyap 44 | make: 45 | chdir: "{{ src_dir }}/iouyap" 46 | 47 | - name: make install iouyap 48 | make: 49 | chdir: "{{ src_dir }}/iouyap" 50 | target: install 51 | become: yes 52 | when: iouyap_source is changed 53 | ... -------------------------------------------------------------------------------- /roles/iouyap/vars/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | packages: 3 | - bison 4 | - flex 5 | ... -------------------------------------------------------------------------------- /roles/libvirt/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: get and install 3 | apt: 4 | name: "{{ packages }}" 5 | state: present 6 | become: yes 7 | 8 | - name: add gns3 user to libvirt system group 9 | user: 10 | name: gns3 11 | groups: libvirt 12 | append: yes 13 | become: yes 14 | 15 | - name: get and install dependencies for virt_net ansible module 16 | pip: 17 | name: "{{ pip_packages }}" 18 | executable: pip3 19 | state: present 20 | become: yes 21 | 22 | - name: start default libvirt network 23 | virt_net: 24 | name: default 25 | state: active 26 | become: yes 27 | 28 | - name: autostart default libvirt network at boot 29 | virt_net: 30 | name: default 31 | autostart: yes 32 | become: yes 33 | ... -------------------------------------------------------------------------------- /roles/libvirt/vars/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | packages: 3 | - libvirt-daemon-system 4 | - libvirt-clients 5 | - libvirt-dev 6 | pip_packages: 7 | - libvirt-python 8 | - lxml 9 | ... -------------------------------------------------------------------------------- /roles/pip3/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: get and install 3 | apt: 4 | name: "{{ packages }}" 5 | state: present 6 | become: yes 7 | ... -------------------------------------------------------------------------------- /roles/pip3/vars/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | packages: 3 | - python3-pip 4 | - pkg-config 5 | ... -------------------------------------------------------------------------------- /roles/qemu-kvm/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: get and install 3 | apt: 4 | name: "{{ packages }}" 5 | state: present 6 | become: yes 7 | 8 | - name: check if ksm is enabled 9 | command: cat /sys/kernel/mm/ksm/run 10 | register: ksm 11 | changed_when: (ksm.stdout | int) != 1 12 | 13 | - block: 14 | - name: enable ksm (runtime) 15 | shell: echo 1 > /sys/kernel/mm/ksm/run 16 | 17 | - name: enable ksm (persistent) 18 | lineinfile: 19 | path: /etc/sysfs.d/kernel.conf 20 | line: 'kernel/mm/ksm/run = 1' 21 | create: yes 22 | become: yes 23 | when: ksm is changed 24 | 25 | - name: get kvm halt polling interval value 26 | command: cat /sys/module/kvm/parameters/halt_poll_ns 27 | register: halt_poll_ns 28 | changed_when: (halt_poll_ns.stdout | int) != 0 29 | 30 | - block: 31 | - name: reduce kvm halt polling interval (runtime) 32 | shell: echo 0 > /sys/module/kvm/parameters/halt_poll_ns 33 | 34 | - name: reduce kvm halt polling interval (persistent) 35 | lineinfile: 36 | path: /etc/sysfs.d/kvm.conf 37 | line: 'module/kvm/parameters/halt_poll_ns = 0' 38 | create: yes 39 | become: yes 40 | when: halt_poll_ns is changed 41 | 42 | - name: add gns3 user to kvm system group 43 | user: 44 | name: gns3 45 | groups: kvm 46 | append: yes 47 | become: yes 48 | ... 49 | -------------------------------------------------------------------------------- /roles/qemu-kvm/vars/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | packages: 3 | - qemu-system-x86 4 | - sysfsutils 5 | ... -------------------------------------------------------------------------------- /roles/ubridge/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: git clone ubridge (master branch) 3 | git: 4 | repo: https://github.com/GNS3/ubridge.git 5 | dest: "{{ src_dir }}/ubridge" 6 | register: ubridge_source 7 | 8 | - block: 9 | - name: make ubridge 10 | make: 11 | chdir: "{{ src_dir }}/ubridge" 12 | 13 | - name: make install ubridge 14 | make: 15 | chdir: "{{ src_dir }}/ubridge" 16 | target: install 17 | become: yes 18 | when: ubridge_source is changed 19 | ... -------------------------------------------------------------------------------- /roles/user/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: create gns3 user 3 | user: 4 | name: gns3 5 | home: /opt/gns3 6 | system: yes 7 | become: yes 8 | 9 | - name: create source code directory 10 | file: 11 | path: "{{ src_dir }}" 12 | state: directory 13 | ... -------------------------------------------------------------------------------- /roles/vpcs/files/install_vpcs.sh: -------------------------------------------------------------------------------- 1 | rgetopt='int getopt(int argc, char *const *argv, const char *optstr);' 2 | sed -i "s/^int getopt.*/$rgetopt/" getopt.h 3 | sed -i 's/i386/x86_64/' Makefile.linux 4 | sed -i 's/-s -static//' Makefile.linux 5 | make -f Makefile.linux 6 | strip --strip-unneeded vpcs 7 | sudo mv vpcs /usr/local/bin 8 | -------------------------------------------------------------------------------- /roles/vpcs/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: get and install svn 3 | apt: 4 | name: subversion 5 | state: present 6 | become: yes 7 | 8 | - name: check out working copy from repository 9 | subversion: 10 | repo: svn://svn.code.sf.net/p/vpcs/code/trunk 11 | dest: "{{ src_dir }}/vpcs-code" 12 | force: yes 13 | 14 | - name: perform source-based install 15 | script: install_vpcs.sh 16 | args: 17 | chdir: "{{ src_dir }}/vpcs-code/src" 18 | creates: /usr/local/bin/vpcs 19 | ... -------------------------------------------------------------------------------- /roles/wireguard/handlers/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: syncconf wireguard 3 | shell: | 4 | set -o errexit 5 | set -o pipefail 6 | set -o nounset 7 | systemctl is-active wg-quick@wg-quick@{{ wireguard_interface|quote }} || systemctl start wg-quick@{{ wireguard_interface|quote }} 8 | wg syncconf {{ wireguard_interface|quote }} <(wg-quick strip /etc/wireguard/{{ wireguard_interface|quote }}.conf) 9 | exit 0 10 | args: 11 | executable: "/bin/bash" 12 | listen: "reconfigure wireguard" 13 | become: yes 14 | ... -------------------------------------------------------------------------------- /roles/wireguard/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: get and install 3 | package: 4 | name: "{{ wireguard_packages }}" 5 | state: present 6 | become: yes 7 | 8 | - name: enable kernel module 9 | modprobe: 10 | name: wireguard 11 | state: present 12 | register: wireguard_module_enabled 13 | until: wireguard_module_enabled is success 14 | failed_when: wireguard_module_enabled is failed 15 | become: yes 16 | 17 | - name: register if config/private key already exists 18 | stat: 19 | path: "{{ wireguard_directory }}/{{ wireguard_interface }}.conf" 20 | register: config_file_stat 21 | become: yes 22 | 23 | - block: 24 | - name: generate private key 25 | command: wg genkey 26 | register: wg_private_key_result 27 | 28 | - name: set private key fact 29 | set_fact: 30 | private_key: "{{ wg_private_key_result.stdout }}" 31 | when: not config_file_stat.stat.exists 32 | become: yes 33 | 34 | - block: 35 | - name: read configuration file 36 | slurp: 37 | src: "{{ wireguard_directory }}/{{ wireguard_interface }}.conf" 38 | register: wg_config 39 | 40 | - name: set private key fact 41 | set_fact: 42 | private_key: "{{ wg_config['content'] | b64decode | regex_findall('PrivateKey = (.*)') | first }}" 43 | when: config_file_stat.stat.exists 44 | become: yes 45 | 46 | - name: derive wireguard public key 47 | shell: "echo '{{ private_key }}' | wg pubkey" 48 | register: wg_public_key_result 49 | changed_when: no 50 | become: yes 51 | 52 | - name: set public key fact 53 | set_fact: 54 | public_key: "{{ wg_public_key_result.stdout }}" 55 | 56 | - name: create configuration directory 57 | file: 58 | dest: "{{ wireguard_directory }}" 59 | state: directory 60 | mode: 0700 61 | become: yes 62 | 63 | - name: generate configuration file 64 | template: 65 | src: wg.conf.j2 66 | dest: "{{ wireguard_directory }}/{{ wireguard_interface }}.conf" 67 | owner: root 68 | group: root 69 | mode: 0600 70 | notify: 71 | - reconfigure wireguard 72 | become: yes 73 | 74 | - name: check if reload-module-on-update is set 75 | stat: 76 | path: "{{ wireguard_directory }}/.reload-module-on-update" 77 | register: reload_module_on_update 78 | become: yes 79 | 80 | - name: set reload-module-on-update 81 | file: 82 | dest: "{{ wireguard_directory }}/.reload-module-on-update" 83 | state: touch 84 | when: not reload_module_on_update.stat.exists 85 | become: yes 86 | 87 | - name: enable and start service 88 | systemd: 89 | name: "wg-quick@{{ wireguard_interface }}" 90 | state: started 91 | enabled: yes 92 | become: yes 93 | ... -------------------------------------------------------------------------------- /roles/wireguard/templates/wg.conf.j2: -------------------------------------------------------------------------------- 1 | [Interface] 2 | PrivateKey = {{ private_key }} 3 | Address = {{ wireguard_address }} 4 | ListenPort = {{ wireguard_port }} 5 | SaveConfig = true 6 | 7 | [Peer] 8 | PublicKey = {{ wireguard_peer_pubkey }} 9 | AllowedIPs = {{ wireguard_allowed_ips }} 10 | -------------------------------------------------------------------------------- /roles/wireguard/vars/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | wireguard_interface: 'wg0' 3 | wireguard_address: '172.16.253.1/24' 4 | wireguard_port: '51820' 5 | wireguard_allowed_ips: '172.16.253.0/24' 6 | wireguard_directory: '/etc/wireguard' 7 | wireguard_packages: 8 | - wireguard 9 | ... -------------------------------------------------------------------------------- /site.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: provision gcp resources 3 | hosts: localhost 4 | gather_facts: no 5 | 6 | vars_files: 7 | - vars.yml 8 | 9 | roles: 10 | - role: gcp 11 | 12 | - name: deploy gns3 server 13 | hosts: gns3 14 | 15 | vars_files: 16 | - vars.yml 17 | 18 | pre_tasks: 19 | - name: pause before updating apt cache 20 | pause: 21 | seconds: 15 22 | 23 | - name: update apt cache 24 | apt: 25 | update_cache: yes 26 | register: apt_result 27 | until: apt_result is success 28 | ignore_errors: yes 29 | become: yes 30 | 31 | - name: use command apt-get update if apt module fails 32 | command: apt-get update 33 | when: apt_result is failed 34 | become: yes 35 | 36 | roles: 37 | - role: user 38 | - role: duckdns 39 | - role: wireguard 40 | - role: i386-arch 41 | - role: dynamips 42 | - role: vpcs 43 | - role: iouyap 44 | - role: iol 45 | - role: ubridge 46 | - role: qemu-kvm 47 | - role: pip3 48 | - role: libvirt 49 | - role: docker 50 | - role: gns3 51 | 52 | post_tasks: 53 | - name: print wireguard public key 54 | debug: 55 | var: public_key 56 | ... 57 | -------------------------------------------------------------------------------- /vars.yml: -------------------------------------------------------------------------------- 1 | --- 2 | ### PROJECT and ZONE ### 3 | 4 | # https://console.cloud.google.com/iam-admin/settings/project 5 | # `gcloud config get-value project` 6 | gcp_project_id: 7 | # https://cloud.google.com/compute/docs/regions-zones 8 | # `gcloud compute zones list` 9 | gcp_zone: 10 | 11 | 12 | ### STORAGE ### 13 | 14 | #gcp_disk_size: 30 15 | #gcp_disk_type: pd-standard 16 | 17 | 18 | ### COMPUTE ### 19 | 20 | # https://cloud.google.com/compute/vm-instance-pricing 21 | #gcp_vm_type: f1-micro 22 | 23 | 24 | ### GNS3 ### 25 | 26 | # https://github.com/GNS3/gns3-server/releases 27 | gns3_version: 28 | 29 | 30 | ### DUCKDNS ### 31 | 32 | # https://www.duckdns.org 33 | ddns_domain: 34 | ddns_token: 35 | 36 | 37 | ### WIREGUARD ### 38 | 39 | wireguard_peer_pubkey: 40 | 41 | 42 | ### DEFAULTS ### 43 | 44 | src_dir: "{{ ansible_env.HOME }}/src" 45 | gcp_cred_kind: serviceaccount 46 | gcp_cred_file_dir: "{{ lookup('env','HOME') }}/.gcred" 47 | gcp_cred_file: ansible-gns3-key.json 48 | gcp_svc_acct_id: ansible-gns3 49 | gcp_role: editor 50 | 51 | gcp_fr_name: default-allow-wireguard 52 | gcp_disk_name: gns3-disk 53 | gcp_vm_name: gns3server 54 | ... --------------------------------------------------------------------------------