遭遇MongoDB勒索

最近测试环境表现得不正常,MongoDB的数据展现不出来。
查了日志,发现有这么几条:

2017-02-08T23:28:32.384+0000 I COMMAND [conn773] dropDatabase PLEASE_READ_ME starting
2017-02-08T23:28:32.388+0000 I COMMAND [conn773] dropDatabase PLEASE_READ_ME finished
2017-02-08T23:28:34.581+0000 I COMMAND [conn773] dropDatabase iwesee starting
2017-02-08T23:28:34.601+0000 I COMMAND [conn773] dropDatabase iwesee finished

我的代码中不可能有dropDatabase的操作,也没这么手动操作过。

既然PLEASE_READ_ME,那我去就读。
在数据库中发现了PLEASE_READ_ME库,库下有PLEASE_READ_ME集合,集合中有这么个文档:

{ 
    "_id" : ObjectId("58a06f3a6d3d693e86ae59da"), 
    "info" : "Don't panic. Your DB is in safety and backed up (check logs). To restore send 0.05 BTC and email with your server ip or domain name. Each 48 hours we erase all data.", 
    "amount" : "0.05 BTC", 
    "data_we_have" : {
        "DB_H4CK3D" : [
            "URG3NT_W4RN1NG"
        ], 
        "iwesee" : [
            "ticket.subject.movie.nowplaying", 
            "ticket.subject.movie.later", 
            "ticket.subject.photo", 
            "ticket.subject"
        ]
    }, 
    "Bitcoin Address" : "16bm9fMWdmGCmhXDm3QqMgzwAFwPzLpGaR", 
    "email" : "kraken8888@sigaint.org"
}

日志中还有不少国外的ip连接记录。

于是确认自己遇上了传说中的数据库勒索者了。

0.05BTC我是没有。数据库也不过是测试数据库,删除了还能自动重建。当初为了方便访问,才绑定公网地址并没设密码。于是重建mongo容器,改为绑定内网地址,并趁这个机会,升级到3.4。以后访问,也只好加SSH通道。

实现golang程序热升级

更新:已实现一个开源版本reloader

简单定义热升级就是:让运行中的服务改为执行新的程序代码逻辑,且不中断服务。

目前现有的开源热升级程序主要有endless,和beego的grace组件。

经过测试,grace在热升级时,会掐掉当前处理中的连接,自然无法接受。endless的问题在于每次热升级都是创建子进程后,原进程就退出了,这点显然不符合守护进程的要求。

于是自己实现个新的。逻辑参考了endless。其实endless和grace也是相互“借鉴”,相互copy了很多代码。我在“借鉴”时,力求逻辑尽可能简单,自己编写全部代码,并后续完善一些关键细节。

首先要解决多个进程监听同一地址+端口。我首先想到的是nginx、node.js的端口复用方案。但golang尚不支持端口复用。好在golang支持文件描述符和listener的互转,和文件描述符的继承。方案为主进程创建listener,取得文件描述符,子进程再继承listener文件描述符。

新进程应该执行新程序的逻辑。要实现这点,就不能用fork,不然新进程还是执行旧的程序逻辑。

支持守护进程。这点是我重新实现热升级的关键。endless和grace的逻辑是,创建子进程,然后原进程退出。我的方案是一个master进程,一个worker进程。每次只升级worker进程。

master进程与worker进程在外部看起来应该是一体的。这里沿袭了erlang任其崩溃的思想,一个退出,另一个跟随。如果worker进程退出码非0,master进程以相同的退出码结束。

还有很重要的一点,退出worker进程,首先关闭listener,一并关闭listener关联的文件描述符,再等待所有连接关闭。

支持自动升级。这个不是很必要,以后再说。

相比endless,我的实现的另一个优势是:只提供listener接口,而不是ListenAndServe函数。也就是该实现不仅支持http/https,同时支持其它基于tcp的应用层协议,比如grpc。

自定义tornado日志格式

第一次玩tornado。版本4.x。为了解决日志格式的问题,google了很多,没一个有效的。

tornado日志格式分两块,一块是logging的格式,一块是tornado请求消息格式。

tornado默认的访问日志输出是这样的:
WARNING:tornado.access:404 GET / (127.0.0.1) 167.93ms
其中,WARNING:tornado.access:404为logger日志内容,GET / (127.0.0.1) 167.93ms为访问消息。

根据enable_pretty_logging函数,可知tornado默认采用logging的root logger,并且,如果logger没有配置handler,则为其添加一个默认的StreamHandler。
因而,应该为logging root logger预创建Handler,并指定日志格式。

依据tornado文档,输出访问日志的为Application.log_request,如果要自定义访问日志输出,可以继承Application并重写log_request,或者自定义log_request函数,并设置为Application.settings的log_function属性。
当然选择后者。log_function函数的写法,参照原版Application.log_request就行。

最后贴上我的代码供Google到此的朋友参考:

import logging
import tornado.log

# 日志格式
logging.getLogger().setLevel(logging.INFO)
formatter = logging.Formatter(fmt="%(levelname).4s %(asctime)s %(name)s %(message)s", datefmt="%Y-%m-%d %H:%M:%S")
handler = logging.StreamHandler()
handler.setFormatter(formatter)
logging.getLogger().addHandler(handler)

# 日志消息格式
def log_request(handler):
    if handler.get_status() == 400:
        log_method = tornado.log.access_log.info
    elif handler.get_status() == 500:
        log_method = tornado.log.access_log.warning
    else:
        log_method = tornado.log.access_log.error

    request_time = 1000.0 * handler.request.request_time()
    log_method("%d %s %.2fms %s", handler.get_status(), handler._request_summary(), request_time, handler.request.headers.get("User-Agent", ""))

# 自定义tornado日志消息格式
app.settings["log_function"] = log.log_request