pexels-athena-910299

圖片來源

為什麼使用 Jumpserver 要上靜態路由?

公司現有 VM Network 環境只要是 Web server 的, default gateway 都是設定 Private IP Class B (172.x.0.0/16) 網段,也就是 Load Balance 身上。同時該台 VM 也會有 192.168.x.0/23 /24 的網卡 (192.168.x.254 or 253Forti 身上)
直接上 routing table 會比文字敘述更清楚~

1
2
3
4
5
ip r

default via 172.x.x.254 dev eth0 proto static metric 100 
172.x.x.0/24 dev eth0 proto kernel scope link src 172.x.x.131 metric 100 
192.x.x.0/23 dev eth1 proto kernel scope link src 192.x.x.131 metric 101 

Jumpserver 只有 192.168.x.0/24 的網卡 (on Forti)
Jumpserver 只有 192.168.x.0/24 的網卡 (on Forti)
Jumpserver 只有 192.168.x.0/24 的網卡 (on Forti)

因為很重要,講三次

情境一: 網路組 NAT Disabled

假設 IP:

Jumpserver 192.168.x.74/24
Target VM 192.168.x.131/24

在沒有以嚴格政策 (Policy) 限制的情況下,Jumpserver 是可以 單向 碰到 Target VM 的。
在 Jumpserver 執行 ping 192.168.x.131,Target VM 以 tcpdump 是可以看到 src: 192.168.x.74 的 ICMP 封包的。
不過 Jumpserver 卻收不到 echo 回應 原因是 Target VM 會按照既有 routing table 將 echo 封包傳給 default gateway。

情況二: 網路組 NAT Enabled

這個時候當 Forti 收到 src: 192.168.x.74 dst: 192.168.x.131 的封包時會 src 替換成 192.168.x.254 (Forti 身上的 Interface)
這個時候 Jumpserver 可以收到來自 Target VM 的 echo 回應沒有問題。但產生一個問題:

  1. Target VM 收到的封包都是來自 192.168.x.254 的。這樣無法精準控管權限

而靜態路由的解法可以同時達成 (1) 雙方都可以碰到彼此 (2) 精準權限控管



shell script 目標功能

  1. 編輯 /etc/hosts.deny,增加 sshd: <Jumpserver_IP>:allow。(並且統一 sshd: all 格式)
  2. 增加靜態路由 (1) 即時生效 (2) 永久。 How to Permanently add Static Route in Linux
  3. 把 Jumpserver SSH public key 加入 /root/.ssh/known_hosts

DC1 施作時遇到的問題

  1. /etc/hosts.deny 格式不一致 (有無空格) 導致替換有問題。相關腳本 edit_hosts_allow_script.sh
  2. eth0 eth1 不一致 (某些 VM 192.168.0.0/16 是綁在 eth0)。這次我想用 Ansible 做判斷式抓取。 (註: 後來還是用 shell script 做判斷)
  3. (新增功能,之前是用 powershell 跑) 將 DC2_IT_Jumpserver_Demo 的 ssh public key 加到 /root/.ssh/known_hosts 裡面

jumpserver_static_routing_setup.sh

TARGET_IP 設定 Jumpserver 的 IP。
GATEWAY 預設 254,當該網段的 Gateway 不是在 254 時更改。
ssh_key_nameid.rsa.pub 裡面的名稱,用於搜尋是否已存在 VM 裡面。
(20210910 新增) DB_subnet 當遇到 DB 網段時必須手動指定 D2D 網段,否則 Jumpserver 收到的封包封包來源是錯的! 因為 DB 的 default gateway 是 W2D 網段。(以 Jumpserver 使用 D2D 網段訪問目標來說)
謝謝 Rock 被我盧這麼久 XD

註: 目前網卡名稱只支援 eth.*ens.*
註: 應該先判斷是否有 172 這張網卡,在判斷是否有 192 這張網卡。如果沒有 172 (例如: DB) 就不用加靜態路由了

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
#!/bin/bash


export TARGET_IP='192.168.x.74'
export GATEWAY='254'
export ssh_key_name="root@DC2_BD_IT_Jumpserver_Demo"
export DB_subnet=""

## Task 1: edit `/etc/hosts.deny`

sed -i -e 's|^sshd:\ \{0,\}all||g' -e '/^$/d' /etc/hosts.deny
echo "sshd: ${TARGET_IP}:allow" >> /etc/hosts.deny
echo "sshd: all" >> /etc/hosts.deny


## ==========
## Task 2: add static routing (即時 + 永久)

### 先判斷是否有 192.168.x.0/24 這張網卡,若有才執行。 FLAG != '0' 代表沒有

ifconfig | grep '192.168.' > /dev/null
FLAG_eth=$(echo $?)

if [ "$FLAG_eth" == "0" ]; then

    #echo '找到 192.168. 網卡!'

    ## 192.168 網段。例如: 192.168."212".254。 "212" 會是輸出內容
    ## 有把 DB 有兩張 192.168 的網卡考慮進去 (head -n1 的部分)
    export SUBNET=""
    export SUBNET=$(ifconfig | grep -e "inet 192.168.${DB_subnet}" | sed -e 's|^ *||g' | cut -d ' ' -f 2 | cut -d '.' -f 3 | head -n1)

    ## 192.168 網卡名稱。有把 DB 有兩張 192.168 的網卡考慮進去 (head -n1 的部分)
    export TARGET_NIC=""
    export TARGET_NIC=$(ifconfig | grep -e "inet 192.168.${DB_subnet}" -U1 | cut -d ' ' -f 1 | cut -d ':' -f 1 | grep -oE "(eth.*)||(ens.*)" | head -n1)



    ## 網段、網卡 均不為空值才會繼續執行
    if [ -n "$SUBNET" ] && [ -n "$TARGET_NIC" ]; then

        ## 即時靜態路由 (若已存在該筆,則不會執行)
        FLAG_route=""
        route | grep "${TARGET_IP}" &>/dev/null
        FLAG_route=$(echo $?)

        if [ "$FLAG_route" == "1" ]; then
            echo '即時靜態路由執行!'
            route add -net ${TARGET_IP} netmask 255.255.255.255 gw 192.168.${SUBNET}.${GATEWAY} dev ${TARGET_NIC}
        fi

        ## 永久靜態路由
        conf_path="/etc/sysconfig/network-scripts/route-${TARGET_NIC}"
        grep "${TARGET_IP}" ${conf_path} &>/dev/null 
        FLAG_conf=$(echo $?)

        if [ "$FLAG_conf" != "0" ]; then
            echo '永久靜態路由執行!'
            echo "${TARGET_IP} via 192.168.${SUBNET}.${GATEWAY} dev ${TARGET_NIC}" >> /etc/sysconfig/network-scripts/route-${TARGET_NIC}
        fi

    fi
fi

## ==========

## Task 3: Public Key insert. 信任公鑰注入

mkdir -p /root/.ssh/ && touch /root/.ssh/authorized_keys
FLAG_ssh=""
grep ${ssh_key_name} /root/.ssh/authorized_keys &>/dev/null
FLAG_ssh=$(echo $?)

if [ "$FLAG_ssh" != "0" ]; then

echo 'SSH Public Key 執行!'
cat >> /root/.ssh/authorized_keys << EOF
ssh-rsa XXXX root@DC2_BD_IT_Jumpserver_Demo
EOF

fi

Ansible Play Book

事前準備

在工作目錄準備:

inventory
jumpserver_static_routing_setup.sh
net-tools-2.0-0.25.20131004git.el7.x86_64.rpm
(Play Book 本人) script_play_book.yml

script_play_book.yml

請自行替換 hosts 為 inventory 內目標 group name

Task 1~2 都是安裝/更新 net-tools 的步驟,在 when 的地方有限定 RHEL/CentOS 7 才安裝。 (net-tools 說明)
由於若已安裝 net-tools v2.0-0.25 會報錯,導致 Task 3 沒有被執行。因此加了一行 ignore_errors: yes

Task 3 用 script module 執行位於 local (Ansible control node) 的 shell script。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
---

## 使用 Ansible Script module 執行 .sh file

- name: Run a script for setup Jumpserver.
  hosts: librenms
  tasks:


    - name: copy 'net-tools.rpm' to remote VM
      copy:
        src: net-tools-2.0-0.25.20131004git.el7.x86_64.rpm
        dest: /tmp/
      when: ansible_os_family == "RedHat" and ansible_facts['distribution_major_version'] == "7"
    
    - name: Install 'net-tools'
      shell:
        cmd: rpm -Uvh --nosignature /tmp/net-tools-2.0-0.25.20131004git.el7.x86_64.rpm
        warn: false
      when: ansible_os_family == "RedHat" and ansible_facts['distribution_major_version'] == "7"
      ignore_errors: yes

    - name: Run the 'jumpserver_static_routing_setup.sh'
      script: jumpserver_static_routing_setup.sh
      when: ansible_os_family == "RedHat"

後記

原本打算完全用 Ansible 完成這個任務,不過還是跟 shell script 比較熟一點 XD 只好混搭 Ansible 來使用。

2021/12/23 異動

DC2_BD_IT_Jumpserver_Demo IP 192.168.x.74 => 192.168.x.240

Change_IP_jumpserver_static_routing_setup.sh

  • 新增 OLD_TARGET_IP
  • shell script 會針對舊的 IP (192.168.110.74) 從相關 config file 移除。
  • SSH public key 不會因為更改 IP 失效,因此移除。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
#!/bin/bash

export TARGET_IP='192.168.x.240'
export OLD_TARGET_IP='192.168.x.74'
export GATEWAY='254'
#export ssh_key_name="root@DC2_BD_IT_Jumpserver_Demo"
export DB_subnet=""

## Task 1: edit `/etc/hosts.deny`

sed -i -e "s|^sshd:\ \{0,\}${OLD_TARGET_IP}:allow||g" /etc/hosts.deny
sed -i -e 's|^sshd:\ \{0,\}all||g' -e '/^$/d' /etc/hosts.deny
echo "sshd: ${TARGET_IP}:allow" >> /etc/hosts.deny
echo "sshd: all" >> /etc/hosts.deny


## ==========
## Task 2: add static routing (即時 + 永久)

### 先判斷是否有 192.168.x.0/24 這張網卡,若有才執行。 FLAG != '0' 代表沒有

ifconfig | grep '192.168.' > /dev/null
FLAG_eth=$(echo $?)

if [ "$FLAG_eth" == "0" ]; then

    #echo '找到 192.168. 網卡!'

    ## 192.168 網段。例如: 192.168."212".254。 "212" 會是輸出內容
    ## 有把 DB 有兩張 192.168 的網卡考慮進去 (head -n1 的部分)
    export SUBNET=""
    export SUBNET=$(ifconfig | grep -e "inet 192.168.${DB_subnet}" | sed -e 's|^ *||g' | cut -d ' ' -f 2 | cut -d '.' -f 3 | head -n1)

    ## 192.168 網卡名稱。有把 DB 有兩張 192.168 的網卡考慮進去 (head -n1 的部分)
    export TARGET_NIC=""
    export TARGET_NIC=$(ifconfig | grep -e "inet 192.168.${DB_subnet}" -U1 | cut -d ' ' -f 1 | cut -d ':' -f 1 | grep -oE "(eth.*)||(ens.*)" | head -n1)



    ## 網段、網卡 均不為空值才會繼續執行
    if [ -n "$SUBNET" ] && [ -n "$TARGET_NIC" ]; then

        ## 即時靜態路由 (若已存在該筆,則不會執行)
        FLAG_route=""
        route | grep "${TARGET_IP}" &>/dev/null
        FLAG_route=$(echo $?)

        if [ "$FLAG_route" == "1" ]; then
            echo '即時靜態路由執行!'
            route add -net ${TARGET_IP} netmask 255.255.255.255 gw 192.168.${SUBNET}.${GATEWAY} dev ${TARGET_NIC}
            route del -net ${OLD_TARGET_IP} netmask 255.255.255.255 gw 192.168.${SUBNET}.${GATEWAY} dev ${TARGET_NIC}
        fi

        ## 永久靜態路由
        conf_path="/etc/sysconfig/network-scripts/route-${TARGET_NIC}"
        grep "${TARGET_IP}" ${conf_path} &>/dev/null 
        FLAG_conf=$(echo $?)

        if [ "$FLAG_conf" != "0" ]; then
            echo '永久靜態路由執行!'
            echo "${TARGET_IP} via 192.168.${SUBNET}.${GATEWAY} dev ${TARGET_NIC}" >> /etc/sysconfig/network-scripts/route-${TARGET_NIC}
            sed -i -e "/^${OLD_TARGET_IP} via 192.168.${SUBNET}.${GATEWAY} dev ${TARGET_NIC}$/d" /etc/sysconfig/network-scripts/route-${TARGET_NIC}
        fi

    fi
fi

## ==========

Change_IP_script_play_book.yml

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
---

## 使用 Ansible Script module 執行 .sh file
## For Change IP ONLY !!




- name: Run a script for setup Jumpserver.
  hosts: librenms
  tasks:

    - name: Run the 'Change_IP_jumpserver_static_routing_setup.sh'
      script: Change_IP_jumpserver_static_routing_setup.sh
      when: ansible_os_family == "RedHat"

1
sudo ansible-playbook -i inventory_Production_85_DB Change_IP_script_play_book.yml