Spring cloud 安全部署與性能優化

http://www.netkiller.cn/journal/spring.html

Mr. Neo Chen (陳景峯), netkiller, BG7NYT


中國
廣東省
深圳市
望海路半島城邦三期
518067
+86 13113668890

MMDVM Hotspot:

YSF80337 - CN China 1 - W24166/TG46001
BM_China_46001 - DMR Radio ID 4600441

版權聲明

轉載請與作者聯繫,轉載時請務必標明文章原始出處和作者信息及本聲明。

http://www.netkiller.cn
http://netkiller.github.io
http://netkiller.sourceforge.net
微信訂閲號 netkiller-ebook (微信掃瞄二維碼)
QQ:13721218 請註明“讀者”
QQ群:128659835 請註明“讀者”

2019-06-12

摘要

目錄

1. 環境安裝

1.1. 操作系統初始化

操作系統按完成後,使用下面腳本做一次初始化

			
curl -s https://raw.githubusercontent.com/oscm/shell/master/os/personalise.sh | bash
			
			

1.2. 安裝 Nginx

			
curl -s https://raw.githubusercontent.com/oscm/shell/master/web/nginx/stable/nginx.sh | bash
			
			

1.3. 安裝資料庫

1.3.1. MySQL

5.7

						
curl -s https://raw.githubusercontent.com/oscm/shell/master/database/mysql/5.7/mysql.server.sh | bash
			
				

安裝完成會看到下面輸出

			
2019-06-04T08:54:24.419092Z 1 [Note] A temporary password is generated for root@localhost: 8X!pE(o+urv;			
			
				

8.0

			
curl -s https://raw.githubusercontent.com/oscm/shell/master/database/mysql/8.0/server.sh | bash

curl -s https://raw.githubusercontent.com/oscm/shell/master/database/mysql/8.0/client.sh | bash			
			
				

1.3.2. PostgreSQL

PostgreSQL 9.4

			
curl -s https://raw.githubusercontent.com/oscm/shell/master/database/postgresql/postgresql94-centos7.sh | bash			
			
				

1.3.3. Redis

			
curl -s https://raw.githubusercontent.com/oscm/shell/master/database/redis/source/redis-5.0.5.sh | bash			
			
				

1.4. RabbitMQ

		
curl -s https://raw.githubusercontent.com/oscm/shell/master/queue/rabbitmq/rabbitmq-server-3.7.15.sh | bash		
		
			

1.5. OpenJDK

			
curl -s https://raw.githubusercontent.com/oscm/shell/master/lang/java/openjdk/java-1.8.0-openjdk.sh | bash
			
			

1.5.1. Maven

				
curl -s https://raw.githubusercontent.com/oscm/shell/master/lang/java/maven/apache-maven-3.6.1.sh | bash				
				
				

1.5.2. Gradle

				
curl -s https://raw.githubusercontent.com/oscm/shell/master/lang/java/gradle/gradle-5.4.1.sh | bash				
				
				

1.6. Node

前端開發會用到Node.js

			
curl -s https://raw.githubusercontent.com/oscm/shell/master/lang/node.js/binrary/node-v12.4.0.sh | bash			
			
			

1.7. Gitlab

			
curl -s https://raw.githubusercontent.com/oscm/shell/master/project/gitlab/gitlab.centos7.sh | bash			
			
			

Gitlab 配置請參考 《Netkiller Project 手札》

1.7.1. Gitlab runner

				
curl -s https://raw.githubusercontent.com/oscm/shell/master/project/gitlab/gitlab-runner.sh | bash				
				
				

java 編譯環境

去 Oracle 網站下載 java 8 jdk-8u212-linux-x64.rpm

				
yum localinstall jdk-8u212-linux-x64.rpm

curl -s https://raw.githubusercontent.com/oscm/shell/master/project/git.sh | bash
curl -s https://raw.githubusercontent.com/oscm/shell/master/lang/java/maven/apache-maven-3.6.1.sh | bash
				
				

2. 操作系統配置與優化

操作系統要求 CentOS 7 Minimal ISO

2.1. 域名

2.1.1. 配置 DNS

修改 /etc/resolv.conf 配置 DNS 確保域名解析正確

				
echo -ne "
search example.com
nameserver 208.67.222.222
nameserver 202.67.220.220
nameserver 8.8.8.8
nameserver 4.4.4.4
" > /etc/resolv.conf
				
				

2.1.2. /etc/hosts

DNS 解析有延遲,很多不可控因素,將域名寫入 /etc/hosts 更為保險

				
192.168.1.1		www.netkiller.cn
192.168.1.2		api.netkiller.cn
192.168.1.3		db.netkiller.cn
				
				

使用域名連結數據的例子

				
spring.datasource.url=jdbc:mysql://db.netkiller.cn:3306/neo				
				
				

DNS 不可靠的原因分析,因為DNS使用UDP傳輸,網絡擁堵,電磁干擾等等都能導致丟包。

2.2. 歷史記錄操作留痕

定義 .history 檔案格式,記錄每一步操作,便于查看什麼時間執行了什麼命令

通過~/.bash_history檔案記錄系統管理員的操作記錄,定製.bash_history格式

			
HISTSIZE=1000
HISTFILESIZE=2000
HISTTIMEFORMAT="%Y-%m-%d-%H:%M:%S "
export HISTTIMEFORMAT
			
			

看看實際效果

			
$ history | head
1 2012-02-27-09:10:45 do-release-upgrade
2 2012-02-27-09:10:45 vim /etc/network/interfaces
3 2012-02-27-09:10:45 vi /etc/network/interfaces
4 2012-02-27-09:10:45 ping www.163.com
			
			

2.3. 臨時檔案安全

臨時檔案不應該有執行權限

/tmp

			
/dev/sda3 /tmp ext4 nosuid,noexec,nodev,rw 0 
			
			

同時使用符號連接將/var/tmp 指向 /tmp

/dev/shm

			
none /dev/shm tmpfs defaults,nosuid,noexec,rw 0 0
			
			

2.4. 執行權限

以資料庫為例,從安全形度考慮我們需要如下更改

			
chown mysql:mysql /usr/bin/mysql*
chmod 700 /usr/bin/mysql*
			
			

mysql用戶是DBA專用用戶, 其他用戶將不能執行mysql等命令。

2.5. Linux 系統資源調配

2.5.1. /etc/security/limits.conf

很多資料上是這麼寫的

				
* soft nofile 65535
* hard nofile 65535
				
				

這樣做是偷懶,會帶來很多問題,如果你的伺服器被攻擊,由於你的設置,系統將耗光你的資源,直到沒有任何響應為止,你可能鍵盤輸入都成問題,你不得不重啟伺服器,但你會發現重啟只能維持短暫幾分鐘,又會陷入無響應狀態。

			
nobody soft nofile 4096
nobody hard nofile 8192
			
				

為什麼會設置為nobody用戶呢?因為root用戶啟動系統後web 伺服器會使用nobody用戶創建子進程,socket連接實際上是nobody用戶在處理。root 僅僅是守護父進程。

				
mysql soft nofile 2048
mysql hard nofile 2048
				
				

針對 mysql 做限制

提示

關於 nofile 即打開檔案數,這個跟socket有非常緊密的關係,在linux系統中任何設備都被看做是一個檔案(字元設備),你連接一個滑鼠,鍵盤,攝像頭,硬碟等等都被看作打開一個設備檔案,所以預設1024是遠遠不夠的。

				
cat >> /etc/security/limits.conf <<EOF
root 	soft nofile 65536
root 	hard nofile 65536
www		soft nofile 65536
www 	hard nofile 65536
mysql	soft nofile 65536
mysql 	hard nofile 65536
EOF
				
				

2.5.2. /etc/sysctl.conf

內核參數調整

			
cat >> /etc/sysctl.conf <<EOF

net.ipv4.ip_local_port_range = 1024 65500
net.ipv4.tcp_syncookies = 1
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_tw_recycle = 1
net.ipv4.tcp_fin_timeout = 60
net.ipv4.tcp_keepalive_time = 1200
net.ipv4.tcp_max_syn_backlog = 8192
net.ipv4.tcp_max_tw_buckets = 4096
EOF
			
				

2.6. 關閉 SELINUX

			
curl -s https://raw.githubusercontent.com/oscm/shell/master/os/selinux.sh | bash				
			
			

2.7. 關閉寫磁碟I/O功能

對於某些檔案沒必要記錄檔案的訪問時間,由其是在高並發的IO密集操作的環境下,通過兩個參數可以實現noatime,nodiratime減少不必要的系統IO資源。

編輯/etc/fstab 添加 noatime,nodiratime 參數

			
/dev/sdb1 /www ext4 noatime,nodiratime 0 0
			
			

我一般分區規劃是,/系統根分區,swap交換分區,/www數據分區,同時 禁止寫入atime時間,因為/www頻繁請求會影響IO

臨時mount

			
mount -o remount,noatime,nodiratime /dev/sda3 /mnt/your
			
			

LABEL 方式

			
LABEL=/www /www ext3 defaults,noatime,nodiratime 1 1
			
			

UUID 方式

			
UUID=eeff3e86-7964-4a48-ac02-51ea167ea6b2 /www ext4 defaults,noatime,nodiratime 1 2
			
			

至此,Linux 的OS部分安裝配置與優化完成。

2.8. 放棄 LVM 使用 Btrfs

btrfa 的快照功能非常適合快速備份

子卷功能比物理分區更靈活

			
[root@netkiller ~]# cat /etc/fstab 

#
# /etc/fstab
# Created by anaconda on Fri Nov 21 18:16:53 2014
#
# Accessible filesystems, by reference, are maintained under '/dev/disk'
# See man pages fstab(5), findfs(8), mount(8) and/or blkid(8) for more info
#
UUID=6634633e-001d-43ba-8fab-202f1df93339 / ext4 defaults,barrier=0 1 1
UUID=786f570d-fe5c-4d5f-832a-c1b0963dd4e6 /srv btrfs defaults 1 1
UUID=786f570d-fe5c-4d5f-832a-c1b0963dd4e6 /var/lib/mongo  btrfs   noatime,nodiratime,subvol=@mongo 0 2
UUID=786f570d-fe5c-4d5f-832a-c1b0963dd4e6 /var/lib/mysql  btrfs   noatime,nodiratime,subvol=@mysql 0 2
UUID=786f570d-fe5c-4d5f-832a-c1b0963dd4e6 /www  btrfs   noatime,nodiratime,subvol=www 0 2			
			
			

2.9. Openssh 安全配置

這節主要講與SSH有關的安全配置

2.9.1. 禁止root用戶登錄

只允許普通用戶登陸,然後通過su命令切換到root用過。後面還會將怎樣限制su命令

			
PermitRootLogin no
			
				

2.9.2. 限制SSH驗證重試次數

超過3次socket連接會斷開,效果不明顯,有一點點用。

			
MaxAuthTries 3
			
				

2.9.3. 禁止證書登陸

證書登陸非常安全,但是很有可能正常用戶在你不知道情況下,給你安裝了一個證書,他隨時都可能進入你的系統

任何一個有權限的用戶都能很方便的植入一個證書到 .ssh/authorized_keys 檔案中

				
PubkeyAuthentication no
AuthorizedKeysFile /dev/null
				
				

2.9.4. 使用證書替代密碼認證

是不是自相矛盾? 這個跟上面講的正好相反,這裡只允許使用key檔案登陸。

			
PasswordAuthentication no
			
				

這種方式比起密碼要安全的多,唯一要注意的地方就是證書被拷貝 ,建議你給證書加上 passphrase。

證書的 passphrase 是可以通過openssl工具將其剝離的,SSH證書我沒有試過,但是原理都差不多。

2.9.5. 圖形窗口客戶端記憶密碼的問題

當你使用XShell, Xftp, WinSCP, SecureCRT, SecureFX ......等等軟件登錄時,該軟件都提供記住密碼的功能,使你下次再登陸的時候無須輸入密碼就可以進入系統。這樣做的確非常方便,

但是你是否想過你的電腦一旦丟失或者被其他人進入,那有多麼危險。我之前每天背着筆記型電腦上班,上面安裝着XShell並且密碼全部記憶在裡面。這使我意識到一點電腦丟失,有多麼可怕。

禁止SSH客戶端記住密碼,你不要要求別人那麼做。你也無法控制,最終我找到了一種解決方案。

			
ChallengeResponseAuthentication yes
			
				

每次登陸都回提示你輸入密碼。密碼保存也無效。

2.9.6. 關閉 GSSAPI

				
GSSAPIAuthentication no
#GSSAPIAuthentication yes
#GSSAPICleanupCredentials yes
#GSSAPICleanupCredentials yes
#GSSAPIStrictAcceptorCheck yes
#GSSAPIKeyExchange no
				
				

2.9.7. 禁止SSH連接埠映射

禁止使用SSH映射Socks5翻牆等等

			
AllowTcpForwarding no
			
				

2.9.8. IP地址限制

只允許通過192.168.2.1,192.168.2.2 訪問本機

					# vim /etc/hosts.allow
					sshd:192.168.2.1,192.168.2.2

				

禁止所有人訪問本機

					# vim /etc/hosts.deny
					sshd:ALL
				

上面使白名單策略,你也可以採用黑名單策略。

2.9.9. 禁止SSH密碼窮舉

駭客常常使用駭客字典窮舉你的SSH密碼,使用下面腳本可以封殺頻繁連結的IP地址

			
#!/bin/bash
########################################
# Homepage: http://netkiller.github.io
# Author: neo <netkiller@msn.com>
########################################
PIPE=/var/tmp/pipe
pidfile=/var/tmp/$0.pid
BLACKLIST=/var/tmp/black.lst
WHITELIST=/var/tmp/white.lst

LOGFILE=/var/log/secure
DAY=5
########################################

if [ -z "$( egrep "CentOS|7." /etc/centos-release)" ]; then
	echo 'Only for CentOS 7.x'
	exit
fi

if [ -f $BLACKLIST ]; then
	find $BLACKLIST -type f -mtime +${DAY} -delete
fi

if [ ! -f ${BLACKLIST} ]; then
    touch ${BLACKLIST}
fi

if [ ! -f ${WHITELIST} ]; then
    touch ${WHITELIST}
fi

for ipaddr in $(grep rhost ${LOGFILE} | grep -oE "\b([0-9]{1,3}\.){3}[0-9]{1,3}\b" | sort | uniq -c | sort -r -n | head -n 10| awk '{print $2}')
do

    if [ $(grep -c $ipaddr ${WHITELIST}) -gt 0 ]; then
		continue
    fi

    if [ $(grep -c $ipaddr ${BLACKLIST}) -eq 0 ] ; then
		echo $ipaddr >> ${BLACKLIST}
        iptables -I INPUT -p tcp --dport 22 -s $ipaddr -j DROP
        #iptables -I INPUT -s $ipaddr -j DROP
    fi
done			
			
				

2.9.10. 解決 SSH 超時退出的問題

很多雲主機 SSH 登錄後,會自動斷開。執行下面腳本可以解決。

				
curl -s https://raw.githubusercontent.com/oscm/shell/master/os/ssh/config.sh | bash				
				
				

2.10. PAM 插件認證加固配置

配置檔案

		
ls  /etc/pam.d/
chfn         crond                login    passwd            remote    runuser-l          smtp          ssh-keycat  sudo-i       system-auth-ac
chsh         fingerprint-auth     newrole  password-auth     run_init  smartcard-auth     smtp.postfix  su          su-l
config-util  fingerprint-auth-ac  other    password-auth-ac  runuser   smartcard-auth-ac  sshd          sudo        system-auth
		
			

認證插件

		
ls /lib64/security/
		
			

2.10.1. pam_tally2.so

此模組的功能是,登陸錯誤輸入密碼3次,5分鐘後自動解禁,在未解禁期間輸入正確密碼也無法登陸。

在配置檔案 /etc/pam.d/sshd 頂端加入

			
auth required pam_tally2.so deny=3 onerr=fail unlock_time=300
			
				

查看失敗次數

			
# pam_tally2
Login           Failures Latest failure     From
root               14    07/12/13 15:44:37  192.168.6.2
neo                 8    07/12/13 15:45:36  192.168.6.2
			
				

重置計數器

			
# pam_tally2 -r -u root
Login           Failures Latest failure     From
root               14    07/12/13 15:44:37  192.168.6.2

# pam_tally2 -r -u neo
Login           Failures Latest failure     From
neo                 8    07/12/13 15:45:36  192.168.6.2
			
				

pam_tally2 計數器日誌保存在 /var/log/tallylog 注意,這是二進制格式的檔案

例 1. /etc/pam.d/sshd - pam_tally2.so
				
# cat  /etc/pam.d/sshd
#%PAM-1.0
auth required pam_tally2.so deny=3 onerr=fail unlock_time=300

auth	   required	pam_sepermit.so
auth       include      password-auth
account    required     pam_nologin.so
account    include      password-auth
password   include      password-auth
# pam_selinux.so close should be the first session rule
session    required     pam_selinux.so close
session    required     pam_loginuid.so
# pam_selinux.so open should only be followed by sessions to be executed in the user context
session    required     pam_selinux.so open env_params
session    optional     pam_keyinit.so force revoke
session    include      password-auth
				
					

以上配置root用戶不受限制, 如果需要限制root用戶,參考下面

			
auth required pam_tally2.so deny=3 unlock_time=5 even_deny_root root_unlock_time=1800
			
				

2.10.2. pam_listfile.so

用戶登陸限制

將下面一行添加到 /etc/pam.d/sshd 中,這裡採用白名單方式,你也可以採用黑名單方式

			
auth       required     pam_listfile.so item=user sense=allow file=/etc/ssh/whitelist onerr=fail
			
				

將允許登陸的用戶添加到 /etc/ssh/whitelist,除此之外的用戶將不能通過ssh登陸到你的系統

			
# cat /etc/ssh/whitelist
neo
www
			
				
例 2. /etc/pam.d/sshd - pam_listfile.so
				
# cat /etc/pam.d/sshd
#%PAM-1.0
auth       required     pam_listfile.so item=user sense=allow file=/etc/ssh/whitelist onerr=fail
auth       required     pam_tally2.so deny=3 onerr=fail unlock_time=300

auth	   required	pam_sepermit.so
auth       include      password-auth
account    required     pam_nologin.so
account    include      password-auth
password   include      password-auth
# pam_selinux.so close should be the first session rule
session    required     pam_selinux.so close
session    required     pam_loginuid.so
# pam_selinux.so open should only be followed by sessions to be executed in the user context
session    required     pam_selinux.so open env_params
session    optional     pam_keyinit.so force revoke
session    include      password-auth
				
					

sense=allow 白名單方式, sense=deny 黑名單方式

			
auth       required     pam_listfile.so item=user sense=deny file=/etc/ssh/blacklist onerr=fail
			
				

更多細節請查看手冊 $ man pam_listfile

2.10.3. pam_access.so

編輯 /etc/pam.d/sshd 檔案,加入下面一行

			
account required pam_access.so
			
				

保存後重啟sshd進程

編輯 /etc/security/access.conf 檔案

			
cat >>  /etc/security/access.conf << EOF

- : root : ALL EXCEPT 192.168.6.1
EOF
			
				

只能通過 192.168.6.1 登陸, 添加多個IP地址

			
- : root : ALL EXCEPT 192.168.6.1 192.168.6.2
			
				

測試是否生效

2.10.4. pam_wheel.so

限制普通用戶通過su命令提升權限至root. 只有屬於wheel組的用戶允許通過su切換到root用戶

編輯 /etc/pam.d/su 檔案,去掉下面的註釋

			
auth		required	pam_wheel.so use_uid
			
				

修改用戶組別,添加到wheel組

			
# usermod -G wheel www

# id www
uid=501(www) gid=501(www) groups=501(www),10(wheel)
			
				

沒有加入到wheel組的用戶使用su時會提示密碼不正確。

			
$ su - root
Password:
su: incorrect password
			
				

2.11. NTP 服務

每個伺服器必須安裝 NTP,以保證伺服器的時間準確。

			
curl -s https://raw.githubusercontent.com/oscm/shell/master/os/ntpd/ntpdate.sh | bash			
			
			

3. Java 虛擬機

3.1. 安裝腳本

OpenJDK 1.8

			
curl -s https://raw.githubusercontent.com/oscm/shell/master/lang/java/openjdk/java-1.8.0-openjdk.sh | bash			
			
			

OpenJDK 11

			
curl -s https://raw.githubusercontent.com/oscm/shell/master/lang/java/openjdk/java-11-openjdk.sh | bash			
			
			

Oracle Server JRE 1.8

http://java.oracle.com 下載 server-jre-8u212-linux-x64.tar.gz 然後運行下面安裝腳本

			
#!/bin/bash
tar zxf server-jre-8u212-linux-x64.tar.gz*
mv jdk1.8.0_212 /srv/
ln -s /srv/jdk1.8.0_212 /srv/java

cat >> /etc/profile.d/java.sh <<'EOF'
export JAVA_HOME=/srv/java
export JAVA_OPTS="-server -Xms512m -Xmx8192m"
export CLASSPATH=$JAVA_HOME/lib:$JAVA_HOME/jre/lib:.
export PATH=$PATH:$JAVA_HOME/bin:$JAVA_HOME/jre/bin:
EOF

sed -i '117s/securerandom.source/#securerandom.source/' /srv/java/jre/lib/security/java.security
sed -i '117isecurerandom.source=file:/dev/./urandom' /srv/java/jre/lib/security/java.security

cat >> /etc/man.config <<EOF
MANPATH  /srv/java/man
EOF

alternatives --install /usr/bin/java java /srv/jdk1.8.0_212/bin/java 100 \
	--family java-1.8.0-server-jre \
	--slave /usr/bin/javac javac /srv/jdk1.8.0_212/bin/javac			
			
			

如果伺服器上安裝了多個 JAVA 版本,切換 JAVA 版本使用下面命令

			
alternatives --config java			
			
			

3.2. 使用 Server JRE 替代JDK。

伺服器上不要安裝JDK,請使用 Server JRE. 伺服器上根本不需要編譯器,代碼應該在Release伺服器上完成編譯打包工作。

理由:一旦伺服器被控制,可以防止在其伺服器上編譯其他惡意代碼並植入到你的程序中。

3.3. JAVA_OPTS

加入 -server 選項使 JRE 工作在伺服器模式。

			
export JAVA_OPTS="-server -Xms512m -Xmx4096m  -XX:PermSize=64M -XX:MaxPermSize=512m"
			
			

-Xms 指定初始化時化的棧內存

-Xmx 指定最大棧內存

提示

Java 8 以後 -XX:PermSize 與 -XX:MaxPermSize 兩個配置項被廢棄

			
export JAVA_OPTS="-server -Xms512m -Xmx4096m"			
			
			

3.4. java.security 優化

打開$JAVA_HOME/jre/lib/security/java.security檔案,找到下面的內容:

			
securerandom.source=file:/dev/urandom
替換成
securerandom.source=file:/dev/./urandom
			
			

3.5. jconsole 配置

			
java -jar -Djava.rmi.server.hostname=192.168.0.1 -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=911 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false netkiller-1.0-SNAPSHOT.jar			
			
			

如果是雲主機,配置 java.rmi.server.hostname=192.168.0.1 為內網IP地址,這樣只能從內網監控 JVM

啟動 jconsole

			
jconsole localhost:911
			
			

4. 配置 Spring boot

下面以 Springboot 2.0 為例。

4.1. 指定配置檔案

配置檔案位置,預設application.properties是放在jar包中的,這樣對於運維並不友好。通過spring.config.location可以制定外部配置檔案,這樣更便于運維。

--spring.config.location 指定配置檔案

			
java -jar demo.jar --spring.config.location=/opt/config/application.properties
			
			

這樣運維人員便于配置資料庫連結,切換伺服器等操作

4.2. 日誌檔案

預設日誌檔案

			
		logging.path=/tmp 		# 日誌目錄預設為 /tmp
		logging.file=spring.log # 日誌檔案名稱,預設為spring.log
			
			

需要需要根據每個伺服器的情況來指定日誌存儲的目錄

			
java -jar spring-boot-app.jar --logging.file=/tmp/spring-2018-12-10.log			
			
			

4.3. 如何啟動 Springboot 程序

4.3.1. systemd

/etc/systemd/system/spring.service

							
####################################################
# Homepage: http://netkiller.github.io
# Author: netkiller<netkiller@msn.com>
# Script: https://github.com/oscm/shell
# Date: 2015-11-03
####################################################
[Unit]
Description=Spring Boot Application
After=network.target

[Service]
User=www
Group=www
Type=oneshot
WorkingDirectory=/www/netkiller.cn/api.netkiller.cn
ExecStart=/usr/bin/java -jar -Xms256m -Xmx4G  -Dlog.level.console=warn your_jar_file.jar --spring.config.location=appliction-production.properties --spring.profiles.active=profile
#ExecStop=pkill -9 -f  
RemainAfterExit=yes

[Install]
WantedBy=multi-user.target
							
				
				

4.3.2. 傳統 init.d 腳本

				
#!/bin/bash
##############################################
# Author: netkiller<netkiller@msn.com>
# Homepage: http://www.netkiller.cn
# Date: 2017-02-08
# $Author$
# $Id: c3354a7fae3e5f05cd230d3be6f811627f319560 $
##############################################
# chkconfig: 345 100 02
# description: Spring boot application
# processname: springbootd
# File : springbootd
##############################################
BASEDIR="/www/netkiller.cn/api.netkiller.cn"
JAVA_HOME=/srv/java
JAVA_OPTS="-server -Xms2048m -Xmx8192m -Dlog.level.console=warn -Djava.security.egd=file:/dev/./urandom"
PACKAGE="api.netkiller.cn-0.0.2-release.jar"
CONFIG="--spring.config.location=$BASEDIR/application.properties"
USER=www
##############################################
NAME=springbootd
PROG="$JAVA_HOME/bin/java $JAVA_OPTS -jar $BASEDIR/$PACKAGE $CONFIG"
LOGFILE=/var/tmp/$NAME.log
PIDFILE=/var/tmp/$NAME.pid
ACCESS_LOG=/var/tmp/$NAME.access.log
##############################################

function log(){
	echo "$(date -d "today" +"%Y-%m-%d %H:%M:%S") $1	$2" >> $LOGFILE
}

function start(){
	if [ -f "$PIDFILE" ]; then
		echo $PIDFILE
		exit 2
	fi

	su - $USER -c "$PROG & echo \$! > $PIDFILE"
	log info start
}
function stop(){
	[ -f $PIDFILE ] && kill `cat $PIDFILE` && rm -rf $PIDFILE
	log info stop
}
function status(){
	ps aux | grep $PACKAGE | grep -v grep | grep -v status
	log info status
}
function reset(){
	pkill -f $PACKAGE
  	[ -f $PIDFILE ] && rm -rf $PIDFILE
	log info reset
}

case "$1" in
	start)
		start
		;;
	stop)
		stop
		;;
	status)
		status
		;;
	restart)
		stop
		start
		;;
	log)
		tail -f $LOGFILE
		;;
	reset)
		reset
		;;
	*)
		echo $"Usage: $0 {start|stop|status|restart|log|reset}"
esac
exit $?				
				
				

4.4. 監聽連接埠

不要使用root用戶啟動springboot,Java程序與C程序不同。nginx,httpd 使用root用戶啟動守護80連接埠,子進程/綫程會通過setuid(),setgid()兩個函數切換到普通用戶。即父進程所有者是root用戶,子進程與多綫程所有者是一個非root用戶,這個用戶沒有shell權限,無法通過ssh與控制台登陸系統。

Java 的JVM 是與系統無關的,是建立在OS之上的,你使用什麼用戶啟動Springboot,那麼Springboot 就會繼承該所有者的權限。

一旦 springboot 被攻擊,黑客將會獲得 root 權限,控制整個系統。

			
server.port=8080 # 監聽連接埠			
			
			

伺服器上有多個網卡的情況,需要指定一個網絡適配器監聽連接埠。

			
server.address=192.168.0.1 # 綁定的地址			
			
			

4.4.1. 使用 80 連接埠

Linux系統小於1024的連接埠只有root可以使用,這也是為什麼Springboot預設連接埠是8080。如果你想使用80連接埠只能使用root啟動Springboot,不建議這樣做,這會帶來了很多安全問題。建議使用iptables連接埠轉發。

解決方案是創建一個普通用戶,如:

				
groupadd -g 80 daemon
adduser -o --home /daemon --shell /sbin/nologin --uid 80 --gid 80 -c "Web Server" daemon
				
				

注意 /sbin/nologin , 意味着該用戶不能登錄,同時我也沒有給它指定密碼,這個用戶只能用於啟動tomcat,沒有Shell權限就以為只被注入後無法運行linux命令。

				
chown daemon:daemon -R /srv/*
su - daemon -c "/srv/apache-tomcat/bin/startup.sh"
				
				

接下來解決80連接埠問題, 思路就是80去調用8080,或者映射連接埠。

下面是影射方案,80 跳轉 8080

				
iptables -t nat -A PREROUTING -p tcp --dport 80 -j REDIRECT --to-port 8080

取消跳轉
iptables -t nat -D PREROUTING -p tcp --dport 80 -j REDIRECT --to-port 8080

查看規則
iptables -t nat -L
				
				

另一個就是從80請求去調用8080的方案

這個方案可以在 Tomcat 前段增加反向代理,例如:Nginx,Apache,Squid,Varnish或者F5, Array這類設備等等

4.5. 連接數配置

系統預設 2048,這裡 max-threads 是 4096 表示伺服器最大連接數是 4096

							
server.tomcat.max-threads=4096 # 最大綫程數				
			
			

空閒綫程數,靜止沒有訪問時,伺服器保留的綫程數。

			
server:
  tomcat:
    min-spare-threads: 20
    max-threads: 100
  connection-timeout: 20000		
			
			

配置 min-spare-threads 可以防止伺服器靜止的狀態下,突然發起大量的連結請求導致系統沒有響應。

			
server.tomcat.connection-timeout=20000
			
			

連結超時時間建議設置 60~20秒之間。

4.6. 編碼配置

有些版本的 Spring boot 預設並非 UTF-8 建議強制配置編碼

			
spring.messages.encoding=UTF-8
server.tomcat.uri-encoding=UTF-8
spring.http.encoding.charset=UTF-8
spring.http.encoding.enabled=true
spring.http.encoding.force=true			
			
			

4.7. Session 配置

修改 Cookie 變數 JSESSIONID, 這個cookie 是用於維持Session關係。建議你改為PHPSESSID,防止黑客騷貓。

			
server.session.cookie.name=PHPSESSID
server.session.cookie.domain=.example.com
server.session.cookie.http-only=true
server.session.cookie.path=/				
			
			

4.8. 壓縮傳輸配置

注意不僅僅壓縮 HTML 頁面,配置 application/json 也可以壓縮傳輸 JSON 數據

			
server.compression.enabled=true
server.compression.mime-types=application/json,application/xml,text/html,text/xml,text/plain,text/css,application/javascript
server.compression.min-response-size=1024	
			
			

4.9. 上傳限制

上傳檔案尺寸限制,注意如果使用了 Nginx 代理 Springboot 同時也需要配置 Nginx 上傳限制。

						
spring.servlet.multipart.max-file-size=2MB
spring.servlet.multipart.max-request-size=10MB
spring.http.multipart.enabled=false			
			
			

4.10. Redis 配置

					
# REDIS (RedisProperties)
# Redis資料庫索引(預設為0)
spring.redis.database=0
# Redis伺服器地址
spring.redis.host=192.168.30.103
# Redis伺服器連接連接埠
spring.redis.port=6379
# Redis伺服器連接密碼(預設為空)
spring.redis.password=
# 連接超時時間(毫秒)
spring.redis.timeout=0
# 連接池最大連接數(使用負值表示沒有限制)
spring.redis.jedis.pool.max-active=8
# 連接池最大阻塞等待時間(使用負值表示沒有限制)
spring.redis.jedis.pool.max-wait=-1
# 連接池中的最大空閒連接
spring.redis.jedis.pool.max-idle=8
# 連接池中的最小空閒連接
spring.redis.jedis.pool.min-idle=0			
			
			

4.11. 資料庫配置

4.11.1. PostgreSQL

				
spring.datasource.driver-class-name=org.postgresql.Driver
spring.datasource.platform=postgres
spring.datasource.url=jdbc:postgresql://192.168.1.240:5432/saas_data?currentSchema=public&useUnicode=true&characterEncoding=UTF-8
spring.datasource.username=ctsuser
spring.datasource.password=saas				
				
				

4.11.2. MySQL

				
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://主機地址:連接埠號/資料庫
spring.datasource.username=用戶名
spring.datasource.password=密碼
spring.jpa.database=MYSQL # 啟用JPA支持			
				
				

4.11.3. Oracle

				
spring.datasource.driver-class-name=oracle.jdbc.OracleDriver
spring.datasource.url=jdbc:oracle:thin:@//odb.netkiller.cn:1521/orcl
spring.datasource.username=orcl
spring.datasource.password=passw0rd
spring.datasource.connection-test-query="SELECT 1 FROM DUAL"
spring.jpa.database-platform=org.hibernate.dialect.Oracle10gDialect
				
				

4.11.4. 連結池

				
spring.jpa.show-sql=true
#spring.jpa.hibernate.ddl-auto=none
#spring.jpa.hibernate.ddl-auto=create-drop
spring.datasource.max-active=50
spring.datasource.initial-size=5
spring.datasource.max-idle=10
spring.datasource.min-idle=5
spring.datasource.test-while-idle=true
spring.datasource.test-on-borrow=false
spring.datasource.validation-query=SELECT 1 FROM DUAL
spring.datasource.time-between-eviction-runs-millis=5000
spring.datasource.min-evictable-idle-time-millis=60000				
				
				

5. Spring Cloud 配置

5.1. Config Server

下面是給配置伺服器增加用戶認證,訪問配置伺服器用戶名config, 密碼 s3cr3t

			
server.port=8888
spring.cloud.config.server.git.uri=/opt/config
security.user.name=config
security.user.password=s3cr3t			
			
			

5.2. Eureka 安全配置

為 eureka 增加HTTP認證,周澤任何人都能註冊進來。

			
security.user.name=eureka
security.user.password=s3cr3t			
			
			

5.3. Openfeign 優化

開發壓縮傳輸

			
feign.compression.response.enabled=true
feign.compression.request.enabled=true
feign.compression.request.mime-types=text/xml,application/xml,application/json
feign.compression.request.min-request-size=2048		
			
			

6. 配置 Nginx

6.1. 處理器數量和連接數配置

worker_processes = CPU 數量

			
user www;
worker_processes 1;

error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;				
			
			

連接數配置

			
events {
    worker_connections  4096;
}				
			
			

6.2. 隱藏nginx版本號

隱藏nginx版本號可以有效的增加黑客攻擊難度

			
vim /etc/nginx/nginx.conf

http {
...
server_tokens off;
}
			
			

6.3. HTTP2

			
	server {
		listen 443 ssl http2;
	
		ssl_certificate server.crt;
		ssl_certificate_key server.key;
	}
			
			

6.4. 用戶訪問 HTTP時強制跳轉到 HTTPS

497 - normal request was sent to HTTPS

			
				#讓http請求重定向到https請求

	server {
		listen 80;
		error_page 497 https://$host$uri?$args;
		rewrite ^(.*)$ https://$host$1 permanent;
	}
			
			
			
	server {
		listen 80
		listen 443 ssl http2;
	
		ssl_certificate server.crt;
		ssl_certificate_key server.key;
	
		error_page 497 https://$host$uri?$args;
	
		if ($scheme = http) {
			return 301 https://$server_name$request_uri;
		}
	}
			
			

6.5. 開啟 gzip 壓縮傳輸

gzip_types 壓縮類型

				
	gzip_types text/plain text/css application/javascript text/javascript application/x-javascript text/xml application/xml application/xml+rss application/json;			
			
			

text/html 是 gzip_types 預設值,所以不要將text/html加入到gzip_types

6.6. 緩存過期時間

			
	location ~ .*\.(htm|html|gif|jpg|jpeg|png|bmp|swf|ioc|rar|zip|txt|flv|mid|doc|ppt|pdf|xls|mp3|wma)$
	{
		expires 30d;
	}
	location ~ .*\.(js|css)$
	{
		expires 1h;
	}
			
			

6.7. 上傳檔案尺寸限制

client_max_body_size 上傳檔案尺寸限制

						
client_max_body_size 2M;				
			
			

6.8. 實現相互備份

兩台伺服器 server1,server2 相互備份

			
	upstream backend {
		server server1.example.com:8080;
		server server2.example.com:8080 backup;
	}
	server {
		location / {
			proxy_pass http://backend;
		}
	}
					
			
			
			
	upstream frontend {
		server server1.example.com:8080 backup;
		server server2.example.com:8080;
	}
	server {
		location / {
			proxy_pass http://frontend;
		}
	}
			
			

6.9. Nginx 狀態信息

允許監控軟件從 localhost 收集 nginx 狀態信息

						
server {
    listen       80;
    server_name  localhost;

    location /status {
        stub_status on;
        access_log off;
        allow 127.0.0.1;
        deny all;
    }
}			
			
			

6.10. 禁止公網訪問 Spring Boot Actuator

Spring Boot Actuator 是會泄漏一些環境變數等敏感信息,甚至可以通過 localhost:8080/actuator/shutdown 遠程關閉伺服器。所以需要禁止公網訪問,只允許內部伺服器採集信息。

			
	location ~ ^/actuator
	{
		allow 172.16.0.10;
		deny all;
	}
			
			

172.16.0.10 是監控伺服器

7. 部署應用程序

7.1. 伺服器軟件安裝目錄

			
/srv		伺服器軟件安裝目錄,例如 jdk
/opt		系統保留,可以用於安裝軟件,存儲備份檔案
			
			

7.2. 應用程序部署目錄

應用程序放在/www目錄下,一般是這樣的結構。

			
/www/example.com/www.example.com
			
			

每次升級將壓錯包解壓到 /www/example.com/目錄下,www.example.com 是符號連接,連接到剛剛解壓的目錄。

這個可以實現通過符號連接在多個版本之間快速切換。

7.3. 數據盤

數據盤是指雲主機額外增加一個磁碟,用於數據存儲。

如果是阿里雲可以使用下面腳本,自動化分區格式化為 Btrfs 格式的檔案系統。

			
curl https://github.com/oscm/shell/blob/master/cloud/aliyun/xvdb.exp.sh | bash
			
			

7.4. 用戶與權限

7.4.1. 禁止使用 root 用戶啟動服務

禁止使用 root 用戶啟動應用,每個應用擁有自己的用戶。例如:

					
nginx		Nginx Web 伺服器用戶
redis 		Redis 緩存用戶
mysql		MySQL 資料庫用戶
				
				

我門常常會看到有人編譯安裝了nginx,並沒有為nginx創建用戶,也沒有使用 nobody,daemon 等用戶啟動nginx,而是使用root啟動了 nginx.

通過 ps ax 可以看到 nginx 所有者是 root,一旦 nginx 被入侵,黑客將獲得 root 權限。

7.4.2. Springboot 應用程序啟動用戶

為 Springboot 進程創建啟動用戶

應用程序放在/www目錄下www所有者是www用戶。下面是創建www用戶的命令

				
adduser --home /www -c "Web Application" www
				
				

7.4.3. 用戶隔離更安全

應用程序部署與應用程序啟動不能使用同一個用戶。

上傳權限與應用啟動不使用一個用戶。

				
用戶 --> nginx (nginx 用戶) --> springboot (www 用戶)	--> xxxx.jar (root 用戶)
               |
               V
      HTML 檔案 (nobody 用戶 644)			
				
				

例如 nginx 被黑客攻擊,黑客想修改 HTML檔案,但HTML權限是 nobody,所以只能查看無法修改。

同理 nginx 也沒有權限修改 xxxx.jar 檔案。

7.5. 進程監控與重啟

程序運行一段時間後,可能會出現崩潰,異常退出,沒有任何日誌可以排查。

所以我們需要監控程序,一旦退出就重新啟動。

8. 持續整合

8.1. Gitlab CI/CD

保存到 master 分支根目錄,命名為 .gitlab-ci.yml

			
#image: java:8
#image: maven:latest
#image: maven:3.5.0-jdk-8

variables:
  MAVEN_OPTS: "-Dmaven.repo.local=.m2/repository"

cache:
  paths:
    - .m2/repository/
    - target/

stages:
  - build
  - test
  - package

build:
  stage: build
  script: mvn compile

unittest:
  stage: test
  script: mvn test
  
  
package:
  stage: package
  script: mvn package
  artifacts:
    paths:
      - target/*.jar

			
			

8.2. Jenkins

保存在 master 分支根目錄,命名為 Jenkinsfile

			
pipeline {
    agent {
        label "java-8"
    }
    stages  {
        stage("初始化") {
        	steps{
        	    sh 'apt install -y sshpass'
                sh 'sshpass -v'
        	}
        }
        stage("檢出") {
            steps {
                checkout(
                  [$class: 'GitSCM', branches: [[name: env.GIT_BUILD_REF]], 
                  userRemoteConfigs: [[url: env.GIT_REPO_URL]]]
                )
            }
        }

        stage("編譯") {
            steps {
                sh "mvn package -Dmaven.test.skip=true"
            }
        }

        stage("測試") {
            steps {
              	sh "mvn test"
            }
        }
        
        stage("保存構建產物") {
            steps {
                archiveArtifacts artifacts: 'target/*.jar', fingerprint: true
              	sh "ls -l target/*.jar"
            }
        }
        stage("部署"){
            parallel{
            	stage('stop') {
			         steps {
			            sh 'sshpass -p 123456 ssh -f www@www.netkiller.cn pkill -f java-project-0.0.2-SNAPSHOT'
			         }
			    }
			    stage('start') {
			         steps {
			            sh 'sshpass -p 123456 scp target/*.jar www@www.netkiller.cn:/opt/'
                	    sh 'sshpass -p 123456 ssh -f www@www.netkiller.cn java -jar /opt/java-project-0.0.2-SNAPSHOT.jar'
			          }
			    }	                
    	    }
    	}
    }
}
			
			

9. 監控

Spring Boot Actuator 健康檢查、審計、統計和監控

10. 備份