基于Lua+pcap实现流量的监控与过滤

by LauCyun Sep 14 23:40:32 1,196 views

在工作中,遇到了一个这样的用例,在每天例行扫描活动中,发现有些应用系统不定期的被扫挂,因为我们不是服务的制造者,没有办法在不同的系统里打印日志,所以我们就想用一个工具来获取特定服务的输入数据流。我们如果不在IDS上看应用的服务,可以直接针对服务所在服务位置,针对应用端口进行,有针对性的监听分析。

1 概述

Tshark、tcpdump和windump这些监听工具提供了比较丰富的命令行参数来监听流量数据。

wireshark、burpsuite这些工具也提供相应的lua、python脚本的机制用于去处理监听的流量数据。但有些场景我们不会使用体积这么大的工具,命令行方式的监听工具又不能加入更多数据处理逻辑,细化对数据的操作。

实际上我们可以自制一个小型的工具,做流量监听,是除了命令联合shell脚本、wireshark、suricata等插件开发的另一种形式。现在很多的监听工具都是基于pcap的,我们基于pcap底层开发一个监听工具。

pcap支持C、python两种开发方式,基于C和pcap库的开发效率比pyton的性能高,这样在高性能的场景python就不太适合,但是从开发效率角度看,用python开发比C又要快很多,毕竟用C开发工具,需要进行边编译,一是有技术门槛,另外的确维护相对比较麻烦。

鉴于以上的原因,既要性能相对够高一些,又能便于维护,在命令行这个粒度上,又可以嵌入自己数据处理逻辑,定制化自己的运行时序,因些,我们选择了C和LUA配合的这种方式。实施方面,就是用C来处理pcap的主件循环,接受pcap监听的buffer数据。然后,将监听的数据通过C与LUA之间的通信,将数据推送给LUA。

数据交给LUA之后,如何管理数据的复杂性就靠LUA的设计方式来解决,因为流量数据是文本流式的,程序原型就想到了pipeline管道的方式进行组织管理。

有了管道的方式,我们就可以在一个监听数据流上,叠加各种插件进地监听数据的处理,可以把复杂的业务,拆解成若干个小的插件处理单元,写作完成任务。

2 pcap的c语言实现

演示原型代码的C部分是很少的,主要的任务是获取buffer的数据,推送lua,代码如下:

#include <stdio.h> 
#include <stdlib.h>  
#include <string.h>
#include <time.h> 

#include <pcap.h> 
#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>
lua_State* L = NULL;

void getPacket(u_char * arg, const struct pcap_pkthdr * pkthdr, const u_char * packet) { 

  L = lua_open();
      luaL_openlibs(L);

  if (luaL_loadfile(L, "buffer.lua") || lua_pcall(L, 0,0,0))
      printf("Cannot run configuration file:%s", lua_tostring(L, -1));

  lua_getglobal(L, "buffer");

  lua_newtable(L); 
  int idx = 0;
  for (idx=1; idx < pkthdr->len; idx++) {
      lua_pushnumber(L, idx);  
      lua_pushnumber(L, packet[idx]);  
      lua_settable(L, -3);  
  }

  lua_pcall(L, 1,0,0);

  int * id = (int *)arg;  
  printf("id: %d\n", ++(*id));  
  printf("Packet length: %d\n", pkthdr->len);  
  printf("Number of bytes: %d\n", pkthdr->caplen);  
  printf("Recieved time: %s", ctime((const time_t *)&pkthdr->ts.tv_sec));   
  printf("\n\n");  
}  
  
int main()  
{  
  char errBuf[PCAP_ERRBUF_SIZE], * devStr;  
    
  /* get a device */  
  //devStr = pcap_lookupdev(errBuf);  
  devStr = "ens33";
    
  if(devStr) {  
    printf("success: device: %s\n", devStr);  
  } else {  
    printf("error: %s\n", errBuf);  
    exit(1);  
  }  
    
  /* open a device, wait until a packet arrives */  
  pcap_t * device = pcap_open_live(devStr, 65535, 1, 0, errBuf);  
    
  if(!device) {  
    printf("error: pcap_open_live(): %s\n", errBuf);  
    exit(1);  
  }  
    
  /* construct a filter */  
  struct bpf_program filter;  
  //pcap_compile(device, &filter, "src port 80", 1, 0);  
  pcap_compile(device, &filter, "dst port 80", 1, 0);  
  pcap_setfilter(device, &filter);  
    
  /* wait loop forever */  
  int id = 0;  
  pcap_loop(device, -1, getPacket, (u_char*)&id);  
  pcap_close(device);  
  
  return 0;  
}  

c部分将监听的流量buffer的数据,以数组的形式给lua,在lua中array其实就是一个table,我们在lua部分重组了一下数组数据,生成了一个字符串,代码如下:

buffer = function(tbl)
    local tmpstr=''
    for k,v in pairs(tbl) do
        tmpstr = tmpstr..string.char(v)
    end
    io.write(tmpstr,"\n")
end

编译c程序就靠下面的命令行,后期我们也可以生成一个makefile简化编译流程。

$ gcc watch.c -I/usr/include/lua5.1 -ldl -lm -llua5.1 -lpcap -o watch

为了方便 ,我们写了一个Makefile

LUALIB=-I/usr/include/lua5.1 -lpcap -ldl -lm -llua5.1 

.PHONY: all win linux

all:
        @echo Please do \'make PLATFORM\' where PLATFORM is one of these:
        @echo win linux

win:

linux: watch

watch : watch.c
         gcc $^ -o$@ $(LUALIB) 
clean:
        rm -f watch 

3 Lua与管道插件设计

为什么要使用管道插件的方式拆分和组织模块?以什么形式传送数据变成了一个手艺,解耦最直接的方法是分层,先把数据与为业分开,再把业务代码和共通代码分开。数据层对我们系统来说就是规则,系统使用的共通代码都封装到了框架层,而系统功能业务共通的部分,以插件为机能单位分开并建立联系。数据是面象用户的,框架是面向插件开发者的, 插件的实现就是机能担当要做的事情,不同的插件组合相对便捷的生成新机能,也是插件便利的益处与存在的意义。

因为管道中的插件是会被顺序调用的,因此插件模板中的initaction函数也会被正常的回调,而这些回调函数在被调用时,管道系统会把流数据push给单元插件,而接到数据流的插件在接到回调push过来的数据后,进行相应的判断筛选,将编辑后的数据通过sink插槽push给后面的插件,直到管道尾端的插件报警或是记日志,一次管道启动运行的时序就结束了。

图1 pipeline示意图

我们用代码说明管道的实现更直观,代码如下:

local pipeline = require "pipeline"
local status = pipeline:new {
    require"plugin.source_plugin",
    require"plugin.filter_plugin",
}
return pipeline

字符型式的管道图示:

+---------------+     +-----------------+

| source-plugin |     |  filter-plugin  |

               src - sink              src 

+---------------+     +-----------------+ 

我们通过LUA特有的类组织方式构建了一个顺序的管道数据结构,管道中的插件是按声明的先后顺序来执行的。pipeline管道程序的主要逻辑就是管理回调函数的调用,代码如下:

local Pipeline = {}
local Pobj = {}


function Pipeline.output(self, list, flg)
    if flg == 0 then
        return 
    end

    for k,v in pairs(list) do
        print(k,v)
    end
end

function Pipeline.new(self, elements)
    self.element_list = elements
    self:output(elements, 0)
    return PObj
end

function Pipeline.run(self, pcapdata)
    local src = {
        metadata= { 
            data= pcapdata,
            request = {
                uri="https://www.laucyun.com"
            }
        }
    }
    for k,v in pairs(self.element_list) do
        v:init()
        v:push(src)
        local src, sink = v:match(pcapdata)
        if type(sink) == "table" then
            self:output(sink, 0)
        end
        src = sink
    end
end

return Pipeline

插件抽像出了几个特的函数给开发用户,时序是事先设计好的,最主要的数据和回调也明确了,主要是Pipeline.run统一回调了几个模板的函数,initpushmatch函数,这样顶层的设计几乎是固定的,之后所有的业务逻辑都在模板了,按这个时序执行,而插件之间的数据传递依靠的就srcsink这个插件。

基于管道插件的设计特点就是之前的插件会把源头的数据推送给后面的插件,如果管道中的数据在之前被编辑过,会体现在后面的插件接受数据后看见变化,具体的实现,代码如下:

local source_plugin = {}
local src = {
   args="source args"
}
local sink = {
    name = "source_plugin",
    ver = "0.1"
}
function source_plugin.output(self, list, flg)
    if flg == 0 then
        return
    end
    for k,v in pairs(list) do
        print(k,v)
    end
end
function source_plugin.push(self, stream) 
    for k,v in pairs(stream.metadata) do
        self.source[k]=v
    end 
end
function  source_plugin.init(self)
    self.source = src
    self.sink = sink
end
function source_plugin.action(self, stream) 
end
function  source_plugin.match(self, param)
    self.sink['found_flg']=false
    for kn,kv in pairs(self.source) do
         self.sink[kn] = kv
    end
    self.sink['metadata'] = { data=self.source['data'] }
    self:action(self.sink)
    return self.source, self.sink
end
return  source_plugin

source_plugin是一个典型的插件模板,所有被pipeline回调函数都一目了然,但对于插件的使用来说,可以完全不用关心内部细节,只关心一个函数就行了,就action(self, stream)这个函数,能提供的所有数据都已经被保存到stream这个数据结构里了,对监听的所有后期处理都从这里开始。如果创建一个新插件呢?就是复制源文件改一个名就行了,创建了一个filter_plugin的插件,代码如下:

local filter_plugin = {}
local src = {
   args="filter args"
}
local sink = {
    name = "filter_plugin",
    ver = "0.1"
}
function filter_plugin.output(self, list, flg)
    if flg == 0 then
        return
    end
    for k,v in pairs(list) do
        print(k,v)
    end
end
function filter_plugin.push(self, stream) 
    for k,v in pairs(stream.metadata) do
        self.source[k]=v
    end 
end
function  filter_plugin.init(self)
    self.source = src
    self.sink = sink
end
function filter_plugin.action(self, stream) 
    io.write(stream.data, "\n")
end
function  filter_plugin.match(self, param)
    self.sink['found_flg']=false
    for kn,kv in pairs(self.source) do
         self.sink[kn] = kv
    end
    self.sink['metadata'] = { data=self.source['data'] }
    self:action(self.sink)
    return self.source, self.sink
end
return  filter_plugin

生成了这个文件,我们在管理里加入这个插件就OK了,代码:

local pipeline = require "pipeline"
local status = pipeline:new {
    require"plugin.source_plugin",
    require"plugin.filter_plugin",
    require"plugin.syslog_plugin",
}
return pipeline

管道图示:

+---------------+     +-----------------+     +------------------+

| source-plugin |     |  filter-plugin  |     | syslog-plugin    |  

               src - sink              src - sink                ....

+---------------+     +-----------------+     +------------------+

我们在这个管道图示的后面,看到多了一个syslog-plugin的插件,这个插件追加的功能就是将前面插件处理的流数据,通过syslog协议,将数所存到远端的syslog服务器上,集中到大数据日志中心进行分析展示,这样这个程序就是一个简单的监控代理的模型。下面我们将程序过行起来,看一下执行的效果。

4 应用实例 

当你取得了流量数据后,理论上我们想干什么,由我们的想象力决定,在实际的应用场景中,我们像不深入一个应用的部分,就想得到这个应用的输入数据,比如这个应用是一个HTTP SEVER,Openrety服务,我们能不能通过启动这个监听程序,来取得对某个nginx服务的用户请求的agent数据呢,其实这个演示程序可以做到,我们构造一个简单的请求。

$ curl www.laucyun.com

然后我们,启动这个脚本程序:

$ ./watch


图2 演示请求laucyun.com的匹配结果

我们只是在filter_plugin这个lua插件中,对action()回调函数,添加了一个简单的处理,就捕获到了Host的信息含有laucyun.com的数据。

function filter_plugin.action(self, stream) 
    io.write(stream.data, "\n")
    local flg = string.find(stream.data, "laucyun.com")
    if flg then 
	print("###########[ OK ]#############")
    end
end

5 总结

实际上我们通过把流量数据转发给Lua,让Lua处理更高级的数据检索需求,在实际的工作中,有些应用的访问者会给出非正常的垃圾信息。如果某些应用输入脏数据,直接会造成程序崩溃,程序又不输出日志,这种机制的流量监听就会有应用场景了,比方说,我们进行大量的扫描行为了,会发出一些某些程序之前预料之外的数据,为了还原是具体那条扫描把程序弄挂了,我们就可以灵活的写一个lua插件,捕获脏数据。

这程序只是一个抛砖引玉,我们直接通过C加Lua的方式,灵感来自至Nginx + lua ,  就是现在流行的openresty服务器,又可以用到C的高性能,又使用Lua提高了后续处理的灵活性。如果要处理更大流量的单机流量监听,应该后续加入环形buffer缓存数据,如果直接将日志数据syslog到远端口的syslog服务上,我们就可以使lua开发一个插件,做syslog转发就好,这就是当时考虑使用lua管道设计做这个实验工具的目地。

源码:http://github.laucyun.com/luapcap

...

Tags Read More


自动化Python异常显示 - better-exceptions

by LauCyun Sep 12 22:09:55 933 views

Better Exceptions是最近一期的Python Weekly和Pycoders Weekly上都推荐的一个库,用处是展示更友好的异常信息。

无论在什么语言里遇到异常是很正常的,遇到线上异常也是常有的。本地异常的话通过pdb调试print输出关键信息是可行的,但是对于线上异常的话,只能从日志里查看,但日志里的信息可能只是提示你:KeyError: 'a',遇到这种问题,一般的做法是本地启动项目,尝试重现,这样你才能知道上下文是什么。但,往往很难复现,因为从日志里你看不到用户的输入是什么?如果你没有手动捕获异常,并把造成异常的数据写入log。

使用方法:

  • 通过pip安装better_exceptions
    $ pip install better_exceptions
  • 将它导入某处:
    import better_exceptions

栗子1:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

# import better_exceptions

def division(a, b):
    return a/b

print(division(1, 0))

运行结果如下:

$ python test.py
Traceback (most recent call last):
  File "test.py", line 11, in <module>
    print(division(1, 0))
  File "test.py", line 8, in division
    return a/b
ZeroDivisionError: integer division or modulo by zero

加上better_exceptions后,其运行结果如下:

$ python test.py
Traceback (most recent call last):
  File "test.py", line 11, in <module>
    print(division(1, 0))
          └ <function division at 0x00000000036566D8>
  File "test.py", line 8, in division
    return a/b
           │ └ 0
           └ 1
ZeroDivisionError: integer division or modulo by zero

从上面的应用场景描述,以及最终呈现的结果来看,似乎是有用的。那么better_exceptions是怎么做到的呢?

看一下better_exceptions的源码:

# better_exceptions/__init__.py

...

def write_stream(data):
    if SHOULD_ENCODE:
        data = _byte(data)

        if PY3:
            STREAM.buffer.write(data)
        else:
            STREAM.write(data)
    else:
        STREAM.write(data)


def format_exception(exc, value, tb):
    formatted, colored_source = format_traceback(tb)

    if not str(value) and exc is AssertionError:
        value.args = (colored_source,)
    title = traceback.format_exception_only(exc, value)

    full_trace = u'Traceback (most recent call last):\n{}{}\n'.format(formatted, title[0].strip())

    return full_trace


def excepthook(exc, value, tb):
    formatted = format_exception(exc, value, tb)
    write_stream(formatted)


sys.excepthook = excepthook

...

主要是用了sys模块的excepthook方法,这个方法的描述如下:

ref: https://docs.python.org/2/library/sys.html#sys.excepthook

This function prints out a given traceback and exception to sys.stderr.

When an exception is raised and uncaught, the interpreter calls sys.excepthook with three arguments, the exception class, exception instance, and a traceback object. In an interactive session this happens just before control is returned to the prompt; in a Python program this happens just before the program exits. The handling of such top-level exceptions can be customized by assigning another three-argument function to sys.excepthook.

大概意思就是,如果系统抛出一个未捕获的异常,那么解释器就会调用sys.excepthook方法,同时传递三个参数:异常类(ValueError或者KeyError之类的)、异常实例traceback对象

这意味着,你可以通过重写这个方法来处理系统未捕获的异常处理。但是对于DjangoTornado这样的Web框架,没啥用。为什么呢?

栗子2:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from django.http import HttpResponse
import better_exceptions


def test(request):
    print(request.GET['q'])
    if 'q' in request.GET:
        message = u'你搜索的内容为: ' + request.GET['q']
    else:
        message = u'你提交了空表单'
    return HttpResponse(message)

这是一个view方法,运行python manage.py runserver 0.0.0.0:8000后,我在浏览器中输入http://127.0.0.1:8000/test?a=111时,并没显示像栗子1中那样的exception提示。

这是因为框架中(DjangoTornado等)会自己处理异常,所以这种hook的方式不会被触发。

...

Tags Read More


如何免费的让网站启用HTTPS

by LauCyun Sep 12 17:39:48 1,345 views

在16年沃通事件之前,我一直用的是沃通的SSL证书,沃通事件之后我用的是腾讯云和第三方合作推出的免费一年的证书GeoTrust DV SSL,每个证书对应一个独立的域名(https://xx.abc.com),可以申请多个证书。今天为大家提供Let's Encrypt这个免费的解决方案。

很早之前我就在关注 Let's Encrypt 这个免费、自动化、开放的证书签发服务。Let's Encrypt 是一个于2015年推出的数字证书认证机构,将通过旨在消除当前手动创建和安装证书的复杂过程的自动化流程,为安全网站提供免费的SSL/TLS证书。这是由互联网安全研究小组(ISRG – Internet Security Research Group,一个公益组织)提供的服务。主要赞助商包括电子前哨基金会Mozilla基金会Akamai以及Cisco等公司(赞助商列表)。

好了,废话不多说了,开始吧!这篇文章也主要讲的是通过Let's Encrypt + Nginx来让网站升级到HTTPS。

1 环境

为你的网站来安装一个证书十分简单,只需要使用电子子前哨基金会EFF的Certbot,就可以完成。

在那个机器上图标下面,选择一下你用的中间件和操作系统。比如,我选的是:Nginx和Debian 9 (stretch),如图1。


图1 choose your server software and the system

2 获取免费证书

安装Certbot客户端

$ sudo apt-get install certbot

获取证书:

$ certbot certonly --webroot -w /var/www/example -d laucyun.com -d www.laucyun.com

这个命令会为http://laucyun.comhttp://www.laucyun.com这两个域名生成一个证书,使用--webroot模式会在/var/www/example中创建.well-known文件夹,这个文件夹里面包含了一些验证文件,certbot会通过访问http://laucyun.com/.well-known/acme-challenge来验证你的域名是否绑定的这个服务器。这个命令在大多数情况下都可以满足需求。

但是,有些时候我们的一些服务并没有根目录,这时候使用--webroot就走不通了。certbot还有另外一种模式--standalone, 这种模式不需要指定网站根目录,他会自动启用服务器的443端口,来验证域名的归属。我们有其他服务(例如Nginx)占用了443端口,就必须先停止这些服务,在证书生成完毕后,再启用。

$ certbot certonly --standalone -d laucyun.com -d www.laucyun.com

证书生成完毕后,我们可以在/etc/letsencrypt/live/目录下看到对应域名的文件夹,里面存放了指向证书的一些快捷方式。

  • 错误信息:
    Attempting to renew cert from /etc/letsencrypt/renewal/laucyun.com.conf produced an unexpected error: At least one of the required ports is already taken.. Skipping.
    这是因为我的http://laucyun.com生成证书的时候使用的是--standalone模式,验证域名的时候,需要启用443端口,这个错误的意思就是要启用的端口已经被占用了。 这时候我必须把Nginx先关掉,才可以成功。果然,我先运行service Nginx stop运行这个命令,就没有报错了,所有的证书都刷新成功。

这时候我们的第一生成证书已经完成了,接下来就是配置我们的web服务器,启用HTTPS。

3 Nginx配置启用HTTPS

到这里已经成功一大半了,只需要配置Nginx支持刚刚生成的证书。

配置可以参考:Mozilla SSL Configuration Generator,这是Mozilla搞得一个HTTPS配置文件自动生成器,支持Apache,Nginx等多种服务器。按照这个配置文件,选择Intermediate的兼容性。

这里生成的配置文件是业界最佳实践和结果,让Nginx打开了各种增加安全性和性能的参数:

server {
    listen 80 laucyun.com;
    listen [::]:80 laucyun.com;

    # Redirect all HTTP requests to HTTPS with a 301 Moved Permanently response.
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;

    # certs sent to the client in SERVER HELLO are concatenated in ssl_certificate
    ssl_certificate     /etc/letsencrypt/live/laucyun.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/laucyun.com/privkey.key;
    ssl_session_timeout 1d;
    ssl_session_cache shared:SSL:50m;
    ssl_session_tickets off;

    # Diffie-Hellman parameter for DHE ciphersuites, recommended 2048 bits
    ssl_dhparam /opt/ssl/nginx/dhparam.pem;

    # intermediate configuration. tweak to your needs.
    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
    ssl_ciphers 'ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS';
    ssl_prefer_server_ciphers on;

    # HSTS (ngx_http_headers_module is required) (15768000 seconds = 6 months)
    add_header Strict-Transport-Security max-age=15768000;

    # OCSP Stapling ---
    # fetch OCSP records from URL in ssl_certificate and cache them
    ssl_stapling on;
    ssl_stapling_verify on;

    ## verify chain of trust of OCSP response using Root CA and Intermediate certs
    ssl_trusted_certificate /etc/letsencrypt/live/laucyun.com/root_ca_cert_plus_intermediates;

    resolver <IP DNS resolver>;
     
    # your website configure
    ....
}

重点关注:

  • listen 443 ssl http2:作用是启用Nginx的http_v2_module模块支持HTTP2,Nginx版本需要高于1.9.5,HTTP2 具有更快的 HTTPS 传输性能,非常值得开启(关于性能你可以看一下这篇文章)。
  • ssl_certificatessl_certificate_key:分别对应/etc/letsencrypt/live/laucyun.com/fullchain.pem/etc/letsencrypt/live/laucyun.com/privkey.pem,这2个文件是之前就生成好的证书和密钥。
  • ssl_dhparam通过下面命令生成:
    $ sudo mkdir /opt/ssl/nginx/
    $ sudo openssl dhparam -out /opt/ssl/nginx/dhparam.pem 2048
  • ssl_trusted_certificate:需要下载Let's Encrypt的Root Certificates,不过根据Nginx官方文档所说,ssl_certificate如果已经包含了intermediates就不再需要提供ssl_trusted_certificate,这一步可以省略:
    $ cd /etc/letsencrypt/live/laucyun.com 
    $ sudo wget https://letsencrypt.org/certs/isrgrootx1.pem 
    $ sudo mv isrgrootx1.pem root.pem 
    $ sudo cat root.pem chain.pem > root_ca_cert_plus_intermediates
  • resolver:作用是resolve names of upstream servers into addresses。 在这个配置中,resolver是用来解析OCSP服务器的域名的,建议填写你的VPS提供商的DNS服务器。

Nginx配置完成后,重启后,用浏览器测试是否一切正常。

$ nginx -s reload

4 自动更新 SSL 证书

配置完这些过后,我们的工作还没有完成。 Let's Encrypt 提供的证书只有90天的有效期,我们必须在证书到期之前,重新获取这些证书,certbot给我们提供了一个很方便的命令,那就是certbot renew

所以,你还要设置上自动化的更新脚本,最容易的莫过于使用crontab了。

新建一个文件/home/certbot-auto-renew-cron,加入如下内容(每隔两个月的凌晨2:15执行更新操作):

15 2 * */2 * /usr/bin/certbot renew --pre-hook "/usr/sbin/service nginx stop" --post-hook "/usr/sbin/service nginx start"

说明:

  • crontab中有六个字段,其含义如下:
    • 第1个字段:分钟 (0-59)
    • 第2个字段:小时 (0-23)
    • 第3个字段:日期 (1-31)
    • 第4个字段:月份 (1-12 [12 代表 December])
    • 第5个字段:一周当中的某天 (0-7 [7 或 0 代表星期天])
    • 第6个字段:计划执行的脚本或命令的名称
  • --pre-hook:这个参数表示执行更新操作之前要做的事情,因为我有--standalone模式的证书,所以需要停止Nginx服务,解除端口占用。
  • --post-hook:这个参数表示执行更新操作完成后要做的事情,这里就恢复Nginx服务的启用

最后我们用crontab来启动这个定时任务:

$ crontab certbot-auto-renew-cron

最后,用专业在线工具Qualys SSL Labs测试你的服务器 SSL 安全性,它提供了全面的 SSL 安全性测试,填写你的网站域名(如图2),给自己的 HTTPS 配置打个分。


图2 SSL Server Test

至此,整个网站升级到HTTPS就完成了。 总结一下我们需要做什么:

  • 获取Let's Encrypt免费证书
  • 配置Nginx开启HTTPS
  • 定时刷新证书

参考文档:

...

Tags Read More


搭建Ngrok服务实现内网穿透

by LauCyun Sep 05 17:08:11 2,104 views

最近,在家里部署了几台服务器,然后在路由器中设置了外网到内网的端口映射表,来实现内网穿透。每重启路由器IP就变化,而且解析也巨慢,实在忍受不了,后来就发现了Ngrok神器,其原理如图1。用了一段时间后,发现国内访问该网站提供的服务相当不稳定,经常连接不上,出于什么原因,你懂得。虽然国内有不少第三方的ngrok服务提供,如natapp、花生壳,但不敢确定它们的稳定性。


图1 Ngrok原理图(来源:ngrok.com

好在ngrok是开源的,我们可以去Github上下载它的源码,在自己的外网服务器上搭建这样一个服务。

源码地址:https://github.com/inconshreveable/ngrok

下面,我们开始搭建ngrok服务。

1 环境

搭建ngrok服务需要有一台公网服务器及一个域名解析到外网服务器上。

环境:

  • 服务器:Ubuntu 16.04
  • 域   名:tunnel.cloud.laucyun.com

新建两个A记录,把域名解析到服务器上,用来关联ngrok服务。如:建立tunnel.cloud.laucyun.com*.tunnel.cloud.laucyun.com解析到服务器上。

2 编译环境

ngrok是基于go语言开发的,所以需要安装go语言开发环境,安装必要的工具如下:

$ sudo apt-get install build-essential golang mercurial git -y

安装完成之后,先检查go环境是否安装成功,执行go version看到如下信息,证明安装成功:

$ go version
go version go1.6.2 linux/amd64

创建/opt/ngrok/目录,然后下载ngrok源码:

$ mkdir -p /opt/ngrok/
$ sudo git clone https://github.com/inconshreveable/ngrok.git /opt/ngrok/

生成并替换源码里默认的证书,注意域名修改为你自己的。之后编译出来的服务端客户端会基于这个证书来加密通讯,保证了安全性。

NGROK_DOMAIN="tunnel.cloud.laucyun.com"

openssl genrsa -out rootCA.key 2048
openssl req -x509 -new -nodes -key rootCA.key -subj "/CN=$NGROK_DOMAIN" -days 5000 -out rootCA.pem
openssl genrsa -out device.key 2048
openssl req -new -key device.key -subj "/CN=$NGROK_DOMAIN" -out device.csr
openssl x509 -req -in device.csr -CA rootCA.pem -CAkey rootCA.key -CAcreateserial -out device.crt -days 5000

把生成的证书拷贝到相应的目录,如下:

$ cp rootCA.pem /opt/ngrok/assets/client/tls/ngrokroot.crt
$ cp device.crt /opt/ngrok/assets/client/tls/snakeoilca.crt
$ cp device.crt /opt/ngrok/assets/server/tls/snakeoil.crt
$ cp device.key /opt/ngrok/assets/server/tls/snakeoil.key

3 服务端

编译:

$ cd /opt/ngrok/ && make release-server

如果一切正常,/opt/ngrok/bin目录下应该有ngrokd可执行文件,ngrokd就是服务端程序了,指定域名和端口启动它:

$ sudo /opt/ngrok/bin/ngrokd -domain="tunnel.cloud.laucyun.com" -httpAddr=":80" -httpsAddr=":443" -tunnelAddr=":4443"

到这一步,ngrok服务已经跑起来了,可以通过屏幕上显示的日志查看更多信息。httpAddr、httpsAddr、tunnelAddr分别是ngrok用来转发http、https、隧道服务的端口,可以随意指定。如果你配置了iptables规则,需要放行这三个端口上的TCP协议。

现在,通过http://tunnel.cloud.laucyun.comhttps://tunnel.cloud.laucyun.com就可以访问到ngrok提供的转发服务。随便访问一个子域,可以看到这样一行提示:

Tunnel tunnel.cloud.laucyun.com not found

这说明万事俱备,只差客户端来连了。

4 客户端

编译:

$ cd /opt/ngrok/ && make release-client

如果一切正常,/opt/ngrok/bin目录下应该有ngrok可执行文件,ngrok就是客户端程序了。

写一个简单的配置文件,随意命名如ngrok.yml

server_addr: tunnel.cloud.laucyun.com:4443
trust_host_root_certs: false

指定子域、要转发的协议和端口,以及配置文件,运行客户端:

$ /opt/ngrok/bin/ngrok -config=/opt/ngrok/ngrok.yml -proto=tcp 22

如果出现以下界面,这说明已经成功连上远端服务了:


图2 客户端连接成功

好了现在就可以访问到内网服务器192.168.100.106的ssh服务了。

...

Tags Read More


如何用shadowsocks-go搭建自己的VPN服务器

by LauCyun Aug 29 17:55:19 5,523 views

Shadowsocks是一个安全的Socks代理,用于保护网络流量不被干扰,也是开源的项目,非常感谢作者@clowwindy。主要特性包括:

  • 快速(异步I/O和事件驱动程序)。
  • 安全(所有的流量都经过加密算法加密,支持自定义算法)。
  • 支持移动客户端(专为移动设备和无线网络优化)。
  • 跨平台(可运行于包括PC,Mac,手机(Android和iOS)和路由器(OpenWrt)在内的多种平台上)。
  • 使用Socks5协议和可自定义密码的工业级算法加密,流量在网络传输过程中不易被他人读取。
  • 开源。
  • 易于维护。

具体而言,Shadowsocks将原来ssh创建的Socks5协议拆开成Server端和Client端,两个端分别安装在境外服务器和境内设备上,其原理如下图:


(图片来源:互联网)

Client和Server之间可以通过多种方式加密,并要求提供密码确保链路的安全性。

本文介绍的是在国外VPS环境下搭建Shadowsocks服务器的方法,完成服务端配置后,运行本地端就可以无障碍的访问被墙的站点。Shadowsocks需要同时具备客户端和服务器端,所以它的部署也需要分两步。

1 安装Shadowsocks服务器端

最早我用的是shadowsocks,但是shadowsocks速度比较慢、资源占用也多,然后就用shadowsocks-libevshadowsocks-libev确实比Python版的速度快很多、内存占用也少了很多,但是shadowsocks-libev不支持多端口,所以现在用shadowsocks-go

shadowsocks-go的安装很简单,你可以下载二进制文件直接运行,也可以从源码安装。

1.1 安装shadowsocks-go

我选择从源码安装,首先你需要安装golang运行环境:

sudo apt-get install golang

然后配置GOPATH.bashrc添加:

export GOPATH=/usr/gopath
PATH = $PATH:$GOPATH

安装shadowsocks-go:

# on server
go get github.com/shadowsocks/shadowsocks-go/cmd/shadowsocks-server
# on client
go get github.com/shadowsocks/shadowsocks-go/cmd/shadowsocks-local

根据你设置的GOPATH可能需要root权限。

1.2 配置

多端口配置文件,具体参考shadowsocks-go

{
    "server":"0.0.0.0",
    "local_port":1080,
    "port_password":{
        "8838":"password1",
        "8839":"password2",
    },
    "timeout":600,
    "method":"aes-256-cfb"
}

1.3 运行

shadowsocks-server -c ss-config.json

你可以需要Supervisor之类的进程管理工具,帮你完成自动重启进程和开机自动启动等功能,当然你也可以自己写启动脚本。

2 安装Shadowsocks客户端

相比服务器端的安装,客户端的安装就简单了许多。

首先,根据操作系统下载相应的客户端。

然后,打开客户端,在「服务器设置」里新增服务器。然后依次填入服务器 IP、服务器端口、你设的密码和加密方式。

然后启用代理,就可以实现科学上网了。

3 Docker镜像

Dockerfile:Shadowsocks/shadowsocks-go/Dockerfile

下载Shadowsocks-go镜像:

docker pull laucyun/shadowsocks-go:1.2.1

这个Docker镜像是基于Busybox,整个镜像只有 9.71 MB大小,非常的轻便!

启动shadowsocks服务端:

docker run -d --name=ss -p 8388:8388 laucyun/shadowsocks-go:1.2.1 -p 8388 -k SS_SERVER_PASSWORD -m aes-256-cfb -t 600
# or
docker run -d --name=ss -p 8388:8388 laucyun/shadowsocks-go:1.2.1 -c /opt/shadowsocks/ss-config.json

如果你通过配置文件启动的话,你需要修改配置文件/opt/shadowsocks/ss-config.json

# copy ss-config.json from ss
docker cp ss:/opt/shadowsocks/ss-config.json ss-config.json
# after modification, copy ss-config.json back to ss
docker cp ss-config.json ss:/opt/shadowsocks/ss-config.json
# restart
docker restart ss

OK,一个属于自己的Shadowsocks服务器已经搭建好了!小伙子们,躁动起来吧~

...

Tags Read More