nginx动态添加访问白名单

本文实现的功能是:网站启用访问白名单,对于不在白名单中又需要访问的客户,只需打开一个不公开的网址,然后自动获得2小时的访问权限,时间达到后自动删除访问权限

实现此功能需要以下几个步骤:

  1. nginx启用访问白名单
  2. 客户打开指定网址自动添加访问白名单
  3. 为网址添加简单的认证
  4. 每两个小时自动恢复默认白名单,删除临时IP访问权限

一、nginx配置访问白名单

这个就比较简单了,简单贴一下配置:

............nginx.conf...........
geo $remote_addr $ip_whitelist {
default 0;
include ip_white.conf;
}
............server段............
location / {
        if ($ip_whitelist = 1) {
            break;
        }
       return 403;
    }

启用白名单的IP写在ip_white.conf文件中,格式为: 8.8.8.8 1;,只需将IP按照格式写入ip_white.conf中即可获得访问权限。详细配置方法:nginx启用IP白名单

二、使用LUA自动添加白名单

nginx需配合lua模块才能实现这个功能,新建一个location,客户访问这个location时,使用lua拿到客户IP并调用shell脚本写入ip_white.conf中,写入后自动reload nginx使配置生效,lua代码:

location /addip {
content_by_lua '

CLIENT_IP = ngx.req.get_headers()["X_real_ip"]
if CLIENT_IP == nil then
    CLIENT_IP = ngx.req.get_headers()["X_Forwarded_For"]
end
if CLIENT_IP == nil then
    CLIENT_IP  = ngx.var.remote_addr
end
if CLIENT_IP == nil then
    CLIENT_IP  = "unknown"
end
    ngx.header.content_type = "text/html;charset=UTF-8";
    ngx.say("你的IP : "..CLIENT_IP.."<br/>");
    os.execute("/opt/ngx_add.sh "..CLIENT_IP.."")
    ngx.say("添加白名单完成,有效时间最长为2小时");
';
}

/opt/ngx_add.sh shell脚本内容:

#!/bin/bash
ngx_conf=/usr/local/nginx/conf/52os.net/ip_white.conf
ngx_back=/usr/local/nginx/conf/52os.net/ip_white.conf.default
result=`cat $ngx_conf |grep $1`

case $1 in

rec)
    rm -rf $ngx_conf 
    cp $ngx_back $ngx_conf
     /usr/local/nginx/sbin/nginx -s reload
 ;;

*)
    if [ -z "$result" ]
       then
         echo  "#####add by web #####" >>$ngx_conf
         echo "$1 1;" >> $ngx_conf
         /usr/local/nginx/sbin/nginx -s reload
     else
         exit 0
     fi
;;
esac

该脚本有两个功能:

  1. 自动加IP并reload nginx
  2. 恢复默认的ip_white.conf文件,配合定时任务可以取消非默认IP的访问权限

nginx主进程使用root运行,shell脚本reload nginx需设置粘滞位:

chown root.root  /usr/local/nginx/sbin/nginx
chmod 4755 /usr/local/nginx/sbin/nginx

nginx启用lua模块见nginx启用lua模块

三、添加简单的认证

使用base auth 添加简单的用户名密码认证,防止非授权访问,生成密码文件:

printf "52os.net:$(openssl passwd -crypt 123456)\n" >>/usr/local/nginx/conf/pass

账号:52os.net
密码:123456

在刚刚的location中加入:

location /addip {

            auth_basic "nginx auto addIP  for 52os.net";
            auth_basic_user_file /usr/local/nginx/conf/pass; 
            autoindex on;
......Lua代码略......

四、自动恢复默认IP白名单

通过web获得访问权限的IP,设置访问有效期为两小时,我是通过每两小时恢复一次默认的IP白名单文件实现。把ip_white.conf文件复制一份作为默认的白名单模版:

 cp /usr/local/nginx/conf/52os.net/ip_white.conf /usr/local/nginx/conf/52os.net/ip_white.conf.default

使用定时任务每两小时通用上面的shell脚本来恢复,定时任务为:

1 */2 *  *  * root /opt/ngx_add.sh rec

使用ssl加密Nginx与uptream servers之间的流量

nginx做为一款优秀的反向代理软件,最常见的架构是:

客户 <---> NGINX(反向代理) <---> 后端服务器(upstream servers)

如果nginx和后端服务器都在公网上,按常规配置,nginx与后端服务器之间的流量是明文传输的,很容易受到篡改、嗅探或者被GFW认证。我就遇到了NGINX反代后端服务器时,竟然被劫持了!!!
Nginx作为工作在7层的软件,最常用的加密方式就是https了,我只需要加密nginx到upstream servers之间的流量,不需要浏览器认证,所以自签名证书就够了,此作法需要:

1. 自签名证书
2. 后端服务器配置https
3. nginx配置解密私钥

一、生成自签名证书
使用openssl生成签名证书:

openssl  req -x509 -nodes -days 3650 -newkey rsa:2048 -keyout 52os.net.key -out 52os.net.crt

二、后端服务器配置自签名证书
为了不影响后端的https配置,我启用了一个新端口,配置自签名证书。我后端(upstream servers)也是nginx服务器

server {
    listen     12345;

    ssl on;
    ssl_certificate        "/etc/ssl/certs/52os.net.crt";
    ssl_certificate_key    "/etc/ssl/certs/52os.net.key";

    ...... server configs ......

}

三、NGINX配置upstream加密
nginx(反向代理)配置upstream和解密私钥:

upstream backend {
    server backend1.52os.net.com:12345;
    server 8.8.8.8:12345;
    check interval=3000 rise=2 fall=3 timeout=30000 type=tcp;
    #check interval=3000 rise=2 fall=3 timeout=30000 type=http;
    #check_http_send "GET / HTTP/1.0\r\nConnection: keep-alive\r\nHost:www.52os.net\r\n\r\n";
    #check_http_expect_alive http_2xx http_3xx;

   }

server {
    listen     80;

    ssl_certificate_key    "/etc/ssl/certs/52os.net.key";

    location / {
        proxy_redirect         off;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header  X-Forwarded-For $proxy_add_x_forwarded_for;

        proxy_pass https://backend;
    }
}

配置好后,nginx到后端的upstream servers就会使用https加密了。
我的NIGNX版本tengine 2.1.2(nginx 1.6.1),nginx版本非常低。如果使用的是官方nginx,版本在1.9.2以上,有另外的模块可以实现Upstream加密的功能,原理是一样的。可以参考官方文章:https://www.nginx.com/resources/admin-guide/nginx-tcp-ssl-upstreams/

使用saltstack初始化系统

目前工作中使用最多的还是Fabric,最近把所有服务器都装上了saltstack,但是目前并没有使用saltstack进行管理,用的最多的还是进行系统和环境的初始化。主要是因为其它同事对saltstack不够熟悉,还有就是生产上的模块和环境都比较复杂,想完全使用saltstack管理还要一个循序渐进的过程,自动化运维想要用还是要趁早建设,越晚束手束脚的地方越多

一、saltstack安装

2.1 简介

saltstack是比较流行的开源配置管理工具,主要特点有:

  1. 基于Python开发,轻量级、灵活、扩展性好
  2. 使用ZeroMQ,确保速度和可靠性
  3. C/S架构,加密通讯,可管理海量客户端,并行能力强
  4. 配置简单、容易学习和使用

主要功能:

  1. 配置管理,自动配置系统和软件环境的状态
  2. 批量命令执行

saltstack软件组件,以下是官方文档中的介绍:

  1. salt-master: 主控制端(Server端),也叫Master
  2. salt-minion: 被控制端(client端),也叫Minion
  3. salt-ssh : 无Minion模式,使用ssh管理客户端,类似ansible
  4. salt-syndic :任务分发的代理,向Minion端发放任务
  5. Execution Modules:执行模块,在Minion系统上执行命令
  6. Runners :即使用salt-run,在Master端执行的命令
  7. Formulas (States):配置管理,描述Minion系统的目标状态
  8. Grains :在minion启动时收集到的系统静态信息
  9. Pillar :在Master端上定义的变量,供满足条件的Minion使用,通常是敏感信息
  10. Reactor : 基于事件触相应的操作
  11. Top File :入口文件
  12. Returners :将Minion执行结果发送给第三方系统

2.2 安装

我是centos系统,直接yum安装,epel源里也有,但是比官方的老一点,推荐使用官方源安装

yum install https://repo.saltstack.com/yum/redhat/salt-repo-latest-1.el6.noarch.rpm

安装并启动Master:

yum install salt-master
service salt-master start

启动salt-master后,zeromq会监听两个端口:

  • 4505:发布消息
  • 4506:接受消息

安装Minion:

yum install salt-minion

启动Minion端之前需在配置文件中指定Master的IP,支持IP和域名:

sed -i 's/#master: salt/master: master.52os.net/' /etc/salt/minion

启动Minion:

service salt-minion start

2.3 认证

master和minion通信之间使用AES加密。minion启动后,会生成公钥和私钥,并将公钥发送给master,被master接受后,master就能对minion发送指令了。同时Master在启动后也会生产公钥和私钥,minion端也会保留master的公钥
Master端查看key状态:

salt-key -L

服务端接受Minion的公钥:

salt-key -a salt-client

执行测试命令:

salt '*'  test.ping

有返回就说明正常通信了,可以用Master管理Minion端了

二、使用saltstack进行配置管理

saltstack的配置管理通过编写sls文件实现,sls文件描述了目标系统的状态,编写sls文件之前,需先在Master端设置保存sls文件的file_roots和pillar_roots,编辑/etc/salt/master,按需添加如下内容:

file_roots:
    base:
        - /etc/salt/base
    prod:
        - /etc/salt/prod

pillar_roots:
    base:
        - /etc/salt/pillar/base
    prod:
        - /etc/salt/pillar/prod

saltstack的sls文件使用YAML定义数据,top.sls文件为states的入口文件

top.sls
base:
 '*':
    - init.*

看一下目录结构:

tree /etc/salt/base
├── init
│   ├── cron.sls
│   ├── date_time.sls
│   ├── dirmode.sls
│   ├── dns.sls
│   ├── files
│   │   ├── limits.conf
│   │   └── resolv.conf
│   ├── history.sls
│   ├── pkg.sls
│   ├── selinux.sls
│   ├── sysctl.sls
│   └── ulimits.sls
└── top.sls

安装并更新常用的软件:pkg.sls

#install epel yum source
epel-release:
    pkg.installed:
        - refresh: True
#install or keep the base packages  latest
init_pkgs:
    pkg.latest:
        - pkgs:
            - yum
            - telnet
            - lrzsz
            - iptables
            - ntpdate
            - crontabs
            - policycoreutils   #selinux modules for saltstack
            - policycoreutils-python

更改时区并校准时间:date_time.sls

include:
    - init.pkg
date_time_setting:
    timezone.system:
        - name: Asia/Shanghai
        - utc: True
cmd.run:
    - name: ntpdate time.windows.com
    - require:
        - init_pkgs

定时任务管理: cron.sls

ntp_cron:
    cron.present:
        - name: ntpdate pool.ntp.org >/dev/null
        - user: root
        - hour: 4

配置系统DNS:dns.sls

/etc/resolv.conf:
    file.managed:
        - source: salt://init/files/resolv.conf
        - user: root
        - group: root
        - mode: 644

更改生产目录权限:dirmode.sls

/web:
file.directory:
    - name: /web
    - user: root
    - group: root
    - dir_mode: 755
    - file_mode: 644
    - recurse:
        - user
        - group
        - mode

记录所有用户bash历史命令到/var/log/bash_history.log:history.sls

/etc/profile:
file.append:
    - text: |
        function log2syslog
        {
        export HISTTIMEFORMAT="[%Y-%m-%d %H:%M:%S] [`who am i 2>/dev/null| awk '{print $NF}'|sed -e 's/[()]//g'`]"
        export PROMPT_COMMAND='\
          if [ -z "$OLD_PWD" ];then
                export OLD_PWD=$(pwd);
          fi;
        if [ ! -z "$LAST_CMD" ] && [ "$(history 1)" != "$LAST_CMD" ]; then
            echo  `whoami`_shell_cmd "[$OLD_PWD]$(history 1)" >>/var/log/bash_history.log;
        fi ;
        export LAST_CMD="$(history 1)";
        export OLD_PWD=$(pwd);'
        }
        trap log2syslog DEBUG
    - unless: grep log2syslog /etc/profile

env_source:
    cmd.run:
        - name: source /etc/profile
        - unless: env |grep  HISTTIMEFORMAT
        - require:
            - file: /etc/profile

关闭selinux:selinux.sls

permissive:
selinux.mode
disabled_selinux:
    file.replace:
        - name: /etc/sysconfig/selinux
        - pattern: SELINUX=enforcing
        - repl: SELINUX=disabled

更改系统最大能打开的文件数:ulimits.sls

/etc/security/limits.conf:
file.managed:
    - source: salt://init/files/limits.conf
    - user: root
    - group: root
    - mode: 644

cmd.run:
    - name: ulimit -a
    - unless:  grep 65535 /etc/security/limits.conf

设置内核参数:sysctl.sls

#本地tcp可使用端口范围
net.ipv4.ip_local_port_range:
    sysctl.present:
        - value: 10000 65000
#设置可以打开的最大文件数
fs.file-max:
    sysctl.present:
        - value: 2000000
#减少swap分区使用
vm.swappiness:
    sysctl.present:
        - value: 0

salt也有一些预先写好的Formulas(Formulas are pre-written Salt States),想省事可以直接拿来用,地址: https://github.com/saltstack-formulas

参考文章:

http://lixcto.blog.51cto.com/4834175/d-1

linux下配置和安装KVM虚拟机

最近要在外网搞一套监控系统,正好有一台服务器配置比较高,也没跑什么重要的业务,就拿来划了一个KVM虚拟机,可能是平时各种虚拟机用的比较多,配置起来感觉比想象中简单的多,简单记录下过程

准备工作

宿主机为centos 6.8 64位,检查宿主机CPU是否支持虚拟化:

cat /proc/cpuinfo | egrep '(vmx|svm)' | wc -l;

结果大于0表示支持

安装kvm

KVM核心软件包:

yum install kvm libvirt python-virtinst qemu-kvm virt-viewer bridge-utils

如果服务器上有桌面环境,想使用图形界面管理器virt-manager,可以安装完整的KVM环境:

yum groupinstall Virtualization 'Virtualization Client' 'Virtualization Platform' 'Virtualization Tools'

验证内核模块是否加载:

lsmod | grep kvm

启动虚拟机管理接口服务:

/etc/init.d/libvirtd start

设置开机启动:

chkconfig libvirtd on

启动libvirtd后,会自动创建了一个网卡,并启动dnsmasq服务,用来为虚拟机分配IP地址

创建虚拟机

下载虚拟机要安装的ISO系统镜像文件,之后需创建存储池,指定在宿主机上虚拟机磁盘的存储位置,创建存储目录:

mkdir -p /opt/kvm

定义一个储存池和绑定目录:

virsh pool-define-as vmspool --type dir --target /opt/kvm

建立并激活存储池:

virsh pool-build vmspool
virsh pool-start vmspool

使用存储池创建虚拟机,并通过vnc连接:

virt-install \
--hvm    \ #全虚拟化
--name=zabbix  \#虚拟机名字
--ram=4096   \ #分配内存
--vcpus=4   \  #分配CPU数
--cdrom=/opt/kvm/iso/CentOS-7-x86_64-DVD-1511.iso \ #使用的ISO
--virt-type=kvm \ #虚拟机类型
--disk  path=/opt/kvm/zabbix.qcow2,device=disk,format=qcow2,bus=virtio,cache=writeback,size=100 \ #磁盘大小,格式
--network default \  #网络设置,defalut为NAT模式
--accelerate \  #KVM内核加速
--graphics vnc,listen=0.0.0.0,port=5922,password=123123\ #vnc配置
--force \
--autostart

之后使用vnc客户端连接 宿主机IP:5922,即可使用图形安装系统;也可以选择nographics模式,无需vnc在命令行下安装,建议使用vnc
安装完成后会生成:

  1. 虚拟机的配置文件:/etc/libvirt/qemu/zabbix.xml
  2. 虚拟硬盘文件:/opt/kvm/zabbix.qcow2
  3. NAT网络配置文件:/etc/libvirt/qemu/networks/default.xml

配置网络

KVM可以配置两种:

  1. NAT网络: 虚拟机使用宿主机的网络访问公网,宿主机和虚拟机能互相访问,但不支持外部访问虚拟机
  2. 桥接网络: 虚拟机复用宿主机物理网卡,虚拟机与宿主机在网络中角色完全相同,支持外部访问

配置NAT网络

默认会有一个叫default的NAT虚拟网络,查看NAT网络:

virsh net-list --all

如果要创建或者修改NAT网络,要先编辑default.xml:

virsh  net-edit default

重新加载和激活配置:

virsh  net-define /etc/libvirt/qemu/networks/default.xml

启动NAT网络:

virsh net-start default
virsh net-autostart default

启动NAT后会自动生成一个虚拟桥接设备virbr0,并分配IP地址,查看状态:

brctl show

正常情况下libirtd启动后就会启动virbr0,并自动添加IPtables规则来实现NAT,要保证打开ip_forward,在/etc/sysctl.conf中:

net.ipv4.ip_forward = 1
sysctl -p

启动虚机并设置自动获取IP即可,如果想手动指定虚拟机IP,要注意配置的IP需在NAT网段内

配置桥接网络

系统如果安装了桌面环境,网络会由NetworkManager进行管理,NetworkManager不支持桥接,需要关闭NetworkManger:

chkconfig NetworkManager off
chkconfig network on
service NetworkManager stop
service network start

不想关闭NetworkManager,也可以在ifcfg-br0中手动添加参数"NM_CONTROLLED=no"
创建网桥:

virsh iface-bridge eth0 br0

创建完后ifconfig会看到br0网桥,如果eth0上有多个IP,更改下相应的文件名,如:ifcfg-eth0:1改为ifcfg-br0:1
编辑虚拟机的配置文件,使用新的网桥:

virsh edit zabbix

找到网卡配置,改为:

<interface type='bridge'>
  <mac address='52:54:00:7a:f4:9b'/>
  <source bridge='br0'/>
  <model type='virtio'/>
  <address type='pci' domain='0x0000' bus='0x00' slot='0x03' function='0x0'/>
</interface>

我是用的br0,为虚拟机添加多块网卡只需复制多个interface,并确保mac address和PCI地址不同即可
重新加载配置:

virsh define /etc/libvirt/qemu/zabbix.xml

重启虚拟机:

virsh shutdown zabbix
virsh start zabbix

之后使用VNC连接虚拟机并设置下网络即可

常用操作

KVM相关操作都通过vish命令完成,参数虽然多,但是功能一目了然,很直观

创建虚拟机快照:

virsh  snapshot-create-as --domain zabbix --name init_snap_1

也可以简写成:

virsh  snapshot-create-as  zabbix  init_snap_1

快照创建后配置文件在/var/lib/libvirt/qemu/snapshot/zabbix/init_snap_1.xml
查看快照:

snapshot-list zabbix

删除快照:

 snapshot-delete zabbix  init_snap_1

排错

  1. ERROR Format cannot be specified for unmanaged storage.
    virt-manager 没有找到存储池,创建储存池即可

  2. KVM VNC客户端连接闪退
    使用real vnc或者其它vnc客户端连接kvm闪退,把客户端设置中的ColourLevel值设置为rgb222或full即可

  3. virsh shutdown 无法关闭虚拟机
    使用该命令关闭虚拟机时,KVM是向虚拟机发送一个ACPI的指令,需要虚拟机安装acpid服务:
    yum -y install acpid && /etc/init.d/acpid start
    否则只能使用virsh destroy 强制关闭虚拟机

参考文章

https://access.redhat.com/documentation/en-US/Red_Hat_Enterprise_Linux/6/html/Virtualization_Host_Configuration_and_Guest_Installation_Guide/index.html

asterisk双向回拨功能实现

目前我们公司电话系统经过两年的发展,已经实现了两个异地办公室、上百部分机,三个机房节点,多家voip供应商的之间的互通与网络冗余,算的上一个小型的呼叫中心了,一直比较懒写这方面的文章。最近公司正好有做回拨的需求,随便写篇关于这方面的文章。
需求:客户通过网站前台提交电话号码,电话系统先呼叫指定客服分机或响铃组,客服摘机后系统呼叫客户电话号码,客户接听后开始与客服通话。

我们的电话系统用的是freePBX,系统自带回拨功能(callback)功能比较简单,商业模块(WebCallBack)可以实现web提交号码,但这两个模块都只能先呼叫客户,客户接通后才能呼叫客服,无法自定义呼叫顺序,不满足我们的需求,只能自己开发了。好在asterisk支持自动呼叫脚本(.call files),功能实现起来比较容易,先看一下脚本工作流程:

1. asterisk 启动时需加载pbx_spool.so 模块支持自动呼叫脚本,Freepbx是自动加载的
2. 按需求编写呼叫脚本,保存为扩展名为.call的文件
3. 将.call脚本移到到/var/spool/asterisk/outgoing/目录下
4. 系统检测到该脚本并自动执行呼叫流程

呼叫脚本语法:

发起呼叫的参数:
Channel: <channel>: 呼叫的通道,可以是分机,trunk,响铃组等等
CallerID: "name" <number>  主叫id,即显示的号码和名称
MaxRetries: <number> 呼叫失败后重试次数,不包括原始呼叫
RetryTime: <number>  等待多久重新尝试呼叫
WaitTime: <number>  等待接听超时的时间
Account: 使用的Accoud code,CDR计费方面会用到

呼叫接通后的参数:
Context: <context-name> 使用的拨号方案
Extension: <ext>  拨号方案的目标号码
Priority: <priority> 优先级
Set: 设置变量
Application: 要启动的应用
Data: 应用使用的数据参数
AlwaysDelete: Yes/No 更改时间为将来时,不会删除该文件
Archive: Yes/No  使否将执行过的脚本归档到outgoing_done

来写一个hello world脚本:

channel: SIP/801   ;我的分机号
Callerid: "call" <801>
Application: Playback
Data: hello-world

将脚本保存为helloworld.call,手动复制到/var/spool/asterisk/outgoing/,系统就会拨打分机801,播放hello world后挂机。
我的freepbx对接了很多的voip供应商,freepbx会生产默认拨号方案:outbound-allroutes,根据用户拨号自动选择呼出线路,如我的规则:使用呼出线路1来打10086的拨号为:1 86 75510086。下面在写一个呼叫脚本:

channel: SIP/801   ;分机
Callerid: "回拨" <801>
Context: outbound-allroutes
Extension: 18675510086

这个脚本个功能是先呼叫分机801,在分机801接听后,系统开始呼叫10086,10086接听后801就可以与10086通话了。
这个脚本中分机801接听后,系统大概需要5秒才能接通10086,之间没有任何提示,freepbx的系统自带了一些提示,编写一个拨号方案加入提示,编辑:etc/asterisk/extensions_custom.conf,加入:

[saylast4num]
exten = _XX.,1,NoOp(say last 4 number of client number works) 
exten = _XX.,n,Wait(1)
exten = _XX.,n,Playback(calling)   ;系统提示音 calling
exten = _XX.,n,SayDigits(${EXTEN:-4}) ;读拨打号码的后4位
exten = _XX.,n,Goto(autocall_out,${EXTEN},1) ;跳转到自动呼出的拨号方案
[autocall_out]
include => outbound-allroutes

修改上面的呼叫脚本为:

channel: SIP/801   ;分机
;channel: Local/400@from-internal  ;本地响铃组400
Callerid: "回拨" <801>
Context: saylast4num
Extension: 18675510086

执行一下,801分机接听后系统会提示“calling 0086”,然后呼叫075510086,以这个脚本为模版,将18675510086替换为客户的号码,在用python封装成web api,就可以实现从web接收号码,写相应call脚本到/var/spool/asterisk/outgoing/,实现我们所需的回拨功能,python +flask写几十行代码就可以搞定

其实不使用呼叫脚本,使用originate也能实现,一个简单的shell脚本:

#!/bin/bash
asterisk -x "channel originate SIP/$1 extension $2@outbound-allroutes"

保存为call.sh ,用法: call.sh 801 18675510086
先呼叫客户在接通内部分机的情况,最方便的是直接花50刀买webcallback模块,这个我不多说了

参考文章:

http://www.voip-info.org/wiki/view/Asterisk+auto-dial+out