From 62c86b96fffc468c5060b3a6822c0cad1e7c6135 Mon Sep 17 00:00:00 2001 From: ayurishchev Date: Mon, 17 Nov 2025 14:19:38 +0300 Subject: [PATCH] CloudRouterDemo --- ansible/group_vars/all.yml | 8 + ansible/inventory.ini | 23 + ansible/roles/base/handlers/main.yml | 3 + ansible/roles/base/tasks/main.yml | 69 +++ ansible/roles/disable_ipv6/tasks/main.yml | 26 ++ ansible/roles/frr_router/handlers/main.yml | 5 + ansible/roles/frr_router/tasks/main.yml | 62 +++ .../templates/backup/bgpd_router.conf.j2 | 58 +++ .../frr_router/templates/bgpd_router.conf.j2 | 104 +++++ ansible/roles/gre/handlers/main.yml | 3 + ansible/roles/gre/tasks/main.yml | 60 +++ .../roles/gre/templates/gre-netplan.yaml.j2 | 19 + ansible/roles/keepalived/tasks/main.yml | 17 + .../keepalived/templates/keepalived.conf.j2 | 19 + ansible/roles/private_base/tasks/main.yml | 17 + ansible/roles/strongswan/tasks/main.yml | 32 ++ .../strongswan/templates/swanctl.conf.j2 | 58 +++ ansible/site.yml | 29 ++ terraform/images.tf | 13 + terraform/main.tf | 293 ++++++++++++ terraform/scripts/network-init.sh | 434 ++++++++++++++++++ terraform/terraform.tfvars | 4 + terraform/variables.tf | 26 ++ 23 files changed, 1382 insertions(+) create mode 100644 ansible/group_vars/all.yml create mode 100644 ansible/inventory.ini create mode 100644 ansible/roles/base/handlers/main.yml create mode 100644 ansible/roles/base/tasks/main.yml create mode 100644 ansible/roles/disable_ipv6/tasks/main.yml create mode 100644 ansible/roles/frr_router/handlers/main.yml create mode 100644 ansible/roles/frr_router/tasks/main.yml create mode 100644 ansible/roles/frr_router/templates/backup/bgpd_router.conf.j2 create mode 100644 ansible/roles/frr_router/templates/bgpd_router.conf.j2 create mode 100644 ansible/roles/gre/handlers/main.yml create mode 100644 ansible/roles/gre/tasks/main.yml create mode 100644 ansible/roles/gre/templates/gre-netplan.yaml.j2 create mode 100644 ansible/roles/keepalived/tasks/main.yml create mode 100644 ansible/roles/keepalived/templates/keepalived.conf.j2 create mode 100644 ansible/roles/private_base/tasks/main.yml create mode 100644 ansible/roles/strongswan/tasks/main.yml create mode 100644 ansible/roles/strongswan/templates/swanctl.conf.j2 create mode 100644 ansible/site.yml create mode 100644 terraform/images.tf create mode 100644 terraform/main.tf create mode 100644 terraform/scripts/network-init.sh create mode 100644 terraform/terraform.tfvars create mode 100644 terraform/variables.tf diff --git a/ansible/group_vars/all.yml b/ansible/group_vars/all.yml new file mode 100644 index 0000000..85180ab --- /dev/null +++ b/ansible/group_vars/all.yml @@ -0,0 +1,8 @@ +--- +ike_proposal: "aes256-sha256-modp2048" +esp_proposal: "aes256-sha256-modp2048" +ike_lifetime: 14400 +esp_lifetime: 7200 +dpd_timeout: 30 + +lan_cidr: "{{ lan_network_address }}/{{ lan_network_prefix }}" diff --git a/ansible/inventory.ini b/ansible/inventory.ini new file mode 100644 index 0000000..8f4bc14 --- /dev/null +++ b/ansible/inventory.ini @@ -0,0 +1,23 @@ +[ubuntu_routers] +router1 ansible_host=210.0.0.1 wan_ip=210.0.0.1 wan_cidr=24 wan_gw=210.0.0.254 lan_ip=10.200.10.254 remote_main_isp_ip=200.0.0.1 remote_backup_isp_ip=80.0.0.2 remote_ibgp_peer=10.200.10.253 gre_main_ip=172.17.1.1 gre_backup_ip=172.17.1.5 local_pref_main=400 bgp_med_main=100 local_pref_backup=300 bgp_med_backup=200 +router2 ansible_host=95.0.0.2 wan_ip=95.0.0.2 wan_cidr=24 wan_gw=95.0.0.254 lan_ip=10.200.10.253 remote_main_isp_ip=200.0.0.1 remote_backup_isp_ip=80.0.0.2 remote_ibgp_peer=10.200.10.254 gre_main_ip=172.17.2.1 gre_backup_ip=172.17.2.5 local_pref_main=200 bgp_med_main=300 local_pref_backup=100 bgp_med_backup=400 + +[private_servers] + +[all:vars] +ansible_user= +ansible_ssh_private_key_file= + +# LAN network +lan_network_address=10.200.10.0 +lan_network_prefix=24 +lan_gateway=10.200.10.1 +vrrp_vip=10.200.10.1 + +# BGP +local_asn=65021 +remote_asn=65011 +# external_cidr=192.0.2.0/24 + +# StrongSwan +ipsec_psk=YourSecurePreSharedKey123! diff --git a/ansible/roles/base/handlers/main.yml b/ansible/roles/base/handlers/main.yml new file mode 100644 index 0000000..2fb10cb --- /dev/null +++ b/ansible/roles/base/handlers/main.yml @@ -0,0 +1,3 @@ +--- +- name: save iptables + command: netfilter-persistent save diff --git a/ansible/roles/base/tasks/main.yml b/ansible/roles/base/tasks/main.yml new file mode 100644 index 0000000..607144c --- /dev/null +++ b/ansible/roles/base/tasks/main.yml @@ -0,0 +1,69 @@ +--- +- name: Update packages + apt: + upgrade: dist + update_cache: yes + cache_valid_time: 3600 + +- name: Install System Utils Packages + apt: + name: + - net-tools + - nmon + - htop + - bmon + - mtr + state: present + +- name: Enable IP forwarding + sysctl: + name: net.ipv4.ip_forward + value: '1' + state: present + reload: yes + +- name: Load GRE module + modprobe: + name: ip_gre + state: present + +- name: Persist GRE module + lineinfile: + path: /etc/modules + line: ip_gre + create: yes + +- name: Pre-seed iptables-persistent IPv4 + debconf: + name: iptables-persistent + question: iptables-persistent/autosave_v4 + value: "true" + vtype: boolean + +- name: Pre-seed iptables-persistent IPv6 + debconf: + name: iptables-persistent + question: iptables-persistent/autosave_v6 + value: "false" + vtype: boolean + +- name: Install iptables-persistent + apt: + name: iptables-persistent + state: present + +- name: NAT masquerade for LAN + iptables: + table: nat + chain: POSTROUTING + jump: MASQUERADE + source: "{{ lan_cidr }}" + out_interface: "eth0" + comment: "LAN to Internet" + notify: save iptables + +- name: Allow forwarding + iptables: + chain: FORWARD + policy: ACCEPT + notify: save iptables diff --git a/ansible/roles/disable_ipv6/tasks/main.yml b/ansible/roles/disable_ipv6/tasks/main.yml new file mode 100644 index 0000000..69455c3 --- /dev/null +++ b/ansible/roles/disable_ipv6/tasks/main.yml @@ -0,0 +1,26 @@ +--- +- name: Disable IPv6 via sysctl + sysctl: + name: "{{ item }}" + value: '1' + sysctl_set: yes + state: present + reload: yes + loop: + - net.ipv6.conf.all.disable_ipv6 + - net.ipv6.conf.default.disable_ipv6 + - net.ipv6.conf.lo.disable_ipv6 + +- name: Disable IPv6 in GRUB + lineinfile: + path: /etc/default/grub + regexp: '^GRUB_CMDLINE_LINUX=' + line: 'GRUB_CMDLINE_LINUX="ipv6.disable=1"' + +- name: Update GRUB + command: update-grub + when: ansible_distribution == "Ubuntu" + +- name: Update initramfs + command: update-initramfs -u + when: ansible_distribution == "Ubuntu" diff --git a/ansible/roles/frr_router/handlers/main.yml b/ansible/roles/frr_router/handlers/main.yml new file mode 100644 index 0000000..89f0bd0 --- /dev/null +++ b/ansible/roles/frr_router/handlers/main.yml @@ -0,0 +1,5 @@ +- name: Restart FRR + systemd: + name: frr + state: restarted + daemon_reload: yes \ No newline at end of file diff --git a/ansible/roles/frr_router/tasks/main.yml b/ansible/roles/frr_router/tasks/main.yml new file mode 100644 index 0000000..138f675 --- /dev/null +++ b/ansible/roles/frr_router/tasks/main.yml @@ -0,0 +1,62 @@ +--- +- name: Update package cache + apt: + update_cache: yes + cache_valid_time: 3600 + +- name: Install FRR and required packages + apt: + name: + - frr + - frr-pythontools + state: present + +- name: Create FRR configuration directory + file: + path: /etc/frr + state: directory + owner: frr + group: frr + mode: '0755' + +- name: Enable FRR daemons + lineinfile: + path: /etc/frr/daemons + regexp: '^{{ item.daemon }}=' + line: '{{ item.daemon }}={{ item.state }}' + backup: yes + loop: + - { daemon: 'bgpd', state: 'yes' } + - { daemon: 'zebra', state: 'yes' } + - { daemon: 'staticd', state: 'yes' } + +- name: Configure FRR startup options + lineinfile: + path: /etc/frr/daemons + regexp: '^{{ item.option }}=' + line: '{{ item.option }}={{ item.value }}' + backup: yes + loop: + - { option: 'frr_options', value: '"-A 127.0.0.1"' } + +- name: Configure FRR + template: + src: bgpd_router.conf.j2 + dest: /etc/frr/frr.conf + owner: frr + group: frr + mode: '0640' + notify: Restart FRR + +- name: Restart FRR + systemd: + name: frr + state: restarted + daemon_reload: yes + +# - name: Wait for FRR to be fully started +# wait_for: +# path: /run/frr/bgpd.vty +# state: present +# timeout: 30 +# when: ansible_service_mgr == "systemd" \ No newline at end of file diff --git a/ansible/roles/frr_router/templates/backup/bgpd_router.conf.j2 b/ansible/roles/frr_router/templates/backup/bgpd_router.conf.j2 new file mode 100644 index 0000000..da6d9b0 --- /dev/null +++ b/ansible/roles/frr_router/templates/backup/bgpd_router.conf.j2 @@ -0,0 +1,58 @@ +! +frr version +frr defaults traditional +hostname {{ inventory_hostname }} +log syslog informational +! +router bgp {{ local_asn }} + bgp router-id {{ lan_ip }} + ! + network {{ lan_cidr }} + network {{ external_cidr }} + ! + neighbor {{ hostvars['router1'].lan_ip if inventory_hostname != 'router1' else hostvars['router2'].lan_ip }} remote-as {{ local_asn }} + neighbor {{ hostvars['router1'].lan_ip if inventory_hostname != 'router1' else hostvars['router2'].lan_ip }} update-source eth1 + ! + neighbor {{ gre_main_ip | ipmath(1) }} remote-as {{ remote_asn }} + neighbor {{ gre_main_ip | ipmath(1) }} update-source gre-main + neighbor {{ gre_main_ip | ipmath(1) }} timers 10 30 + ! + neighbor {{ gre_backup_ip | ipmath(1) }} remote-as {{ remote_asn }} + neighbor {{ gre_backup_ip | ipmath(1) }} update-source gre-backup + neighbor {{ gre_backup_ip | ipmath(1) }} timers 10 30 + ! + address-family ipv4 unicast + neighbor {{ hostvars['router1'].lan_ip if inventory_hostname != 'router1' else hostvars['router2'].lan_ip }} activate + neighbor {{ hostvars['router1'].lan_ip if inventory_hostname != 'router1' else hostvars['router2'].lan_ip }} next-hop-self + neighbor {{ gre_main_ip | ipmath(1) }} activate + neighbor {{ gre_backup_ip | ipmath(1) }} activate + neighbor {{ gre_main_ip | ipmath(1) }} route-map SET-PREF-{{ local_pref_main }} in + neighbor {{ gre_backup_ip | ipmath(1) }} route-map SET-PREF-{{ local_pref_backup }} in + neighbor {{ hostvars['router1'].lan_ip if inventory_hostname != 'router1' else hostvars['router2'].lan_ip }} route-map OUT-LAN out + neighbor {{ gre_main_ip | ipmath(1) }} route-map OUT-EXTERNAL out + neighbor {{ gre_backup_ip | ipmath(1) }} route-map OUT-EXTERNAL out + exit-address-family +! +route-map SET-PREF-400 permit 10 + set local-preference 400 +! +route-map SET-PREF-300 permit 10 + set local-preference 300 +! +route-map SET-PREF-200 permit 10 + set local-preference 200 +! +route-map SET-PREF-100 permit 10 + set local-preference 100 +! +route-map OUT-LAN permit 10 + match ip address prefix-list LAN-ONLY +! +route-map OUT-EXTERNAL permit 10 + match ip address prefix-list EXTERNAL-ONLY +! +ip prefix-list LAN-ONLY seq 5 permit {{ lan_cidr }} +ip prefix-list EXTERNAL-ONLY seq 5 permit {{ external_cidr }} +! +line vty +! diff --git a/ansible/roles/frr_router/templates/bgpd_router.conf.j2 b/ansible/roles/frr_router/templates/bgpd_router.conf.j2 new file mode 100644 index 0000000..5c3393a --- /dev/null +++ b/ansible/roles/frr_router/templates/bgpd_router.conf.j2 @@ -0,0 +1,104 @@ +! +! FRR BGP Configuration Template (Managed by Ansible) +! +frr version +frr defaults traditional +hostname {{ inventory_hostname }} +log syslog informational +! +! Static route for originating a network in BGP if it's not directly connected +! + +! Interface configurations +interface eth1 + ip address {{ lan_ip }}/{{ lan_network_prefix }} +! +interface gre-main + ip address {{ gre_main_ip }}/30 +! +interface gre-backup + ip address {{ gre_backup_ip }}/30 +! + +! BGP Configuration +router bgp {{ local_asn }} + bgp router-id {{ lan_ip }} + ! + ! Networks to originate from this router + network {{ lan_cidr }} + ! + ! iBGP neighbor configuration + neighbor {{ remote_ibgp_peer }} remote-as {{ local_asn }} + neighbor {{ remote_ibgp_peer }} update-source eth1 + ! + ! eBGP neighbors configuration + neighbor {{ gre_main_ip | ipmath(1) }} remote-as {{ remote_asn }} + neighbor {{ gre_main_ip | ipmath(1) }} ebgp-multihop 255 + neighbor {{ gre_main_ip | ipmath(1) }} update-source gre-main + neighbor {{ gre_main_ip | ipmath(1) }} timers 10 30 + ! + neighbor {{ gre_backup_ip | ipmath(1) }} remote-as {{ remote_asn }} + neighbor {{ gre_backup_ip | ipmath(1) }} ebgp-multihop 255 + neighbor {{ gre_backup_ip | ipmath(1) }} update-source gre-backup + neighbor {{ gre_backup_ip | ipmath(1) }} timers 10 30 + ! + address-family ipv4 unicast + ! Activate neighbors + neighbor {{ remote_ibgp_peer }} activate + neighbor {{ gre_main_ip | ipmath(1) }} activate + neighbor {{ gre_backup_ip | ipmath(1) }} activate + + ! Policy configurations for neighbors + neighbor {{ remote_ibgp_peer }} next-hop-self + neighbor {{ remote_ibgp_peer }} route-map ALLOW-RFC1918-OUT out + + neighbor {{ gre_main_ip | ipmath(1) }} route-map FROM-MAIN-PEER in + neighbor {{ gre_main_ip | ipmath(1) }} route-map TO-MAIN-PEER out + + neighbor {{ gre_backup_ip | ipmath(1) }} route-map FROM-BACKUP-PEER in + neighbor {{ gre_backup_ip | ipmath(1) }} route-map TO-BACKUP-PEER out + exit-address-family +! + +! +! Route Maps for BGP Policy +! +! Inbound policy from the main external peer +route-map FROM-MAIN-PEER permit 10 + match ip address prefix-list RFC1918-NETS + set local-preference {{ local_pref_main | default(400) }} +! +! Inbound policy from the backup external peer +route-map FROM-BACKUP-PEER permit 10 + match ip address prefix-list RFC1918-NETS + set local-preference {{ local_pref_backup | default(300) }} +! +! Outbound policy for the main external peer +route-map TO-MAIN-PEER permit 10 + match ip address prefix-list RFC1918-NETS + set metric {{ bgp_med_main }} +! +! Outbound policy for the main external peer +route-map TO-BACKUP-PEER permit 10 + match ip address prefix-list RFC1918-NETS + set metric {{ bgp_med_backup }} +! +! +! Prefix List for RFC1918 networks and default route filtering +! +! Rule 1: Explicitly deny the default route +ip prefix-list RFC1918-NETS seq 5 deny 0.0.0.0/0 + +! Rule 2: Permit 10.0.0.0/8 and all its subnets +ip prefix-list RFC1918-NETS seq 10 permit 10.0.0.0/8 le 32 + +! Rule 3: Permit 172.16.0.0/12 and all its subnets +ip prefix-list RFC1918-NETS seq 15 permit 172.16.0.0/12 le 32 + +! Rule 4: Permit 192.168.0.0/16 and all its subnets +ip prefix-list RFC1918-NETS seq 20 permit 192.168.0.0/16 le 32 +! + +! VTY lines for management access +line vty +! \ No newline at end of file diff --git a/ansible/roles/gre/handlers/main.yml b/ansible/roles/gre/handlers/main.yml new file mode 100644 index 0000000..0ac74b2 --- /dev/null +++ b/ansible/roles/gre/handlers/main.yml @@ -0,0 +1,3 @@ +--- +- name: save iptables + command: netfilter-persistent save \ No newline at end of file diff --git a/ansible/roles/gre/tasks/main.yml b/ansible/roles/gre/tasks/main.yml new file mode 100644 index 0000000..dc030c9 --- /dev/null +++ b/ansible/roles/gre/tasks/main.yml @@ -0,0 +1,60 @@ +--- +- name: Create GRE tunnels via Netplan + template: + src: gre-netplan.yaml.j2 + dest: "/etc/netplan/10-gre-{{ item }}.yaml" + mode: '0600' + loop: + - main + - backup + +- name: Apply Netplan + command: netplan apply + +- name: Refresh interface facts + ansible.builtin.setup: + gather_subset: + - 'interfaces' + +- name: Discover GRE interfaces + ansible.builtin.set_fact: + gre_interfaces: "{{ ansible_interfaces | select('match', '^gre-(m|b).*') | list }}" + +- name: Debug GRE interfaces + ansible.builtin.debug: + msg: "Found GRE interfaces: {{ gre_interfaces }}" + +- name: Validate that GRE interfaces exist + ansible.builtin.assert: + that: + - gre_interfaces | length > 0 + fail_msg: "No GRE interfaces found" + success_msg: "GRE interfaces found: {{ gre_interfaces }}" + +- name: Apply MSS clamping rule only if not already exists + ansible.builtin.shell: | + if ! iptables -t mangle -C FORWARD -d {{ lan_cidr }} -i {{ item }} -p tcp --tcp-flags SYN,RST SYN -m length --length 1321:65535 -j TCPMSS --set-mss 1320 -m comment --comment 'Clamp MSS to 1320 for GRE tunnel {{ item }}' 2>/dev/null; then + iptables -t mangle -I FORWARD -d {{ lan_cidr }} -i {{ item }} -p tcp --tcp-flags SYN,RST SYN -m length --length 1321:65535 -j TCPMSS --set-mss 1320 -m comment --comment 'Clamp MSS to 1320 for GRE tunnel {{ item }}' + echo "rule_added" + else + echo "rule_already_exists" + fi + loop: "{{ gre_interfaces }}" + register: iptables_shell + changed_when: + - iptables_shell.stdout == "rule_added" + notify: save iptables + +- name: Apply MSS clamping rule only if not already exists + ansible.builtin.shell: | + if ! iptables -t mangle -C FORWARD -s {{ lan_cidr }} -o {{ item }} -p tcp --tcp-flags SYN,RST SYN -m length --length 1321:65535 -j TCPMSS --set-mss 1320 -m comment --comment 'Clamp MSS to 1320 for GRE tunnel {{ item }}' 2>/dev/null; then + iptables -t mangle -I FORWARD -s {{ lan_cidr }} -o {{ item }} -p tcp --tcp-flags SYN,RST SYN -m length --length 1321:65535 -j TCPMSS --set-mss 1320 -m comment --comment 'Clamp MSS to 1320 for GRE tunnel {{ item }}' + echo "rule_added" + else + echo "rule_already_exists" + fi + loop: "{{ gre_interfaces }}" + register: iptables_shell + changed_when: + - iptables_shell.stdout == "rule_added" + notify: save iptables \ No newline at end of file diff --git a/ansible/roles/gre/templates/gre-netplan.yaml.j2 b/ansible/roles/gre/templates/gre-netplan.yaml.j2 new file mode 100644 index 0000000..454a841 --- /dev/null +++ b/ansible/roles/gre/templates/gre-netplan.yaml.j2 @@ -0,0 +1,19 @@ +{% if item == "main" %} +{% set gre_ip = gre_main_ip %} +{% set remote_wan = remote_main_isp_ip %} +{% elif item == "backup" %} +{% set gre_ip = gre_backup_ip %} +{% set remote_wan = remote_backup_isp_ip %} +{% endif %} +network: + version: 2 + tunnels: + gre-{{ item }}: + mode: gre + local: {{ wan_ip }} + remote: {{ remote_wan }} + addresses: + - {{ gre_ip }}/30 + mtu: 1360 + dhcp4: no + dhcp6: no diff --git a/ansible/roles/keepalived/tasks/main.yml b/ansible/roles/keepalived/tasks/main.yml new file mode 100644 index 0000000..5ae498c --- /dev/null +++ b/ansible/roles/keepalived/tasks/main.yml @@ -0,0 +1,17 @@ +--- +- name: Install Keepalived + apt: + name: keepalived + state: present + +- name: Configure Keepalived + template: + src: keepalived.conf.j2 + dest: /etc/keepalived/keepalived.conf + mode: '0644' + +- name: Enable and start Keepalived + systemd: + name: keepalived + enabled: yes + state: restarted diff --git a/ansible/roles/keepalived/templates/keepalived.conf.j2 b/ansible/roles/keepalived/templates/keepalived.conf.j2 new file mode 100644 index 0000000..81f4d76 --- /dev/null +++ b/ansible/roles/keepalived/templates/keepalived.conf.j2 @@ -0,0 +1,19 @@ +vrrp_instance VI_1 { + interface eth1 + state BACKUP + + # set priority + priority {% if inventory_hostname == 'router1' %}101{% else %}100{% endif %} + + # set VRRP Router ID + virtual_router_id 51 + + advert_int 1 + authentication { + auth_type PASS + auth_pass secret123 + } + virtual_ipaddress { + {{ vrrp_vip }}/{{ lan_network_prefix }} + } +} diff --git a/ansible/roles/private_base/tasks/main.yml b/ansible/roles/private_base/tasks/main.yml new file mode 100644 index 0000000..52a4a45 --- /dev/null +++ b/ansible/roles/private_base/tasks/main.yml @@ -0,0 +1,17 @@ +--- +- name: Update packages + apt: + upgrade: dist + update_cache: yes + cache_valid_time: 3600 + +- name: Install utilities + apt: + name: + - net-tools + - traceroute + - iproute2 + - mtr + - bmon + - htop + state: present diff --git a/ansible/roles/strongswan/tasks/main.yml b/ansible/roles/strongswan/tasks/main.yml new file mode 100644 index 0000000..523a76c --- /dev/null +++ b/ansible/roles/strongswan/tasks/main.yml @@ -0,0 +1,32 @@ +--- +- name: Install StrongSwan including extra plugins + apt: + name: [strongswan, strongswan-swanctl, libstrongswan-extra-plugins] + state: present + +- name: Configure swanctl.conf + template: + src: swanctl.conf.j2 + dest: /etc/swanctl/conf.d/deployment.conf + mode: '0644' + +- name: Configure StrongSwan charon.conf to load all tunnels on startup + block: + - name: Check if swanctl startup command already exists in charon.conf + command: grep -q "swanctl = /usr/sbin/swanctl --load-all" /etc/strongswan.d/charon.conf + register: swanctl_exists + failed_when: false + changed_when: false + + - name: Update charon.conf to include swanctl startup command + shell: | + sed -i '/start-scripts\s*{/,/}/{ + /}/i\ swanctl = /usr/sbin/swanctl --load-all + }' /etc/strongswan.d/charon.conf + when: swanctl_exists.rc != 0 + +- name: Restart StrongSwan (using strongswan-starter) + systemd: + name: strongswan-starter + state: restarted + enabled: yes \ No newline at end of file diff --git a/ansible/roles/strongswan/templates/swanctl.conf.j2 b/ansible/roles/strongswan/templates/swanctl.conf.j2 new file mode 100644 index 0000000..e0338ca --- /dev/null +++ b/ansible/roles/strongswan/templates/swanctl.conf.j2 @@ -0,0 +1,58 @@ +connections { + gre-main { + local_addrs = {{ wan_ip }} + remote_addrs = {{ remote_main_isp_ip }} + + local { auth = psk } + remote { auth = psk } + + version = 2 + proposals = {{ ike_proposal }} + rekey_time = {{ ike_lifetime }}s + + children { + gre-main { + local_ts = dynamic[gre] + remote_ts = dynamic[gre] + esp_proposals = {{ esp_proposal }} + rekey_time = {{ esp_lifetime }}s + mode = transport + dpd_action = restart + close_action = restart + } + } + dpd_timeout = {{ dpd_timeout }}s + } + + gre-backup { + local_addrs = {{ wan_ip }} + remote_addrs = {{ remote_backup_isp_ip }} + + local { auth = psk } + remote { auth = psk } + + version = 2 + proposals = {{ ike_proposal }} + rekey_time = {{ ike_lifetime }}s + + children { + gre-backup { + mode = transport + local_ts = dynamic[gre] + remote_ts = dynamic[gre] + esp_proposals = {{ esp_proposal }} + rekey_time = {{ esp_lifetime }}s + mode = transport + dpd_action = restart + close_action = restart + } + } + dpd_timeout = {{ dpd_timeout }}s + } +} + +secrets { + ike { + secret = "{{ ipsec_psk }}" + } +} diff --git a/ansible/site.yml b/ansible/site.yml new file mode 100644 index 0000000..5031161 --- /dev/null +++ b/ansible/site.yml @@ -0,0 +1,29 @@ +--- +### Disable IPv6 + +- name: Disable IPv6 on all servers + hosts: all + become: yes + roles: + - disable_ipv6 + + +### Configure Routers + +- name: Configure Routers + hosts: ubuntu_routers + become: yes + roles: + - base + - gre + - strongswan + - frr_router + - keepalived + +### (optional) configure private servers + +- name: Configure Private Servers + hosts: private_servers + become: yes + roles: + - private_base \ No newline at end of file diff --git a/terraform/images.tf b/terraform/images.tf new file mode 100644 index 0000000..d6456b3 --- /dev/null +++ b/terraform/images.tf @@ -0,0 +1,13 @@ +# Get all Ubuntu images +# data "vkcs_images_images" "images" { +# visibility = "public" +# default = true +# properties = { +# mcs_os_distro = "ubuntu" +# } +# } + +# List all Ubuntu images +# output "all_image_names" { +# value = [for img in data.vkcs_images_images.images.images : img.name] +# } \ No newline at end of file diff --git a/terraform/main.tf b/terraform/main.tf new file mode 100644 index 0000000..05c60c5 --- /dev/null +++ b/terraform/main.tf @@ -0,0 +1,293 @@ +data "vkcs_images_image" "ubuntu24" { + visibility = "public" + most_recent = true + properties = { + mcs_os_distro = "ubuntu" + mcs_os_version = "24.04" + } +} + +data "vkcs_networking_network" "extnet" { + name = "internet" + sdn = "sprut" +} + +# LAN Network +resource "vkcs_networking_network" "lan_net" { + name = "router-lan-net" + sdn = "sprut" + admin_state_up = true +} + +resource "vkcs_networking_subnet" "lan_subnet" { + network_id = vkcs_networking_network.lan_net.id + name = "router-lan-subnet" + cidr = "10.200.10.0/24" + gateway_ip = "10.200.10.1" + dns_nameservers = ["8.8.8.8", "1.1.1.1"] + sdn = "sprut" + + allocation_pool { + start = "10.200.10.100" + end = "10.200.10.200" + } +} + +# Security Groups +resource "vkcs_networking_secgroup" "router_sg" { + name = "router-sg" + sdn = "sprut" +} + +resource "vkcs_networking_secgroup" "private_sg" { + name = "private-sg" + sdn = "sprut" +} + +# Private SG rules +resource "vkcs_networking_secgroup_rule" "from_rfc_net192_in" { + direction = "ingress" + remote_ip_prefix = "192.168.0.0/16" + security_group_id = vkcs_networking_secgroup.private_sg.id + sdn = "sprut" +} + +resource "vkcs_networking_secgroup_rule" "from_rfc_net172_in" { + direction = "ingress" + remote_ip_prefix = "172.16.0.0/12" + security_group_id = vkcs_networking_secgroup.private_sg.id + sdn = "sprut" +} + +resource "vkcs_networking_secgroup_rule" "from_rfc_net10_in" { + direction = "ingress" + remote_ip_prefix = "10.0.0.0/8" + security_group_id = vkcs_networking_secgroup.private_sg.id + sdn = "sprut" +} + +# Router SG rules +resource "vkcs_networking_secgroup_rule" "router_ssh" { + direction = "ingress" + protocol = "tcp" + port_range_min = 22 + port_range_max = 22 + remote_ip_prefix = "0.0.0.0/0" + security_group_id = vkcs_networking_secgroup.router_sg.id + sdn = "sprut" +} + +resource "vkcs_networking_secgroup_rule" "router_icmp" { + direction = "ingress" + protocol = "icmp" + remote_ip_prefix = "0.0.0.0/0" + security_group_id = vkcs_networking_secgroup.router_sg.id + sdn = "sprut" +} + +resource "vkcs_networking_secgroup_rule" "router_ipsec_ike" { + direction = "ingress" + protocol = "udp" + port_range_min = 500 + port_range_max = 500 + remote_ip_prefix = "0.0.0.0/0" + security_group_id = vkcs_networking_secgroup.router_sg.id + sdn = "sprut" +} + +resource "vkcs_networking_secgroup_rule" "router_ipsec_nat_t" { + direction = "ingress" + protocol = "udp" + port_range_min = 4500 + port_range_max = 4500 + remote_ip_prefix = "0.0.0.0/0" + security_group_id = vkcs_networking_secgroup.router_sg.id + sdn = "sprut" +} + +# Router LAN Ports +resource "vkcs_networking_port" "lan_port1" { + name = "router1-lan-port" + network_id = vkcs_networking_network.lan_net.id + admin_state_up = true + port_security_enabled = false + full_security_groups_control = true + security_group_ids = [] + sdn = "sprut" + fixed_ip { + subnet_id = vkcs_networking_subnet.lan_subnet.id + ip_address = "10.200.10.254" + } +} + +resource "vkcs_networking_port" "lan_port2" { + name = "router2-lan-port" + network_id = vkcs_networking_network.lan_net.id + admin_state_up = true + port_security_enabled = false + full_security_groups_control = true + security_group_ids = [] + sdn = "sprut" + fixed_ip { + subnet_id = vkcs_networking_subnet.lan_subnet.id + ip_address = "10.200.10.253" + } +} + +resource "vkcs_compute_instance" "router1" { + name = "router1" + image_id = data.vkcs_images_image.ubuntu24.id + flavor_name = "STD3-4-4" + availability_zone = "ME1" + key_pair = var.ssh_key_name + + security_group_ids = [ + vkcs_networking_secgroup.router_sg.id, + "d479b4d7-55b3-4ff1-bf8d-24d826a38f11" + ] + + config_drive = true + + # Configure persistent networking using script + user_data = file("${path.module}/scripts/network-init.sh") + + # WAN: dynamically created port + network { + uuid = data.vkcs_networking_network.extnet.id + } + + # LAN: pre-created port + network { + port = vkcs_networking_port.lan_port1.id + } + + block_device { + uuid = data.vkcs_images_image.ubuntu24.id + source_type = "image" + volume_size = 20 + boot_index = 0 + destination_type = "volume" + volume_type = "ceph-ssd" + delete_on_termination = true + } +} + +resource "vkcs_compute_instance" "router2" { + name = "router2" + image_id = data.vkcs_images_image.ubuntu24.id + flavor_name = "STD3-4-4" + availability_zone = "ME1" + key_pair = var.ssh_key_name + + security_group_ids = [ + vkcs_networking_secgroup.router_sg.id, + "d479b4d7-55b3-4ff1-bf8d-24d826a38f11" + ] + + config_drive = true + + # Configure persistent networking using script + user_data = file("${path.module}/scripts/network-init.sh") + + # WAN: dynamically created port + network { + uuid = data.vkcs_networking_network.extnet.id + } + + # LAN: pre-created port + network { + port = vkcs_networking_port.lan_port2.id + } + + block_device { + uuid = data.vkcs_images_image.ubuntu24.id + source_type = "image" + volume_size = 20 + boot_index = 0 + destination_type = "volume" + volume_type = "ceph-ssd" + delete_on_termination = true + } +} + +resource "vkcs_compute_instance" "priv_srv_01" { + name = "Priv-SRV-01" + image_id = data.vkcs_images_image.ubuntu24.id + flavor_name = "STD3-4-4" + availability_zone = "ME1" + key_pair = var.ssh_key_name + + security_group_ids = [ + vkcs_networking_secgroup.private_sg.id, + "d479b4d7-55b3-4ff1-bf8d-24d826a38f11" + ] + + network { + uuid = vkcs_networking_network.lan_net.id + } + + block_device { + uuid = data.vkcs_images_image.ubuntu24.id + source_type = "image" + volume_size = 20 + boot_index = 0 + destination_type = "volume" + volume_type = "ceph-ssd" + delete_on_termination = true + } + +} + +resource "vkcs_compute_instance" "priv_srv_02" { + name = "Priv-SRV-02" + image_id = data.vkcs_images_image.ubuntu24.id + flavor_name = "STD3-4-4" + availability_zone = "ME1" + key_pair = var.ssh_key_name + + security_group_ids = [ + vkcs_networking_secgroup.private_sg.id, + "d479b4d7-55b3-4ff1-bf8d-24d826a38f11" + ] + + network { + uuid = vkcs_networking_network.lan_net.id + } + + block_device { + uuid = data.vkcs_images_image.ubuntu24.id + source_type = "image" + volume_size = 20 + boot_index = 0 + destination_type = "volume" + volume_type = "ceph-ssd" + delete_on_termination = true + } +} + +resource "vkcs_compute_instance" "priv_srv_03" { + name = "Priv-SRV-03" + image_id = data.vkcs_images_image.ubuntu24.id + flavor_name = "STD3-4-4" + availability_zone = "MS1" + key_pair = var.ssh_key_name + + security_group_ids = [ + vkcs_networking_secgroup.private_sg.id, + "d479b4d7-55b3-4ff1-bf8d-24d826a38f11" + ] + + network { + uuid = vkcs_networking_network.lan_net.id + } + + block_device { + uuid = data.vkcs_images_image.ubuntu24.id + source_type = "image" + volume_size = 20 + boot_index = 0 + destination_type = "volume" + volume_type = "ceph-ssd" + delete_on_termination = true + } +} \ No newline at end of file diff --git a/terraform/scripts/network-init.sh b/terraform/scripts/network-init.sh new file mode 100644 index 0000000..ac53841 --- /dev/null +++ b/terraform/scripts/network-init.sh @@ -0,0 +1,434 @@ +#!/bin/bash +set -e + +log() { + echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a /var/log/network-config.log +} + +log "Starting network configuration..." + +# Validate required commands exist +for cmd in ip netplan systemctl; do + if ! command -v "$cmd" &> /dev/null; then + log "ERROR: Required command '$cmd' is not available" + exit 1 + fi +done + +# Validate directories exist +if [ ! -d "/etc/netplan" ]; then + log "ERROR: /etc/netplan directory does not exist" + exit 1 +fi + +if [ ! -d "/sys/class/net" ]; then + log "ERROR: /sys/class/net directory does not exist" + exit 1 +fi + +# Enhanced function to check if IP is in private subnet (RFC 1918) +is_private_ip() { + local ip="$1" + local clean_ip=$(echo "$ip" | cut -d'/' -f1) # Remove subnet mask if present + + # Check RFC 1918 private ranges: + # 10.0.0.0/8 (10.0.0.0 - 10.255.255.255) + # 172.16.0.0/12 (172.16.0.0 - 172.31.255.255) + # 192.168.0.0/16 (192.168.0.0 - 192.168.255.255) + + if [[ $clean_ip =~ ^10\. ]]; then + return 0 # 10.0.0.0/8 + elif [[ $clean_ip =~ ^172\.(1[6-9]|2[0-9]|3[0-1])\. ]]; then + return 0 # 172.16.0.0/12 + elif [[ $clean_ip =~ ^192\.168\. ]]; then + return 0 # 192.168.0.0/16 + else + return 1 # Is public IP + fi +} + +# Function to convert CIDR to netmask +cidr_to_netmask() { + local cidr="$1" + + # Input validation + if [[ ! $cidr =~ ^[0-9]+$ ]] || [ "$cidr" -lt 0 ] || [ "$cidr" -gt 32 ]; then + echo "Error: CIDR must be a number between 0 and 32" >&2 + return 1 + fi + + local netmask="" + local full_octets=$((cidr / 8)) + local remaining_bits=$((cidr % 8)) + local partial_octet=0 + + # Calculate partial octet if there are remaining bits + if [ "$remaining_bits" -gt 0 ]; then + partial_octet=$((256 - (256 >> remaining_bits))) + fi + + for ((i=0; i<4; i++)); do + if [ "$i" -lt "$full_octets" ]; then + netmask="${netmask}255" + elif [ "$i" -eq "$full_octets" ] && [ "$remaining_bits" -gt 0 ]; then + netmask="${netmask}${partial_octet}" + else + netmask="${netmask}0" + fi + + if [ "$i" -lt 3 ]; then + netmask="${netmask}." + fi + done + + echo "$netmask" +} + +# Function to convert netmask to CIDR +netmask_to_cidr() { + local netmask="$1" + local cidr=0 + + # Use -a for array, not -o + IFS='.' read -ra octets <<< "$netmask" + + for octet in "${octets[@]}"; do + case $octet in + 255) cidr=$((cidr + 8)) ;; + 254) cidr=$((cidr + 7)) ;; + 252) cidr=$((cidr + 6)) ;; + 248) cidr=$((cidr + 5)) ;; + 240) cidr=$((cidr + 4)) ;; + 224) cidr=$((cidr + 3)) ;; + 192) cidr=$((cidr + 2)) ;; + 128) cidr=$((cidr + 1)) ;; + 0) ;; + *) echo "32"; return 1 ;; # Invalid netmask, default to /32 + esac + done + + echo "$cidr" +} + +# Function to extract IP, netmask, and CIDR from CIDR notation +get_ip_netmask_cidr() { + local cidr_ip="$1" + local ip=$(echo "$cidr_ip" | cut -d'/' -f1) + local cidr_part=$(echo "$cidr_ip" | cut -d'/' -f2) + local netmask="" + local cidr="" + + if [[ $cidr_part =~ ^[0-9]{1,2}$ ]]; then + # CIDR notation (e.g., /24) + cidr="$cidr_part" + netmask=$(cidr_to_netmask "$cidr") + elif [[ $cidr_part =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + # Already in netmask format + netmask="$cidr_part" + cidr=$(netmask_to_cidr "$netmask") + else + # Default to /32 if no valid netmask found + cidr="32" + netmask="255.255.255.255" + fi + + echo "$ip,$netmask,$cidr" +} + +# Function to extract the first private IPv4 address, netmask, and CIDR from an interface +get_private_ip_netmask_cidr() { + local interface="$1" + local ip_addrs=$(ip addr show "$interface" 2>/dev/null | grep 'inet ' | grep -v '127.0.0.1' | awk '{print $2}') + + if [ -n "$ip_addrs" ]; then + while IFS= read -r ip; do + if [ -n "$ip" ] && is_private_ip "$ip"; then + # Return IP, netmask, and CIDR + get_ip_netmask_cidr "$ip" + return 0 + fi + done <<< "$ip_addrs" + fi + return 1 +} + +# Function to extract the first public IPv4 address, netmask, and CIDR from an interface +get_public_ip_netmask_cidr() { + local interface="$1" + local ip_addrs=$(ip addr show "$interface" 2>/dev/null | grep 'inet ' | grep -v '127.0.0.1' | awk '{print $2}') + + if [ -n "$ip_addrs" ]; then + while IFS= read -r ip; do + if [ -n "$ip" ] && ! is_private_ip "$ip"; then + # Return IP, netmask, and CIDR + get_ip_netmask_cidr "$ip" + return 0 + fi + done <<< "$ip_addrs" + fi + return 1 +} + +# Function to get default gateway for an interface +get_interface_gateway() { + local interface="$1" + + # Try to get gateway from route table for the specific interface + local gateway=$(ip route show dev "$interface" 2>/dev/null | grep '^default via' | awk '{print $3}' | head -n1) + + if [ -n "$gateway" ]; then + echo "$gateway" + return 0 + fi + + # Fallback: get default gateway from main route table + gateway=$(ip route show 2>/dev/null | grep '^default via' | awk '{print $3}' | head -n1) + + if [ -n "$gateway" ]; then + echo "$gateway" + return 0 + fi + + return 1 +} + +# Enhanced function to check if interface has private IP +has_private_ip() { + local interface="$1" + local ip_addrs=$(ip addr show "$interface" 2>/dev/null | grep 'inet ' | grep -v '127.0.0.1' | awk '{print $2}') + + if [ -n "$ip_addrs" ]; then + while IFS= read -r ip; do + if [ -n "$ip" ] && is_private_ip "$ip"; then + return 0 # Has at least one private IP + fi + done <<< "$ip_addrs" + fi + return 1 # No private IP +} + +# Identify LAN and WAN interfaces with enhanced logic +LAN_IFACE="" +LAN_MAC="" +LAN_IFACE_IPv4="" +LAN_NETMASK="" +LAN_CIDR="" +WAN_IFACE="" +WAN_MAC="" +WAN_IFACE_IPv4="" +WAN_NETMASK="" +WAN_CIDR="" +WAN_GW_IPv4="" + +# First pass: Look for interfaces with private IPs (LAN candidates) +for iface in $(ls /sys/class/net/ | grep -v lo); do + if has_private_ip "$iface"; then + if [ -z "$LAN_IFACE" ]; then + LAN_IFACE="$iface" + LAN_MAC=$(cat "/sys/class/net/$iface/address" 2>/dev/null) + + # Get IP, netmask, and CIDR + lan_ip_netmask_cidr=$(get_private_ip_netmask_cidr "$iface") + if [ -n "$lan_ip_netmask_cidr" ]; then + LAN_IFACE_IPv4=$(echo "$lan_ip_netmask_cidr" | cut -d',' -f1) + LAN_NETMASK=$(echo "$lan_ip_netmask_cidr" | cut -d',' -f2) + LAN_CIDR=$(echo "$lan_ip_netmask_cidr" | cut -d',' -f3) + fi + + log "Identified LAN interface: $iface (MAC: $LAN_MAC) with private IP: $LAN_IFACE_IPv4, Netmask: $LAN_NETMASK, CIDR: /$LAN_CIDR" + else + log "Multiple LAN interface candidates found: $LAN_IFACE and $iface" + fi + fi +done + +# Second pass: Look for WAN interface +for iface in $(ls /sys/class/net/ | grep -v lo); do + # Skip if this is already identified as LAN + [ "$iface" = "$LAN_IFACE" ] && continue + + ip_addrs=$(ip addr show "$iface" 2>/dev/null | grep 'inet ' | grep -v '127.0.0.1' | awk '{print $2}') + + if [ -n "$ip_addrs" ]; then + # Check if interface has public IPs + has_public="false" + while IFS= read -r ip; do + if [ -n "$ip" ] && ! is_private_ip "$ip"; then + has_public="true" + break + fi + done <<< "$ip_addrs" + + if [ "$has_public" = "true" ]; then + WAN_IFACE="$iface" + WAN_MAC=$(cat "/sys/class/net/$iface/address" 2>/dev/null) + + # Get both IP, netmask, and CIDR for WAN + wan_ip_netmask_cidr=$(get_public_ip_netmask_cidr "$iface") + if [ -n "$wan_ip_netmask_cidr" ]; then + WAN_IFACE_IPv4=$(echo "$wan_ip_netmask_cidr" | cut -d',' -f1) + WAN_NETMASK=$(echo "$wan_ip_netmask_cidr" | cut -d',' -f2) + WAN_CIDR=$(echo "$wan_ip_netmask_cidr" | cut -d',' -f3) + fi + + WAN_GW_IPv4=$(get_interface_gateway "$iface") + log "Identified WAN interface: $iface (MAC: $WAN_MAC) with public IP: $WAN_IFACE_IPv4, Netmask: $WAN_NETMASK, CIDR: /$WAN_CIDR, Gateway: $WAN_GW_IPv4" + break + fi + else + # Interface with no IP - potential WAN candidate + if [ -z "$WAN_IFACE" ]; then + WAN_IFACE="$iface" + WAN_MAC=$(cat "/sys/class/net/$iface/address" 2>/dev/null) + WAN_GW_IPv4=$(get_interface_gateway "$iface") + log "Identified WAN interface candidate: $iface (MAC: $WAN_MAC) - no IP assigned, Gateway: $WAN_GW_IPv4" + fi + fi +done + +# If no WAN found but we have LAN, pick first non-LAN interface +if [ -z "$WAN_IFACE" ] && [ -n "$LAN_IFACE" ]; then + for iface in $(ls /sys/class/net/ | grep -v lo); do + if [ "$iface" != "$LAN_IFACE" ]; then + WAN_IFACE="$iface" + WAN_MAC=$(cat "/sys/class/net/$iface/address" 2>/dev/null) + WAN_GW_IPv4=$(get_interface_gateway "$iface") + log "Assumed WAN interface: $iface (MAC: $WAN_MAC) - default selection, Gateway: $WAN_GW_IPv4" + break + fi + done +fi + +# Final assignment and logging +log "Final interface assignment:" +log " LAN Interface: $LAN_IFACE (MAC: $LAN_MAC)" + +if [ -n "$LAN_IFACE_IPv4" ]; then + log " LAN IPv4: $LAN_IFACE_IPv4" + log " LAN Netmask: $LAN_NETMASK" + log " LAN CIDR: /$LAN_CIDR" +else + log " LAN IPv4: Not assigned" + LAN_IFACE_IPv4="" # Ensure it's empty if no IP found + LAN_NETMASK="" # Ensure netmask is also empty + LAN_CIDR="" # Ensure CIDR is also empty +fi + +if [ -n "$WAN_IFACE" ]; then + # Only set WAN IP, Netmask, CIDR and Gateway if WAN interface is detected + log " WAN Interface: $WAN_IFACE (MAC: $WAN_MAC)" + + if [ -n "$WAN_IFACE_IPv4" ]; then + log " WAN IPv4: $WAN_IFACE_IPv4" + log " WAN Netmask: $WAN_NETMASK" + log " WAN CIDR: /$WAN_CIDR" + else + log " WAN IPv4: Not assigned" + log " WAN Netmask: Not available" + log " WAN CIDR: Not available" + WAN_IFACE_IPv4="" # Ensure it's empty if no IP found + WAN_NETMASK="" # Ensure netmask is also empty + WAN_CIDR="" # Ensure CIDR is also empty + fi + + if [ -n "$WAN_GW_IPv4" ]; then + log " WAN Gateway: $WAN_GW_IPv4" + else + log " WAN Gateway: Not detected" + WAN_GW_IPv4="" # Ensure it's empty if no gateway found + fi +else + log " WAN Interface: Not detected" + # Ensure WAN-related variables are empty + WAN_IFACE_IPv4="" + WAN_NETMASK="" + WAN_CIDR="" + WAN_GW_IPv4="" +fi + +# Validate that we have the required information before proceeding +if [ -z "$WAN_MAC" ] || [ -z "$WAN_IFACE_IPv4" ] || [ -z "$WAN_CIDR" ] || [ -z "$WAN_GW_IPv4" ] || [ -z "$LAN_MAC" ] || [ -z "$LAN_IFACE_IPv4" ] || [ -z "$LAN_CIDR" ]; then + log "ERROR: Required network information is missing. Cannot proceed with network configuration." + log "Missing information:" + [ -z "$WAN_MAC" ] && log " - WAN MAC address" + [ -z "$WAN_IFACE_IPv4" ] && log " - WAN IP address" + [ -z "$WAN_CIDR" ] && log " - WAN CIDR" + [ -z "$WAN_GW_IPv4" ] && log " - WAN Gateway" + [ -z "$LAN_MAC" ] && log " - LAN MAC address" + [ -z "$LAN_IFACE_IPv4" ] && log " - LAN IP address" + [ -z "$LAN_CIDR" ] && log " - LAN CIDR" + exit 1 +fi + +# Create backup of existing netplan config if it exists +if [ -f "/etc/netplan/50-cloud-init.yaml" ]; then + cp "/etc/netplan/50-cloud-init.yaml" "/etc/netplan/50-cloud-init.yaml.backup.$(date +%s)" + log "Backed up existing netplan configuration" +fi + +# Disabling Cloud-Init +log "Disabling Cloud-Init for Networking..." + +cat > /etc/cloud/cloud.cfg.d/99-disable-network-config.cfg <<'EOF' +network: {config: disabled} +EOF + +log "Creating network config file" + +# Creating netplan config with proper validation +cat > /etc/netplan/50-cloud-init.yaml << EOF +network: + version: 2 + ethernets: + wan-iface: + match: + macaddress: "$WAN_MAC" + set-name: eth0 + dhcp4: false + addresses: + - $WAN_IFACE_IPv4/$WAN_CIDR + routes: + - to: default + via: $WAN_GW_IPv4 + nameservers: + addresses: [8.8.8.8, 1.1.1.1] + lan-iface: + match: + macaddress: "$LAN_MAC" + set-name: eth1 + dhcp4: false + addresses: + - $LAN_IFACE_IPv4/$LAN_CIDR +EOF + +log "Network config file has been created" + +# Test the netplan configuration before applying +if netplan --debug generate; then + log "Netplan configuration generated successfully" + + # Apply the configuration + netplan apply + systemctl restart systemd-networkd + + # Wait a moment for network to come up + sleep 2 + + # Test connectivity to gateway + if ping -c 1 -W 5 "$WAN_GW_IPv4" >/dev/null 2>&1; then + log "Network configuration applied successfully! Gateway is reachable." + + # Ask for confirmation before rebooting (optional safety measure) + log "Network configuration applied. Rebooting in 10 seconds. Press Ctrl+C to cancel." + sleep 10 + reboot + else + log "WARNING: Gateway is not reachable after configuration. Not rebooting to prevent lockout." + log "Please check the network configuration manually." + exit 1 + fi +else + log "ERROR: Netplan configuration failed to generate. Rolling back changes." + # Note: In a real scenario, you'd want to restore the backup here + exit 1 +fi \ No newline at end of file diff --git a/terraform/terraform.tfvars b/terraform/terraform.tfvars new file mode 100644 index 0000000..88ce6a2 --- /dev/null +++ b/terraform/terraform.tfvars @@ -0,0 +1,4 @@ +username = "user@domain.local" +password = "DemoUserPassw" +project_id = "XXXXXd424998422XXXXXf13ac9XXXXX" +ssh_key_name = "AdminSSH" \ No newline at end of file diff --git a/terraform/variables.tf b/terraform/variables.tf new file mode 100644 index 0000000..d762013 --- /dev/null +++ b/terraform/variables.tf @@ -0,0 +1,26 @@ +variable "username" { + description = "VK Cloud username" + type = string +} + +variable "password" { + description = "VK Cloud password" + type = string + sensitive = true +} + +variable "project_id" { + description = "Project ID" + type = string +} + +variable "region" { + description = "Region" + type = string + default = "ME1" +} + +variable "ssh_key_name" { + description = "Name of SSH key pair in VK Cloud" + type = string +}