CloudRouterDemo

This commit is contained in:
ayurishchev committed 2025-11-17 14:19:38 +03:00
commit 62c86b96ff
23 files changed
+1382

No files matched your search

+8
View File
@@ -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 }}"
+23
View File
@@ -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=<Your_CloudOS_User>
ansible_ssh_private_key_file=<Your_Path_to_Admin_SSH>
# 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!
+3
View File
@@ -0,0 +1,3 @@
---
- name: save iptables
command: netfilter-persistent save
+69
View File
@@ -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
+26
View File
@@ -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"
@@ -0,0 +1,5 @@
- name: Restart FRR
systemd:
name: frr
state: restarted
daemon_reload: yes
+62
View File
@@ -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"
@@ -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
!
@@ -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
!
+3
View File
@@ -0,0 +1,3 @@
---
- name: save iptables
command: netfilter-persistent save
+60
View File
@@ -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
@@ -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
+17
View File
@@ -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
@@ -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 }}
}
}
+17
View File
@@ -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
+32
View File
@@ -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
@@ -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 }}"
}
}
+29
View File
@@ -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
+13
View File
@@ -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]
# }
+293
View File
@@ -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
}
}
+434
View File
@@ -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
+4
View File
@@ -0,0 +1,4 @@
username = "user@domain.local"
password = "DemoUserPassw"
project_id = "XXXXXd424998422XXXXXf13ac9XXXXX"
ssh_key_name = "AdminSSH"
+26
View File
@@ -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
}