知乎专栏 |
操作系统按完成后,使用下面脚本做一次初始化
curl -s https://raw.githubusercontent.com/oscm/shell/master/os/personalise.sh | bash
curl -s https://raw.githubusercontent.com/oscm/shell/master/web/nginx/stable/nginx.sh | bash
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
curl -s https://raw.githubusercontent.com/oscm/shell/master/queue/rabbitmq/rabbitmq-server-3.7.15.sh | bash
curl -s https://raw.githubusercontent.com/oscm/shell/master/lang/java/openjdk/java-1.8.0-openjdk.sh | bash
curl -s https://raw.githubusercontent.com/oscm/shell/master/lang/java/maven/apache-maven-3.6.1.sh | bash
前端开发会用到Node.js
curl -s https://raw.githubusercontent.com/oscm/shell/master/lang/node.js/binrary/node-v12.4.0.sh | bash
curl -s https://raw.githubusercontent.com/oscm/shell/master/project/gitlab/gitlab.centos7.sh | bash
Gitlab 配置请参考 《Netkiller Project 手札》
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
操作系统要求 CentOS 7 Minimal ISO
修改 /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
定义 .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
临时文件不应该有执行权限
/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
以数据库为例,从安全角度考虑我们需要如下更改
chown mysql:mysql /usr/bin/mysql* chmod 700 /usr/bin/mysql*
mysql用户是DBA专用用户, 其他用户将不能执行mysql等命令。
很多资料上是这么写的
* 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
内核参数调整
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
curl -s https://raw.githubusercontent.com/oscm/shell/master/os/selinux.sh | bash
对于某些文件没必要记录文件的访问时间,由其是在高并发的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部分安装配置与优化完成。
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
这节主要讲与SSH有关的安全配置
证书登陆非常安全,但是很有可能正常用户在你不知道情况下,给你安装了一个证书,他随时都可能进入你的系统
任何一个有权限的用户都能很方便的植入一个证书到 .ssh/authorized_keys 文件中
PubkeyAuthentication no AuthorizedKeysFile /dev/null
是不是自相矛盾? 这个跟上面讲的正好相反,这里只允许使用key文件登陆。
PasswordAuthentication no
这种方式比起密码要安全的多,唯一要注意的地方就是证书被拷贝 ,建议你给证书加上 passphrase。
证书的 passphrase 是可以通过openssl工具将其剥离的,SSH证书我没有试过,但是原理都差不多。
当你使用XShell, Xftp, WinSCP, SecureCRT, SecureFX ......等等软件登录时,该软件都提供记住密码的功能,使你下次再登陆的时候无须输入密码就可以进入系统。这样做的确非常方便,
但是你是否想过你的电脑一旦丢失或者被其他人进入,那有多么危险。我之前每天背着笔记本电脑上班,上面安装着XShell并且密码全部记忆在里面。这使我意识到一点电脑丢失,有多么可怕。
禁止SSH客户端记住密码,你不要要求别人那么做。你也无法控制,最终我找到了一种解决方案。
ChallengeResponseAuthentication yes
每次登陆都回提示你输入密码。密码保存也无效。
GSSAPIAuthentication no #GSSAPIAuthentication yes #GSSAPICleanupCredentials yes #GSSAPICleanupCredentials yes #GSSAPIStrictAcceptorCheck yes #GSSAPIKeyExchange no
只允许通过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
上面使白名单策略,你也可以采用黑名单策略。
骇客常常使用骇客字典穷举你的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
配置文件
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/
此模块的功能是,登陆错误输入密码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 注意,这是二进制格式的文件
例 6.3. /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
将下面一行添加到 /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
例 6.4. /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
编辑 /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
测试是否生效
限制普通用户通过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
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
服务器上不要安装JDK,请使用 Server JRE. 服务器上根本不需要编译器,代码应该在Release服务器上完成编译打包工作。
理由:一旦服务器被控制,可以防止在其服务器上编译其他恶意代码并植入到你的程序中。
加入 -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"
打开$JAVA_HOME/jre/lib/security/java.security文件,找到下面的内容:
securerandom.source=file:/dev/urandom 替换成 securerandom.source=file:/dev/./urandom
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
下面以 Springboot 2.0 为例。
配置文件位置,默认application.properties是放在jar包中的,这样对于运维并不友好。通过spring.config.location可以制定外部配置文件,这样更便于运维。
--spring.config.location 指定配置文件
java -jar demo.jar --spring.config.location=/opt/config/application.properties
这样运维人员便于配置数据库链接,切换服务器等操作
默认日志文件
logging.path=/tmp # 日志目录默认为 /tmp logging.file=spring.log # 日志文件名称,默认为spring.log
需要需要根据每个服务器的情况来指定日志存储的目录
java -jar spring-boot-app.jar --logging.file=/tmp/spring-2018-12-10.log
/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
#!/bin/bash ############################################## # Author: netkiller<netkiller@msn.com> # Homepage: http://www.netkiller.cn # Date: 2017-02-08 # $Author$ # $Id$ ############################################## # 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 $?
不要使用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 # 绑定的地址
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这类设备等等
系统默认 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秒之间。
server: tomcat: connection-timeout: 300000 accept-count: 1000 max-threads: 4000 min-spare-threads: 100 max-connections: 10000
配置Openfeign超时时间
feign: httpclient: enabled: true connection-timeout: 120000 compression: request: enabled: true min-request-size: 1024
有些版本的 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
修改 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=/
注意不仅仅压缩 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
feign 压缩传输
feign: httpclient: enabled: true connection-timeout: 120000 compression: request: enabled: true min-request-size: 1024
上传文件尺寸限制,注意如果使用了 Nginx 代理 Springboot 同时也需要配置 Nginx 上传限制。
spring.servlet.multipart.max-file-size=2MB spring.servlet.multipart.max-request-size=10MB spring.http.multipart.enabled=false
# 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
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
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支持
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
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
下面是给配置服务器增加用户认证,访问配置服务器用户名config, 密码 s3cr3t
server.port=8888 spring.cloud.config.server.git.uri=/opt/config security.user.name=config security.user.password=s3cr3t
为 eureka 增加HTTP认证,周泽任何人都能注册进来。
security.user.name=eureka security.user.password=s3cr3t
开发压缩传输
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
Feign metrics
feign.metrics.enabled=true
超时时间
feign: client: config: default: connectTimeout: 300000 readTimeout: 300000 httpclient: enabled: true connection-timeout: 300000 compression: request: enabled: true min-request-size: 1024
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; }
隐藏nginx版本号可以有效的增加黑客攻击难度
vim /etc/nginx/nginx.conf http { ... server_tokens off; }
server { listen 443 ssl http2; ssl_certificate server.crt; ssl_certificate_key server.key; }
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; } }
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
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; }
两台服务器 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; } }
允许监控软件从 localhost 收集 nginx 状态信息
server { listen 80; server_name localhost; location /status { stub_status on; access_log off; allow 127.0.0.1; deny all; } }
应用程序放在/www目录下,一般是这样的结构。
/www/example.com/www.example.com
每次升级将压错包解压到 /www/example.com/目录下,www.example.com 是符号连接,连接到刚刚解压的目录。
这个可以实现通过符号连接在多个版本之间快速切换。
数据盘是指云主机额外增加一个磁盘,用于数据存储。
如果是阿里云可以使用下面脚本,自动化分区格式化为 Btrfs 格式的文件系统。
curl https://github.com/oscm/shell/blob/master/cloud/aliyun/xvdb.exp.sh | bash
禁止使用 root 用户启动应用,每个应用拥有自己的用户。例如:
nginx Nginx Web 服务器用户 redis Redis 缓存用户 mysql MySQL 数据库用户
我门常常会看到有人编译安装了nginx,并没有为nginx创建用户,也没有使用 nobody,daemon 等用户启动nginx,而是使用root启动了 nginx.
通过 ps ax 可以看到 nginx 所有者是 root,一旦 nginx 被入侵,黑客将获得 root 权限。
为 Springboot 进程创建启动用户
应用程序放在/www目录下www所有者是www用户。下面是创建www用户的命令
adduser --home /www -c "Web Application" www
保存到 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
保存在 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' } } } } } }
常规接口
neo@MacBook-Pro ~ % curl -s http://www.netkiller.cn:8080/actuator | jq { "_links": { "self": { "href": "http://www.netkiller.cn:8080/actuator", "templated": false }, "health": { "href": "http://www.netkiller.cn:8080/actuator/health", "templated": false }, "health-component": { "href": "http://www.netkiller.cn:8080/actuator/health/{component}", "templated": true }, "health-component-instance": { "href": "http://www.netkiller.cn:8080/actuator/health/{component}/{instance}", "templated": true }, "info": { "href": "http://www.netkiller.cn:8080/actuator/info", "templated": false } } }
暴漏所有接口
management: endpoints: web: exposure: include: "*"
再次访问 /actuator
neo@MacBook-Pro-Neo ~/w/Architect (master)> curl -s https://www.netkiller.cn/actuator/ | jq { "_links": { "self": { "href": "http://pre.ejiayou.com/ensd-channel-service/monitor", "templated": false }, "archaius": { "href": "https://www.netkiller.cn/actuator/archaius", "templated": false }, "nacosconfig": { "href": "https://www.netkiller.cn/actuator/nacosconfig", "templated": false }, "nacosdiscovery": { "href": "https://www.netkiller.cn/actuator/nacosdiscovery", "templated": false }, "auditevents": { "href": "https://www.netkiller.cn/actuator/auditevents", "templated": false }, "beans": { "href": "https://www.netkiller.cn/actuator/beans", "templated": false }, "caches-cache": { "href": "https://www.netkiller.cn/actuator/caches/{cache}", "templated": true }, "caches": { "href": "https://www.netkiller.cn/actuator/caches", "templated": false }, "health-component": { "href": "https://www.netkiller.cn/actuator/health/{component}", "templated": true }, "health": { "href": "https://www.netkiller.cn/actuator/health", "templated": false }, "health-component-instance": { "href": "https://www.netkiller.cn/actuator/health/{component}/{instance}", "templated": true }, "conditions": { "href": "https://www.netkiller.cn/actuator/conditions", "templated": false }, "configprops": { "href": "https://www.netkiller.cn/actuator/configprops", "templated": false }, "env-toMatch": { "href": "https://www.netkiller.cn/actuator/env/{toMatch}", "templated": true }, "env": { "href": "https://www.netkiller.cn/actuator/env", "templated": false }, "info": { "href": "https://www.netkiller.cn/actuator/info", "templated": false }, "loggers-name": { "href": "https://www.netkiller.cn/actuator/loggers/{name}", "templated": true }, "loggers": { "href": "https://www.netkiller.cn/actuator/loggers", "templated": false }, "heapdump": { "href": "https://www.netkiller.cn/actuator/heapdump", "templated": false }, "threaddump": { "href": "https://www.netkiller.cn/actuator/threaddump", "templated": false }, "metrics": { "href": "https://www.netkiller.cn/actuator/metrics", "templated": false }, "metrics-requiredMetricName": { "href": "https://www.netkiller.cn/actuator/metrics/{requiredMetricName}", "templated": true }, "scheduledtasks": { "href": "https://www.netkiller.cn/actuator/scheduledtasks", "templated": false }, "httptrace": { "href": "https://www.netkiller.cn/actuator/httptrace", "templated": false }, "mappings": { "href": "https://www.netkiller.cn/actuator/mappings", "templated": false }, "refresh": { "href": "https://www.netkiller.cn/actuator/refresh", "templated": false }, "features": { "href": "https://www.netkiller.cn/actuator/features", "templated": false }, "service-registry": { "href": "https://www.netkiller.cn/actuator/service-registry", "templated": false } } }
curl www.netkiller.cn:8080/actuator/health
neo@MacBook-Pro ~ % curl -s http://www.netkiller.cn:8080/actuator/health | jq { "status": "UP" }
metadata: name: netkiller labels: app: netkiller group: finance namespace: neo annotations: description: 服务 spec: replicas: 1 progressDeadlineSeconds: 600 revisionHistoryLimit: 10 selector: matchLabels: app: netkiller strategy: type: RollingUpdate rollingUpdate: maxSurge: 1 maxUnavailable: 0 template: metadata: labels: app: netkiller spec: hostAliases: - ip: 172.18.200.10 hostnames: - log.netkiller.cn containers: - name: netkiller image: registry.netkiller.cn/project/netkiller ports: - name: http containerPort: 8080 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 3 httpGet: path: /netkiller-service/monitor/health port: 8080 scheme: HTTP initialDelaySeconds: 60 periodSeconds: 10 successThreshold: 1 timeoutSeconds: 5 readinessProbe: failureThreshold: 3 httpGet: path: /netkiller-service/monitor/health port: 8080 scheme: HTTP initialDelaySeconds: 30 periodSeconds: 10 successThreshold: 1 timeoutSeconds: 5 env: - name: JAVA_OPTS value: -Duser.timezone=Asia/Shanghai -Xmx2g -Xms1g -Dspring.application.name=netkiller-service -Dspring.profiles.active=neo -Dserver.port=8080 -Dlog.level=info -Dspring.cloud.nacos.config.server-addr=nacos.default.svc.cluster.local:8848 -Dspring.cloud.nacos.discovery.server-addr=nacos.default.svc.cluster.local:8848 volumeMounts: - mountPath: /tmp name: tmp - name: filebeat image: docker.elastic.co/beats/filebeat:8.6.1 args: - -c - /usr/share/filebeat/filebeat.yml - -e imagePullPolicy: IfNotPresent env: - name: environment value: prod - name: service value: netkiller-service volumeMounts: - mountPath: /tmp name: tmp - mountPath: /usr/share/filebeat/data name: data - mountPath: /usr/share/filebeat/filebeat.yml name: config subPath: filebeat.yml imagePullSecrets: - name: registry restartPolicy: Always dnsPolicy: ClusterFirst volumes: - emptyDir: {} name: data - emptyDir: {} name: tmp - configMap: defaultMode: 420 name: filebeat name: config apiVersion: apps/v1 kind: Deployment --- metadata: name: netkiller namespace: neo spec: ports: - name: http-80 protocol: TCP port: 80 targetPort: 8080 selector: app: netkiller apiVersion: v1 kind: Service --- metadata: name: netkiller namespace: neo annotations: kubernetes.io/ingress.class: nginx nginx.ingress.kubernetes.io/proxy-connect-timeout: '300' nginx.ingress.kubernetes.io/proxy-read-timeout: '300' nginx.ingress.kubernetes.io/proxy-send-timeout: '300' spec: rules: - host: neo.netkiller.cn http: paths: - path: /netkiller-service pathType: ImplementationSpecific backend: service: name: netkiller port: number: 80 tls: - hosts: - neo.netkiller.cn secretName: tls apiVersion: networking.k8s.io/v1 kind: Ingress