优雅地让docker,nginx,acme.sh一起工作

前阵子买了个配置不错的阿里云,先搭了个Minecraft上去,Minecraft也跑在Docker里,并加载了DynMap显示实时地图。为了日后好扩展,方便上一些比较好看的页面,先用一个nginx反代一下,顺便挂上HTTPS,证书当然要从Letsencrypt搞。

看了下现有的方案,感觉都不太好看,都需要独立维护一个Dockerfile,不能直接用Nginx mainline,刷新证书的时候需要先关闭服务器,而且acme.sh和nginx跑在同一个容器里。想了一下,打算让acme.sh和nginx分开跑,nginx把challenge内容转发给acme.sh,这样应该就妥了,惟一的不好的地方是需要写个crontab,每隔2个月重启一下nginx的容器。不过其他方案也有downtime,也算可以接受。

目录结构和配置文件:

/
    acme.sh  -  SSL证书存放和acme帐号信息,配置信息
    nginx  -  自定义nginx配置存放的地方
        conf.d  -  一些nginx全局配置
        sites-enabled - 各个domain设置
        sites-conf  -  domain之间共用的设置

nignx/conf.d/acme.sh.conf

upstream acme-sh {
  server acme-sh:80;
}

nignx/conf.d/enabled-sites.conf

include /etc/nginx/sites-enabled/*.conf;

nginx/sites-conf/acme.sh.conf

location ~ "^/\.well-known/acme-challenge/([-_a-zA-Z0-9]+)$" {
    default_type text/plain;
    proxy_read_timeout    60;
    proxy_connect_timeout 60;
    proxy_redirect        off;
    proxy_pass http://acme-sh;

    proxy_set_header      X-Real-IP $remote_addr;
    proxy_set_header      X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header      X-Forwarded-Proto $scheme;
    proxy_set_header      Host $host;
}

nginx/sites-enabled/example.com.conf

server {
    server_name example.com;

    listen 0.0.0.0:443 ssl;
    listen [::]:443 ssl;

    ssl on;
    ssl_certificate /etc/ssl/letsencrypt/example.com/fullchain.cer;
    ssl_certificate_key /etc/ssl/letsencrypt/example.com/example.com.key;
    include sites-conf/acme.sh.conf;

    ...
}

server {
    server_name example.com;

    listen 0.0.0.0:80;
    listen [::]:80;

    include sites-conf/acme.sh.conf;

    location / {
        return 301 https://example.com$request_uri;
    }
}

第一次要先获取一个有效证书:

docker run --rm  -it  \
  -v "$(pwd)/acme.sh":/acme.sh  \
  --net=host \
  neilpang/acme.sh  --issue -d example.com  --standalone

使用docker-compose做容器管理,yml内容如下:

some-other-service:
    ...

nginx-srv:
    image: nginx:mainline
    volumes:
        - ./nginx/conf.d:/etc/nginx/conf.d
        - ./nginx/sites-conf:/etc/nginx/sites-conf
        - ./nginx/sites-enabled:/etc/nginx/sites-enabled
        - ./acme.sh:/etc/ssl/letsencrypt
    links:
        - some-other-service:upstream
        - acme-sh:acme-sh
    ports:
        - "443:443/tcp"
        - "80:80/tcp"

acme-sh:
    image: neilpang/acme.sh
    command: daemon
    volumes:
        - ./acme.sh:/acme.sh

调 docker-compose up就可以了。

最后加一个cronjob让nginx定期重新读取证书:

/var/spool/cron/root:

cd /path/to/docker-compose.yaml && /usr/bin/docker-compose exec nginx-srv nginx -s reload

 

记一次openwrt上折腾使用xkcptun,openvpn,ipset的策略路由

工作需求,需要加速外网访问,因此一个国外VPS服务器是必须的。由于外网条件限制,常常会有丢包,因此使用KCP进行加速可以极大的改善高丢包网络下的通讯速度,副作用是会重复发包放大流量,不过一般流量管够随便用,不需要担心。

首先要安装xkcptun:

基于Golang的kcptun无法很好的运行在MIPS环境上,因此使用C编写的xkcptun。

服务端:

我在repo中加入了可以build使用的Dockerfile,直接clone:

git clone https://github.com/ryncsn/xkcptun

之后直接进入目录,修改一下Dockerfile里面的参数,比如密码,端口地址等等,可以直接build了。这些参数应该单独拿出来,不过一时省事先不管了。

docker build -t xkcptun .

我一般习惯用docker-compose来管理:

xkcptun-ovpn:
  image: xkcptun
  net: "host"
  command: xkcp_server -c /etc/xkcptun.json -f
  restart: always
Openwrt(客户端):

直接从这里下载包安装:

https://github.com/gigibox/openwrt-xkcptun/releases

之后修改/etc/config/xkcptun,之后执行

/etc/init.d/xkcptun_client start
/etc/init.d/xkcptun_client enable

没研究UI,我的WNDR4300上只安装openwrt_xkcptun_0.4.409-1_ar71xx.ipk就够了。

此处挖坑

Openwrt-xkcptun release的build编译的有问题,用原repo的编译出来client和server无法连接,经过各种抓包和调试,发现是竟然是大小端的问题。应该重新编译一下修复大小端问题,考虑到Openwrt编译又要好久,就先在自己的repo里dirty hack了一下来兼容老的ar71xx build,因此这个repo编译出的xkcptun与其他的不兼容。

Openvpn配置:

直接使用docker-openvpn进行配置:

https://github.com/kylemanna/docker-openvpn

其中udp://VPN.SERVERNAME.COM换成vpn的地址,并且换成tcp协议,方便kcp进行包装。可以加上一个不存在的子域名前缀,方便之后添加hosts项来让kcp充当代理的作用。

CLIENTNAME是要链接的client name。

生成ovpn文件之后,将其拷到Openwrt设备上,并且在/etc/hosts中添加127.0.0.1 VPN.SERVERNAME.COM,让xkcp监听本地的1194转发到远端的1194,这样就可以让openvpn跑在kcp上面了。

策略路由:

为了完成策略路由,要先安装相关包:

opkg install iptables-mod-nat-extra ipset

opkg remove dnsmasq && opkg install dnsmasq-full

修改/etc/dnsmasq.conf,加入confdir=/etc/dnsmasq.d

建立/etc/dnsmasq.d,使用下面的脚本生成一个配置文件,命名dnsmasq_flist_ipset.conf,扔到/etc/dnsmasq.d/文件夹中:

https://github.com/cokebar/gfwlist2dnsmasq/blob/master/gfwlist2dnsmasq.sh

使用如下命令建立一个ipset list:

ipset -N flist iphash

防止DNS解析问题,先加入两个DNS服务器:

ipset add flist 8.8.8.8
ipset add flist 8.8.4.4

使用iptables做一下标记和允许转发,让标记包走特定路由表:

# iptables mark package matches the ipset
iptables -t mangle -A OUTPUT -j fwmark
iptables -t mangle -A fwmark -m set --match-set flist dst -j MARK --set-mark 0xffff

# Masquirade the package as routing is done before iptables mangle
iptables -t nat -A POSTROUTING -m mark --mark 0xffff -j MASQUERADE

# Route all package using ovpn table
ip rule add fwmark 0xffff table ovpn

# And allow forwarding
iptables -I FORWARD 1 -o tun0 -j ACCEPT

在之前的ovpn文件中加入,实现自动恢复:

script-security 2

up '/bin/sh -c "ip route add default dev tun0 table ovpn"'

这样便可以实现vpn连接自动添加路由和允许iptables转发的功能了,大功告成。

将iptables和ipset相关命令放入/etc/firewall.user,iproute2相关放入/etc/rc.local即可完成自动恢复。

Python Hybrid Method

这两天有一个特殊需求,在一个Python class上实现一个method,可以被当作Class method调用,也可以被当作Instance的Method调用。看起来不是很Pythonic,一个函数有两个不同的行为,不过在特殊情况下确实很实用。

尝试对性能进行了一下优化,并没有用到太复杂的优化方法,所以只是相对性能可以接受。调用消耗大概是普通调用的一倍。

from functools import wraps
class hybridmethod(object):
    """
    High performance hybrid function wrapper
    """
    __slot__ = ['context', 'method', 'clsmethod']

    def __init__(self, func):
        self.clsmethod = self.method = wraps(func)(lambda *a, **kw: func(self.context, *a, **kw))

    def classmethod(self, func):
        """
        Function to call when calling with class
        """
        self.clsmethod = wraps(func)(lambda *a, **kw: func(self.context, *a, **kw))
        return self

    def __get__(self, instance, cls):
        if instance is None:
            self.context = cls
            return self.clsmethod
        self.context = instance
        return self.method

自己用到的场景:

class Model(object):
    @hybridmethod
    def from_dict(self, dict_):
        """
        Inflate the instance with dict
        """
        for k, v in dict_.items():
            setattr(self, k, v)
        return self

    @from_dict.classmethod
    def from_dict(cls, dict_):
        """
        Inflate the instance with dict
        """
        instance = cls()
        for k, v in dict_.items():
            setattr(instance, k, v)
        return instance

新技能GET – 修热水器

家里的燃气热水器坏了一阵子,本来打算打算趁五一电商活动买个新的换上。不过,本着电器不用到彻底报废就不打算坏的原则,决定自己修一下。

打开盖子前后还是有点犹豫,毕竟涉及天然气,水路和电路,万一搞出差错出问题就不好办了。不过一番七手八脚折腾打开之后,经过仔细端详感觉原理也挺简单。感觉主要是温度反馈控制和可靠性设计这两天比较考验厂家实力。

好了不装逼了,经过大致判断应该是水流传感器坏了。依据是:打开水龙头由于有旧的存水,所以随着新水源进入温度变化看显示面板是有变化的,不过就是不打火。而且是完全没有打火的痕迹,比如风扇预启动,打火装置充电之类的都没发生。

水流传感器样子看起来是一个管子上多了一个方形盒子的感觉。淘宝上20一个,快递要好几天才能到,那就愉快地拆传感器吧。不过家里正好水阀坏了,ORZ。但拆都拆了,决定带着水压尝试拆一下电路部分。(似乎有的水流传感器电路部分和密封的盖子是一起固定的,拆开就崩。而家里的水流传感器可能是分离设计的或者锈地太厉害结果密封部分粘住了)

打开之后发现果然,电路部分整个浸水了,而且都锈了。再一看发现这个水流传感器似乎安装有问题,塑料外壳上有两个突起用来卡住电路,但直接怼在的电路板上了,导致密封不严实。猜测这应该也是进水的原因。直接把电路拆下来之后对着传感器剩下部分观察了一下,看起来大概原理是密封部分里面一个水轮,末端是磁铁,正对着电路部分的一个霍尔元件,水流导致水轮转动就有信号了。霍尔元件直接三个引脚接出来。其中两个脚分别是+5V和GND,另一个是信号脚,+5V处串一个5K电阻。其中电阻锈地彻底废了,但还好霍尔原件完好。

尝试修复比较简单就不做更多监测了,用飞线大法重连接了一下电路板短路的地方,朽烂了的电阻从其他报废电器上拆一个过来。装回热水器,安全起见先关闭燃起阀门测试一下水流,热水器立马嗡嗡地跑起来了,稳如狗。

Gentoo Zsh 缺少 curses.so 的问题

长期以来一直快乐的使用着 oh-my-zsh 里的 zsh-navigation-tools plugin 来进行模糊查询,不知道哪次 Gentoo 升级之后这个插件不能用了,报错:
command not found: zcurses
网上简单查询发现这个是个 zsh 内置模块的指令,应该是编译的时候 curses.so 被跳过了。
检查一下ebuild,没有看到有相关的USE flag,尝试调整ncurse和zsh包的版本和USE flag均无果。
检查一下 zsh 代码中的 configure.ac,发现$LIB中没有加入 ncurses 相关的 lib,zsh 会编译的时候会跳过 ncurses 相关模块的编译。

临时解决方法:
1、本地建立一个 Portage Overlay – https://forums.gentoo.org/viewtopic-t-827407-start-0.html,按需求 copy & paste 相关 ebuild
2、src_configure 中加入一行 myconf += ( –enable-libs=-lncursesw )
3、emerge zsh

zsh-navigation-tools plugin 又欢乐的跑起来了。

Gentoo Tips

记录一下一些有用不好找的小知识:

忽略一些package自带的pre emerge check failed:

export I_KNOW_WHAT_I_AM_DOING=yes

 

建立Hardened Desktop Profile:

Gentoo 以前是有 Hardened Desktop 的Profile的,后来取消了。

使用的话可以自己建一个

mkdir -p  /etc/portage/make.profile && cd /etc/portage/make.profile

echo 5 > eapi

echo “gentoo:hardened/linux/amd64
gentoo:targets/desktop” > parent

echo “-abi_x86_32” > use.mask (启用multilib,不用的话跳过)

 

建立 Package Set:

将一堆包归为一个Set,方便安装和管理:

mkdir /etc/portage/sets && cd /etc/portage/sets

echo “<packages>” > <set name>

比如建立Android编译环境的包:

echo “media-gfx/pngcrush
app-arch/lz4
dev-python/lz4
sys-devel/bison
dev-python/buildutils
net-misc/curl
sys-devel/flex
dev-vcs/git
app-crypt/gnupg
dev-util/gperf
media-libs/audiofile
media-libs/alsa-lib
app-arch/unzip
dev-util/valgrind” > android

emerge -av @android

 

分步编译安装Package:

ebuild myebuild fetch (if you don’t have it in distfiles)

ebuild myebuild unpack (unpacked to /var/tmp/portage/packagename/something)

ebuild myebuild compil

ebuild myebuild install

ebuild myebuild qmerge

 

将package.use,package.keywords等建立为目录:

可以将这些文件用同名目录替代,portage会遍历目录下文件

 

忽略emerge过程中部分失败的包

emerge –keep-going

 

 

ESP8266当成Arduino使用

近期入了个ESP8266-12,发现可以直接当作MCU使用。

ESP8266 + 面包板 = 可以直接当作带Wifi的开发版来用,缺点就是IO太少,比较费电

(费电估计是我配置问题)

1、把引脚接到面包板上

2、CE,VCC,GPIO X接3.3v

3、GND,GPIO X接GND

4、用USB-TTL和TX,RX,GND相连。GND相连确保信号电平正常,注意ESP8266尽量使用外接电源,一般USB-TTL转接器(我用的PL2312)供电不够ESP8266用的,非常不稳定。

5、ESP8266加电时GPIO 2的电位决定了Boot Mode,接VCC的话即运行已烧录的程序,接地者进入烧录模式。

Arduino IDE:

git clone https://github.com/esp8266/Arduino/<Arduino Diretory>/libraries/hardware/esp8266

重起或启动Arduino IDE,硬件选Genertic ESP8266,下载模式可以换成QIO(DIO,QIO的D,Q分别代表Dual,Quard,双通,四通,四通下载会快一些,不工作可换回双通),其他参数还没动过。

下载方法和Arduino一样,在Arduino IDE里写写写,然后确保启动为烧录模式后下载即可,之后把GPIO 2接VCC,Reset或者重接电就可以拿着烧好的固件到处跑了,连着串口的话可以调试。

esp8266-arduino项目:https://github.com/esp8266/Arduino

 

使用Postfix,Dovecot,MySQL搭建的邮件服务器

前阵子和几位朋友一同建起了一个小社区网站,期间搭建了一个团队服务器用同步代码和收发邮件,搭建邮件服务还是头一回。现在现成的邮件服务器解决方案也是有的,不过还是自己搭建了一遍,熟悉了一下具体流程。这两天在自己的服务器上也部署了,大致记录下:

结构:

Postfix 提供 SMTP,submission 服务,并对请求进行过滤,转发。SMTP 是邮件投递的基础。Postfix 各种功能可以添加外部实现,比如这里会将 Virtual Mail 通过 LMTP 转给Dovecot,Email 信息查询部分由 MySQL 提供,通过 pypolicyd-spf 进行过滤,用户验证也由 Dovecot 完成。

Dovecot 负责 IMAP 等服务的请求和用户验证,管理虚拟用户的邮箱内容等,和Postfix通过 LMTP 链接。

使用 MySQL 存储邮箱用户的账户信息,MySQL是可选的存储方式之一。

为了安全起见所有链接均用了加密的方式,普通 STMP,IMAP 链接强制必须使用 STARTTLS 升级为安全链接,STMPS,IMAPS 链接和 HTTPS 一样一开始就为加密链接。

安装Postfix、Dovecot、MySQL、pypolicyd-spf

视不同发行版安装相应的包即可。

 

配置:

系统:

建立vmail用户,vmail文件夹

useradd -s /sbin/nologin -d /var/vmail -m -r -g mail vmail

chown vmail:mail /var/vmail

chmod o-rwx vmail /var/vmail

grep vmail /etc/passwd | awk -F : '{print $3}'

建立并获得的vmail的uid,稍后会用到。

也可以指定uid建立vmail用户:

useradd -s /sbin/nologin -d /var/vmail -m -r -u <uid> vmail

建立 aliases.db(如果不存在的话),Postfix会用到

touch /etc/alises

newaliases

MySQL:

初次开启的话记得运行一下mysql_secure_installation。

通过 mysql -u root -p 输入密码进入 Mysql,参考以下步骤建表,插入数据。

create user vmail@localhost identified by 'vmailpassword';

create database vmail;

grant select on vmail.* to vmail@localhost identified by 'vmailpassword';

use vmail;

create table domains (name char(30) not null primary key);

create table users (email char(64) not null primary key, password char(128) not null, domain char(30) not null, foreign key (domain) references domains(name));

create table aliases (source char(64) not null primary key, destination char(64) not null, foreign key (destination) references users(email));

insert into domains values('----.com');

insert into users values('----@----.com', ENCRYPT('--password--here--', CONCAT('$6$', SUBSTRING(SHA(RAND()), -16))),'----.com');

insert into aliases values ('--alias--@----.com', '----@----.com');

domains表存放virtual domian,user表存放可登陆的用户邮箱和密码。

Postfix 配置:

配置文件:

注意修改前备份,方便恢复和参考。主要修改以下参数:

设置服务器信息,有单个服务器的情况下设置比较简单,domain为服务器域名:

mydestination 不可与 virtual main domain 冲突,否则发往 Dovecot 的邮件会被 Postfix 截下来,一般会找不到用户而 Reject,如果正好有重名的用户的话应该会发往错误的用户。

mydestination = localhost
mydomain = <domain>
myorigin = <domain>
myhostname = <domain>
mynetworks = 127.0.0.0/8
mynetworks_style = host
relay_domains =

TLS证书设置:

smtpd_tls_cert_file = /etc/ssl/private/----crt----
smtpd_tls_key_file = /etc/ssl/private/----key----
smtpd_tls_CApath = /etc/ssl/certs
smtpd_use_tls = yes

SASL支持设置,设置为不兼容老旧客户端:

smtpd_sasl_type = dovecot
smtpd_sasl_path = private/auth
smtpd_sasl_auth_enable = yes
smtpd_sasl_security_options = noanonymous, noactive, nodictionary
smtpd_sasl_local_domain = $mydomain
broken_sasl_auth_clients = no

一些安全强化选项,使用高强度的加密算法,和启用TLS,以及设置空的readme文件夹等。

smtpd_tls_mandatory_exclude_ciphers = aNULL, eNULL, EXPORT, DES, RC4, MD5, PSK, aECDH, EDH-DSS-DES-CBC3-SHA, EDH-RSA-DES-CDC3-SHA, KRB5-DE5, CBC3-SHA
smtpd_tls_mandatory_ciphers = high
smtpd_tls_mandatory_protocols = !SSLv2, !SSLv3, TLSv1, TLSv1.1, TLSv1.2
smtpd_tls_exclude_ciphers = $smtpd_tls_mandatory_exclude_ciphers
smtpd_tls_ciphers = $smtpd_tls_mandatory_ciphers
smtpd_tls_protocols = $smtpd_tls_mandatory_protocols
smtpd_tls_auth_only = yes
smtpd_tls_dh1024_param_file = /etc/postfix/dh2048.pem
smtpd_tls_dh512_param_file = /etc/postfix/dh512.pem
smtpd_tls_eecdh_grade = ultra
smtpd_tls_security_level = may
smtpd_tls_loglevel = 1
smtpd_tls_received_header = yes

# outgoing
smtp_tls_mandatory_exclude_ciphers = aNULL, eNULL, EXPORT, DES, RC4, MD5, PSK, aECDH, EDH-DSS-DES-CBC3-SHA, EDH-RSA-DES-CDC3-SHA, KRB5-DE5, CBC3-SHA
smtp_tls_mandatory_ciphers = high
smtp_tls_mandatory_protocols = !SSLv2, !SSLv3, TLSv1, TLSv1.1, TLSv1.2
smtp_tls_exclude_ciphers = $smtp_tls_mandatory_exclude_ciphers
smtp_tls_ciphers = $smtp_tls_mandatory_ciphers
smtp_tls_protocols = $smtp_tls_mandatory_protocols
smtp_tls_security_level = may
smtp_tls_note_starttls_offer = yes
smtp_tls_loglevel = 1


tls_random_source = dev:/dev/urandom
tls_preempt_cipherlist = yes
# Log the hostname of a remote SMTP server that offers STARTTLS, when TLS is not already enabled for that server. 

# No readme for more security
readme_directory = no
sample_directory = no

生成对应的文件,在Shell中执行以下语句:

openssl dhparam -out /etc/postfix/dh2048.pem 2048
openssl dhparam -out /etc/postfix/dh512.pem 512

如果强制启用TLS的话,将上面的 *_level = may 改为 enforce。不过会导致和一些邮箱不兼容,比如126,163。

一些行为配置,酌情修改:

在找不到收信人的情况下返回永久错误,让发信服务器不再重试投递:

unknown_local_recipient_reject_code = 550

超时和错误次数限制:

# Time before log a delayed warning
delay_warning_time = 4h
# how long to keep message on queue before return as failed.
maximal_queue_lifetime = 7d
# max and min time in seconds between retries if connection failed
minimal_backoff_time = 1000s
maximal_backoff_time = 8000s
# how long to wait when servers connect before receiving rest of data
smtp_helo_timeout = 60s
# how many address can be used in one message.
# effective stopper to mass spammers, accidental copy in whole address list
# but may restrict intentional mail shots.
smtpd_recipient_limit = 16
# how many error before back off.
smtpd_soft_error_limit = 3
# how many max errors before blocking it.
smtpd_hard_error_limit = 12

配置和Dovecot的链接参数:

通过lmtp协议连接到dovecot,设置vmail的文件夹,用户等参数。

其中 uid 为 vmail 的 uid,gid 为 mail 的 gid,如果不是12的话酌情修改。(grep mail /etc/group)

virtual_transport = lmtp:unix:private/dovecot-lmtp
address_verify_virtual_transport = lmtp:unix:private/dovecot-lmtp
virtual_mailbox_domains = mysql:/etc/postfix/mysql-virtual-mailbox-domains.cf
virtual_mailbox_maps = mysql:/etc/postfix/mysql-virtual-mailbox-maps.cf
virtual_alias_maps = mysql:/etc/postfix/mysql-virtual-alias-maps.cf
virtual_mailbox_base = /var/vmail
virtual_uid_maps = static:998
virtual_gid_maps = static:12

其中一些位置可以写死,减少SQL调用,比如只有一个 virtual_mailbox_domain:

virtual_mailbox_domains = ----.com

其中需要三个SQL查询用的配置文件:

/etc/postfix/mysql-virtual-mailbox-domains.cf:

vim mysql-virtual-mailbox-domains.cf

user = vmail
password = vmailpassword
hosts = 127.0.0.1
dbname = vmail
query = SELECT 1 FROM domains WHERE name='%s'

mysql-virtual-mailbox-maps.cf:

vim /etc/postfix/mysql-virtual-mailbox-maps.cf

user = vmail
password = vmailpassword
hosts = 127.0.0.1
dbname = vmail
query = SELECT 1 FROM users WHERE email='%s'

/etc/postfix/mysql-virtual-alias-maps.cf:

vim /etc/postfix/mysql-virtual-alias-maps.cf

user = vmail
password = vmailpassword
hosts = 127.0.0.1
dbname = vmail
query = SELECT destination FROM aliases WHERE source='%s'

最后邮件发送/接受限制,阻止一些简单的垃圾/骚扰邮件:

强制 Client 发送 HELO,启用 REJECT DELAY 和下面的 client 中的 reject_unknown_client_hostname 过滤配合,只让有MX记录/通过验证的用户使用SMTP,禁用VRFY:

smtpd_helo_required = yes
smtpd_delay_reject = yes
disable_vrfy_command = yes

restriction 规则:

tpd_client_restrictions = reject_unauth_pipelining, permit_mynetworks, permit_sasl_authenticated, warn_if_reject, reject_plaintext_session, reject_unknown_client_hostname, permit
smtpd_relay_restrictions = reject_unauth_pipelining, permit_mynetworks, permit_sasl_authenticated, warn_if_reject, reject_non_fqdn_recipient, reject_unknown_recipient_domain, reject_unauth_destination, check_policy_service unix:private/policyd-spf, permit
smtpd_helo_restrictions = reject_unauth_pipelining, permit_mynetworks, permit_sasl_authenticated, warn_if_reject, reject_invalid_helo_hostname, reject_non_fqdn_helo_hostname, reject_unknown_helo_hostname, permit
smtpd_sender_restrictions = reject_unauth_pipelining, permit_mynetworks, permit_sasl_authenticated, warn_if_reject, reject_non_fqdn_sender, reject_unknown_sender_domain, reject_unverified_sender, permit
smtpd_recipient_restrictions = reject_unauth_pipelining, permit_mynetworks, permit_sasl_authenticated, warn_if_reject, reject_non_fqdn_recipient, reject_unknown_recipient_domain, reject_unauth_destination, check_policy_service unix:private/policyd-spf, permit
smtpd_data_restrictions = reject_unauth_pipelining, permit_mynetworks, permit_sasl_authenticated, warn_if_reject, reject_multi_recipient_bounce, permit
smtpd_end_of_data_restrictions = reject_unauth_pipelining, permit_mynetworks, permit_sasl_authenticated, warn_if_reject, reject_multi_recipient_bounce, permit
smtpd_etrn_restrictions = reject

SPF设置:延长SPF验证超时时间:

policy_time_limit = 3600

 

编辑 /etc/postfix/master.cf 加入以下来开启SPF服务,配合上面的过滤:

policyd-spf  unix  -       n       n       -       0       spawn
  user=tpe argv=/usr/bin/policyd-spf /etc/policyd-spf/policyd-spf.conf

继续编辑 /etc/postfix/master.cf ,开启SMTP服务:

smtp      inet  n       -       n       -       -       smtpd
...

submission inet n       -       n       -       -       smtpd
-o syslog_name=postfix/submission
-o smtpd_tls_security_level=encrypt
-o smtpd_sasl_auth_enable=yes
....

smtps     inet  n       -       n       -       -       smtpd
-o syslog_name=postfix/smtps
-o smtpd_tls_wrappermode=yes
-o smtpd_sasl_auth_enable=yes
....

smpts工作于wraper模式,即不要STARTTLS,直接建立TLS链接,其他参数按照main.cf为默认不修改。

执行:

postfix set-permissions

service postfix start

启动 postfix。

 

Dovecot:

配置文件,修改前注意备份:

编辑 /etc/dovecot/dovecot.conf,开启 imap lmtp。 imap 提供外部访问,lmtp给postfix用。

protocols = imap lmtp

需要这一行来加载conf.d中的配置文件。

!include conf.d/*.conf

 

编辑 /etc/dovecot/conf.d/10-mail.conf,指定vmail的文件位置和UID,GID,同时限制Dovecot能使用的GID,UID。如果inbox没有启用的话,取消注释启用。

mail_location = maildir:/var/vmail/%d/%n

mail_uid = vmail
mail_gid = mail

first_valid_uid = 998
last_valid_uid = 998

first_valid_gid = 12
last_valid_gid = 12

 

编辑: /etc/dovecot/conf.d/10-auth.conf,设置验证:

disable_plaintext_auth = yes

auth_mechanisms = plain login

注释除了sql以外的验证途径,仅保留:

!include auth-sql.conf.ext

编辑 conf.d/auth-sql.conf.ext,配置SQL验证的参数:

密码通过MySQL验证,用户登陆后使用的GID,UID统一使用vmail,mail,文件位置统一为/var/mail/。

passdb {
driver = sql

# Path for SQL configuration file, see example-config/dovecot-sql.conf.ext
args = /etc/dovecot/dovecot-sql.conf.ext
}

userdb {
driver = static
args = uid=vmail gid=vmail home=/var/vmail/%d/%n
}

编辑 /etc/dovecot/dovecot-sql.conf.ext,配置查询语句:

driver = mysql

connect = host=localhost dbname=vmail user=vmail password=vmailpassword

default_pass_scheme = SHA512-CRYPT

password_query = \
SELECT email as user, password FROM users WHERE email='%u';

 

编辑 /etc/dovecot/conf.d/10-logging.conf,显示更多日志:

auth_verbose = yes

 

编辑 /etc/dovecot/conf.d/10-master.conf,配置 Dovecot 提供的各种服务参数:

LMTP 和 Auth 服务均以 unix socket 形式与 Postfix 链接。worker 进程以 dovecot 身份运行。socket的位置均放在 /var/spool/postfix/private/ 中:

default_internal_user = dovecot

service lmtp {
unix_listener /var/spool/postfix/private/dovecot-lmtp {
mode = 0666
}

# Create inet listener only if you can't use the above UNIX socket
#inet_listener lmtp {
# Avoid making LMTP visible for the entire internet
#address =
#port =
#}
}

service auth {
# auth_socket_path points to this userdb socket by default. It's typically
# used by dovecot-lda, doveadm, possibly imap process, etc. Users that have
# full permissions to this socket are able to get a list of all usernames and
# get the results of everyone's userdb lookups.
#
# The default 0666 mode allows anyone to connect to the socket, but the
# userdb lookups will succeed only if the userdb returns an "uid" field that
# matches the caller process's UID. Also if caller's uid or gid matches the
# socket's uid or gid the lookup succeeds. Anything else causes a failure.
#
# To give the caller full permissions to lookup all users, set the mode to
# something else than 0666 and Dovecot lets the kernel enforce the
# permissions (e.g. 0777 allows everyone full permissions).
unix_listener auth-userdb {
mode = 0666
#user =
#group =
}

# Postfix smtp-auth
unix_listener /var/spool/postfix/private/auth {
mode = 0666
}

# Auth process is run as this user.
user = $default_internal_user
}

default_internal_user = dovecot

service auth-worker {
# Auth worker process is run as root by default, so that it can access
# /etc/shadow. If this isn't necessary, the user should be changed to
# $default_internal_user.
user = $default_internal_user
}

 

编辑 /etc/dovecot/conf.d/10-ssl.conf,管理IMAP服务的链接安全性:

ssl = required

ssl_cert = </etc/ssl/private/<cert_file>
ssl_key = </etc/ssl/private/<key_file>

ssl_dh_parameters_length = 2048

ssl_protocols = !SSLv2 !SSLv3 TLSv1 TLSv1.1 TLSv1.2

ssl_cipher_list = DEFAULT:!EXPORT:!LOW:!MEDIUM:!MD5

ssl_prefer_server_ciphers = yes

其中将dh key长度设置为了2048,启动dovecot 后 dovecot 会去生成新的 ssl-parameters,如果设备性能不高的话会花费很长时间。如果ssl-parameters出现问题可以删除旧的 ssl-parameters (/var/lib/ssl-parameters.dat) 之后重启dovecot。

 

编辑 15-lda.conf,配置LMTP参数:

postmaster_address = postmaster@--domain.com--

hostname = --domain.com--

 

防火墙:

使用Iptables的话,需要打开以下端口:

参考设置(没启用POP3):

#imap/imaps
-A INPUT -p tcp -m tcp --dport 143 -m conntrack --ctstate NEW -j ACCEPT
-A INPUT -p tcp -m tcp --dport 993 -m conntrack --ctstate NEW -j ACCEPT
#smtp/smpts
-A INPUT -p tcp -m tcp --dport 25 -m conntrack --ctstate NEW -j ACCEPT
-A INPUT -p tcp -m tcp --dport 465 -m conntrack --ctstate NEW -j ACCEPT

重新加载iptables,postfix,dovecot,之后服务器应该就可以用了。

 

配置域名:

首先要有个域名,添加MX记录,SPF记录,MX记录用于在接收邮件的时候解析,这样其他邮件服务器可以知道给你投递邮件的话投递到什么地方,SPF记录用来判断一个IP是否有权利以此域名的名义发送邮件,防止别人冒充你。

MX记录为服务器IP即可。

TXT记录可以参考:

v=spf1 mx ~all

意为允许MX记录中的IP地址使用你的域名发信,其他地址均不能以你的名义发信。

 

完成:

邮件服务器建好了,启动服务,之后可以用Thunderbird之类的客户端登陆。

 

TODO:

反垃圾,Docker,性能,Header checker

 

在 Raspberry Pi 2 上安装 Hardened Gentoo

一段时间以前入了树梅派,想当作日后的主力服务器使用,尝试配置一个好用的服务器,而且最近比较热衷于 hardened 系统,可以提供额外的安全。

配置参考:https://wiki.gentoo.org/wiki/Raspberry_Pi

首先应该配置sd卡分区,我之前用的raspbian,打算直接用旧的分区,不动boot分区,只对root分区(mount在/mnt/ext4上)进行修改。

首先下来最新的stage3,校验,解压:

wget http://distfiles.gentoo.org/experimental/arm/hardened/stage3-armv7a_hardfp-hardened-201xxxxx.tar.bz2

wget http://distfiles.gentoo.org/experimental/arm/hardened/stage3-armv7a_hardfp-hardened-201xxxxx.DIGESTS

和 DIGESTS 对比核对下各种校验值对不对,之后解压到sd卡根目录(/mnt/ext4):

tar xvf stage3-armv7a_hardfp-hardened-201xxxxx.tar.bz2 -C /mnt/ext4/

由于正在PC上使用gentoo,直接使用cp /usr/portage ./usr/ 复制portage tree。注意先清理一下distfiles。

cp /usr/portage /mnt/ext4/usr/portage -r

如果没有现成的Portage Tree下载解压一个即可。

 

Linux Kernel and Grsecurity Patch:

配置Toolchain:

Gentoo 中可以很方便地使用 crossdev 配置:

crossdev -S -v -t armv7a-hardfloat-linux-gnueabi

或者其他方式准备工具链,之后设置环境变量:

export ARCH=arm

export CROSS_COMPILE=armv7a-hardfloat-linux-gnueabi-

export INSTALL_MOD_PATH=/mnt/ext4

export INSTALL_HDR_PATH=/mnt/ext4

export KERNEL=kernel7

准备内核代码:

git clone git://github.com/raspberrypi/linux.git

如果已有现成的Linux git repo ,而且不介意加入更多remote的话,可以通过

git remote add rasp git://github.com/raspberrypi/linux.git

git fetch rasp

更快地获得代码。

应用Grsecurity补丁,使用branch rpi-4.3.y的代码。

git checkout rasp/rpi-4.3.y

wget https://grsecurity.net/test/grsecurity-3.1-4.3.3-201xxxxxxxxx.patch

patch -p1 -s < grsecurity-3.1-4.3.3-201xxxxxxxxx.patch

有Reject,一般比较好解决,手动解决即可。

写了个小Patch,修复驱动的小问题:

diff --git a/drivers/misc/vc04_services/interface/vchiq_arm/vchiq_shim.c b/drivers/misc/vc04_services/interface/vchiq_arm/vchiq_shim.c
index 8072ff6..74c3af3 100644
--- a/drivers/misc/vc04_services/interface/vchiq_arm/vchiq_shim.c
+++ b/drivers/misc/vc04_services/interface/vchiq_arm/vchiq_shim.c
@@ -39,8 +39,6 @@

#include "vchiq_util.h"

-#include <stddef.h>
-
#define vchiq_status_to_vchi(status) ((int32_t)status)

typedef struct {
diff --git a/drivers/net/wireless/rtl8192cu/include/hal_intf.h b/drivers/net/wireless/rtl8192cu/include/hal_intf.h
index cac4408..2c08180 100644
--- a/drivers/net/wireless/rtl8192cu/include/hal_intf.h
+++ b/drivers/net/wireless/rtl8192cu/include/hal_intf.h
@@ -231,7 +231,7 @@ struct hal_ops {

s32 (*c2h_handler)(_adapter *padapter, struct c2h_evt_hdr *c2h_evt);
c2h_id_filter c2h_id_filter_ccx;
-};
+} __no_const;

typedef        enum _RT_EEPROM_TYPE{
EEPROM_93C46,
diff --git a/drivers/net/wireless/rtl8192cu/include/rtw_io.h b/drivers/net/wireless/rtl8192cu/include/rtw_io.h
index daf342ac..1ef9295 100644
--- a/drivers/net/wireless/rtl8192cu/include/rtw_io.h
+++ b/drivers/net/wireless/rtl8192cu/include/rtw_io.h
@@ -152,7 +152,7 @@ struct _io_ops
void (*_read_port_cancel)(struct intf_hdl *pintfhdl);
void (*_write_port_cancel)(struct intf_hdl *pintfhdl);

-};
+} __no_const;

准备config,通过menuconfig开关一下Grsecurity的安全选项:

make bcm2709_defconfig

make menuconfig

按需修改,测试发现需要关闭以下Grsecurity相关标记,不然树梅派会不能启动。CONFIG_PAX_KERNEXEC,CONFIG_PAX_MEMORY_UDEREF,CONFIG_GRKERNSEC_RANDSTRUCT

注意:使用CONFIG_DEBUG_RODATA 会让 Grsecurity 的 RBAC 无法开启,开启会导致Kernel Panic。

编译内核,模块,Devtree。

make zImage modules dtbs

安装新内核:

make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- INSTALL_MOD_PATH=mnt/ext4 modules_install

make headers_install

cp mnt/fat32/$KERNEL.img mnt/fat32/$KERNEL-backup.img

scripts/mkknlimg arch/arm/boot/zImage mnt/fat32/$KERNEL.img

cp arch/arm/boot/dts/*.dtb mnt/fat32/

cp arch/arm/boot/dts/overlays/*.dtb* mnt/fat32/overlays/

cp arch/arm/boot/dts/overlays/README mnt/fat32/overlays/

sync

sudo umount mnt/fat32

sudo umount mnt/ext4

修改fstab,加上boot挂载点。

塞入SD可以开机了,其余参考Wiki配置即可。

 

相关连接:

树梅派官方内核编译向导:

https://www.raspberrypi.org/documentation/linux/kernel/building.md

Gentoo Wiki相关:

https://wiki.gentoo.org/wiki/Raspberry_Pi/Quick_Install_Guide

https://wiki.gentoo.org/wiki/Raspberry_Pi/Cross_building

已经打好补丁的Patch:

https://github.com/ryncsn/linux

由于GRSEC违反GPL2协议,应该不会再有更新了,默哀。

搭建自配置可用的IPv6网络

为了能有IPv6链接,在重新配置了系统之后又把以前做过的 Radvd 和 使用 HE 提供的 Tunnel Broker 的服务又配置了一遍:

首先要有一个 Hurrican Electric 的 Tunnel Broker 帐号,注册很方便,过程略。

内核要开启Tunnel支持,可见链接中的Wiki。

自用的配置脚本:

由于我用的ADSL拨号网络IP总是变化,所以不能用固定的文件去配置,需要一枚脚本:

https://github.com/ryncsn/he-ipv6-init-script/raw/master/hev6.sh

放到/usr/local/sbin/

wget https://raw.githubusercontent.com/ryncsn/he-ipv6-init-script/master/hev6.sh -O /usr/local/sbin/hev6-init

vim /usr/local/sbin/hev6-init

按照 Tunnel Broker 帐号和申请的 Tunnel 信息配置开头的各种变量,INTERFACE是 Tunnel 经过的的 Interface,需要能接受服务器发来的 6in4 (Protocal 41) 包,且有独立公网地址。ROUTE_INTERFACE是本地需要提供IPv6网络的接口,对我来说就是内网。

 

配置服务:

要让服务自启动。之后写一个服务资源文件:

Gentoo中:

vim /etc/init.d/hev6-init
#!/sbin/runscript
# Copyright 1999-2013 Gentoo Foundation
# Distributed under the terms of the GNU General Public License v2
# $Id$

description="Hurrican Electric Tunnel Broker"

command="/usr/local/sbin/hev6-init"

log_file="/var/log/hev6"
depend() {
        need net
        use logger dns
}

start() {
        ebegin "Starting DDNS on ${IFACE}"
        start-stop-daemon --start --background \
        --stdout $log_file --stderr $log_file \
        --exec $command
}

stop() {
        ebegin "Stopping HE v6 tunnel"
        $command clear
        eend $?
}

启用,启动服务。

rc-update add hev6-init default

service hev6-init start

Systemd版:

vim /etc/systemd/system/hev6.service
[Unit]
Description=Hurrican Electric IPv6 Tunnel Broker
After=syslog.target network.target remote-fs.target nss-lookup.target

[Service]
TimeoutSec=0
PIDFile=/run/hev6.pid
ExecStart=/usr/local/sbin/hev6-init
ExecStop=/usr/local/sbin/hev6-init clear
Restart=on-failure


[Install]
WantedBy=multi-user.target
systemctl enable hev6

systemctl start hev6

服务启动之后通过ping6 ipv6.google.com测试,无问题。

 

配置Radvd:

配置radvd,在要提供IPv6服务的接口上开启路由通知:

emerge radvd

vim /etc/radvd.conf

参考配置,酌情修改网段信息等:

interface eth0
{       
        AdvSendAdvert on;
        
        MinRtrAdvInterval 3;
        MaxRtrAdvInterval 10;
        
        AdvDefaultPreference low;               
        AdvHomeAgentFlag off;
        
        prefix 2001:470:ffff:ffff::/64
        {       
                AdvOnLink on;
                AdvAutonomous on;                                                                               
                AdvRouterAddr on;                                                                                       
        };
};

之后配置服务开机自起:

service radvd start

rc-update add radvd default

Gentoo中启动radvd 服务会自动开启ipv6 forwarding。

我的另一机器是 Centos 7,配置时需要手动在/etc/sysctl.con中加入:

net.ipv6.conf.default.forwarding=1
net.ipv6.conf.all.forwarding=1

 

配置ip6tables:

增加TUNNEL Chain,配置类似如下:

-A FORWARD -j TUNNEL

:TUNNEL - [0:0]
-A TUNNEL -s 2001:470:ffff:ffff::/64 -j ACCEPT
-A TUNNEL -d 2001:470:ffff:ffff::/64 -j ACCEPT
-A TUNNEL -s fe80::/10 -j ACCEPT
-A TUNNEL -d fe80::/10 -j ACCEPT
-A TUNNEL -j RETURN

之后在网络上接入其他设备,ping6 ipv6.google.com,测试无问题即可用。

 

相关链接:

Tunnel broker:

http://tunnelbroker.net/

Gentoo Wiki:

https://wiki.gentoo.org/wiki/IPv6_router_guide#Using_radvd