Looyao's Blog

记录一些点滴

SOCKET:探测客户端连接是否断开

| Comments

上一篇介绍了使用nginx扩展来实现websocket服务,最近自己使用C++造了同样的轮子(comet),但是性能比nginx扩展要好些。思路是发布消息使用HTTP,订阅走websocket,只实现了websocket的推送消息。使用epoll/kqueue + 多线程,超时检测单独开一个线程定期扫描。但是最近把检查客户端是否在线的逻辑集成进来,一般情况,客户端主动close服务端是可以马上获知状态,但是如果客户端突然断电、拔掉网线,这种情况,是没法及时获知的,只能依赖那个超时检查线程遍历,但是这并不很及时。然后继续查找一些简单的解决方案。

最初准备调整系统TCP keepalive参数,主要有三个:

1
2
3
4
5
6
7
8
# cat /proc/sys/net/ipv4/tcp_keepalive_time
7200

# cat /proc/sys/net/ipv4/tcp_keepalive_intvl
75

# cat /proc/sys/net/ipv4/tcp_keepalive_probes
9

第一个参数的单位是秒,默认值是两小时,意思是两小时后开始检测对端状态。第二个参数是检查的间隔,第三个是尝试次数,如果在尝试次数内都没有对端的ACK响应,就标记为连接断开。可以把参数值设置的小一些来解决上边的问题。但还有另外一种情况就是客户端异常了,TCP连接在,但是客户端卡死(代码bug,死锁之类),这种通过TCP keepalive是无法检测的。PS:参数调整不一定要修改全局配置,可以在应用逻辑代码里边使用setsockopt来设置。

接下来的方案就是要实现心跳机制了,websocket协议自带PING/PONG,实现起来也不算麻烦,方案就是客户端定时发送PING,服务端收到响应一个PONG,并更新alive时间,这里没有选择在服务端实现PING。这样客户端的在线状态就相对准确的判断了。

小记一下。

参考资料:

1、https://tools.ietf.org/html/rfc6455#section-5.5.2

2、http://tldp.org/HOWTO/TCP-Keepalive-HOWTO/usingkeepalive.html

一个简单的弹幕服务端实现,基于nginx

| Comments

弹幕越来越火,今天介绍一种简单的弹幕服务器实现。

nginx push stream module这个模块非常适合弹幕的场景,支持多种连接方式。

安装

1
2
3
4
5
6
git clone https://github.com/wandenberg/nginx-push-stream-module.git
wget http://nginx.org/download/nginx-1.8.1.tar.gz
tar xvf nginx-1.8.1.tar.gz
cd nginx-1.8.1
./configure --prefix=/usr/local/nginx --add-module=../nginx-push-stream-module
make && make install

配置

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
http {
    ...

    push_stream_shared_memory_size 32m; #设置共享内存大小
    push_stream_authorized_channels_only on; #只有有内容的频道才能订阅
    push_stream_max_messages_stored_per_channel 100; #每个频道最大存储消息的最大数量,达到上限之后,新的消息会取代旧的。

    server {
        listen       80;
        server_name  localhost;

        root   html;

        location / {
            index  index.html index.htm;
        }

        location ~ /ws/(.*) { 
            push_stream_subscriber websocket; #订阅使用WebSocket方式

            push_stream_channels_path                   $1;
            push_stream_message_template                "{\"id\":~id~,\"channel\":\"~channel~\",\"text\":\"~text~\"}";

            push_stream_websocket_allow_publish         off; #只允许订阅,不允许发布。

            push_stream_ping_message_interval           10s;
        }

        location /pub {
            allow 127.0.0.1; #只允许内网访问发布消息
            allow 192.168.0.0/24;
            deny all;

            push_stream_publisher admin;
            push_stream_store_messages on;

            push_stream_channels_path               $arg_id;
        }
    }

}

使用

官方文档比较详细,提供了很多使用事例,上边的的配置是订阅使用WebSocket的方式,长连接,效率更高。发布使用正常的HTTP协议,提供内网访问,这样前端逻辑收到用户发布内容可以进行过滤等处理,然后通过HTTP POST发布内容到nginx。

订阅:官方有提供html例子,我们使用C++的Client来测试。下载地址:https://github.com/dhbaird/easywsclient。修改example-client.cpp,替换WebSocket地址。

1
ws = WebSocket::from_url("ws://localhost/ws/test");

make之后,执行./example-client,需要先发布一条消息才能正常监听,不然会返回404。

发布:可以使用curl模拟测试

1
curl -X POST http://localhost/pub?id=test -d 'hello world'

这样一条消息就被发布了,订阅会得到:

1
2
3
4
./example-client
easywsclient: connecting: host=localhost port=80 path=/ws/test
Connected to: ws://localhost/ws/test
>>> {"id":5,"channel":"test","text":"hello"}

这样,一个弹幕服务端就实现了。细节可以自己调整,详细配置参数可以参考官方文档。

分布式

目前官方不提供分布式处理,但是可以通过前端逻辑来实现,可以部署多个服务,发布消息的时候每个服务都POST一次,也不是很麻烦。可以根据实际的访问量来做负载均衡。

最后

推荐一篇文章,http://highscalability.com/blog/2014/4/28/how-disqus-went-realtime-with-165k-messages-per-second-and-l.html,Disqus对于nginx push stream module的使用。

现在开源世界越来越强大,很多场景都能找到开源的解决方案,感谢开源的世界。

UPDATE(2016-04-24): 还有另外一个扩展可以选择,nchan,这个可以通过redis做集群。

用php实现一个敏感词过滤功能

| Comments

周末空余时间撸了一个敏感词过滤功能,下边记录下实现过程。

敏感词,一方面是你懂的,另一方面是我们自己可能也要过滤一些人身攻击或者广告信息等,具体词库可以google下,有很多。

过滤敏感词,使用简单的循环str_replace是性能很低效的,还会随着词库的增加,性能指数下降,而且简单的替换,不能解决一些不是完全匹配的词。这时候就需要先构建一个字典树(trie),单纯的字典树占用空间较大,使用Double-Array Trie或者Ternary Search Tree可以在保证性能的同时节省一部分空间,但是敏感词基本不会很多,几千甚至上万个词基本没压力,所以就实现就选择先构建一个字典树,然后逐字做匹配。

代码不多,就贴到这里。

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
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
<?php

class SensitiveWordFilter
{
    private $dict;
    private $dictPath;

    public function __construct($dictPath)
    {
        $this->dict = array();
        $this->dictPath = $dictPath;
        $this->initDict();
    }

    private function initDict()
    {
        $handle = fopen($this->dictPath, 'r');
        if (!$handle) {
            throw new RuntimeException('open dictionary file error.');
        }

        while (!feof($handle)) {
            $word = trim(fgets($handle, 128));

            if (empty($word)) {
                continue;
            }

            $uWord = $this->unicodeSplit($word);

            $pdict = &$this->dict;

            $count = count($uWord);
            for ($i = 0; $i < $count; $i++) {
                if (!isset($pdict[$uWord[$i]])) {
                    $pdict[$uWord[$i]] = array();
                }
                $pdict = &$pdict[$uWord[$i]];
            }

            $pdict['end'] = true;
        }

        fclose($handle);
    }

    public function filter($str, $maxDistance = 5)
    {
        if ($maxDistance < 1) {
            $maxDistance = 1;
        }

        $uStr = $this->unicodeSplit($str);

        $count = count($uStr);

        for ($i = 0; $i < $count; $i++) {
            if (isset($this->dict[$uStr[$i]])) {
                $pdict = &$this->dict[$uStr[$i]];

                $matchIndexes = array();

                for ($j = $i + 1, $d = 0; $d < $maxDistance && $j < $count; $j++, $d++) {
                    if (isset($pdict[$uStr[$j]])) {
                        $matchIndexes[] = $j;
                        $pdict = &$pdict[$uStr[$j]];
                        $d = -1;
                    }
                }

                if (isset($pdict['end'])) {
                    $uStr[$i] = '*';
                    foreach ($matchIndexes as $k) {
                        if ($k - $i == 1) {
                            $i = $k;
                        }
                        $uStr[$k] = '*';
                    }
                }
            }
        }

        return implode($uStr);
    }

    public function unicodeSplit($str)
    {
        $str = strtolower($str);
        $ret = array();
        $len = strlen($str);
        for ($i = 0; $i < $len; $i++) {
            $c = ord($str[$i]);

            if ($c & 0x80) {
                if (($c & 0xf8) == 0xf0 && $len - $i >= 4) {
                    if ((ord($str[$i + 1]) & 0xc0) == 0x80 && (ord($str[$i + 2]) & 0xc0) == 0x80 && (ord($str[$i + 3]) & 0xc0) == 0x80) {
                        $uc = substr($str, $i, 4);
                        $ret[] = $uc;
                        $i += 3;
                    }
                } else if (($c & 0xf0) == 0xe0 && $len - $i >= 3) {
                    if ((ord($str[$i + 1]) & 0xc0) == 0x80 && (ord($str[$i + 2]) & 0xc0) == 0x80) {
                        $uc = substr($str, $i, 3);
                        $ret[] = $uc;
                        $i += 2;
                    }
                } else if (($c & 0xe0) == 0xc0 && $len - $i >= 2) {
                    if ((ord($str[$i + 1])  & 0xc0) == 0x80) {
                        $uc = substr($str, $i, 2);
                        $ret[] = $uc;
                        $i += 1;
                    }
                }
            } else {
                $ret[] = $str[$i];
            }
        }

        return $ret;
    }
}

使用方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php
require 'SensitiveWordFilter.php';

/*
初始化传入词库文件路径,词库文件每个词一个换行符。
如:
敏感1
敏感2

目前只支持UTF-8编码
*/
$filter = new SensitiveWordFilter(__DIR__ . '/sensitive_words.txt');

/*
第一个参数传入要过滤的字符串,第二个是匹配的字间距,
比如'枪支'是一个敏感词,想过滤'枪||||支'的时候,
就需要指定一个两个字的间距,可以根据情况设定,
超过指定间距就不会过滤。所有匹配的敏感词会被替换为'*'。
*/
$filter->filter('这是一个敏感词', 10);

性能没有具体详细的做测试,不过一般场景足够,主要是吃CPU,词库可以把生成好的字典JSON编码后存到Redis或者Memcached中,下次使用直接取出还原。

PHP写WEB的话,不是Daemon这种,所以构建的数据结构不能方便的驻留内存,相比来说,C、C++、Java等可能更合适,如果对性能要求苛刻,可以用其他语言写个服务。当然,如果非要使用PHP,也可以使用Swoole封装服务。

ELK使用笔记

| Comments

ELK即为Elasticsearch\Logstash\Kibana组合的简称,可以做日志收集,索引,查询,生成图表。

先说下我对这个组合的使用场景的理解:基本就是可以迅速查找,可以自定义图表来实时分析数据的趋势,然而并不是很适合数据统计和生成报表。比如收集WEB服务器日志,服务器应用逻辑的日志,可以迅速查找和定位问题,包括一些简单计数,可以通过自定义图表(柱状图,饼图等)来直观的展现。

Elasticsearch

这个是做数据存储和索引的,Java实现,是基于Lucence开发的,部署和简单,不管是单机还是集群。部署完成后可以安装一个head插件,可以通过WEB页面来查看和管理,部署WEB访问建议绑定到内网,然后通过nginx反代,因为nginx可以方便做一些安全访问限制和配置Virtual Host。对服务器的要求就是尽量大的内存和磁盘,然后配置选项把JVM的内存调大些,不过这样启动会很慢,本人不了解Java,不知道是否可以优化速度。

补充:Elasticsearch并不一定非要配合Kibana和Logstash来使用,很多公司用它来提供索引和搜索服务。

Kibana

这个是一个查询UI工具,用来访问Elastcisearch,它提供一些图表模板,可以自定义数据源和条件,来生成图表,本身的一些配置数据也是存贮在Elasticsearch中。Kibana3是全部都是静态文件,通过AJAX来请求ELashticsearch接口,需要配置nginx来访问。Kibana4是基于Node.js开发的,直接提供WEB服务,不过部署还是建议绑定内网IP,通过nginx反代来访问,和Elasticsearch一样。

Logstash

这个是做日志收集,指定数据源和数据格式,然后插入到Elasticsearch中。这个是Ruby开发的,这个我们并没有用,而是自己来写,更可控并且也不复杂,我们使用PHP来实现的,实现tail -f这种方式来实时获取日志信息,然后插入到Elasticsearch中,目前每天上百万数据没什么压力。Elashticsearch官方提供了PHP的库,通过Composer很方便的引入。需要注意的就是时间格式,@timestamp这个是特殊字段,Kibana用这个来做时间索引,这个字段不是int的时间戳,而是ISO8601格式,PHP使用date('c', $timestamp)来生成,不然会不识别,Kibana无法使用。在说下日志集中化收集,目前是日志写入到Beanstalk队列服务,然后统一写入日志。

最后

通过目前使用来看,还是比较方便,出问题迅速查找日志,实时的通过自定义图表来查看数据趋势,提高了很多工作效率。

Composer的简单使用介绍

| Comments

Composer很好的解决了PHP的包管理和依赖问题,只需要import一个文件,Composer中管理的所有库就会按需加载,而不需要每个import。

先介绍一下Composer的安装。Composer的官方地址:https://getcomposer.org/

安装

1
curl -sS https://getcomposer.org/installer | php

这样在当前目录下会下载一个composer.phar的文件,为了方便,我们可以重命名一下,并放到/usr/local/bin下,方便执行。

1
mv composer.phar /usr/local/bin/composer

使用

一般我习惯把Composer放在/opt下,所以先创建一个Composer的目录

1
mkdir /opt/composer

先下载一个KLogger,一个写日志的库,本人比较喜欢。

1
2
cd /opt/composer
composer require katzgrau/klogger:dev-master

这样,KLogger就被下载下来了,使用很简单,只需要引入一个文件。

1
2
3
4
5
6
<?php

require '/opt/composer/vendor/autoload.php';

$logger = new Katzgrau\KLogger\Logger(__DIR__.'/logs');
...

KLogger可以使用了,我们在下载一个别的库,Medoo:一个轻量级的数据库框架。同样的方式来下载。

1
2
cd /opt/composer
composer require catfan/Medoo

使用同理

1
2
3
4
5
6
7
8
9
<?php

require '/opt/composer/vendor/autoload.php';

$medoo = new medoo();

//继续使用KLogger
$logger = new Katzgrau\KLogger\Logger(__DIR__.'/logs');
...

使用很简单,只要引入了vendor/autoload.php,就可以自动引入用到库,的确很酷。

加速

如果发现composer执行起来很慢,可以考虑换个镜像。这里推荐一个日本的镜像:http://composer-proxy.jp/

执行

1
composer config -g -e

编辑后

1
2
3
4
5
6
7
8
9
10
11
{
    "config": {}
    ,
    "repositories": [
        { "packagist": false },
        {
            "type": "composer",
            "url": "http://composer-proxy.jp/proxy/packagist"
        }
    ]
}

如果发现composer长时间没反应,可以在执行时候加入-vvv选项,可以输出更多信息,方便查找问题。

1
composer -vvv require ...

最后

PHP的spl_autoload_register可以很方便做按需引入,当new一个类没发现的时候,就会触发,然后我们自己按照规则来处理引入。

一个简单的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php

class ClassAutoloader
{
    public function __construct()
    {
        spl_autoload_register(array($this, 'loader'));
    }

    public function loader($className)
    {
        $file = dirname(__FILE__) . '/' . $className . '.php';
        if (is_file($file)) {
            require $file;
        }
    }
}

$autoloader = new ClassAutoloader();

具体路径规则可以自己定义,可以参考PSR

Beanstalkd - 一个简单、高性能的消息队列系统

| Comments

消息队列适用于很多业务场景,比如发邮件、短信,这种业务逻辑需要放到后端异步处理,而不需要阻塞前端,消息队列就排上用场,前端负责把任务put到消息队列中,后端服务从消息队列get任务处理。这种也可以成为生产者/消费者模型,这种模型试用范围很广,包括写多线程逻辑也经常会用到,主线程负责生产任务,工作线程负责消费任务。利用消息队列,这种生产者/消费者模型可以很容易扩展,提高整个系统的吞吐率。

在介绍Beanstalkd之前先说下其他。目前市面上有N多消息队列系统,这里有个网站专门介绍:http://queues.io/,尝试过搭建RabbitMQ,使用Erlang实现的,配置略复杂,最主要是,开放N个端口,我希望所有端口都绑定到内网,但是其中一个端口google了N久都没有找到如何绑定到内网,都是0.0.0.0这种,当然采用iptable限制一下也可以,但是略纠结,也可能是本人不是很懂Erlang,也不是很明白他的分布式原理,总之最后放弃不准备用它。当然RabbitMQ很功能很完善,只是我不想用==,我只是想找一个轻量级\好维护的消息队列。Redis也可以当作队列来用,使用list,或者pub/sub,但是毕竟Redis不是专业做消息队列的。下面Beanstalkd登场。

Beanstalkd,使用纯c实现,安装\配置\使用都非常简单,支持持久化,但是不支持分布式,不过我感觉这个还好,可以启动多个实例,前端逻辑做下支持也没关系。

安装

去github下载源码,https://github.com/kr/beanstalkd,下载下来make即可。beanstalkd就是可执行文件,为了方便,我们可以把可执行文件拷贝到指定目录。

1
2
3
mkdir -p /usr/local/beanstalkd/bin
cp beanstalkd /usr/local/beanstalkd/bin
ln -sv /usr/local/beanstalkd/bin/beanstalkd /usr/local/bin/

配置

配置非常简单,不需要配置文件,只有几个参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
[looyao@vm ~]$ beanstalkd -h
Use: beanstalkd [OPTIONS]

Options:
 -b DIR   wal directory
 -f MS    fsync at most once every MS milliseconds (use -f0 for "always fsync")
 -F       never fsync (default)
 -l ADDR  listen on address (default is 0.0.0.0)
 -p PORT  listen on port (default is 11300)
 -u USER  become user and group
 -z BYTES set the maximum job size in bytes (default is 65535)
 -s BYTES set the size of each wal file (default is 10485760)
            (will be rounded up to a multiple of 512 bytes)
 -c       compact the binlog (default)
 -n       do not compact the binlog
 -v       show version information
 -V       increase verbosity
 -h       show this help

例子:

1
2
3
4
5
#绑定内网IP:10.10.0.100,端口11300,不持久化
((beanstalkd -l 10.10.0.100 -p 11300 &) &)

#绑定内网IP:10.10.0.100,端口11300,开启持久化,持久化文件保存在/data/beanstalkd/data,默认10兆一个文件,可以根据自己情况修改
beanstalkd -l 10.10.0.100 -p 11300 -b /data/beanstalkd/data -s 10485760

如果开启持久化,性能会下降好多,主要是磁盘I/O导致,每一次数据变化都会写磁盘。

使用

首先要理解下Beanstalk的几个概念。tube:队列,job:消息任务,job存放在tube中。在看下job的状态说明,https://github.com/kr/beanstalkd/blob/master/doc/protocol.txt

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
Here is a picture of the typical job lifecycle:


   put            reserve               delete
  -----> [READY] ---------> [RESERVED] --------> *poof*



Here is a picture with more possibilities:



   put with delay               release with delay
  ----------------> [DELAYED] <------------.
                        |                   |
                        | (time passes)     |
                        |                   |
   put                  v     reserve       |       delete
  -----------------> [READY] ---------> [RESERVED] --------> *poof*
                       ^  ^                |  |
                       |   \  release      |  |
                       |    `-------------'   |
                       |                      |
                       | kick                 |
                       |                      |
                       |       bury           |
                    [BURIED] <---------------'
                       |
                       |  delete
                        `--------> *poof*

这张图比较简单明了, DELAYED:当put时设置了延迟时间,在延迟时间内就是DELAYED状态,过了就自动进入READY状态。 READY:就是可以被消费的状态。 RESERVED:从READY中取任务,任务自动进入RESERVED,处理完成可以删除掉,如果需要重新放入队列可以手动release,则任务重新进入READY状态。补充:RESERVED是针对连接来的,当任务进入RESERVED状态是不能被其他连接(消费者)获取的,当连接断掉且没有被delete的任务会自动重新进入READY状态。 BURIED:这个状态可以理解为操作系统的废纸篓,消费者获取到任务后,可以将任务放到这里,后续可以delete或者kick重新把它置为READY状态。

还有一个比较重要的就是可以设置任务优先级,值越小越先被执行。

语言支持,https://github.com/kr/beanstalkd/wiki/client-libraries,基本上主流语言都有支持。

下边介绍下PHP使用Beanstalkd。

  • 下载pheanstalkd

https://github.com/pda/pheanstalk,最好通过composer下载,省心省力。

  • 几个例子
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php

require '/opt/composer/vendor/autoload.php';

$b = new Pheanstalk\Pheanstalk('127.0.0.1', 11300);

$b->useTube('firstTube');

$b->put('hello world . ' . time());

$b->watch('firstTube');
$b->ignore('default');

//如果无任务,这里会阻塞
$job = $b->reserve();

echo $job->getData() . "\n";

$b->delete($job);
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
<?php

require '/opt/composer/vendor/autoload.php';

$b = new Pheanstalk\Pheanstalk('127.0.0.1', 11300);

$b->useTube('firstTube');

$b->put('hello world . ' . time());

$b->watch('firstTube');
$b->ignore('default');

$job = $b->reserve();

echo $job->getData() . "\n";


$b->bury($job);

$job = $b->peekBuried();

//$b->kickJob($job);

$b->delete($job);
  • 状态查看

官方有一些介绍,https://github.com/kr/beanstalkd/wiki/Tools

最简单是使用telnet

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
telnet localhost 11300
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
list-tubes
OK 26
---
- default
- firstTube

stats-tube firstTube
OK 267
---
name: firstTube
current-jobs-urgent: 0
current-jobs-ready: 0
current-jobs-reserved: 0
current-jobs-delayed: 0
current-jobs-buried: 1
total-jobs: 1
current-using: 0
current-watching: 0
current-waiting: 0
cmd-delete: 0
cmd-pause-tube: 0
pause: 0
pause-time-left: 0

quit
Connection closed by foreign host.

最后,可以读一下FAQ,https://github.com/kr/beanstalkd/wiki/faq,很多疑问这里可能会有答案。

我们目前使用Beanstalk做集中统计日志收集,前端将统计数据放入队列,后端负责记录,解析挖掘,目前来看性能和稳定性都非常好。

Sudo配置

| Comments

1、什么是sudo?

sudo可以让普通用户获得root权限来执行操作,而不需要知道root密码。sudo时需要键入的密码是执行的用户密码。

root权限来执行操作,相对还是比较危险的(当然作为管理员还是要认真、细心,清楚的知道每个命令执行的后果如何),一般的操作,并不需要root权限,当需要root权限时,sudo即可。

当然我认为比较有用的是,ssh禁止root登陆来加固系统安全,管理员以普通用户登陆时,可以用sudo来提升权限,su也可以,但是su不好的地方在于需要知道root密码。

2、配置

下边以CentOS为例。

1)、首先以root登陆,增加一个普通用户。

如:

1
2
[root@vm ~]# useradd testuser
[root@vm ~]# passwd testuser

2)、配置sudo,执行visudo命令,找到%wheel ALL=(ALL) ALL,去掉前边注释。

1
2
## Allows people in group wheel to run all commands
%wheel    ALL=(ALL)   ALL

3)、将testuser加入到wheel组

1
[root@vm ~]# usermod -aG wheel testuser

4)、测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[root@vm ~]# su testuser
[testuser@vm root]$ groups
testuser wheel
[testuser@vm root]$ sudo whoami

We trust you have received the usual lecture from the local System
Administrator. It usually boils down to these three things:

    #1) Respect the privacy of others.
    #2) Think before you type.
    #3) With great power comes great responsibility.

[sudo] password for testuser:
root

这样,testuser就拥有sudo权限了。

sudo可以配置每个用户可以执行的命令,但是,拥有sudo权限后,这种限制只是君子协定,通过sudo依然可以修改配置,给予普通用户sudo权限,那么他就是管理员了,所以第一次输入sudo时,那些信息提示就是说的大概这个意思。

5)、sudo执行的PATH可能不全,需要自定义的话,可以加入一行到~/.bashrc文件中,如要继承当前用户的PATH环境变量:

1
echo "alias sudo='sudo env PATH=\$PATH'" >> ~/.bashrc

6)、sudo后保持ssh agent,执行visudo,找到Defaults env_keep配置段,下边加入一行

1
Defaults    env_keep += "SSH_AUTH_SOCK"

参考:

1、https://access.redhat.com/documentation/en-US/Red_Hat_Enterprise_Linux_OpenStack_Platform/2/html/Getting_Started_Guide/ch02s03.html

SSH安全加固

| Comments

1、修改默认22端口,改为一个高位未被占用的端口,如62375。

1
Port 62375

避免一些网络扫描器扫描到ssh默认22端口,进而进行下一步攻击。

2、不允许root登陆

1
PermitRootLogin no

使用其他用户登陆,登陆后使用sudo来获得root权限。这样攻击者首先要猜对用户名才能进行攻击,增加攻击难度。

3、不允许密码登陆

1
PasswordAuthentication no

使用公钥/私钥来进行登陆认证。防止暴力攻击破解密码。

4、使用fail2ban、denyhosts等,防止暴力破解攻击

可以将多次尝试登陆的攻击IP屏蔽。

5、设置IP白名单

如果条件允许,可以指定IP可以登陆,公司有固定IP最好,如果没有,可以使用动态域名,服务器要定时获取和修改IP白名单配置。

参考:

1、http://wiki.centos.org/zh/HowTos/Network/SecuringSSH

NGINX+PHP-FPM配置安全

| Comments

记录一下NGINX+PHP-FPM的安全配置问题

1、屏蔽NGINX版本显示,预防如果恰巧使用的NGINX版本爆出漏洞,很容易被攻击者扫描到进行针对攻击。

配置:在http配置内加入

1
server_tokens    off;

2、屏蔽.svn、.git等目录,如果线上代码中包含了这些目录且没有被屏蔽,很容易被别人利用,因为这里边包含了代码信息,可以还原代码文件,简单分析可能就知道哪里有可利用漏洞,进行攻击。乌云中也经常发布通过此方式攻击的漏洞。所以线上NGINX配置要过滤这些目录。

配置:在server配置内加入

1
2
3
4
5
6
7
8
location ~ /\.(svn|git) {
    return 404;
}

#如果没有特殊需求,也可以屏蔽所有隐藏文件或目录,这样.svn、.git、.htacsess等都会被过滤
location ~ /\. {
    return 404;
}

3、NGINX PHP-FPM配置中加入try_files处理404,如果没有特殊配置,如果访问一个不存在的php文件,会出现no input file specified,我们希望返回的是404状态提示。

配置:

1
2
3
4
5
6
location ~ \.php$ {
    try_files    $uri = 404;

    fastcgi_pass   127.0.0.1:9000;
    ...
}

这样也能防止几年前曝的漏洞,详见:http://www.80sec.com/nginx-securit.htmlhttp://www.laruence.com/2010/05/20/1495.html

4、禁止受保护的目录被外部访问,如PHP YII框架的protected目录

配置:

1
2
3
location ^~ /protected {
    return 404;
}

5、屏蔽HTTP响应头部的X-Powered-By,默认会显示PHP版本,防止漏洞扫描工具扫到有漏洞版本进行攻击

修改php.ini:

1
expose_php = Off

未完待续。

Vim: 使用Vundle安装NERDTree

| Comments

记录下使用Vundle安装NERDTree。

如果没有.vimrc.vim文件夹需要先创建下

1
2
touch ~/.vimrc
mkdir -p ~/.vim/bundle

安装Vundle,GitHub地址:https://github.com/gmarik/Vundle.vim

1
git clone https://github.com/gmarik/Vundle.vim.git ~/.vim/bundle/Vundle.vim

编辑.vimrc,加入

1
2
3
4
5
6
7
8
9
10
set nocompatible              " be iMproved, required
filetype off                  " required

set rtp+=~/.vim/bundle/Vundle.vim
call vundle#begin()

Bundle 'scrooloose/nerdtree' " 加入NERDTree

call vundle#end()            " required
filetype plugin indent on    " required

保存退出,并重新进入vim,执行:PluginInstall,等待安装,安装完成会有Done状态提示,这样NERDTree插件就安装上了,执行:NERDTree命令,侧栏文件树形目录就出来了。可以映射快捷键调出NERDTree,在.vimrc中加入:map <C-f> :NERDTree<CR>,这样就可以使用Ctrl+f来调出侧边栏。

NERDTree基本操作:

1
2
3
4
移动:hjkl
展开:o
切换窗口:Ctrl + w,多次执行或加方向键hjkl来切换
其他:在侧边栏输入'?'看,:P

最后,贴下我的.vimrc配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
set fencs=utf-8,gbk,latin1
set autoindent
set cindent
set ru
syntax on
set hlsearch
set ts=4
set sw=4
set expandtab 
set smartindent

set nocompatible              " be iMproved, required
filetype off                  " required

set rtp+=~/.vim/bundle/Vundle.vim
call vundle#begin()

Bundle 'scrooloose/nerdtree'

call vundle#end()            " required
filetype plugin indent on    " required

:map <C-f> :NERDTree<CR>